Compare commits
6 commits
master
...
feature/da
Author | SHA1 | Date | |
---|---|---|---|
47520c974d | |||
cf77bcd60c | |||
f60e99915a | |||
1c186d9280 | |||
c302c1b1a3 | |||
f6908bba1e |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/download.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/upload.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
dist/qt_themes/colorful/icons/48x48/user-trash.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
3
dist/qt_themes/colorful/style.qrc
vendored
|
@ -18,6 +18,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
|
||||
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
|
|
|
@ -11,6 +11,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
|
||||
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
|
||||
|
|
3
dist/qt_themes/default/default.qrc
vendored
|
@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
|
||||
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||
|
|
BIN
dist/qt_themes/default/icons/256x256/eden.png
vendored
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
dist/qt_themes/default/icons/48x48/download.png
vendored
Normal file
After Width: | Height: | Size: 853 B |
BIN
dist/qt_themes/default/icons/48x48/upload.png
vendored
Normal file
After Width: | Height: | Size: 820 B |
BIN
dist/qt_themes/default/icons/48x48/user-trash.png
vendored
Normal file
After Width: | Height: | Size: 584 B |
3
dist/qt_themes/default_dark/style.qrc
vendored
|
@ -13,6 +13,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
|
||||
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
|
||||
|
|
BIN
dist/qt_themes/qdarkstyle/icons/48x48/download.png
vendored
Normal file
After Width: | Height: | Size: 883 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/upload.png
vendored
Normal file
After Width: | Height: | Size: 853 B |
BIN
dist/qt_themes/qdarkstyle/icons/48x48/user-trash.png
vendored
Normal file
After Width: | Height: | Size: 584 B |
3
dist/qt_themes/qdarkstyle/style.qrc
vendored
|
@ -9,6 +9,9 @@
|
|||
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
|
||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
<file alias="48x48/bad_folder.png">../qdarkstyle/icons/48x48/bad_folder.png</file>
|
||||
<file alias="48x48/chip.png">../qdarkstyle/icons/48x48/chip.png</file>
|
||||
<file alias="48x48/folder.png">../qdarkstyle/icons/48x48/folder.png</file>
|
||||
<file alias="48x48/user-trash.png">../qdarkstyle/icons/48x48/user-trash.png</file>
|
||||
<file alias="48x48/download.png">../qdarkstyle/icons/48x48/download.png</file>
|
||||
<file alias="48x48/upload.png">../qdarkstyle/icons/48x48/upload.png</file>
|
||||
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
|
||||
<file alias="48x48/list-add.png">../qdarkstyle/icons/48x48/list-add.png</file>
|
||||
<file alias="48x48/sd_card.png">../qdarkstyle/icons/48x48/sd_card.png</file>
|
||||
|
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 244 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
@ -161,7 +161,7 @@ struct Values {
|
|||
Category::LibraryApplet};
|
||||
Setting<AppletMode> photo_viewer_applet_mode{
|
||||
linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
|
||||
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode",
|
||||
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
|
||||
Category::LibraryApplet};
|
||||
Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
|
||||
Category::LibraryApplet};
|
||||
|
|
|
@ -509,9 +509,6 @@ std::vector<std::string> 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";
|
||||
|
|
|
@ -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)
|
||||
|
|
77
src/frontend_common/data_manager.cpp
Normal file
|
@ -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 <filesystem>
|
||||
#include <fmt/format.h>
|
||||
|
||||
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<int>(std::log10(size) / std::log10(1024)),
|
||||
static_cast<int>(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;
|
||||
}
|
||||
|
||||
}
|
24
src/frontend_common/data_manager.h
Normal file
|
@ -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 <string>
|
||||
|
||||
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
|
|
@ -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()
|
||||
|
|
3
src/qt_common/externals/CMakeLists.txt
vendored
|
@ -16,5 +16,4 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
|
|||
AddJsonPackage(quazip)
|
||||
|
||||
# frozen
|
||||
# TODO(crueter): Qt String Lookup
|
||||
# AddJsonPackage(frozen)
|
||||
AddJsonPackage(frozen)
|
||||
|
|
351
src/qt_common/qt_compress.cpp
Normal file
|
@ -0,0 +1,351 @@
|
|||
#include "qt_compress.h"
|
||||
#include "quazipfileinfo.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
|
||||
/** 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<QuaZipNewInfo> qzni;
|
||||
if (options.getDateTime().isNull()) {
|
||||
qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir)
|
||||
+ QLatin1String("/"),
|
||||
dir);
|
||||
} else {
|
||||
qzni = std::make_unique<QuaZipNewInfo>(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
|
68
src/qt_common/qt_compress.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <JlCompress.h>
|
||||
#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);
|
||||
|
||||
}
|
|
@ -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 <QFuture>
|
||||
#include <QtConcurrentRun>
|
||||
#include <JlCompress.h>
|
||||
#include <qfuturewatcher.h>
|
||||
|
||||
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 <a href='https://yuzu-mirror.github.io/help/quickstart'>"
|
||||
"dump and install firmware</a>, 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 <a href='https://yuzu-mirror.github.io/help/quickstart'>"
|
||||
"dump and install firmware</a>, 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<std::string> result =
|
||||
ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback);
|
||||
const std::vector<std::string> 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<void()> 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<int>((processed_size * 100) / total_size));
|
||||
|
||||
return !progress->wasCanceled();
|
||||
};
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run([=]() {
|
||||
return QtCommon::Compress::compressDir(zip_dump_location,
|
||||
QString::fromStdString(dir),
|
||||
JlCompress::Options(),
|
||||
progress_callback);
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
|
||||
|
||||
QObject::connect(watcher, &QFutureWatcher<bool>::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<void()> 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<bool> delete_future = QtConcurrent::run([=]() {
|
||||
FrontendCommon::DataManager::ClearDir(data_dir);
|
||||
return !progress->wasCanceled();
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* delete_watcher = new QFutureWatcher<bool>(rootObject);
|
||||
delete_watcher->setFuture(delete_future);
|
||||
|
||||
QObject::connect(delete_watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
|
||||
auto progress_callback = [=](size_t total_size, size_t processed_size) {
|
||||
QMetaObject::invokeMethod(progress,
|
||||
&QtProgressDialog::setValue,
|
||||
static_cast<int>((processed_size * 100) / total_size));
|
||||
|
||||
return !progress->wasCanceled();
|
||||
};
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run([=]() {
|
||||
return !QtCommon::Compress::extractDir(zip_dump_location,
|
||||
QString::fromStdString(dir),
|
||||
progress_callback)
|
||||
.empty();
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
|
||||
|
||||
QObject::connect(watcher, &QFutureWatcher<bool>::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
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#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<void()> callback = {});
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, std::function<void()> callback = {});
|
||||
|
||||
// Profiles //
|
||||
void FixProfiles();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)};
|
||||
|
|
38
src/qt_common/qt_string_lookup.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <frozen/string.h>
|
||||
#include <frozen/unordered_map.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
namespace QtCommon::StringLookup {
|
||||
|
||||
Q_NAMESPACE
|
||||
|
||||
// TODO(crueter): QML interface
|
||||
enum StringKey {
|
||||
SavesTooltip,
|
||||
ShadersTooltip,
|
||||
UserNandTooltip,
|
||||
SysNandTooltip,
|
||||
ModsTooltip,
|
||||
};
|
||||
|
||||
static constexpr const frozen::unordered_map<StringKey, frozen::string, 5> 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());
|
||||
}
|
||||
|
||||
}
|
|
@ -337,11 +337,6 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
|||
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
|
||||
compute_pass_descriptor_queue);
|
||||
}
|
||||
const u32 ubo_align = static_cast<u32>(
|
||||
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<QuadArrayIndexBuffer>(device_, memory_allocator_,
|
||||
scheduler_, staging_pool_);
|
||||
quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(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<u8> BufferCacheRuntime::UniformRing::Alloc(u32 bytes, u32& out_offset) {
|
||||
const u64 aligned = Common::AlignUp(head, static_cast<u64>(align));
|
||||
u64 end = aligned + bytes;
|
||||
|
||||
if (end > size) {
|
||||
return {}; // Fallback to staging pool
|
||||
}
|
||||
|
||||
out_offset = static_cast<u32>(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<Buffer>& slot_buffers) noe
|
|||
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
|
||||
it->ResetUsageTracking();
|
||||
}
|
||||
uniform_ring.BeginFrame();
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::Finish() {
|
||||
|
|
|
@ -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<Buffer>& bindings);
|
||||
|
||||
std::span<u8> 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<u8> 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<u32>(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<vk::Buffer, NUM_FRAMES> buffers{};
|
||||
std::array<u8*, NUM_FRAMES> 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<u8> Alloc(u32 bytes, u32& out_offset);
|
||||
};
|
||||
UniformRing uniform_ring;
|
||||
|
||||
const Device& device;
|
||||
MemoryAllocator& memory_allocator;
|
||||
Scheduler& scheduler;
|
||||
|
|
|
@ -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<size_t>((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<VkDeviceSize>(offset),
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 <QButtonGroup>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QtConcurrent/qtconcurrentrun.h>
|
||||
#include <qtconcurrentrun.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "ui_compatdb.h"
|
||||
#include "yuzu/compatdb.h"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include <QRegExpValidator>
|
||||
#endif
|
||||
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtConcurrentRun>
|
||||
#include "common/settings.h"
|
||||
#include "ui_configure_web.h"
|
||||
#include "qt_common/uisettings.h"
|
||||
|
|
110
src/yuzu/data_dialog.cpp
Normal file
|
@ -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 <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include <QProgressDialog>
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
DataDialog::DataDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, ui(std::make_unique<Ui::DataDialog>())
|
||||
{
|
||||
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<Ui::DataWidget>())
|
||||
, 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<u64> *watcher = new QFutureWatcher<u64>(this);
|
||||
|
||||
connect(watcher, &QFutureWatcher<u64>::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); }));
|
||||
}
|
50
src/yuzu/data_dialog.h
Normal file
|
@ -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 <QDialog>
|
||||
#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::DataDialog> 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::DataWidget> ui;
|
||||
FrontendCommon::DataManager::DataDir m_dir;
|
||||
};
|
||||
|
||||
#endif // DATA_DIALOG_H
|
148
src/yuzu/data_dialog.ui
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DataDialog</class>
|
||||
<widget class="QDialog" name="DataDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>320</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>320</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Data Manager</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
|
||||
<item>
|
||||
<widget class="QListWidget" name="labels">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Saves</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Shaders</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>UserNAND</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>SysNAND</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Mods</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="page">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>275</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Deleting ANY data is IRREVERSABLE!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>DataDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>DataDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
205
src/yuzu/data_widget.ui
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DataWidget</class>
|
||||
<widget class="QWidget" name="DataWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>275</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="3,2">
|
||||
<item>
|
||||
<widget class="QLabel" name="tooltip">
|
||||
<property name="text">
|
||||
<string>Tooltip</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="size">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Size</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="open">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>52</width>
|
||||
<height>47</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open with your system file manager</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
max-width: 50px;
|
||||
max-height: 45px;
|
||||
min-width: 50px;
|
||||
min-height: 45px;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>52</width>
|
||||
<height>47</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete all data in this directory. THIS IS 100% IRREVERSABLE!</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
max-width: 50px;
|
||||
max-height: 45px;
|
||||
min-width: 50px;
|
||||
min-height: 45px;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="upload">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>52</width>
|
||||
<height>47</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Export all data in this directory. This may take a while!</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
max-width: 50px;
|
||||
max-height: 45px;
|
||||
min-width: 50px;
|
||||
min-height: 45px;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="download">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>52</width>
|
||||
<height>47</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Import data for this directory. This may take a while, and will delete ALL EXISTING DATA!</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
max-width: 50px;
|
||||
max-height: 45px;
|
||||
min-width: 50px;
|
||||
min-height: 45px;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>28</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -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());
|
||||
|
|
|
@ -387,6 +387,7 @@ private slots:
|
|||
void OnInstallDecryptionKeys();
|
||||
void OnAbout();
|
||||
void OnEdenDependencies();
|
||||
void OnDataDialog();
|
||||
void OnToggleFilterBar();
|
||||
void OnToggleStatusBar();
|
||||
void OnGameListRefresh();
|
||||
|
|
|
@ -158,13 +158,23 @@
|
|||
</property>
|
||||
<widget class="QMenu" name="menu_cabinet_applet">
|
||||
<property name="title">
|
||||
<string>&Amiibo</string>
|
||||
<string>Am&iibo</string>
|
||||
</property>
|
||||
<addaction name="action_Load_Cabinet_Nickname_Owner"/>
|
||||
<addaction name="action_Load_Cabinet_Eraser"/>
|
||||
<addaction name="action_Load_Cabinet_Restorer"/>
|
||||
<addaction name="action_Load_Cabinet_Formatter"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Applets">
|
||||
<property name="title">
|
||||
<string>&Applets</string>
|
||||
</property>
|
||||
<addaction name="action_Load_Home_Menu"/>
|
||||
<addaction name="action_Load_Album"/>
|
||||
<addaction name="action_Load_Mii_Edit"/>
|
||||
<addaction name="action_Open_Controller_Menu"/>
|
||||
<addaction name="action_Open_Setup"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTAS">
|
||||
<property name="title">
|
||||
<string>&TAS</string>
|
||||
|
@ -184,7 +194,7 @@
|
|||
</widget>
|
||||
<widget class="QMenu" name="menuInstall_Firmware">
|
||||
<property name="title">
|
||||
<string>Install Firmware</string>
|
||||
<string>Install &Firmware</string>
|
||||
</property>
|
||||
<addaction name="action_Firmware_From_Folder"/>
|
||||
<addaction name="action_Firmware_From_ZIP"/>
|
||||
|
@ -192,13 +202,10 @@
|
|||
<addaction name="action_Install_Keys"/>
|
||||
<addaction name="menuInstall_Firmware"/>
|
||||
<addaction name="action_Verify_installed_contents"/>
|
||||
<addaction name="action_Data_Manager"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_cabinet_applet"/>
|
||||
<addaction name="action_Load_Album"/>
|
||||
<addaction name="action_Load_Mii_Edit"/>
|
||||
<addaction name="action_Open_Controller_Menu"/>
|
||||
<addaction name="action_Load_Home_Menu"/>
|
||||
<addaction name="action_Open_Setup"/>
|
||||
<addaction name="menu_Applets"/>
|
||||
<addaction name="menu_Create_Shortcuts"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Capture_Screenshot"/>
|
||||
|
@ -497,12 +504,12 @@
|
|||
</action>
|
||||
<action name="action_Install_Keys">
|
||||
<property name="text">
|
||||
<string>Install Decryption Keys</string>
|
||||
<string>Install Decryption &Keys</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Load_Home_Menu">
|
||||
<property name="text">
|
||||
<string>Open Home Menu</string>
|
||||
<string>Open &Home Menu</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Discord">
|
||||
|
@ -593,6 +600,11 @@
|
|||
<string>&Eden Dependencies</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Data_Manager">
|
||||
<property name="text">
|
||||
<string>&Data Manager</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="yuzu.qrc"/>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "network/announce_multiplayer_session.h"
|
||||
#include "ui_chat_room.h"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include <QLocale>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "network/announce_multiplayer_session.h"
|
||||
#include "ui_client_room.h"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <QIntValidator>
|
||||
#include <QRegularExpressionValidator>
|
||||
#include <QString>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtConcurrentRun>
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/internal_network/network_interface.h"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <QMessageBox>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <QInputDialog>
|
||||
#include <QList>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtConcurrentRun>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
|
|