diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 0b3c78f70..f3bdf80bc 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -167,6 +167,8 @@ void GMainWindow::ShowCommandOutput(std::string title, std::string message) { GMainWindow::GMainWindow(Core::System& system_) : ui{std::make_unique()}, system{system_}, movie{system.Movie()}, + emu_user_dir_exists{ + std::filesystem::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))}, config{std::make_unique()}, emu_thread{nullptr} { Common::Log::Initialize(); Common::Log::Start(); @@ -307,6 +309,11 @@ GMainWindow::GMainWindow(Core::System& system_) } } + // Check if data migration from Citra/Lime3DS needs to be performed + if (!emu_user_dir_exists) { + ShowMigrationPrompt(); + } + #ifdef __unix__ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); #endif @@ -1205,6 +1212,115 @@ void GMainWindow::ShowUpdaterWidgets() { } #endif +void GMainWindow::ShowMigrationCancelledMessage() { + QMessageBox::information( + this, tr("Migration"), + tr("You can manually re-trigger this prompt by deleting the " + "new user data directory:\n" + "%1") + .arg(QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))), + QMessageBox::Ok); +} + +void GMainWindow::ShowMigrationPrompt() { + namespace fs = std::filesystem; + + const QString migration_prompt_message = + tr("Would you like to migrate your data for use in Azahar?\n" + "(This may take a while; The old data will not be deleted)"); + const bool citra_dir_found = + fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraUserDir)); + const bool lime3ds_dir_found = + fs::is_directory(FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSUserDir)); + + if (citra_dir_found && lime3ds_dir_found) { + QMessageBox migration_prompt; + migration_prompt.setWindowTitle(tr("Migration")); + migration_prompt.setText( + tr("Azahar has detected emulator data for Citra and Lime3DS.\n\n") + + migration_prompt_message); + migration_prompt.setIcon(QMessageBox::Information); + + const QAbstractButton* lime3dsButton = + migration_prompt.addButton(tr("Migrate from Lime3DS"), QMessageBox::YesRole); + const QAbstractButton* citraButton = + migration_prompt.addButton(tr("Migrate from Citra"), QMessageBox::YesRole); + migration_prompt.addButton(QMessageBox::No); + + migration_prompt.exec(); + + if (citraButton == migration_prompt.clickedButton()) { + MigrateUserData(LegacyEmu::Citra); + } + if (lime3dsButton == migration_prompt.clickedButton()) { + MigrateUserData(LegacyEmu::Lime3DS); + } + + return; + } + + else if (citra_dir_found) { + if (QMessageBox::information( + this, tr("Migration"), + tr("Azahar has detected emulator data for Citra.\n\n") + migration_prompt_message, + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + MigrateUserData(LegacyEmu::Citra); + return; + } + } + + else if (lime3ds_dir_found) { + if (QMessageBox::information( + this, tr("Migration"), + tr("Azahar has detected emulator data for Lime3DS.\n\n") + migration_prompt_message, + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + MigrateUserData(LegacyEmu::Lime3DS); + return; + } + } + + // If we're here, the user chose not to migrate + ShowMigrationCancelledMessage(); +} + +void GMainWindow::MigrateUserData(const LegacyEmu selected_legacy_emu) { + namespace fs = std::filesystem; + 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; + if (LegacyEmu::Citra == selected_legacy_emu) { + legacy_user_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraUserDir); + legacy_config_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraConfigDir); + legacy_cache_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyCitraCacheDir); + } else if (LegacyEmu::Lime3DS == selected_legacy_emu) { + legacy_user_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSUserDir); + legacy_config_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSConfigDir); + legacy_cache_dir = FileUtil::GetUserPath(FileUtil::UserPath::LegacyLime3DSCacheDir); + } + + fs::copy(legacy_user_dir, FileUtil::GetUserPath(FileUtil::UserPath::UserDir), copy_options); + + if (fs::is_directory(legacy_config_dir)) { + fs::copy(legacy_config_dir, FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir), + copy_options); + } + if (fs::is_directory(legacy_cache_dir)) { + fs::copy(legacy_cache_dir, FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), + copy_options); + } + + QMessageBox::information( + this, tr("Migration"), + tr("Data was migrated successfully. Azahar will now restart.\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); +} + #if defined(HAVE_SDL2) && defined(__unix__) && !defined(__APPLE__) static std::optional HoldWakeLockLinux(u32 window_id = 0) { if (!QDBusConnection::sessionBus().isConnected()) { diff --git a/src/citra_qt/citra_qt.h b/src/citra_qt/citra_qt.h index 6f623885b..ce247373c 100644 --- a/src/citra_qt/citra_qt.h +++ b/src/citra_qt/citra_qt.h @@ -1,4 +1,4 @@ -// Copyright 2014 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -173,6 +173,14 @@ private: void CheckForUpdates(); #endif + enum LegacyEmu { + Citra, + Lime3DS, + }; + void ShowMigrationCancelledMessage(); + void ShowMigrationPrompt(); + void MigrateUserData(const LegacyEmu selected_legacy_emu); + /** * Stores the filename in the recently loaded files list. * The new filename is stored at the beginning of the recently loaded files list. @@ -349,6 +357,10 @@ private: bool message_label_used_for_movie = false; MultiplayerState* multiplayer_state = nullptr; + + // Created before `config` to ensure that emu data directory + // isn't created before the check is performed + bool emu_user_dir_exists; std::unique_ptr config; // Whether emulation is currently running in Citra. diff --git a/src/common/common_paths.h b/src/common/common_paths.h index e8ff6f88f..bb73c049e 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -23,18 +23,28 @@ #define EMU_DATA_DIR USER_DIR #else #ifdef _WIN32 -#define EMU_DATA_DIR "Citra" +#define EMU_DATA_DIR "Azahar" +#define LEGACY_CITRA_DATA_DIR "Citra" +#define LEGACY_LIME3DS_DATA_DIR "Lime3DS" #elif defined(__APPLE__) #include #if TARGET_OS_IPHONE -#define APPLE_EMU_DATA_DIR "Documents" DIR_SEP "Citra" +#define EMU_APPLE_DATA_DIR "Documents" DIR_SEP "Azahar" +#define LEGACY_CITRA_APPLE_DATA_DIR "Documents" DIR_SEP "Citra" +#define LEGACY_LIME3DS_APPLE_DATA_DIR "Documents" DIR_SEP "Lime3DS" #else -#define APPLE_EMU_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Citra" +#define EMU_APPLE_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Azahar" +#define LEGACY_CITRA_APPLE_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Citra" +#define LEGACY_LIME3DS_APPLE_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Lime3DS" #endif // For compatibility with XDG paths. -#define EMU_DATA_DIR "citra-emu" +#define EMU_DATA_DIR "azahar-emu" +#define LEGACY_CITRA_DATA_DIR "citra-emu" +#define LEGACY_LIME3DS_DATA_DIR "lime3ds-emu" #else -#define EMU_DATA_DIR "citra-emu" +#define EMU_DATA_DIR "azahar-emu" +#define LEGACY_CITRA_DATA_DIR "citra-emu" +#define LEGACY_LIME3DS_DATA_DIR "lime3ds-emu" #endif #endif diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b4e4393d2..217ae2db9 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -783,8 +783,15 @@ void SetUserPath(const std::string& path) { } else { #ifdef _WIN32 user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; + std::string& legacy_citra_user_path = g_paths[UserPath::LegacyCitraUserDir]; + std::string& legacy_lime3ds_user_path = g_paths[UserPath::LegacyLime3DSUserDir]; + if (!FileUtil::IsDirectory(user_path)) { user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; + legacy_citra_user_path = + AppDataRoamingDirectory() + DIR_SEP LEGACY_CITRA_DATA_DIR DIR_SEP; + legacy_lime3ds_user_path = + AppDataRoamingDirectory() + DIR_SEP LEGACY_LIME3DS_DATA_DIR DIR_SEP; } else { LOG_INFO(Common_Filesystem, "Using the local user directory"); } @@ -796,6 +803,8 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); #else + std::string& legacy_citra_user_path = g_paths[UserPath::LegacyCitraUserDir]; + std::string& legacy_lime3ds_user_path = g_paths[UserPath::LegacyLime3DSUserDir]; auto current_dir = FileUtil::GetCurrentDir(); if (current_dir.has_value() && FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) { @@ -804,23 +813,47 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); } else { std::string data_dir = GetUserDirectory("XDG_DATA_HOME") + DIR_SEP EMU_DATA_DIR DIR_SEP; + + std::string legacy_citra_data_dir = + GetUserDirectory("XDG_DATA_HOME") + DIR_SEP LEGACY_CITRA_DATA_DIR DIR_SEP; + std::string legacy_lime3ds_data_dir = + GetUserDirectory("XDG_DATA_HOME") + DIR_SEP LEGACY_LIME3DS_DATA_DIR DIR_SEP; std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME") + DIR_SEP EMU_DATA_DIR DIR_SEP; std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME") + DIR_SEP EMU_DATA_DIR DIR_SEP; + g_paths.emplace(UserPath::LegacyCitraConfigDir, + GetUserDirectory("XDG_CONFIG_HOME") + + DIR_SEP LEGACY_CITRA_DATA_DIR DIR_SEP); + g_paths.emplace(UserPath::LegacyCitraCacheDir, + GetUserDirectory("XDG_CACHE_HOME") + + DIR_SEP LEGACY_CITRA_DATA_DIR DIR_SEP); + g_paths.emplace(UserPath::LegacyLime3DSConfigDir, + GetUserDirectory("XDG_CONFIG_HOME") + + DIR_SEP LEGACY_LIME3DS_DATA_DIR DIR_SEP); + g_paths.emplace(UserPath::LegacyLime3DSCacheDir, + GetUserDirectory("XDG_CACHE_HOME") + + DIR_SEP LEGACY_LIME3DS_DATA_DIR DIR_SEP); + #if defined(__APPLE__) // If XDG directories don't already exist from a previous setup, use standard macOS // paths. if (!FileUtil::Exists(data_dir) && !FileUtil::Exists(config_dir) && !FileUtil::Exists(cache_dir)) { - data_dir = GetHomeDirectory() + DIR_SEP APPLE_EMU_DATA_DIR DIR_SEP; + data_dir = GetHomeDirectory() + DIR_SEP EMU_APPLE_DATA_DIR DIR_SEP; + legacy_citra_data_dir = + GetHomeDirectory() + DIR_SEP LEGACY_CITRA_APPLE_DATA_DIR DIR_SEP; + legacy_lime3ds_data_dir = + GetHomeDirectory() + DIR_SEP LEGACY_LIME3DS_APPLE_DATA_DIR DIR_SEP; config_dir = data_dir + CONFIG_DIR DIR_SEP; cache_dir = data_dir + CACHE_DIR DIR_SEP; } #endif user_path = data_dir; + legacy_citra_user_path = legacy_citra_data_dir; + legacy_lime3ds_user_path = legacy_lime3ds_data_dir; g_paths.emplace(UserPath::ConfigDir, config_dir); g_paths.emplace(UserPath::CacheDir, cache_dir); } diff --git a/src/common/file_util.h b/src/common/file_util.h index 51ce3d7f2..0a1e8324c 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -39,17 +39,23 @@ enum class UserPath { ConfigDir, DLLDir, DumpDir, + IconsDir, + LegacyCitraCacheDir, // LegacyXXXCacheDir and LegacyXXXConfigDir are only defined if migrating + LegacyCitraConfigDir, // these directories is necessary (aka not a child of LegacyXXXUserDir) + LegacyCitraUserDir, + LegacyLime3DSCacheDir, + LegacyLime3DSConfigDir, + LegacyLime3DSUserDir, LoadDir, LogDir, NANDDir, + PlayTimeDir, RootDir, SDMCDir, ShaderDir, StatesDir, SysDataDir, UserDir, - IconsDir, - PlayTimeDir, }; // Replaces install-specific paths with standard placeholders, and back again