frontend_common: Add content manager utility functions
Creates utility functions to remove/install DLC, updates, and base game content
This commit is contained in:
parent
b4a8e1ef8a
commit
ccd3dd842f
12 changed files with 318 additions and 221 deletions
|
@ -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;
|
return false;
|
||||||
} else {
|
};
|
||||||
return InstallResult::Failure;
|
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 = {});
|
||||||
|
|
Loading…
Reference in a new issue