diff options
Diffstat (limited to 'src/frontend_common')
-rw-r--r-- | src/frontend_common/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/frontend_common/config.cpp | 78 | ||||
-rw-r--r-- | src/frontend_common/config.h | 23 | ||||
-rw-r--r-- | src/frontend_common/content_manager.h | 379 |
4 files changed, 455 insertions, 26 deletions
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 @@ add_library(frontend_common STATIC config.cpp config.h + content_manager.h ) create_target_directory_groups(frontend_common) diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index 51576b4ee..905f35118 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -5,13 +5,14 @@ #include <array> #include "common/fs/fs.h" #include "common/fs/path_util.h" +#include "common/logging/log.h" #include "common/settings.h" #include "common/settings_common.h" #include "common/settings_enums.h" #include "config.h" #include "core/core.h" #include "core/hle/service/acc/profile_manager.h" -#include "core/hle/service/hid/controllers/npad.h" +#include "hid_core/resources/npad/npad.h" #include "network/network.h" #include <boost/algorithm/string/replace.hpp> @@ -58,6 +59,19 @@ void Config::Initialize(const std::optional<std::string> config_path) { } void Config::WriteToIni() const { + std::string config_type; + switch (type) { + case ConfigType::GlobalConfig: + config_type = "Global"; + break; + case ConfigType::PerGameConfig: + config_type = "Game Specific"; + break; + case ConfigType::InputProfile: + config_type = "Input Profile"; + break; + } + LOG_INFO(Config, "Writing {} configuration to: {}", config_type, config_loc); FILE* fp = nullptr; #ifdef _WIN32 fp = _wfopen(Common::UTF8ToUTF16W(config_loc).data(), L"wb"); @@ -117,10 +131,10 @@ void Config::ReadPlayerValues(const std::size_t player_index) { player_prefix.append("player_").append(ToString(player_index)).append("_"); } + const auto profile_name = ReadStringSetting(std::string(player_prefix).append("profile_name")); + auto& player = Settings::values.players.GetValue()[player_index]; if (IsCustomConfig()) { - const auto profile_name = - ReadStringSetting(std::string(player_prefix).append("profile_name")); if (profile_name.empty()) { // Use the global input config player = Settings::values.players.GetValue(true)[player_index]; @@ -139,6 +153,10 @@ void Config::ReadPlayerValues(const std::size_t player_index) { player.controller_type = controller; } } else { + if (global) { + auto& player_global = Settings::values.players.GetValue(true)[player_index]; + player_global.profile_name = profile_name; + } std::string connected_key = player_prefix; player.connected = ReadBooleanSetting(connected_key.append("connected"), std::make_optional(player_index == 0)); @@ -280,6 +298,16 @@ void Config::ReadDebuggingValues() { EndGroup(); } +#ifdef __unix__ +void Config::ReadLinuxValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Linux)); + + ReadCategory(Settings::Category::Linux); + + EndGroup(); +} +#endif + void Config::ReadServiceValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Services)); @@ -386,6 +414,9 @@ void Config::ReadValues() { ReadControlValues(); ReadCoreValues(); ReadCpuValues(); +#ifdef __unix__ + ReadLinuxValues(); +#endif ReadRendererValues(); ReadAudioValues(); ReadSystemValues(); @@ -412,6 +443,11 @@ void Config::SavePlayerValues(const std::size_t player_index) { std::make_optional(static_cast<u8>(Settings::ControllerType::ProController))); if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) { + if (global) { + const auto& player_global = Settings::values.players.GetValue(true)[player_index]; + WriteStringSetting(std::string(player_prefix).append("profile_name"), + player_global.profile_name, std::make_optional(std::string(""))); + } WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected, std::make_optional(player_index == 0)); WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"), @@ -468,16 +504,22 @@ void Config::SaveMotionTouchValues() { void Config::SaveValues() { if (global) { + LOG_DEBUG(Config, "Saving global generic configuration values"); SaveDataStorageValues(); SaveDebuggingValues(); SaveDisabledAddOnValues(); SaveNetworkValues(); SaveWebServiceValues(); SaveMiscellaneousValues(); + } else { + LOG_DEBUG(Config, "Saving only generic configuration values"); } SaveControlValues(); SaveCoreValues(); SaveCpuValues(); +#ifdef __unix__ + SaveLinuxValues(); +#endif SaveRendererValues(); SaveAudioValues(); SaveSystemValues(); @@ -552,6 +594,16 @@ void Config::SaveDebuggingValues() { EndGroup(); } +#ifdef __unix__ +void Config::SaveLinuxValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::Linux)); + + WriteCategory(Settings::Category::Linux); + + EndGroup(); +} +#endif + void Config::SaveNetworkValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Services)); @@ -762,17 +814,6 @@ void Config::WriteBooleanSetting(const std::string& key, const bool& value, WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global); } -template <typename T> -std::enable_if_t<std::is_integral_v<T>> Config::WriteIntegerSetting( - const std::string& key, const T& value, const std::optional<T>& default_value, - const std::optional<bool>& use_global) { - std::optional<std::string> string_default = std::nullopt; - if (default_value.has_value()) { - string_default = std::make_optional(ToString(default_value.value())); - } - WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global); -} - void Config::WriteDoubleSetting(const std::string& key, const double& value, const std::optional<double>& default_value, const std::optional<bool>& use_global) { @@ -825,10 +866,6 @@ void Config::Reload() { SaveValues(); } -void Config::Save() { - SaveValues(); -} - void Config::ClearControlPlayerValues() const { // If key is an empty string, all keys in the current group() are removed. const char* section = Settings::TranslateCategory(Settings::Category::Controls); @@ -894,9 +931,10 @@ void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) { WriteBooleanSetting(std::string(key).append("\\use_global"), setting->UsingGlobal()); } if (global || !setting->UsingGlobal()) { + auto value = global ? setting->ToStringGlobal() : setting->ToString(); WriteBooleanSetting(std::string(key).append("\\default"), - setting->ToString() == setting->DefaultToString()); - WriteStringSetting(key, setting->ToString()); + value == setting->DefaultToString()); + WriteStringSetting(key, value); } } else if (global) { WriteBooleanSetting(std::string(key).append("\\default"), diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h index 0c4d505b8..4ecb97044 100644 --- a/src/frontend_common/config.h +++ b/src/frontend_common/config.h @@ -51,7 +51,6 @@ protected: [[nodiscard]] bool IsCustomConfig() const; void Reload(); - void Save(); /** * Derived config classes must implement this so they can reload all platform-specific @@ -77,6 +76,9 @@ protected: void ReadCoreValues(); void ReadDataStorageValues(); void ReadDebuggingValues(); +#ifdef __unix__ + void ReadLinuxValues(); +#endif void ReadServiceValues(); void ReadDisabledAddOnValues(); void ReadMiscellaneousValues(); @@ -108,6 +110,9 @@ protected: void SaveCoreValues(); void SaveDataStorageValues(); void SaveDebuggingValues(); +#ifdef __unix__ + void SaveLinuxValues(); +#endif void SaveNetworkValues(); void SaveDisabledAddOnValues(); void SaveMiscellaneousValues(); @@ -157,17 +162,23 @@ protected: void WriteBooleanSetting(const std::string& key, const bool& value, const std::optional<bool>& default_value = std::nullopt, const std::optional<bool>& use_global = std::nullopt); - template <typename T> - std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting( - const std::string& key, const T& value, - const std::optional<T>& default_value = std::nullopt, - const std::optional<bool>& use_global = std::nullopt); void WriteDoubleSetting(const std::string& key, const double& value, const std::optional<double>& default_value = std::nullopt, const std::optional<bool>& use_global = std::nullopt); void WriteStringSetting(const std::string& key, const std::string& value, const std::optional<std::string>& default_value = std::nullopt, const std::optional<bool>& use_global = std::nullopt); + template <typename T> + std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting( + const std::string& key, const T& value, + const std::optional<T>& default_value = std::nullopt, + const std::optional<bool>& use_global = std::nullopt) { + std::optional<std::string> string_default = std::nullopt; + if (default_value.has_value()) { + string_default = std::make_optional(ToString(default_value.value())); + } + WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global); + } void ReadCategory(Settings::Category category); void WriteCategory(Settings::Category category); diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h new file mode 100644 index 000000000..f3efe3465 --- /dev/null +++ b/src/frontend_common/content_manager.h @@ -0,0 +1,379 @@ +// 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/fs_filesystem.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.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" +#include "core/loader/nca.h" + +namespace ContentManager { + +enum class InstallResult { + Success, + Overwrite, + Failure, + BaseInstallAttempted, +}; + +enum class GameVerificationResult { + Success, + Failed, + NotImplemented, +}; + +/** + * \brief Removes a single installed DLC + * \param fs_controller [FileSystemController] reference from the Core::System instance + * \param title_id Unique title ID representing the DLC which will be removed + * \return 'true' if successful + */ +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); +} + +/** + * \brief Removes all DLC for a game + * \param system Reference to the system instance + * \param program_id Program ID for the game that will have all of its DLC removed + * \return Number of DLC removed + */ +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; +} + +/** + * \brief Removes the installed update for a game + * \param fs_controller [FileSystemController] reference from the Core::System instance + * \param program_id Program ID for the game that will have its installed update removed + * \return 'true' if successful + */ +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); +} + +/** + * \brief Removes the base content for a game + * \param fs_controller [FileSystemController] reference from the Core::System instance + * \param program_id Program ID for the game that will have its base content removed + * \return 'true' if successful + */ +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); +} + +/** + * \brief Removes a mod for a game + * \param fs_controller [FileSystemController] reference from the Core::System instance + * \param program_id Program ID for the game where [mod_name] will be removed + * \param mod_name The name of a mod as given by FileSys::PatchManager::GetPatches. This corresponds + * with the name of the mod's directory in a game's load folder. + * \return 'true' if successful + */ +inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller, + const u64 program_id, const std::string& mod_name) { + // Check general Mods (LayeredFS and IPS) + const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id); + if (mod_dir != nullptr) { + return mod_dir->DeleteSubdirectoryRecursive(mod_name); + } + + // Check SDMC mod directory (RomFS LayeredFS) + const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id); + if (sdmc_mod_dir != nullptr) { + return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name); + } + + return false; +} + +/** + * \brief Installs an NSP + * \param system Reference to the system instance + * \param vfs Reference to the VfsFilesystem instance in Core::System + * \param filename Path to the NSP file + * \param callback Callback to report the progress of the installation. The first size_t + * parameter is the total size of the virtual file and the second is the current progress. If you + * return true to the callback, it will cancel the installation as soon as possible. + * \return [InstallResult] representing how the installation finished + */ +inline InstallResult InstallNSP(Core::System& system, FileSys::VfsFilesystem& vfs, + const std::string& filename, + const std::function<bool(size_t, size_t)>& callback) { + 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::OpenMode::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; + } +} + +/** + * \brief Installs an NCA + * \param vfs Reference to the VfsFilesystem instance in Core::System + * \param filename Path to the NCA file + * \param registered_cache Reference to the registered cache that the NCA will be installed to + * \param title_type Type of NCA package to install + * \param callback Callback to report the progress of the installation. The first size_t + * parameter is the total size of the virtual file and the second is the current progress. If you + * return true to the callback, it will cancel the installation as soon as possible. + * \return [InstallResult] representing how the installation finished + */ +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) { + 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::OpenMode::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; + } +} + +/** + * \brief Verifies the installed contents for a given ManualContentProvider + * \param system Reference to the system instance + * \param provider Reference to the content provider that's tracking indexed games + * \param callback Callback to report the progress of the installation. The first size_t + * parameter is the total size of the installed contents and the second is the current progress. If + * you return true to the callback, it will cancel the installation as soon as possible. + * \return A list of entries that failed to install. Returns an empty vector if successful. + */ +inline std::vector<std::string> VerifyInstalledContents( + Core::System& system, FileSys::ManualContentProvider& provider, + const std::function<bool(size_t, size_t)>& callback) { + // Get content registries. + auto bis_contents = system.GetFileSystemController().GetSystemNANDContents(); + auto user_contents = system.GetFileSystemController().GetUserNANDContents(); + + std::vector<FileSys::RegisteredCache*> content_providers; + if (bis_contents) { + content_providers.push_back(bis_contents); + } + if (user_contents) { + content_providers.push_back(user_contents); + } + + // Get associated NCA files. + std::vector<FileSys::VirtualFile> nca_files; + + // Get all installed IDs. + size_t total_size = 0; + for (auto nca_provider : content_providers) { + const auto entries = nca_provider->ListEntriesFilter(); + + for (const auto& entry : entries) { + auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); + if (!nca_file) { + continue; + } + + total_size += nca_file->GetSize(); + nca_files.push_back(std::move(nca_file)); + } + } + + // Declare a list of file names which failed to verify. + std::vector<std::string> failed; + + size_t processed_size = 0; + bool cancelled = false; + auto nca_callback = [&](size_t nca_processed, size_t nca_total) { + cancelled = callback(total_size, processed_size + nca_processed); + return !cancelled; + }; + + // Using the NCA loader, determine if all NCAs are valid. + for (auto& nca_file : nca_files) { + Loader::AppLoader_NCA nca_loader(nca_file); + + auto status = nca_loader.VerifyIntegrity(nca_callback); + if (cancelled) { + break; + } + if (status != Loader::ResultStatus::Success) { + FileSys::NCA nca(nca_file); + const auto title_id = nca.GetTitleId(); + std::string title_name = "unknown"; + + const auto control = provider.GetEntry(FileSys::GetBaseTitleID(title_id), + FileSys::ContentRecordType::Control); + if (control && control->GetStatus() == Loader::ResultStatus::Success) { + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + provider}; + const auto [nacp, logo] = pm.ParseControlNCA(*control); + if (nacp) { + title_name = nacp->GetApplicationName(); + } + } + + if (title_id > 0) { + failed.push_back( + fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); + } else { + failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); + } + } + + processed_size += nca_file->GetSize(); + } + return failed; +} + +/** + * \brief Verifies the contents of a given game + * \param system Reference to the system instance + * \param game_path Patch to the game file + * \param callback Callback to report the progress of the installation. The first size_t + * parameter is the total size of the installed contents and the second is the current progress. If + * you return true to the callback, it will cancel the installation as soon as possible. + * \return GameVerificationResult representing how the verification process finished + */ +inline GameVerificationResult VerifyGameContents( + Core::System& system, const std::string& game_path, + const std::function<bool(size_t, size_t)>& callback) { + const auto loader = Loader::GetLoader( + system, system.GetFilesystem()->OpenFile(game_path, FileSys::OpenMode::Read)); + if (loader == nullptr) { + return GameVerificationResult::NotImplemented; + } + + bool cancelled = false; + auto loader_callback = [&](size_t processed, size_t total) { + cancelled = callback(total, processed); + return !cancelled; + }; + + const auto status = loader->VerifyIntegrity(loader_callback); + if (cancelled || status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { + return GameVerificationResult::NotImplemented; + } + + if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { + return GameVerificationResult::Failed; + } + return GameVerificationResult::Success; +} + +/** + * Checks if the keys required for decrypting firmware and games are available + */ +inline bool AreKeysPresent() { + return !Core::Crypto::KeyManager::Instance().BaseDeriveNecessary(); +} + +} // namespace ContentManager |