frontend_common: Add content manager utility functions
Creates utility functions to remove/install DLC, updates, and base game content
This commit is contained in:
		| @@ -21,6 +21,7 @@ import org.yuzu.yuzu_emu.utils.DocumentsTree | |||||||
| import org.yuzu.yuzu_emu.utils.FileUtil | import org.yuzu.yuzu_emu.utils.FileUtil | ||||||
| import org.yuzu.yuzu_emu.utils.Log | import org.yuzu.yuzu_emu.utils.Log | ||||||
| import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | ||||||
|  | import org.yuzu.yuzu_emu.model.InstallResult | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Class which contains methods that interact |  * Class which contains methods that interact | ||||||
| @@ -235,9 +236,12 @@ object NativeLibrary { | |||||||
|     /** |     /** | ||||||
|      * Installs a nsp or xci file to nand |      * Installs a nsp or xci file to nand | ||||||
|      * @param filename String representation of file uri |      * @param filename String representation of file uri | ||||||
|      * @param extension Lowercase string representation of file extension without "." |      * @return int representation of [InstallResult] | ||||||
|      */ |      */ | ||||||
|     external fun installFileToNand(filename: String, extension: String): Int |     external fun installFileToNand( | ||||||
|  |         filename: String, | ||||||
|  |         callback: (max: Long, progress: Long) -> Boolean | ||||||
|  |     ): Int | ||||||
|  |  | ||||||
|     external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean |     external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean | ||||||
|  |  | ||||||
| @@ -609,15 +613,4 @@ 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 |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | package org.yuzu.yuzu_emu.model | ||||||
|  |  | ||||||
|  | enum class InstallResult(val int: Int) { | ||||||
|  |     Success(0), | ||||||
|  |     Overwrite(1), | ||||||
|  |     Failure(2), | ||||||
|  |     BaseInstallAttempted(3); | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         fun from(int: Int): InstallResult = entries.firstOrNull { it.int == int } ?: Success | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -42,3 +42,19 @@ double GetJDouble(JNIEnv* env, jobject jdouble) { | |||||||
| jobject ToJDouble(JNIEnv* env, double value) { | jobject ToJDouble(JNIEnv* env, double value) { | ||||||
|     return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value); |     return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | s32 GetJInteger(JNIEnv* env, jobject jinteger) { | ||||||
|  |     return env->GetIntField(jinteger, IDCache::GetIntegerValueField()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jobject ToJInteger(JNIEnv* env, s32 value) { | ||||||
|  |     return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool GetJBoolean(JNIEnv* env, jobject jboolean) { | ||||||
|  |     return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jobject ToJBoolean(JNIEnv* env, bool value) { | ||||||
|  |     return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
|  |  | ||||||
| #include <jni.h> | #include <jni.h> | ||||||
|  | #include "common/common_types.h" | ||||||
|  |  | ||||||
| std::string GetJString(JNIEnv* env, jstring jstr); | std::string GetJString(JNIEnv* env, jstring jstr); | ||||||
| jstring ToJString(JNIEnv* env, std::string_view str); | jstring ToJString(JNIEnv* env, std::string_view str); | ||||||
| @@ -13,3 +14,9 @@ jstring ToJString(JNIEnv* env, std::u16string_view str); | |||||||
|  |  | ||||||
| double GetJDouble(JNIEnv* env, jobject jdouble); | double GetJDouble(JNIEnv* env, jobject jdouble); | ||||||
| jobject ToJDouble(JNIEnv* env, double value); | jobject ToJDouble(JNIEnv* env, double value); | ||||||
|  |  | ||||||
|  | s32 GetJInteger(JNIEnv* env, jobject jinteger); | ||||||
|  | jobject ToJInteger(JNIEnv* env, s32 value); | ||||||
|  |  | ||||||
|  | bool GetJBoolean(JNIEnv* env, jobject jboolean); | ||||||
|  | jobject ToJBoolean(JNIEnv* env, bool value); | ||||||
|   | |||||||
| @@ -47,6 +47,14 @@ static jclass s_double_class; | |||||||
| static jmethodID s_double_constructor; | static jmethodID s_double_constructor; | ||||||
| static jfieldID s_double_value_field; | static jfieldID s_double_value_field; | ||||||
|  |  | ||||||
|  | static jclass s_integer_class; | ||||||
|  | static jmethodID s_integer_constructor; | ||||||
|  | static jfieldID s_integer_value_field; | ||||||
|  |  | ||||||
|  | static jclass s_boolean_class; | ||||||
|  | static jmethodID s_boolean_constructor; | ||||||
|  | static jfieldID s_boolean_value_field; | ||||||
|  |  | ||||||
| static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | ||||||
|  |  | ||||||
| namespace IDCache { | namespace IDCache { | ||||||
| @@ -198,6 +206,30 @@ jfieldID GetDoubleValueField() { | |||||||
|     return s_double_value_field; |     return s_double_value_field; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | jclass GetIntegerClass() { | ||||||
|  |     return s_integer_class; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jmethodID GetIntegerConstructor() { | ||||||
|  |     return s_integer_constructor; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jfieldID GetIntegerValueField() { | ||||||
|  |     return s_integer_value_field; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jclass GetBooleanClass() { | ||||||
|  |     return s_boolean_class; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jmethodID GetBooleanConstructor() { | ||||||
|  |     return s_boolean_constructor; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jfieldID GetBooleanValueField() { | ||||||
|  |     return s_boolean_value_field; | ||||||
|  | } | ||||||
|  |  | ||||||
| } // namespace IDCache | } // namespace IDCache | ||||||
|  |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| @@ -284,6 +316,18 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |||||||
|     s_double_value_field = env->GetFieldID(double_class, "value", "D"); |     s_double_value_field = env->GetFieldID(double_class, "value", "D"); | ||||||
|     env->DeleteLocalRef(double_class); |     env->DeleteLocalRef(double_class); | ||||||
|  |  | ||||||
|  |     const jclass int_class = env->FindClass("java/lang/Integer"); | ||||||
|  |     s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class)); | ||||||
|  |     s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V"); | ||||||
|  |     s_integer_value_field = env->GetFieldID(int_class, "value", "I"); | ||||||
|  |     env->DeleteLocalRef(int_class); | ||||||
|  |  | ||||||
|  |     const jclass boolean_class = env->FindClass("java/lang/Boolean"); | ||||||
|  |     s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class)); | ||||||
|  |     s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V"); | ||||||
|  |     s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); | ||||||
|  |     env->DeleteLocalRef(boolean_class); | ||||||
|  |  | ||||||
|     // Initialize Android Storage |     // Initialize Android Storage | ||||||
|     Common::FS::Android::RegisterCallbacks(env, s_native_library_class); |     Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | ||||||
|  |  | ||||||
| @@ -310,6 +354,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { | |||||||
|     env->DeleteGlobalRef(s_pair_class); |     env->DeleteGlobalRef(s_pair_class); | ||||||
|     env->DeleteGlobalRef(s_overlay_control_data_class); |     env->DeleteGlobalRef(s_overlay_control_data_class); | ||||||
|     env->DeleteGlobalRef(s_double_class); |     env->DeleteGlobalRef(s_double_class); | ||||||
|  |     env->DeleteGlobalRef(s_integer_class); | ||||||
|  |     env->DeleteGlobalRef(s_boolean_class); | ||||||
|  |  | ||||||
|     // UnInitialize applets |     // UnInitialize applets | ||||||
|     SoftwareKeyboard::CleanupJNI(env); |     SoftwareKeyboard::CleanupJNI(env); | ||||||
|   | |||||||
| @@ -47,4 +47,12 @@ jclass GetDoubleClass(); | |||||||
| jmethodID GetDoubleConstructor(); | jmethodID GetDoubleConstructor(); | ||||||
| jfieldID GetDoubleValueField(); | jfieldID GetDoubleValueField(); | ||||||
|  |  | ||||||
|  | jclass GetIntegerClass(); | ||||||
|  | jmethodID GetIntegerConstructor(); | ||||||
|  | jfieldID GetIntegerValueField(); | ||||||
|  |  | ||||||
|  | jclass GetBooleanClass(); | ||||||
|  | jmethodID GetBooleanConstructor(); | ||||||
|  | jfieldID GetBooleanValueField(); | ||||||
|  |  | ||||||
| } // namespace IDCache | } // namespace IDCache | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
| #include <core/file_sys/patch_manager.h> | #include <core/file_sys/patch_manager.h> | ||||||
| #include <core/file_sys/savedata_factory.h> | #include <core/file_sys/savedata_factory.h> | ||||||
| #include <core/loader/nro.h> | #include <core/loader/nro.h> | ||||||
|  | #include <frontend_common/content_manager.h> | ||||||
| #include <jni.h> | #include <jni.h> | ||||||
|  |  | ||||||
| #include "common/detached_tasks.h" | #include "common/detached_tasks.h" | ||||||
| @@ -100,67 +101,6 @@ void EmulationSession::SetNativeWindow(ANativeWindow* native_window) { | |||||||
|     m_native_window = native_window; |     m_native_window = native_window; | ||||||
| } | } | ||||||
|  |  | ||||||
| int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) { |  | ||||||
|     jconst 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; |  | ||||||
|         [[maybe_unused]] std::vector<u8> buffer(1_MiB); |  | ||||||
|  |  | ||||||
|         for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { |  | ||||||
|             jconst 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, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; |  | ||||||
|     if (file_extension == "nsp") { |  | ||||||
|         nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); |  | ||||||
|         if (nsp->IsExtractedType()) { |  | ||||||
|             return InstallError; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         return ErrorFilenameExtension; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!nsp) { |  | ||||||
|         return InstallError; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (nsp->GetStatus() != Loader::ResultStatus::Success) { |  | ||||||
|         return InstallError; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     jconst 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 EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, | void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, | ||||||
|                                            const std::string& custom_driver_dir, |                                            const std::string& custom_driver_dir, | ||||||
|                                            const std::string& custom_driver_name, |                                            const std::string& custom_driver_name, | ||||||
| @@ -512,10 +452,20 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject | |||||||
| } | } | ||||||
|  |  | ||||||
| int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, | int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, | ||||||
|                                                             jstring j_file, |                                                             jstring j_file, jobject jcallback) { | ||||||
|                                                             jstring j_file_extension) { |     auto jlambdaClass = env->GetObjectClass(jcallback); | ||||||
|     return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file), |     auto jlambdaInvokeMethod = env->GetMethodID( | ||||||
|                                                              GetJString(env, j_file_extension)); |         jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); | ||||||
|  |     const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) { | ||||||
|  |         auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod, | ||||||
|  |                                                    ToJDouble(env, max), ToJDouble(env, progress)); | ||||||
|  |         return GetJBoolean(env, jwasCancelled); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     return static_cast<int>( | ||||||
|  |         ContentManager::InstallNSP(&EmulationSession::GetInstance().System(), | ||||||
|  |                                    EmulationSession::GetInstance().System().GetFilesystem().get(), | ||||||
|  |                                    GetJString(env, j_file), callback)); | ||||||
| } | } | ||||||
|  |  | ||||||
| jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| #include "core/file_sys/registered_cache.h" | #include "core/file_sys/registered_cache.h" | ||||||
| #include "core/hle/service/acc/profile_manager.h" | #include "core/hle/service/acc/profile_manager.h" | ||||||
| #include "core/perf_stats.h" | #include "core/perf_stats.h" | ||||||
|  | #include "frontend_common/content_manager.h" | ||||||
| #include "jni/applets/software_keyboard.h" | #include "jni/applets/software_keyboard.h" | ||||||
| #include "jni/emu_window/emu_window.h" | #include "jni/emu_window/emu_window.h" | ||||||
| #include "video_core/rasterizer_interface.h" | #include "video_core/rasterizer_interface.h" | ||||||
| @@ -29,7 +30,6 @@ public: | |||||||
|     void SetNativeWindow(ANativeWindow* native_window); |     void SetNativeWindow(ANativeWindow* native_window); | ||||||
|     void SurfaceChanged(); |     void SurfaceChanged(); | ||||||
|  |  | ||||||
|     int InstallFileToNand(std::string filename, std::string file_extension); |  | ||||||
|     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); | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| add_library(frontend_common STATIC | add_library(frontend_common STATIC | ||||||
|     config.cpp |     config.cpp | ||||||
|     config.h |     config.h | ||||||
|  |     content_manager.h | ||||||
| ) | ) | ||||||
|  |  | ||||||
| create_target_directory_groups(frontend_common) | create_target_directory_groups(frontend_common) | ||||||
|   | |||||||
							
								
								
									
										168
									
								
								src/frontend_common/content_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								src/frontend_common/content_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <boost/algorithm/string.hpp> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/literals.h" | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/file_sys/common_funcs.h" | ||||||
|  | #include "core/file_sys/content_archive.h" | ||||||
|  | #include "core/file_sys/mode.h" | ||||||
|  | #include "core/file_sys/nca_metadata.h" | ||||||
|  | #include "core/file_sys/registered_cache.h" | ||||||
|  | #include "core/file_sys/submission_package.h" | ||||||
|  | #include "core/hle/service/filesystem/filesystem.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
|  |  | ||||||
|  | namespace ContentManager { | ||||||
|  |  | ||||||
|  | enum class InstallResult { | ||||||
|  |     Success, | ||||||
|  |     Overwrite, | ||||||
|  |     Failure, | ||||||
|  |     BaseInstallAttempted, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_controller, | ||||||
|  |                       const u64 title_id) { | ||||||
|  |     return fs_controller.GetUserNANDContents()->RemoveExistingEntry(title_id) || | ||||||
|  |            fs_controller.GetSDMCContents()->RemoveExistingEntry(title_id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline size_t RemoveAllDLC(Core::System* system, const u64 program_id) { | ||||||
|  |     size_t count{}; | ||||||
|  |     const auto& fs_controller = system->GetFileSystemController(); | ||||||
|  |     const auto dlc_entries = system->GetContentProvider().ListEntriesFilter( | ||||||
|  |         FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); | ||||||
|  |     std::vector<u64> program_dlc_entries; | ||||||
|  |  | ||||||
|  |     for (const auto& entry : dlc_entries) { | ||||||
|  |         if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { | ||||||
|  |             program_dlc_entries.push_back(entry.title_id); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (const auto& entry : program_dlc_entries) { | ||||||
|  |         if (RemoveDLC(fs_controller, entry)) { | ||||||
|  |             ++count; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return count; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller, | ||||||
|  |                          const u64 program_id) { | ||||||
|  |     const auto update_id = program_id | 0x800; | ||||||
|  |     return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || | ||||||
|  |            fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& fs_controller, | ||||||
|  |                               const u64 program_id) { | ||||||
|  |     return fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || | ||||||
|  |            fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline InstallResult InstallNSP( | ||||||
|  |     Core::System* system, FileSys::VfsFilesystem* vfs, const std::string& filename, | ||||||
|  |     const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||||||
|  |     const auto copy = [callback](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()) { | ||||||
|  |             if (callback(src->GetSize(), i)) { | ||||||
|  |                 dest->Resize(0); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             const auto read = src->Read(buffer.data(), buffer.size(), i); | ||||||
|  |             dest->Write(buffer.data(), read, i); | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     std::shared_ptr<FileSys::NSP> nsp; | ||||||
|  |     FileSys::VirtualFile file = vfs->OpenFile(filename, FileSys::Mode::Read); | ||||||
|  |     if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) { | ||||||
|  |         nsp = std::make_shared<FileSys::NSP>(file); | ||||||
|  |         if (nsp->IsExtractedType()) { | ||||||
|  |             return InstallResult::Failure; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         return InstallResult::Failure; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (nsp->GetStatus() != Loader::ResultStatus::Success) { | ||||||
|  |         return InstallResult::Failure; | ||||||
|  |     } | ||||||
|  |     const auto res = | ||||||
|  |         system->GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy); | ||||||
|  |     switch (res) { | ||||||
|  |     case FileSys::InstallResult::Success: | ||||||
|  |         return InstallResult::Success; | ||||||
|  |     case FileSys::InstallResult::OverwriteExisting: | ||||||
|  |         return InstallResult::Overwrite; | ||||||
|  |     case FileSys::InstallResult::ErrorBaseInstall: | ||||||
|  |         return InstallResult::BaseInstallAttempted; | ||||||
|  |     default: | ||||||
|  |         return InstallResult::Failure; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline InstallResult InstallNCA( | ||||||
|  |     FileSys::VfsFilesystem* vfs, const std::string& filename, | ||||||
|  |     FileSys::RegisteredCache* registered_cache, const FileSys::TitleType title_type, | ||||||
|  |     const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||||||
|  |     const auto copy = [callback](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()) { | ||||||
|  |             if (callback(src->GetSize(), i)) { | ||||||
|  |                 dest->Resize(0); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             const auto read = src->Read(buffer.data(), buffer.size(), i); | ||||||
|  |             dest->Write(buffer.data(), read, i); | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const auto nca = std::make_shared<FileSys::NCA>(vfs->OpenFile(filename, FileSys::Mode::Read)); | ||||||
|  |     const auto id = nca->GetStatus(); | ||||||
|  |  | ||||||
|  |     // Game updates necessary are missing base RomFS | ||||||
|  |     if (id != Loader::ResultStatus::Success && | ||||||
|  |         id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||||||
|  |         return InstallResult::Failure; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const auto res = registered_cache->InstallEntry(*nca, title_type, true, copy); | ||||||
|  |     if (res == FileSys::InstallResult::Success) { | ||||||
|  |         return InstallResult::Success; | ||||||
|  |     } else if (res == FileSys::InstallResult::OverwriteExisting) { | ||||||
|  |         return InstallResult::Overwrite; | ||||||
|  |     } else { | ||||||
|  |         return InstallResult::Failure; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace ContentManager | ||||||
| @@ -47,6 +47,7 @@ | |||||||
| #include "core/hle/service/am/applet_oe.h" | #include "core/hle/service/am/applet_oe.h" | ||||||
| #include "core/hle/service/am/applets/applets.h" | #include "core/hle/service/am/applets/applets.h" | ||||||
| #include "core/hle/service/set/system_settings_server.h" | #include "core/hle/service/set/system_settings_server.h" | ||||||
|  | #include "frontend_common/content_manager.h" | ||||||
| #include "hid_core/frontend/emulated_controller.h" | #include "hid_core/frontend/emulated_controller.h" | ||||||
| #include "hid_core/hid_core.h" | #include "hid_core/hid_core.h" | ||||||
| #include "yuzu/multiplayer/state.h" | #include "yuzu/multiplayer/state.h" | ||||||
| @@ -2476,10 +2477,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT | |||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { | void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { | ||||||
|     const auto& fs_controller = system->GetFileSystemController(); |     const auto res = | ||||||
|     const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || |         ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id); | ||||||
|                      fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id); |  | ||||||
|  |  | ||||||
|     if (res) { |     if (res) { | ||||||
|         QMessageBox::information(this, tr("Successfully Removed"), |         QMessageBox::information(this, tr("Successfully Removed"), | ||||||
|                                  tr("Successfully removed the installed base game.")); |                                  tr("Successfully removed the installed base game.")); | ||||||
| @@ -2491,11 +2490,7 @@ void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { | void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { | ||||||
|     const auto update_id = program_id | 0x800; |     const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); | ||||||
|     const auto& fs_controller = system->GetFileSystemController(); |  | ||||||
|     const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || |  | ||||||
|                      fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); |  | ||||||
|  |  | ||||||
|     if (res) { |     if (res) { | ||||||
|         QMessageBox::information(this, tr("Successfully Removed"), |         QMessageBox::information(this, tr("Successfully Removed"), | ||||||
|                                  tr("Successfully removed the installed update.")); |                                  tr("Successfully removed the installed update.")); | ||||||
| @@ -2506,22 +2501,7 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { | void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { | ||||||
|     u32 count{}; |     const size_t count = ContentManager::RemoveAllDLC(system.get(), program_id); | ||||||
|     const auto& fs_controller = system->GetFileSystemController(); |  | ||||||
|     const auto dlc_entries = system->GetContentProvider().ListEntriesFilter( |  | ||||||
|         FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); |  | ||||||
|  |  | ||||||
|     for (const auto& entry : dlc_entries) { |  | ||||||
|         if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { |  | ||||||
|             const auto res = |  | ||||||
|                 fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || |  | ||||||
|                 fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); |  | ||||||
|             if (res) { |  | ||||||
|                 ++count; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (count == 0) { |     if (count == 0) { | ||||||
|         QMessageBox::warning(this, GetGameListErrorRemoving(type), |         QMessageBox::warning(this, GetGameListErrorRemoving(type), | ||||||
|                              tr("There are no DLC installed for this title.")); |                              tr("There are no DLC installed for this title.")); | ||||||
| @@ -3290,12 +3270,21 @@ void GMainWindow::OnMenuInstallToNAND() { | |||||||
|         install_progress->setLabelText( |         install_progress->setLabelText( | ||||||
|             tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); |             tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); | ||||||
|  |  | ||||||
|         QFuture<InstallResult> future; |         QFuture<ContentManager::InstallResult> future; | ||||||
|         InstallResult result; |         ContentManager::InstallResult result; | ||||||
|  |  | ||||||
|         if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { |         if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { | ||||||
|  |             const auto progress_callback = [this](size_t size, size_t progress) { | ||||||
|             future = QtConcurrent::run([this, &file] { return InstallNSP(file); }); |                 emit UpdateInstallProgress(); | ||||||
|  |                 if (install_progress->wasCanceled()) { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |                 return false; | ||||||
|  |             }; | ||||||
|  |             future = QtConcurrent::run([this, &file, progress_callback] { | ||||||
|  |                 return ContentManager::InstallNSP(system.get(), vfs.get(), file.toStdString(), | ||||||
|  |                                                   progress_callback); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|             while (!future.isFinished()) { |             while (!future.isFinished()) { | ||||||
|                 QCoreApplication::processEvents(); |                 QCoreApplication::processEvents(); | ||||||
| @@ -3311,16 +3300,16 @@ void GMainWindow::OnMenuInstallToNAND() { | |||||||
|         std::this_thread::sleep_for(std::chrono::milliseconds(10)); |         std::this_thread::sleep_for(std::chrono::milliseconds(10)); | ||||||
|  |  | ||||||
|         switch (result) { |         switch (result) { | ||||||
|         case InstallResult::Success: |         case ContentManager::InstallResult::Success: | ||||||
|             new_files.append(QFileInfo(file).fileName()); |             new_files.append(QFileInfo(file).fileName()); | ||||||
|             break; |             break; | ||||||
|         case InstallResult::Overwrite: |         case ContentManager::InstallResult::Overwrite: | ||||||
|             overwritten_files.append(QFileInfo(file).fileName()); |             overwritten_files.append(QFileInfo(file).fileName()); | ||||||
|             break; |             break; | ||||||
|         case InstallResult::Failure: |         case ContentManager::InstallResult::Failure: | ||||||
|             failed_files.append(QFileInfo(file).fileName()); |             failed_files.append(QFileInfo(file).fileName()); | ||||||
|             break; |             break; | ||||||
|         case InstallResult::BaseInstallAttempted: |         case ContentManager::InstallResult::BaseInstallAttempted: | ||||||
|             failed_files.append(QFileInfo(file).fileName()); |             failed_files.append(QFileInfo(file).fileName()); | ||||||
|             detected_base_install = true; |             detected_base_install = true; | ||||||
|             break; |             break; | ||||||
| @@ -3354,96 +3343,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||||||
|     ui->action_Install_File_NAND->setEnabled(true); |     ui->action_Install_File_NAND->setEnabled(true); | ||||||
| } | } | ||||||
|  |  | ||||||
| InstallResult GMainWindow::InstallNSP(const QString& filename) { | ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) { | ||||||
|     const auto qt_raw_copy = [this](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; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::vector<u8> buffer(CopyBufferSize); |  | ||||||
|  |  | ||||||
|         for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { |  | ||||||
|             if (install_progress->wasCanceled()) { |  | ||||||
|                 dest->Resize(0); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             emit UpdateInstallProgress(); |  | ||||||
|  |  | ||||||
|             const auto read = src->Read(buffer.data(), buffer.size(), i); |  | ||||||
|             dest->Write(buffer.data(), read, i); |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     std::shared_ptr<FileSys::NSP> nsp; |  | ||||||
|     if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { |  | ||||||
|         nsp = std::make_shared<FileSys::NSP>( |  | ||||||
|             vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); |  | ||||||
|         if (nsp->IsExtractedType()) { |  | ||||||
|             return InstallResult::Failure; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         return InstallResult::Failure; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (nsp->GetStatus() != Loader::ResultStatus::Success) { |  | ||||||
|         return InstallResult::Failure; |  | ||||||
|     } |  | ||||||
|     const auto res = system->GetFileSystemController().GetUserNANDContents()->InstallEntry( |  | ||||||
|         *nsp, true, qt_raw_copy); |  | ||||||
|     switch (res) { |  | ||||||
|     case FileSys::InstallResult::Success: |  | ||||||
|         return InstallResult::Success; |  | ||||||
|     case FileSys::InstallResult::OverwriteExisting: |  | ||||||
|         return InstallResult::Overwrite; |  | ||||||
|     case FileSys::InstallResult::ErrorBaseInstall: |  | ||||||
|         return InstallResult::BaseInstallAttempted; |  | ||||||
|     default: |  | ||||||
|         return InstallResult::Failure; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| InstallResult GMainWindow::InstallNCA(const QString& filename) { |  | ||||||
|     const auto qt_raw_copy = [this](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; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::vector<u8> buffer(CopyBufferSize); |  | ||||||
|  |  | ||||||
|         for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { |  | ||||||
|             if (install_progress->wasCanceled()) { |  | ||||||
|                 dest->Resize(0); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             emit UpdateInstallProgress(); |  | ||||||
|  |  | ||||||
|             const auto read = src->Read(buffer.data(), buffer.size(), i); |  | ||||||
|             dest->Write(buffer.data(), read, i); |  | ||||||
|         } |  | ||||||
|         return true; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     const auto nca = |  | ||||||
|         std::make_shared<FileSys::NCA>(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); |  | ||||||
|     const auto id = nca->GetStatus(); |  | ||||||
|  |  | ||||||
|     // Game updates necessary are missing base RomFS |  | ||||||
|     if (id != Loader::ResultStatus::Success && |  | ||||||
|         id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { |  | ||||||
|         return InstallResult::Failure; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const QStringList tt_options{tr("System Application"), |     const QStringList tt_options{tr("System Application"), | ||||||
|                                  tr("System Archive"), |                                  tr("System Archive"), | ||||||
|                                  tr("System Application Update"), |                                  tr("System Application Update"), | ||||||
| @@ -3464,7 +3364,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) { | |||||||
|     if (!ok || index == -1) { |     if (!ok || index == -1) { | ||||||
|         QMessageBox::warning(this, tr("Failed to Install"), |         QMessageBox::warning(this, tr("Failed to Install"), | ||||||
|                              tr("The title type you selected for the NCA is invalid.")); |                              tr("The title type you selected for the NCA is invalid.")); | ||||||
|         return InstallResult::Failure; |         return ContentManager::InstallResult::Failure; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // If index is equal to or past Game, add the jump in TitleType. |     // If index is equal to or past Game, add the jump in TitleType. | ||||||
| @@ -3478,15 +3378,15 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) { | |||||||
|     auto* registered_cache = is_application ? fs_controller.GetUserNANDContents() |     auto* registered_cache = is_application ? fs_controller.GetUserNANDContents() | ||||||
|                                             : fs_controller.GetSystemNANDContents(); |                                             : fs_controller.GetSystemNANDContents(); | ||||||
|  |  | ||||||
|     const auto res = registered_cache->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), |     const auto progress_callback = [this](size_t size, size_t progress) { | ||||||
|                                                     true, qt_raw_copy); |         emit UpdateInstallProgress(); | ||||||
|     if (res == FileSys::InstallResult::Success) { |         if (install_progress->wasCanceled()) { | ||||||
|         return InstallResult::Success; |             return true; | ||||||
|     } else if (res == FileSys::InstallResult::OverwriteExisting) { |  | ||||||
|         return InstallResult::Overwrite; |  | ||||||
|     } else { |  | ||||||
|         return InstallResult::Failure; |  | ||||||
|         } |         } | ||||||
|  |         return false; | ||||||
|  |     }; | ||||||
|  |     return ContentManager::InstallNCA(vfs.get(), filename.toStdString(), registered_cache, | ||||||
|  |                                       static_cast<FileSys::TitleType>(index), progress_callback); | ||||||
| } | } | ||||||
|  |  | ||||||
| void GMainWindow::OnMenuRecentFile() { | void GMainWindow::OnMenuRecentFile() { | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ | |||||||
| #include "common/announce_multiplayer_room.h" | #include "common/announce_multiplayer_room.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "configuration/qt_config.h" | #include "configuration/qt_config.h" | ||||||
|  | #include "frontend_common/content_manager.h" | ||||||
| #include "input_common/drivers/tas_input.h" | #include "input_common/drivers/tas_input.h" | ||||||
| #include "yuzu/compatibility_list.h" | #include "yuzu/compatibility_list.h" | ||||||
| #include "yuzu/hotkeys.h" | #include "yuzu/hotkeys.h" | ||||||
| @@ -124,13 +125,6 @@ enum class EmulatedDirectoryTarget { | |||||||
|     SDMC, |     SDMC, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class InstallResult { |  | ||||||
|     Success, |  | ||||||
|     Overwrite, |  | ||||||
|     Failure, |  | ||||||
|     BaseInstallAttempted, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class ReinitializeKeyBehavior { | enum class ReinitializeKeyBehavior { | ||||||
|     NoWarning, |     NoWarning, | ||||||
|     Warning, |     Warning, | ||||||
| @@ -427,8 +421,7 @@ private: | |||||||
|     void RemoveCacheStorage(u64 program_id); |     void RemoveCacheStorage(u64 program_id); | ||||||
|     bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, |     bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, | ||||||
|                                u64* selected_title_id, u8* selected_content_record_type); |                                u64* selected_title_id, u8* selected_content_record_type); | ||||||
|     InstallResult InstallNSP(const QString& filename); |     ContentManager::InstallResult InstallNCA(const QString& filename); | ||||||
|     InstallResult InstallNCA(const QString& filename); |  | ||||||
|     void MigrateConfigFiles(); |     void MigrateConfigFiles(); | ||||||
|     void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, |     void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, | ||||||
|                            std::string_view gpu_vendor = {}); |                            std::string_view gpu_vendor = {}); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user