Browse Source

Properly migrate internal NAND, Load, etc. directories (#167)

Signed-off-by: crueter <swurl@swurl.xyz>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/167
Co-authored-by: crueter <swurl@swurl.xyz>
Co-committed-by: crueter <swurl@swurl.xyz>
pull/21/head
crueter 6 months ago
committed by crueter
parent
commit
c4ca8d2367
  1. 27
      src/yuzu/main.cpp
  2. 3
      src/yuzu/migration_dialog.cpp
  3. 33
      src/yuzu/migration_worker.cpp
  4. 53
      src/yuzu/migration_worker.h
  5. 221
      src/yuzu/user_data_migration.cpp
  6. 5
      src/yuzu/user_data_migration.h

27
src/yuzu/main.cpp

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <cinttypes>
#include <clocale> #include <clocale>
#include <cmath> #include <cmath>
#include <fstream> #include <fstream>
@ -36,6 +35,7 @@
#include "configuration/configure_per_game.h" #include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h" #include "configuration/configure_tas.h"
#include "core/file_sys/romfs_factory.h" #include "core/file_sys/romfs_factory.h"
#include "core/core_timing.h"
#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/vfs/vfs_real.h" #include "core/file_sys/vfs/vfs_real.h"
#include "core/frontend/applets/cabinet.h" #include "core/frontend/applets/cabinet.h"
@ -121,7 +121,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#endif #endif
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h" #include "core/file_sys/common_funcs.h"
@ -149,7 +148,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "video_core/shader_notify.h" #include "video_core/shader_notify.h"
#include "yuzu/about_dialog.h" #include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h" #include "yuzu/bootmanager.h"
#include "yuzu/compatdb.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
#include "yuzu/configuration/configure_dialog.h" #include "yuzu/configuration/configure_dialog.h"
#include "yuzu/configuration/configure_input_per_game.h" #include "yuzu/configuration/configure_input_per_game.h"
@ -319,6 +317,22 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
Common::FS::CreateEdenPaths(); Common::FS::CreateEdenPaths();
this->config = std::make_unique<QtConfig>(); this->config = std::make_unique<QtConfig>();
if (user_data_migrator.migrated) {
// Sort-of hack whereby we only move the old dir if it's a subfolder of the user dir
#define MIGRATE_DIR(type) std::string type##path = Common::FS::GetEdenPathString(Common::FS::EdenPath::type##Dir); \
if (type##path.starts_with(user_data_migrator.selected_emu.get_user_dir())) { \
boost::replace_all(type##path, user_data_migrator.selected_emu.lower_name(), "eden"); \
Common::FS::SetEdenPath(Common::FS::EdenPath::type##Dir, type##path); \
}
MIGRATE_DIR(NAND)
MIGRATE_DIR(SDMC)
MIGRATE_DIR(Dump)
MIGRATE_DIR(Load)
#undef MIGRATE_DIR
}
#ifdef __unix__ #ifdef __unix__
SetupSigInterrupts(); SetupSigInterrupts();
SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
@ -506,13 +520,14 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
SetupPrepareForSleep(); SetupPrepareForSleep();
QStringList args = QApplication::arguments();
if (args.size() < 2) {
// Some moron added a race condition to the status bar // Some moron added a race condition to the status bar
// so now we have to make this completely unnecessary call // so now we have to make this completely unnecessary call
// to prevent the UI from blowing up. // to prevent the UI from blowing up.
UpdateUITheme(); UpdateUITheme();
QStringList args = QApplication::arguments();
if (args.size() < 2) {
return; return;
} }

3
src/yuzu/migration_dialog.cpp

@ -47,10 +47,11 @@ QAbstractButton *MigrationDialog::addButton(
m_buttons->addWidget(button, 1); m_buttons->addWidget(button, 1);
connect(button, &QAbstractButton::clicked, this, [this, button, reject]() { connect(button, &QAbstractButton::clicked, this, [this, button, reject]() {
m_clickedButton = button;
if (reject) { if (reject) {
this->reject(); this->reject();
} else { } else {
m_clickedButton = button;
this->accept(); this->accept();
} }
}); });

33
src/yuzu/migration_worker.cpp

@ -1,10 +1,13 @@
#include "migration_worker.h" #include "migration_worker.h"
#include <QMap>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <filesystem> #include <filesystem>
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
MigrationWorker::MigrationWorker(const LegacyEmu selected_legacy_emu_,
MigrationWorker::MigrationWorker(const Emulator selected_legacy_emu_,
const bool clear_shader_cache_, const bool clear_shader_cache_,
const MigrationStrategy strategy_) const MigrationStrategy strategy_)
: QObject() : QObject()
@ -18,27 +21,9 @@ void MigrationWorker::process()
namespace fs = std::filesystem; namespace fs = std::filesystem;
const auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive; const auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive;
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;
switch (selected_legacy_emu) {
LEGACY_EMU(Citron)
LEGACY_EMU(Sudachi)
LEGACY_EMU(Yuzu)
LEGACY_EMU(Suyu)
}
#undef LEGACY_EMU
std::string legacy_user_dir = selected_legacy_emu.get_user_dir();
std::string legacy_config_dir = selected_legacy_emu.get_config_dir();
std::string legacy_cache_dir = selected_legacy_emu.get_cache_dir();
fs::path eden_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir); fs::path eden_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir);
fs::path config_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir); fs::path config_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir);
@ -62,7 +47,7 @@ void MigrationWorker::process()
emit error(tr("Linking the old directory failed. You may need to re-run with " emit error(tr("Linking the old directory failed. You may need to re-run with "
"administrative privileges on Windows.\nOS gave error: %1") "administrative privileges on Windows.\nOS gave error: %1")
.arg(tr(e.what()))); .arg(tr(e.what())));
return;
std::exit(-1);
} }
// Windows doesn't need any more links, because cache and config // Windows doesn't need any more links, because cache and config
@ -119,5 +104,5 @@ void MigrationWorker::process()
fs::create_directory(shader_dir); fs::create_directory(shader_dir);
} }
emit finished(success_text);
emit finished(success_text, legacy_user_dir);
} }

53
src/yuzu/migration_worker.h

@ -2,25 +2,58 @@
#define MIGRATION_WORKER_H #define MIGRATION_WORKER_H
#include <QObject> #include <QObject>
#include "common/fs/path_util.h"
using namespace Common::FS;
typedef struct Emulator {
const char *name;
LegacyPath e_user_dir;
LegacyPath e_config_dir;
LegacyPath e_cache_dir;
const std::string get_user_dir() const {
return Common::FS::GetLegacyPath(e_user_dir).string();
}
const std::string get_config_dir() const {
return Common::FS::GetLegacyPath(e_config_dir).string();
}
const std::string get_cache_dir() const {
return Common::FS::GetLegacyPath(e_cache_dir).string();
}
const std::string lower_name() const {
std::string lower_name{name};
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
[](unsigned char c){ return std::tolower(c); });
return lower_name;
}
} Emulator;
#define EMU(name) Emulator{#name, name##Dir, name##ConfigDir, name##CacheDir}
static constexpr std::array<Emulator, 4> legacy_emus = {
EMU(Citron),
EMU(Sudachi),
EMU(Suyu),
EMU(Yuzu),
};
#undef EMU
class MigrationWorker : public QObject class MigrationWorker : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum class LegacyEmu {
Citron,
Sudachi,
Yuzu,
Suyu,
};
enum class MigrationStrategy { enum class MigrationStrategy {
Copy, Copy,
Move, Move,
Link, Link,
}; };
MigrationWorker(const LegacyEmu selected_legacy_emu,
MigrationWorker(const Emulator selected_legacy_emu,
const bool clear_shader_cache, const bool clear_shader_cache,
const MigrationStrategy strategy); const MigrationStrategy strategy);
@ -28,11 +61,11 @@ public slots:
void process(); void process();
signals: signals:
void finished(const QString &success_text);
void finished(const QString &success_text, const std::string &user_dir);
void error(const QString &error_message); void error(const QString &error_message);
private: private:
LegacyEmu selected_legacy_emu;
Emulator selected_legacy_emu;
bool clear_shader_cache; bool clear_shader_cache;
MigrationStrategy strategy; MigrationStrategy strategy;
QString success_text = tr("Data was migrated successfully."); QString success_text = tr("Data was migrated successfully.");

221
src/yuzu/user_data_migration.cpp

@ -40,150 +40,144 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
{ {
namespace fs = std::filesystem; namespace fs = std::filesystem;
const QString migration_prompt_message = main_window->tr(
"Would you like to migrate your data for use in Eden?\n"
// define strings here for easy access
static constexpr const char *prompt_prefix_text
= "Eden has detected user data for the following emulators:";
static constexpr const char *migration_prompt_message
= "Would you like to migrate your data for use in Eden?\n"
"Select the corresponding button to migrate data from that emulator.\n" "Select the corresponding button to migrate data from that emulator.\n"
"This may take a while.");
"This may take a while.";
static constexpr const char *clear_shader_tooltip
= "Clearing shader cache is recommended for all "
"users.\nDo not uncheck unless you know what "
"you're doing.";
static constexpr const char *keep_old_data_tooltip
= "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.";
static constexpr const char *clear_old_data_tooltip
= "Deletes the old data directory.\nThis is recommended on "
"devices with space constraints.";
static constexpr const char *link_old_dir_tooltip
= "Creates a filesystem link between the old directory and Eden directory.\n"
"This is recommended if you want to share data between emulators.";
bool any_found = false;
// actual migration code
MigrationDialog migration_prompt; MigrationDialog migration_prompt;
migration_prompt.setWindowTitle(main_window->tr("Migration"));
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);
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
migration_prompt.setWindowTitle(QObject::tr("Migration"));
// mutually exclusive
QButtonGroup *group = new QButtonGroup(&migration_prompt); 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!!!!
QMap<QString, bool> found;
QMap<QString, MigrationWorker::LegacyEmu> legacyMap;
QMap<QString, QAbstractButton *> 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)] = MigrationWorker::LegacyEmu::name; \
found[main_window->tr(#name)] = name##_found; \
if (name##_found) \
any_found = true;
EMU_MAP(Citron)
EMU_MAP(Sudachi)
EMU_MAP(Yuzu)
EMU_MAP(Suyu)
#undef EMU_MAP
if (any_found) {
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;
QAbstractButton *button = migration_prompt.addButton(iter.key());
buttonMap[iter.key()] = button;
promptText.append(main_window->tr("\n- %1").arg(iter.key()));
// MACRO MADNESS
#define BUTTON(clazz, name, text, tooltip, checkState) \
clazz *name = new clazz(&migration_prompt); \
name->setText(QObject::tr(text)); \
name->setToolTip(QObject::tr(tooltip)); \
name->setChecked(checkState); \
migration_prompt.addBox(name);
BUTTON(QCheckBox, clear_shaders, "Clear Shader Cache", clear_shader_tooltip, true)
#define RADIO(name, text, tooltip, checkState) \
BUTTON(QRadioButton, name, text, tooltip, checkState) \
group->addButton(name);
RADIO(keep_old, "Keep Old Data", keep_old_data_tooltip, true)
RADIO(clear_old, "Clear Old Data", clear_old_data_tooltip, false)
RADIO(link_old, "Link Old Directory", link_old_dir_tooltip, false)
#undef RADIO
#undef BUTTON
std::vector<Emulator> found{};
for (const Emulator &emu : legacy_emus)
if (fs::is_directory(emu.get_user_dir()))
found.emplace_back(emu);
if (found.empty()) {
return;
}
// makes my life easier
qRegisterMetaType<Emulator>();
QString prompt_text = QObject::tr(prompt_prefix_text);
// natural language processing is a nightmare
for (const Emulator &emu : found) {
prompt_text.append(QStringLiteral("\n- %1").arg(QObject::tr(emu.name)));
QAbstractButton *button = migration_prompt.addButton(QObject::tr(emu.name));
// This is cursed, but it's actually the most efficient way by a mile
button->setProperty("emulator", QVariant::fromValue(emu));
} }
promptText.append(main_window->tr("\n\n"));
prompt_text.append(QObject::tr("\n\n"));
prompt_text.append(QObject::tr(migration_prompt_message));
migration_prompt.setText(promptText + migration_prompt_message);
migration_prompt.addButton(main_window->tr("No"), true);
migration_prompt.setText(prompt_text);
migration_prompt.addButton(QObject::tr("No"), true);
migration_prompt.exec(); migration_prompt.exec();
QAbstractButton *button = migration_prompt.clickedButton();
if (button->text() == QObject::tr("No")) {
return ShowMigrationCancelledMessage(main_window);
}
MigrationWorker::MigrationStrategy strategy; MigrationWorker::MigrationStrategy strategy;
if (link->isChecked()) {
strategy = MigrationWorker::MigrationStrategy::Link;
} else if (clear_old->isChecked()) {
strategy = MigrationWorker::MigrationStrategy::Move;
} else {
switch (group->checkedId()) {
default:
[[fallthrough]];
case 0:
strategy = MigrationWorker::MigrationStrategy::Copy; strategy = MigrationWorker::MigrationStrategy::Copy;
break;
case 1:
strategy = MigrationWorker::MigrationStrategy::Move;
break;
case 2:
strategy = MigrationWorker::MigrationStrategy::Link;
break;
} }
QMapIterator buttonIter(buttonMap);
while (buttonIter.hasNext()) {
buttonIter.next();
if (buttonIter.value() == migration_prompt.clickedButton()) {
MigrateUserData(main_window, MigrateUserData(main_window,
legacyMap[buttonIter.key()],
button->property("emulator").value<Emulator>(),
clear_shaders->isChecked(), clear_shaders->isChecked(),
strategy); strategy);
return;
}
}
// If we're here, the user chose not to migrate
ShowMigrationCancelledMessage(main_window);
}
else // no other data was found
return;
} }
void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow *main_window) void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow *main_window)
{ {
QMessageBox::information(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")
QObject::tr("Migration"),
QObject::tr("You can manually re-trigger this prompt by deleting the "
"new config directory:\n%1")
.arg(QString::fromStdString(Common::FS::GetEdenPathString( .arg(QString::fromStdString(Common::FS::GetEdenPathString(
Common::FS::EdenPath::ConfigDir))), Common::FS::EdenPath::ConfigDir))),
QMessageBox::Ok); QMessageBox::Ok);
} }
void UserDataMigrator::MigrateUserData(QMainWindow *main_window, void UserDataMigrator::MigrateUserData(QMainWindow *main_window,
const MigrationWorker::LegacyEmu selected_legacy_emu,
const Emulator selected_legacy_emu,
const bool clear_shader_cache, const bool clear_shader_cache,
const MigrationWorker::MigrationStrategy strategy) const MigrationWorker::MigrationStrategy strategy)
{ {
// Create a dialog to let the user know it's migrating, some users noted confusion.
selected_emu = selected_legacy_emu;
// Create a dialog to let the user know it's migrating
QProgressDialog *progress = new QProgressDialog(main_window); QProgressDialog *progress = new QProgressDialog(main_window);
progress->setWindowTitle(main_window->tr("Migrating"));
progress->setLabelText(main_window->tr("Migrating, this may take a while..."));
progress->setWindowTitle(QObject::tr("Migrating"));
progress->setLabelText(QObject::tr("Migrating, this may take a while..."));
progress->setRange(0, 0); progress->setRange(0, 0);
progress->setCancelButton(nullptr); progress->setCancelButton(nullptr);
progress->setWindowModality(Qt::WindowModality::ApplicationModal); progress->setWindowModality(Qt::WindowModality::ApplicationModal);
@ -194,13 +188,14 @@ void UserDataMigrator::MigrateUserData(QMainWindow *main_window,
thread->connect(thread, &QThread::started, worker, &MigrationWorker::process); thread->connect(thread, &QThread::started, worker, &MigrationWorker::process);
thread->connect(worker, &MigrationWorker::finished, progress, [=](const QString &success_text) {
thread->connect(worker, &MigrationWorker::finished, progress, [=, this](const QString &success_text, const std::string &path) {
progress->close(); progress->close();
QMessageBox::information(main_window, QMessageBox::information(main_window,
main_window->tr("Migration"),
QObject::tr("Migration"),
success_text, success_text,
QMessageBox::Ok); QMessageBox::Ok);
migrated = true;
thread->quit(); thread->quit();
}); });

5
src/yuzu/user_data_migration.h

@ -14,11 +14,14 @@ class UserDataMigrator {
public: public:
UserDataMigrator(QMainWindow* main_window); UserDataMigrator(QMainWindow* main_window);
bool migrated{false};
Emulator selected_emu;
private: private:
void ShowMigrationPrompt(QMainWindow* main_window); void ShowMigrationPrompt(QMainWindow* main_window);
void ShowMigrationCancelledMessage(QMainWindow* main_window); void ShowMigrationCancelledMessage(QMainWindow* main_window);
void MigrateUserData(QMainWindow* main_window, void MigrateUserData(QMainWindow* main_window,
const MigrationWorker::LegacyEmu selected_legacy_emu,
const Emulator selected_legacy_emu,
const bool clear_shader_cache, const bool clear_shader_cache,
const MigrationWorker::MigrationStrategy strategy); const MigrationWorker::MigrationStrategy strategy);
}; };
Loading…
Cancel
Save