diff --git a/.ci/windows/package.ps1 b/.ci/windows/package.ps1 index de7fd051bb..f7c5f09042 100755 --- a/.ci/windows/package.ps1 +++ b/.ci/windows/package.ps1 @@ -22,9 +22,10 @@ if ($debug -eq "yes") { Move-Item $BUILD_PDB $ARTIFACTS_DIR/ -ErrorAction SilentlyContinue } } else { - Get-ChildItem "build/$target/bin/" -Recurse -Filter "*.pdb" | Remove-Item -Force -ErrorAction SilentlyContinue + Remove-Item -Force "$RELEASE_DIST\*.pdb" } + Copy-Item "build/$target/bin/Release/*" -Destination "$RELEASE_DIST" -Recurse -ErrorAction SilentlyContinue if (-not $?) { # Try without Release subfolder if that doesn't exist diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index ef2dd57044..15b898279b 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -237,6 +237,7 @@ add_executable(yuzu yuzu.qrc yuzu.rc + migration_dialog.h migration_dialog.cpp ) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") diff --git a/src/yuzu/migration_dialog.cpp b/src/yuzu/migration_dialog.cpp new file mode 100644 index 0000000000..d4a6e9cd3c --- /dev/null +++ b/src/yuzu/migration_dialog.cpp @@ -0,0 +1,63 @@ +#include "migration_dialog.h" + +#include +#include +#include +#include +#include + +MigrationDialog::MigrationDialog( + QWidget *parent) + : QDialog(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + + m_text = new QLabel(this); + m_boxes = new QVBoxLayout; + m_buttons = new QHBoxLayout; + + layout->addWidget(m_text, 1); + layout->addLayout(m_boxes, 1); + layout->addLayout(m_buttons, 1); +} + +MigrationDialog::~MigrationDialog() { + m_boxes->deleteLater(); + m_buttons->deleteLater(); +} + +void MigrationDialog::setText( + const QString &text) +{ + m_text->setText(text); +} + +void MigrationDialog::addBox( + QWidget *box) +{ + m_boxes->addWidget(box); + +} + +QAbstractButton *MigrationDialog::addButton( + const QString &text, const bool reject) +{ + QAbstractButton *button = new QPushButton(this); + button->setText(text); + m_buttons->addWidget(button, 1); + + connect(button, &QAbstractButton::clicked, this, [this, button, reject]() { + if (reject) { + this->reject(); + } else { + m_clickedButton = button; + this->accept(); + } + }); + return button; +} + +QAbstractButton *MigrationDialog::clickedButton() const +{ + return m_clickedButton; +} diff --git a/src/yuzu/migration_dialog.h b/src/yuzu/migration_dialog.h new file mode 100644 index 0000000000..7123fd6b87 --- /dev/null +++ b/src/yuzu/migration_dialog.h @@ -0,0 +1,30 @@ +#ifndef MIGRATION_DIALOG_H +#define MIGRATION_DIALOG_H + +#include +#include +#include +#include + +class MigrationDialog : public QDialog { + Q_OBJECT +public: + MigrationDialog(QWidget *parent = nullptr); + + virtual ~MigrationDialog(); + + void setText(const QString &text); + void addBox(QWidget *box); + QAbstractButton *addButton(const QString &text, const bool reject = false); + + QAbstractButton *clickedButton() const; + +private: + QLabel *m_text; + QVBoxLayout *m_boxes; + QHBoxLayout *m_buttons; + + QAbstractButton *m_clickedButton; +}; + +#endif // MIGRATION_DIALOG_H diff --git a/src/yuzu/user_data_migration.cpp b/src/yuzu/user_data_migration.cpp index d890550222..1946254fb2 100644 --- a/src/yuzu/user_data_migration.cpp +++ b/src/yuzu/user_data_migration.cpp @@ -5,20 +5,25 @@ // SPDX-FileCopyrightText: Copyright 2025 eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include "user_data_migration.h" #include #include #include #include #include "common/fs/path_util.h" -#include "user_data_migration.h" +#include "migration_dialog.h" // Needs to be included at the end due to https://bugreports.qt.io/browse/QTBUG-73263 +#include #include +#include #include namespace fs = std::filesystem; -UserDataMigrator::UserDataMigrator(QMainWindow* main_window) { +UserDataMigrator::UserDataMigrator( + QMainWindow *main_window) +{ // NOTE: Logging is not initialized yet, do not produce logs here. // Check migration if config directory does not exist @@ -29,25 +34,58 @@ UserDataMigrator::UserDataMigrator(QMainWindow* main_window) { } } -void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) { +void UserDataMigrator::ShowMigrationPrompt( + QMainWindow *main_window) +{ namespace fs = std::filesystem; - const QString migration_prompt_message = - main_window->tr("Would you like to migrate your data for use in Eden?\n" - "Select the corresponding button to migrate data from that emulator.\n" - "(This may take a while; The old data will not be deleted)\n\n" - "Clearing shader cache is recommended for all users.\nDo not uncheck unless you know what you're doing."); + const QString migration_prompt_message = main_window->tr( + "Would you like to migrate your data for use in Eden?\n" + "Select the corresponding button to migrate data from that emulator.\n" + "This may take a while."); bool any_found = false; - QMessageBox migration_prompt; + MigrationDialog migration_prompt; migration_prompt.setWindowTitle(main_window->tr("Migration")); - migration_prompt.setIcon(QMessageBox::Information); QCheckBox *clear_shaders = new QCheckBox(&migration_prompt); clear_shaders->setText(main_window->tr("Clear Shader Cache")); + clear_shaders->setToolTip(main_window->tr( + "Clearing shader cache is recommended for all users.\nDo not uncheck unless you know what " + "you're doing.")); clear_shaders->setChecked(true); - migration_prompt.setCheckBox(clear_shaders); + + QRadioButton *keep_old = new QRadioButton(&migration_prompt); + keep_old->setText(main_window->tr("Keep Old Data")); + keep_old->setToolTip( + main_window->tr("Keeps the old data directory. This is recommended if you aren't\n" + "space-constrained and want to keep separate data for the old emulator.")); + keep_old->setChecked(true); + + QRadioButton *clear_old = new QRadioButton(&migration_prompt); + clear_old->setText(main_window->tr("Clear Old Data")); + clear_old->setToolTip(main_window->tr("Deletes the old data directory.\nThis is recommended on " + "devices with space constraints.")); + clear_old->setChecked(false); + + QRadioButton *link = new QRadioButton(&migration_prompt); + link->setText(main_window->tr("Link Old Directory")); + link->setToolTip( + main_window->tr("Creates a filesystem link between the old directory and Eden directory.\n" + "This is recommended if you want to share data between emulators..")); + link->setChecked(false); + + // Link and Clear Old are mutually exclusive + QButtonGroup *group = new QButtonGroup(&migration_prompt); + group->addButton(keep_old); + group->addButton(clear_old); + group->addButton(link); + + migration_prompt.addBox(clear_shaders); + migration_prompt.addBox(keep_old); + migration_prompt.addBox(clear_old); + migration_prompt.addBox(link); // Reflection would make this code 10x better // but for now... MACRO MADNESS!!!! @@ -55,10 +93,13 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) { QMap legacyMap; QMap buttonMap; -#define EMU_MAP(name) const bool name##_found = fs::is_directory(Common::FS::GetLegacyPath(Common::FS::LegacyPath::name##Dir)); \ - legacyMap[main_window->tr(#name)] = LegacyEmu::name; \ - found[main_window->tr(#name)] = name##_found; \ - if (name##_found) any_found = true; +#define EMU_MAP(name) \ + const bool name##_found = fs::is_directory( \ + Common::FS::GetLegacyPath(Common::FS::LegacyPath::name##Dir)); \ + legacyMap[main_window->tr(#name)] = LegacyEmu::name; \ + found[main_window->tr(#name)] = name##_found; \ + if (name##_found) \ + any_found = true; EMU_MAP(Citron) EMU_MAP(Sudachi) @@ -68,30 +109,44 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) { #undef EMU_MAP if (any_found) { - QString promptText = main_window->tr("Eden has detected user data for the following emulators:"); + QString promptText = main_window->tr( + "Eden has detected user data for the following emulators:"); QMapIterator iter(found); while (iter.hasNext()) { iter.next(); - if (!iter.value()) continue; + if (!iter.value()) + continue; - buttonMap[iter.key()] = migration_prompt.addButton(iter.key(), QMessageBox::YesRole); + buttonMap[iter.key()] = migration_prompt.addButton(iter.key()); promptText.append(main_window->tr("\n- %1").arg(iter.key())); } promptText.append(main_window->tr("\n\n")); migration_prompt.setText(promptText + migration_prompt_message); - migration_prompt.addButton(QMessageBox::No); + migration_prompt.addButton(main_window->tr("No"), true); migration_prompt.exec(); + MigrationStrategy strategy; + if (link->isChecked()) { + strategy = MigrationStrategy::Link; + } else if (clear_old->isChecked()) { + strategy = MigrationStrategy::Move; + } else { + strategy = MigrationStrategy::Copy; + } + QMapIterator buttonIter(buttonMap); while (buttonIter.hasNext()) { buttonIter.next(); if (buttonIter.value() == migration_prompt.clickedButton()) { - MigrateUserData(main_window, legacyMap[iter.key()], clear_shaders->isChecked()); + MigrateUserData(main_window, + legacyMap[buttonIter.key()], + clear_shaders->isChecked(), + strategy); return; } } @@ -104,31 +159,43 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow* main_window) { return; } -void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow* main_window) { - QMessageBox::information( - main_window, main_window->tr("Migration"), - main_window - ->tr("You can manually re-trigger this prompt by deleting the " - "new config directory:\n" - "%1") - .arg(QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::ConfigDir))), - QMessageBox::Ok); +void UserDataMigrator::ShowMigrationCancelledMessage( + QMainWindow *main_window) +{ + QMessageBox::information(main_window, + main_window->tr("Migration"), + main_window + ->tr("You can manually re-trigger this prompt by deleting the " + "new config directory:\n" + "%1") + .arg(QString::fromStdString(Common::FS::GetEdenPathString( + Common::FS::EdenPath::ConfigDir))), + QMessageBox::Ok); } -void UserDataMigrator::MigrateUserData(QMainWindow* main_window, - const LegacyEmu selected_legacy_emu, const bool clear_shader_cache) { +void UserDataMigrator::MigrateUserData( + QMainWindow *main_window, + const LegacyEmu selected_legacy_emu, + const bool clear_shader_cache, + const MigrationStrategy strategy) +{ namespace fs = std::filesystem; const auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive; + QString success_text = main_window->tr("Data was migrated successfully."); + std::string legacy_user_dir; std::string legacy_config_dir; std::string legacy_cache_dir; -#define LEGACY_EMU(emu) case LegacyEmu::emu: \ - legacy_user_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##Dir).string(); \ - legacy_config_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##ConfigDir).string(); \ - legacy_cache_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##CacheDir).string(); \ - break; +#define LEGACY_EMU(emu) \ +case LegacyEmu::emu: \ + legacy_user_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##Dir).string(); \ + legacy_config_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##ConfigDir) \ + .string(); \ + legacy_cache_dir = Common::FS::GetLegacyPath(Common::FS::LegacyPath::emu##CacheDir) \ + .string(); \ + break; switch (selected_legacy_emu) { LEGACY_EMU(Citron) @@ -139,30 +206,74 @@ void UserDataMigrator::MigrateUserData(QMainWindow* main_window, #undef LEGACY_EMU - fs::copy(legacy_user_dir, Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir), copy_options); + fs::path eden_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir); + fs::path config_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir); + fs::path cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir); + fs::path shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); - if (fs::is_directory(legacy_config_dir)) { - fs::copy(legacy_config_dir, Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir), - copy_options); - } - if (fs::is_directory(legacy_cache_dir)) { - fs::copy(legacy_cache_dir, Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir), - copy_options); + fs::remove_all(eden_dir); + + switch (strategy) { + case MigrationStrategy::Link: + // Create symlinks/directory junctions if requested + fs::create_directory_symlink(legacy_user_dir, eden_dir); + +// Windows doesn't need any more links, because cache and config +// are already children of the root directory +#ifndef WIN32 + if (fs::is_directory(legacy_config_dir)) { + fs::create_directory_symlink(legacy_config_dir, config_dir); + } + + if (fs::is_directory(legacy_cache_dir)) { + fs::create_directory_symlink(legacy_cache_dir, cache_dir); + } +#endif + break; + case MigrationStrategy::Move: + // Rename directories if deletion is requested (achieves the same result) + fs::rename(legacy_user_dir, eden_dir); + +// Windows doesn't need any more renames, because cache and config +// are already children of the root directory +#ifndef WIN32 + if (fs::is_directory(legacy_config_dir)) { + fs::rename(legacy_config_dir, config_dir); + } + + if (fs::is_directory(legacy_cache_dir)) { + fs::rename(legacy_cache_dir, cache_dir); + } +#endif + break; + case MigrationStrategy::Copy: + default: + // Default behavior: copy + fs::copy(legacy_user_dir, eden_dir, copy_options); + + if (fs::is_directory(legacy_config_dir)) { + fs::copy(legacy_config_dir, config_dir, copy_options); + } + + if (fs::is_directory(legacy_cache_dir)) { + fs::copy(legacy_cache_dir, cache_dir, copy_options); + } + + success_text.append( + main_window->tr("\n\nIf you wish to clean up the files which were left in the old " + "data location, you can do so by deleting the following directory:\n" + "%1").arg(QString::fromStdString(legacy_user_dir))); + break; } // Delete and re-create shader dir if (clear_shader_cache) { - fs::remove_all(Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)); - fs::create_directory(Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)); + fs::remove_all(shader_dir); + fs::create_directory(shader_dir); } - QMessageBox::information( - main_window, main_window->tr("Migration"), - main_window - ->tr("Data was migrated successfully.\n\n" - "If you wish to clean up the files which were left in the old " - "data location, you can do so by deleting the following directory:\n" - "%1") - .arg(QString::fromStdString(legacy_user_dir)), - QMessageBox::Ok); + QMessageBox::information(main_window, + main_window->tr("Migration"), + success_text, + QMessageBox::Ok); } diff --git a/src/yuzu/user_data_migration.h b/src/yuzu/user_data_migration.h index 098f19280b..3c4f745b87 100644 --- a/src/yuzu/user_data_migration.h +++ b/src/yuzu/user_data_migration.h @@ -21,7 +21,14 @@ private: Suyu, }; + enum class MigrationStrategy { + Copy, + Move, + Link, + }; + void ShowMigrationPrompt(QMainWindow* main_window); void ShowMigrationCancelledMessage(QMainWindow* main_window); - void MigrateUserData(QMainWindow* main_window, const LegacyEmu selected_legacy_emu, const bool clear_shader_cache); + void MigrateUserData(QMainWindow* main_window, const LegacyEmu selected_legacy_emu, + const bool clear_shader_cache, const MigrationStrategy strategy); };