diff options
91 files changed, 1479 insertions, 397 deletions
| diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6cbd5c21..db7bd653 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -206,7 +206,7 @@ jobs:          shell: msys2 {0}          run: |            cd ${{ env.INSTALL_DIR }} -          makensis -NOCD "-DVERSION=${{ env.VERSION }}" "-DMUI_ICON=${{ github.workspace }}/program_info/polymc.ico" "-XOutFile ${{ github.workspace }}/PolyMC-Setup.exe" "${{ github.workspace }}/program_info/win_install.nsi" +          makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"        - name: Package (Linux)          if: runner.os == 'Linux' && matrix.appimage != true diff --git a/CMakeLists.txt b/CMakeLists.txt index 11d58213..b09e7fd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,8 +45,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")  # set CXXFLAGS for build targets -set(CMAKE_CXX_FLAGS_DEBUG "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") -set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")  option(ENABLE_LTO "Enable Link Time Optimization" off) @@ -73,8 +72,8 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL  ######## Set version numbers ########  set(Launcher_VERSION_MAJOR    1) -set(Launcher_VERSION_MINOR    3) -set(Launcher_VERSION_HOTFIX   1) +set(Launcher_VERSION_MINOR    4) +set(Launcher_VERSION_HOTFIX   0)  # Build number  set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") @@ -47,7 +47,7 @@ If there are any issues with the space or you are using a client that does not s  [](https://matrix.to/#/#polymc-support:matrix.org)  [](https://matrix.to/#/#polymc-voice:matrix.org) -we also have a subreddit you can post your issues and suggestions on: +We also have a subreddit you can post your issues and suggestions on:  [r/PolyMCLauncher](https://www.reddit.com/r/PolyMCLauncher/) @@ -96,3 +96,16 @@ If you do not agree with these terms and conditions, then remove the associated  All launcher code is available under the GPL-3.0-only license.  The logo and related assets are under the CC BY-SA 4.0 license. + +## Sponsors +Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc). + +[](https://opencollective.com/polymc#backers) + +Also, thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/). + +[](https://www.jetbrains.com/opensource/) + +Additionally, thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes! + +<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a> @@ -34,11 +34,11 @@      },      "nixpkgs": {        "locked": { -        "lastModified": 1653326962, -        "narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=", +        "lastModified": 1654665288, +        "narHash": "sha256-7blJpfoZEu7GKb84uh3io/5eSJNdaagXD9d15P9iQMs=",          "owner": "nixos",          "repo": "nixpkgs", -        "rev": "41cc1d5d9584103be4108c1815c350e07c807036", +        "rev": "43ecbe7840d155fa933ee8a500fb00dbbc651fc8",          "type": "github"        },        "original": { diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 99e3d4c5..ab3110a3 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -154,6 +154,7 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt      fflush(stderr);  } +#ifdef LAUNCHER_WITH_UPDATER  QString getIdealPlatform(QString currentPlatform) {      auto info = Sys::getKernelInfo();      switch(info.kernelType) { @@ -192,6 +193,7 @@ QString getIdealPlatform(QString currentPlatform) {      }      return currentPlatform;  } +#endif  } @@ -711,6 +713,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)          // Custom MSA credentials          m_settings->registerSetting("MSAClientIDOverride", "");          m_settings->registerSetting("CFKeyOverride", ""); +        m_settings->registerSetting("UserAgentOverride", "");          // Init page provider          { @@ -753,6 +756,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)          qDebug() << "<> Translations loaded.";      } +#ifdef LAUNCHER_WITH_UPDATER      // initialize the updater      if(BuildConfig.UPDATER_ENABLED)      { @@ -762,6 +766,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)          m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));          qDebug() << "<> Updater started.";      } +#endif      // Instance icons      { @@ -874,6 +879,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)          m_mcedit.reset(new MCEditTool(m_settings));      } +#ifdef Q_OS_MACOS +    connect(this, &Application::clickedOnDock, [this]() { +        this->showMainWindow(); +    }); +#endif +      connect(this, &Application::aboutToQuit, [this](){          if(m_instances)          { @@ -957,6 +968,21 @@ bool Application::createSetupWizard()      return false;  } +bool Application::event(QEvent* event) { +#ifdef Q_OS_MACOS +    if (event->type() == QEvent::ApplicationStateChange) { +        auto ev = static_cast<QApplicationStateChangeEvent*>(event); + +        if (m_prevAppState == Qt::ApplicationActive +                && ev->applicationState() == Qt::ApplicationActive) { +            emit clickedOnDock(); +        } +        m_prevAppState = ev->applicationState(); +    } +#endif +    return QApplication::event(event); +} +  void Application::setupWizardFinished(int status)  {      qDebug() << "Wizard result =" << status; @@ -1386,7 +1412,9 @@ MainWindow* Application::showMainWindow(bool minimized)          }          m_mainWindow->checkInstancePathForProblems(); +#ifdef LAUNCHER_WITH_UPDATER          connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged); +#endif          connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);          m_openWindows++;      } @@ -1556,3 +1584,24 @@ QString Application::getCurseKey()      return BuildConfig.CURSEFORGE_API_KEY;  } + +QString Application::getUserAgent() +{ +    QString uaOverride = m_settings->get("UserAgentOverride").toString(); +    if (!uaOverride.isEmpty()) { +        return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString()); +    } + +    return BuildConfig.USER_AGENT; +} + +QString Application::getUserAgentUncached() +{ +    QString uaOverride = m_settings->get("UserAgentOverride").toString(); +    if (!uaOverride.isEmpty()) { +        uaOverride += " (Uncached)"; +        return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString()); +    } + +    return BuildConfig.USER_AGENT_UNCACHED; +} diff --git a/launcher/Application.h b/launcher/Application.h index 3129b4fb..09007160 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -42,7 +42,10 @@  #include <QIcon>  #include <QDateTime>  #include <QUrl> + +#ifdef LAUNCHER_WITH_UPDATER  #include <updater/GoUpdate.h> +#endif  #include <BaseInstance.h> @@ -94,6 +97,8 @@ public:      Application(int &argc, char **argv);      virtual ~Application(); +    bool event(QEvent* event) override; +      std::shared_ptr<SettingsObject> settings() const {          return m_settings;      } @@ -156,6 +161,8 @@ public:      QString getMSAClientID();      QString getCurseKey(); +    QString getUserAgent(); +    QString getUserAgentUncached();      /// this is the root of the 'installation'. Used for automatic updates      const QString &root() { @@ -181,6 +188,10 @@ signals:      void globalSettingsAboutToOpen();      void globalSettingsClosed(); +#ifdef Q_OS_MACOS +    void clickedOnDock(); +#endif +  public slots:      bool launch(          InstancePtr instance, @@ -238,6 +249,10 @@ private:      QString m_rootPath;      Status m_status = Application::StartingUp; +#ifdef Q_OS_MACOS +    Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive; +#endif +  #if defined Q_OS_WIN32      // used on Windows to attach the standard IO streams      bool consoleAttached = false; diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 2fb31d94..0efbdddc 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>   *   *  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 @@ -59,7 +60,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s      m_settings->registerSetting("lastLaunchTime", 0);      m_settings->registerSetting("totalTimePlayed", 0);      m_settings->registerSetting("lastTimePlayed", 0); -    m_settings->registerSetting("InstanceType", "OneSix"); + +    // NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of +    // a locally stored instance +    if (!m_settings->getSetting("InstanceType")) +        m_settings->registerSetting("InstanceType", "");      // Custom Commands      auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); @@ -76,6 +81,14 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s      m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);      m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); + +    // Managed Packs +    m_settings->registerSetting("ManagedPack", false); +    m_settings->registerSetting("ManagedPackType", ""); +    m_settings->registerSetting("ManagedPackID", ""); +    m_settings->registerSetting("ManagedPackName", ""); +    m_settings->registerSetting("ManagedPackVersionID", ""); +    m_settings->registerSetting("ManagedPackVersionName", "");  }  QString BaseInstance::getPreLaunchCommand() @@ -93,6 +106,46 @@ QString BaseInstance::getPostExitCommand()      return settings()->get("PostExitCommand").toString();  } +bool BaseInstance::isManagedPack() +{ +    return settings()->get("ManagedPack").toBool(); +} + +QString BaseInstance::getManagedPackType() +{ +    return settings()->get("ManagedPackType").toString(); +} + +QString BaseInstance::getManagedPackID() +{ +    return settings()->get("ManagedPackID").toString(); +} + +QString BaseInstance::getManagedPackName() +{ +    return settings()->get("ManagedPackName").toString(); +} + +QString BaseInstance::getManagedPackVersionID() +{ +    return settings()->get("ManagedPackVersionID").toString(); +} + +QString BaseInstance::getManagedPackVersionName() +{ +    return settings()->get("ManagedPackVersionName").toString(); +} + +void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version) +{ +    settings()->set("ManagedPack", true); +    settings()->set("ManagedPackType", type); +    settings()->set("ManagedPackID", id); +    settings()->set("ManagedPackName", name); +    settings()->set("ManagedPackVersionID", versionId); +    settings()->set("ManagedPackVersionName", version); +} +  int BaseInstance::getConsoleMaxLines() const  {      auto lineSetting = settings()->getSetting("ConsoleMaxLines"); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index c973fcd4..66177614 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>   *   *  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 @@ -139,6 +140,14 @@ public:      QString getPostExitCommand();      QString getWrapperCommand(); +    bool isManagedPack(); +    QString getManagedPackType(); +    QString getManagedPackID(); +    QString getManagedPackName(); +    QString getManagedPackVersionID(); +    QString getManagedPackVersionName(); +    void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); +      /// guess log level from a line of game log      virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)      { diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 5397a988..b8db803b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -156,27 +156,29 @@ set(LAUNCH_SOURCES      launch/LogModel.h  ) -# Old update system -set(UPDATE_SOURCES -    updater/GoUpdate.h -    updater/GoUpdate.cpp -    updater/UpdateChecker.h -    updater/UpdateChecker.cpp -    updater/DownloadTask.h -    updater/DownloadTask.cpp -) - -add_unit_test(UpdateChecker -    SOURCES updater/UpdateChecker_test.cpp -    LIBS Launcher_logic -    DATA updater/testdata +if (Launcher_UPDATER_BASE) +    set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_WITH_UPDATER ${Launcher_APP_BINARY_DEFS}") +    # Old update system +    set(UPDATE_SOURCES +        updater/GoUpdate.h +        updater/GoUpdate.cpp +        updater/UpdateChecker.h +        updater/UpdateChecker.cpp +        updater/DownloadTask.h +        updater/DownloadTask.cpp      ) -add_unit_test(DownloadTask -    SOURCES updater/DownloadTask_test.cpp -    LIBS Launcher_logic -    DATA updater/testdata +    add_unit_test(UpdateChecker +        SOURCES updater/UpdateChecker_test.cpp +        LIBS Launcher_logic +        DATA updater/testdata +    ) +    add_unit_test(DownloadTask +        SOURCES updater/DownloadTask_test.cpp +        LIBS Launcher_logic +        DATA updater/testdata      ) +endif()  # Backend for the news bar... there's usually no news.  set(NEWS_SOURCES diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 6de20de6..3837d75f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na      return false;  #endif  } + +QStringList listFolderPaths(QDir root) +{ +    auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); }; + +    QStringList entries; + +    root.refresh(); +    for (auto entry : root.entryInfoList(QDir::Filter::Files)) { +        entries.append(createAbsPath(entry)); +    } + +    for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) { +        entries.append(listFolderPaths(createAbsPath(entry))); +    } + +    return entries; +} + +bool overrideFolder(QString overwritten_path, QString override_path) +{ +    if (!FS::ensureFolderPathExists(overwritten_path)) +        return false; + +    QStringList paths_to_override; +    QDir root_override (override_path); +    for (auto file : listFolderPaths(root_override)) { +        QString destination = file; +        destination.replace(override_path, overwritten_path); + +        qDebug() << QString("Applying override %1 in %2").arg(file, destination); + +        if (QFile::exists(destination)) +            QFile::remove(destination); +        if (!QFile::rename(file, destination)) { +            qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination); +            return false; +        } +    } + +    return true; +} +  } diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 8f6e8b48..bc942ab3 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -124,4 +124,8 @@ QString getDesktopDir();  // call it *name* and assign it the icon *icon*  // return true if operation succeeded  bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation); + +// Overrides one folder with the contents of another, preserving items exclusive to the first folder +// Equivalent to doing QDir::rename, but allowing for overrides +bool overrideFolder(QString overwritten_path, QString override_path);  } diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 09c2a333..d5684805 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -582,10 +582,10 @@ void InstanceImportTask::processMultiMC()      emitSucceeded();  } +// https://docs.modrinth.com/docs/modpacks/format_definition/  void InstanceImportTask::processModrinth()  {      std::vector<Modrinth::File> files; -    std::vector<Modrinth::File> non_whitelisted_files;      QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;      try {          QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); @@ -600,26 +600,30 @@ void InstanceImportTask::processModrinth()              auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");              bool had_optional = false; -            for (auto& modInfo : jsonFiles) { +            for (auto modInfo : jsonFiles) {                  Modrinth::File file;                  file.path = Json::requireString(modInfo, "path");                  auto env = Json::ensureObject(modInfo, "env"); -                QString support = Json::ensureString(env, "client", "unsupported"); -                if (support == "unsupported") { -                    continue; -                } else if (support == "optional") { -                    // TODO: Make a review dialog for choosing which ones the user wants! -                    if (!had_optional) { -                        had_optional = true; -                        auto info = CustomMessageBox::selectable( -                            m_parent, tr("Optional mod detected!"), -                            tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information); -                        info->exec(); -                    } +                // 'env' field is optional +                if (!env.isEmpty()) { +                    QString support = Json::ensureString(env, "client", "unsupported"); +                    if (support == "unsupported") { +                        continue; +                    } else if (support == "optional") { +                        // TODO: Make a review dialog for choosing which ones the user wants! +                        if (!had_optional) { +                            had_optional = true; +                            auto info = CustomMessageBox::selectable( +                                m_parent, tr("Optional mod detected!"), +                                tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), +                                QMessageBox::Information); +                            info->exec(); +                        } -                    if (file.path.endsWith(".jar")) -                        file.path += ".disabled"; +                        if (file.path.endsWith(".jar")) +                            file.path += ".disabled"; +                    }                  }                  QJsonObject hashes = Json::requireObject(modInfo, "hashes"); @@ -640,40 +644,31 @@ void InstanceImportTask::processModrinth()                  }                  file.hash = QByteArray::fromHex(hash.toLatin1());                  file.hashAlgorithm = hashAlgorithm; +                                  // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode                  // (as Modrinth seems to incorrectly handle spaces) - -                file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); - -                if (!file.download.isValid()) { -                    qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path); -                    throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); -                } -                else if (!Modrinth::validateDownloadUrl(file.download)) { -                    qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path); -                    non_whitelisted_files.push_back(file); +                 +                auto download_arr = Json::ensureArray(modInfo, "downloads"); +                for(auto download : download_arr) { +                    qWarning() << download.toString(); +                    bool is_last = download.toString() == download_arr.last().toString(); + +                    auto download_url = QUrl(download.toString()); + +                    if (!download_url.isValid()) { +                        qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL") +                                        .arg(download_url.toString(), file.path); +                        if(is_last && file.downloads.isEmpty()) +                            throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); +                    } +                    else { +                        file.downloads.push_back(download_url); +                    }                  }                  files.push_back(file);              } -            if (!non_whitelisted_files.empty()) { -                QString text; -                for (const auto& file : non_whitelisted_files) { -                    text += tr("Filepath: %1<br>URL: <a href='%2'>%2</a><br>").arg(file.path, file.download.toString()); -                } - -                auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"), -                                                           tr("The following mods have URLs that are not whitelisted by Modrinth.\n" -                                                              "Proceed with caution!"), -                                                           text); -                message_dialog->setModal(true); -                if (message_dialog->exec() == QDialog::Rejected) { -                    emitFailed("Aborted"); -                    return; -                } -            } -              auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");              for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {                  QString name = it.key(); @@ -701,16 +696,26 @@ void InstanceImportTask::processModrinth()          emitFailed(tr("Could not understand pack index:\n") + e.cause());          return;      } +     +    auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); -    QString overridePath = FS::PathCombine(m_stagingPath, "overrides"); -    if (QFile::exists(overridePath)) { -        QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); -        if (!QFile::rename(overridePath, mcPath)) { +    auto override_path = FS::PathCombine(m_stagingPath, "overrides"); +    if (QFile::exists(override_path)) { +        if (!QFile::rename(override_path, mcPath)) {              emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");              return;          }      } +    // Do client overrides +    auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); +    if (QFile::exists(client_override_path)) { +        if (!FS::overrideFolder(mcPath, client_override_path)) { +            emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides"); +            return; +        } +    } +      QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");      auto instanceSettings = std::make_shared<INISettingsObject>(configPath);      MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); @@ -735,13 +740,24 @@ void InstanceImportTask::processModrinth()      instance.saveNow();      m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); -    for (auto &file : files) +    for (auto file : files)      {          auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); -        qDebug() << "Will download" << file.download << "to" << path; -        auto dl = Net::Download::makeFile(file.download, path); +        qDebug() << "Will try to download" << file.downloads.front() << "to" << path; +        auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);          dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));          m_filesNetJob->addNetAction(dl); + +        if (file.downloads.size() > 0) { +            // FIXME: This really needs to be put into a ConcurrentTask of +            // MultipleOptionsTask's , once those exist :) +            connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{ +                auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); +                dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); +                m_filesNetJob->addNetAction(dl); +                dl->succeeded(); +            }); +        }      }      connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()              { diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 847d897e..3e3c81f7 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -547,8 +547,20 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)      auto instanceRoot = FS::PathCombine(m_instDir, id);      auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));      InstancePtr inst; -    // TODO: Handle incompatible instances -    inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); + +    instanceSettings->registerSetting("InstanceType", ""); + +    QString inst_type = instanceSettings->get("InstanceType").toString(); + +    // NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix instance +    if (inst_type == "OneSix" || inst_type.isEmpty()) +    { +        inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); +    } +    else +    { +        inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); +    }      qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();      return inst;  } diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp index 301b6637..41856fb5 100644 --- a/launcher/ModDownloadTask.cpp +++ b/launcher/ModDownloadTask.cpp @@ -2,6 +2,7 @@  /*  *  PolyMC - Minecraft Launcher  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>  *  *  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 @@ -21,12 +22,14 @@  #include "Application.h"  #include "minecraft/mod/ModFolderModel.h" -ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods) +ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed)      : m_mod(mod), m_mod_version(version), mods(mods)  { -    m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); +    if (is_indexed) { +        m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); -    addTask(m_update_task); +        addTask(m_update_task); +    }      m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network()));      m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h index f4438a8d..b3c25909 100644 --- a/launcher/ModDownloadTask.h +++ b/launcher/ModDownloadTask.h @@ -2,6 +2,7 @@  /*  *  PolyMC - Minecraft Launcher  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>  *  *  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 @@ -29,7 +30,7 @@ class ModFolderModel;  class ModDownloadTask : public SequentialTask {      Q_OBJECT  public: -    explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods); +    explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed);      const QString& getFilename() const { return m_mod_version.fileName; }  private: diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 0ddfae55..d426aa80 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -56,6 +56,15 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren      emit iconUpdated({});  } +void IconList::sortIconList() +{ +    qDebug() << "Sorting icon list..."; +    std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { +        return a.m_key.localeAwareCompare(b.m_key) < 0; +    }); +    reindex(); +} +  void IconList::directoryChanged(const QString &path)  {      QDir new_dir (path); @@ -141,6 +150,8 @@ void IconList::directoryChanged(const QString &path)              emit iconUpdated(key);          }      } + +    sortIconList();  }  void IconList::fileChanged(const QString &path) diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h index ebbb52e2..f9f49e7f 100644 --- a/launcher/icons/IconList.h +++ b/launcher/icons/IconList.h @@ -71,6 +71,7 @@ private:      // hide assign op      IconList &operator=(const IconList &) = delete;      void reindex(); +    void sortIconList();  public slots:      void directoryChanged(const QString &path); diff --git a/launcher/main.cpp b/launcher/main.cpp index 85c5fdee..3d25b4ff 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -27,10 +27,6 @@ int main(int argc, char *argv[])      QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);      QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) -    QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif -      // initialize Qt      Application app(argc, argv); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 2f339014..7e72601f 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -168,6 +168,8 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO      m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride);      m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); +    m_settings->set("InstanceType", "OneSix"); +      m_components.reset(new PackProfile(this));  } @@ -1013,7 +1015,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const  {      if (!m_loader_mod_list)      { -        m_loader_mod_list.reset(new ModFolderModel(modsRoot())); +        bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); +        m_loader_mod_list.reset(new ModFolderModel(modsRoot(), is_indexed));          m_loader_mod_list->disableInteraction(isRunning());          connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);      } @@ -1024,7 +1027,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const  {      if (!m_core_mod_list)      { -        m_core_mod_list.reset(new ModFolderModel(coreModsDir())); +        bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); +        m_core_mod_list.reset(new ModFolderModel(coreModsDir(), is_indexed));          m_core_mod_list->disableInteraction(isRunning());          connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);      } diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index d7010355..427bc32b 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -1,21 +1,42 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * 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 + *  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.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  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.   * - * 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. + *  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 "LauncherPartLaunch.h"  #include <QStandardPaths> +#include <QRegularExpression>  #include "launch/LaunchTask.h"  #include "minecraft/MinecraftInstance.h" diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 71a32d32..742709e3 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -2,6 +2,7 @@  /*  *  PolyMC - Minecraft Launcher  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>  *  *  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 @@ -58,8 +59,6 @@ Mod::Mod(const QFileInfo& file)  Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)      : m_file(mods_dir.absoluteFilePath(metadata.filename)) -    // It is weird, but name is not reliable for comparing with the JAR files name -    // FIXME: Maybe use hash when implemented?      , m_internal_id(metadata.filename)      , m_name(metadata.name)  { @@ -131,7 +130,7 @@ auto Mod::enable(bool value) -> bool              return false;      } else {          path += ".disabled"; -         +          if (!file.rename(path))              return false;      } @@ -145,23 +144,29 @@ auto Mod::enable(bool value) -> bool  void Mod::setStatus(ModStatus status)  { -    if(m_localDetails.get()) +    if (m_localDetails) {          m_localDetails->status = status; +    } else { +        m_temp_status = status; +    }  }  void Mod::setMetadata(Metadata::ModStruct* metadata)  { -    if(status() == ModStatus::NoMetadata) +    if (status() == ModStatus::NoMetadata)          setStatus(ModStatus::Installed); -    if(m_localDetails.get()) +    if (m_localDetails) {          m_localDetails->metadata.reset(metadata); +    } else { +        m_temp_metadata.reset(metadata); +    }  }  auto Mod::destroy(QDir& index_dir) -> bool  {      auto n = name();      // FIXME: This can fail to remove the metadata if the -    // "DontUseModMetadata" setting is on, since there could +    // "ModMetadataDisabled" setting is on, since there could      // be a name mismatch!      Metadata::remove(index_dir, n); @@ -205,20 +210,36 @@ auto Mod::authors() const -> QStringList  auto Mod::status() const -> ModStatus  { +    if (!m_localDetails) +        return m_temp_status;      return details().status;  } +auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct> +{ +    if (m_localDetails) +        return m_localDetails->metadata; +    return m_temp_metadata; +} + +auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct> +{ +    if (m_localDetails) +        return m_localDetails->metadata; +    return m_temp_metadata; +} +  void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)  {      m_resolving = false;      m_resolved = true;      m_localDetails = details; -    if (status() != ModStatus::NoMetadata  -            && m_temp_metadata.get() -            && m_temp_metadata->isValid() &&  -            m_localDetails.get()) { -         -        m_localDetails->metadata.swap(m_temp_metadata); +    if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) { +        m_localDetails->metadata = m_temp_metadata; +        if (status() == ModStatus::NoMetadata) +            setStatus(ModStatus::Installed);      } + +    setStatus(m_temp_status);  } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 96d471b4..5f9c4684 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -73,8 +73,8 @@ public:      auto authors()     const -> QStringList;      auto status()      const -> ModStatus; -    auto metadata() const -> const std::shared_ptr<Metadata::ModStruct> { return details().metadata; }; -    auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_localDetails->metadata; }; +    auto metadata() -> std::shared_ptr<Metadata::ModStruct>; +    auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;      void setStatus(ModStatus status);      void setMetadata(Metadata::ModStruct* metadata); @@ -109,6 +109,10 @@ protected:      /* If the mod has metadata, this will be filled in the constructor, and passed to        * the ModDetails when calling finishResolvingWithDetails */      std::shared_ptr<Metadata::ModStruct> m_temp_metadata; + +    /* Set the mod status while it doesn't have local details just yet */ +    ModStatus m_temp_status = ModStatus::NotInstalled; +      std::shared_ptr<ModDetails> m_localDetails;      bool m_enabled = true; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index b2d8f03e..ded2d3a2 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -1,17 +1,38 @@ -/* 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. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +*  PolyMC - Minecraft Launcher +*  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> +* +*  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/>. +* +* 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 "ModFolderModel.h" @@ -28,7 +49,7 @@  #include "minecraft/mod/tasks/LocalModParseTask.h"  #include "minecraft/mod/tasks/ModFolderLoadTask.h" -ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) +ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed)  {      FS::ensureFolderPathExists(m_dir.absolutePath());      m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); @@ -82,7 +103,7 @@ bool ModFolderModel::update()      }      auto index_dir = indexDir(); -    auto task = new ModFolderLoadTask(dir(), index_dir); +    auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);      m_update = task->result(); diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 10a72691..24b4d358 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -1,17 +1,38 @@ -/* 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. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +*  PolyMC - Minecraft Launcher +*  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> +* +*  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/>. +* +* 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 @@ -52,7 +73,7 @@ public:          Enable,          Toggle      }; -    ModFolderModel(const QString &dir); +    ModFolderModel(const QString &dir, bool is_indexed = false);      virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;      virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; @@ -146,6 +167,7 @@ protected:      bool scheduled_update = false;      bool interaction_disabled = false;      QDir m_dir; +    bool m_is_indexed;      QMap<QString, int> modsIndex;      QMap<int, LocalModParseTask::ResultPtr> activeTickets;      int nextResolutionTicket = 0; diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp index 76f16ed5..34a3b83a 100644 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ b/launcher/minecraft/mod/ModFolderModel_test.cpp @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +*  PolyMC - Minecraft Launcher +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> +* +*  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/>. +* +* 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 <QTest>  #include <QTemporaryDir> @@ -32,8 +66,11 @@ slots:          {              QString folder = source;              QTemporaryDir tempDir; -            ModFolderModel m(tempDir.path()); +            QEventLoop loop; +            ModFolderModel m(tempDir.path(), true); +            connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);              m.installMod(folder); +            loop.exec();              verify(tempDir.path());          } @@ -41,8 +78,11 @@ slots:          {              QString folder = source + '/';              QTemporaryDir tempDir; -            ModFolderModel m(tempDir.path()); +            QEventLoop loop; +            ModFolderModel m(tempDir.path(), true); +            connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);              m.installMod(folder); +            loop.exec();              verify(tempDir.path());          }      } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index f3d7f566..276804ed 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +*  PolyMC - Minecraft Launcher +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> +* +*  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/>. +* +* 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 "ResourcePackFolderModel.h"  ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index d5956da1..e3a22219 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +*  PolyMC - Minecraft Launcher +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> +* +*  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/>. +* +* 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 "TexturePackFolderModel.h"  TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp index a3fcd9d9..1bdecb8c 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp @@ -2,6 +2,7 @@  /*  *  PolyMC - Minecraft Launcher  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>  *  *  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 @@ -43,11 +44,6 @@ void LocalModUpdateTask::executeTask()  {      setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); -    if(APPLICATION->settings()->get("DontUseModMetadata").toBool()){ -        emitSucceeded(); -        return; -    } -      auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);      Metadata::update(m_index_dir, pw_mod); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 62d856f6..276414e4 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -2,6 +2,7 @@  /*  *  PolyMC - Minecraft Launcher  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>  *  *  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 @@ -38,13 +39,13 @@  #include "Application.h"  #include "minecraft/mod/MetadataHandler.h" -ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir)  -    : m_mods_dir(mods_dir), m_index_dir(index_dir), m_result(new Result()) +ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)  +    : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())  {}  void ModFolderLoadTask::run()  { -    if (!APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { +    if (m_is_indexed) {          // Read metadata first          getFromMetadata();      } @@ -53,12 +54,33 @@ void ModFolderLoadTask::run()      m_mods_dir.refresh();      for (auto entry : m_mods_dir.entryInfoList()) {          Mod mod(entry); -        if(m_result->mods.contains(mod.internal_id())){ -            m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); + +        if (mod.enabled()) { +            if (m_result->mods.contains(mod.internal_id())) { +                m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); +            } +            else { +                m_result->mods[mod.internal_id()] = mod; +                m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); +            }          } -        else { -            m_result->mods[mod.internal_id()] = mod; -            m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); +        else {  +            QString chopped_id = mod.internal_id().chopped(9); +            if (m_result->mods.contains(chopped_id)) { +                m_result->mods[mod.internal_id()] = mod; + +                auto metadata = m_result->mods[chopped_id].metadata(); +                if (metadata) { +                    mod.setMetadata(new Metadata::ModStruct(*metadata)); + +                    m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed); +                    m_result->mods.remove(chopped_id); +                } +            } +            else { +                m_result->mods[mod.internal_id()] = mod; +                m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata); +            }          }      } diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 89a0f84e..088f873e 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -2,6 +2,7 @@  /*  *  PolyMC - Minecraft Launcher  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> +*  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>  *  *  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 @@ -55,7 +56,7 @@ public:      }  public: -    ModFolderLoadTask(QDir& mods_dir, QDir& index_dir); +    ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);      void run();  signals:      void succeeded(); @@ -65,5 +66,6 @@ private:  private:      QDir& m_mods_dir, m_index_dir; +    bool m_is_indexed;      ResultPtr m_result;  }; diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index eb0de3f0..91b760df 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -7,6 +7,7 @@  namespace ModPlatform {  class ListModel; +struct IndexedPack;  }  class ModAPI { @@ -35,6 +36,7 @@ class ModAPI {      };      virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; +    virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;      struct VersionSearchArgs { diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 04dd2dac..c27643af 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -44,6 +44,12 @@ struct ModpackAuthor {      QString url;  }; +struct DonationData { +    QString id; +    QString platform; +    QString url; +}; +  struct IndexedVersion {      QVariant addonId;      QVariant fileId; @@ -57,6 +63,15 @@ struct IndexedVersion {      QString hash;  }; +struct ExtraPackData { +    QList<DonationData> donate; + +    QString issuesUrl; +    QString sourceUrl; +    QString wikiUrl; +    QString discordUrl; +}; +  struct IndexedPack {      QVariant addonId;      Provider provider; @@ -69,6 +84,10 @@ struct IndexedPack {      bool versionsLoaded = false;      QVector<IndexedVersion> versions; + +    // Don't load by default, since some modplatform don't have that info +    bool extraDataLoaded = true; +    ExtraPackData extraData;  };  }  // namespace ModPlatform diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 62c7bf6d..b4936bd8 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -60,10 +60,11 @@ namespace ATLauncher {  static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version); -PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version) +PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)  {      m_support = support; -    m_pack = pack; +    m_pack_name = packName; +    m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");      m_version_name = version;  } @@ -81,7 +82,7 @@ void PackInstallTask::executeTask()      qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();      auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network());      auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") -            .arg(m_pack).arg(m_version_name); +            .arg(m_pack_safe_name).arg(m_version_name);      netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));      jobPtr = netJob;      jobPtr->start(); @@ -98,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()      QJsonParseError parse_error {};      QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);      if(parse_error.error != QJsonParseError::NoError) { -        qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); +        qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString();          qWarning() << response;          return;      } @@ -319,7 +320,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared      auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");      auto f = std::make_shared<VersionFile>(); -    f->name = m_pack + " " + m_version_name + " (libraries)"; +    f->name = m_pack_name + " " + m_version_name + " (libraries)";      const static QMap<QString, QString> liteLoaderMap = {              { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, @@ -465,7 +466,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<      }      auto f = std::make_shared<VersionFile>(); -    f->name = m_pack + " " + m_version_name; +    f->name = m_pack_name + " " + m_version_name;      if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {          f->mainClass = mainClass;      } @@ -507,9 +508,9 @@ void PackInstallTask::installConfigs()      setStatus(tr("Downloading configs..."));      jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); -    auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name); +    auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name);      auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") -            .arg(m_pack).arg(m_version_name); +            .arg(m_pack_safe_name).arg(m_version_name);      auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);      entry->setStale(true); @@ -862,6 +863,7 @@ void PackInstallTask::install()      instance.setName(m_instName);      instance.setIconKey(m_instIcon); +    instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name);      instanceSettings->resumeSave();      jarmods.clear(); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index f0af4e3a..f55873e9 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -75,7 +75,7 @@ class PackInstallTask : public InstanceTask  Q_OBJECT  public: -    explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version); +    explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);      virtual ~PackInstallTask(){}      bool canAbort() const override { return true; } @@ -117,7 +117,8 @@ private:      NetJob::Ptr jobPtr;      QByteArray response; -    QString m_pack; +    QString m_pack_name; +    QString m_pack_safe_name;      QString m_version_name;      PackVersion m_version; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index e31cf0a1..424153d2 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -42,6 +42,11 @@ class FlameAPI : public NetworkModAPI {              .arg(gameVersionStr);      }; +    inline auto getModInfoURL(QString& id) const -> QString override +    { +        return QString("https://api.curseforge.com/v1/mods/%1").arg(id); +    }; +      inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override      {          QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index ed6d64c3..b99bfb26 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -28,6 +28,27 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)          packAuthor.url = Json::requireString(author, "url");          pack.authors.append(packAuthor);      } + +    loadExtraPackData(pack, obj); +} + +void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) +{ +    auto links_obj = Json::ensureObject(obj, "links"); + +    pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl"); +    if(pack.extraData.issuesUrl.endsWith('/')) +        pack.extraData.issuesUrl.chop(1); + +    pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl"); +    if(pack.extraData.sourceUrl.endsWith('/')) +        pack.extraData.sourceUrl.chop(1); + +    pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl"); +    if(pack.extraData.wikiUrl.endsWith('/')) +        pack.extraData.wikiUrl.chop(1); + +    pack.extraDataLoaded = true;  }  static QString enumToString(int hash_algorithm) diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index 2e0f2e86..9c6c1c6c 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -12,6 +12,7 @@  namespace FlameMod {  void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);  void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,                               QJsonArray& arr,                               const shared_qobject_ptr<QNetworkAccessManager>& network, diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index bece7843..ba1622d1 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -6,7 +6,6 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)  {      pack.addonId = Json::requireInteger(obj, "id");      pack.name = Json::requireString(obj, "name"); -    pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");      pack.description = Json::ensureString(obj, "summary", "");      auto logo = Json::requireObject(obj, "logo"); @@ -46,6 +45,32 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)      if (!found) {          throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name));      } + +    loadIndexedInfo(pack, obj); +} + +void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj) +{ +    auto links_obj = Json::ensureObject(obj, "links"); + +    pack.extra.websiteUrl = Json::ensureString(links_obj, "issuesUrl"); +    if(pack.extra.websiteUrl.endsWith('/')) +        pack.extra.websiteUrl.chop(1); + +    pack.extra.issuesUrl = Json::ensureString(links_obj, "issuesUrl"); +    if(pack.extra.issuesUrl.endsWith('/')) +        pack.extra.issuesUrl.chop(1); + +    pack.extra.sourceUrl = Json::ensureString(links_obj, "sourceUrl"); +    if(pack.extra.sourceUrl.endsWith('/')) +        pack.extra.sourceUrl.chop(1); + +    pack.extra.wikiUrl = Json::ensureString(links_obj, "wikiUrl"); +    if(pack.extra.wikiUrl.endsWith('/')) +        pack.extra.wikiUrl.chop(1); + +    pack.extraInfoLoaded = true; +  }  void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index 7ffa29c3..1ca0fc0e 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -20,6 +20,13 @@ struct IndexedVersion {      QString downloadUrl;  }; +struct ModpackExtra { +    QString websiteUrl; +    QString wikiUrl; +    QString issuesUrl; +    QString sourceUrl; +}; +  struct IndexedPack  {      int addonId; @@ -28,13 +35,16 @@ struct IndexedPack      QList<ModpackAuthor> authors;      QString logoName;      QString logoUrl; -    QString websiteUrl;      bool versionsLoaded = false;      QVector<IndexedVersion> versions; + +    bool extraInfoLoaded = false; +    ModpackExtra extra;  };  void loadIndexedPack(IndexedPack & m, QJsonObject & obj); +void loadIndexedInfo(IndexedPack&, QJsonObject&);  void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);  } diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp index 6829b837..d7abd10f 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ b/launcher/modplatform/helpers/NetworkModAPI.cpp @@ -31,6 +31,31 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const      netJob->start();  } +void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) +{ +    auto id_str = pack.addonId.toString(); +    auto netJob = new NetJob(QString("%1::ModInfo").arg(id_str), APPLICATION->network()); +    auto searchUrl = getModInfoURL(id_str); + +    auto response = new QByteArray(); +    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); + +    QObject::connect(netJob, &NetJob::succeeded, [response, &pack, caller] { +        QJsonParseError parse_error{}; +        auto doc = QJsonDocument::fromJson(*response, &parse_error); +        if (parse_error.error != QJsonParseError::NoError) { +            qWarning() << "Error while parsing JSON response for " << pack.name << " at " << parse_error.offset +                       << " reason: " << parse_error.errorString(); +            qWarning() << *response; +            return; +        } + +        caller->infoRequestFinished(doc, pack); +    }); + +    netJob->start(); +} +  void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const  {      auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network()); diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h index 000620b2..87d77ad1 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ b/launcher/modplatform/helpers/NetworkModAPI.h @@ -5,9 +5,11 @@  class NetworkModAPI : public ModAPI {     public:      void searchMods(CallerType* caller, SearchArgs&& args) const override; +    void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override;      void getVersions(CallerType* caller, VersionSearchArgs&& args) const override;     protected:      virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; +    virtual auto getModInfoURL(QString& id) const -> QString = 0;      virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0;  }; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 33df6fa4..c324ffda 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only  /* - * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> + *  PolyMC - Minecraft Launcher + *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>   * - * 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 + *  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.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  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.   * - * 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. + *  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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> + *      Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> + * + *      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 "FTBPackInstallTask.h" @@ -80,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()      QJsonParseError parse_error;      QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);      if(parse_error.error != QJsonParseError::NoError) { -        qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); +        qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();          qWarning() << response;          return;      } @@ -220,6 +239,7 @@ void PackInstallTask::install()      instance.setName(m_instName);      instance.setIconKey(m_instIcon); +    instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);      instanceSettings->resumeSave();      emitSucceeded(); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 60ecbd32..89e52d6c 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -76,6 +76,11 @@ class ModrinthAPI : public NetworkModAPI {              .arg(getGameVersionsArray(args.versions));      }; +    inline auto getModInfoURL(QString& id) const -> QString override +    { +        return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; +    }; +      inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override      {          return QString(BuildConfig.MODRINTH_PROD_URL + diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index fdce71c3..b6f5490a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -48,6 +48,43 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)      modAuthor.name = Json::requireString(obj, "author");      modAuthor.url = api.getAuthorURL(modAuthor.name);      pack.authors.append(modAuthor); + +    // Modrinth can have more data than what's provided by the basic search :) +    pack.extraDataLoaded = false; +} + +void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) +{ +    pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url"); +    if(pack.extraData.issuesUrl.endsWith('/')) +        pack.extraData.issuesUrl.chop(1); + +    pack.extraData.sourceUrl = Json::ensureString(obj, "source_url"); +    if(pack.extraData.sourceUrl.endsWith('/')) +        pack.extraData.sourceUrl.chop(1); + +    pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url"); +    if(pack.extraData.wikiUrl.endsWith('/')) +        pack.extraData.wikiUrl.chop(1); + +    pack.extraData.discordUrl = Json::ensureString(obj, "discord_url"); +    if(pack.extraData.discordUrl.endsWith('/')) +        pack.extraData.discordUrl.chop(1); + +    auto donate_arr = Json::ensureArray(obj, "donation_urls"); +    for(auto d : donate_arr){ +        auto d_obj = Json::requireObject(d); + +        ModPlatform::DonationData donate; + +        donate.id = Json::ensureString(d_obj, "id"); +        donate.platform = Json::ensureString(d_obj, "platform"); +        donate.url = Json::ensureString(d_obj, "url"); + +        pack.extraData.donate.append(donate); +    } + +    pack.extraDataLoaded = true;  }  void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index df70278f..b7936204 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -25,6 +25,7 @@  namespace Modrinth {  void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);  void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,                               QJsonArray& arr,                               const shared_qobject_ptr<QNetworkAccessManager>& network, diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 33116231..a4620df9 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -64,8 +64,35 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)  {      pack.extra.body = Json::ensureString(obj, "body");      pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug")); + +    pack.extra.issuesUrl = Json::ensureString(obj, "issues_url"); +    if(pack.extra.issuesUrl.endsWith('/')) +        pack.extra.issuesUrl.chop(1); +      pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); +    if(pack.extra.sourceUrl.endsWith('/')) +        pack.extra.sourceUrl.chop(1); +      pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); +    if(pack.extra.wikiUrl.endsWith('/')) +        pack.extra.wikiUrl.chop(1); + +    pack.extra.discordUrl = Json::ensureString(obj, "discord_url"); +    if(pack.extra.discordUrl.endsWith('/')) +        pack.extra.discordUrl.chop(1); + +    auto donate_arr = Json::ensureArray(obj, "donation_urls"); +    for(auto d : donate_arr){ +        auto d_obj = Json::requireObject(d); + +        DonationData donate; + +        donate.id = Json::ensureString(d_obj, "id"); +        donate.platform = Json::ensureString(d_obj, "platform"); +        donate.url = Json::ensureString(d_obj, "url"); + +        pack.extra.donate.append(donate); +    }      pack.extraInfoLoaded = true;  } @@ -95,19 +122,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)      pack.versionsLoaded = true;  } -auto validateDownloadUrl(QUrl url) -> bool -{ -    static QSet<QString> domainWhitelist{ -        "cdn.modrinth.com", -        "github.com", -        "raw.githubusercontent.com", -        "gitlab.com" -    }; - -    auto domain = url.host(); -    return domainWhitelist.contains(domain); -} -  auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion  {      ModpackVersion file; @@ -137,9 +151,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion          auto url = Json::requireString(parent, "url"); -        if(!validateDownloadUrl(url)) -            continue; -          file.download_url = url;          if(is_primary)              break; diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index e5fc9a70..035dc62e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -40,6 +40,7 @@  #include <QByteArray>  #include <QCryptographicHash> +#include <QQueue>  #include <QString>  #include <QUrl>  #include <QVector> @@ -48,22 +49,32 @@ class MinecraftInstance;  namespace Modrinth { -struct File -{ +struct File {      QString path;      QCryptographicHash::Algorithm hashAlgorithm;      QByteArray hash; -    // TODO: should this support multiple download URLs, like the JSON does? -    QUrl download; +    QQueue<QUrl> downloads; +}; + +struct DonationData { +    QString id; +    QString platform; +    QString url;  };  struct ModpackExtra {      QString body;      QString projectUrl; + +    QString issuesUrl;      QString sourceUrl;      QString wikiUrl; +    QString discordUrl; + +    QList<DonationData> donate; +  };  struct ModpackVersion { diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp index 023b990e..3d47f9f7 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/launcher/modplatform/packwiz/Packwiz_test.cpp @@ -1,20 +1,21 @@  // SPDX-License-Identifier: GPL-3.0-only  /* -*  PolyMC - Minecraft Launcher -*  Copyright (c) 2022 flowln <flowlnlnln@gmail.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/>. -*/ + *  PolyMC - Minecraft Launcher + *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  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 <QTemporaryDir>  #include <QTest> @@ -61,7 +62,7 @@ class PackwizTest : public QObject {          QVERIFY(index_dir.entryList().contains(name_mod));          // Try without the .pw.toml at the end -        name_mod.chop(5); +        name_mod.chop(8);          auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod); diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 782fb9b2..471b4a2f 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const                      components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));                  }              } -            else if (libraryName.startsWith("net.minecraftforge:minecraftforge:")) +            else              { -                components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2)); -            } -            else if (libraryName.startsWith("net.fabricmc:fabric-loader:")) -            { -                components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2)); +                static QStringList possibleLoaders{ +                        "net.minecraftforge:minecraftforge:", +                        "net.fabricmc:fabric-loader:", +                        "org.quiltmc:quilt-loader:" +                }; +                for (const auto& loader : possibleLoaders) +                { +                    if (libraryName.startsWith(loader)) +                    { +                        auto loaderComponent = loader.chopped(1).replace(":", "."); +                        components->setComponentVersion(loaderComponent, libraryName.section(':', 2)); +                        break; +                    } +                }              }          }      } diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 966d4126..d93eb088 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -116,7 +116,7 @@ void Download::executeTask()              return;      } -    request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); +    request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());      if (request.url().host().contains("api.curseforge.com")) {          request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());      }; diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 3855190a..7438e1a1 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -3,6 +3,7 @@   *  PolyMC - Minecraft Launcher   *  Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>   *  Copyright (C) 2022 Swirl <swurl@swurl.xyz> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -43,6 +44,8 @@  #include <QJsonArray>  #include <QJsonDocument>  #include <QFile> +#include <QHttpPart> +#include <QUrlQuery>  std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {      {{"0x0.st", "https://0x0.st", ""}, @@ -71,7 +74,7 @@ void PasteUpload::executeTask()      QNetworkRequest request{QUrl(m_uploadUrl)};      QNetworkReply *rep{}; -    request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); +    request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());      switch (m_pasteType) {      case NullPointer: { @@ -91,7 +94,7 @@ void PasteUpload::executeTask()          break;      }      case Hastebin: { -        request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); +        request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());          rep = APPLICATION->network()->post(request, m_text);          break;      } diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index bbd27390..c9942a8d 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -173,7 +173,7 @@ namespace Net {                  return;          } -        request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); +        request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());          if (request.url().host().contains("api.curseforge.com")) {              request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());          } diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 7afdc5cc..04e26ea2 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -55,7 +55,7 @@ void ImgurAlbumCreation::executeTask()  {      m_state = State::Running;      QNetworkRequest request(m_url); -    request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); +    request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());      request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");      request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());      request.setRawHeader("Accept", "application/json"); diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index fbcfb95f..9aeb6fb8 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -35,6 +35,7 @@  #include "ImgurUpload.h"  #include "BuildConfig.h" +#include "Application.h"  #include <QNetworkRequest>  #include <QHttpMultiPart> @@ -56,7 +57,7 @@ void ImgurUpload::executeTask()      finished = false;      m_state = Task::State::Running;      QNetworkRequest request(m_url); -    request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); +    request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgentUncached().toUtf8());      request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());      request.setRawHeader("Accept", "application/json"); diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index 320f1502..b1ea5ee9 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -38,6 +39,7 @@  #include <QClipboard>  #include <QApplication>  #include <QFileDialog> +#include <QStandardPaths>  #include "ui/dialogs/ProgressDialog.h"  #include "ui/dialogs/CustomMessageBox.h" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 7e152b96..210442df 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1,21 +1,42 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  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/>. + * + * This file incorporates work covered by the following copyright and + * permission notice:   * - * Authors: Andrew Okin - *          Peterix - *          Orochimarufan <orochimarufan.x3@gmail.com> + *      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 + *      Authors: Andrew Okin + *               Peterix + *               Orochimarufan <orochimarufan.x3@gmail.com>   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *      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   * - * 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. + *          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 "Application.h"  #include "BuildConfig.h" @@ -1010,6 +1031,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow      } +#ifdef LAUNCHER_WITH_UPDATER      if(BuildConfig.UPDATER_ENABLED)      {          bool updatesAllowed = APPLICATION->updatesAreAllowed(); @@ -1028,6 +1050,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow              updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);          }      } +#endif      setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); @@ -1337,6 +1360,7 @@ void MainWindow::repopulateAccountsMenu()      ui->profileMenu->addAction(ui->actionManageAccounts);  } +#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::updatesAllowedChanged(bool allowed)  {      if(!BuildConfig.UPDATER_ENABLED) @@ -1345,6 +1369,7 @@ void MainWindow::updatesAllowedChanged(bool allowed)      }      ui->actionCheckUpdate->setEnabled(allowed);  } +#endif  /*   * Assumes the sender is a QAction @@ -1450,6 +1475,7 @@ void MainWindow::updateNewsLabel()      }  } +#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::updateAvailable(GoUpdate::Status status)  {      if(!APPLICATION->updatesAreAllowed()) @@ -1475,6 +1501,7 @@ void MainWindow::updateNotAvailable()      UpdateDialog dlg(false, this);      dlg.exec();  } +#endif  QList<int> stringToIntList(const QString &string)  { @@ -1496,6 +1523,7 @@ QString intListToString(const QList<int> &list)      return slist.join(',');  } +#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::downloadUpdates(GoUpdate::Status status)  {      if(!APPLICATION->updatesAreAllowed()) @@ -1529,6 +1557,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)          CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show();      }  } +#endif  void MainWindow::onCatToggled(bool state)  { @@ -1841,6 +1870,7 @@ void MainWindow::on_actionConfig_Folder_triggered()      }  } +#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::checkForUpdates()  {      if(BuildConfig.UPDATER_ENABLED) @@ -1853,6 +1883,7 @@ void MainWindow::checkForUpdates()          qWarning() << "Updater not set up. Cannot check for updates.";      }  } +#endif  void MainWindow::on_actionSettings_triggered()  { @@ -2101,6 +2132,9 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &          selectionBad();          return;      } +    if (m_selectedInstance) { +        disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); +    }      QString id = current.data(InstanceList::InstanceIDRole).toString();      m_selectedInstance = APPLICATION->instances()->getInstanceById(id);      if (m_selectedInstance) @@ -2127,6 +2161,8 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &          updateToolsMenu();          APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); + +        connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);      }      else      { @@ -2216,3 +2252,9 @@ void MainWindow::updateStatusCenter()          m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));      }  } + +void MainWindow::refreshCurrentInstance(bool running) +{ +    auto current = view->selectionModel()->currentIndex(); +    instanceChanged(current, current); +} diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 2032acba..6c64756f 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -1,16 +1,40 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * 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 + *  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.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  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.   * - * 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. + *  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 + * + *      Authors: Andrew Okin + *               Peterix + *               Orochimarufan <orochimarufan.x3@gmail.com> + * + *      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 @@ -54,7 +78,9 @@ public:      void checkInstancePathForProblems(); +#ifdef LAUNCHER_WITH_UPDATER      void updatesAllowedChanged(bool allowed); +#endif      void droppedURLs(QList<QUrl> urls);  signals: @@ -100,7 +126,9 @@ private slots:      void on_actionViewCentralModsFolder_triggered(); +#ifdef LAUNCHER_WITH_UPDATER      void checkForUpdates(); +#endif      void on_actionSettings_triggered(); @@ -167,9 +195,11 @@ private slots:      void startTask(Task *task); +#ifdef LAUNCHER_WITH_UPDATER      void updateAvailable(GoUpdate::Status status);      void updateNotAvailable(); +#endif      void defaultAccountChanged(); @@ -179,10 +209,12 @@ private slots:      void updateNewsLabel(); +#ifdef LAUNCHER_WITH_UPDATER      /*!       * Runs the DownloadTask and installs updates.       */      void downloadUpdates(GoUpdate::Status status); +#endif      void konamiTriggered(); @@ -192,6 +224,8 @@ private slots:      void keyReleaseEvent(QKeyEvent *event) override;  #endif +    void refreshCurrentInstance(bool running); +  private:      void retranslateUi(); diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 5d812d07..b889e6f7 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -75,9 +75,9 @@ APIPage::APIPage(QWidget *parent) :      // This function needs to be called even when the ComboBox's index is still in its default state.      updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());      ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); -    ui->tabWidget->tabBar()->hide();      ui->metaURL->setPlaceholderText(BuildConfig.META_URL); +    ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT);      loadSettings(); @@ -139,6 +139,8 @@ void APIPage::loadSettings()      ui->metaURL->setText(metaURL);      QString curseKey = s->get("CFKeyOverride").toString();      ui->curseKey->setText(curseKey); +    QString customUserAgent = s->get("UserAgentOverride").toString(); +    ui->userAgentLineEdit->setText(customUserAgent);  }  void APIPage::applySettings() @@ -167,6 +169,7 @@ void APIPage::applySettings()      s->set("MetaURLOverride", metaURL);      QString curseKey = ui->curseKey->text();      s->set("CFKeyOverride", curseKey); +    s->set("UserAgentOverride", ui->userAgentLineEdit->text());  }  bool APIPage::apply() diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 5c927391..5327771c 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -30,7 +30,7 @@       </property>       <widget class="QWidget" name="tab">        <attribute name="title"> -       <string notr="true">Tab 1</string> +       <string notr="true">Services</string>        </attribute>        <layout class="QVBoxLayout" name="verticalLayout_2">         <item> @@ -86,15 +86,15 @@          </widget>         </item>         <item> -        <widget class="QGroupBox" name="groupBox_msa"> +        <widget class="QGroupBox" name="groupBox_meta">           <property name="title"> -          <string>&Microsoft Authentication</string> +          <string>Meta&data Server</string>           </property> -         <layout class="QVBoxLayout" name="verticalLayout_4"> +         <layout class="QVBoxLayout" name="verticalLayout_5">            <item> -           <widget class="QLabel" name="label_3"> +           <widget class="QLabel" name="label_5">              <property name="text"> -             <string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string> +             <string>You can set this to a third-party metadata server to use patched libraries or other hacks.</string>              </property>              <property name="textFormat">               <enum>Qt::RichText</enum> @@ -105,16 +105,16 @@             </widget>            </item>            <item> -           <widget class="QLineEdit" name="msaClientID"> +           <widget class="QLineEdit" name="metaURL">              <property name="placeholderText"> -             <string>(Default)</string> +             <string/>              </property>             </widget>            </item>            <item> -           <widget class="QLabel" name="label_4"> +           <widget class="QLabel" name="label_6">              <property name="text"> -             <string>Enter a custom client ID for Microsoft Authentication here. </string> +             <string>Enter a custom URL for meta here.</string>              </property>              <property name="textFormat">               <enum>Qt::RichText</enum> @@ -131,15 +131,35 @@          </widget>         </item>         <item> -        <widget class="QGroupBox" name="groupBox_meta"> +        <spacer name="verticalSpacer_2"> +         <property name="orientation"> +          <enum>Qt::Vertical</enum> +         </property> +         <property name="sizeHint" stdset="0"> +          <size> +           <width>20</width> +           <height>40</height> +          </size> +         </property> +        </spacer> +       </item> +      </layout> +     </widget> +     <widget class="QWidget" name="tab_2"> +      <attribute name="title"> +       <string>API Keys</string> +      </attribute> +      <layout class="QVBoxLayout" name="verticalLayout_8"> +       <item> +        <widget class="QGroupBox" name="groupBox_msa">           <property name="title"> -          <string>Meta&data Server</string> +          <string>&Microsoft Authentication</string>           </property> -         <layout class="QVBoxLayout" name="verticalLayout_5"> +         <layout class="QVBoxLayout" name="verticalLayout_4">            <item> -           <widget class="QLabel" name="label_5"> +           <widget class="QLabel" name="label_3">              <property name="text"> -             <string>You can set this to a third-party metadata server to use patched libraries or other hacks.</string> +             <string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>              </property>              <property name="textFormat">               <enum>Qt::RichText</enum> @@ -150,16 +170,16 @@             </widget>            </item>            <item> -           <widget class="QLineEdit" name="metaURL"> +           <widget class="QLineEdit" name="msaClientID">              <property name="placeholderText"> -             <string/> +             <string>(Default)</string>              </property>             </widget>            </item>            <item> -           <widget class="QLabel" name="label_6"> +           <widget class="QLabel" name="label_4">              <property name="text"> -             <string>Enter a custom URL for meta here.</string> +             <string>Enter a custom client ID for Microsoft Authentication here. </string>              </property>              <property name="textFormat">               <enum>Qt::RichText</enum> @@ -183,25 +203,15 @@           <property name="title">            <string>&CurseForge Core API</string>           </property> -         <layout class="QVBoxLayout" name="verticalLayout_6"> -          <item> +         <layout class="QGridLayout" name="gridLayout"> +          <item row="0" column="0">             <widget class="QLabel" name="label_8">              <property name="text">               <string>Note: you probably don't need to set this if CurseForge already works.</string>              </property>             </widget>            </item> -          <item> -           <widget class="QLineEdit" name="curseKey"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="placeholderText"> -             <string>(Default)</string> -            </property> -           </widget> -          </item> -          <item> +          <item row="2" column="0">             <widget class="QLabel" name="label_7">              <property name="text">               <string>Enter a custom API Key for CurseForge here. </string> @@ -217,6 +227,16 @@              </property>             </widget>            </item> +          <item row="1" column="0"> +           <widget class="QLineEdit" name="curseKey"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="placeholderText"> +             <string>(Default)</string> +            </property> +           </widget> +          </item>           </layout>          </widget>         </item> @@ -235,6 +255,51 @@         </item>        </layout>       </widget> +     <widget class="QWidget" name="tab_3"> +      <attribute name="title"> +       <string>Miscellaneous</string> +      </attribute> +      <layout class="QVBoxLayout" name="verticalLayout_6"> +       <item> +        <widget class="QGroupBox" name="groupBox_ua"> +         <property name="minimumSize"> +          <size> +           <width>0</width> +           <height>0</height> +          </size> +         </property> +         <property name="title"> +          <string>User Agent</string> +         </property> +         <layout class="QVBoxLayout" name="verticalLayout_7"> +          <item> +           <widget class="QLineEdit" name="userAgentLineEdit"/> +          </item> +          <item> +           <widget class="QLabel" name="userAgentLabel"> +            <property name="text"> +             <string>Enter a custom User Agent here. The special string $LAUNCHER_VER will be replaced with the version of the launcher.</string> +            </property> +           </widget> +          </item> +         </layout> +        </widget> +       </item> +       <item> +        <spacer name="verticalSpacer_3"> +         <property name="orientation"> +          <enum>Qt::Vertical</enum> +         </property> +         <property name="sizeHint" stdset="0"> +          <size> +           <width>20</width> +           <height>40</height> +          </size> +         </property> +        </spacer> +       </item> +      </layout> +     </widget>      </widget>     </item>    </layout> diff --git a/launcher/ui/pages/global/LanguagePage.cpp b/launcher/ui/pages/global/LanguagePage.cpp index 485d7fd4..fcd174bd 100644 --- a/launcher/ui/pages/global/LanguagePage.cpp +++ b/launcher/ui/pages/global/LanguagePage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/global/LanguagePage.h b/launcher/ui/pages/global/LanguagePage.h index 9b321170..2fd4ab0d 100644 --- a/launcher/ui/pages/global/LanguagePage.h +++ b/launcher/ui/pages/global/LanguagePage.h @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index faf9272d..edbf609f 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -78,6 +78,7 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch      m_languageModel = APPLICATION->translations();      loadSettings(); +#ifdef LAUNCHER_WITH_UPDATER      if(BuildConfig.UPDATER_ENABLED)      {          QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList); @@ -90,11 +91,9 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch          {              APPLICATION->updateChecker()->updateChanList(false);          } +        ui->updateSettingsBox->setHidden(false);      } -    else -    { -        ui->updateSettingsBox->setHidden(true); -    } +#endif      connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));      connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));  } @@ -189,6 +188,7 @@ void LauncherPage::on_metadataDisableBtn_clicked()      ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());  } +#ifdef LAUNCHER_WITH_UPDATER  void LauncherPage::refreshUpdateChannelList()  {      // Stop listening for selection changes. It's going to change a lot while we update it and @@ -260,6 +260,7 @@ void LauncherPage::refreshUpdateChannelDesc()          m_currentUpdateChannel = selected.id;      }  } +#endif  void LauncherPage::applySettings()  { @@ -450,7 +451,7 @@ void LauncherPage::loadSettings()      }      // Mods -    ui->metadataDisableBtn->setChecked(s->get("DontUseModMetadata").toBool()); +    ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool());      ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());  } diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index f38c922e..ccfd7e9e 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -90,6 +90,7 @@ slots:      void on_iconsDirBrowseBtn_clicked();      void on_metadataDisableBtn_clicked(); +#ifdef LAUNCHER_WITH_UPDATER      /*!       * Updates the list of update channels in the combo box.       */ @@ -100,13 +101,13 @@ slots:       */      void refreshUpdateChannelDesc(); +    void updateChannelSelectionChanged(int index); +#endif      /*!       * Updates the font preview       */      void refreshFontPreview(); -    void updateChannelSelectionChanged(int index); -  private:      Ui::LauncherPage *ui; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 417bbe05..ceb68c5b 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -50,6 +50,9 @@           <property name="title">            <string>Update Settings</string>           </property> +         <property name="visible"> +          <bool>false</bool> +         </property>           <layout class="QVBoxLayout" name="verticalLayout_7">            <item>             <widget class="QCheckBox" name="autoUpdateCheckBox"> diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 30a8735f..a6c98c08 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -39,6 +40,7 @@  #include "Application.h"  #include <QIcon> +#include <QIdentityProxyModel>  #include <QScrollBar>  #include <QShortcut> diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index b0cd405f..d929a0ea 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 72e2d404..2dd44e85 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -41,6 +41,7 @@  #include "ui/pages/BasePage.h"  #include <Application.h> +#include <QSortFilterProxyModel>  class ModFolderModel;  namespace Ui diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 2cf17b32..51163e28 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index c22706af..c34c9755 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -35,6 +35,7 @@  #pragma once +#include <QItemSelection>  #include <QMainWindow>  #include "ui/pages/BasePage.h" diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 2af6164c..c3bde612 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -47,6 +48,7 @@  #include <QFileSystemWatcher>  #include <QMenu> +#include <QTimer>  static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 76725539..ff30dd82 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -46,6 +47,7 @@  #include <QInputDialog>  #include <QProcess>  #include <Qt> +#include <QSortFilterProxyModel>  #include "tools/MCEditTool.h"  #include "FileSystem.h" diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index b3ed1b73..0b8577b1 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -110,11 +110,13 @@ void ImportPage::updateState()          {              // FIXME: actually do some validation of what's inside here... this is fake AF              QFileInfo fi(input); -            // mrpack is a modrinth pack              // Allow non-latin people to use ZIP files! -            auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); -            if(fi.exists() && (zip || fi.suffix() == "mrpack")) +            bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); +            // mrpack is a modrinth pack +            bool isMRPack = fi.suffix() == "mrpack"; + +            if(fi.exists() && (isZip || isMRPack))              {                  QFileInfo fi(url.fileName());                  dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this)); @@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)  void ImportPage::on_modpackBtn_clicked()  {      auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); -    filter += ";;" + tr("Modrinth pack (*.mrpack)"); +    //: Option for filtering for *.mrpack files when importing +    filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";      const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);      if (url.isValid())      { diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 1eb5837b..98eec31c 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -96,6 +96,11 @@ void ListModel::performPaginatedSearch()          this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });  } +void ListModel::requestModInfo(ModPlatform::IndexedPack& current) +{ +    m_parent->apiProvider()->getModInfo(this, current); +} +  void ListModel::refresh()  {      if (jobPtr) { @@ -242,6 +247,21 @@ void ListModel::searchRequestFailed(QString reason)      }  } +void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack) +{ +    qDebug() << "Loading mod info"; + +    try { +        auto obj = Json::requireObject(doc); +        loadExtraPackInfo(pack, obj); +    } catch (const JSONValidationError& e) { +        qDebug() << doc; +        qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause(); +    } + +    m_parent->updateUi(); +} +  void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId)  {      auto& current = m_parent->getCurrent(); diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index d460cff2..dd22407c 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -36,9 +36,11 @@ class ListModel : public QAbstractListModel {      void fetchMore(const QModelIndex& parent) override;      void refresh();      void searchWithTerm(const QString& term, const int sort, const bool filter_changed); +    void requestModInfo(ModPlatform::IndexedPack& current);      void requestModVersions(const ModPlatform::IndexedPack& current);      virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; +    virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};      virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;      void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); @@ -49,6 +51,8 @@ class ListModel : public QAbstractListModel {      void searchRequestFinished(QJsonDocument& doc);      void searchRequestFailed(QString reason); +    void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack); +      void versionRequestSucceeded(QJsonDocument doc, QString addonId);     protected slots: diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 5020d44c..200fe59e 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -1,4 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  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/>. + * + * 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 "ModPage.h" +#include "Application.h"  #include "ui_ModPage.h"  #include <QKeyEvent> @@ -94,28 +130,6 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)      if (!first.isValid()) { return; }      current = listModel->data(first, Qt::UserRole).value<ModPlatform::IndexedPack>(); -    QString text = ""; -    QString name = current.name; - -    if (current.websiteUrl.isEmpty()) -        text = name; -    else -        text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; - -    if (!current.authors.empty()) { -        auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { -            if (author.url.isEmpty()) { return author.name; } -            return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); -        }; -        QStringList authorStrs; -        for (auto& author : current.authors) { -            authorStrs.push_back(authorToStr(author)); -        } -        text += "<br>" + tr(" by ") + authorStrs.join(", "); -    } -    text += "<br><br>"; - -    ui->packDescription->setHtml(text + current.description);      if (!current.versionsLoaded) {          qDebug() << QString("Loading %1 mod versions").arg(debugName()); @@ -132,6 +146,13 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)          updateSelectionButton();      } + +    if(!current.extraDataLoaded){ +        qDebug() << QString("Loading %1 mod info").arg(debugName()); +        listModel->requestModInfo(current); +    } + +    updateUi();  }  void ModPage::onVersionSelectionChanged(QString data) @@ -150,7 +171,8 @@ void ModPage::onModSelected()      if (dialog->isModSelected(current.name, version.fileName)) {          dialog->removeSelectedMod(current.name);      } else { -        dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods)); +        bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); +        dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed));      }      updateSelectionButton(); @@ -207,3 +229,61 @@ void ModPage::updateSelectionButton()          ui->modSelectionButton->setText(tr("Deselect mod for download"));      }  } + +void ModPage::updateUi() +{ +    QString text = ""; +    QString name = current.name; + +    if (current.websiteUrl.isEmpty()) +        text = name; +    else +        text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; + +    if (!current.authors.empty()) { +        auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { +            if (author.url.isEmpty()) { return author.name; } +            return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); +        }; +        QStringList authorStrs; +        for (auto& author : current.authors) { +            authorStrs.push_back(authorToStr(author)); +        } +        text += "<br>" + tr(" by ") + authorStrs.join(", "); +    } + +     +    if(current.extraDataLoaded) { +        if (!current.extraData.donate.isEmpty()) { +            text += "<br><br>" + tr("Donate information: "); +            auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { +                return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform); +            }; +            QStringList donates; +            for (auto& donate : current.extraData.donate) { +                donates.append(donateToStr(donate)); +            } +            text += donates.join(", "); +        } + +        if (!current.extraData.issuesUrl.isEmpty() +         || !current.extraData.sourceUrl.isEmpty() +         || !current.extraData.wikiUrl.isEmpty() +         || !current.extraData.discordUrl.isEmpty()) { +            text += "<br><br>" + tr("External links:") + "<br>"; +        } + +        if (!current.extraData.issuesUrl.isEmpty()) +            text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extraData.issuesUrl) + "<br>"; +        if (!current.extraData.wikiUrl.isEmpty()) +            text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extraData.wikiUrl) + "<br>"; +        if (!current.extraData.sourceUrl.isEmpty()) +            text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extraData.sourceUrl) + "<br>"; +        if (!current.extraData.discordUrl.isEmpty()) +            text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extraData.discordUrl) + "<br>"; +    } + +    text += "<hr>"; + +    ui->packDescription->setHtml(text + current.description); +} diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 32affd20..cf00e16e 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -36,10 +36,12 @@ class ModPage : public QWidget, public BasePage {      void retranslate() override; +    void updateUi(); +      auto shouldDisplay() const -> bool override = 0;      virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; -    auto apiProvider() const -> const ModAPI* { return api.get(); }; +    auto apiProvider() -> ModAPI* { return api.get(); };      auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }      auto getDialog() const -> const ModDownloadDialog* { return dialog; } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 7bc6fc6b..8de5211c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -117,7 +117,7 @@ void AtlPage::suggestCurrent()          return;      } -    dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion)); +    dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion));      auto editedLogoName = selected.safeName;      auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());      listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 7e90af47..b65ace6b 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -119,29 +119,6 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)      }      current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>(); -    QString text = ""; -    QString name = current.name; - -    if (current.websiteUrl.isEmpty()) -        text = name; -    else -        text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; -    if (!current.authors.empty()) { -        auto authorToStr = [](Flame::ModpackAuthor& author) { -            if (author.url.isEmpty()) { -                return author.name; -            } -            return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); -        }; -        QStringList authorStrs; -        for (auto& author : current.authors) { -            authorStrs.push_back(authorToStr(author)); -        } -        text += "<br>" + tr(" by ") + authorStrs.join(", "); -    } -    text += "<br><br>"; - -    ui->packDescription->setHtml(text + current.description);      if (current.versionsLoaded == false) {          qDebug() << "Loading flame modpack versions"; @@ -188,6 +165,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)          suggestCurrent();      } + +    updateUi();  }  void FlamePage::suggestCurrent() @@ -217,3 +196,46 @@ void FlamePage::onVersionSelectionChanged(QString data)      selectedVersion = ui->versionSelectionBox->currentData().toString();      suggestCurrent();  } + +void FlamePage::updateUi() +{ +    QString text = ""; +    QString name = current.name; + +    if (current.extra.websiteUrl.isEmpty()) +        text = name; +    else +        text = "<a href=\"" + current.extra.websiteUrl + "\">" + name + "</a>"; +    if (!current.authors.empty()) { +        auto authorToStr = [](Flame::ModpackAuthor& author) { +            if (author.url.isEmpty()) { +                return author.name; +            } +            return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); +        }; +        QStringList authorStrs; +        for (auto& author : current.authors) { +            authorStrs.push_back(authorToStr(author)); +        } +        text += "<br>" + tr(" by ") + authorStrs.join(", "); +    } + +    if(current.extraInfoLoaded) { +        if (!current.extra.issuesUrl.isEmpty() +         || !current.extra.sourceUrl.isEmpty() +         || !current.extra.wikiUrl.isEmpty()) { +            text += "<br><br>" + tr("External links:") + "<br>"; +        } + +        if (!current.extra.issuesUrl.isEmpty()) +            text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extra.issuesUrl) + "<br>"; +        if (!current.extra.wikiUrl.isEmpty()) +            text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extra.wikiUrl) + "<br>"; +        if (!current.extra.sourceUrl.isEmpty()) +            text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>"; +    } + +    text += "<hr>"; + +    ui->packDescription->setHtml(text + current.description); +} diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index baac57c9..8130e416 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -79,6 +79,8 @@ public:      virtual bool shouldDisplay() const override;      void retranslate() override; +    void updateUi(); +      void openedImpl() override;      bool eventFilter(QObject * watched, QEvent * event) override; diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp index 37244fed..ad15b6e6 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp @@ -122,10 +122,10 @@ void ListModel::requestFinished()      jobPtr.reset();      remainingPacks.clear(); -    QJsonParseError parse_error; +    QJsonParseError parse_error {};      QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);      if(parse_error.error != QJsonParseError::NoError) { -        qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); +        qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();          qWarning() << response;          return;      } @@ -169,7 +169,7 @@ void ListModel::packRequestFinished()      QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);      if(parse_error.error != QJsonParseError::NoError) { -        qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); +        qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();          qWarning() << response;          return;      } @@ -184,7 +184,7 @@ void ListModel::packRequestFinished()      catch (const JSONValidationError &e)      {          qDebug() << QString::fromUtf8(response); -        qWarning() << "Error while reading pack manifest from FTB: " << e.cause(); +        qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause();          return;      } @@ -192,7 +192,7 @@ void ListModel::packRequestFinished()      // ignore those "dud" packs.      if (pack.versions.empty())      { -        qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions"; +        qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions";      }      else      { @@ -270,7 +270,7 @@ void ListModel::requestLogo(QString logo, QString url)      bool stale = entry->isStale(); -    NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo), APPLICATION->network()); +    NetJob *job = new NetJob(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network());      job->addNetAction(Net::Download::makeCached(QUrl(url), entry));      auto fullPath = entry->getFullPath(); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 63b944c4..06e9db4f 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  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/>. + * + * 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 "ListModel.h"  #include "Application.h" @@ -11,6 +46,8 @@  #include <BuildConfig.h> +#include <net/NetJob.h> +  namespace LegacyFTB {  FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp index 1d9f4d60..af92e63e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp @@ -30,6 +30,11 @@ void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)      Modrinth::loadIndexedPack(m, obj);  } +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ +    Modrinth::loadExtraPackData(m, obj); +} +  void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)  {      Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h index ae7b0bdd..386897fd 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h @@ -31,6 +31,7 @@ class ListModel : public ModPlatform::ListModel {     private:      void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; +    void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;      void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;      auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 7cacf37a..07d1687c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 14aa6747..1b4d8da4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -39,6 +39,7 @@  #include "modplatform/modrinth/ModrinthPackManifest.h"  #include "ui/pages/modplatform/modrinth/ModrinthPage.h" +#include "net/NetJob.h"  class ModPage;  class Version; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 9bd24b57..d8500674 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -224,7 +224,37 @@ void ModrinthPage::updateUI()      // TODO: Implement multiple authors with links      text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); -    text += "<br>"; +    if(current.extraInfoLoaded) { +        if (!current.extra.donate.isEmpty()) { +            text += "<br><br>" + tr("Donate information: "); +            auto donateToStr = [](Modrinth::DonationData& donate) -> QString { +                return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform); +            }; +            QStringList donates; +            for (auto& donate : current.extra.donate) { +                donates.append(donateToStr(donate)); +            } +            text += donates.join(", "); +        } + +        if (!current.extra.issuesUrl.isEmpty() +         || !current.extra.sourceUrl.isEmpty() +         || !current.extra.wikiUrl.isEmpty() +         || !current.extra.discordUrl.isEmpty()) { +            text += "<br><br>" + tr("External links:") + "<br>"; +        } + +        if (!current.extra.issuesUrl.isEmpty()) +            text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extra.issuesUrl) + "<br>"; +        if (!current.extra.wikiUrl.isEmpty()) +            text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extra.wikiUrl) + "<br>"; +        if (!current.extra.sourceUrl.isEmpty()) +            text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>"; +        if (!current.extra.discordUrl.isEmpty()) +            text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extra.discordUrl) + "<br>"; +    } + +    text += "<hr>";      HoeDown h;      text += h.process(current.extra.body.toUtf8()); diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index ca6a9b7e..15bf645f 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -60,7 +60,11 @@        </widget>       </item>       <item row="0" column="1"> -      <widget class="QTextBrowser" name="packDescription"/> +      <widget class="QTextBrowser" name="packDescription"> +       <property name="openExternalLinks"> +        <bool>true</bool> +       </property> +      </widget>       </item>      </layout>     </item> diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java index 265fa66a..6bf671be 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/net/minecraft/Launcher.java @@ -24,24 +24,65 @@ import java.net.URL;  import java.util.Map;  import java.util.TreeMap; +/* + * WARNING: This class is reflectively accessed by legacy Forge versions. + * Changing field and method declarations without further testing is not recommended. + */  public final class Launcher extends Applet implements AppletStub {      private final Map<String, String> params = new TreeMap<>(); -    private final Applet wrappedApplet; +    private Applet wrappedApplet; + +    private URL documentBase;      private boolean active = false;      public Launcher(Applet applet) { +        this(applet, null); +    } + +    public Launcher(Applet applet, URL documentBase) {          this.setLayout(new BorderLayout());          this.add(applet, "Center");          this.wrappedApplet = applet; + +        try { +            if (documentBase != null) { +                this.documentBase = documentBase; +            } else if (applet.getClass().getPackage().getName().startsWith("com.mojang")) { +                // Special case only for Classic versions + +                this.documentBase = new URL("http", "www.minecraft.net", 80, "/game/"); +            } else { +                this.documentBase = new URL("http://www.minecraft.net/game/"); +            } +        } catch (MalformedURLException e) { +            throw new RuntimeException(e); +        } +    } + +    public void replace(Applet applet) { +        this.wrappedApplet = applet; + +        applet.setStub(this); +        applet.setSize(getWidth(), getHeight()); + +        this.setLayout(new BorderLayout()); +        this.add(applet, "Center"); + +        applet.init(); + +        active = true; + +        applet.start(); + +        validate();      } -    public void setParameter(String name, String value) -    { +    public void setParameter(String name, String value) {          params.put(name, value);      } @@ -54,7 +95,7 @@ public final class Launcher extends Applet implements AppletStub {          try {              return super.getParameter(name); -        } catch (Exception ignore) {} +        } catch (Exception ignored) {}          return null;      } @@ -108,25 +149,13 @@ public final class Launcher extends Applet implements AppletStub {          try {              return new URL("http://www.minecraft.net/game/");          } catch (MalformedURLException e) { -            e.printStackTrace(); +            throw new RuntimeException(e);          } - -        return null;      }      @Override      public URL getDocumentBase() { -        try { -            // Special case only for Classic versions -            if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) -                return new URL("http", "www.minecraft.net", 80, "/game/"); - -            return new URL("http://www.minecraft.net/game/"); -        } catch (MalformedURLException e) { -            e.printStackTrace(); -        } - -        return null; +        return documentBase;      }      @Override diff --git a/nix/default.nix b/nix/default.nix index 969b455e..d6aa370c 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -14,6 +14,7 @@  , quazip  , libGL  , msaClientID ? "" +, extraJDKs ? [ ]    # flake  , self @@ -36,6 +37,8 @@ let    # This variable will be passed to Minecraft by PolyMC    gameLibraryPath = libpath + ":/run/opengl-driver/lib"; + +  javaPaths = lib.makeSearchPath "bin/java" ([ jdk jdk8 ] ++ extraJDKs);  in  stdenv.mkDerivation rec { @@ -67,7 +70,7 @@ stdenv.mkDerivation rec {      # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128      wrapQtApp $out/bin/polymc \        --set GAME_LIBRARY_PATH ${gameLibraryPath} \ -      --prefix POLYMC_JAVA_PATHS : ${jdk}/lib/openjdk/bin/java:${jdk8}/lib/openjdk/bin/java \ +      --prefix POLYMC_JAVA_PATHS : ${javaPaths} \        --prefix PATH : ${lib.makeBinPath [ xorg.xrandr ]}    ''; diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 2cbef1b6..1000be23 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -14,6 +14,8 @@ set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE  set(Launcher_ManPage "program_info/polymc.6.txt" PARENT_SCOPE)  set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE)  set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) +set(Launcher_Branding_ICO "program_info/polymc.ico") +set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE)  set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE)  set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) @@ -24,3 +26,4 @@ configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml)  configure_file(polymc.rc.in polymc.rc @ONLY)  configure_file(polymc.manifest.in polymc.manifest @ONLY)  configure_file(polymc.ico polymc.ico COPYONLY) +configure_file(win_install.nsi.in win_install.nsi @ONLY) diff --git a/program_info/win_install.nsi b/program_info/win_install.nsi.in index cb4c8d1d..e5687de7 100644 --- a/program_info/win_install.nsi +++ b/program_info/win_install.nsi.in @@ -4,10 +4,13 @@  Unicode true -Name "PolyMC" -InstallDir "$LOCALAPPDATA\Programs\PolyMC" -InstallDirRegKey HKCU "Software\PolyMC" "InstallDir" +Name "@Launcher_CommonName@" +InstallDir "$LOCALAPPDATA\Programs\@Launcher_CommonName@" +InstallDirRegKey HKCU "Software\@Launcher_CommonName@" "InstallDir"  RequestExecutionLevel user +OutFile "../@Launcher_CommonName@-Setup.exe" + +!define MUI_ICON "../@Launcher_Branding_ICO@"  ;-------------------------------- @@ -18,7 +21,7 @@ RequestExecutionLevel user  !insertmacro MUI_PAGE_COMPONENTS  !insertmacro MUI_PAGE_DIRECTORY  !insertmacro MUI_PAGE_INSTFILES -!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe" +!define MUI_FINISHPAGE_RUN "$InstDir\@Launcher_APP_BINARY_NAME@.exe"  !insertmacro MUI_PAGE_FINISH  !insertmacro MUI_UNPAGE_CONFIRM @@ -98,16 +101,23 @@ RequestExecutionLevel user  ;-------------------------------- +; Version info +VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@" +VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@" +VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@" + +;-------------------------------- +  ; The stuff to install -Section "PolyMC" +Section "@Launcher_CommonName@"    SectionIn RO -  nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' +  nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F'    SetOutPath $INSTDIR -  File "polymc.exe" +  File "@Launcher_APP_BINARY_NAME@.exe"    File "qt.conf"    File *.dll    File /r "iconengines" @@ -117,20 +127,20 @@ Section "PolyMC"    File /r "styles"    ; Write the installation path into the registry -  WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR" +  WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"    ; Write the uninstall keys for Windows    ${GetParameters} $R0    ${GetOptions} $R0 "/NoUninstaller" $R1    ${If} ${Errors} -    !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" -    WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC" -    WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe" +    !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" +    WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "@Launcher_CommonName@" +    WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe"      WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'      WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'      WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" -    WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors" -    WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}" +    WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors" +    WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"      ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2      IntFmt $0 "0x%08X" $0      WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0" @@ -143,13 +153,13 @@ SectionEnd  Section "Start Menu Shortcut" SM_SHORTCUTS -  CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 +  CreateShortcut "$SMPROGRAMS\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0  SectionEnd  Section "Desktop Shortcut" DESKTOP_SHORTCUTS -  CreateShortcut "$DESKTOP\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0 +  CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0  SectionEnd @@ -159,12 +169,12 @@ SectionEnd  Section "Uninstall" -  nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F' +  nsExec::Exec /TIMEOUT=2000 'TaskKill /IM @Launcher_APP_BINARY_NAME@.exe /F' -  DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC" -  DeleteRegKey HKCU SOFTWARE\PolyMC +  DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\@Launcher_CommonName@" +  DeleteRegKey HKCU SOFTWARE\@Launcher_CommonName@ -  Delete $INSTDIR\polymc.exe +  Delete $INSTDIR\@Launcher_APP_BINARY_NAME@.exe    Delete $INSTDIR\uninstall.exe    Delete $INSTDIR\portable.txt @@ -220,8 +230,8 @@ Section "Uninstall"    RMDir /r $INSTDIR\platforms    RMDir /r $INSTDIR\styles -  Delete "$SMPROGRAMS\PolyMC.lnk" -  Delete "$DESKTOP\PolyMC.lnk" +  Delete "$SMPROGRAMS\@Launcher_CommonName@.lnk" +  Delete "$DESKTOP\@Launcher_CommonName@.lnk"    RMDir "$INSTDIR" | 
