diff options
28 files changed, 468 insertions, 427 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c0e49c03..ce46a2c2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,8 +118,17 @@ message(STATUS "Target architecture: ${ARCHITECTURE}") # Configure C++ standard # =========================== -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) +# boost asio's concept usage doesn't play nicely with some compilers yet. +add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS) +if (MSVC) + add_compile_options(/std:c++latest) + + # cubeb and boost still make use of deprecated result_of. + add_definitions(-D_HAS_DEPRECATED_RESULT_OF) +else() + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() # Output binaries to bin/ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) @@ -330,14 +339,16 @@ elseif(SDL2_FOUND) endif() # Ensure libusb is properly configured (based on dolphin libusb include) -include(FindPkgConfig) -find_package(LibUSB) +if(NOT APPLE) + include(FindPkgConfig) + find_package(LibUSB) +endif() if (NOT LIBUSB_FOUND) add_subdirectory(externals/libusb) + set(LIBUSB_INCLUDE_DIR "") set(LIBUSB_LIBRARIES usb) endif() - # Prefer the -pthread flag on Linux. set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -2,7 +2,7 @@ yuzu emulator ============= [![Travis CI Build Status](https://travis-ci.com/yuzu-emu/yuzu.svg?branch=master)](https://travis-ci.com/yuzu-emu/yuzu) [![Azure Mainline CI Build Status](https://dev.azure.com/yuzu-emu/yuzu/_apis/build/status/yuzu%20mainline?branchName=master)](https://dev.azure.com/yuzu-emu/yuzu/) -[![Discord](https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white)](https://discord.gg/XQV6dn9) +[![Discord](https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white)](https://discord.com/invite/u77vRWY) yuzu is an experimental open-source emulator for the Nintendo Switch from the creators of [Citra](https://citra-emu.org/). @@ -16,7 +16,7 @@ yuzu is licensed under the GPLv2 (or any later version). Refer to the license.tx Check out our [website](https://yuzu-emu.org/)! -For development discussion, please join us on [Discord](https://discord.gg/XQV6dn9). +For development discussion, please join us on [Discord](https://discord.com/invite/u77vRWY). ### Development diff --git a/src/common/alignment.h b/src/common/alignment.h index f8c49e079..b37044bb6 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -11,7 +11,9 @@ namespace Common { template <typename T> constexpr T AlignUp(T value, std::size_t size) { static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); - return static_cast<T>(value + (size - value % size) % size); + auto mod{static_cast<T>(value % size)}; + value -= mod; + return static_cast<T>(mod == T{0} ? value : value + size); } template <typename T> diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 8935a62c3..285277ef8 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -12,6 +12,10 @@ namespace FileSys { +constexpr u64 NAND_USER_SIZE = 0x680000000; // 26624 MiB +constexpr u64 NAND_SYSTEM_SIZE = 0xA0000000; // 2560 MiB +constexpr u64 NAND_TOTAL_SIZE = 0x747C00000; // 29820 MiB + BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir dump_root_) : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)), dump_root(std::move(dump_root_)), @@ -110,30 +114,29 @@ VirtualDir BISFactory::GetImageDirectory() const { u64 BISFactory::GetSystemNANDFreeSpace() const { const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system"); - if (sys_dir == nullptr) - return 0; + if (sys_dir == nullptr) { + return GetSystemNANDTotalSpace(); + } return GetSystemNANDTotalSpace() - sys_dir->GetSize(); } u64 BISFactory::GetSystemNANDTotalSpace() const { - return static_cast<u64>(Settings::values.nand_system_size); + return NAND_SYSTEM_SIZE; } u64 BISFactory::GetUserNANDFreeSpace() const { - const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user"); - if (usr_dir == nullptr) - return 0; - - return GetUserNANDTotalSpace() - usr_dir->GetSize(); + // For some reason games such as BioShock 1 checks whether this is exactly 0x680000000 bytes. + // Set the free space to be 1 MiB less than the total as a workaround to this issue. + return GetUserNANDTotalSpace() - 0x100000; } u64 BISFactory::GetUserNANDTotalSpace() const { - return static_cast<u64>(Settings::values.nand_user_size); + return NAND_USER_SIZE; } u64 BISFactory::GetFullNANDTotalSpace() const { - return static_cast<u64>(Settings::values.nand_total_size); + return NAND_TOTAL_SIZE; } VirtualDir BISFactory::GetBCATDirectory(u64 title_id) const { diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index 5113a1ca6..6f732e4d8 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -10,6 +10,8 @@ namespace FileSys { +constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB + SDMCFactory::SDMCFactory(VirtualDir dir_) : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>( GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), @@ -46,7 +48,7 @@ u64 SDMCFactory::GetSDMCFreeSpace() const { } u64 SDMCFactory::GetSDMCTotalSpace() const { - return static_cast<u64>(Settings::values.sdmc_size); + return SDMC_TOTAL_SIZE; } } // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index e21300a7c..96ce5957c 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -112,19 +112,26 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_ const auto new_path = FileUtil::SanitizePath(new_path_, FileUtil::DirectorySeparator::PlatformDefault); - if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || - FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) - return nullptr; - if (cache.find(old_path) != cache.end()) { - auto cached = cache[old_path]; - if (!cached.expired()) { - auto file = cached.lock(); - file->Open(new_path, "r+b"); - cache.erase(old_path); - cache[new_path] = file; + auto file = cache[old_path].lock(); + + if (!cache[old_path].expired()) { + file->Close(); + } + + if (!FileUtil::Exists(old_path) || FileUtil::Exists(new_path) || + FileUtil::IsDirectory(old_path) || !FileUtil::Rename(old_path, new_path)) { + return nullptr; } + + cache.erase(old_path); + file->Open(new_path, "r+b"); + cache[new_path] = file; + } else { + UNREACHABLE(); + return nullptr; } + return OpenFile(new_path, Mode::ReadWrite); } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 256449aa7..4e7a0bec9 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1407,7 +1407,19 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { u32 supported_languages = 0; FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; - const auto res = pm.GetControlMetadata(); + const auto res = [this] { + const auto title_id = system.CurrentProcess()->GetTitleID(); + + FileSys::PatchManager pm{title_id}; + auto res = pm.GetControlMetadata(); + if (res.first != nullptr) { + return res; + } + + FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)}; + return pm_update.GetControlMetadata(); + }(); + if (res.first != nullptr) { supported_languages = res.first->GetSupportedLanguages(); } diff --git a/src/core/settings.h b/src/core/settings.h index b3451a704..3eb336f75 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -346,31 +346,6 @@ struct TouchscreenInput { u32 rotation_angle; }; -enum class NANDTotalSize : u64 { - S29_1GB = 0x747C00000ULL, -}; - -enum class NANDUserSize : u64 { - S26GB = 0x680000000ULL, -}; - -enum class NANDSystemSize : u64 { - S2_5GB = 0xA0000000, -}; - -enum class SDMCSize : u64 { - S1GB = 0x40000000, - S2GB = 0x80000000, - S4GB = 0x100000000ULL, - S8GB = 0x200000000ULL, - S16GB = 0x400000000ULL, - S32GB = 0x800000000ULL, - S64GB = 0x1000000000ULL, - S128GB = 0x2000000000ULL, - S256GB = 0x4000000000ULL, - S1TB = 0x10000000000ULL, -}; - enum class RendererBackend { OpenGL = 0, Vulkan = 1, @@ -508,10 +483,6 @@ struct Values { bool gamecard_inserted; bool gamecard_current_game; std::string gamecard_path; - NANDTotalSize nand_total_size; - NANDSystemSize nand_system_size; - NANDUserSize nand_user_size; - SDMCSize sdmc_size; // Debugging bool record_frame_times; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 3bd76dd23..317c25bad 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -30,7 +30,8 @@ if(SDL2_FOUND) target_compile_definitions(input_common PRIVATE HAVE_SDL2) endif() -target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES}) +target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR}) +target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES}) create_target_directory_groups(input_common) target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index 6d9f4d9eb..38210ffcb 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -4,6 +4,7 @@ #include <chrono> #include <thread> +#include <libusb.h> #include "common/logging/log.h" #include "input_common/gcadapter/gc_adapter.h" @@ -33,7 +34,7 @@ Adapter::Adapter() { } } -GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) { +GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { GCPadStatus pad = {}; bool get_origin = false; @@ -198,7 +199,7 @@ void Adapter::StartScanThread() { } detect_thread_running = true; - detect_thread = std::thread([=] { ScanThreadFunc(); }); + detect_thread = std::thread(&Adapter::ScanThreadFunc, this); } void Adapter::StopScanThread() { @@ -227,7 +228,7 @@ void Adapter::Setup() { } if (devices != nullptr) { - for (std::size_t index = 0; index < device_count; ++index) { + for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) { if (CheckDeviceAccess(devices[index])) { // GC Adapter found and accessible, registering it GetGCEndpoint(devices[index]); @@ -357,11 +358,11 @@ void Adapter::Reset() { } } -bool Adapter::DeviceConnected(int port) { +bool Adapter::DeviceConnected(std::size_t port) { return adapter_controllers_status[port] != ControllerTypes::None; } -void Adapter::ResetDeviceType(int port) { +void Adapter::ResetDeviceType(std::size_t port) { adapter_controllers_status[port] = ControllerTypes::None; } diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index b1c2a1958..e2cdd6255 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -8,10 +8,13 @@ #include <mutex> #include <thread> #include <unordered_map> -#include <libusb.h> #include "common/common_types.h" #include "common/threadsafe_queue.h" +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + namespace GCAdapter { enum { @@ -97,6 +100,9 @@ public: void BeginConfiguration(); void EndConfiguration(); + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port); + std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; @@ -104,7 +110,7 @@ public: const std::array<GCState, 4>& GetPadState() const; private: - GCPadStatus GetPadStatus(int port, const std::array<u8, 37>& adapter_payload); + GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload); void PadToState(const GCPadStatus& pad, GCState& state); @@ -116,11 +122,8 @@ private: /// Stop scanning for the adapter void StopScanThread(); - /// Returns true if there is a device connected to port - bool DeviceConnected(int port); - /// Resets status of device connected to port - void ResetDeviceType(int port); + void ResetDeviceType(std::size_t port); /// Returns true if we successfully gain access to GC Adapter bool CheckDeviceAccess(libusb_device* device); diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 385ce8430..b20419ec3 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -6,6 +6,7 @@ #include <list> #include <mutex> #include <utility> +#include "common/assert.h" #include "common/threadsafe_queue.h" #include "input_common/gcadapter/gc_adapter.h" #include "input_common/gcadapter/gc_poller.h" @@ -20,7 +21,10 @@ public: ~GCButton() override; bool GetStatus() const override { - return gcadapter->GetPadState()[port].buttons.at(button); + if (gcadapter->DeviceConnected(port)) { + return gcadapter->GetPadState()[port].buttons.at(button); + } + return false; } private: @@ -43,13 +47,17 @@ public: } bool GetStatus() const override { - const float axis_value = (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f; - if (trigger_if_greater) { - // TODO: Might be worthwile to set a slider for the trigger threshold. It is currently - // always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick - return axis_value > threshold; + if (gcadapter->DeviceConnected(port)) { + const float axis_value = + (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 128.0f; + if (trigger_if_greater) { + // TODO: Might be worthwile to set a slider for the trigger threshold. It is + // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick + return axis_value > threshold; + } + return axis_value < -threshold; } - return axis_value < -threshold; + return false; } private: @@ -94,9 +102,12 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater, adapter.get()); } + + UNREACHABLE(); + return nullptr; } -Common::ParamPackage GCButtonFactory::GetNextInput() { +Common::ParamPackage GCButtonFactory::GetNextInput() const { Common::ParamPackage params; GCAdapter::GCPadStatus pad; auto& queue = adapter->GetPadQueue(); @@ -147,11 +158,14 @@ public: : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {} float GetAxis(int axis) const { - std::lock_guard lock{mutex}; - // division is not by a perfect 128 to account for some variance in center location - // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range - // [20-230] - return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f; + if (gcadapter->DeviceConnected(port)) { + std::lock_guard lock{mutex}; + // division is not by a perfect 128 to account for some variance in center location + // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range + // [20-230] + return (gcadapter->GetPadState()[port].axes.at(axis) - 128.0f) / 95.0f; + } + return 0.0f; } std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { @@ -249,7 +263,7 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() { const u8 axis = static_cast<u8>(pad.axis); if (analog_x_axis == -1) { analog_x_axis = axis; - controller_number = port; + controller_number = static_cast<int>(port); } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) { analog_y_axis = axis; } diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index e96af7d51..0527f328f 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/src/input_common/gcadapter/gc_poller.h @@ -25,7 +25,7 @@ public: */ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; - Common::ParamPackage GetNextInput(); + Common::ParamPackage GetNextInput() const; /// For device input configuration/polling void BeginConfiguration(); diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index fd0af1019..b9d5d0ec3 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -4,7 +4,6 @@ #include <memory> #include <thread> -#include <libusb.h> #include "common/param_package.h" #include "input_common/analog_from_button.h" #include "input_common/gcadapter/gc_adapter.h" diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index da5227058..e63c73c4f 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -234,7 +234,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( std::function<void(Status)> status_callback, std::function<void(u16, u16, u16, u16)> data_callback) { - std::thread([=] { + std::thread([=, this] { constexpr u16 CALIBRATION_THRESHOLD = 100; u16 min_x{UINT16_MAX}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 380ed532b..7625871c2 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -332,23 +332,23 @@ private: if constexpr (has_extended_dynamic_state) { // With extended dynamic states we can specify the length and stride of a vertex buffer - // std::array<VkDeviceSize, N> sizes; + std::array<VkDeviceSize, N> sizes; std::array<u16, N> strides; - // std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin()); + std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin()); std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin()); if constexpr (is_indexed) { scheduler.Record( - [buffers, offsets, strides, index = index](vk::CommandBuffer cmdbuf) { + [buffers, offsets, sizes, strides, index = index](vk::CommandBuffer cmdbuf) { cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type); cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(), - offsets.data(), nullptr, + offsets.data(), sizes.data(), ExpandStrides(strides).data()); }); } else { - scheduler.Record([buffers, offsets, strides](vk::CommandBuffer cmdbuf) { + scheduler.Record([buffers, offsets, sizes, strides](vk::CommandBuffer cmdbuf) { cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(), - offsets.data(), nullptr, + offsets.data(), sizes.data(), ExpandStrides(strides).data()); }); } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 5f175b989..a862b2610 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -104,11 +104,13 @@ add_executable(yuzu game_list_p.h game_list_worker.cpp game_list_worker.h + hotkeys.cpp + hotkeys.h + install_dialog.cpp + install_dialog.h loading_screen.cpp loading_screen.h loading_screen.ui - hotkeys.cpp - hotkeys.h main.cpp main.h main.ui diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 430e78e5f..9e9b38214 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -505,22 +505,6 @@ void Config::ReadDataStorageValues() { ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool(); Settings::values.gamecard_path = ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString(); - Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>( - ReadSetting(QStringLiteral("nand_total_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB))) - .toULongLong()); - Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>( - ReadSetting(QStringLiteral("nand_user_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB))) - .toULongLong()); - Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( - ReadSetting(QStringLiteral("nand_system_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB))) - .toULongLong()); - Settings::values.sdmc_size = static_cast<Settings::SDMCSize>( - ReadSetting(QStringLiteral("sdmc_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB))) - .toULongLong()); qt_config->endGroup(); } @@ -1034,18 +1018,7 @@ void Config::SaveDataStorageValues() { false); WriteSetting(QStringLiteral("gamecard_path"), QString::fromStdString(Settings::values.gamecard_path), QStringLiteral("")); - WriteSetting(QStringLiteral("nand_total_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)), - QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB))); - WriteSetting(QStringLiteral("nand_user_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)), - QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB))); - WriteSetting(QStringLiteral("nand_system_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)), - QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB))); - WriteSetting(QStringLiteral("sdmc_size"), - QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)), - QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB))); + qt_config->endGroup(); } diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 835ee821c..a089f5733 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -11,19 +11,6 @@ #include "yuzu/configuration/configure_filesystem.h" #include "yuzu/uisettings.h" -namespace { - -template <typename T> -void SetComboBoxFromData(QComboBox* combo_box, T data) { - const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data))); - if (index >= combo_box->count() || index < 0) - return; - - combo_box->setCurrentIndex(index); -} - -} // Anonymous namespace - ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) { ui->setupUi(this); @@ -73,11 +60,6 @@ void ConfigureFilesystem::setConfiguration() { ui->cache_game_list->setChecked(UISettings::values.cache_game_list); - SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size); - SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size); - SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size); - SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size); - UpdateEnabledControls(); } @@ -98,15 +80,6 @@ void ConfigureFilesystem::applyConfiguration() { Settings::values.dump_nso = ui->dump_nso->isChecked(); UISettings::values.cache_game_list = ui->cache_game_list->isChecked(); - - Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>( - ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong()); - Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( - ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong()); - Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>( - ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong()); - Settings::values.sdmc_size = static_cast<Settings::SDMCSize>( - ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong()); } void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) { diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui index 58cd07f52..84bea0600 100644 --- a/src/yuzu/configuration/configure_filesystem.ui +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -116,127 +116,6 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> - <property name="title"> - <string>Storage Sizes</string> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="3" column="0"> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>SD Card</string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>System NAND</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QComboBox" name="sysnand_size"> - <item> - <property name="text"> - <string>2.5 GB</string> - </property> - </item> - </widget> - </item> - <item row="3" column="1"> - <widget class="QComboBox" name="sdmc_size"> - <property name="currentText"> - <string>32 GB</string> - </property> - <item> - <property name="text"> - <string>1 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>2 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>4 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>8 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>16 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>32 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>64 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>128 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>256 GB</string> - </property> - </item> - <item> - <property name="text"> - <string>1 TB</string> - </property> - </item> - </widget> - </item> - <item row="2" column="1"> - <widget class="QComboBox" name="usrnand_size"> - <item> - <property name="text"> - <string>26 GB</string> - </property> - </item> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_6"> - <property name="text"> - <string>User NAND</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string>NAND</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="nand_size"> - <item> - <property name="text"> - <string>29.1 GB</string> - </property> - </item> - </widget> - </item> - </layout> - </widget> - </item> - <item> <widget class="QGroupBox" name="groupBox_4"> <property name="title"> <string>Patch Manager</string> diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 1fb62d1cf..20316c9cc 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -65,6 +65,8 @@ void ConfigureGeneral::ApplyConfiguration() { Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() == Qt::Checked); Settings::values.frame_limit.SetValue(ui->frame_limit->value()); + } + if (Settings::values.use_multi_core.UsingGlobal()) { Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked()); } } else { diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index bfb600df0..ab7fc7a24 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -531,8 +531,8 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { UISettings::GameDir& game_dir = *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); - QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up")); - QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down ")); + QAction* move_up = context_menu.addAction(tr("\u25B2 Move Up")); + QAction* move_down = context_menu.addAction(tr("\u25bc Move Down")); QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location")); const int row = selected.row(); diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp new file mode 100644 index 000000000..06b0b1874 --- /dev/null +++ b/src/yuzu/install_dialog.cpp @@ -0,0 +1,72 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QCheckBox> +#include <QDialogButtonBox> +#include <QFileInfo> +#include <QHBoxLayout> +#include <QLabel> +#include <QListWidget> +#include <QVBoxLayout> +#include "yuzu/install_dialog.h" +#include "yuzu/uisettings.h" + +InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialog(parent) { + file_list = new QListWidget(this); + + for (const QString& file : files) { + QListWidgetItem* item = new QListWidgetItem(QFileInfo(file).fileName(), file_list); + item->setData(Qt::UserRole, file); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); + } + + file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 11) / 10); + + vbox_layout = new QVBoxLayout; + + hbox_layout = new QHBoxLayout; + + description = new QLabel(tr("Please confirm these are the files you wish to install.")); + + update_description = + new QLabel(tr("Installing an Update or DLC will overwrite the previously installed one.")); + + buttons = new QDialogButtonBox; + buttons->addButton(QDialogButtonBox::Cancel); + buttons->addButton(tr("Install"), QDialogButtonBox::AcceptRole); + + connect(buttons, &QDialogButtonBox::accepted, this, &InstallDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &InstallDialog::reject); + + hbox_layout->addWidget(buttons); + + vbox_layout->addWidget(description); + vbox_layout->addWidget(update_description); + vbox_layout->addWidget(file_list); + vbox_layout->addLayout(hbox_layout); + + setLayout(vbox_layout); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowTitle(tr("Install Files to NAND")); +} + +InstallDialog::~InstallDialog() = default; + +QStringList InstallDialog::GetFiles() const { + QStringList files; + + for (int i = 0; i < file_list->count(); ++i) { + const QListWidgetItem* item = file_list->item(i); + if (item->checkState() == Qt::Checked) { + files.append(item->data(Qt::UserRole).toString()); + } + } + + return files; +} + +int InstallDialog::GetMinimumWidth() const { + return file_list->width(); +} diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h new file mode 100644 index 000000000..e4aba1b06 --- /dev/null +++ b/src/yuzu/install_dialog.h @@ -0,0 +1,36 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> + +class QCheckBox; +class QDialogButtonBox; +class QHBoxLayout; +class QLabel; +class QListWidget; +class QVBoxLayout; + +class InstallDialog : public QDialog { + Q_OBJECT + +public: + explicit InstallDialog(QWidget* parent, const QStringList& files); + ~InstallDialog() override; + + QStringList GetFiles() const; + bool ShouldOverwriteFiles() const; + int GetMinimumWidth() const; + +private: + QListWidget* file_list; + + QVBoxLayout* vbox_layout; + QHBoxLayout* hbox_layout; + + QLabel* description; + QLabel* update_description; + QDialogButtonBox* buttons; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4d501a8f9..432379705 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -107,6 +107,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/hotkeys.h" +#include "yuzu/install_dialog.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" #include "yuzu/uisettings.h" @@ -847,6 +848,9 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); + connect(this, &GMainWindow::UpdateInstallProgress, this, + &GMainWindow::IncrementInstallProgress); + connect(this, &GMainWindow::EmulationStarting, render_window, &GRenderWindow::OnEmulationStarting); connect(this, &GMainWindow::EmulationStopping, render_window, @@ -1593,187 +1597,255 @@ void GMainWindow::OnMenuLoadFolder() { } } +void GMainWindow::IncrementInstallProgress() { + install_progress->setValue(install_progress->value() + 1); +} + void GMainWindow::OnMenuInstallToNAND() { const QString file_filter = tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " - "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge " + "(*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge " "Image (*.xci)"); - QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), - UISettings::values.roms_path, file_filter); - if (filename.isEmpty()) { + QStringList filenames = QFileDialog::getOpenFileNames( + this, tr("Install Files"), UISettings::values.roms_path, file_filter); + + if (filenames.isEmpty()) { return; } + InstallDialog installDialog(this, filenames); + if (installDialog.exec() == QDialog::Rejected) { + return; + } + + const QStringList files = installDialog.GetFiles(); + + if (files.isEmpty()) { + return; + } + + int remaining = filenames.size(); + + // This would only overflow above 2^43 bytes (8.796 TB) + int total_size = 0; + for (const QString& file : files) { + total_size += static_cast<int>(QFile(file).size() / 0x1000); + } + if (total_size < 0) { + LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting."); + return; + } + + QStringList new_files{}; // Newly installed files that do not yet exist in the NAND + QStringList overwritten_files{}; // Files that overwrote those existing in the NAND + QStringList failed_files{}; // Files that failed to install due to errors + + ui.action_Install_File_NAND->setEnabled(false); + + install_progress = new QProgressDialog(QStringLiteral(""), tr("Cancel"), 0, total_size, this); + install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & + ~Qt::WindowMaximizeButtonHint); + install_progress->setAttribute(Qt::WA_DeleteOnClose, true); + install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); + install_progress->show(); + + for (const QString& file : files) { + install_progress->setWindowTitle(tr("%n file(s) remaining", "", remaining)); + install_progress->setLabelText( + tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); + + QFuture<InstallResult> future; + InstallResult result; + + if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || + file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + + future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); + + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + + result = future.result(); + + } else { + result = InstallNCA(file); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + switch (result) { + case InstallResult::Success: + new_files.append(QFileInfo(file).fileName()); + break; + case InstallResult::Overwrite: + overwritten_files.append(QFileInfo(file).fileName()); + break; + case InstallResult::Failure: + failed_files.append(QFileInfo(file).fileName()); + break; + } + + --remaining; + } + + install_progress->close(); + + const QString install_results = + (new_files.isEmpty() ? QStringLiteral("") + : tr("%n file(s) were newly installed\n", "", new_files.size())) + + (overwritten_files.isEmpty() + ? QStringLiteral("") + : tr("%n file(s) were overwritten\n", "", overwritten_files.size())) + + (failed_files.isEmpty() ? QStringLiteral("") + : tr("%n file(s) failed to install\n", "", failed_files.size())); + + QMessageBox::information(this, tr("Install Results"), install_results); + game_list->PopulateAsync(UISettings::values.game_dirs); + FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list"); + ui.action_Install_File_NAND->setEnabled(true); +} + +InstallResult GMainWindow::InstallNSPXCI(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) + if (src == nullptr || dest == nullptr) { return false; - if (!dest->Resize(src->GetSize())) + } + if (!dest->Resize(src->GetSize())) { return false; + } std::array<u8, 0x1000> buffer{}; - const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size()); - - QProgressDialog progress( - tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())), - tr("Cancel"), 0, progress_maximum, this); - progress.setWindowModality(Qt::WindowModal); for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { - if (progress.wasCanceled()) { + if (install_progress->wasCanceled()) { dest->Resize(0); return false; } - const int progress_value = static_cast<int>(i / buffer.size()); - progress.setValue(progress_value); + emit UpdateInstallProgress(); const auto read = src->Read(buffer.data(), buffer.size(), i); dest->Write(buffer.data(), read, i); } - return true; }; - const auto success = [this]() { - QMessageBox::information(this, tr("Successfully Installed"), - tr("The file was successfully installed.")); - game_list->PopulateAsync(UISettings::values.game_dirs); - FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + - DIR_SEP + "game_list"); - }; - - const auto failed = [this]() { - QMessageBox::warning( - this, tr("Failed to Install"), - tr("There was an error while attempting to install the provided file. It " - "could have an incorrect format or be missing metadata. Please " - "double-check your file and try again.")); - }; - - const auto overwrite = [this]() { - return QMessageBox::question(this, tr("Failed to Install"), - tr("The file you are attempting to install already exists " - "in the cache. Would you like to overwrite it?")) == - QMessageBox::Yes; - }; - - if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || - filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - 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()) - failed(); - } else { - const auto xci = std::make_shared<FileSys::XCI>( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - nsp = xci->GetSecurePartitionNSP(); - } - - if (nsp->GetStatus() != Loader::ResultStatus::Success) { - failed(); - return; - } - const auto res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nsp, false, qt_raw_copy); - if (res == FileSys::InstallResult::Success) { - success(); - } else { - if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite()) { - const auto res2 = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nsp, true, qt_raw_copy); - if (res2 == FileSys::InstallResult::Success) { - success(); - } else { - failed(); - } - } - } else { - failed(); - } + 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 { - const auto nca = std::make_shared<FileSys::NCA>( + const auto xci = std::make_shared<FileSys::XCI>( vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - const auto id = nca->GetStatus(); + nsp = xci->GetSecurePartitionNSP(); + } - // Game updates necessary are missing base RomFS - if (id != Loader::ResultStatus::Success && - id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { - failed(); - return; - } + if (nsp->GetStatus() != Loader::ResultStatus::Success) { + return InstallResult::Failure; + } + const auto res = + Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->InstallEntry( + *nsp, true, qt_raw_copy); + if (res == FileSys::InstallResult::Success) { + return InstallResult::Success; + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + return InstallResult::Overwrite; + } else { + return InstallResult::Failure; + } +} - const QStringList tt_options{tr("System Application"), - tr("System Archive"), - tr("System Application Update"), - tr("Firmware Package (Type A)"), - tr("Firmware Package (Type B)"), - tr("Game"), - tr("Game Update"), - tr("Game DLC"), - tr("Delta Title")}; - bool ok; - const auto item = QInputDialog::getItem( - this, tr("Select NCA Install Type..."), - tr("Please select the type of title you would like to install this NCA as:\n(In " - "most instances, the default 'Game' is fine.)"), - tt_options, 5, false, &ok); - - auto index = tt_options.indexOf(item); - if (!ok || index == -1) { - QMessageBox::warning(this, tr("Failed to Install"), - tr("The title type you selected for the NCA is invalid.")); - return; +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 index is equal to or past Game, add the jump in TitleType. - if (index >= 5) { - index += static_cast<size_t>(FileSys::TitleType::Application) - - static_cast<size_t>(FileSys::TitleType::FirmwarePackageB); + if (!dest->Resize(src->GetSize())) { + return false; } - FileSys::InstallResult res; - if (index >= static_cast<s32>(FileSys::TitleType::Application)) { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false, - qt_raw_copy); - } else { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetSystemNANDContents() - ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false, - qt_raw_copy); - } + std::array<u8, 0x1000> buffer{}; - if (res == FileSys::InstallResult::Success) { - success(); - } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite()) { - const auto res2 = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), - true, qt_raw_copy); - if (res2 == FileSys::InstallResult::Success) { - success(); - } else { - failed(); - } + for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { + if (install_progress->wasCanceled()) { + dest->Resize(0); + return false; } - } else { - failed(); + + 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"), + tr("System Archive"), + tr("System Application Update"), + tr("Firmware Package (Type A)"), + tr("Firmware Package (Type B)"), + tr("Game"), + tr("Game Update"), + tr("Game DLC"), + tr("Delta Title")}; + bool ok; + const auto item = QInputDialog::getItem( + this, tr("Select NCA Install Type..."), + tr("Please select the type of title you would like to install this NCA as:\n(In " + "most instances, the default 'Game' is fine.)"), + tt_options, 5, false, &ok); + + auto index = tt_options.indexOf(item); + if (!ok || index == -1) { + QMessageBox::warning(this, tr("Failed to Install"), + tr("The title type you selected for the NCA is invalid.")); + return InstallResult::Failure; + } + + // If index is equal to or past Game, add the jump in TitleType. + if (index >= 5) { + index += static_cast<size_t>(FileSys::TitleType::Application) - + static_cast<size_t>(FileSys::TitleType::FirmwarePackageB); + } + + FileSys::InstallResult res; + if (index >= static_cast<s32>(FileSys::TitleType::Application)) { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); + } else { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetSystemNANDContents() + ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); + } + + if (res == FileSys::InstallResult::Success) { + return InstallResult::Success; + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + return InstallResult::Overwrite; + } else { + return InstallResult::Failure; } } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8e3d39c38..adff65fb5 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -28,6 +28,7 @@ class MicroProfileDialog; class ProfilerWidget; class QLabel; class QPushButton; +class QProgressDialog; class WaitTreeWidget; enum class GameListOpenTarget; class GameListPlaceholder; @@ -47,6 +48,12 @@ enum class EmulatedDirectoryTarget { SDMC, }; +enum class InstallResult { + Success, + Overwrite, + Failure, +}; + enum class ReinitializeKeyBehavior { NoWarning, Warning, @@ -102,6 +109,8 @@ signals: // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); + void UpdateInstallProgress(); + void ErrorDisplayFinished(); void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid); @@ -198,6 +207,7 @@ private slots: void OnGameListOpenPerGameProperties(const std::string& file); void OnMenuLoadFile(); void OnMenuLoadFolder(); + void IncrementInstallProgress(); void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); @@ -218,6 +228,8 @@ private slots: private: std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); + InstallResult InstallNSPXCI(const QString& filename); + InstallResult InstallNCA(const QString& filename); void UpdateWindowTitle(const std::string& title_name = {}, const std::string& title_version = {}); void UpdateStatusBar(); @@ -272,6 +284,9 @@ private: HotkeyRegistry hotkey_registry; + // Install progress dialog + QProgressDialog* install_progress; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index bee6e107e..c3a1d715e 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -130,7 +130,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Install File to NAND...</string> + <string>Install Files to NAND...</string> </property> </action> <action name="action_Load_File"> diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 441d8e8f7..7773228c8 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -335,15 +335,6 @@ void Config::ReadValues() { Settings::values.gamecard_current_game = sdl2_config->GetBoolean("Data Storage", "gamecard_current_game", false); Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", ""); - Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(sdl2_config->GetInteger( - "Data Storage", "nand_total_size", static_cast<long>(Settings::NANDTotalSize::S29_1GB))); - Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(sdl2_config->GetInteger( - "Data Storage", "nand_user_size", static_cast<long>(Settings::NANDUserSize::S26GB))); - Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( - sdl2_config->GetInteger("Data Storage", "nand_system_size", - static_cast<long>(Settings::NANDSystemSize::S2_5GB))); - Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(sdl2_config->GetInteger( - "Data Storage", "sdmc_size", static_cast<long>(Settings::SDMCSize::S16GB))); // System Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); |