diff --git a/dist/qt_themes/colorful/icons/256x256/plus_folder.png b/dist/qt_themes/colorful/icons/256x256/plus_folder.png index 31471e3a2a..92539549ee 100644 Binary files a/dist/qt_themes/colorful/icons/256x256/plus_folder.png and b/dist/qt_themes/colorful/icons/256x256/plus_folder.png differ diff --git a/dist/qt_themes/colorful/icons/48x48/download.png b/dist/qt_themes/colorful/icons/48x48/download.png new file mode 100644 index 0000000000..baceb7ae99 Binary files /dev/null and b/dist/qt_themes/colorful/icons/48x48/download.png differ diff --git a/dist/qt_themes/colorful/icons/48x48/upload.png b/dist/qt_themes/colorful/icons/48x48/upload.png new file mode 100644 index 0000000000..053b8c3fea Binary files /dev/null and b/dist/qt_themes/colorful/icons/48x48/upload.png differ diff --git a/dist/qt_themes/colorful/icons/48x48/user-trash.png b/dist/qt_themes/colorful/icons/48x48/user-trash.png new file mode 100644 index 0000000000..19ce265069 Binary files /dev/null and b/dist/qt_themes/colorful/icons/48x48/user-trash.png differ diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc index 82cd367be9..f64d405707 100644 --- a/dist/qt_themes/colorful/style.qrc +++ b/dist/qt_themes/colorful/style.qrc @@ -18,6 +18,9 @@ SPDX-License-Identifier: GPL-2.0-or-later icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png + icons/48x48/user-trash.png + icons/48x48/download.png + icons/48x48/upload.png icons/48x48/list-add.png icons/48x48/no_avatar.png icons/48x48/sd_card.png diff --git a/dist/qt_themes/colorful_midnight_blue/style.qrc b/dist/qt_themes/colorful_midnight_blue/style.qrc index b9821c6722..18f8a6b823 100644 --- a/dist/qt_themes/colorful_midnight_blue/style.qrc +++ b/dist/qt_themes/colorful_midnight_blue/style.qrc @@ -11,6 +11,9 @@ SPDX-License-Identifier: GPL-2.0-or-later ../colorful/icons/48x48/bad_folder.png ../colorful/icons/48x48/chip.png ../colorful/icons/48x48/folder.png + ../colorful/icons/48x48/user-trash.png + ../colorful/icons/48x48/download.png + ../colorful/icons/48x48/upload.png ../colorful/icons/48x48/list-add.png ../colorful/icons/48x48/sd_card.png ../colorful/icons/256x256/plus_folder.png diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index 0d8af20fc1..e59f0ade9c 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc @@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-2.0-or-later icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png + icons/48x48/user-trash.png + icons/48x48/download.png + icons/48x48/upload.png icons/48x48/list-add.png icons/48x48/sd_card.png icons/48x48/star.png diff --git a/dist/qt_themes/default/icons/256x256/eden.png b/dist/qt_themes/default/icons/256x256/eden.png index c0d7d8e37c..dee7b91dc7 100644 Binary files a/dist/qt_themes/default/icons/256x256/eden.png and b/dist/qt_themes/default/icons/256x256/eden.png differ diff --git a/dist/qt_themes/default/icons/48x48/download.png b/dist/qt_themes/default/icons/48x48/download.png new file mode 100644 index 0000000000..c0e3213655 Binary files /dev/null and b/dist/qt_themes/default/icons/48x48/download.png differ diff --git a/dist/qt_themes/default/icons/48x48/upload.png b/dist/qt_themes/default/icons/48x48/upload.png new file mode 100644 index 0000000000..46120cbdaf Binary files /dev/null and b/dist/qt_themes/default/icons/48x48/upload.png differ diff --git a/dist/qt_themes/default/icons/48x48/user-trash.png b/dist/qt_themes/default/icons/48x48/user-trash.png new file mode 100644 index 0000000000..ef360a4775 Binary files /dev/null and b/dist/qt_themes/default/icons/48x48/user-trash.png differ diff --git a/dist/qt_themes/default_dark/style.qrc b/dist/qt_themes/default_dark/style.qrc index 7de4737c2c..b76f9a0bda 100644 --- a/dist/qt_themes/default_dark/style.qrc +++ b/dist/qt_themes/default_dark/style.qrc @@ -13,6 +13,9 @@ SPDX-License-Identifier: GPL-2.0-or-later ../colorful/icons/48x48/bad_folder.png ../colorful/icons/48x48/chip.png ../colorful/icons/48x48/folder.png + ../colorful/icons/48x48/user-trash.png + ../colorful/icons/48x48/download.png + ../colorful/icons/48x48/upload.png ../qdarkstyle/icons/48x48/no_avatar.png ../colorful/icons/48x48/list-add.png ../colorful/icons/48x48/sd_card.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/download.png b/dist/qt_themes/qdarkstyle/icons/48x48/download.png new file mode 100644 index 0000000000..d4d26fe2ea Binary files /dev/null and b/dist/qt_themes/qdarkstyle/icons/48x48/download.png differ diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/upload.png b/dist/qt_themes/qdarkstyle/icons/48x48/upload.png new file mode 100644 index 0000000000..ce255fb467 Binary files /dev/null and b/dist/qt_themes/qdarkstyle/icons/48x48/upload.png differ diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/user-trash.png b/dist/qt_themes/qdarkstyle/icons/48x48/user-trash.png new file mode 100644 index 0000000000..e273528101 Binary files /dev/null and b/dist/qt_themes/qdarkstyle/icons/48x48/user-trash.png differ diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc index a89fb26c68..3902996058 100644 --- a/dist/qt_themes/qdarkstyle/style.qrc +++ b/dist/qt_themes/qdarkstyle/style.qrc @@ -9,6 +9,9 @@ icons/48x48/bad_folder.png icons/48x48/chip.png icons/48x48/folder.png + icons/48x48/user-trash.png + icons/48x48/download.png + icons/48x48/upload.png icons/48x48/no_avatar.png icons/48x48/list-add.png icons/48x48/sd_card.png diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc index dc3d7fecbd..75212008a3 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc @@ -6,6 +6,9 @@ ../qdarkstyle/icons/48x48/bad_folder.png ../qdarkstyle/icons/48x48/chip.png ../qdarkstyle/icons/48x48/folder.png + ../qdarkstyle/icons/48x48/user-trash.png + ../qdarkstyle/icons/48x48/download.png + ../qdarkstyle/icons/48x48/upload.png ../qdarkstyle/icons/48x48/no_avatar.png ../qdarkstyle/icons/48x48/list-add.png ../qdarkstyle/icons/48x48/sd_card.png diff --git a/src/android/app/src/main/legacy/drawable/ic_icon_bg.png b/src/android/app/src/main/legacy/drawable/ic_icon_bg.png index 3327014f8f..f3d4c55dea 100644 Binary files a/src/android/app/src/main/legacy/drawable/ic_icon_bg.png and b/src/android/app/src/main/legacy/drawable/ic_icon_bg.png differ diff --git a/src/android/app/src/main/legacy/drawable/ic_icon_bg_orig.png b/src/android/app/src/main/legacy/drawable/ic_icon_bg_orig.png index a9fc55a4f5..5859c43f86 100644 Binary files a/src/android/app/src/main/legacy/drawable/ic_icon_bg_orig.png and b/src/android/app/src/main/legacy/drawable/ic_icon_bg_orig.png differ diff --git a/src/android/app/src/main/res/drawable/ic_icon_bg.png b/src/android/app/src/main/res/drawable/ic_icon_bg.png index 7da2c6a1a3..db7e53410d 100644 Binary files a/src/android/app/src/main/res/drawable/ic_icon_bg.png and b/src/android/app/src/main/res/drawable/ic_icon_bg.png differ diff --git a/src/android/app/src/main/res/drawable/ic_icon_bg_orig.png b/src/android/app/src/main/res/drawable/ic_icon_bg_orig.png index 18325c031a..ddb43b349c 100644 Binary files a/src/android/app/src/main/res/drawable/ic_icon_bg_orig.png and b/src/android/app/src/main/res/drawable/ic_icon_bg_orig.png differ diff --git a/src/common/settings.h b/src/common/settings.h index dd9b03f28e..c6b52f7ba3 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -161,7 +161,7 @@ struct Values { Category::LibraryApplet}; Setting photo_viewer_applet_mode{ linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet}; - Setting offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode", + Setting offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode", Category::LibraryApplet}; Setting login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode", Category::LibraryApplet}; diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 12ea5f7aa1..4a892f7c65 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -509,9 +509,6 @@ std::vector ProfileManager::FindOrphanedProfiles() good_uuids.emplace_back(uuid_string); } - // used for acnh, etc - good_uuids.emplace_back("00000000000000000000000000000000"); - // TODO: fetch save_id programmatically const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000"; diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt index 70e142bb0c..aa44740596 100644 --- a/src/frontend_common/CMakeLists.txt +++ b/src/frontend_common/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later @@ -7,6 +10,7 @@ add_library(frontend_common STATIC content_manager.h firmware_manager.h firmware_manager.cpp + data_manager.h data_manager.cpp ) create_target_directory_groups(frontend_common) diff --git a/src/frontend_common/data_manager.cpp b/src/frontend_common/data_manager.cpp new file mode 100644 index 0000000000..e83e3ada40 --- /dev/null +++ b/src/frontend_common/data_manager.cpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "data_manager.h" +#include "common/assert.h" +#include "common/fs/path_util.h" +#include +#include + +namespace FrontendCommon::DataManager { + +namespace fs = std::filesystem; + +const std::string GetDataDir(DataDir dir) +{ + const fs::path nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir); + + switch (dir) { + case DataDir::Saves: + return (nand_dir / "user" / "save" / "0000000000000000").string(); + case DataDir::UserNand: + return (nand_dir / "user" / "Contents" / "registered").string(); + case DataDir::SysNand: + // NB: do NOT delete save + // that contains profile data and other stuff + return (nand_dir / "system" / "Contents" / "registered").string(); + case DataDir::Mods: + return Common::FS::GetEdenPathString(Common::FS::EdenPath::LoadDir); + case DataDir::Shaders: + return Common::FS::GetEdenPathString(Common::FS::EdenPath::ShaderDir); + default: + UNIMPLEMENTED(); + } + + return ""; +} + +u64 ClearDir(DataDir dir) +{ + fs::path data_dir = GetDataDir(dir); + u64 result = fs::remove_all(data_dir); + + // mkpath at the end just so it actually exists + fs::create_directories(data_dir); + return result; +} + +const std::string ReadableBytesSize(u64 size) +{ + static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"}; + if (size == 0) { + return "0 B"; + } + + const int digit_groups = (std::min) (static_cast(std::log10(size) / std::log10(1024)), + static_cast(units.size())); + return fmt::format("{:.1f} {}", size / std::pow(1024, digit_groups), units[digit_groups]); +} + +u64 DataDirSize(DataDir dir) +{ + fs::path data_dir = GetDataDir(dir); + u64 size = 0; + + if (!fs::exists(data_dir)) + return 0; + + for (const auto& entry : fs::recursive_directory_iterator(data_dir)) { + if (!entry.is_directory()) { + size += entry.file_size(); + } + } + + return size; +} + +} diff --git a/src/frontend_common/data_manager.h b/src/frontend_common/data_manager.h new file mode 100644 index 0000000000..f6ae836c14 --- /dev/null +++ b/src/frontend_common/data_manager.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DATA_MANAGER_H +#define DATA_MANAGER_H + +#include "common/common_types.h" +#include + +namespace FrontendCommon::DataManager { + +enum class DataDir { Saves, UserNand, SysNand, Mods, Shaders }; + +const std::string GetDataDir(DataDir dir); + +u64 ClearDir(DataDir dir); + +const std::string ReadableBytesSize(u64 size); + +u64 DataDirSize(DataDir dir); + +}; // namespace FrontendCommon::DataManager + +#endif // DATA_MANAGER_H diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 9b3a8fdb8d..5c4aa8c613 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -4,9 +4,6 @@ # SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -find_package(Qt6 REQUIRED COMPONENTS Core) -find_package(Qt6 REQUIRED COMPONENTS Core) - add_library(qt_common STATIC qt_common.h qt_common.cpp @@ -27,6 +24,8 @@ add_library(qt_common STATIC qt_rom_util.h qt_rom_util.cpp qt_applet_util.h qt_applet_util.cpp qt_progress_dialog.h qt_progress_dialog.cpp + qt_string_lookup.h + qt_compress.h qt_compress.cpp ) @@ -37,18 +36,34 @@ if (ENABLE_QT) target_link_libraries(qt_common PRIVATE Qt6::Widgets) endif() +target_compile_definitions(qt_common PUBLIC + # Use QStringBuilder for string concatenation to reduce + # the overall number of temporary strings created. + QT_USE_QSTRINGBUILDER + + # Disable implicit conversions from/to C strings + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + + # Disable implicit type narrowing in signal/slot connect() calls. + QT_NO_NARROWING_CONVERSIONS_IN_CONNECT + + # Disable unsafe overloads of QProcess' start() function. + QT_NO_PROCESS_COMBINED_ARGUMENT_START + + # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. + QT_NO_URL_CAST_FROM_STRING +) + add_subdirectory(externals) -target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip) +target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip) +target_link_libraries(qt_common PUBLIC frozen::frozen) if (NOT APPLE AND ENABLE_OPENGL) target_compile_definitions(qt_common PUBLIC HAS_OPENGL) endif() -if (UNIX AND NOT APPLE) - if (TARGET Qt6::GuiPrivate) - target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate) - else() - target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) - endif() +if (NOT WIN32) + target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() diff --git a/src/qt_common/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt index e7b2e7b3e6..b998090d90 100644 --- a/src/qt_common/externals/CMakeLists.txt +++ b/src/qt_common/externals/CMakeLists.txt @@ -16,5 +16,4 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON) AddJsonPackage(quazip) # frozen -# TODO(crueter): Qt String Lookup -# AddJsonPackage(frozen) +AddJsonPackage(frozen) diff --git a/src/qt_common/qt_compress.cpp b/src/qt_common/qt_compress.cpp new file mode 100644 index 0000000000..d1660b0ba8 --- /dev/null +++ b/src/qt_common/qt_compress.cpp @@ -0,0 +1,351 @@ +#include "qt_compress.h" +#include "quazipfileinfo.h" + +#include + +/** This is a modified version of JlCompress **/ +namespace QtCommon::Compress { + +bool compressDir(QString fileCompressed, + QString dir, + const JlCompress::Options &options, + QtCommon::QtProgressCallback callback) +{ + // Create zip + QuaZip zip(fileCompressed); + QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(fileCompressed); + return false; + } + + // See how big the overall fs structure is + // good approx. of total progress + // TODO(crueter): QDirListing impl + QDirIterator iter(dir, + QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files, + QDirIterator::Subdirectories); + + std::size_t total = 0; + while (iter.hasNext()) { + total += iter.nextFileInfo().size(); + } + + std::size_t progress = 0; + callback(total, progress); + + // Add the files and subdirectories + if (!compressSubDir(&zip, dir, dir, options, total, progress, callback)) { + QFile::remove(fileCompressed); + return false; + } + + // Close zip + zip.close(); + if (zip.getZipError() != 0) { + QFile::remove(fileCompressed); + return false; + } + + return true; +} + +bool compressSubDir(QuaZip *zip, + QString dir, + QString origDir, + const JlCompress::Options &options, + std::size_t total, + std::size_t &progress, + QtProgressCallback callback) +{ + // zip: object where to add the file + // dir: current real directory + // origDir: original real directory + // (path(dir)-path(origDir)) = path inside the zip object + + if (!zip) + return false; + if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend + && zip->getMode() != QuaZip::mdAdd) + return false; + + QDir directory(dir); + if (!directory.exists()) + return false; + + + QDir origDirectory(origDir); + if (dir != origDir) { + QuaZipFile dirZipFile(zip); + std::unique_ptr qzni; + if (options.getDateTime().isNull()) { + qzni = std::make_unique(origDirectory.relativeFilePath(dir) + + QLatin1String("/"), + dir); + } else { + qzni = std::make_unique(origDirectory.relativeFilePath(dir) + + QLatin1String("/"), + dir, + options.getDateTime()); + } + if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) { + return false; + } + dirZipFile.close(); + } + + // For each subfolder + QFileInfoList subfiles = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot + | QDir::Hidden | QDir::Dirs); + for (const auto &file : std::as_const(subfiles)) { + if (!compressSubDir( + zip, file.absoluteFilePath(), origDir, options, total, progress, callback)) { + return false; + } + } + + // For each file in directory + QFileInfoList files = directory.entryInfoList(QDir::Hidden | QDir::Files); + for (const auto &file : std::as_const(files)) { + // If it's not a file or it's the compressed file being created + if (!file.isFile() || file.absoluteFilePath() == zip->getZipName()) + continue; + + // Create relative name for the compressed file + QString filename = origDirectory.relativeFilePath(file.absoluteFilePath()); + + // Compress the file + if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, callback)) { + return false; + } + } + + return true; +} + +bool compressFile(QuaZip *zip, + QString fileName, + QString fileDest, + const JlCompress::Options &options, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback) +{ + // zip: object where to add the file + // fileName: real file name + // fileDest: file name inside the zip object + + if (!zip) + return false; + if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend + && zip->getMode() != QuaZip::mdAdd) + return false; + + QuaZipFile outFile(zip); + if (options.getDateTime().isNull()) { + if (!outFile.open(QIODevice::WriteOnly, + QuaZipNewInfo(fileDest, fileName), + nullptr, + 0, + options.getCompressionMethod(), + options.getCompressionLevel())) + return false; + } else { + if (!outFile.open(QIODevice::WriteOnly, + QuaZipNewInfo(fileDest, fileName, options.getDateTime()), + nullptr, + 0, + options.getCompressionMethod(), + options.getCompressionLevel())) + return false; + } + + QFileInfo input(fileName); + if (quazip_is_symlink(input)) { + // Not sure if we should use any specialized codecs here. + // After all, a symlink IS just a byte array. And + // this is mostly for Linux, where UTF-8 is ubiquitous these days. + QString path = quazip_symlink_target(input); + QString relativePath = input.dir().relativeFilePath(path); + outFile.write(QFile::encodeName(relativePath)); + } else { + QFile inFile; + inFile.setFileName(fileName); + if (!inFile.open(QIODevice::ReadOnly)) { + return false; + } + if (!copyData(inFile, outFile, total, progress, callback) || outFile.getZipError() != UNZ_OK) { + return false; + } + inFile.close(); + } + + outFile.close(); + return outFile.getZipError() == UNZ_OK; +} + +bool copyData(QIODevice &inFile, + QIODevice &outFile, + std::size_t total, + std::size_t &progress, + QtProgressCallback callback) +{ + while (!inFile.atEnd()) { + char buf[4096]; + qint64 readLen = inFile.read(buf, 4096); + if (readLen <= 0) + return false; + if (outFile.write(buf, readLen) != readLen) + return false; + + progress += readLen; + if (!callback(total, progress)) { + return false; + } + } + return true; +} + +QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback) +{ + // Open zip + QuaZip zip(fileCompressed); + return extractDir(zip, dir, callback); +} + +QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback) +{ + if (!zip.open(QuaZip::mdUnzip)) { + return QStringList(); + } + QString cleanDir = QDir::cleanPath(dir); + QDir directory(cleanDir); + QString absCleanDir = directory.absolutePath(); + if (!absCleanDir.endsWith(QLatin1Char('/'))) // It only ends with / if it's the FS root. + absCleanDir += QLatin1Char('/'); + QStringList extracted; + if (!zip.goToFirstFile()) { + return QStringList(); + } + + std::size_t total = 0; + for (const QuaZipFileInfo64 &info : zip.getFileInfoList64()) { + total += info.uncompressedSize; + } + + std::size_t progress = 0; + callback(total, progress); + + do { + QString name = zip.getCurrentFileName(); + QString absFilePath = directory.absoluteFilePath(name); + QString absCleanPath = QDir::cleanPath(absFilePath); + if (!absCleanPath.startsWith(absCleanDir)) + continue; + if (!extractFile(&zip, QLatin1String(""), absFilePath, total, progress, callback)) { + removeFile(extracted); + return QStringList(); + } + extracted.append(absFilePath); + } while (zip.goToNextFile()); + + // Close zip + zip.close(); + if (zip.getZipError() != 0) { + removeFile(extracted); + return QStringList(); + } + + return extracted; +} + +bool extractFile(QuaZip *zip, + QString fileName, + QString fileDest, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback) +{ + // zip: object where to add the file + // filename: real file name + // fileincompress: file name of the compressed file + + if (!zip) + return false; + if (zip->getMode() != QuaZip::mdUnzip) + return false; + + if (!fileName.isEmpty()) + zip->setCurrentFile(fileName); + QuaZipFile inFile(zip); + if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK) + return false; + + // Check existence of resulting file + QDir curDir; + if (fileDest.endsWith(QLatin1String("/"))) { + if (!curDir.mkpath(fileDest)) { + return false; + } + } else { + if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) { + return false; + } + } + + QuaZipFileInfo64 info; + if (!zip->getCurrentFileInfo(&info)) + return false; + + QFile::Permissions srcPerm = info.getPermissions(); + if (fileDest.endsWith(QLatin1String("/")) && QFileInfo(fileDest).isDir()) { + if (srcPerm != 0) { + QFile(fileDest).setPermissions(srcPerm); + } + return true; + } + + if (info.isSymbolicLink()) { + QString target = QFile::decodeName(inFile.readAll()); + return QFile::link(target, fileDest); + } + + // Open resulting file + QFile outFile; + outFile.setFileName(fileDest); + if (!outFile.open(QIODevice::WriteOnly)) + return false; + + // Copy data + if (!copyData(inFile, outFile, total, progress, callback) || inFile.getZipError() != UNZ_OK) { + outFile.close(); + removeFile(QStringList(fileDest)); + return false; + } + outFile.close(); + + // Close file + inFile.close(); + if (inFile.getZipError() != UNZ_OK) { + removeFile(QStringList(fileDest)); + return false; + } + + if (srcPerm != 0) { + outFile.setPermissions(srcPerm); + } + return true; +} + +bool removeFile(QStringList listFile) +{ + bool ret = true; + // For each file + for (int i = 0; i < listFile.count(); i++) { + // Remove + ret = ret && QFile::remove(listFile.at(i)); + } + return ret; +} + +} // namespace QtCommon::Compress diff --git a/src/qt_common/qt_compress.h b/src/qt_common/qt_compress.h new file mode 100644 index 0000000000..cc91e73fab --- /dev/null +++ b/src/qt_common/qt_compress.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include "qt_common/qt_common.h" + +/** This is a modified version of JlCompress **/ +namespace QtCommon::Compress { + +/** + * @brief Compress an entire directory and report its progress. + * @param fileCompressed Destination file + * @param dir The directory to compress + * @param options Compression level, etc + * @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled. + */ +bool compressDir(QString fileCompressed, + QString dir, + const JlCompress::Options& options = JlCompress::Options(), + QtCommon::QtProgressCallback callback = {}); + +// Internal // +bool compressSubDir(QuaZip *zip, + QString dir, + QString origDir, + const JlCompress::Options &options, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback); + +bool compressFile(QuaZip *zip, + QString fileName, + QString fileDest, + const JlCompress::Options &options, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback); + +bool copyData(QIODevice &inFile, + QIODevice &outFile, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback); + +// Extract // + +/** + * @brief Extract a zip file and report its progress. + * @param fileCompressed Compressed file + * @param dir The directory to push the results to + * @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled. + */ +QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback = {}); + +// Internal // +QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback); + +bool extractFile(QuaZip *zip, + QString fileName, + QString fileDest, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback); + +bool removeFile(QStringList listFile); + +} diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp index 2f659cf1b2..9072d455a7 100644 --- a/src/qt_common/qt_content_util.cpp +++ b/src/qt_common/qt_content_util.cpp @@ -1,17 +1,22 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "qt_common/qt_game_util.h" #include "qt_content_util.h" #include "common/fs/fs.h" #include "core/hle/service/acc/profile_manager.h" #include "frontend_common/content_manager.h" +#include "frontend_common/data_manager.h" #include "frontend_common/firmware_manager.h" #include "qt_common/qt_common.h" +#include "qt_common/qt_compress.h" +#include "qt_common/qt_game_util.h" #include "qt_common/qt_progress_dialog.h" #include "qt_frontend_util.h" +#include +#include #include +#include namespace QtCommon::Content { @@ -21,10 +26,10 @@ bool CheckGameFirmware(u64 program_id, QObject* parent) && !FirmwareManager::CheckFirmwarePresence(*system)) { auto result = QtCommon::Frontend::ShowMessage( QMessageBox::Warning, - "Game Requires Firmware", - "The game you are trying to launch requires firmware to boot or to get past the " - "opening menu. Please " - "dump and install firmware, or press \"OK\" to launch anyways.", + tr("Game Requires Firmware"), + tr("The game you are trying to launch requires firmware to boot or to get past the " + "opening menu. Please " + "dump and install firmware, or press \"OK\" to launch anyways."), QMessageBox::Ok | QMessageBox::Cancel, parent); @@ -60,8 +65,8 @@ void InstallFirmware(const QString& location, bool recursive) const auto ShowMessage = [&]() { QtCommon::Frontend::ShowMessage(icon, - failedTitle, - GetFirmwareInstallResultString(result)); + tr(failedTitle), + tr(GetFirmwareInstallResultString(result))); }; LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); @@ -125,8 +130,8 @@ void InstallFirmware(const QString& location, bool recursive) i++; auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read); - auto firmware_dst_vfile = firmware_vdir - ->CreateFileRelative(firmware_src_path.filename().string()); + auto firmware_dst_vfile = firmware_vdir->CreateFileRelative( + firmware_src_path.filename().string()); if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { LOG_ERROR(Frontend, @@ -168,9 +173,9 @@ void InstallFirmware(const QString& location, bool recursive) const auto failed_names = QString::fromStdString( fmt::format("{}", fmt::join(results, "\n"))); progress.close(); - QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"), - tr("Verification failed for the following files:\n\n%1") - .arg(failed_names)); + QtCommon::Frontend::Critical( + tr("Firmware integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1").arg(failed_names)); return; } @@ -181,10 +186,10 @@ void InstallFirmware(const QString& location, bool recursive) const std::string display_version(firmware_data.display_version.data()); result = FirmwareInstallResult::Success; - QtCommon::Frontend::Information(rootObject, - tr(successTitle), - tr(GetFirmwareInstallResultString(result)) - .arg(QString::fromStdString(display_version))); + QtCommon::Frontend::Information( + rootObject, + tr(successTitle), + tr(GetFirmwareInstallResultString(result)).arg(QString::fromStdString(display_version))); } QString UnzipFirmwareToTmp(const QString& location) @@ -193,7 +198,7 @@ QString UnzipFirmwareToTmp(const QString& location) fs::path tmp{fs::temp_directory_path()}; if (!fs::create_directories(tmp / "eden" / "firmware")) { - return ""; + return QString(); } tmp /= "eden"; @@ -205,7 +210,7 @@ QString UnzipFirmwareToTmp(const QString& location) QStringList result = JlCompress::extractDir(&zip, qCacheDir); if (result.isEmpty()) { - return ""; + return QString(); } return qCacheDir; @@ -264,9 +269,8 @@ void InstallKeys() return; } - FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location - .toStdString(), - "keys"); + FirmwareManager::KeyInstallResult result + = FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys"); system->GetFileSystemController().CreateFactories(*QtCommon::vfs); @@ -282,9 +286,14 @@ void InstallKeys() } } -void VerifyInstalledContents() { +void VerifyInstalledContents() +{ // Initialize a progress dialog. - QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject); + QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), + tr("Cancel"), + 0, + 100, + QtCommon::rootObject); progress.setWindowModality(Qt::WindowModal); progress.setMinimumDuration(100); progress.setAutoClose(false); @@ -296,16 +305,17 @@ void VerifyInstalledContents() { return progress.wasCanceled(); }; - const std::vector result = - ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback); + const std::vector result + = ContentManager::VerifyInstalledContents(*QtCommon::system, + *QtCommon::provider, + QtProgressCallback); progress.close(); if (result.empty()) { QtCommon::Frontend::Information(tr("Integrity verification succeeded!"), tr("The operation completed successfully.")); } else { - const auto failed_names = - QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); + const auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); QtCommon::Frontend::Critical( tr("Integrity verification failed!"), tr("Verification failed for the following files:\n\n%1").arg(failed_names)); @@ -332,7 +342,7 @@ void FixProfiles() qorphaned.reserve(8 * 33); for (const std::string& s : orphaned) { - qorphaned += "\n" + QString::fromStdString(s); + qorphaned = qorphaned % QStringLiteral("\n") % QString::fromStdString(s); } QtCommon::Frontend::Critical( @@ -348,4 +358,179 @@ void FixProfiles() QtCommon::Game::OpenSaveFolder(); } +void ClearDataDir(FrontendCommon::DataManager::DataDir dir) +{ + auto result = QtCommon::Frontend::Warning(tr("Really clear data?"), + tr("Important data may be lost!"), + QMessageBox::Yes | QMessageBox::No); + + if (result != QMessageBox::Yes) + return; + + result = QtCommon::Frontend::Warning( + tr("Are you REALLY sure?"), + tr("Once deleted, your data will NOT come back!\n" + "Only do this if you're 100% sure you want to delete this data."), + QMessageBox::Yes | QMessageBox::No); + + if (result != QMessageBox::Yes) + return; + + QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0); + dialog.show(); + + FrontendCommon::DataManager::ClearDir(dir); + + dialog.close(); +} + +void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, std::function callback) +{ + using namespace QtCommon::Frontend; + const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir); + + const QString zip_dump_location = GetSaveFileName(tr("Select Export Location"), + QStringLiteral("export.zip"), + tr("Zipped Archives (*.zip)")); + + if (zip_dump_location.isEmpty()) + return; + + QtProgressDialog* progress = new QtProgressDialog( + tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100, rootObject); + + progress->setWindowTitle(tr("Exporting")); + progress->setWindowModality(Qt::WindowModal); + progress->setMinimumDuration(100); + progress->setAutoClose(false); + progress->setAutoReset(false); + progress->show(); + + QGuiApplication::processEvents(); + + auto progress_callback = [=](size_t total_size, size_t processed_size) { + QMetaObject::invokeMethod(progress, + &QtProgressDialog::setValue, + static_cast((processed_size * 100) / total_size)); + + return !progress->wasCanceled(); + }; + + QFuture future = QtConcurrent::run([=]() { + return QtCommon::Compress::compressDir(zip_dump_location, + QString::fromStdString(dir), + JlCompress::Options(), + progress_callback); + }); + + QFutureWatcher* watcher = new QFutureWatcher(rootObject); + + QObject::connect(watcher, &QFutureWatcher::finished, rootObject, [=]() { + progress->close(); + + if (watcher->result()) { + Information(tr("Exported Successfully"), tr("Data was exported successfully.")); + } else if (progress->wasCanceled()) { + Information(tr("Export Cancelled"), tr("Export was cancelled by the user.")); + } else { + Critical( + tr("Export Failed"), + tr("Ensure you have write permissions on the targeted directory and try again.")); + } + + progress->deleteLater(); + watcher->deleteLater(); + if (callback) + callback(); + }); + + watcher->setFuture(future); +} + +void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, std::function callback) +{ + const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir); + + using namespace QtCommon::Frontend; + + const QString zip_dump_location = GetOpenFileName(tr("Select Import Location"), + {}, + tr("Zipped Archives (*.zip)")); + + if (zip_dump_location.isEmpty()) + return; + + StandardButton button = Warning( + tr("Import Warning"), + tr("All previous data in this directory will be deleted. Are you sure you wish to " + "proceed?"), + StandardButton::Yes | StandardButton::No); + + if (button != QMessageBox::Yes) + return; + + QtProgressDialog* progress = new QtProgressDialog( + tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100, rootObject); + + progress->setWindowTitle(tr("Importing")); + progress->setWindowModality(Qt::WindowModal); + progress->setMinimumDuration(100); + progress->setAutoClose(false); + progress->setAutoReset(false); + progress->show(); + progress->setValue(0); + + QGuiApplication::processEvents(); + + // to prevent GUI mangling we have to run this in a thread as well + QFuture delete_future = QtConcurrent::run([=]() { + FrontendCommon::DataManager::ClearDir(data_dir); + return !progress->wasCanceled(); + }); + + QFutureWatcher* delete_watcher = new QFutureWatcher(rootObject); + delete_watcher->setFuture(delete_future); + + QObject::connect(delete_watcher, &QFutureWatcher::finished, rootObject, [=]() { + auto progress_callback = [=](size_t total_size, size_t processed_size) { + QMetaObject::invokeMethod(progress, + &QtProgressDialog::setValue, + static_cast((processed_size * 100) / total_size)); + + return !progress->wasCanceled(); + }; + + QFuture future = QtConcurrent::run([=]() { + return !QtCommon::Compress::extractDir(zip_dump_location, + QString::fromStdString(dir), + progress_callback) + .empty(); + }); + + QFutureWatcher* watcher = new QFutureWatcher(rootObject); + + QObject::connect(watcher, &QFutureWatcher::finished, rootObject, [=]() { + progress->close(); + + // this sucks + if (watcher->result()) { + Information(tr("Imported Successfully"), tr("Data was imported successfully.")); + } else if (progress->wasCanceled()) { + Information(tr("Import Cancelled"), tr("Import was cancelled by the user.")); + } else { + Critical(tr("Import Failed"), + tr("Ensure you have read permissions on the targeted directory and try " + "again.")); + } + + progress->deleteLater(); + watcher->deleteLater(); + if (callback) + callback(); + }); + + watcher->setFuture(future); + }); +} + } // namespace QtCommon::Content diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h index b95e78c0a0..fb6d1b85fa 100644 --- a/src/qt_common/qt_content_util.h +++ b/src/qt_common/qt_content_util.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "frontend_common/data_manager.h" namespace QtCommon::Content { @@ -46,6 +47,10 @@ void InstallKeys(); void VerifyGameContents(const std::string &game_path); void VerifyInstalledContents(); +void ClearDataDir(FrontendCommon::DataManager::DataDir dir); +void ExportDataDir(FrontendCommon::DataManager::DataDir dir, std::function callback = {}); +void ImportDataDir(FrontendCommon::DataManager::DataDir dir, std::function callback = {}); + // Profiles // void FixProfiles(); } diff --git a/src/qt_common/qt_frontend_util.cpp b/src/qt_common/qt_frontend_util.cpp index d519669ad5..3fe0ba0a80 100644 --- a/src/qt_common/qt_frontend_util.cpp +++ b/src/qt_common/qt_frontend_util.cpp @@ -32,4 +32,15 @@ const QString GetOpenFileName(const QString &title, #endif } +const QString GetSaveFileName(const QString &title, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ +#ifdef YUZU_QT_WIDGETS + return QFileDialog::getSaveFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options); +#endif +} + } // namespace QtCommon::Frontend diff --git a/src/qt_common/qt_frontend_util.h b/src/qt_common/qt_frontend_util.h index f86b9e1357..59e8f15a4e 100644 --- a/src/qt_common/qt_frontend_util.h +++ b/src/qt_common/qt_frontend_util.h @@ -97,10 +97,10 @@ Q_ENUM_NS(Icon) // TODO(crueter) widgets-less impl, choices et al. StandardButton ShowMessage(Icon icon, - const QString &title, - const QString &text, - StandardButtons buttons = StandardButton::NoButton, - QObject *parent = nullptr); + const QString &title, + const QString &text, + StandardButtons buttons = StandardButton::NoButton, + QObject *parent = nullptr); #define UTIL_OVERRIDES(level) \ inline StandardButton level(QObject *parent, \ @@ -110,21 +110,6 @@ StandardButton ShowMessage(Icon icon, { \ return ShowMessage(Icon::level, title, text, buttons, parent); \ } \ - inline StandardButton level(QObject *parent, \ - const char *title, \ - const char *text, \ - StandardButtons buttons \ - = StandardButton::Ok) \ - { \ - return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \ - } \ - inline StandardButton level(const char *title, \ - const char *text, \ - StandardButtons buttons \ - = StandardButton::Ok) \ - { \ - return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \ - } \ inline StandardButton level(const QString title, \ const QString &text, \ StandardButtons buttons \ @@ -144,5 +129,11 @@ const QString GetOpenFileName(const QString &title, QString *selectedFilter = nullptr, Options options = Options()); +const QString GetSaveFileName(const QString &title, + const QString &dir, + const QString &filter, + QString *selectedFilter = nullptr, + Options options = Options()); + } // namespace QtCommon::Frontend #endif // QT_FRONTEND_UTIL_H diff --git a/src/qt_common/qt_game_util.cpp b/src/qt_common/qt_game_util.cpp index ac922ea967..0eddd10fe1 100644 --- a/src/qt_common/qt_game_util.cpp +++ b/src/qt_common/qt_game_util.cpp @@ -220,8 +220,8 @@ void RemoveBaseContent(u64 program_id, InstalledEntryType type) program_id); if (res) { QtCommon::Frontend::Information(rootObject, - "Successfully Removed", - "Successfully removed the installed base game."); + tr("Successfully Removed"), + tr("Successfully removed the installed base game.")); } else { QtCommon::Frontend::Warning( rootObject, @@ -235,8 +235,8 @@ void RemoveUpdateContent(u64 program_id, InstalledEntryType type) const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); if (res) { QtCommon::Frontend::Information(rootObject, - "Successfully Removed", - "Successfully removed the installed update."); + tr("Successfully Removed"), + tr("Successfully removed the installed update.")); } else { QtCommon::Frontend::Warning(rootObject, GetGameListErrorRemoving(type), diff --git a/src/qt_common/qt_path_util.cpp b/src/qt_common/qt_path_util.cpp index 761e6e8405..632424e0ca 100644 --- a/src/qt_common/qt_path_util.cpp +++ b/src/qt_common/qt_path_util.cpp @@ -17,7 +17,12 @@ bool OpenShaderCache(u64 program_id, QObject *parent) const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)}; if (!Common::FS::CreateDirs(shader_cache_folder_path)) { - QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent); + QtCommon::Frontend::ShowMessage(QMessageBox::Warning, + tr("Error Opening Shader Cache"), + tr("Failed to create or open shader cache for this title, " + "ensure your app data directory has write permissions."), + QMessageBox::Ok, + parent); } const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)}; diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h new file mode 100644 index 0000000000..de6acac8a1 --- /dev/null +++ b/src/qt_common/qt_string_lookup.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +namespace QtCommon::StringLookup { + +Q_NAMESPACE + +// TODO(crueter): QML interface +enum StringKey { + SavesTooltip, + ShadersTooltip, + UserNandTooltip, + SysNandTooltip, + ModsTooltip, +}; + +static constexpr const frozen::unordered_map strings = { + {SavesTooltip, "Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"}, + {ShadersTooltip, "Contains Vulkan and OpenGL pipeline caches. Generally safe to remove."}, + {UserNandTooltip, "Contains updates and DLC for games."}, + {SysNandTooltip, "Contains firmware and applet data."}, + {ModsTooltip, "Contains game mods, patches, and cheats."}, +}; + +static inline const QString Lookup(StringKey key) +{ + return QString::fromStdString(strings.at(key).data()); +} + +} diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 0f0d27c6f7..55565e3d79 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -337,11 +337,6 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m uint8_pass = std::make_unique(device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue); } - const u32 ubo_align = static_cast( - device.GetUniformBufferAlignment() //check if the device has it - ); - // add the ability to change the size in settings in future - uniform_ring.Init(device, memory_allocator, 8 * 1024 * 1024 /* 8 MiB */, ubo_align ? ubo_align : 256); quad_array_index_buffer = std::make_shared(device_, memory_allocator_, scheduler_, staging_pool_); quad_strip_index_buffer = std::make_shared(device_, memory_allocator_, @@ -360,42 +355,6 @@ void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { staging_pool.FreeDeferred(ref); } -void BufferCacheRuntime::UniformRing::Init(const Device& device, - MemoryAllocator& alloc, - u64 bytes, u32 alignment) { - for (size_t i = 0; i < NUM_FRAMES; ++i) { - VkBufferCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = bytes, - .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - }; - buffers[i] = alloc.CreateBuffer(ci, MemoryUsage::Upload); - mapped[i] = buffers[i].Mapped().data(); - } - size = bytes; - align = alignment ? alignment : 256; - head = 0; - current_frame = 0; -} - -std::span BufferCacheRuntime::UniformRing::Alloc(u32 bytes, u32& out_offset) { - const u64 aligned = Common::AlignUp(head, static_cast(align)); - u64 end = aligned + bytes; - - if (end > size) { - return {}; // Fallback to staging pool - } - - out_offset = static_cast(aligned); - head = end; - return {mapped[current_frame] + out_offset, bytes}; -} - u64 BufferCacheRuntime::GetDeviceLocalMemory() const { return device.GetDeviceLocalMemory(); } @@ -416,7 +375,6 @@ void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noe for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { it->ResetUsageTracking(); } - uniform_ring.BeginFrame(); } void BufferCacheRuntime::Finish() { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 86bce01596..efe960258c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -127,15 +124,8 @@ public: void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); - std::span BindMappedUniformBuffer([[maybe_unused]] size_t /*stage*/, - [[maybe_unused]] u32 /*binding_index*/, - u32 size) { - u32 offset = 0; - if (auto span = uniform_ring.Alloc(size, offset); !span.empty()) { - BindBuffer(*uniform_ring.buffers[uniform_ring.current_frame], offset, size); - return span; - } - // Fallback for giant requests + std::span BindMappedUniformBuffer([[maybe_unused]] size_t stage, + [[maybe_unused]] u32 binding_index, u32 size) { const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); BindBuffer(ref.buffer, static_cast(ref.offset), size); return ref.mapped_span; @@ -163,24 +153,6 @@ private: void ReserveNullBuffer(); vk::Buffer CreateNullBuffer(); - struct UniformRing { - static constexpr size_t NUM_FRAMES = 3; - std::array buffers{}; - std::array mapped{}; - u64 size = 0; - u64 head = 0; - u32 align = 256; - size_t current_frame = 0; - - void Init(const Device& device, MemoryAllocator& alloc, u64 bytes, u32 alignment); - void BeginFrame() { - current_frame = (current_frame + 1) % NUM_FRAMES; - head = 0; - } - std::span Alloc(u32 bytes, u32& out_offset); - }; - UniformRing uniform_ring; - const Device& device; MemoryAllocator& memory_allocator; Scheduler& scheduler; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 08513d1534..ecc4f77dc7 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -33,19 +33,29 @@ constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; size_t GetStreamBufferSize(const Device& device) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { - ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) { + bool found_heap = false; + ForEachDeviceLocalHostVisibleHeap(device, [&size, &found_heap](size_t /*index*/, VkMemoryHeap& heap) { size = (std::max)(size, heap.size); + found_heap = true; }); - // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be - // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue - // as the heap will be much larger. - if (size <= 256_MiB) { + // If no suitable heap was found fall back to the default cap to avoid creating a zero-sized stream buffer. + if (!found_heap) { + size = MAX_STREAM_BUFFER_SIZE; + } else if (size <= 256_MiB) { + // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be + // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue + // as the heap will be much larger. size = size * 40 / 100; } } else { size = MAX_STREAM_BUFFER_SIZE; } - return (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); + + // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so + // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. + const VkDeviceSize aligned = (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); + const VkDeviceSize min_size = MAX_ALIGNMENT * StagingBufferPool::NUM_SYNCS; + return static_cast((std::max)(aligned, min_size)); } } // Anonymous namespace @@ -106,31 +116,53 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - if (AreRegionsActive(Region(free_iterator) + 1, - (std::min)(Region(iterator + size) + 1, NUM_SYNCS))) { + const size_t aligned_size = Common::AlignUp(size, MAX_ALIGNMENT); + const bool wraps = iterator + size >= stream_buffer_size; + const size_t new_iterator = + wraps ? aligned_size : Common::AlignUp(iterator + size, MAX_ALIGNMENT); + const size_t begin_region = wraps ? 0 : Region(iterator); + const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; + const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); + const size_t guard_begin = (std::min)(Region(free_iterator) + 1, NUM_SYNCS); + + if (!wraps) { + if (guard_begin < end_region && AreRegionsActive(guard_begin, end_region)) { + // Avoid waiting for the previous usages to be free + return GetStagingBuffer(size, MemoryUsage::Upload); + } + } else if (guard_begin < NUM_SYNCS && AreRegionsActive(guard_begin, NUM_SYNCS)) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } + const u64 current_tick = scheduler.CurrentTick(); std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator), current_tick); used_iterator = iterator; - free_iterator = (std::max)(free_iterator, iterator + size); - if (iterator + size >= stream_buffer_size) { + if (wraps) { std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, current_tick); used_iterator = 0; iterator = 0; free_iterator = size; - - if (AreRegionsActive(0, Region(size) + 1)) { + const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; + const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); + if (AreRegionsActive(0, head_end_region)) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } } - const size_t offset = iterator; - iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); + + std::fill(sync_ticks.begin() + begin_region, sync_ticks.begin() + end_region, current_tick); + + const size_t offset = wraps ? 0 : iterator; + iterator = new_iterator; + + if (!wraps) { + free_iterator = (std::max)(free_iterator, offset + size); + } + return StagingBufferRef{ .buffer = *stream_buffer, .offset = static_cast(offset), diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index a8e3953716..010a3db174 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -234,6 +234,9 @@ add_executable(yuzu deps_dialog.cpp deps_dialog.h deps_dialog.ui + + data_dialog.h data_dialog.cpp data_dialog.ui + data_widget.ui ) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") @@ -390,32 +393,21 @@ endif() target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common) -target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets) +target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Concurrent) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) +if (NOT WIN32) + target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) +endif() + if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt6::DBus) + + if (TARGET Qt6::GuiPrivate) + target_link_libraries(yuzu PRIVATE Qt6::GuiPrivate) + endif() endif() -target_compile_definitions(yuzu PRIVATE - # Use QStringBuilder for string concatenation to reduce - # the overall number of temporary strings created. - QT_USE_QSTRINGBUILDER - - # Disable implicit conversions from/to C strings - QT_NO_CAST_FROM_ASCII - QT_NO_CAST_TO_ASCII - - # Disable implicit type narrowing in signal/slot connect() calls. - QT_NO_NARROWING_CONVERSIONS_IN_CONNECT - - # Disable unsafe overloads of QProcess' start() function. - QT_NO_PROCESS_COMBINED_ARGUMENT_START - - # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. - QT_NO_URL_CAST_FROM_STRING -) - if (YUZU_ENABLE_COMPATIBILITY_REPORTING) target_compile_definitions(yuzu PRIVATE YUZU_ENABLE_COMPATIBILITY_REPORTING) endif() diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 0e58f17405..6d5d263e16 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include -#include +#include #include "common/logging/log.h" #include "ui_compatdb.h" #include "yuzu/compatdb.h" diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index b825348760..18f629f639 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -83,7 +83,8 @@ void ConfigureDebug::SetConfiguration() { #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); #else - ui->disable_web_applet->setVisible(false); + ui->disable_web_applet->setEnabled(false); + ui->disable_web_applet->setText(tr("Web applet not compiled")); #endif } diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index 15a0029901..7a693be10c 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -14,7 +14,7 @@ #include #endif -#include +#include #include "common/settings.h" #include "ui_configure_web.h" #include "qt_common/uisettings.h" diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp new file mode 100644 index 0000000000..a097a6f893 --- /dev/null +++ b/src/yuzu/data_dialog.cpp @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "data_dialog.h" +#include "frontend_common/data_manager.h" +#include "qt_common/qt_content_util.h" +#include "qt_common/qt_frontend_util.h" +#include "qt_common/qt_progress_dialog.h" +#include "qt_common/qt_string_lookup.h" +#include "ui_data_dialog.h" + +#include +#include +#include +#include +#include + +DataDialog::DataDialog(QWidget *parent) + : QDialog(parent) + , ui(std::make_unique()) +{ + ui->setupUi(this); + + // TODO: Should we make this a single widget that pulls data from a model? +#define WIDGET(name) \ + ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \ + QtCommon::StringLookup::name##Tooltip, \ + this)); + + WIDGET(Saves) + WIDGET(Shaders) + WIDGET(UserNand) + WIDGET(SysNand) + WIDGET(Mods) + +#undef WIDGET + + connect(ui->labels, &QListWidget::itemSelectionChanged, this, [this]() { + const auto items = ui->labels->selectedItems(); + if (items.isEmpty()) { + return; + } + + ui->page->setCurrentIndex(ui->labels->row(items[0])); + }); +} + +DataDialog::~DataDialog() = default; + +DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, + QtCommon::StringLookup::StringKey tooltip, + QWidget *parent) + : QWidget(parent) + , ui(std::make_unique()) + , m_dir(data_dir) +{ + ui->setupUi(this); + + ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip)); + + ui->clear->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"))); + ui->open->setIcon(QIcon::fromTheme(QStringLiteral("folder"))); + ui->upload->setIcon(QIcon::fromTheme(QStringLiteral("upload"))); + ui->download->setIcon(QIcon::fromTheme(QStringLiteral("download"))); + + connect(ui->clear, &QPushButton::clicked, this, &DataWidget::clear); + connect(ui->open, &QPushButton::clicked, this, &DataWidget::open); + connect(ui->upload, &QPushButton::clicked, this, &DataWidget::upload); + connect(ui->download, &QPushButton::clicked, this, &DataWidget::download); + + scan(); +} + +void DataWidget::clear() +{ + QtCommon::Content::ClearDataDir(m_dir); + scan(); +} + +void DataWidget::open() +{ + QDesktopServices::openUrl(QUrl::fromLocalFile( + QString::fromStdString(FrontendCommon::DataManager::GetDataDir(m_dir)))); +} + +void DataWidget::upload() +{ + QtCommon::Content::ExportDataDir(m_dir); +} + +void DataWidget::download() +{ + QtCommon::Content::ImportDataDir(m_dir, std::bind(&DataWidget::scan, this)); +} + +void DataWidget::scan() { + ui->size->setText(tr("Calculating...")); + + QFutureWatcher *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcher::finished, this, [=, this]() { + u64 size = watcher->result(); + ui->size->setText( + QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size))); + watcher->deleteLater(); + }); + + watcher->setFuture( + QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); })); +} diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h new file mode 100644 index 0000000000..8a64659965 --- /dev/null +++ b/src/yuzu/data_dialog.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DATA_DIALOG_H +#define DATA_DIALOG_H + +#include +#include "frontend_common/data_manager.h" +#include "qt_common/qt_string_lookup.h" + +#include "ui_data_widget.h" + +namespace Ui { +class DataDialog; +} + +class DataDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DataDialog(QWidget *parent = nullptr); + ~DataDialog(); + +private: + std::unique_ptr ui; +}; + +class DataWidget : public QWidget +{ + Q_OBJECT +public: + explicit DataWidget(FrontendCommon::DataManager::DataDir data_dir, + QtCommon::StringLookup::StringKey tooltip, + QWidget *parent = nullptr); + +public slots: + void clear(); + void open(); + void upload(); + void download(); + + void scan(); + +private: + std::unique_ptr ui; + FrontendCommon::DataManager::DataDir m_dir; +}; + +#endif // DATA_DIALOG_H diff --git a/src/yuzu/data_dialog.ui b/src/yuzu/data_dialog.ui new file mode 100644 index 0000000000..06751e2fb1 --- /dev/null +++ b/src/yuzu/data_dialog.ui @@ -0,0 +1,148 @@ + + + DataDialog + + + + 0 + 0 + 480 + 320 + + + + + 0 + 0 + + + + + 300 + 320 + + + + Data Manager + + + + + + + + + 0 + 0 + + + + + Saves + + + + + Shaders + + + + + UserNAND + + + + + SysNAND + + + + + Mods + + + + + + + + + 0 + 0 + + + + + 275 + 200 + + + + -1 + + + + + + + + + 10 + + + + + Deleting ANY data is IRREVERSABLE! + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Ok + + + + + + + + + + + buttonBox + accepted() + DataDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DataDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/yuzu/data_widget.ui b/src/yuzu/data_widget.ui new file mode 100644 index 0000000000..99010d8856 --- /dev/null +++ b/src/yuzu/data_widget.ui @@ -0,0 +1,205 @@ + + + DataWidget + + + + 0 + 0 + 275 + 200 + + + + Form + + + + + + + + Tooltip + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + + 10 + true + + + + Size + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + + + + + + 1 + 1 + + + + + 52 + 47 + + + + Open with your system file manager + + + QPushButton { +max-width: 50px; +max-height: 45px; +min-width: 50px; +min-height: 45px; +} + + + + + + + 28 + 28 + + + + + + + + + 1 + 1 + + + + + 52 + 47 + + + + Delete all data in this directory. THIS IS 100% IRREVERSABLE! + + + QPushButton { +max-width: 50px; +max-height: 45px; +min-width: 50px; +min-height: 45px; +} + + + + + + + 28 + 28 + + + + + + + + + 1 + 1 + + + + + 52 + 47 + + + + Export all data in this directory. This may take a while! + + + QPushButton { +max-width: 50px; +max-height: 45px; +min-width: 50px; +min-height: 45px; +} + + + + + + + 28 + 28 + + + + + + + + + 1 + 1 + + + + + 52 + 47 + + + + Import data for this directory. This may take a while, and will delete ALL EXISTING DATA! + + + QPushButton { +max-width: 50px; +max-height: 45px; +min-width: 50px; +min-height: 45px; +} + + + + + + + 28 + 28 + + + + + + + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 44ed29f141..21c92c495f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -156,6 +156,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/debugger/console.h" #include "yuzu/debugger/controller.h" #include "yuzu/debugger/wait_tree.h" +#include "yuzu/data_dialog.h" #include "yuzu/deps_dialog.h" #include "yuzu/discord.h" #include "yuzu/game_list.h" @@ -1705,6 +1706,7 @@ void GMainWindow::ConnectMenuEvents() { connect_menu(ui->action_Install_Keys, &GMainWindow::OnInstallDecryptionKeys); connect_menu(ui->action_About, &GMainWindow::OnAbout); connect_menu(ui->action_Eden_Dependencies, &GMainWindow::OnEdenDependencies); + connect_menu(ui->action_Data_Manager, &GMainWindow::OnDataDialog); } void GMainWindow::UpdateMenuState() { @@ -3934,6 +3936,15 @@ void GMainWindow::OnEdenDependencies() { depsDialog.exec(); } +void GMainWindow::OnDataDialog() { + DataDialog dataDialog(this); + dataDialog.exec(); + + // refresh stuff in case it was cleared + OnGameListRefresh(); + +} + void GMainWindow::OnToggleFilterBar() { game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked()); if (ui->action_Show_Filter_Bar->isChecked()) { @@ -4474,11 +4485,15 @@ void GMainWindow::SetFirmwareVersion() { if (result.IsError() || !CheckFirmwarePresence()) { LOG_INFO(Frontend, "Installed firmware: No firmware available"); + ui->menu_Applets->setEnabled(false); + ui->menu_Create_Shortcuts->setEnabled(false); firmware_label->setVisible(false); return; } firmware_label->setVisible(true); + ui->menu_Applets->setEnabled(true); + ui->menu_Create_Shortcuts->setEnabled(true); const std::string display_version(firmware_data.display_version.data()); const std::string display_title(firmware_data.display_title.data()); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e3922759b0..a3e99c05fe 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -387,6 +387,7 @@ private slots: void OnInstallDecryptionKeys(); void OnAbout(); void OnEdenDependencies(); + void OnDataDialog(); void OnToggleFilterBar(); void OnToggleStatusBar(); void OnGameListRefresh(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 5f56c9e6d1..12ff4efdf1 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -158,13 +158,23 @@ - &Amiibo + Am&iibo + + + &Applets + + + + + + + &TAS @@ -184,7 +194,7 @@ - Install Firmware + Install &Firmware @@ -192,13 +202,10 @@ + - - - - - + @@ -497,12 +504,12 @@ - Install Decryption Keys + Install Decryption &Keys - Open Home Menu + Open &Home Menu @@ -593,6 +600,11 @@ &Eden Dependencies + + + &Data Manager + + diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 4c2c41ea2a..53beda0f8e 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include "common/logging/log.h" #include "network/announce_multiplayer_session.h" #include "ui_chat_room.h" diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index 93d6662c1e..4e995c044f 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "common/logging/log.h" #include "network/announce_multiplayer_session.h" #include "ui_client_room.h" diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index deac3b9e59..6291979fe6 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "common/settings.h" #include "core/core.h" #include "core/internal_network/network_interface.h" diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 4dd3958550..cc163a5420 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 84723041df..e8daa0c6eb 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h"