summaryrefslogtreecommitdiff
path: root/meshmc/launcher/minecraft/legacy
diff options
context:
space:
mode:
Diffstat (limited to 'meshmc/launcher/minecraft/legacy')
-rw-r--r--meshmc/launcher/minecraft/legacy/LegacyInstance.cpp270
-rw-r--r--meshmc/launcher/minecraft/legacy/LegacyInstance.h172
-rw-r--r--meshmc/launcher/minecraft/legacy/LegacyModList.cpp150
-rw-r--r--meshmc/launcher/minecraft/legacy/LegacyModList.h69
-rw-r--r--meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.cpp151
-rw-r--r--meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.h49
6 files changed, 861 insertions, 0 deletions
diff --git a/meshmc/launcher/minecraft/legacy/LegacyInstance.cpp b/meshmc/launcher/minecraft/legacy/LegacyInstance.cpp
new file mode 100644
index 0000000000..c010974481
--- /dev/null
+++ b/meshmc/launcher/minecraft/legacy/LegacyInstance.cpp
@@ -0,0 +1,270 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QFileInfo>
+#include <minecraft/launch/MeshMCPartLaunch.h>
+#include <QDir>
+#include <settings/Setting.h>
+
+#include "LegacyInstance.h"
+
+#include "minecraft/legacy/LegacyModList.h"
+#include "minecraft/WorldList.h"
+#include <MMCZip.h>
+#include <FileSystem.h>
+
+LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings,
+ SettingsObjectPtr settings,
+ const QString& rootDir)
+ : BaseInstance(globalSettings, settings, rootDir)
+{
+ settings->registerSetting("NeedsRebuild", true);
+ settings->registerSetting("ShouldUpdate", false);
+ settings->registerSetting("JarVersion", QString());
+ settings->registerSetting("IntendedJarVersion", QString());
+ /*
+ * custom base jar has no default. it is determined in code... see the
+ * accessor methods for it
+ *
+ * for instances that DO NOT have the CustomBaseJar setting (legacy
+ * instances),
+ * [.]minecraft/bin/mcbackup.jar is the default base jar
+ */
+ settings->registerSetting("UseCustomBaseJar", true);
+ settings->registerSetting("CustomBaseJar", "");
+}
+
+QString LegacyInstance::mainJarToPreserve() const
+{
+ bool customJar = m_settings->get("UseCustomBaseJar").toBool();
+ if (customJar) {
+ auto base = baseJar();
+ if (QFile::exists(base)) {
+ return base;
+ }
+ }
+ auto runnable = runnableJar();
+ if (QFile::exists(runnable)) {
+ return runnable;
+ }
+ return QString();
+}
+
+QString LegacyInstance::baseJar() const
+{
+ bool customJar = m_settings->get("UseCustomBaseJar").toBool();
+ if (customJar) {
+ return customBaseJar();
+ } else
+ return defaultBaseJar();
+}
+
+QString LegacyInstance::customBaseJar() const
+{
+ QString value = m_settings->get("CustomBaseJar").toString();
+ if (value.isNull() || value.isEmpty()) {
+ return defaultCustomBaseJar();
+ }
+ return value;
+}
+
+bool LegacyInstance::shouldUseCustomBaseJar() const
+{
+ return m_settings->get("UseCustomBaseJar").toBool();
+}
+
+Task::Ptr LegacyInstance::createUpdateTask(Net::Mode)
+{
+ return nullptr;
+}
+
+std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const
+{
+ if (!jar_mod_list) {
+ auto list = new LegacyModList(jarModsDir(), modListFile());
+ jar_mod_list.reset(list);
+ }
+ jar_mod_list->update();
+ return jar_mod_list;
+}
+
+QString LegacyInstance::gameRoot() const
+{
+ QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
+ QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
+
+ if (mcDir.exists() && !dotMCDir.exists())
+ return mcDir.filePath();
+ else
+ return dotMCDir.filePath();
+}
+
+QString LegacyInstance::binRoot() const
+{
+ return FS::PathCombine(gameRoot(), "bin");
+}
+
+QString LegacyInstance::modsRoot() const
+{
+ return FS::PathCombine(gameRoot(), "mods");
+}
+
+QString LegacyInstance::jarModsDir() const
+{
+ return FS::PathCombine(instanceRoot(), "instMods");
+}
+
+QString LegacyInstance::libDir() const
+{
+ return FS::PathCombine(gameRoot(), "lib");
+}
+
+QString LegacyInstance::savesDir() const
+{
+ return FS::PathCombine(gameRoot(), "saves");
+}
+
+QString LegacyInstance::coreModsDir() const
+{
+ return FS::PathCombine(gameRoot(), "coremods");
+}
+
+QString LegacyInstance::resourceDir() const
+{
+ return FS::PathCombine(gameRoot(), "resources");
+}
+QString LegacyInstance::texturePacksDir() const
+{
+ return FS::PathCombine(gameRoot(), "texturepacks");
+}
+
+QString LegacyInstance::runnableJar() const
+{
+ return FS::PathCombine(binRoot(), "minecraft.jar");
+}
+
+QString LegacyInstance::modListFile() const
+{
+ return FS::PathCombine(instanceRoot(), "modlist");
+}
+
+QString LegacyInstance::instanceConfigFolder() const
+{
+ return FS::PathCombine(gameRoot(), "config");
+}
+
+bool LegacyInstance::shouldRebuild() const
+{
+ return m_settings->get("NeedsRebuild").toBool();
+}
+
+QString LegacyInstance::currentVersionId() const
+{
+ return m_settings->get("JarVersion").toString();
+}
+
+QString LegacyInstance::intendedVersionId() const
+{
+ return m_settings->get("IntendedJarVersion").toString();
+}
+
+bool LegacyInstance::shouldUpdate() const
+{
+ QVariant var = settings()->get("ShouldUpdate");
+ if (!var.isValid() || var.toBool() == false) {
+ return intendedVersionId() != currentVersionId();
+ }
+ return true;
+}
+
+QString LegacyInstance::defaultBaseJar() const
+{
+ return "versions/" + intendedVersionId() + "/" + intendedVersionId() +
+ ".jar";
+}
+
+QString LegacyInstance::defaultCustomBaseJar() const
+{
+ return FS::PathCombine(binRoot(), "mcbackup.jar");
+}
+
+std::shared_ptr<WorldList> LegacyInstance::worldList() const
+{
+ if (!m_world_list) {
+ m_world_list.reset(new WorldList(savesDir()));
+ }
+ return m_world_list;
+}
+
+QString LegacyInstance::typeName() const
+{
+ return tr("Legacy");
+}
+
+QString LegacyInstance::getStatusbarDescription()
+{
+ return tr("Instance from previous versions.");
+}
+
+QStringList
+LegacyInstance::verboseDescription(AuthSessionPtr session,
+ MinecraftServerTargetPtr serverToJoin)
+{
+ QStringList out;
+
+ auto alltraits = traits();
+ if (alltraits.size()) {
+ out << "Traits:";
+ for (auto trait : alltraits) {
+ out << " " + trait;
+ }
+ out << "";
+ }
+
+ QString windowParams;
+ if (settings()->get("LaunchMaximized").toBool()) {
+ out << "Window size: max (if available)";
+ } else {
+ auto width = settings()->get("MinecraftWinWidth").toInt();
+ auto height = settings()->get("MinecraftWinHeight").toInt();
+ out << "Window size: " + QString::number(width) + " x " +
+ QString::number(height);
+ }
+ out << "";
+ return out;
+}
diff --git a/meshmc/launcher/minecraft/legacy/LegacyInstance.h b/meshmc/launcher/minecraft/legacy/LegacyInstance.h
new file mode 100644
index 0000000000..6edcee96db
--- /dev/null
+++ b/meshmc/launcher/minecraft/legacy/LegacyInstance.h
@@ -0,0 +1,172 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "BaseInstance.h"
+#include "launch/LaunchTask.h"
+
+class ModFolderModel;
+class LegacyModList;
+class WorldList;
+class Task;
+/*
+ * WHY: Legacy instances - from MultiMC 3 and 4 - are here only to provide a way
+ * to upgrade them to the current format.
+ */
+class LegacyInstance : public BaseInstance
+{
+ Q_OBJECT
+ public:
+ explicit LegacyInstance(SettingsObjectPtr globalSettings,
+ SettingsObjectPtr settings, const QString& rootDir);
+
+ virtual void saveNow() override {}
+
+ /// Path to the instance's minecraft.jar
+ QString runnableJar() const;
+
+ //! Path to the instance's modlist file.
+ QString modListFile() const;
+
+ ////// Directories //////
+ QString libDir() const;
+ QString savesDir() const;
+ QString texturePacksDir() const;
+ QString jarModsDir() const;
+ QString coreModsDir() const;
+ QString resourceDir() const;
+
+ QString instanceConfigFolder() const override;
+
+ QString
+ gameRoot() const override; // Path to the instance's minecraft directory.
+ QString
+ modsRoot() const override; // Path to the instance's minecraft directory.
+ QString binRoot() const; // Path to the instance's minecraft bin directory.
+
+ /// Get the curent base jar of this instance. By default, it's the
+ /// versions/$version/$version.jar
+ QString baseJar() const;
+
+ /// the default base jar of this instance
+ QString defaultBaseJar() const;
+ /// the default custom base jar of this instance
+ QString defaultCustomBaseJar() const;
+
+ // the main jar that we actually want to keep when migrating the instance
+ QString mainJarToPreserve() const;
+
+ /*!
+ * Whether or not custom base jar is used
+ */
+ bool shouldUseCustomBaseJar() const;
+
+ /*!
+ * The value of the custom base jar
+ */
+ QString customBaseJar() const;
+
+ std::shared_ptr<LegacyModList> jarModList() const;
+ std::shared_ptr<WorldList> worldList() const;
+
+ /*!
+ * Whether or not the instance's minecraft.jar needs to be rebuilt.
+ * If this is true, when the instance launches, its jar mods will be
+ * re-added to a fresh minecraft.jar file.
+ */
+ bool shouldRebuild() const;
+
+ QString currentVersionId() const;
+ QString intendedVersionId() const;
+
+ QSet<QString> traits() const override
+ {
+ return {"legacy-instance", "texturepacks"};
+ };
+
+ virtual bool shouldUpdate() const;
+ virtual Task::Ptr createUpdateTask(Net::Mode mode) override;
+
+ virtual QString typeName() const override;
+
+ bool canLaunch() const override
+ {
+ return false;
+ }
+ bool canEdit() const override
+ {
+ return true;
+ }
+ bool canExport() const override
+ {
+ return false;
+ }
+ shared_qobject_ptr<LaunchTask>
+ createLaunchTask(AuthSessionPtr account,
+ MinecraftServerTargetPtr serverToJoin) override
+ {
+ return nullptr;
+ }
+ IPathMatcher::Ptr getLogFileMatcher() override
+ {
+ return nullptr;
+ }
+ QString getLogFileRoot() override
+ {
+ return gameRoot();
+ }
+
+ QString getStatusbarDescription() override;
+ QStringList
+ verboseDescription(AuthSessionPtr session,
+ MinecraftServerTargetPtr serverToJoin) override;
+
+ QProcessEnvironment createEnvironment() override
+ {
+ return QProcessEnvironment();
+ }
+ QMap<QString, QString> getVariables() const override
+ {
+ return {};
+ }
+
+ protected:
+ mutable std::shared_ptr<LegacyModList> jar_mod_list;
+ mutable std::shared_ptr<WorldList> m_world_list;
+};
diff --git a/meshmc/launcher/minecraft/legacy/LegacyModList.cpp b/meshmc/launcher/minecraft/legacy/LegacyModList.cpp
new file mode 100644
index 0000000000..32158bf59e
--- /dev/null
+++ b/meshmc/launcher/minecraft/legacy/LegacyModList.cpp
@@ -0,0 +1,150 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LegacyModList.h"
+#include <FileSystem.h>
+#include <QString>
+#include <QDebug>
+
+LegacyModList::LegacyModList(const QString& dir, const QString& list_file)
+ : m_dir(dir), m_list_file(list_file)
+{
+ FS::ensureFolderPathExists(m_dir.absolutePath());
+ m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files |
+ QDir::Dirs);
+ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
+}
+
+struct OrderItem {
+ QString id;
+ bool enabled = false;
+};
+typedef QList<OrderItem> OrderList;
+
+static void internalSort(QList<LegacyModList::Mod>& what)
+{
+ auto predicate = [](const LegacyModList::Mod& left,
+ const LegacyModList::Mod& right) {
+ return left.fileName().localeAwareCompare(right.fileName()) < 0;
+ };
+ std::sort(what.begin(), what.end(), predicate);
+}
+
+static OrderList readListFile(const QString& m_list_file)
+{
+ OrderList itemList;
+ if (m_list_file.isNull() || m_list_file.isEmpty())
+ return itemList;
+
+ QFile textFile(m_list_file);
+ if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
+ return OrderList();
+
+ QTextStream textStream;
+ textStream.setAutoDetectUnicode(true);
+ textStream.setDevice(&textFile);
+ while (true) {
+ QString line = textStream.readLine();
+ if (line.isNull() || line.isEmpty())
+ break;
+ else {
+ OrderItem it;
+ it.enabled = !line.endsWith(".disabled");
+ if (!it.enabled) {
+ line.chop(9);
+ }
+ it.id = line;
+ itemList.append(it);
+ }
+ }
+ textFile.close();
+ return itemList;
+}
+
+bool LegacyModList::update()
+{
+ if (!m_dir.exists() || !m_dir.isReadable())
+ return false;
+
+ QList<Mod> orderedMods;
+ QList<Mod> newMods;
+ m_dir.refresh();
+ auto folderContents = m_dir.entryInfoList();
+
+ // first, process the ordered items (if any)
+ OrderList listOrder = readListFile(m_list_file);
+ for (auto item : listOrder) {
+ QFileInfo infoEnabled(m_dir.filePath(item.id));
+ QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
+ int idxEnabled = folderContents.indexOf(infoEnabled);
+ int idxDisabled = folderContents.indexOf(infoDisabled);
+ bool isEnabled;
+ // if both enabled and disabled versions are present, it's a special
+ // case...
+ if (idxEnabled >= 0 && idxDisabled >= 0) {
+ // we only process the one we actually have in the order file.
+ // and exactly as we have it.
+ // THIS IS A CORNER CASE
+ isEnabled = item.enabled;
+ } else {
+ // only one is present.
+ // we pick the one that we found.
+ // we assume the mod was enabled/disabled by external means
+ isEnabled = idxEnabled >= 0;
+ }
+ int idx = isEnabled ? idxEnabled : idxDisabled;
+ QFileInfo& info = isEnabled ? infoEnabled : infoDisabled;
+ // if the file from the index file exists
+ if (idx != -1) {
+ // remove from the actual folder contents list
+ folderContents.takeAt(idx);
+ // append the new mod
+ orderedMods.append(info);
+ }
+ }
+ // if there are any untracked files... append them sorted at the end
+ if (folderContents.size()) {
+ for (auto entry : folderContents) {
+ newMods.append(entry);
+ }
+ internalSort(newMods);
+ orderedMods.append(newMods);
+ }
+ mods.swap(orderedMods);
+ return true;
+}
diff --git a/meshmc/launcher/minecraft/legacy/LegacyModList.h b/meshmc/launcher/minecraft/legacy/LegacyModList.h
new file mode 100644
index 0000000000..4a5627e7c8
--- /dev/null
+++ b/meshmc/launcher/minecraft/legacy/LegacyModList.h
@@ -0,0 +1,69 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QString>
+#include <QDir>
+
+class LegacyModList
+{
+ public:
+ using Mod = QFileInfo;
+
+ LegacyModList(const QString& dir, const QString& list_file = QString());
+
+ /// Reloads the mod list and returns true if the list changed.
+ bool update();
+
+ QDir dir()
+ {
+ return m_dir;
+ }
+
+ const QList<Mod>& allMods()
+ {
+ return mods;
+ }
+
+ protected:
+ QDir m_dir;
+ QString m_list_file;
+ QList<Mod> mods;
+};
diff --git a/meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.cpp b/meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.cpp
new file mode 100644
index 0000000000..89b3506153
--- /dev/null
+++ b/meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.cpp
@@ -0,0 +1,151 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "LegacyUpgradeTask.h"
+#include "settings/INISettingsObject.h"
+#include "FileSystem.h"
+#include "NullInstance.h"
+#include "pathmatcher/RegexpMatcher.h"
+#include <QtConcurrentRun>
+#include "LegacyInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "LegacyModList.h"
+#include "classparser.h"
+
+LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance)
+{
+ m_origInstance = origInstance;
+}
+
+void LegacyUpgradeTask::executeTask()
+{
+ setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
+
+ FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
+ folderCopy.followSymlinks(true);
+
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this,
+ &LegacyUpgradeTask::copyFinished);
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this,
+ &LegacyUpgradeTask::copyAborted);
+ m_copyFutureWatcher.setFuture(m_copyFuture);
+}
+
+static QString decideVersion(const QString& currentVersion,
+ const QString& intendedVersion)
+{
+ if (intendedVersion != currentVersion) {
+ if (!intendedVersion.isEmpty()) {
+ return intendedVersion;
+ } else if (!currentVersion.isEmpty()) {
+ return currentVersion;
+ }
+ } else {
+ if (!intendedVersion.isEmpty()) {
+ return intendedVersion;
+ }
+ }
+ return QString();
+}
+
+void LegacyUpgradeTask::copyFinished()
+{
+ auto successful = m_copyFuture.result();
+ if (!successful) {
+ emitFailed(tr("Instance folder copy failed."));
+ return;
+ }
+ auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance);
+
+ auto instanceSettings = std::make_shared<INISettingsObject>(
+ FS::PathCombine(m_stagingPath, "instance.cfg"));
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+ // NOTE: this scope ensures the instance is fully saved before we
+ // emitSucceeded
+ {
+ MinecraftInstance inst(m_globalSettings, instanceSettings,
+ m_stagingPath);
+ inst.setName(m_instName);
+
+ QString preferredVersionNumber = decideVersion(
+ legacyInst->currentVersionId(), legacyInst->intendedVersionId());
+ if (preferredVersionNumber.isNull()) {
+ // try to decide version based on the jar(s?)
+ preferredVersionNumber =
+ classparser::GetMinecraftJarVersion(legacyInst->baseJar());
+ if (preferredVersionNumber.isNull()) {
+ preferredVersionNumber = classparser::GetMinecraftJarVersion(
+ legacyInst->runnableJar());
+ if (preferredVersionNumber.isNull()) {
+ emitFailed(tr("Could not decide Minecraft version."));
+ return;
+ }
+ }
+ }
+ auto components = inst.getPackProfile();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", preferredVersionNumber,
+ true);
+
+ QString jarPath = legacyInst->mainJarToPreserve();
+ if (!jarPath.isNull()) {
+ qDebug() << "Preserving base jar! : " << jarPath;
+ // FIXME: handle case when the jar is unreadable?
+ // TODO: check the hash, if it's the same as the upstream jar, do
+ // not do this
+ components->installCustomJar(jarPath);
+ }
+
+ auto jarMods = legacyInst->jarModList()->allMods();
+ for (auto& jarMod : jarMods) {
+ QString modPath = jarMod.absoluteFilePath();
+ qDebug() << "jarMod: " << modPath;
+ components->installJarMods({modPath});
+ }
+
+ // remove all the extra garbage we no longer need
+ auto removeAll = [&](const QString& root, const QStringList& things) {
+ for (auto& thing : things) {
+ auto removePath = FS::PathCombine(root, thing);
+ QFileInfo stat(removePath);
+ if (stat.isDir()) {
+ FS::deletePath(removePath);
+ } else {
+ QFile::remove(removePath);
+ }
+ }
+ };
+ QStringList rootRemovables = {"modlist", "version", "instMods"};
+ QStringList mcRemovables = {"bin", "MeshMCLauncher.jar", "icon.png"};
+ removeAll(inst.instanceRoot(), rootRemovables);
+ removeAll(inst.gameRoot(), mcRemovables);
+ }
+ emitSucceeded();
+}
+
+void LegacyUpgradeTask::copyAborted()
+{
+ emitFailed(tr("Instance folder copy has been aborted."));
+ return;
+}
diff --git a/meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.h b/meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.h
new file mode 100644
index 0000000000..a407cb8df8
--- /dev/null
+++ b/meshmc/launcher/minecraft/legacy/LegacyUpgradeTask.h
@@ -0,0 +1,49 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 "InstanceTask.h"
+#include "net/NetJob.h"
+#include <QUrl>
+#include <QFuture>
+#include <QFutureWatcher>
+#include "settings/SettingsObject.h"
+#include "BaseVersion.h"
+#include "BaseInstance.h"
+
+class LegacyUpgradeTask : public InstanceTask
+{
+ Q_OBJECT
+ public:
+ explicit LegacyUpgradeTask(InstancePtr origInstance);
+
+ protected:
+ //! Entry point for tasks.
+ virtual void executeTask() override;
+ void copyFinished();
+ void copyAborted();
+
+ private: /* data */
+ InstancePtr m_origInstance;
+ QFuture<bool> m_copyFuture;
+ QFutureWatcher<bool> m_copyFutureWatcher;
+};