summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar t8952024-01-19 00:56:43 -0500
committerGravatar t8952024-01-19 17:09:35 -0500
commitccd3dd842f2bf7cf16c7b93e3b83a2afc8af4a69 (patch)
tree552347f0f71c9341a139f929907fef9243c6e337
parentMerge pull request #12713 from shinra-electric/mvk-127 (diff)
downloadyuzu-ccd3dd842f2bf7cf16c7b93e3b83a2afc8af4a69.tar.gz
yuzu-ccd3dd842f2bf7cf16c7b93e3b83a2afc8af4a69.tar.xz
yuzu-ccd3dd842f2bf7cf16c7b93e3b83a2afc8af4a69.zip
frontend_common: Add content manager utility functions
Creates utility functions to remove/install DLC, updates, and base game content
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt15
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.cpp16
-rw-r--r--src/android/app/src/main/jni/android_common/android_common.h7
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp46
-rw-r--r--src/android/app/src/main/jni/id_cache.h8
-rw-r--r--src/android/app/src/main/jni/native.cpp80
-rw-r--r--src/android/app/src/main/jni/native.h2
-rw-r--r--src/frontend_common/CMakeLists.txt1
-rw-r--r--src/frontend_common/content_manager.h168
-rw-r--r--src/yuzu/main.cpp166
-rw-r--r--src/yuzu/main.h11
12 files changed, 318 insertions, 221 deletions
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 b7556e353..8cb98d6d7 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
@@ -21,6 +21,7 @@ import org.yuzu.yuzu_emu.utils.DocumentsTree
21import org.yuzu.yuzu_emu.utils.FileUtil 21import org.yuzu.yuzu_emu.utils.FileUtil
22import org.yuzu.yuzu_emu.utils.Log 22import org.yuzu.yuzu_emu.utils.Log
23import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 23import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
24import org.yuzu.yuzu_emu.model.InstallResult
24 25
25/** 26/**
26 * Class which contains methods that interact 27 * Class which contains methods that interact
@@ -235,9 +236,12 @@ object NativeLibrary {
235 /** 236 /**
236 * Installs a nsp or xci file to nand 237 * Installs a nsp or xci file to nand
237 * @param filename String representation of file uri 238 * @param filename String representation of file uri
238 * @param extension Lowercase string representation of file extension without "." 239 * @return int representation of [InstallResult]
239 */ 240 */
240 external fun installFileToNand(filename: String, extension: String): Int 241 external fun installFileToNand(
242 filename: String,
243 callback: (max: Long, progress: Long) -> Boolean
244 ): Int
241 245
242 external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean 246 external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
243 247
@@ -609,15 +613,4 @@ object NativeLibrary {
609 const val RELEASED = 0 613 const val RELEASED = 0
610 const val PRESSED = 1 614 const val PRESSED = 1
611 } 615 }
612
613 /**
614 * Result from installFileToNand
615 */
616 object InstallFileToNandResult {
617 const val Success = 0
618 const val SuccessFileOverwritten = 1
619 const val Error = 2
620 const val ErrorBaseGame = 3
621 const val ErrorFilenameExtension = 4
622 }
623} 616}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt
new file mode 100644
index 000000000..0c3cd0521
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/InstallResult.kt
@@ -0,0 +1,15 @@
1// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.model
5
6enum class InstallResult(val int: Int) {
7 Success(0),
8 Overwrite(1),
9 Failure(2),
10 BaseInstallAttempted(3);
11
12 companion object {
13 fun from(int: Int): InstallResult = entries.firstOrNull { it.int == int } ?: Success
14 }
15}
diff --git a/src/android/app/src/main/jni/android_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp
index 1e884ffdd..7018a52af 100644
--- a/src/android/app/src/main/jni/android_common/android_common.cpp
+++ b/src/android/app/src/main/jni/android_common/android_common.cpp
@@ -42,3 +42,19 @@ double GetJDouble(JNIEnv* env, jobject jdouble) {
42jobject ToJDouble(JNIEnv* env, double value) { 42jobject ToJDouble(JNIEnv* env, double value) {
43 return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value); 43 return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
44} 44}
45
46s32 GetJInteger(JNIEnv* env, jobject jinteger) {
47 return env->GetIntField(jinteger, IDCache::GetIntegerValueField());
48}
49
50jobject ToJInteger(JNIEnv* env, s32 value) {
51 return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value);
52}
53
54bool GetJBoolean(JNIEnv* env, jobject jboolean) {
55 return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField());
56}
57
58jobject ToJBoolean(JNIEnv* env, bool value) {
59 return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value);
60}
diff --git a/src/android/app/src/main/jni/android_common/android_common.h b/src/android/app/src/main/jni/android_common/android_common.h
index 8eb803e1b..29a338c0a 100644
--- a/src/android/app/src/main/jni/android_common/android_common.h
+++ b/src/android/app/src/main/jni/android_common/android_common.h
@@ -6,6 +6,7 @@
6#include <string> 6#include <string>
7 7
8#include <jni.h> 8#include <jni.h>
9#include "common/common_types.h"
9 10
10std::string GetJString(JNIEnv* env, jstring jstr); 11std::string GetJString(JNIEnv* env, jstring jstr);
11jstring ToJString(JNIEnv* env, std::string_view str); 12jstring ToJString(JNIEnv* env, std::string_view str);
@@ -13,3 +14,9 @@ jstring ToJString(JNIEnv* env, std::u16string_view str);
13 14
14double GetJDouble(JNIEnv* env, jobject jdouble); 15double GetJDouble(JNIEnv* env, jobject jdouble);
15jobject ToJDouble(JNIEnv* env, double value); 16jobject ToJDouble(JNIEnv* env, double value);
17
18s32 GetJInteger(JNIEnv* env, jobject jinteger);
19jobject ToJInteger(JNIEnv* env, s32 value);
20
21bool GetJBoolean(JNIEnv* env, jobject jboolean);
22jobject ToJBoolean(JNIEnv* env, bool value);
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index c79ad7d76..19ced175f 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -47,6 +47,14 @@ static jclass s_double_class;
47static jmethodID s_double_constructor; 47static jmethodID s_double_constructor;
48static jfieldID s_double_value_field; 48static jfieldID s_double_value_field;
49 49
50static jclass s_integer_class;
51static jmethodID s_integer_constructor;
52static jfieldID s_integer_value_field;
53
54static jclass s_boolean_class;
55static jmethodID s_boolean_constructor;
56static jfieldID s_boolean_value_field;
57
50static constexpr jint JNI_VERSION = JNI_VERSION_1_6; 58static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
51 59
52namespace IDCache { 60namespace IDCache {
@@ -198,6 +206,30 @@ jfieldID GetDoubleValueField() {
198 return s_double_value_field; 206 return s_double_value_field;
199} 207}
200 208
209jclass GetIntegerClass() {
210 return s_integer_class;
211}
212
213jmethodID GetIntegerConstructor() {
214 return s_integer_constructor;
215}
216
217jfieldID GetIntegerValueField() {
218 return s_integer_value_field;
219}
220
221jclass GetBooleanClass() {
222 return s_boolean_class;
223}
224
225jmethodID GetBooleanConstructor() {
226 return s_boolean_constructor;
227}
228
229jfieldID GetBooleanValueField() {
230 return s_boolean_value_field;
231}
232
201} // namespace IDCache 233} // namespace IDCache
202 234
203#ifdef __cplusplus 235#ifdef __cplusplus
@@ -284,6 +316,18 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
284 s_double_value_field = env->GetFieldID(double_class, "value", "D"); 316 s_double_value_field = env->GetFieldID(double_class, "value", "D");
285 env->DeleteLocalRef(double_class); 317 env->DeleteLocalRef(double_class);
286 318
319 const jclass int_class = env->FindClass("java/lang/Integer");
320 s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
321 s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
322 s_integer_value_field = env->GetFieldID(int_class, "value", "I");
323 env->DeleteLocalRef(int_class);
324
325 const jclass boolean_class = env->FindClass("java/lang/Boolean");
326 s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
327 s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
328 s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z");
329 env->DeleteLocalRef(boolean_class);
330
287 // Initialize Android Storage 331 // Initialize Android Storage
288 Common::FS::Android::RegisterCallbacks(env, s_native_library_class); 332 Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
289 333
@@ -310,6 +354,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
310 env->DeleteGlobalRef(s_pair_class); 354 env->DeleteGlobalRef(s_pair_class);
311 env->DeleteGlobalRef(s_overlay_control_data_class); 355 env->DeleteGlobalRef(s_overlay_control_data_class);
312 env->DeleteGlobalRef(s_double_class); 356 env->DeleteGlobalRef(s_double_class);
357 env->DeleteGlobalRef(s_integer_class);
358 env->DeleteGlobalRef(s_boolean_class);
313 359
314 // UnInitialize applets 360 // UnInitialize applets
315 SoftwareKeyboard::CleanupJNI(env); 361 SoftwareKeyboard::CleanupJNI(env);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index 784d1412f..0e5267b73 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -47,4 +47,12 @@ jclass GetDoubleClass();
47jmethodID GetDoubleConstructor(); 47jmethodID GetDoubleConstructor();
48jfieldID GetDoubleValueField(); 48jfieldID GetDoubleValueField();
49 49
50jclass GetIntegerClass();
51jmethodID GetIntegerConstructor();
52jfieldID GetIntegerValueField();
53
54jclass GetBooleanClass();
55jmethodID GetBooleanConstructor();
56jfieldID GetBooleanValueField();
57
50} // namespace IDCache 58} // namespace IDCache
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index ed3b1353a..b8fef5c6f 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -17,6 +17,7 @@
17#include <core/file_sys/patch_manager.h> 17#include <core/file_sys/patch_manager.h>
18#include <core/file_sys/savedata_factory.h> 18#include <core/file_sys/savedata_factory.h>
19#include <core/loader/nro.h> 19#include <core/loader/nro.h>
20#include <frontend_common/content_manager.h>
20#include <jni.h> 21#include <jni.h>
21 22
22#include "common/detached_tasks.h" 23#include "common/detached_tasks.h"
@@ -100,67 +101,6 @@ void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
100 m_native_window = native_window; 101 m_native_window = native_window;
101} 102}
102 103
103int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
104 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
105 std::size_t block_size) {
106 if (src == nullptr || dest == nullptr) {
107 return false;
108 }
109 if (!dest->Resize(src->GetSize())) {
110 return false;
111 }
112
113 using namespace Common::Literals;
114 [[maybe_unused]] std::vector<u8> buffer(1_MiB);
115
116 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
117 jconst read = src->Read(buffer.data(), buffer.size(), i);
118 dest->Write(buffer.data(), read, i);
119 }
120 return true;
121 };
122
123 enum InstallResult {
124 Success = 0,
125 SuccessFileOverwritten = 1,
126 InstallError = 2,
127 ErrorBaseGame = 3,
128 ErrorFilenameExtension = 4,
129 };
130
131 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
132 if (file_extension == "nsp") {
133 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
134 if (nsp->IsExtractedType()) {
135 return InstallError;
136 }
137 } else {
138 return ErrorFilenameExtension;
139 }
140
141 if (!nsp) {
142 return InstallError;
143 }
144
145 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
146 return InstallError;
147 }
148
149 jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
150 copy_func);
151
152 switch (res) {
153 case FileSys::InstallResult::Success:
154 return Success;
155 case FileSys::InstallResult::OverwriteExisting:
156 return SuccessFileOverwritten;
157 case FileSys::InstallResult::ErrorBaseInstall:
158 return ErrorBaseGame;
159 default:
160 return InstallError;
161 }
162}
163
164void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, 104void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
165 const std::string& custom_driver_dir, 105 const std::string& custom_driver_dir,
166 const std::string& custom_driver_name, 106 const std::string& custom_driver_name,
@@ -512,10 +452,20 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
512} 452}
513 453
514int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance, 454int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
515 jstring j_file, 455 jstring j_file, jobject jcallback) {
516 jstring j_file_extension) { 456 auto jlambdaClass = env->GetObjectClass(jcallback);
517 return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file), 457 auto jlambdaInvokeMethod = env->GetMethodID(
518 GetJString(env, j_file_extension)); 458 jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
459 const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) {
460 auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod,
461 ToJDouble(env, max), ToJDouble(env, progress));
462 return GetJBoolean(env, jwasCancelled);
463 };
464
465 return static_cast<int>(
466 ContentManager::InstallNSP(&EmulationSession::GetInstance().System(),
467 EmulationSession::GetInstance().System().GetFilesystem().get(),
468 GetJString(env, j_file), callback));
519} 469}
520 470
521jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, 471jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 4a8049578..dadb138ad 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -7,6 +7,7 @@
7#include "core/file_sys/registered_cache.h" 7#include "core/file_sys/registered_cache.h"
8#include "core/hle/service/acc/profile_manager.h" 8#include "core/hle/service/acc/profile_manager.h"
9#include "core/perf_stats.h" 9#include "core/perf_stats.h"
10#include "frontend_common/content_manager.h"
10#include "jni/applets/software_keyboard.h" 11#include "jni/applets/software_keyboard.h"
11#include "jni/emu_window/emu_window.h" 12#include "jni/emu_window/emu_window.h"
12#include "video_core/rasterizer_interface.h" 13#include "video_core/rasterizer_interface.h"
@@ -29,7 +30,6 @@ public:
29 void SetNativeWindow(ANativeWindow* native_window); 30 void SetNativeWindow(ANativeWindow* native_window);
30 void SurfaceChanged(); 31 void SurfaceChanged();
31 32
32 int InstallFileToNand(std::string filename, std::string file_extension);
33 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, 33 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
34 const std::string& custom_driver_name, 34 const std::string& custom_driver_name,
35 const std::string& file_redirect_dir); 35 const std::string& file_redirect_dir);
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt
index 22e9337c4..94d8cc4c3 100644
--- a/src/frontend_common/CMakeLists.txt
+++ b/src/frontend_common/CMakeLists.txt
@@ -4,6 +4,7 @@
4add_library(frontend_common STATIC 4add_library(frontend_common STATIC
5 config.cpp 5 config.cpp
6 config.h 6 config.h
7 content_manager.h
7) 8)
8 9
9create_target_directory_groups(frontend_common) 10create_target_directory_groups(frontend_common)
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h
new file mode 100644
index 000000000..8e55f4ca0
--- /dev/null
+++ b/src/frontend_common/content_manager.h
@@ -0,0 +1,168 @@
1// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <boost/algorithm/string.hpp>
7#include "common/common_types.h"
8#include "common/literals.h"
9#include "core/core.h"
10#include "core/file_sys/common_funcs.h"
11#include "core/file_sys/content_archive.h"
12#include "core/file_sys/mode.h"
13#include "core/file_sys/nca_metadata.h"
14#include "core/file_sys/registered_cache.h"
15#include "core/file_sys/submission_package.h"
16#include "core/hle/service/filesystem/filesystem.h"
17#include "core/loader/loader.h"
18
19namespace ContentManager {
20
21enum class InstallResult {
22 Success,
23 Overwrite,
24 Failure,
25 BaseInstallAttempted,
26};
27
28inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_controller,
29 const u64 title_id) {
30 return fs_controller.GetUserNANDContents()->RemoveExistingEntry(title_id) ||
31 fs_controller.GetSDMCContents()->RemoveExistingEntry(title_id);
32}
33
34inline size_t RemoveAllDLC(Core::System* system, const u64 program_id) {
35 size_t count{};
36 const auto& fs_controller = system->GetFileSystemController();
37 const auto dlc_entries = system->GetContentProvider().ListEntriesFilter(
38 FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
39 std::vector<u64> program_dlc_entries;
40
41 for (const auto& entry : dlc_entries) {
42 if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
43 program_dlc_entries.push_back(entry.title_id);
44 }
45 }
46
47 for (const auto& entry : program_dlc_entries) {
48 if (RemoveDLC(fs_controller, entry)) {
49 ++count;
50 }
51 }
52 return count;
53}
54
55inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
56 const u64 program_id) {
57 const auto update_id = program_id | 0x800;
58 return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
59 fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
60}
61
62inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& fs_controller,
63 const u64 program_id) {
64 return fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
65 fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
66}
67
68inline InstallResult InstallNSP(
69 Core::System* system, FileSys::VfsFilesystem* vfs, const std::string& filename,
70 const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) {
71 const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
72 std::size_t block_size) {
73 if (src == nullptr || dest == nullptr) {
74 return false;
75 }
76 if (!dest->Resize(src->GetSize())) {
77 return false;
78 }
79
80 using namespace Common::Literals;
81 std::vector<u8> buffer(1_MiB);
82
83 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
84 if (callback(src->GetSize(), i)) {
85 dest->Resize(0);
86 return false;
87 }
88 const auto read = src->Read(buffer.data(), buffer.size(), i);
89 dest->Write(buffer.data(), read, i);
90 }
91 return true;
92 };
93
94 std::shared_ptr<FileSys::NSP> nsp;
95 FileSys::VirtualFile file = vfs->OpenFile(filename, FileSys::Mode::Read);
96 if (boost::to_lower_copy(file->GetName()).ends_with(std::string("nsp"))) {
97 nsp = std::make_shared<FileSys::NSP>(file);
98 if (nsp->IsExtractedType()) {
99 return InstallResult::Failure;
100 }
101 } else {
102 return InstallResult::Failure;
103 }
104
105 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
106 return InstallResult::Failure;
107 }
108 const auto res =
109 system->GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, copy);
110 switch (res) {
111 case FileSys::InstallResult::Success:
112 return InstallResult::Success;
113 case FileSys::InstallResult::OverwriteExisting:
114 return InstallResult::Overwrite;
115 case FileSys::InstallResult::ErrorBaseInstall:
116 return InstallResult::BaseInstallAttempted;
117 default:
118 return InstallResult::Failure;
119 }
120}
121
122inline InstallResult InstallNCA(
123 FileSys::VfsFilesystem* vfs, const std::string& filename,
124 FileSys::RegisteredCache* registered_cache, const FileSys::TitleType title_type,
125 const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) {
126 const auto copy = [callback](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
127 std::size_t block_size) {
128 if (src == nullptr || dest == nullptr) {
129 return false;
130 }
131 if (!dest->Resize(src->GetSize())) {
132 return false;
133 }
134
135 using namespace Common::Literals;
136 std::vector<u8> buffer(1_MiB);
137
138 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
139 if (callback(src->GetSize(), i)) {
140 dest->Resize(0);
141 return false;
142 }
143 const auto read = src->Read(buffer.data(), buffer.size(), i);
144 dest->Write(buffer.data(), read, i);
145 }
146 return true;
147 };
148
149 const auto nca = std::make_shared<FileSys::NCA>(vfs->OpenFile(filename, FileSys::Mode::Read));
150 const auto id = nca->GetStatus();
151
152 // Game updates necessary are missing base RomFS
153 if (id != Loader::ResultStatus::Success &&
154 id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
155 return InstallResult::Failure;
156 }
157
158 const auto res = registered_cache->InstallEntry(*nca, title_type, true, copy);
159 if (res == FileSys::InstallResult::Success) {
160 return InstallResult::Success;
161 } else if (res == FileSys::InstallResult::OverwriteExisting) {
162 return InstallResult::Overwrite;
163 } else {
164 return InstallResult::Failure;
165 }
166}
167
168} // namespace ContentManager
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 3c562e3b2..05bd4174c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -47,6 +47,7 @@
47#include "core/hle/service/am/applet_oe.h" 47#include "core/hle/service/am/applet_oe.h"
48#include "core/hle/service/am/applets/applets.h" 48#include "core/hle/service/am/applets/applets.h"
49#include "core/hle/service/set/system_settings_server.h" 49#include "core/hle/service/set/system_settings_server.h"
50#include "frontend_common/content_manager.h"
50#include "hid_core/frontend/emulated_controller.h" 51#include "hid_core/frontend/emulated_controller.h"
51#include "hid_core/hid_core.h" 52#include "hid_core/hid_core.h"
52#include "yuzu/multiplayer/state.h" 53#include "yuzu/multiplayer/state.h"
@@ -2476,10 +2477,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
2476} 2477}
2477 2478
2478void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { 2479void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) {
2479 const auto& fs_controller = system->GetFileSystemController(); 2480 const auto res =
2480 const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) || 2481 ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id);
2481 fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
2482
2483 if (res) { 2482 if (res) {
2484 QMessageBox::information(this, tr("Successfully Removed"), 2483 QMessageBox::information(this, tr("Successfully Removed"),
2485 tr("Successfully removed the installed base game.")); 2484 tr("Successfully removed the installed base game."));
@@ -2491,11 +2490,7 @@ void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) {
2491} 2490}
2492 2491
2493void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { 2492void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
2494 const auto update_id = program_id | 0x800; 2493 const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
2495 const auto& fs_controller = system->GetFileSystemController();
2496 const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
2497 fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
2498
2499 if (res) { 2494 if (res) {
2500 QMessageBox::information(this, tr("Successfully Removed"), 2495 QMessageBox::information(this, tr("Successfully Removed"),
2501 tr("Successfully removed the installed update.")); 2496 tr("Successfully removed the installed update."));
@@ -2506,22 +2501,7 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
2506} 2501}
2507 2502
2508void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { 2503void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) {
2509 u32 count{}; 2504 const size_t count = ContentManager::RemoveAllDLC(system.get(), program_id);
2510 const auto& fs_controller = system->GetFileSystemController();
2511 const auto dlc_entries = system->GetContentProvider().ListEntriesFilter(
2512 FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
2513
2514 for (const auto& entry : dlc_entries) {
2515 if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
2516 const auto res =
2517 fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) ||
2518 fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id);
2519 if (res) {
2520 ++count;
2521 }
2522 }
2523 }
2524
2525 if (count == 0) { 2505 if (count == 0) {
2526 QMessageBox::warning(this, GetGameListErrorRemoving(type), 2506 QMessageBox::warning(this, GetGameListErrorRemoving(type),
2527 tr("There are no DLC installed for this title.")); 2507 tr("There are no DLC installed for this title."));
@@ -3290,12 +3270,21 @@ void GMainWindow::OnMenuInstallToNAND() {
3290 install_progress->setLabelText( 3270 install_progress->setLabelText(
3291 tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); 3271 tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName()));
3292 3272
3293 QFuture<InstallResult> future; 3273 QFuture<ContentManager::InstallResult> future;
3294 InstallResult result; 3274 ContentManager::InstallResult result;
3295 3275
3296 if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { 3276 if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3297 3277 const auto progress_callback = [this](size_t size, size_t progress) {
3298 future = QtConcurrent::run([this, &file] { return InstallNSP(file); }); 3278 emit UpdateInstallProgress();
3279 if (install_progress->wasCanceled()) {
3280 return true;
3281 }
3282 return false;
3283 };
3284 future = QtConcurrent::run([this, &file, progress_callback] {
3285 return ContentManager::InstallNSP(system.get(), vfs.get(), file.toStdString(),
3286 progress_callback);
3287 });
3299 3288
3300 while (!future.isFinished()) { 3289 while (!future.isFinished()) {
3301 QCoreApplication::processEvents(); 3290 QCoreApplication::processEvents();
@@ -3311,16 +3300,16 @@ void GMainWindow::OnMenuInstallToNAND() {
3311 std::this_thread::sleep_for(std::chrono::milliseconds(10)); 3300 std::this_thread::sleep_for(std::chrono::milliseconds(10));
3312 3301
3313 switch (result) { 3302 switch (result) {
3314 case InstallResult::Success: 3303 case ContentManager::InstallResult::Success:
3315 new_files.append(QFileInfo(file).fileName()); 3304 new_files.append(QFileInfo(file).fileName());
3316 break; 3305 break;
3317 case InstallResult::Overwrite: 3306 case ContentManager::InstallResult::Overwrite:
3318 overwritten_files.append(QFileInfo(file).fileName()); 3307 overwritten_files.append(QFileInfo(file).fileName());
3319 break; 3308 break;
3320 case InstallResult::Failure: 3309 case ContentManager::InstallResult::Failure:
3321 failed_files.append(QFileInfo(file).fileName()); 3310 failed_files.append(QFileInfo(file).fileName());
3322 break; 3311 break;
3323 case InstallResult::BaseInstallAttempted: 3312 case ContentManager::InstallResult::BaseInstallAttempted:
3324 failed_files.append(QFileInfo(file).fileName()); 3313 failed_files.append(QFileInfo(file).fileName());
3325 detected_base_install = true; 3314 detected_base_install = true;
3326 break; 3315 break;
@@ -3354,96 +3343,7 @@ void GMainWindow::OnMenuInstallToNAND() {
3354 ui->action_Install_File_NAND->setEnabled(true); 3343 ui->action_Install_File_NAND->setEnabled(true);
3355} 3344}
3356 3345
3357InstallResult GMainWindow::InstallNSP(const QString& filename) { 3346ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) {
3358 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
3359 const FileSys::VirtualFile& dest, std::size_t block_size) {
3360 if (src == nullptr || dest == nullptr) {
3361 return false;
3362 }
3363 if (!dest->Resize(src->GetSize())) {
3364 return false;
3365 }
3366
3367 std::vector<u8> buffer(CopyBufferSize);
3368
3369 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
3370 if (install_progress->wasCanceled()) {
3371 dest->Resize(0);
3372 return false;
3373 }
3374
3375 emit UpdateInstallProgress();
3376
3377 const auto read = src->Read(buffer.data(), buffer.size(), i);
3378 dest->Write(buffer.data(), read, i);
3379 }
3380 return true;
3381 };
3382
3383 std::shared_ptr<FileSys::NSP> nsp;
3384 if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
3385 nsp = std::make_shared<FileSys::NSP>(
3386 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
3387 if (nsp->IsExtractedType()) {
3388 return InstallResult::Failure;
3389 }
3390 } else {
3391 return InstallResult::Failure;
3392 }
3393
3394 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
3395 return InstallResult::Failure;
3396 }
3397 const auto res = system->GetFileSystemController().GetUserNANDContents()->InstallEntry(
3398 *nsp, true, qt_raw_copy);
3399 switch (res) {
3400 case FileSys::InstallResult::Success:
3401 return InstallResult::Success;
3402 case FileSys::InstallResult::OverwriteExisting:
3403 return InstallResult::Overwrite;
3404 case FileSys::InstallResult::ErrorBaseInstall:
3405 return InstallResult::BaseInstallAttempted;
3406 default:
3407 return InstallResult::Failure;
3408 }
3409}
3410
3411InstallResult GMainWindow::InstallNCA(const QString& filename) {
3412 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
3413 const FileSys::VirtualFile& dest, std::size_t block_size) {
3414 if (src == nullptr || dest == nullptr) {
3415 return false;
3416 }
3417 if (!dest->Resize(src->GetSize())) {
3418 return false;
3419 }
3420
3421 std::vector<u8> buffer(CopyBufferSize);
3422
3423 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
3424 if (install_progress->wasCanceled()) {
3425 dest->Resize(0);
3426 return false;
3427 }
3428
3429 emit UpdateInstallProgress();
3430
3431 const auto read = src->Read(buffer.data(), buffer.size(), i);
3432 dest->Write(buffer.data(), read, i);
3433 }
3434 return true;
3435 };
3436
3437 const auto nca =
3438 std::make_shared<FileSys::NCA>(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
3439 const auto id = nca->GetStatus();
3440
3441 // Game updates necessary are missing base RomFS
3442 if (id != Loader::ResultStatus::Success &&
3443 id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
3444 return InstallResult::Failure;
3445 }
3446
3447 const QStringList tt_options{tr("System Application"), 3347 const QStringList tt_options{tr("System Application"),
3448 tr("System Archive"), 3348 tr("System Archive"),
3449 tr("System Application Update"), 3349 tr("System Application Update"),
@@ -3464,7 +3364,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) {
3464 if (!ok || index == -1) { 3364 if (!ok || index == -1) {
3465 QMessageBox::warning(this, tr("Failed to Install"), 3365 QMessageBox::warning(this, tr("Failed to Install"),
3466 tr("The title type you selected for the NCA is invalid.")); 3366 tr("The title type you selected for the NCA is invalid."));
3467 return InstallResult::Failure; 3367 return ContentManager::InstallResult::Failure;
3468 } 3368 }
3469 3369
3470 // If index is equal to or past Game, add the jump in TitleType. 3370 // If index is equal to or past Game, add the jump in TitleType.
@@ -3478,15 +3378,15 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) {
3478 auto* registered_cache = is_application ? fs_controller.GetUserNANDContents() 3378 auto* registered_cache = is_application ? fs_controller.GetUserNANDContents()
3479 : fs_controller.GetSystemNANDContents(); 3379 : fs_controller.GetSystemNANDContents();
3480 3380
3481 const auto res = registered_cache->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), 3381 const auto progress_callback = [this](size_t size, size_t progress) {
3482 true, qt_raw_copy); 3382 emit UpdateInstallProgress();
3483 if (res == FileSys::InstallResult::Success) { 3383 if (install_progress->wasCanceled()) {
3484 return InstallResult::Success; 3384 return true;
3485 } else if (res == FileSys::InstallResult::OverwriteExisting) { 3385 }
3486 return InstallResult::Overwrite; 3386 return false;
3487 } else { 3387 };
3488 return InstallResult::Failure; 3388 return ContentManager::InstallNCA(vfs.get(), filename.toStdString(), registered_cache,
3489 } 3389 static_cast<FileSys::TitleType>(index), progress_callback);
3490} 3390}
3491 3391
3492void GMainWindow::OnMenuRecentFile() { 3392void GMainWindow::OnMenuRecentFile() {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index f3276da64..280fae5c3 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -16,6 +16,7 @@
16#include "common/announce_multiplayer_room.h" 16#include "common/announce_multiplayer_room.h"
17#include "common/common_types.h" 17#include "common/common_types.h"
18#include "configuration/qt_config.h" 18#include "configuration/qt_config.h"
19#include "frontend_common/content_manager.h"
19#include "input_common/drivers/tas_input.h" 20#include "input_common/drivers/tas_input.h"
20#include "yuzu/compatibility_list.h" 21#include "yuzu/compatibility_list.h"
21#include "yuzu/hotkeys.h" 22#include "yuzu/hotkeys.h"
@@ -124,13 +125,6 @@ enum class EmulatedDirectoryTarget {
124 SDMC, 125 SDMC,
125}; 126};
126 127
127enum class InstallResult {
128 Success,
129 Overwrite,
130 Failure,
131 BaseInstallAttempted,
132};
133
134enum class ReinitializeKeyBehavior { 128enum class ReinitializeKeyBehavior {
135 NoWarning, 129 NoWarning,
136 Warning, 130 Warning,
@@ -427,8 +421,7 @@ private:
427 void RemoveCacheStorage(u64 program_id); 421 void RemoveCacheStorage(u64 program_id);
428 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 422 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
429 u64* selected_title_id, u8* selected_content_record_type); 423 u64* selected_title_id, u8* selected_content_record_type);
430 InstallResult InstallNSP(const QString& filename); 424 ContentManager::InstallResult InstallNCA(const QString& filename);
431 InstallResult InstallNCA(const QString& filename);
432 void MigrateConfigFiles(); 425 void MigrateConfigFiles();
433 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, 426 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
434 std::string_view gpu_vendor = {}); 427 std::string_view gpu_vendor = {});