diff options
Diffstat (limited to 'launcher/minecraft/legacy')
| -rw-r--r-- | launcher/minecraft/legacy/LegacyInstance.cpp | 256 | ||||
| -rw-r--r-- | launcher/minecraft/legacy/LegacyInstance.h | 140 | ||||
| -rw-r--r-- | launcher/minecraft/legacy/LegacyModList.cpp | 136 | ||||
| -rw-r--r-- | launcher/minecraft/legacy/LegacyModList.h | 47 | ||||
| -rw-r--r-- | launcher/minecraft/legacy/LegacyUpgradeTask.cpp | 138 | ||||
| -rw-r--r-- | launcher/minecraft/legacy/LegacyUpgradeTask.h | 29 | 
6 files changed, 746 insertions, 0 deletions
| diff --git a/launcher/minecraft/legacy/LegacyInstance.cpp b/launcher/minecraft/legacy/LegacyInstance.cpp new file mode 100644 index 00000000..9f9bda5a --- /dev/null +++ b/launcher/minecraft/legacy/LegacyInstance.cpp @@ -0,0 +1,256 @@ +/* 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/LauncherPartLaunch.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(); +} + + +shared_qobject_ptr<Task> 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::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::loaderModsDir() const +{ +    return FS::PathCombine(gameRoot(), "mods"); +} + +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/launcher/minecraft/legacy/LegacyInstance.h b/launcher/minecraft/legacy/LegacyInstance.h new file mode 100644 index 00000000..ac2a8543 --- /dev/null +++ b/launcher/minecraft/legacy/LegacyInstance.h @@ -0,0 +1,140 @@ +/* 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 loaderModsDir() const; +    QString coreModsDir() const; +    QString resourceDir() const; +    virtual QString instanceConfigFolder() const override; +    QString gameRoot() 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 shared_qobject_ptr<Task> 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/launcher/minecraft/legacy/LegacyModList.cpp b/launcher/minecraft/legacy/LegacyModList.cpp new file mode 100644 index 00000000..7301eb8c --- /dev/null +++ b/launcher/minecraft/legacy/LegacyModList.cpp @@ -0,0 +1,136 @@ +/* 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 | QDir::NoSymLinks); +    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/launcher/minecraft/legacy/LegacyModList.h b/launcher/minecraft/legacy/LegacyModList.h new file mode 100644 index 00000000..fade736e --- /dev/null +++ b/launcher/minecraft/legacy/LegacyModList.h @@ -0,0 +1,47 @@ +/* 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/launcher/minecraft/legacy/LegacyUpgradeTask.cpp b/launcher/minecraft/legacy/LegacyUpgradeTask.cpp new file mode 100644 index 00000000..a4ea60cd --- /dev/null +++ b/launcher/minecraft/legacy/LegacyUpgradeTask.cpp @@ -0,0 +1,138 @@ +#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", "MultiMCLauncher.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/launcher/minecraft/legacy/LegacyUpgradeTask.h b/launcher/minecraft/legacy/LegacyUpgradeTask.h new file mode 100644 index 00000000..542e17b8 --- /dev/null +++ b/launcher/minecraft/legacy/LegacyUpgradeTask.h @@ -0,0 +1,29 @@ +#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; +}; | 
