Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 51 additions & 16 deletions launcher/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@

#include "ApplicationMessage.h"

#include <array>
#include <iostream>
#include <mutex>

Expand Down Expand Up @@ -133,6 +134,8 @@
#include <LocalPeer.h>

#include <stdlib.h>

#include "ConfigMigration.h"
#include "SysInfo.h"

#ifdef Q_OS_LINUX
Expand Down Expand Up @@ -265,6 +268,35 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
fflush(stderr);
}

QString combineAppDataPath(const QString& path)
{
return FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), path);
}

void handleConfigMigration()
{
if (!QFile::exists(BuildConfig.LAUNCHER_CONFIGFILE)) {
struct Migration {
QString configFile;
QString pathToExclude;
};

if (!ConfigMigration::migrate("elyprismlauncher.cfg", BuildConfig.LAUNCHER_CONFIGFILE,
ConfigMigration::ExcludeByPrefix(combineAppDataPath("../../ElyPrismLauncher")),
ConfigMigration::TransformPineconeMCSettings{})) {
std::array migrations{ Migration{ "prismlauncher.cfg", combineAppDataPath("../../PrismLauncher") },
Migration{ "polymc.cfg", combineAppDataPath("../../PolyMC") },
Migration{ "multimc.cfg", combineAppDataPath("../../multimc") } };

for (const auto& [conf, path] : migrations) {
if (ConfigMigration::migrate(conf, BuildConfig.LAUNCHER_CONFIGFILE, ConfigMigration::ExcludeByPrefix(path))) {
break;
}
}
}
}
}

} // namespace

std::tuple<QDateTime, QString, QString, QString, QString> read_lock_File(const QString& path)
Expand Down Expand Up @@ -586,20 +618,22 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}

{
bool migrated = false;

if (!migrated)
migrated = handleDataMigration(
dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PrismLauncher"),
"Prism Launcher", "prismlauncher.cfg");
if (!migrated)
migrated = handleDataMigration(
dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC",
"polymc.cfg");
if (!migrated)
migrated = handleDataMigration(
dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC",
"multimc.cfg");
struct Migration {
QString oldData;
QString name;
QString configFile;
};

std::array migrations = { Migration{ combineAppDataPath("../../ElyPrismLauncher"), "PineconeMC", "elyprismlauncher.cfg" },
Migration{ combineAppDataPath("../../PrismLauncher"), "Prism Launcher", "prismlauncher.cfg" },
Migration{ combineAppDataPath("../../PolyMC"), "PolyMC", "polymc.cfg" },
Migration{ combineAppDataPath("../../multimc"), "MultiMC", "multimc.cfg" } };

for (const auto& [oldData, name, configFile] : migrations) {
if (handleDataMigration(dataPath, oldData, name, configFile)) {
break;
}
}
}

{
Expand Down Expand Up @@ -649,8 +683,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)

// Initialize application settings
{
// Provide a fallback for migration from PolyMC
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
// Provide a fallback for migration from some launchers
handleConfigMigration();
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE));

// Date
QDate now = QDate::currentDate();
Expand Down
2 changes: 2 additions & 0 deletions launcher/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,8 @@ SET(LAUNCHER_SOURCES

KnownJavaAgents.cpp
KnownJavaAgents.h
ConfigMigration.h
ConfigMigration.cpp

# GUI - windows
ui/GuiUtil.h
Expand Down
57 changes: 57 additions & 0 deletions launcher/ConfigMigration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Freesm Launcher - Minecraft Launcher
* Copyright (C) 2026 so5iso4ka <so5iso4ka@icloud.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include "ConfigMigration.h"

namespace ConfigMigration {
Setting ExcludeByPrefix::operator()(const Setting& s, const QString&) const
{
if (s.value.toString().startsWith(prefix)) {
return {};
}
return s;
}

Setting TransformPineconeMCSettings::operator()(const Setting& setting, const QString& group) const
{
auto elySkinSettingProj = [](int elyPatchPreference) -> int {
switch (elyPatchPreference) {
case 0: { // Always
return 1;
}
case 1: { // When using Ely and Offline accounts
return 3;
}
case 2: { // When using Ely accounts
return 2;
}
case 3: { // Never
return 0;
}
default: {
return 2;
}
}
};

if (group.isEmpty() && setting.key == "ElyPatchPreference" && !setting.value.isNull()) {
return Setting{ "UseElySkins", elySkinSettingProj(setting.value.toInt()) };
}
return setting;
}
} // namespace ConfigMigration
93 changes: 93 additions & 0 deletions launcher/ConfigMigration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Freesm Launcher - Minecraft Launcher
* Copyright (C) 2026 so5iso4ka <so5iso4ka@icloud.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include <QFile>
#include <QSettings>
#include <QString>
#include <QStringList>
#include <QVariant>
#include <algorithm>
#include <concepts>
#include <functional>
#include <list>

namespace ConfigMigration {
struct Setting {
QString key;
QVariant value;
};

template <typename F>
concept SettingProj =
std::regular_invocable<F, Setting, QString> && std::is_convertible_v<std::invoke_result_t<F, Setting, QString>, Setting>;

class ExcludeByPrefix {
public:
Setting operator()(const Setting& s, const QString&) const;

QString prefix;
};

class TransformPineconeMCSettings {
public:
Setting operator()(const Setting& setting, const QString& group) const;
};

template <SettingProj... Proj>
bool migrate(const QString& oldConfig, const QString& newConfig, Proj... proj)
{
if (!QFile::exists(oldConfig)) {
return false;
}

QSettings oldSettings(oldConfig, QSettings::IniFormat);
QSettings newSettings(newConfig, QSettings::IniFormat);

std::list<QString> groups{ "" };
{
auto topLevelGroups = oldSettings.childGroups();
groups.insert(groups.end(), topLevelGroups.begin(), topLevelGroups.end());
}

for (auto it = groups.begin(); it != groups.end(); ++it) {
oldSettings.beginGroup(*it);
newSettings.beginGroup(*it);

if (it != groups.begin()) {
auto innerGroups = oldSettings.childGroups();
std::ranges::for_each(innerGroups, [currentGroup = *it](QString& group) { group.prepend(currentGroup + '/'); });
groups.insert(groups.end(), innerGroups.begin(), innerGroups.end());
}

auto keys = oldSettings.childKeys();
for (const auto& key : keys) {
Setting setting{ key, oldSettings.value(key) };
((setting = std::invoke(proj, setting, *it)), ...);
if (!setting.key.isEmpty()) {
newSettings.setValue(setting.key, setting.value);
}
}

newSettings.endGroup();
oldSettings.endGroup();
}
return true;
}
} // namespace ConfigMigration
11 changes: 10 additions & 1 deletion launcher/minecraft/auth/AccountData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)

bool AccountData::resumeStateFromV3(QJsonObject data)
{
bool migratingFromEPL = false;
auto typeV = data.value("type");
if (!typeV.isString()) {
qWarning() << "Failed to parse account data: type is missing.";
Expand All @@ -295,6 +296,9 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
type = AccountType::Elyby;
} else if (typeS == "Custom") {
type = AccountType::Custom;
} else if (typeS == "Ely") {
type = AccountType::Elyby;
migratingFromEPL = true;
} else {
qWarning() << "Failed to parse account data: type is not recognized.";
return false;
Expand All @@ -313,7 +317,12 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
case AccountType::Offline:
break;
case AccountType::Elyby: {
auto clientIDV = data.value("elyby-client-id");
QJsonValue clientIDV;
if (migratingFromEPL) {
clientIDV = data.value("msa-client-id");
} else {
clientIDV = data.value("elyby-client-id");
}
if (clientIDV.isString()) {
clientID = clientIDV.toString();
} // leave clientID empty if it doesn't exist or isn't a string
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ ecm_add_test(CatPack_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR

ecm_add_test(XmlLogs_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME XmlLogs)

ecm_add_test(ConfigMigration_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME ConfigMigration)
Loading
Loading