summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt21
-rw-r--r--README.md4
-rw-r--r--src/common/alignment.h4
-rw-r--r--src/core/file_sys/bis_factory.cpp23
-rw-r--r--src/core/file_sys/sdmc_factory.cpp4
-rw-r--r--src/core/file_sys/vfs_real.cpp27
-rw-r--r--src/core/hle/service/am/am.cpp14
-rw-r--r--src/core/settings.h29
-rw-r--r--src/input_common/CMakeLists.txt3
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp11
-rw-r--r--src/input_common/gcadapter/gc_adapter.h15
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp42
-rw-r--r--src/input_common/gcadapter/gc_poller.h2
-rw-r--r--src/input_common/main.cpp1
-rw-r--r--src/input_common/udp/client.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp12
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/configuration/config.cpp29
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp27
-rw-r--r--src/yuzu/configuration/configure_filesystem.ui121
-rw-r--r--src/yuzu/configuration/configure_general.cpp2
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/install_dialog.cpp72
-rw-r--r--src/yuzu/install_dialog.h36
-rw-r--r--src/yuzu/main.cpp358
-rw-r--r--src/yuzu/main.h15
-rw-r--r--src/yuzu/main.ui2
-rw-r--r--src/yuzu_cmd/config.cpp9
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)
diff --git a/README.md b/README.md
index 1e9f67e08..e4ecb531d 100644
--- a/README.md
+++ b/README.md
@@ -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);