This commit is contained in:
fn2006 2023-02-05 22:11:58 +00:00
commit 1821081521
294 changed files with 8312 additions and 12400 deletions

View File

@ -61,7 +61,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
qt_version: '6.4.0' qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -73,7 +73,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.4.0' qt_version: '6.4.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -105,6 +105,7 @@ jobs:
INSTALL_APPIMAGE_DIR: "install-appdir" INSTALL_APPIMAGE_DIR: "install-appdir"
BUILD_DIR: "build" BUILD_DIR: "build"
CCACHE_VAR: "" CCACHE_VAR: ""
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
steps: steps:
## ##
@ -135,6 +136,7 @@ jobs:
quazip-qt6:p quazip-qt6:p
ccache:p ccache:p
qt6-5compat:p qt6-5compat:p
cmark:p
- name: Force newer ccache - name: Force newer ccache
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
@ -143,10 +145,19 @@ jobs:
- name: Setup ccache - name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.5 uses: hendrikmuhs/ccache-action@v1.2.8
with: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.2.4
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
restore-keys: |
${{ matrix.os }}-mingw-w64-ccache
- name: Setup ccache (Windows MinGW-w64) - name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
shell: msys2 {0} shell: msys2 {0}
@ -163,15 +174,6 @@ jobs:
run: | run: |
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.11
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64
restore-keys: |
${{ matrix.os }}-mingw-w64
- name: Set short version - name: Set short version
shell: bash shell: bash
run: | run: |
@ -508,4 +510,81 @@ jobs:
name: PollyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage name: PollyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PollyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PollyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
- name: ccache stats (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
run: |
ccache -s
snap:
runs-on: ubuntu-20.04
steps:
- name: Checkout
if: inputs.build_type == 'Debug'
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Set short version
shell: bash
if: inputs.build_type == 'Debug'
run: |
ver_short=`git rev-parse --short HEAD`
echo "VERSION=$ver_short" >> $GITHUB_ENV
- name: Package Snap (Linux)
id: snapcraft
if: inputs.build_type == 'Debug'
uses: snapcore/action-build@v1
- name: Upload Snap (Linux)
if: inputs.build_type == 'Debug'
uses: actions/upload-artifact@v3
with:
name: pollymc_${{ env.VERSION }}_amd64.snap
path: ${{ steps.snapcraft.outputs.snap }}
flatpak:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v3
if: inputs.build_type == 'Debug'
with:
submodules: 'true'
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v5
with:
bundle: "PollyMC.flatpak"
manifest-path: flatpak/org.fn2006.PollyMC.yml
nix:
runs-on: ubuntu-latest
strategy:
matrix:
package:
- pollymc
- pollymc-qt5
steps:
- name: Clone repository
if: inputs.build_type == 'Debug'
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Install nix
if: inputs.build_type == 'Debug'
uses: cachix/install-nix-action@v18
with:
install_url: https://nixos.org/nix/install
extra_nix_config: |
auto-optimise-store = true
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v12
if: inputs.build_type == 'Debug'
with:
name: pollymc
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
if: inputs.build_type == 'Debug'
run: nix build .#${{ matrix.package }} --print-build-logs

3
.gitmodules vendored
View File

@ -16,3 +16,6 @@
[submodule "libraries/extra-cmake-modules"] [submodule "libraries/extra-cmake-modules"]
path = libraries/extra-cmake-modules path = libraries/extra-cmake-modules
url = https://github.com/KDE/extra-cmake-modules url = https://github.com/KDE/extra-cmake-modules
[submodule "libraries/cmark"]
path = libraries/cmark
url = https://github.com/commonmark/cmark.git

View File

@ -208,9 +208,15 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
################################ 3rd Party Libs ################################ ################################ 3rd Party Libs ################################
if(NOT Launcher_FORCE_BUNDLED_LIBS) # Successive configurations of cmake without cleaning the build dir will cause zlib fallback to fail due to cached values
# Record when fallback triggered and skip this find_package
if(NOT Launcher_FORCE_BUNDLED_LIBS AND NOT FORCE_BUNDLED_ZLIB)
find_package(ZLIB QUIET) find_package(ZLIB QUIET)
endif() endif()
if(NOT ZLIB_FOUND)
set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "")
mark_as_advanced(FORCE_BUNDLED_ZLIB)
endif()
# Find the required Qt parts # Find the required Qt parts
include(QtVersionlessBackport) include(QtVersionlessBackport)
@ -266,8 +272,13 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
# Find ghc_filesystem # Find ghc_filesystem
find_package(ghc_filesystem QUIET) find_package(ghc_filesystem QUIET)
# Find cmark
find_package(cmark QUIET)
endif() endif()
include(ECMQtDeclareLoggingCategory)
####################################### Program Info ####################################### ####################################### Program Info #######################################
set(Launcher_APP_BINARY_NAME "pollymc" CACHE STRING "Name of the Launcher binary") set(Launcher_APP_BINARY_NAME "pollymc" CACHE STRING "Name of the Launcher binary")
@ -372,16 +383,26 @@ option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/libnbtplusplus)
add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/systeminfo) # system information library
add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker add_subdirectory(libraries/javacheck) # java compatibility checker
if(NOT ZLIB_FOUND) if(FORCE_BUNDLED_ZLIB)
message(STATUS "Using bundled zlib") message(STATUS "Using bundled zlib")
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib
set(SKIP_INSTALL_ALL ON) set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "") # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
check_include_file(unistd.h NEED_GENERATED_ZCONF)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
# zlib's cmake script renames a file, dirtying the submodule, see https://github.com/madler/zlib/issues/162
message(STATUS "Undoing Rename")
message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h")
file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h")
endif()
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" CACHE STRING "" FORCE)
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}") set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
add_library(ZLIB::ZLIB ALIAS zlibstatic) add_library(ZLIB::ZLIB ALIAS zlibstatic)
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name") set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
@ -406,6 +427,16 @@ if(NOT tomlplusplus_FOUND)
else() else()
message(STATUS "Using system tomlplusplus") message(STATUS "Using system tomlplusplus")
endif() endif()
if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark")
set(CMARK_STATIC ON CACHE BOOL "Build static libcmark library" FORCE)
set(CMARK_SHARED OFF CACHE BOOL "Build shared libcmark library" FORCE)
set(CMARK_TESTS OFF CACHE BOOL "Build cmark tests and enable testing" FORCE)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark_static)
else()
message(STATUS "Using system cmark")
endif()
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode) add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API

View File

@ -1,7 +1,7 @@
## Prism Launcher ## Prism Launcher
Prism Launcher - Minecraft Launcher Prism Launcher - Minecraft Launcher
Copyright (C) 2022 Prism Launcher Contributors Copyright (C) 2022-2023 Prism Launcher Contributors
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -156,23 +156,34 @@
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA. Boston, MA 02110-1301, USA.
## Hoedown ## cmark
Copyright (c) 2008, Natacha Porté Copyright (c) 2014, John MacFarlane
Copyright (c) 2011, Vicent Martí
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
Permission to use, copy, modify, and distribute this software for any All rights reserved.
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES Redistribution and use in source and binary forms, with or without
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF modification, are permitted provided that the following conditions are met:
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * Redistributions of source code must retain the above copyright
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN notice, this list of conditions and the following disclaimer.
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## Batch icon set ## Batch icon set

View File

@ -77,7 +77,9 @@ Config::Config()
// Assume that builds outside of Git repos are "stable" // Assume that builds outside of Git repos are "stable"
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND")
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")) || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")
|| GIT_REFSPEC == QStringLiteral("")
|| GIT_TAG == QStringLiteral("GIT-NOTFOUND"))
{ {
GIT_REFSPEC = "refs/heads/stable"; GIT_REFSPEC = "refs/heads/stable";
GIT_TAG = versionString(); GIT_TAG = versionString();

View File

@ -39,6 +39,7 @@ modules:
sources: sources:
- type: dir - type: dir
path: ../ path: ../
builddir: true
- name: openjdk - name: openjdk
buildsystem: simple buildsystem: simple
build-commands: build-commands:

View File

@ -62,15 +62,11 @@
#include "ui/pages/global/APIPage.h" #include "ui/pages/global/APIPage.h"
#include "ui/pages/global/CustomCommandsPage.h" #include "ui/pages/global/CustomCommandsPage.h"
#ifdef Q_OS_WIN
#include "ui/WinDarkmode.h"
#include <versionhelpers.h>
#endif
#include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/PasteWizardPage.h" #include "ui/setupwizard/PasteWizardPage.h"
#include "ui/setupwizard/ThemeWizardPage.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
@ -81,6 +77,7 @@
#include "ApplicationMessage.h" #include "ApplicationMessage.h"
#include <iostream> #include <iostream>
#include <mutex>
#include <QAccessible> #include <QAccessible>
#include <QCommandLineParser> #include <QCommandLineParser>
@ -107,7 +104,7 @@
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "updater/UpdateChecker.h" #include "updater/ExternalUpdater.h"
#include "tools/JProfiler.h" #include "tools/JProfiler.h"
#include "tools/JVisualVM.h" #include "tools/JVisualVM.h"
@ -131,6 +128,10 @@
#include "MangoHud.h" #include "MangoHud.h"
#endif #endif
#ifdef Q_OS_MAC
#include "updater/MacSparkleUpdater.h"
#endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
@ -148,19 +149,15 @@ static const QLatin1String liveCheckFile("live.check");
PixmapCache* PixmapCache::s_instance = nullptr; PixmapCache* PixmapCache::s_instance = nullptr;
namespace { namespace {
/** This is used so that we can output to the log file in addition to the CLI. */
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{ {
const char *levels = "DWCFIS"; static std::mutex loggerMutex;
const QString format("%1 %2 %3\n"); const std::lock_guard<std::mutex> lock(loggerMutex); // synchronized, QFile logFile is not thread-safe
qint64 msecstotal = APPLICATION->timeSinceStart(); QString out = qFormatLogMessage(type, context, msg);
qint64 seconds = msecstotal / 1000; out += QChar::LineFeed;
qint64 msecs = msecstotal % 1000;
QString foo;
char buf[1025] = {0};
::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
QString out = format.arg(buf).arg(levels[type]).arg(msg);
APPLICATION->logFile->write(out.toUtf8()); APPLICATION->logFile->write(out.toUtf8());
APPLICATION->logFile->flush(); APPLICATION->logFile->flush();
@ -168,45 +165,6 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr); fflush(stderr);
} }
QString getIdealPlatform(QString currentPlatform) {
auto info = Sys::getKernelInfo();
switch(info.kernelType) {
case Sys::KernelType::Darwin: {
if(info.kernelMajor >= 17) {
// macOS 10.13 or newer
return "osx64-5.15.2";
}
else {
// macOS 10.12 or older
return "osx64";
}
}
case Sys::KernelType::Windows: {
// FIXME: 5.15.2 is not stable on Windows, due to a large number of completely unpredictable and hard to reproduce issues
break;
/*
if(info.kernelMajor == 6 && info.kernelMinor >= 1) {
// Windows 7
return "win32-5.15.2";
}
else if (info.kernelMajor > 6) {
// Above Windows 7
return "win32-5.15.2";
}
else {
// Below Windows 7
return "win32";
}
*/
}
case Sys::KernelType::Undetermined:
case Sys::KernelType::Linux: {
break;
}
}
return currentPlatform;
}
} }
Application::Application(int &argc, char **argv) : QApplication(argc, argv) Application::Application(int &argc, char **argv) : QApplication(argc, argv)
@ -268,9 +226,19 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server"); m_serverToJoin = parser.value("server");
m_profileToUse = parser.value("profile"); m_profileToUse = parser.value("profile");
m_liveCheck = parser.isSet("alive"); m_liveCheck = parser.isSet("alive");
m_zipToImport = parser.value("import");
m_instanceIdToShowWindowOf = parser.value("show"); m_instanceIdToShowWindowOf = parser.value("show");
for (auto zip_path : parser.values("import")){
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
}
// treat unspecified positional arguments as import urls
for (auto zip_path : parser.positionalArguments()) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
}
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
{ {
@ -354,7 +322,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
} }
/* /*
* Establish the mechanism for communication with an already running PolyMC that uses the same data path. * Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
* If there is one, tell it what the user actually wanted to do and exit. * If there is one, tell it what the user actually wanted to do and exit.
* We want to initialize this before logging to avoid messing with the log of a potential already running copy. * We want to initialize this before logging to avoid messing with the log of a potential already running copy.
*/ */
@ -372,12 +340,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
activate.command = "activate"; activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout); m_peerInstance->sendMessage(activate.serialize(), timeout);
if(!m_zipToImport.isEmpty()) if(!m_zipsToImport.isEmpty())
{ {
ApplicationMessage import; for (auto zip_url : m_zipsToImport) {
import.command = "import"; ApplicationMessage import;
import.args.insert("path", m_zipToImport.toString()); import.command = "import";
m_peerInstance->sendMessage(import.serialize(), timeout); import.args.insert("path", zip_url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
} }
} }
else else
@ -433,6 +403,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
return; return;
} }
qInstallMessageHandler(appDebugOutput); qInstallMessageHandler(appDebugOutput);
qSetMessagePattern(
"%{time process}" " "
"%{if-debug}D%{endif}" "%{if-info}I%{endif}" "%{if-warning}W%{endif}" "%{if-critical}C%{endif}" "%{if-fatal}F%{endif}"
" " "|" " "
"%{if-category}[%{category}]: %{endif}"
"%{message}");
qDebug() << "<> Log initialized."; qDebug() << "<> Log initialized.";
} }
@ -496,14 +474,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{ {
// Provide a fallback for migration from PolyMC // Provide a fallback for migration from PolyMC
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this)); m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
// Updates
// Multiple channels are separated by spaces
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
m_settings->registerSetting("AutoUpdate", true);
// Theming // Theming
m_settings->registerSetting("IconTheme", QString("pe_colored")); m_settings->registerSetting("IconTheme", QString("pe_colored"));
m_settings->registerSetting("ApplicationTheme", QString("system")); m_settings->registerSetting("ApplicationTheme", QString());
m_settings->registerSetting("BackgroundCat", QString("kitteh")); m_settings->registerSetting("BackgroundCat", QString("kitteh"));
// Remembered state // Remembered state
@ -712,7 +686,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// initialize network access and proxy setup // initialize network access and proxy setup
{ {
m_network = new QNetworkAccessManager(); m_network.reset(new QNetworkAccessManager());
QString proxyTypeStr = settings()->get("ProxyType").toString(); QString proxyTypeStr = settings()->get("ProxyType").toString();
QString addr = settings()->get("ProxyAddr").toString(); QString addr = settings()->get("ProxyAddr").toString();
int port = settings()->get("ProxyPort").value<qint16>(); int port = settings()->get("ProxyPort").value<qint16>();
@ -734,10 +708,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// initialize the updater // initialize the updater
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
{ {
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM); qDebug() << "Initializing updater";
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json"; #ifdef Q_OS_MAC
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl; m_updater.reset(new MacSparkleUpdater());
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL)); #endif
qDebug() << "<> Updater started."; qDebug() << "<> Updater started.";
} }
@ -854,10 +828,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}); });
{ {
setIconTheme(settings()->get("IconTheme").toString()); applyCurrentlySelectedTheme();
qDebug() << "<> Icon theme set.";
setApplicationTheme(settings()->get("ApplicationTheme").toString(), true);
qDebug() << "<> Application theme set.";
} }
updateCapabilities(); updateCapabilities();
@ -900,7 +871,8 @@ bool Application::createSetupWizard()
return false; return false;
}(); }();
bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired; bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
if(wizardRequired) if(wizardRequired)
{ {
@ -919,6 +891,12 @@ bool Application::createSetupWizard()
{ {
m_setupWizard->addPage(new PasteWizardPage(m_setupWizard)); m_setupWizard->addPage(new PasteWizardPage(m_setupWizard));
} }
if (themeInterventionRequired)
{
settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
}
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show(); m_setupWizard->show();
return true; return true;
@ -941,7 +919,7 @@ bool Application::event(QEvent* event)
if (event->type() == QEvent::FileOpen) { if (event->type() == QEvent::FileOpen) {
auto ev = static_cast<QFileOpenEvent*>(event); auto ev = static_cast<QFileOpenEvent*>(event);
m_mainWindow->droppedURLs({ ev->url() }); m_mainWindow->processURLs({ ev->url() });
} }
return QApplication::event(event); return QApplication::event(event);
@ -956,7 +934,6 @@ void Application::setupWizardFinished(int status)
void Application::performMainStartupAction() void Application::performMainStartupAction()
{ {
m_status = Application::Initialized; m_status = Application::Initialized;
if(!m_instanceIdToLaunch.isEmpty()) if(!m_instanceIdToLaunch.isEmpty())
{ {
auto inst = instances()->getInstanceById(m_instanceIdToLaunch); auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
@ -1018,10 +995,10 @@ void Application::performMainStartupAction()
showMainWindow(false); showMainWindow(false);
qDebug() << "<> Main window shown."; qDebug() << "<> Main window shown.";
} }
if(!m_zipToImport.isEmpty()) if(!m_zipsToImport.isEmpty())
{ {
qDebug() << "<> Importing instance from zip:" << m_zipToImport; qDebug() << "<> Importing from zip:" << m_zipsToImport;
m_mainWindow->droppedURLs({ m_zipToImport }); m_mainWindow->processURLs( m_zipsToImport );
} }
} }
@ -1074,7 +1051,7 @@ void Application::messageReceived(const QByteArray& message)
qWarning() << "Received" << command << "message without a zip path/URL."; qWarning() << "Received" << command << "message without a zip path/URL.";
return; return;
} }
m_mainWindow->droppedURLs({ QUrl(path) }); m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
} }
else if(command == "launch") else if(command == "launch")
{ {
@ -1143,9 +1120,14 @@ QList<ITheme*> Application::getValidApplicationThemes()
return m_themeManager->getValidApplicationThemes(); return m_themeManager->getValidApplicationThemes();
} }
void Application::setApplicationTheme(const QString& name, bool initial) void Application::applyCurrentlySelectedTheme()
{ {
m_themeManager->setApplicationTheme(name, initial); m_themeManager->applyCurrentlySelectedTheme();
}
void Application::setApplicationTheme(const QString& name)
{
m_themeManager->setApplicationTheme(name);
} }
void Application::setIconTheme(const QString& name) void Application::setIconTheme(const QString& name)
@ -1373,16 +1355,7 @@ MainWindow* Application::showMainWindow(bool minimized)
m_mainWindow = new MainWindow(); m_mainWindow = new MainWindow();
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
#ifdef Q_OS_WIN
if (IsWindows10OrGreater())
{
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
} else {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
}
}
#endif
if(minimized) if(minimized)
{ {
m_mainWindow->showMinimized(); m_mainWindow->showMinimized();
@ -1559,7 +1532,8 @@ QString Application::getJarPath(QString jarFile)
FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME), FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
#endif #endif
FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(m_rootPath, "jars"),
FS::PathCombine(applicationDirPath(), "jars") FS::PathCombine(applicationDirPath(), "jars"),
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
}; };
for(QString p : potentialPaths) for(QString p : potentialPaths)
{ {
@ -1710,3 +1684,13 @@ bool Application::handleDataMigration(const QString& currentData,
return true; return true;
} }
void Application::triggerUpdateCheck()
{
if (m_updater) {
qDebug() << "Checking for updates.";
m_updater->setBetaAllowed(false); // There are no other channels than stable
m_updater->checkForUpdates();
} else {
qDebug() << "Updater not available.";
}
}

View File

@ -43,7 +43,6 @@
#include <QIcon> #include <QIcon>
#include <QDateTime> #include <QDateTime>
#include <QUrl> #include <QUrl>
#include <updater/GoUpdate.h>
#include <BaseInstance.h> #include <BaseInstance.h>
@ -63,7 +62,7 @@ class AccountList;
class IconList; class IconList;
class QNetworkAccessManager; class QNetworkAccessManager;
class JavaInstallList; class JavaInstallList;
class UpdateChecker; class ExternalUpdater;
class BaseProfilerFactory; class BaseProfilerFactory;
class BaseDetachedToolFactory; class BaseDetachedToolFactory;
class TranslationsModel; class TranslationsModel;
@ -120,14 +119,18 @@ public:
void setIconTheme(const QString& name); void setIconTheme(const QString& name);
void applyCurrentlySelectedTheme();
QList<ITheme*> getValidApplicationThemes(); QList<ITheme*> getValidApplicationThemes();
void setApplicationTheme(const QString& name, bool initial); void setApplicationTheme(const QString& name);
shared_qobject_ptr<UpdateChecker> updateChecker() { shared_qobject_ptr<ExternalUpdater> updater() {
return m_updateChecker; return m_updater;
} }
void triggerUpdateCheck();
std::shared_ptr<TranslationsModel> translations(); std::shared_ptr<TranslationsModel> translations();
std::shared_ptr<JavaInstallList> javalist(); std::shared_ptr<JavaInstallList> javalist();
@ -206,6 +209,7 @@ signals:
void updateAllowedChanged(bool status); void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen(); void globalSettingsAboutToOpen();
void globalSettingsClosed(); void globalSettingsClosed();
int currentCatChanged(int index);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
void clickedOnDock(); void clickedOnDock();
@ -248,7 +252,7 @@ private:
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
shared_qobject_ptr<UpdateChecker> m_updateChecker; shared_qobject_ptr<ExternalUpdater> m_updater;
shared_qobject_ptr<AccountList> m_accounts; shared_qobject_ptr<AccountList> m_accounts;
shared_qobject_ptr<HttpMetaCache> m_metacache; shared_qobject_ptr<HttpMetaCache> m_metacache;
@ -303,8 +307,7 @@ public:
QString m_serverToJoin; QString m_serverToJoin;
QString m_profileToUse; QString m_profileToUse;
bool m_liveCheck = false; bool m_liveCheck = false;
QUrl m_zipToImport; QList<QUrl> m_zipsToImport;
QString m_instanceIdToShowWindowOf; QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile; std::unique_ptr<QFile> logFile;
}; };

View File

@ -38,9 +38,9 @@ set(CORE_SOURCES
InstanceImportTask.h InstanceImportTask.h
InstanceImportTask.cpp InstanceImportTask.cpp
# Mod downloading task # Resource downloading task
ModDownloadTask.h ResourceDownloadTask.h
ModDownloadTask.cpp ResourceDownloadTask.cpp
# Use tracking separate from memory management # Use tracking separate from memory management
Usable.h Usable.h
@ -163,12 +163,6 @@ set(LAUNCH_SOURCES
# Old update system # Old update system
set(UPDATE_SOURCES set(UPDATE_SOURCES
updater/GoUpdate.h
updater/GoUpdate.cpp
updater/UpdateChecker.h
updater/UpdateChecker.cpp
updater/DownloadTask.h
updater/DownloadTask.cpp
updater/ExternalUpdater.h updater/ExternalUpdater.h
) )
@ -341,12 +335,18 @@ set(MINECRAFT_SOURCES
minecraft/mod/Resource.cpp minecraft/mod/Resource.cpp
minecraft/mod/ResourceFolderModel.h minecraft/mod/ResourceFolderModel.h
minecraft/mod/ResourceFolderModel.cpp minecraft/mod/ResourceFolderModel.cpp
minecraft/mod/DataPack.h
minecraft/mod/DataPack.cpp
minecraft/mod/ResourcePack.h minecraft/mod/ResourcePack.h
minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePack.cpp
minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.h
minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/ResourcePackFolderModel.cpp
minecraft/mod/TexturePack.h minecraft/mod/TexturePack.h
minecraft/mod/TexturePack.cpp minecraft/mod/TexturePack.cpp
minecraft/mod/ShaderPack.h
minecraft/mod/ShaderPack.cpp
minecraft/mod/WorldSave.h
minecraft/mod/WorldSave.cpp
minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h minecraft/mod/ShaderPackFolderModel.h
@ -357,10 +357,18 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalModParseTask.cpp minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h minecraft/mod/tasks/LocalResourcePackParseTask.h
minecraft/mod/tasks/LocalResourcePackParseTask.cpp minecraft/mod/tasks/LocalResourcePackParseTask.cpp
minecraft/mod/tasks/LocalTexturePackParseTask.h minecraft/mod/tasks/LocalTexturePackParseTask.h
minecraft/mod/tasks/LocalTexturePackParseTask.cpp minecraft/mod/tasks/LocalTexturePackParseTask.cpp
minecraft/mod/tasks/LocalShaderPackParseTask.h
minecraft/mod/tasks/LocalShaderPackParseTask.cpp
minecraft/mod/tasks/LocalWorldSaveParseTask.h
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
minecraft/mod/tasks/LocalResourceParse.h
minecraft/mod/tasks/LocalResourceParse.cpp
# Assets # Assets
minecraft/AssetsUtils.h minecraft/AssetsUtils.h
@ -469,7 +477,7 @@ set(API_SOURCES
modplatform/ModIndex.h modplatform/ModIndex.h
modplatform/ModIndex.cpp modplatform/ModIndex.cpp
modplatform/ModAPI.h modplatform/ResourceAPI.h
modplatform/EnsureMetadataTask.h modplatform/EnsureMetadataTask.h
modplatform/EnsureMetadataTask.cpp modplatform/EnsureMetadataTask.cpp
@ -480,8 +488,8 @@ set(API_SOURCES
modplatform/flame/FlameAPI.cpp modplatform/flame/FlameAPI.cpp
modplatform/modrinth/ModrinthAPI.h modplatform/modrinth/ModrinthAPI.h
modplatform/modrinth/ModrinthAPI.cpp modplatform/modrinth/ModrinthAPI.cpp
modplatform/helpers/NetworkModAPI.h modplatform/helpers/NetworkResourceAPI.h
modplatform/helpers/NetworkModAPI.cpp modplatform/helpers/NetworkResourceAPI.cpp
modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.h
modplatform/helpers/HashUtils.cpp modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h modplatform/helpers/OverrideUtils.h
@ -561,6 +569,24 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h modplatform/atlauncher/ATLShareCode.h
) )
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
HEADER Logging.h
IDENTIFIER authCredentials
CATEGORY_NAME "launcher.auth.credentials"
DEFAULT_SEVERITY Warning
DESCRIPTION "Secrets and credentials for debugging purposes"
EXPORT "${Launcher_Name}"
)
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
ecm_qt_install_logging_categories(
EXPORT "${Launcher_Name}"
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
endif()
################################ COMPILE ################################ ################################ COMPILE ################################
set(LOGIC_SOURCES set(LOGIC_SOURCES
@ -599,8 +625,6 @@ SET(LAUNCHER_SOURCES
Application.cpp Application.cpp
DataMigrationTask.h DataMigrationTask.h
DataMigrationTask.cpp DataMigrationTask.cpp
UpdateController.cpp
UpdateController.h
ApplicationMessage.h ApplicationMessage.h
ApplicationMessage.cpp ApplicationMessage.cpp
@ -609,7 +633,7 @@ SET(LAUNCHER_SOURCES
DesktopServices.cpp DesktopServices.cpp
VersionProxyModel.h VersionProxyModel.h
VersionProxyModel.cpp VersionProxyModel.cpp
HoeDown.h Markdown.h
# Super secret! # Super secret!
KonamiCode.h KonamiCode.h
@ -661,6 +685,8 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/LanguageWizardPage.h ui/setupwizard/LanguageWizardPage.h
ui/setupwizard/PasteWizardPage.cpp ui/setupwizard/PasteWizardPage.cpp
ui/setupwizard/PasteWizardPage.h ui/setupwizard/PasteWizardPage.h
ui/setupwizard/ThemeWizardPage.cpp
ui/setupwizard/ThemeWizardPage.h
# GUI - themes # GUI - themes
ui/themes/FusionTheme.cpp ui/themes/FusionTheme.cpp
@ -747,6 +773,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/VanillaPage.cpp ui/pages/modplatform/VanillaPage.cpp
ui/pages/modplatform/VanillaPage.h ui/pages/modplatform/VanillaPage.h
ui/pages/modplatform/ResourcePage.cpp
ui/pages/modplatform/ResourcePage.h
ui/pages/modplatform/ResourceModel.cpp
ui/pages/modplatform/ResourceModel.h
ui/pages/modplatform/ModPage.cpp ui/pages/modplatform/ModPage.cpp
ui/pages/modplatform/ModPage.h ui/pages/modplatform/ModPage.h
ui/pages/modplatform/ModModel.cpp ui/pages/modplatform/ModModel.cpp
@ -779,10 +810,10 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/flame/FlameModel.h ui/pages/modplatform/flame/FlameModel.h
ui/pages/modplatform/flame/FlamePage.cpp ui/pages/modplatform/flame/FlamePage.cpp
ui/pages/modplatform/flame/FlamePage.h ui/pages/modplatform/flame/FlamePage.h
ui/pages/modplatform/flame/FlameModModel.cpp ui/pages/modplatform/flame/FlameResourceModels.cpp
ui/pages/modplatform/flame/FlameModModel.h ui/pages/modplatform/flame/FlameResourceModels.h
ui/pages/modplatform/flame/FlameModPage.cpp ui/pages/modplatform/flame/FlameResourcePages.cpp
ui/pages/modplatform/flame/FlameModPage.h ui/pages/modplatform/flame/FlameResourcePages.h
ui/pages/modplatform/modrinth/ModrinthPage.cpp ui/pages/modplatform/modrinth/ModrinthPage.cpp
ui/pages/modplatform/modrinth/ModrinthPage.h ui/pages/modplatform/modrinth/ModrinthPage.h
@ -797,10 +828,10 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.cpp
ui/pages/modplatform/ImportPage.h ui/pages/modplatform/ImportPage.h
ui/pages/modplatform/modrinth/ModrinthModModel.cpp ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
ui/pages/modplatform/modrinth/ModrinthModModel.h ui/pages/modplatform/modrinth/ModrinthResourceModels.h
ui/pages/modplatform/modrinth/ModrinthModPage.cpp ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
ui/pages/modplatform/modrinth/ModrinthModPage.h ui/pages/modplatform/modrinth/ModrinthResourcePages.h
# GUI - dialogs # GUI - dialogs
ui/dialogs/AboutDialog.cpp ui/dialogs/AboutDialog.cpp
@ -821,8 +852,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportInstanceDialog.h
ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourcePackDialog.cpp ui/dialogs/ImportResourceDialog.cpp
ui/dialogs/ImportResourcePackDialog.h ui/dialogs/ImportResourceDialog.h
ui/dialogs/LoginDialog.cpp ui/dialogs/LoginDialog.cpp
ui/dialogs/LoginDialog.h ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.cpp
@ -841,14 +872,12 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ProgressDialog.h ui/dialogs/ProgressDialog.h
ui/dialogs/ReviewMessageBox.cpp ui/dialogs/ReviewMessageBox.cpp
ui/dialogs/ReviewMessageBox.h ui/dialogs/ReviewMessageBox.h
ui/dialogs/UpdateDialog.cpp
ui/dialogs/UpdateDialog.h
ui/dialogs/VersionSelectDialog.cpp ui/dialogs/VersionSelectDialog.cpp
ui/dialogs/VersionSelectDialog.h ui/dialogs/VersionSelectDialog.h
ui/dialogs/SkinUploadDialog.cpp ui/dialogs/SkinUploadDialog.cpp
ui/dialogs/SkinUploadDialog.h ui/dialogs/SkinUploadDialog.h
ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ResourceDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h ui/dialogs/ResourceDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h ui/dialogs/ScrollMessageBox.h
ui/dialogs/BlockedModsDialog.cpp ui/dialogs/BlockedModsDialog.cpp
@ -902,6 +931,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/ProgressWidget.cpp ui/widgets/ProgressWidget.cpp
ui/widgets/WideBar.h ui/widgets/WideBar.h
ui/widgets/WideBar.cpp ui/widgets/WideBar.cpp
ui/widgets/ThemeCustomizationWidget.h
ui/widgets/ThemeCustomizationWidget.cpp
# GUI - instance group view # GUI - instance group view
ui/instanceview/InstanceProxyModel.cpp ui/instanceview/InstanceProxyModel.cpp
@ -917,18 +948,10 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h ui/instanceview/VisualGroup.h
) )
if(WIN32)
set(LAUNCHER_SOURCES
${LAUNCHER_SOURCES}
# GUI - dark titlebar for Windows 10/11
ui/WinDarkmode.h
ui/WinDarkmode.cpp
)
endif()
qt_wrap_ui(LAUNCHER_UI qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui ui/setupwizard/PasteWizardPage.ui
ui/setupwizard/ThemeWizardPage.ui
ui/pages/global/AccountListPage.ui ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui ui/pages/global/LauncherPage.ui
@ -950,7 +973,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/atlauncher/AtlPage.ui
ui/pages/modplatform/VanillaPage.ui ui/pages/modplatform/VanillaPage.ui
ui/pages/modplatform/ModPage.ui ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ImportPage.ui
@ -961,18 +984,18 @@ qt_wrap_ui(LAUNCHER_UI
ui/widgets/CustomCommands.ui ui/widgets/CustomCommands.ui
ui/widgets/InfoFrame.ui ui/widgets/InfoFrame.ui
ui/widgets/ModFilterWidget.ui ui/widgets/ModFilterWidget.ui
ui/widgets/ThemeCustomizationWidget.ui
ui/dialogs/CopyInstanceDialog.ui ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui ui/dialogs/ProgressDialog.ui
ui/dialogs/NewInstanceDialog.ui ui/dialogs/NewInstanceDialog.ui
ui/dialogs/UpdateDialog.ui
ui/dialogs/NewComponentDialog.ui ui/dialogs/NewComponentDialog.ui
ui/dialogs/NewsDialog.ui ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui ui/dialogs/ProfileSelectDialog.ui
ui/dialogs/SkinUploadDialog.ui ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/IconPickerDialog.ui ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourcePackDialog.ui ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/ElybyLoginDialog.ui ui/dialogs/ElybyLoginDialog.ui
@ -1038,7 +1061,7 @@ target_link_libraries(Launcher_logic
) )
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
QuaZip::QuaZip QuaZip::QuaZip
hoedown cmark::cmark
LocalPeer LocalPeer
Launcher_rainbow Launcher_rainbow
) )

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -56,6 +57,7 @@
#include <shlobj.h> #include <shlobj.h>
#include <shobjidl.h> #include <shobjidl.h>
#include <sys/utime.h> #include <sys/utime.h>
#include <versionhelpers.h>
#include <windows.h> #include <windows.h>
#include <winnls.h> #include <winnls.h>
#include <string> #include <string>
@ -213,6 +215,22 @@ bool copy::operator()(const QString& offset, bool dryRun)
return err.value() == 0; return err.value() == 0;
} }
bool move(const QString& source, const QString& dest)
{
std::error_code err;
ensureFilePathExists(dest);
fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err);
if (err) {
qWarning() << "Failed to move file:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << source;
qDebug() << "Destination file:" << dest;
}
return err.value() == 0;
}
bool deletePath(QString path) bool deletePath(QString path)
{ {
std::error_code err; std::error_code err;
@ -226,7 +244,7 @@ bool deletePath(QString path)
return err.value() == 0; return err.value() == 0;
} }
bool trash(QString path, QString *pathInTrash = nullptr) bool trash(QString path, QString *pathInTrash)
{ {
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
return false; return false;
@ -234,6 +252,10 @@ bool trash(QString path, QString *pathInTrash = nullptr)
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
if (DesktopServices::isFlatpak()) if (DesktopServices::isFlatpak())
return false; return false;
#if defined Q_OS_WIN32
if (IsWindowsServer())
return false;
#endif
return QFile::moveToTrash(path, pathInTrash); return QFile::moveToTrash(path, pathInTrash);
#endif #endif
} }

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -121,6 +122,14 @@ class copy : public QObject {
int m_copied; int m_copied;
}; };
/**
* @brief moves a file by renaming it
* @param source source file path
* @param dest destination filepath
*
*/
bool move(const QString& source, const QString& dest);
/** /**
* Delete a folder recursively * Delete a folder recursively
*/ */
@ -129,7 +138,7 @@ bool deletePath(QString path);
/** /**
* Trash a folder / file * Trash a folder / file
*/ */
bool trash(QString path, QString *pathInTrash); bool trash(QString path, QString *pathInTrash = nullptr);
QString PathCombine(const QString& path1, const QString& path2); QString PathCombine(const QString& path1, const QString& path2);
QString PathCombine(const QString& path1, const QString& path2, const QString& path3); QString PathCombine(const QString& path1, const QString& path2, const QString& path3);

View File

@ -1,76 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <hoedown/html.h>
#include <hoedown/document.h>
#include <QString>
#include <QByteArray>
/**
* hoedown wrapper, because dealing with resource lifetime in C is stupid
*/
class HoeDown
{
public:
class buffer
{
public:
buffer(size_t unit = 4096)
{
buf = hoedown_buffer_new(unit);
}
~buffer()
{
hoedown_buffer_free(buf);
}
const char * cstr()
{
return hoedown_buffer_cstr(buf);
}
void put(QByteArray input)
{
hoedown_buffer_put(buf, reinterpret_cast<uint8_t *>(input.data()), input.size());
}
const uint8_t * data() const
{
return buf->data;
}
size_t size() const
{
return buf->size;
}
hoedown_buffer * buf;
} ib, ob;
HoeDown()
{
renderer = hoedown_html_renderer_new((hoedown_html_flags) 0,0);
document = hoedown_document_new(renderer, (hoedown_extensions) 0, 8);
}
~HoeDown()
{
hoedown_document_free(document);
hoedown_html_renderer_free(renderer);
}
QString process(QByteArray input)
{
ib.put(input);
hoedown_document_render(document, ob.buf, ib.data(), ib.size());
return ob.cstr();
}
private:
hoedown_document * document;
hoedown_renderer * renderer;
};

View File

@ -88,7 +88,7 @@ void InstanceImportTask::executeTask()
entry->setStale(true); entry->setStale(true);
m_archivePath = entry->getFullPath(); m_archivePath = entry->getFullPath();
m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network()); m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
@ -301,7 +301,7 @@ void InstanceImportTask::processFlame()
void InstanceImportTask::processTechnic() void InstanceImportTask::processTechnic()
{ {
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor(); shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor{ new Technic::TechnicPackProcessor };
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath); packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath);

View File

@ -112,7 +112,15 @@ void LaunchController::decideAccount()
} }
} }
m_accountToUse = accounts->defaultAccount(); // Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
if (instanceAccountIndex == -1) {
m_accountToUse = accounts->defaultAccount();
} else {
m_accountToUse = accounts->at(instanceAccountIndex);
}
if (!m_accountToUse) if (!m_accountToUse)
{ {
// If no default account is set, ask the user which one to use. // If no default account is set, ask the user which one to use.
@ -374,15 +382,15 @@ void LaunchController::launchInstance()
} }
resolved_servers = resolved_servers + "]\n\n"; resolved_servers = resolved_servers + "]\n\n";
} }
m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher)); m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
} else { } else {
online_mode = m_demo ? "demo" : "offline"; online_mode = m_demo ? "demo" : "offline";
} }
m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// Prepend Version // Prepend Version
m_launcher->prependStep(new TextPrint(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher)); m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
m_launcher->start(); m_launcher->start();
} }

34
launcher/Markdown.h Normal file
View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Joshua Goins <josh@redstrate.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <cmark.h>
static QString markdownToHTML(const QString& markdown)
{
const QByteArray markdownData = markdown.toUtf8();
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
QString htmlStr(buffer);
free(buffer);
return htmlStr;
}

View File

@ -1,72 +0,0 @@
// 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/>.
*/
#include "ModDownloadTask.h"
#include "Application.h"
#include "minecraft/mod/ModFolderModel.h"
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)
{
if (is_indexed) {
m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ModDownloadTask::hasOldMod);
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));
m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed);
addTask(m_filesNetJob);
}
void ModDownloadTask::downloadSucceeded()
{
m_filesNetJob.reset();
auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_mod_version.fileName) {
mods->uninstallMod(filename, true);
}
}
void ModDownloadTask::downloadFailed(QString reason)
{
emitFailed(reason);
m_filesNetJob.reset();
}
void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{
emit progress(current, total);
}
// This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully!
void ModDownloadTask::hasOldMod(QString name, QString filename)
{
to_delete = {name, filename};
}

View File

@ -20,18 +20,34 @@ using unique_qobject_ptr = QScopedPointer<T, QScopedPointerDeleteLater>;
template <typename T> template <typename T>
class shared_qobject_ptr : public QSharedPointer<T> { class shared_qobject_ptr : public QSharedPointer<T> {
public: public:
constexpr shared_qobject_ptr() : QSharedPointer<T>() {} constexpr explicit shared_qobject_ptr() : QSharedPointer<T>() {}
constexpr shared_qobject_ptr(T* ptr) : QSharedPointer<T>(ptr, &QObject::deleteLater) {} constexpr explicit shared_qobject_ptr(T* ptr) : QSharedPointer<T>(ptr, &QObject::deleteLater) {}
constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer<T>(null_ptr, &QObject::deleteLater) {} constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer<T>(null_ptr, &QObject::deleteLater) {}
template <typename Derived> template <typename Derived>
constexpr shared_qobject_ptr(const shared_qobject_ptr<Derived>& other) : QSharedPointer<T>(other) constexpr shared_qobject_ptr(const shared_qobject_ptr<Derived>& other) : QSharedPointer<T>(other)
{} {}
template <typename Derived>
constexpr shared_qobject_ptr(const QSharedPointer<Derived>& other) : QSharedPointer<T>(other)
{}
void reset() { QSharedPointer<T>::reset(); } void reset() { QSharedPointer<T>::reset(); }
void reset(T*&& other)
{
shared_qobject_ptr<T> t(other);
this->swap(t);
}
void reset(const shared_qobject_ptr<T>& other) void reset(const shared_qobject_ptr<T>& other)
{ {
shared_qobject_ptr<T> t(other); shared_qobject_ptr<T> t(other);
this->swap(t); this->swap(t);
} }
}; };
template <typename T, typename... Args>
shared_qobject_ptr<T> makeShared(Args... args)
{
auto obj = new T(args...);
return shared_qobject_ptr<T>(obj);
}

View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022-2023 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 "ResourceDownloadTask.h"
#include "Application.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
ModPlatform::IndexedVersion version,
const std::shared_ptr<ResourceFolderModel> packs,
bool is_indexed)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
{
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) {
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
addTask(m_update_task);
}
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
QDir dir { m_pack_model->dir() };
{
// FIXME: Make this more generic. May require adding additional info to IndexedVersion,
// or adquiring a reference to the base instance.
if (!m_pack_version.custom_target_folder.isEmpty()) {
dir.cdUp();
dir.cd(m_pack_version.custom_target_folder);
}
}
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
addTask(m_filesNetJob);
}
void ResourceDownloadTask::downloadSucceeded()
{
m_filesNetJob.reset();
auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_pack_version.fileName) {
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model)
model->uninstallMod(filename, true);
else
m_pack_model->uninstallResource(filename);
}
}
void ResourceDownloadTask::downloadFailed(QString reason)
{
emitFailed(reason);
m_filesNetJob.reset();
}
void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
{
emit progress(current, total);
}
// This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully!
void ResourceDownloadTask::hasOldResource(QString name, QString filename)
{
to_delete = { name, filename };
}

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -25,32 +25,32 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h"
class ModFolderModel; class ResourceFolderModel;
class ModDownloadTask : public SequentialTask { class ResourceDownloadTask : public SequentialTask {
Q_OBJECT Q_OBJECT
public: public:
explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr<ModFolderModel> mods, bool is_indexed = true); explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr<ResourceFolderModel> packs, bool is_indexed = true);
const QString& getFilename() const { return m_mod_version.fileName; } const QString& getFilename() const { return m_pack_version.fileName; }
const QString& getCustomPath() const { return m_pack_version.custom_target_folder; }
const QVariant& getVersionID() const { return m_pack_version.fileId; }
private: private:
ModPlatform::IndexedPack m_mod; ModPlatform::IndexedPack m_pack;
ModPlatform::IndexedVersion m_mod_version; ModPlatform::IndexedVersion m_pack_version;
const std::shared_ptr<ModFolderModel> mods; const std::shared_ptr<ResourceFolderModel> m_pack_model;
NetJob::Ptr m_filesNetJob; NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task; LocalModUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total); void downloadProgressChanged(qint64 current, qint64 total);
void downloadFailed(QString reason); void downloadFailed(QString reason);
void downloadSucceeded(); void downloadSucceeded();
std::tuple<QString, QString> to_delete {"", ""}; std::tuple<QString, QString> to_delete {"", ""};
private slots: private slots:
void hasOldMod(QString name, QString filename); void hasOldResource(QString name, QString filename);
}; };

View File

@ -1,443 +0,0 @@
#include <QFile>
#include <QMessageBox>
#include <FileSystem.h>
#include <updater/GoUpdate.h>
#include "UpdateController.h"
#include <QApplication>
#include <thread>
#include <chrono>
#include <LocalPeer.h>
#include "BuildConfig.h"
// from <sys/stat.h>
#ifndef S_IRUSR
#define __S_IREAD 0400 /* Read by owner. */
#define __S_IWRITE 0200 /* Write by owner. */
#define __S_IEXEC 0100 /* Execute by owner. */
#define S_IRUSR __S_IREAD /* Read by owner. */
#define S_IWUSR __S_IWRITE /* Write by owner. */
#define S_IXUSR __S_IEXEC /* Execute by owner. */
#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
#endif
static QFile::Permissions unixModeToPermissions(const int mode)
{
QFile::Permissions perms;
if (mode & S_IRUSR)
{
perms |= QFile::ReadUser;
}
if (mode & S_IWUSR)
{
perms |= QFile::WriteUser;
}
if (mode & S_IXUSR)
{
perms |= QFile::ExeUser;
}
if (mode & S_IRGRP)
{
perms |= QFile::ReadGroup;
}
if (mode & S_IWGRP)
{
perms |= QFile::WriteGroup;
}
if (mode & S_IXGRP)
{
perms |= QFile::ExeGroup;
}
if (mode & S_IROTH)
{
perms |= QFile::ReadOther;
}
if (mode & S_IWOTH)
{
perms |= QFile::WriteOther;
}
if (mode & S_IXOTH)
{
perms |= QFile::ExeOther;
}
return perms;
}
static const QLatin1String liveCheckFile("live.check");
UpdateController::UpdateController(QWidget * parent, const QString& root, const QString updateFilesDir, GoUpdate::OperationList operations)
{
m_parent = parent;
m_root = root;
m_updateFilesDir = updateFilesDir;
m_operations = operations;
}
void UpdateController::installUpdates()
{
qint64 pid = -1;
QStringList args;
bool started = false;
qDebug() << "Installing updates.";
#ifdef Q_OS_WIN
QString finishCmd = QApplication::applicationFilePath();
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD)
QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME);
#elif defined Q_OS_MAC
QString finishCmd = QApplication::applicationFilePath();
#else
#error Unsupported operating system.
#endif
QString backupPath = FS::PathCombine(m_root, "update", "backup");
QDir origin(m_root);
// clean up the backup folder. it should be empty before we start
if(!FS::deletePath(backupPath))
{
qWarning() << "couldn't remove previous backup folder" << backupPath;
}
// and it should exist.
if(!FS::ensureFolderPathExists(backupPath))
{
qWarning() << "couldn't create folder" << backupPath;
return;
}
bool useXPHack = false;
QString exePath;
QString exeOrigin;
QString exeBackup;
// perform the update operations
for(auto op: m_operations)
{
switch(op.type)
{
// replace = move original out to backup, if it exists, move the new file in its place
case GoUpdate::Operation::OP_REPLACE:
{
#ifdef Q_OS_WIN32
QString windowsExeName = BuildConfig.LAUNCHER_NAME + ".exe";
// hack for people renaming the .exe because ... reasons :)
if(op.destination == windowsExeName)
{
op.destination = QFileInfo(QApplication::applicationFilePath()).fileName();
}
#endif
QFileInfo destination (FS::PathCombine(m_root, op.destination));
if(destination.exists())
{
QString backupName = op.destination;
backupName.replace('/', '_');
QString backupFilePath = FS::PathCombine(backupPath, backupName);
if(!QFile::rename(destination.absoluteFilePath(), backupFilePath))
{
qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath;
m_failedOperationType = Replace;
m_failedFile = op.destination;
fail();
return;
}
BackupEntry be;
be.original = destination.absoluteFilePath();
be.backup = backupFilePath;
be.update = op.source;
m_replace_backups.append(be);
}
// make sure the folder we are putting this into exists
if(!FS::ensureFilePathExists(destination.absoluteFilePath()))
{
qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath();
m_failedOperationType = Replace;
m_failedFile = op.destination;
fail();
return;
}
// now move the new file in
if(!QFile::rename(op.source, destination.absoluteFilePath()))
{
qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath();
m_failedOperationType = Replace;
m_failedFile = op.destination;
fail();
return;
}
QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode));
}
break;
// delete = move original to backup
case GoUpdate::Operation::OP_DELETE:
{
QString destFilePath = FS::PathCombine(m_root, op.destination);
if(QFile::exists(destFilePath))
{
QString backupName = op.destination;
backupName.replace('/', '_');
QString trashFilePath = FS::PathCombine(backupPath, backupName);
if(!QFile::rename(destFilePath, trashFilePath))
{
qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath;
m_failedFile = op.destination;
m_failedOperationType = Delete;
fail();
return;
}
BackupEntry be;
be.original = destFilePath;
be.backup = trashFilePath;
m_delete_backups.append(be);
}
}
break;
}
}
// try to start the new binary
args = qApp->arguments();
args.removeFirst();
// on old Windows, do insane things... no error checking here, this is just to have something.
if(useXPHack)
{
QString script;
auto nativePath = QDir::toNativeSeparators(exePath);
auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin);
auto nativeBackupPath = QDir::toNativeSeparators(exeBackup);
// so we write this vbscript thing...
QTextStream out(&script);
out << "WScript.Sleep 1000\n";
out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n";
out << "Set shell=CreateObject(\"WScript.Shell\")\n";
out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n";
out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n";
out << "shell.Run \"" << nativePath << "\"\n";
QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs");
// we save it
QFile scriptFile(scriptPath);
scriptFile.open(QIODevice::WriteOnly);
scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n"));
scriptFile.close();
// we run it
started = QProcess::startDetached("wscript", {scriptPath}, m_root);
// and we quit. conscious thought.
qApp->quit();
return;
}
bool doLiveCheck = true;
bool startFailed = false;
// remove live check file, if any
if(QFile::exists(liveCheckFile))
{
if(!QFile::remove(liveCheckFile))
{
qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :(";
doLiveCheck = false;
}
}
if(doLiveCheck)
{
if(!args.contains("--alive"))
{
args.append("--alive");
}
}
// FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874:
QStringList realargs;
int skip = 0;
for(auto & arg: args)
{
if(skip)
{
skip--;
continue;
}
if(arg == "-l")
{
skip = 1;
continue;
}
realargs.append(arg);
}
// start the updated application
started = QProcess::startDetached(finishCmd, realargs, QDir::currentPath(), &pid);
// much dumber check - just find out if the call
if(!started || pid == -1)
{
qWarning() << "Couldn't start new process properly!";
startFailed = true;
}
if(!startFailed && doLiveCheck)
{
int attempts = 0;
while(attempts < 10)
{
attempts++;
QString key;
std::this_thread::sleep_for(std::chrono::milliseconds(250));
if(!QFile::exists(liveCheckFile))
{
qWarning() << "Couldn't find the" << liveCheckFile << "file!";
startFailed = true;
continue;
}
try
{
key = QString::fromUtf8(FS::read(liveCheckFile));
auto id = ApplicationId::fromRawString(key);
LocalPeer peer(nullptr, id);
if(peer.isClient())
{
startFailed = false;
qDebug() << "Found process started with key " << key;
break;
}
else
{
startFailed = true;
qDebug() << "Process started with key " << key << "apparently died or is not reponding...";
break;
}
}
catch (const Exception &e)
{
qWarning() << "Couldn't read the" << liveCheckFile << "file!";
startFailed = true;
continue;
}
}
}
if(startFailed)
{
m_failedOperationType = Start;
fail();
return;
}
else
{
origin.rmdir(m_updateFilesDir);
qApp->quit();
return;
}
}
void UpdateController::fail()
{
qWarning() << "Update failed!";
QString msg;
bool doRollback = false;
QString failTitle = QObject::tr("Update failed!");
QString rollFailTitle = QObject::tr("Rollback failed!");
switch (m_failedOperationType)
{
case Replace:
{
msg = QObject::tr(
"Couldn't replace file %1. Changes will be reverted.\n"
"See the %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
doRollback = true;
QMessageBox::critical(m_parent, failTitle, msg);
break;
}
case Delete:
{
msg = QObject::tr(
"Couldn't remove file %1. Changes will be reverted.\n"
"See the %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
doRollback = true;
QMessageBox::critical(m_parent, failTitle, msg);
break;
}
case Start:
{
msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n"
"\n"
"Roll back to previous version?");
auto result = QMessageBox::critical(
m_parent,
failTitle,
msg,
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes
);
doRollback = (result == QMessageBox::Yes);
break;
}
case Nothing:
default:
return;
}
if(doRollback)
{
auto rollbackOK = rollback();
if(!rollbackOK)
{
msg = QObject::tr("The rollback failed too.\n"
"You will have to repair %1 manually.\n"
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_DISPLAYNAME);
QMessageBox::critical(m_parent, rollFailTitle, msg);
qApp->quit();
}
}
else
{
qApp->quit();
}
}
bool UpdateController::rollback()
{
bool revertOK = true;
// if the above failed, roll back changes
for(auto backup:m_replace_backups)
{
qWarning() << "restoring" << backup.original << "from" << backup.backup;
if(!QFile::rename(backup.original, backup.update))
{
revertOK = false;
qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!";
continue;
}
if(!QFile::rename(backup.backup, backup.original))
{
revertOK = false;
qWarning() << "restoring" << backup.original << "failed!";
}
}
for(auto backup:m_delete_backups)
{
qWarning() << "restoring" << backup.original << "from" << backup.backup;
if(!QFile::rename(backup.backup, backup.original))
{
revertOK = false;
qWarning() << "restoring" << backup.original << "failed!";
}
}
return revertOK;
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <QString>
#include <QList>
#include <updater/GoUpdate.h>
class QWidget;
class UpdateController
{
public:
UpdateController(QWidget * parent, const QString &root, const QString updateFilesDir, GoUpdate::OperationList operations);
void installUpdates();
private:
void fail();
bool rollback();
private:
QString m_root;
QString m_updateFilesDir;
GoUpdate::OperationList m_operations;
QWidget * m_parent;
struct BackupEntry
{
// path where we got the new file from
QString update;
// path of what is being actually updated
QString original;
// path where the backup of the updated file was placed
QString backup;
};
QList <BackupEntry> m_replace_backups;
QList <BackupEntry> m_delete_backups;
enum Failure
{
Replace,
Delete,
Start,
Nothing
} m_failedOperationType = Nothing;
QString m_failedFile;
};

View File

@ -67,7 +67,7 @@ void JavaInstallList::load()
if(m_status != Status::InProgress) if(m_status != Status::InProgress)
{ {
m_status = Status::InProgress; m_status = Status::InProgress;
m_loadTask = new JavaListLoadTask(this); m_loadTask.reset(new JavaListLoadTask(this));
m_loadTask->start(); m_loadTask->start();
} }
} }
@ -167,7 +167,7 @@ void JavaListLoadTask::executeTask()
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); QList<QString> candidate_paths = ju.FindJavaPaths();
m_job = new JavaCheckerJob("Java detection"); m_job.reset(new JavaCheckerJob("Java detection"));
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);

View File

@ -93,7 +93,7 @@ void CheckJava::executeTask()
|| storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0 || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
|| storedVendor.size() == 0) || storedVendor.size() == 0)
{ {
m_JavaChecker = new JavaChecker(); m_JavaChecker.reset(new JavaChecker);
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath; m_JavaChecker->m_path = realJavaPath;

View File

@ -126,7 +126,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
{ {
return; return;
} }
m_updateTask = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network()); m_updateTask.reset(new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network()));
auto url = this->url(); auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename()); auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
entry->setStale(true); entry->setStale(true);

View File

@ -340,7 +340,7 @@ QString AssetObject::getRelPath()
NetJob::Ptr AssetsIndex::getDownloadJob() NetJob::Ptr AssetsIndex::getDownloadJob()
{ {
auto job = new NetJob(QObject::tr("Assets for %1").arg(id), APPLICATION->network()); auto job = makeShared<NetJob>(QObject::tr("Assets for %1").arg(id), APPLICATION->network());
for (auto &object : objects.values()) for (auto &object : objects.values())
{ {
auto dl = object.getDownloadAction(); auto dl = object.getDownloadAction();

View File

@ -572,7 +572,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
// add stuff... // add stuff...
for(auto &add: toAdd) for(auto &add: toAdd)
{ {
ComponentPtr component = new Component(d->m_list, add.uid); auto component = makeShared<Component>(d->m_list, add.uid);
if(!add.equalsVersion.isEmpty()) if(!add.equalsVersion.isEmpty())
{ {
// exact version // exact version

View File

@ -192,6 +192,10 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting("JoinServerOnLaunch", false);
m_settings->registerSetting("JoinServerOnLaunchAddress", ""); m_settings->registerSetting("JoinServerOnLaunchAddress", "");
// Use account for instance, this does not have a global override
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
qDebug() << "Instance-type specific settings were loaded!"; qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true); setSpecificSettingsLoaded(true);
@ -374,7 +378,6 @@ QStringList MinecraftInstance::extraArguments()
if (!addn.isEmpty()) { if (!addn.isEmpty()) {
list.append(addn); list.append(addn);
} }
auto agents = m_components->getProfile()->getAgents(); auto agents = m_components->getProfile()->getAgents();
for (auto agent : agents) for (auto agent : agents)
{ {
@ -966,12 +969,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
// print a header // print a header
{ {
process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); process->appendStep(makeShared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher));
} }
// check java // check java
{ {
process->appendStep(new CheckJava(pptr)); process->appendStep(makeShared<CheckJava>(pptr));
} }
// check launch method // check launch method
@ -979,13 +982,13 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
QString method = launchMethod(); QString method = launchMethod();
if(!validMethods.contains(method)) if(!validMethods.contains(method))
{ {
process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); process->appendStep(makeShared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
return process; return process;
} }
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
{ {
process->appendStep(new CreateGameFolders(pptr)); process->appendStep(makeShared<CreateGameFolders>(pptr));
} }
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool())
@ -997,7 +1000,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
if(serverToJoin && serverToJoin->port == 25565) if(serverToJoin && serverToJoin->port == 25565)
{ {
// Resolve server address to join on launch // Resolve server address to join on launch
auto *step = new LookupServerAddress(pptr); auto step = makeShared<LookupServerAddress>(pptr);
step->setLookupAddress(serverToJoin->address); step->setLookupAddress(serverToJoin->address);
step->setOutputAddressPtr(serverToJoin); step->setOutputAddressPtr(serverToJoin);
process->appendStep(step); process->appendStep(step);
@ -1006,7 +1009,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
// run pre-launch command if that's needed // run pre-launch command if that's needed
if(getPreLaunchCommand().size()) if(getPreLaunchCommand().size())
{ {
auto step = new PreLaunchCommand(pptr); auto step = makeShared<PreLaunchCommand>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
process->appendStep(step); process->appendStep(step);
} }
@ -1015,50 +1018,49 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
if(session->status != AuthSession::PlayableOffline) if(session->status != AuthSession::PlayableOffline)
{ {
if(!session->demo) { if(!session->demo) {
process->appendStep(new ClaimAccount(pptr, session)); process->appendStep(makeShared<ClaimAccount>(pptr, session));
} }
// authlib patch // authlib patch
if (session->user_type == "elyby") if (session->user_type == "elyby")
{ {
process->appendStep(new InjectAuthlib(pptr, &m_injector)); process->appendStep(makeShared<InjectAuthlib>(pptr, &m_injector));
} }
process->appendStep(new Update(pptr, Net::Mode::Online)); process->appendStep(makeShared<Update>(pptr, Net::Mode::Online));
} }
else else
{ {
process->appendStep(new Update(pptr, Net::Mode::Offline)); process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline));
} }
// if there are any jar mods // if there are any jar mods
{ {
process->appendStep(new ModMinecraftJar(pptr)); process->appendStep(makeShared<ModMinecraftJar>(pptr));
} }
// Scan mods folders for mods // Scan mods folders for mods
{ {
process->appendStep(new ScanModFolders(pptr)); process->appendStep(makeShared<ScanModFolders>(pptr));
} }
// print some instance info here... // print some instance info here...
{ {
process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin)); process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, serverToJoin));
} }
// extract native jars if needed // extract native jars if needed
{ {
process->appendStep(new ExtractNatives(pptr)); process->appendStep(makeShared<ExtractNatives>(pptr));
} }
// reconstruct assets if needed // reconstruct assets if needed
{ {
process->appendStep(new ReconstructAssets(pptr)); process->appendStep(makeShared<ReconstructAssets>(pptr));
} }
// verify that minimum Java requirements are met // verify that minimum Java requirements are met
{ {
process->appendStep(new VerifyJavaInstall(pptr)); process->appendStep(makeShared<VerifyJavaInstall>(pptr));
} }
{ {
@ -1066,7 +1068,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
auto method = launchMethod(); auto method = launchMethod();
if(method == "LauncherPart") if(method == "LauncherPart")
{ {
auto step = new LauncherPartLaunch(pptr); auto step = makeShared<LauncherPartLaunch>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
step->setAuthSession(session); step->setAuthSession(session);
step->setServerToJoin(serverToJoin); step->setServerToJoin(serverToJoin);
@ -1074,7 +1076,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
} }
else if (method == "DirectJava") else if (method == "DirectJava")
{ {
auto step = new DirectJavaLaunch(pptr); auto step = makeShared<DirectJavaLaunch>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
step->setAuthSession(session); step->setAuthSession(session);
step->setServerToJoin(serverToJoin); step->setServerToJoin(serverToJoin);
@ -1085,7 +1087,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
// run post-exit command if that's needed // run post-exit command if that's needed
if(getPostExitCommand().size()) if(getPostExitCommand().size())
{ {
auto step = new PostLaunchCommand(pptr); auto step = makeShared<PostLaunchCommand>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
process->appendStep(step); process->appendStep(step);
} }
@ -1095,8 +1097,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
} }
if(m_settings->get("QuitAfterGameStop").toBool()) if(m_settings->get("QuitAfterGameStop").toBool())
{ {
auto step = new QuitAfterGameStop(pptr); process->appendStep(makeShared<QuitAfterGameStop>(pptr));
process->appendStep(step);
} }
m_launchProcess = process; m_launchProcess = process;
emit launchTaskChanged(m_launchProcess); emit launchTaskChanged(m_launchProcess);

View File

@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask()
m_tasks.clear(); m_tasks.clear();
// create folders // create folders
{ {
m_tasks.append(new FoldersTask(m_inst)); m_tasks.append(makeShared<FoldersTask>(m_inst));
} }
// add metadata update task if necessary // add metadata update task if necessary
@ -59,17 +59,17 @@ void MinecraftUpdate::executeTask()
// libraries download // libraries download
{ {
m_tasks.append(new LibrariesTask(m_inst)); m_tasks.append(makeShared<LibrariesTask>(m_inst));
} }
// FML libraries download and copy into the instance // FML libraries download and copy into the instance
{ {
m_tasks.append(new FMLLibrariesTask(m_inst)); m_tasks.append(makeShared<FMLLibrariesTask>(m_inst));
} }
// assets update // assets update
{ {
m_tasks.append(new AssetUpdateTask(m_inst)); m_tasks.append(makeShared<AssetUpdateTask>(m_inst));
} }
if(!m_preFailure.isEmpty()) if(!m_preFailure.isEmpty())

View File

@ -39,6 +39,8 @@
#include "minecraft/ParseUtils.h" #include "minecraft/ParseUtils.h"
#include <minecraft/MojangVersionFormat.h> #include <minecraft/MojangVersionFormat.h>
#include <QRegularExpression>
using namespace Json; using namespace Json;
static void readString(const QJsonObject &root, const QString &key, QString &variable) static void readString(const QJsonObject &root, const QString &key, QString &variable)
@ -121,6 +123,15 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
out->uid = root.value("fileId").toString(); out->uid = root.value("fileId").toString();
} }
const QRegularExpression valid_uid_regex{ QRegularExpression::anchoredPattern(QStringLiteral(R"([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*)")) };
if (!valid_uid_regex.match(out->uid).hasMatch()) {
qCritical() << "The component's 'uid' contains illegal characters! UID:" << out->uid;
out->addProblem(
ProblemSeverity::Error,
QObject::tr("The component's 'uid' contains illegal characters! This can cause security issues.")
);
}
out->version = root.value("version").toString(); out->version = root.value("version").toString();
MojangVersionFormat::readVersionProperties(root, out.get()); MojangVersionFormat::readVersionProperties(root, out.get());

View File

@ -55,12 +55,13 @@
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "modplatform/ModAPI.h" #include "Application.h"
#include "modplatform/ResourceAPI.h"
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{ static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
{"net.minecraftforge", ModAPI::Forge}, {"net.minecraftforge", ResourceAPI::Forge},
{"net.fabricmc.fabric-loader", ModAPI::Fabric}, {"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
{"org.quiltmc.quilt-loader", ModAPI::Quilt} {"org.quiltmc.quilt-loader", ResourceAPI::Quilt}
}; };
PackProfile::PackProfile(MinecraftInstance * instance) PackProfile::PackProfile(MinecraftInstance * instance)
@ -129,7 +130,7 @@ static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & co
// critical // critical
auto uid = Json::requireString(obj.value("uid")); auto uid = Json::requireString(obj.value("uid"));
auto filePath = componentJsonPattern.arg(uid); auto filePath = componentJsonPattern.arg(uid);
auto component = new Component(parent, uid); auto component = makeShared<Component>(parent, uid);
component->m_version = Json::ensureString(obj.value("version")); component->m_version = Json::ensureString(obj.value("version"));
component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false);
component->m_important = Json::ensureBoolean(obj.value("important"), false); component->m_important = Json::ensureBoolean(obj.value("important"), false);
@ -517,23 +518,23 @@ bool PackProfile::revertToBase(int index)
return true; return true;
} }
Component * PackProfile::getComponent(const QString &id) ComponentPtr PackProfile::getComponent(const QString &id)
{ {
auto iter = d->componentIndex.find(id); auto iter = d->componentIndex.find(id);
if (iter == d->componentIndex.end()) if (iter == d->componentIndex.end())
{ {
return nullptr; return nullptr;
} }
return (*iter).get(); return (*iter);
} }
Component * PackProfile::getComponent(int index) ComponentPtr PackProfile::getComponent(int index)
{ {
if(index < 0 || index >= d->components.size()) if(index < 0 || index >= d->components.size())
{ {
return nullptr; return nullptr;
} }
return d->components[index].get(); return d->components[index];
} }
QVariant PackProfile::data(const QModelIndex &index, int role) const QVariant PackProfile::data(const QModelIndex &index, int role) const
@ -764,7 +765,7 @@ bool PackProfile::installEmpty(const QString& uid, const QString& name)
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
appendComponent(new Component(this, f->uid, f)); appendComponent(makeShared<Component>(this, f->uid, f));
scheduleSave(); scheduleSave();
invalidateLaunchProfile(); invalidateLaunchProfile();
return true; return true;
@ -871,7 +872,7 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
appendComponent(new Component(this, f->uid, f)); appendComponent(makeShared<Component>(this, f->uid, f));
} }
scheduleSave(); scheduleSave();
invalidateLaunchProfile(); invalidateLaunchProfile();
@ -932,7 +933,7 @@ bool PackProfile::installCustomJar_internal(QString filepath)
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
appendComponent(new Component(this, f->uid, f)); appendComponent(makeShared<Component>(this, f->uid, f));
scheduleSave(); scheduleSave();
invalidateLaunchProfile(); invalidateLaunchProfile();
@ -988,7 +989,7 @@ bool PackProfile::installAgents_internal(QStringList filepaths)
patchFile.write(OneSixVersionFormat::versionFileToJson(versionFile).toJson()); patchFile.write(OneSixVersionFormat::versionFileToJson(versionFile).toJson());
patchFile.close(); patchFile.close();
appendComponent(new Component(this, versionFile->uid, versionFile)); appendComponent(makeShared<Component>(this, versionFile->uid, versionFile));
} }
scheduleSave(); scheduleSave();
@ -1037,7 +1038,7 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
else else
{ {
// add new // add new
auto component = new Component(this, uid); auto component = makeShared<Component>(this, uid);
component->m_version = version; component->m_version = version;
component->m_important = important; component->m_important = important;
appendComponent(component); appendComponent(component);
@ -1066,19 +1067,22 @@ void PackProfile::disableInteraction(bool disable)
} }
} }
ModAPI::ModLoaderTypes PackProfile::getModLoaders() std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
{ {
ModAPI::ModLoaderTypes result = ModAPI::Unspecified; ResourceAPI::ModLoaderTypes result;
bool has_any_loader = false;
QMapIterator<QString, ModAPI::ModLoaderType> i(modloaderMapping); QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) while (i.hasNext()) {
{
i.next(); i.next();
Component* c = getComponent(i.key()); if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) {
if (c != nullptr && c->isEnabled()) {
result |= i.value(); result |= i.value();
has_any_loader = true;
} }
} }
if (!has_any_loader)
return {};
return result; return result;
} }

View File

@ -49,7 +49,7 @@
#include "BaseVersion.h" #include "BaseVersion.h"
#include "MojangDownloadInfo.h" #include "MojangDownloadInfo.h"
#include "net/Mode.h" #include "net/Mode.h"
#include "modplatform/ModAPI.h" #include "modplatform/ResourceAPI.h"
class MinecraftInstance; class MinecraftInstance;
struct PackProfileData; struct PackProfileData;
@ -136,16 +136,16 @@ signals:
public: public:
/// get the profile component by id /// get the profile component by id
Component * getComponent(const QString &id); ComponentPtr getComponent(const QString &id);
/// get the profile component by index /// get the profile component by index
Component * getComponent(int index); ComponentPtr getComponent(int index);
/// Add the component to the internal list of patches /// Add the component to the internal list of patches
// todo(merged): is this the best approach // todo(merged): is this the best approach
void appendComponent(ComponentPtr component); void appendComponent(ComponentPtr component);
ModAPI::ModLoaderTypes getModLoaders(); std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
private: private:
void scheduleSave(); void scheduleSave();

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -545,6 +546,10 @@ bool World::replace(World &with)
bool World::destroy() bool World::destroy()
{ {
if(!is_valid) return false; if(!is_valid) return false;
if (FS::trash(m_containerFile.filePath()))
return true;
if (m_containerFile.isDir()) if (m_containerFile.isDir())
{ {
QDir d(m_containerFile.filePath()); QDir d(m_containerFile.filePath());

View File

@ -76,7 +76,7 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username) MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username)
{ {
MinecraftAccountPtr account = new MinecraftAccount(); auto account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Mojang; account->data.type = AccountType::Mojang;
account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["userName"] = username;
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
@ -92,7 +92,7 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA()
MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
{ {
MinecraftAccountPtr account = new MinecraftAccount(); auto account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Offline; account->data.type = AccountType::Offline;
account->data.yggdrasilToken.token = "offline"; account->data.yggdrasilToken.token = "offline";
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
@ -109,7 +109,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
MinecraftAccountPtr MinecraftAccount::createElyby(const QString &username) MinecraftAccountPtr MinecraftAccount::createElyby(const QString &username)
{ {
MinecraftAccountPtr account = new MinecraftAccount(); MinecraftAccountPtr account = makeShared<MinecraftAccount>();
account->data.type = AccountType::Elyby; account->data.type = AccountType::Elyby;
account->data.yggdrasilToken.extra["userName"] = username; account->data.yggdrasilToken.extra["userName"] = username;
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));

View File

@ -1,5 +1,6 @@
#include "Parsers.h" #include "Parsers.h"
#include "Json.h" #include "Json.h"
#include "Logging.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonArray> #include <QJsonArray>
@ -75,9 +76,7 @@ bool getBool(QJsonValue value, bool & out) {
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) { bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
qDebug() << "Parsing" << name <<":"; qDebug() << "Parsing" << name <<":";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) { if(jsonError.error) {
@ -137,9 +136,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) { bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile..."; qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -275,9 +272,7 @@ decoded base64 "value":
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) { bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
qDebug() << "Parsing Minecraft profile..."; qDebug() << "Parsing Minecraft profile...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -389,9 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) { bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
qDebug() << "Parsing Minecraft entitlements..."; qDebug() << "Parsing Minecraft entitlements...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -424,9 +417,7 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
bool parseRolloutResponse(QByteArray & data, bool& result) { bool parseRolloutResponse(QByteArray & data, bool& result) {
qDebug() << "Parsing Rollout response..."; qDebug() << "Parsing Rollout response...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonParseError jsonError; QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@ -455,9 +446,7 @@ bool parseRolloutResponse(QByteArray & data, bool& result) {
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) { bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
QJsonParseError jsonError; QJsonParseError jsonError;
qDebug() << "Parsing Mojang response..."; qDebug() << "Parsing Mojang response...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error) { if(jsonError.error) {
qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString(); qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();

View File

@ -8,9 +8,9 @@ ElybyRefresh::ElybyRefresh(
AccountData *data, AccountData *data,
QObject *parent QObject *parent
) : AuthFlow(data, parent) { ) : AuthFlow(data, parent) {
m_steps.append(new ElybyStep(m_data, QString())); m_steps.append(makeShared<ElybyStep>(m_data, QString()));
m_steps.append(new ElybyProfileStep(m_data)); m_steps.append(makeShared<ElybyProfileStep>(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }
ElybyLogin::ElybyLogin( ElybyLogin::ElybyLogin(
@ -18,7 +18,7 @@ ElybyLogin::ElybyLogin(
QString password, QString password,
QObject *parent QObject *parent
): AuthFlow(data, parent), m_password(password) { ): AuthFlow(data, parent), m_password(password) {
m_steps.append(new ElybyStep(m_data, m_password)); m_steps.append(makeShared<ElybyStep>(m_data, m_password));
m_steps.append(new ElybyProfileStep(m_data)); m_steps.append(makeShared<ElybyProfileStep>(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }

View File

@ -10,28 +10,28 @@
#include "minecraft/auth/steps/GetSkinStep.h" #include "minecraft/auth/steps/GetSkinStep.h"
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) { MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) {
m_steps.append(new MSAStep(m_data, MSAStep::Action::Refresh)); m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Refresh));
m_steps.append(new XboxUserStep(m_data)); m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
m_steps.append(new XboxAuthorizationStep(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
m_steps.append(new LauncherLoginStep(m_data)); m_steps.append(makeShared<LauncherLoginStep>(m_data));
m_steps.append(new XboxProfileStep(m_data)); m_steps.append(makeShared<XboxProfileStep>(m_data));
m_steps.append(new EntitlementsStep(m_data)); m_steps.append(makeShared<EntitlementsStep>(m_data));
m_steps.append(new MinecraftProfileStep(m_data)); m_steps.append(makeShared<MinecraftProfileStep>(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }
MSAInteractive::MSAInteractive( MSAInteractive::MSAInteractive(
AccountData* data, AccountData* data,
QObject* parent QObject* parent
) : AuthFlow(data, parent) { ) : AuthFlow(data, parent) {
m_steps.append(new MSAStep(m_data, MSAStep::Action::Login)); m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Login));
m_steps.append(new XboxUserStep(m_data)); m_steps.append(makeShared<XboxUserStep>(m_data));
m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
m_steps.append(new XboxAuthorizationStep(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang"));
m_steps.append(new LauncherLoginStep(m_data)); m_steps.append(makeShared<LauncherLoginStep>(m_data));
m_steps.append(new XboxProfileStep(m_data)); m_steps.append(makeShared<XboxProfileStep>(m_data));
m_steps.append(new EntitlementsStep(m_data)); m_steps.append(makeShared<EntitlementsStep>(m_data));
m_steps.append(new MinecraftProfileStep(m_data)); m_steps.append(makeShared<MinecraftProfileStep>(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }

View File

@ -9,10 +9,10 @@ MojangRefresh::MojangRefresh(
AccountData *data, AccountData *data,
QObject *parent QObject *parent
) : AuthFlow(data, parent) { ) : AuthFlow(data, parent) {
m_steps.append(new YggdrasilStep(m_data, QString())); m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
m_steps.append(new MinecraftProfileStepMojang(m_data)); m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
m_steps.append(new MigrationEligibilityStep(m_data)); m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }
MojangLogin::MojangLogin( MojangLogin::MojangLogin(
@ -20,8 +20,8 @@ MojangLogin::MojangLogin(
QString password, QString password,
QObject *parent QObject *parent
): AuthFlow(data, parent), m_password(password) { ): AuthFlow(data, parent), m_password(password) {
m_steps.append(new YggdrasilStep(m_data, m_password)); m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
m_steps.append(new MinecraftProfileStepMojang(m_data)); m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
m_steps.append(new MigrationEligibilityStep(m_data)); m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
m_steps.append(new GetSkinStep(m_data)); m_steps.append(makeShared<GetSkinStep>(m_data));
} }

View File

@ -6,12 +6,12 @@ OfflineRefresh::OfflineRefresh(
AccountData *data, AccountData *data,
QObject *parent QObject *parent
) : AuthFlow(data, parent) { ) : AuthFlow(data, parent) {
m_steps.append(new OfflineStep(m_data)); m_steps.append(makeShared<OfflineStep>(m_data));
} }
OfflineLogin::OfflineLogin( OfflineLogin::OfflineLogin(
AccountData *data, AccountData *data,
QObject *parent QObject *parent
) : AuthFlow(data, parent) { ) : AuthFlow(data, parent) {
m_steps.append(new OfflineStep(m_data)); m_steps.append(makeShared<OfflineStep>(m_data));
} }

View File

@ -3,6 +3,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUuid> #include <QUuid>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
@ -41,9 +42,7 @@ void EntitlementsStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
// TODO: check presence of same entitlementsRequestId? // TODO: check presence of same entitlementsRequestId?
// TODO: validate JWTs? // TODO: validate JWTs?

View File

@ -2,9 +2,10 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "minecraft/auth/AccountTask.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) { LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
@ -51,14 +52,10 @@ void LauncherLoginStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (Net::isApplicationError(error)) { if (Net::isApplicationError(error)) {
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
@ -76,9 +73,7 @@ void LauncherLoginStep::onRequestDone(
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) { if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
qWarning() << "Could not parse login_with_xbox response..."; qWarning() << "Could not parse login_with_xbox response...";
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to parse the Minecraft access token response.") tr("Failed to parse the Minecraft access token response.")

View File

@ -42,6 +42,7 @@
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "Application.h" #include "Application.h"
#include "Logging.h"
using OAuth2 = Katabasis::DeviceFlow; using OAuth2 = Katabasis::DeviceFlow;
using Activity = Katabasis::Activity; using Activity = Katabasis::Activity;
@ -117,14 +118,12 @@ void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
// Succeeded or did not invalidate tokens // Succeeded or did not invalidate tokens
emit hideVerificationUriAndCode(); emit hideVerificationUriAndCode();
QVariantMap extraTokens = m_oauth2->extraTokens(); QVariantMap extraTokens = m_oauth2->extraTokens();
#ifndef NDEBUG
if (!extraTokens.isEmpty()) { if (!extraTokens.isEmpty()) {
qDebug() << "Extra tokens in response:"; qCDebug(authCredentials()) << "Extra tokens in response:";
foreach (QString key, extraTokens.keys()) { foreach (QString key, extraTokens.keys()) {
qDebug() << "\t" << key << ":" << extraTokens.value(key); qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key);
} }
} }
#endif
emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
return; return;
} }

View File

@ -2,6 +2,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -40,9 +41,7 @@ void MinecraftProfileStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error == QNetworkReply::ContentNotFoundError) { if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state. // NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) { if(m_data->type == AccountType::Mojang) {

View File

@ -2,6 +2,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -43,9 +44,7 @@ void MinecraftProfileStepMojang::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error == QNetworkReply::ContentNotFoundError) { if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state. // NOTE: Succeed even if we do not have a profile. This is a valid account state.
if(m_data->type == AccountType::Mojang) { if(m_data->type == AccountType::Mojang) {

View File

@ -4,6 +4,7 @@
#include <QJsonParseError> #include <QJsonParseError>
#include <QJsonDocument> #include <QJsonDocument>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -58,9 +59,7 @@ void XboxAuthorizationStep::onRequestDone(
auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
requestor->deleteLater(); requestor->deleteLater();
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
if (Net::isApplicationError(error)) { if (Net::isApplicationError(error)) {

View File

@ -3,7 +3,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrlQuery> #include <QUrlQuery>
#include "Logging.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.h" #include "net/NetUtils.h"
@ -56,9 +56,7 @@ void XboxProfileStep::onRequestDone(
if (error != QNetworkReply::NoError) { if (error != QNetworkReply::NoError) {
qWarning() << "Reply error:" << error; qWarning() << "Reply error:" << error;
#ifndef NDEBUG qCDebug(authCredentials()) << data;
qDebug() << data;
#endif
if (Net::isApplicationError(error)) { if (Net::isApplicationError(error)) {
emit finished( emit finished(
AccountTaskState::STATE_FAILED_SOFT, AccountTaskState::STATE_FAILED_SOFT,
@ -74,9 +72,7 @@ void XboxProfileStep::onRequestDone(
return; return;
} }
#ifndef NDEBUG qCDebug(authCredentials()) << "XBox profile: " << data;
qDebug() << "XBox profile: " << data;
#endif
emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile"));
} }

View File

@ -28,13 +28,14 @@ InjectAuthlib::InjectAuthlib(LaunchTask *parent, AuthlibInjectorPtr* injector) :
void InjectAuthlib::executeTask() void InjectAuthlib::executeTask()
{ {
if (m_aborted) if (m_aborted)
{ {
emitFailed(tr("Task aborted.")); emitFailed(tr("Task aborted."));
return; return;
} }
auto latestVersionInfo = QString("https://authlib-injector.yushi.moe/artifact/latest.json"); auto latestVersionInfo = QString("https://authlib-injector.yushi.moe/artifact/latest.json");
auto netJob = new NetJob("Injector versions info download", APPLICATION->network()); auto netJob = makeShared<NetJob>("Injector versions info download", APPLICATION->network());
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("injectors", "version.json"); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("injectors", "version.json");
if (!m_offlineMode) if (!m_offlineMode)
{ {
@ -43,8 +44,8 @@ void InjectAuthlib::executeTask()
netJob->addNetAction(task); netJob->addNetAction(task);
jobPtr.reset(netJob); jobPtr.reset(netJob);
QObject::connect(netJob, &NetJob::succeeded, this, &InjectAuthlib::onVersionDownloadSucceeded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &InjectAuthlib::onVersionDownloadSucceeded);
QObject::connect(netJob, &NetJob::failed, this, &InjectAuthlib::onDownloadFailed); QObject::connect(netJob.get(), &NetJob::failed, this, &InjectAuthlib::onDownloadFailed);
jobPtr->start(); jobPtr->start();
} }
else else
@ -110,15 +111,15 @@ void InjectAuthlib::onVersionDownloadSucceeded()
qDebug() << "Authlib injector version:" << m_versionName; qDebug() << "Authlib injector version:" << m_versionName;
if (!m_offlineMode) if (!m_offlineMode)
{ {
auto netJob = new NetJob("Injector download", APPLICATION->network()); auto netJob = makeShared<NetJob>("Injector download", APPLICATION->network());
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("injectors", m_versionName); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("injectors", m_versionName);
entry->setStale(true); entry->setStale(true);
auto task = Net::Download::makeCached(QUrl(downloadUrl), entry); auto task = Net::Download::makeCached(QUrl(downloadUrl), entry);
netJob->addNetAction(task); netJob->addNetAction(task);
jobPtr.reset(netJob); jobPtr.reset(netJob);
QObject::connect(netJob, &NetJob::succeeded, this, &InjectAuthlib::onDownloadSucceeded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &InjectAuthlib::onDownloadSucceeded);
QObject::connect(netJob, &NetJob::failed, this, &InjectAuthlib::onDownloadFailed); QObject::connect(netJob.get(), &NetJob::failed, this, &InjectAuthlib::onDownloadFailed);
jobPtr->start(); jobPtr->start();
} }
else else

View File

@ -36,6 +36,7 @@
#include "LauncherPartLaunch.h" #include "LauncherPartLaunch.h"
#include <QStandardPaths> #include <QStandardPaths>
#include <QRegularExpression>
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"

View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "DataPack.h"
#include <QDebug>
#include <QMap>
#include <QRegularExpression>
#include "Version.h"
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
{ 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } },
{ 10, { Version("1.19"), Version("1.19.3") } },
};
void DataPack::setPackFormat(int new_format_id)
{
QMutexLocker locker(&m_data_lock);
if (!s_pack_format_versions.contains(new_format_id)) {
qWarning() << "Pack format '" << new_format_id << "' is not a recognized data pack id!";
}
m_pack_format = new_format_id;
}
void DataPack::setDescription(QString new_description)
{
QMutexLocker locker(&m_data_lock);
m_description = new_description;
}
std::pair<Version, Version> DataPack::compatibleVersions() const
{
if (!s_pack_format_versions.contains(m_pack_format)) {
return { {}, {} };
}
return s_pack_format_versions.constFind(m_pack_format).value();
}
std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) const
{
auto const& cast_other = static_cast<DataPack const&>(other);
switch (type) {
default: {
auto res = Resource::compare(other, type);
if (res.first != 0)
return res;
}
case SortType::PACK_FORMAT: {
auto this_ver = packFormat();
auto other_ver = cast_other.packFormat();
if (this_ver > other_ver)
return { 1, type == SortType::PACK_FORMAT };
if (this_ver < other_ver)
return { -1, type == SortType::PACK_FORMAT };
}
}
return { 0, false };
}
bool DataPack::applyFilter(QRegularExpression filter) const
{
if (filter.match(description()).hasMatch())
return true;
if (filter.match(QString::number(packFormat())).hasMatch())
return true;
if (filter.match(compatibleVersions().first.toString()).hasMatch())
return true;
if (filter.match(compatibleVersions().second.toString()).hasMatch())
return true;
return Resource::applyFilter(filter);
}
bool DataPack::valid() const
{
return m_pack_format != 0;
}

View File

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "Resource.h"
#include <QMutex>
class Version;
/* TODO:
*
* Store localized descriptions
* */
class DataPack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
DataPack(QObject* parent = nullptr) : Resource(parent) {}
DataPack(QFileInfo file_info) : Resource(file_info) {}
/** Gets the numerical ID of the pack format. */
[[nodiscard]] int packFormat() const { return m_pack_format; }
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
/** Gets the description of the data pack. */
[[nodiscard]] QString description() const { return m_description; }
/** Thread-safe. */
void setPackFormat(int new_format_id);
/** Thread-safe. */
void setDescription(QString new_description);
bool valid() const override;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
protected:
mutable QMutex m_data_lock;
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
* See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;
/** The data pack's description, as defined in the pack.mcmeta file.
*/
QString m_description;
};

View File

@ -43,6 +43,9 @@
#include "MetadataHandler.h" #include "MetadataHandler.h"
#include "Version.h" #include "Version.h"
#include "minecraft/mod/ModDetails.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{ {
@ -68,6 +71,10 @@ void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
m_local_details.metadata = metadata; m_local_details.metadata = metadata;
} }
void Mod::setDetails(const ModDetails& details) {
m_local_details = details;
}
std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
{ {
auto cast_other = dynamic_cast<Mod const*>(&other); auto cast_other = dynamic_cast<Mod const*>(&other);
@ -91,6 +98,11 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
if (this_ver < other_ver) if (this_ver < other_ver)
return { -1, type == SortType::VERSION }; return { -1, type == SortType::VERSION };
} }
case SortType::PROVIDER: {
auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
if (compare_result != 0)
return { compare_result, type == SortType::PROVIDER };
}
} }
return { 0, false }; return { 0, false };
} }
@ -189,4 +201,16 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
m_local_details = std::move(details); m_local_details = std::move(details);
if (metadata) if (metadata)
setMetadata(std::move(metadata)); setMetadata(std::move(metadata));
};
auto Mod::provider() const -> std::optional<QString>
{
if (metadata())
return ProviderCaps.readableName(metadata()->provider);
return {};
}
bool Mod::valid() const
{
return !m_local_details.mod_id.isEmpty();
} }

View File

@ -39,6 +39,8 @@
#include <QFileInfo> #include <QFileInfo>
#include <QList> #include <QList>
#include <optional>
#include "Resource.h" #include "Resource.h"
#include "ModDetails.h" #include "ModDetails.h"
@ -61,6 +63,7 @@ public:
auto description() const -> QString; auto description() const -> QString;
auto authors() const -> QStringList; auto authors() const -> QStringList;
auto status() const -> ModStatus; auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>; auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>; auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
@ -68,6 +71,9 @@ public:
void setStatus(ModStatus status); void setStatus(ModStatus status);
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata); void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); } void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
void setDetails(const ModDetails& details);
bool valid() const override;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override; [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;

View File

@ -81,7 +81,7 @@ struct ModDetails
ModDetails() = default; ModDetails() = default;
/** Metadata should be handled manually to properly set the mod status. */ /** Metadata should be handled manually to properly set the mod status. */
ModDetails(ModDetails& other) ModDetails(const ModDetails& other)
: mod_id(other.mod_id) : mod_id(other.mod_id)
, name(other.name) , name(other.name)
, version(other.version) , version(other.version)
@ -92,7 +92,7 @@ struct ModDetails
, status(other.status) , status(other.status)
{} {}
ModDetails& operator=(ModDetails& other) ModDetails& operator=(const ModDetails& other)
{ {
this->mod_id = other.mod_id; this->mod_id = other.mod_id;
this->name = other.name; this->name = other.name;
@ -106,7 +106,7 @@ struct ModDetails
return *this; return *this;
} }
ModDetails& operator=(ModDetails&& other) ModDetails& operator=(const ModDetails&& other)
{ {
this->mod_id = other.mod_id; this->mod_id = other.mod_id;
this->name = other.name; this->name = other.name;

View File

@ -48,10 +48,11 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed) ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed)
{ {
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
} }
QVariant ModFolderModel::data(const QModelIndex &index, int role) const QVariant ModFolderModel::data(const QModelIndex &index, int role) const
@ -82,7 +83,15 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
} }
case DateColumn: case DateColumn:
return m_resources[row]->dateTimeChanged(); return m_resources[row]->dateTimeChanged();
case ProviderColumn: {
auto provider = at(row)->provider();
if (!provider.has_value()) {
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
return tr("Unknown");
}
return provider.value();
}
default: default:
return QVariant(); return QVariant();
} }
@ -118,6 +127,8 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
return tr("Version"); return tr("Version");
case DateColumn: case DateColumn:
return tr("Last changed"); return tr("Last changed");
case ProviderColumn:
return tr("Provider");
default: default:
return QVariant(); return QVariant();
} }
@ -133,6 +144,8 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
return tr("The version of the mod."); return tr("The version of the mod.");
case DateColumn: case DateColumn:
return tr("The date and time this mod was last changed (or added)."); return tr("The date and time this mod was last changed (or added).");
case ProviderColumn:
return tr("Where the mod was downloaded from.");
default: default:
return QVariant(); return QVariant();
} }

View File

@ -67,6 +67,7 @@ public:
NameColumn, NameColumn,
VersionColumn, VersionColumn,
DateColumn, DateColumn,
ProviderColumn,
NUM_COLUMNS NUM_COLUMNS
}; };
enum ModStatusAction { enum ModStatusAction {

View File

@ -143,5 +143,9 @@ bool Resource::enable(EnableAction action)
bool Resource::destroy() bool Resource::destroy()
{ {
m_type = ResourceType::UNKNOWN; m_type = ResourceType::UNKNOWN;
if (FS::trash(m_file_info.filePath()))
return true;
return FS::deletePath(m_file_info.filePath()); return FS::deletePath(m_file_info.filePath());
} }

View File

@ -20,7 +20,8 @@ enum class SortType {
DATE, DATE,
VERSION, VERSION,
ENABLED, ENABLED,
PACK_FORMAT PACK_FORMAT,
PROVIDER
}; };
enum class EnableAction { enum class EnableAction {

View File

@ -260,7 +260,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
return; return;
} }
auto task = createParseTask(*res); Task::Ptr task{ createParseTask(*res) };
if (!task) if (!task)
return; return;
@ -270,11 +270,11 @@ void ResourceFolderModel::resolveResource(Resource* res)
m_active_parse_tasks.insert(ticket, task); m_active_parse_tasks.insert(ticket, task);
connect( connect(
task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect( connect(
task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect( connect(
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection); task.get(), &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
m_helper_thread_task.addTask(task); m_helper_thread_task.addTask(task);

View File

@ -13,11 +13,12 @@
// Values taken from: // Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = { static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } }, { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("1.19.3"), Version("1.19.3") } }, { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } },
{ 12, { Version("1.19.3"), Version("1.19.3") } },
}; };
void ResourcePack::setPackFormat(int new_format_id) void ResourcePack::setPackFormat(int new_format_id)
@ -25,7 +26,7 @@ void ResourcePack::setPackFormat(int new_format_id)
QMutexLocker locker(&m_data_lock); QMutexLocker locker(&m_data_lock);
if (!s_pack_format_versions.contains(new_format_id)) { if (!s_pack_format_versions.contains(new_format_id)) {
qWarning() << "Pack format '%1' is not a recognized resource pack id!"; qWarning() << "Pack format '" << new_format_id << "' is not a recognized resource pack id!";
} }
m_pack_format = new_format_id; m_pack_format = new_format_id;

View File

@ -142,7 +142,7 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
Task* ResourcePackFolderModel::createUpdateTask() Task* ResourcePackFolderModel::createUpdateTask()
{ {
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new ResourcePack(entry); }); return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ResourcePack>(entry); });
} }
Task* ResourcePackFolderModel::createParseTask(Resource& resource) Task* ResourcePackFolderModel::createParseTask(Resource& resource)

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ShaderPack.h"
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
{
QMutexLocker locker(&m_data_lock);
m_pack_format = new_format;
}
bool ShaderPack::valid() const
{
return m_pack_format != ShaderPackFormat::INVALID;
}

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "Resource.h"
/* Info:
* Currently For Optifine / Iris shader packs,
* could be expanded to support others should they exist?
*
* This class and enum are mostly here as placeholders for validating
* that a shaderpack exists and is in the right format,
* namely that they contain a folder named 'shaders'.
*
* In the technical sense it would be possible to parse files like `shaders/shaders.properties`
* to get information like the available profiles but this is not all that useful without more knowledge of the
* shader mod used to be able to change settings.
*/
#include <QMutex>
enum class ShaderPackFormat { VALID, INVALID };
class ShaderPack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
[[nodiscard]] ShaderPackFormat packFormat() const { return m_pack_format; }
ShaderPack(QObject* parent = nullptr) : Resource(parent) {}
ShaderPack(QFileInfo file_info) : Resource(file_info) {}
/** Thread-safe. */
void setPackFormat(ShaderPackFormat new_format);
bool valid() const override;
protected:
mutable QMutex m_data_lock;
ShaderPackFormat m_pack_format = ShaderPackFormat::INVALID;
};

View File

@ -43,7 +43,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFol
Task* TexturePackFolderModel::createUpdateTask() Task* TexturePackFolderModel::createUpdateTask()
{ {
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new TexturePack(entry); }); return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<TexturePack>(entry); });
} }
Task* TexturePackFolderModel::createParseTask(Resource& resource) Task* TexturePackFolderModel::createParseTask(Resource& resource)

View File

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "WorldSave.h"
#include "minecraft/mod/tasks/LocalWorldSaveParseTask.h"
void WorldSave::setSaveFormat(WorldSaveFormat new_save_format)
{
QMutexLocker locker(&m_data_lock);
m_save_format = new_save_format;
}
void WorldSave::setSaveDirName(QString dir_name)
{
QMutexLocker locker(&m_data_lock);
m_save_dir_name = dir_name;
}
bool WorldSave::valid() const
{
return m_save_format != WorldSaveFormat::INVALID;
}

View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "Resource.h"
#include <QMutex>
class Version;
enum class WorldSaveFormat { SINGLE, MULTI, INVALID };
class WorldSave : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
WorldSave(QObject* parent = nullptr) : Resource(parent) {}
WorldSave(QFileInfo file_info) : Resource(file_info) {}
/** Gets the format of the save. */
[[nodiscard]] WorldSaveFormat saveFormat() const { return m_save_format; }
/** Gets the name of the save dir (first found in multi mode). */
[[nodiscard]] QString saveDirName() const { return m_save_dir_name; }
/** Thread-safe. */
void setSaveFormat(WorldSaveFormat new_save_format);
/** Thread-safe. */
void setSaveDirName(QString dir_name);
bool valid() const override;
protected:
mutable QMutex m_data_lock;
/** The format in which the save file is in.
* Since saves can be distributed in various slightly different ways, this allows us to treat them separately.
*/
WorldSaveFormat m_save_format = WorldSaveFormat::INVALID;
QString m_save_dir_name;
};

View File

@ -26,11 +26,11 @@ class BasicFolderLoadTask : public Task {
public: public:
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
{ {
m_create_func = [](QFileInfo const& entry) -> Resource* { m_create_func = [](QFileInfo const& entry) -> Resource::Ptr {
return new Resource(entry); return makeShared<Resource>(entry);
}; };
} }
BasicFolderLoadTask(QDir dir, std::function<Resource*(QFileInfo const&)> create_function) BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
: Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread()) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread())
{} {}
@ -65,7 +65,7 @@ private:
std::atomic<bool> m_aborted = false; std::atomic<bool> m_aborted = false;
std::function<Resource*(QFileInfo const&)> m_create_func; std::function<Resource::Ptr(QFileInfo const&)> m_create_func;
/** This is the thread in which we should put new mod objects */ /** This is the thread in which we should put new mod objects */
QThread* m_thread_to_spawn_into; QThread* m_thread_to_spawn_into;

View File

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LocalDataPackParseTask.h"
#include "FileSystem.h"
#include "Json.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include <QCryptographicHash>
namespace DataPackUtils {
bool process(DataPack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
return DataPackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
return DataPackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for data pack parse task!";
return false;
}
}
bool processFolder(DataPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return mcmeta_invalid(); // can't open mcmeta file
auto data = mcmeta_file.readAll();
bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
mcmeta_file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // mcmeta file isn't a valid file
}
QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data"));
if (!data_dir_info.exists() || !data_dir_info.isDir()) {
return false; // data dir does not exists or isn't valid
}
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
return true; // all tests passed
}
bool processZIP(DataPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return mcmeta_invalid();
}
auto data = file.readAll();
bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
}
QuaZipDir zipDir(&zip);
if (!zipDir.exists("/data")) {
return false; // data dir does not exists at zip root
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return true; // only need basic info already checked
}
zip.close();
return true;
}
// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
{
try {
auto json_doc = QJsonDocument::fromJson(raw_data);
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
return false;
}
return true;
}
bool validate(QFileInfo file)
{
DataPack dp{ file };
return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
}
} // namespace DataPackUtils
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {}
bool LocalDataPackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalDataPackParseTask::executeTask()
{
if (!DataPackUtils::process(m_data_pack))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDebug>
#include <QObject>
#include "minecraft/mod/DataPack.h"
#include "tasks/Task.h"
namespace DataPackUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processMCMeta(DataPack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a data pack or not. */
bool validate(QFileInfo file);
} // namespace DataPackUtils
class LocalDataPackParseTask : public Task {
Q_OBJECT
public:
LocalDataPackParseTask(int token, DataPack& dp);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
int m_token;
DataPack& m_data_pack;
bool m_aborted = false;
};

View File

@ -11,12 +11,13 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "minecraft/mod/ModDetails.h"
#include "settings/INIFile.h" #include "settings/INIFile.h"
namespace { namespace ModUtils {
// NEW format // NEW format
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/c8d8f1929aff9979e322af79a59ce81f3e02db6a
// OLD format: // OLD format:
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
@ -73,10 +74,11 @@ ModDetails ReadMCModInfo(QByteArray contents)
version = Json::ensureString(val, "").toInt(); version = Json::ensureString(val, "").toInt();
if (version != 2) { if (version != 2) {
qCritical() << "BAD stuff happened to mod json:"; qWarning() << QString(R"(The value of 'modListVersion' is "%1" (expected "2")! The file may be corrupted.)").arg(version);
qCritical() << contents; qWarning() << "The contents of 'mcmod.info' are as follows:";
return {}; qWarning() << contents;
} }
auto arrVal = jsonDoc.object().value("modlist"); auto arrVal = jsonDoc.object().value("modlist");
if (arrVal.isUndefined()) { if (arrVal.isUndefined()) {
arrVal = jsonDoc.object().value("modList"); arrVal = jsonDoc.object().value("modList");
@ -283,35 +285,46 @@ ModDetails ReadLiteModInfo(QByteArray contents)
return details; return details;
} }
} // namespace bool process(Mod& mod, ProcessingLevel level)
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
{}
void LocalModParseTask::processAsZip()
{ {
QuaZip zip(m_modFile.filePath()); switch (mod.type()) {
case ResourceType::FOLDER:
return processFolder(mod, level);
case ResourceType::ZIPFILE:
return processZIP(mod, level);
case ResourceType::LITEMOD:
return processLitemod(mod);
default:
qWarning() << "Invalid type for mod parse task!";
return false;
}
}
bool processZIP(Mod& mod, ProcessingLevel level)
{
ModDetails details;
QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return; return false;
QuaZipFile file(&zip); QuaZipFile file(&zip);
if (zip.setCurrentFile("META-INF/mods.toml")) { if (zip.setCurrentFile("META-INF/mods.toml")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
m_result->details = ReadMCModTOML(file.readAll()); details = ReadMCModTOML(file.readAll());
file.close(); file.close();
// to replace ${file.jarVersion} with the actual version, as needed // to replace ${file.jarVersion} with the actual version, as needed
if (m_result->details.version == "${file.jarVersion}") { if (details.version == "${file.jarVersion}") {
if (zip.setCurrentFile("META-INF/MANIFEST.MF")) { if (zip.setCurrentFile("META-INF/MANIFEST.MF")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
// quick and dirty line-by-line parser // quick and dirty line-by-line parser
@ -330,93 +343,131 @@ void LocalModParseTask::processAsZip()
manifestVersion = "NONE"; manifestVersion = "NONE";
} }
m_result->details.version = manifestVersion; details.version = manifestVersion;
file.close(); file.close();
} }
} }
zip.close(); zip.close();
return; mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("mcmod.info")) { } else if (zip.setCurrentFile("mcmod.info")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
m_result->details = ReadMCModInfo(file.readAll()); details = ReadMCModInfo(file.readAll());
file.close(); file.close();
zip.close(); zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("quilt.mod.json")) { } else if (zip.setCurrentFile("quilt.mod.json")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
m_result->details = ReadQuiltModInfo(file.readAll()); details = ReadQuiltModInfo(file.readAll());
file.close(); file.close();
zip.close(); zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("fabric.mod.json")) { } else if (zip.setCurrentFile("fabric.mod.json")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
m_result->details = ReadFabricModInfo(file.readAll()); details = ReadFabricModInfo(file.readAll());
file.close(); file.close();
zip.close(); zip.close();
return;
mod.setDetails(details);
return true;
} else if (zip.setCurrentFile("forgeversion.properties")) { } else if (zip.setCurrentFile("forgeversion.properties")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
m_result->details = ReadForgeInfo(file.readAll()); details = ReadForgeInfo(file.readAll());
file.close(); file.close();
zip.close(); zip.close();
return;
mod.setDetails(details);
return true;
} }
zip.close(); zip.close();
return false; // no valid mod found in archive
} }
void LocalModParseTask::processAsFolder() bool processFolder(Mod& mod, ProcessingLevel level)
{ {
QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); ModDetails details;
if (mcmod_info.isFile()) {
QFileInfo mcmod_info(FS::PathCombine(mod.fileinfo().filePath(), "mcmod.info"));
if (mcmod_info.exists() && mcmod_info.isFile()) {
QFile mcmod(mcmod_info.filePath()); QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly)) if (!mcmod.open(QIODevice::ReadOnly))
return; return false;
auto data = mcmod.readAll(); auto data = mcmod.readAll();
if (data.isEmpty() || data.isNull()) if (data.isEmpty() || data.isNull())
return; return false;
m_result->details = ReadMCModInfo(data); details = ReadMCModInfo(data);
mod.setDetails(details);
return true;
} }
return false; // no valid mcmod.info file found
} }
void LocalModParseTask::processAsLitemod() bool processLitemod(Mod& mod, ProcessingLevel level)
{ {
QuaZip zip(m_modFile.filePath()); ModDetails details;
QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return; return false;
QuaZipFile file(&zip); QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json")) { if (zip.setCurrentFile("litemod.json")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
zip.close(); zip.close();
return; return false;
} }
m_result->details = ReadLiteModInfo(file.readAll()); details = ReadLiteModInfo(file.readAll());
file.close(); file.close();
mod.setDetails(details);
return true;
} }
zip.close(); zip.close();
return false; // no valid litemod.json found in archive
} }
/** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file)
{
Mod mod{ file };
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
}
} // namespace ModUtils
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
{}
bool LocalModParseTask::abort() bool LocalModParseTask::abort()
{ {
m_aborted.store(true); m_aborted.store(true);
@ -425,19 +476,10 @@ bool LocalModParseTask::abort()
void LocalModParseTask::executeTask() void LocalModParseTask::executeTask()
{ {
switch (m_type) { Mod mod{ m_modFile };
case ResourceType::ZIPFILE: ModUtils::process(mod, ModUtils::ProcessingLevel::Full);
processAsZip();
break; m_result->details = mod.details();
case ResourceType::FOLDER:
processAsFolder();
break;
case ResourceType::LITEMOD:
processAsLitemod();
break;
default:
break;
}
if (m_aborted) if (m_aborted)
emit finished(); emit finished();

View File

@ -8,32 +8,48 @@
#include "tasks/Task.h" #include "tasks/Task.h"
class LocalModParseTask : public Task namespace ModUtils {
{
ModDetails ReadFabricModInfo(QByteArray contents);
ModDetails ReadQuiltModInfo(QByteArray contents);
ModDetails ReadForgeInfo(QByteArray contents);
ModDetails ReadLiteModInfo(QByteArray contents);
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file);
} // namespace ModUtils
class LocalModParseTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
struct Result { struct Result {
ModDetails details; ModDetails details;
}; };
using ResultPtr = std::shared_ptr<Result>; using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const { ResultPtr result() const { return m_result; }
return m_result;
}
[[nodiscard]] bool canAbort() const override { return true; } [[nodiscard]] bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile); LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile);
void executeTask() override; void executeTask() override;
[[nodiscard]] int token() const { return m_token; } [[nodiscard]] int token() const { return m_token; }
private: private:
void processAsZip(); void processAsZip();
void processAsFolder(); void processAsFolder();
void processAsLitemod(); void processAsLitemod();
private: private:
int m_token; int m_token;
ResourceType m_type; ResourceType m_type;
QFileInfo m_modFile; QFileInfo m_modFile;

View File

@ -22,6 +22,7 @@
#include "Json.h" #include "Json.h"
#include <quazip/quazip.h> #include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h> #include <quazip/quazipfile.h>
#include <QCryptographicHash> #include <QCryptographicHash>
@ -32,99 +33,152 @@ bool process(ResourcePack& pack, ProcessingLevel level)
{ {
switch (pack.type()) { switch (pack.type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
ResourcePackUtils::processFolder(pack, level); return ResourcePackUtils::processFolder(pack, level);
return true;
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
ResourcePackUtils::processZIP(pack, level); return ResourcePackUtils::processZIP(pack, level);
return true;
default: default:
qWarning() << "Invalid type for resource pack parse task!"; qWarning() << "Invalid type for resource pack parse task!";
return false; return false;
} }
} }
void processFolder(ResourcePack& pack, ProcessingLevel level) bool processFolder(ResourcePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::FOLDER); Q_ASSERT(pack.type() == ResourceType::FOLDER);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
if (mcmeta_file_info.isFile()) { if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath()); QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return mcmeta_invalid(); // can't open mcmeta file
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
ResourcePackUtils::processMCMeta(pack, std::move(data)); bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // mcmeta file isn't a valid file
} }
if (level == ProcessingLevel::BasicInfoOnly) QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
return; if (!assets_dir_info.exists() || !assets_dir_info.isDir()) {
return false; // assets dir does not exists or isn't valid
}
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
auto png_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return true; // the png is optional
};
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) { if (image_file_info.exists() && image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath()); QFile pack_png_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!pack_png_file.open(QIODevice::ReadOnly))
return; return png_invalid(); // can't open pack.png file
auto data = mcmeta_file.readAll(); auto data = pack_png_file.readAll();
ResourcePackUtils::processPackPNG(pack, std::move(data)); bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close(); pack_png_file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // pack.png does not exists or is not a valid file.
} }
return true; // all tests passed
} }
void processZIP(ResourcePack& pack, ProcessingLevel level) bool processZIP(ResourcePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::ZIPFILE); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath()); QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return; return false; // can't open zip file
QuaZipFile file(&zip); QuaZipFile file(&zip);
auto mcmeta_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
if (zip.setCurrentFile("pack.mcmeta")) { if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return mcmeta_invalid();
} }
auto data = file.readAll(); auto data = file.readAll();
ResourcePackUtils::processMCMeta(pack, std::move(data)); bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
file.close(); file.close();
if (!mcmeta_result) {
return mcmeta_invalid(); // mcmeta invalid
}
} else {
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
}
QuaZipDir zipDir(&zip);
if (!zipDir.exists("/assets")) {
return false; // assets dir does not exists at zip root
} }
if (level == ProcessingLevel::BasicInfoOnly) { if (level == ProcessingLevel::BasicInfoOnly) {
zip.close(); zip.close();
return; return true; // only need basic info already checked
} }
auto png_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return true; // the png is optional
};
if (zip.setCurrentFile("pack.png")) { if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return png_invalid();
} }
auto data = file.readAll(); auto data = file.readAll();
ResourcePackUtils::processPackPNG(pack, std::move(data)); bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // could not set pack.mcmeta as current file.
} }
zip.close(); zip.close();
return true;
} }
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data) bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{ {
try { try {
auto json_doc = QJsonDocument::fromJson(raw_data); auto json_doc = QJsonDocument::fromJson(raw_data);
@ -134,17 +188,21 @@ void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
pack.setDescription(Json::ensureString(pack_obj, "description", "")); pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause(); qWarning() << "JsonException: " << e.what() << e.cause();
return false;
} }
return true;
} }
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data) bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
{ {
auto img = QImage::fromData(raw_data); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
pack.setImage(img); pack.setImage(img);
} else { } else {
qWarning() << "Failed to parse pack.png."; qWarning() << "Failed to parse pack.png.";
return false;
} }
return true;
} }
bool validate(QFileInfo file) bool validate(QFileInfo file)

View File

@ -31,11 +31,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a resource pack or not. */ /** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QObject>
#include "LocalResourceParse.h"
#include "LocalDataPackParseTask.h"
#include "LocalModParseTask.h"
#include "LocalResourcePackParseTask.h"
#include "LocalShaderPackParseTask.h"
#include "LocalTexturePackParseTask.h"
#include "LocalWorldSaveParseTask.h"
static const QMap<PackedResourceType, QString> s_packed_type_names = {
{PackedResourceType::ResourcePack, QObject::tr("resource pack")},
{PackedResourceType::TexturePack, QObject::tr("texture pack")},
{PackedResourceType::DataPack, QObject::tr("data pack")},
{PackedResourceType::ShaderPack, QObject::tr("shader pack")},
{PackedResourceType::WorldSave, QObject::tr("world save")},
{PackedResourceType::Mod , QObject::tr("mod")},
{PackedResourceType::UNKNOWN, QObject::tr("unknown")}
};
namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){
if (file.exists() && file.isFile()) {
if (ResourcePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a resource pack";
return PackedResourceType::ResourcePack;
} else if (TexturePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a pre 1.6 texture pack";
return PackedResourceType::TexturePack;
} else if (DataPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a data pack";
return PackedResourceType::DataPack;
} else if (ModUtils::validate(file)) {
qDebug() << file.fileName() << "is a mod";
return PackedResourceType::Mod;
} else if (WorldSaveUtils::validate(file)) {
qDebug() << file.fileName() << "is a world save";
return PackedResourceType::WorldSave;
} else if (ShaderPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a shader pack";
return PackedResourceType::ShaderPack;
} else {
qDebug() << "Can't Identify" << file.fileName() ;
}
} else {
qDebug() << "Can't find" << file.absolutePath();
}
return PackedResourceType::UNKNOWN;
}
QString getPackedTypeName(PackedResourceType type) {
return s_packed_type_names.constFind(type).value();
}
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <set>
#include <QDebug>
#include <QFileInfo>
#include <QObject>
enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN };
namespace ResourceUtils {
static const std::set<PackedResourceType> ValidResourceTypes = { PackedResourceType::DataPack, PackedResourceType::ResourcePack,
PackedResourceType::TexturePack, PackedResourceType::ShaderPack,
PackedResourceType::WorldSave, PackedResourceType::Mod };
PackedResourceType identify(QFileInfo file);
QString getPackedTypeName(PackedResourceType type);
} // namespace ResourceUtils

View File

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LocalShaderPackParseTask.h"
#include "FileSystem.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
namespace ShaderPackUtils {
bool process(ShaderPack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
return ShaderPackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
return ShaderPackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for shader pack parse task!";
return false;
}
}
bool processFolder(ShaderPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
QFileInfo shaders_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "shaders"));
if (!shaders_dir_info.exists() || !shaders_dir_info.isDir()) {
return false; // assets dir does not exists or isn't valid
}
pack.setPackFormat(ShaderPackFormat::VALID);
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
return true; // all tests passed
}
bool processZIP(ShaderPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
QuaZipDir zipDir(&zip);
if (!zipDir.exists("/shaders")) {
return false; // assets dir does not exists at zip root
}
pack.setPackFormat(ShaderPackFormat::VALID);
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return true; // only need basic info already checked
}
zip.close();
return true;
}
bool validate(QFileInfo file)
{
ShaderPack sp{ file };
return ShaderPackUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
}
} // namespace ShaderPackUtils
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {}
bool LocalShaderPackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalShaderPackParseTask::executeTask()
{
if (!ShaderPackUtils::process(m_shader_pack))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDebug>
#include <QObject>
#include "minecraft/mod/ShaderPack.h"
#include "tasks/Task.h"
namespace ShaderPackUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a shader pack or not. */
bool validate(QFileInfo file);
} // namespace ShaderPackUtils
class LocalShaderPackParseTask : public Task {
Q_OBJECT
public:
LocalShaderPackParseTask(int token, ShaderPack& sp);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
int m_token;
ShaderPack& m_shader_pack;
bool m_aborted = false;
};

View File

@ -32,18 +32,16 @@ bool process(TexturePack& pack, ProcessingLevel level)
{ {
switch (pack.type()) { switch (pack.type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
TexturePackUtils::processFolder(pack, level); return TexturePackUtils::processFolder(pack, level);
return true;
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
TexturePackUtils::processZIP(pack, level); return TexturePackUtils::processZIP(pack, level);
return true;
default: default:
qWarning() << "Invalid type for resource pack parse task!"; qWarning() << "Invalid type for resource pack parse task!";
return false; return false;
} }
} }
void processFolder(TexturePack& pack, ProcessingLevel level) bool processFolder(TexturePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::FOLDER); Q_ASSERT(pack.type() == ResourceType::FOLDER);
@ -51,39 +49,51 @@ void processFolder(TexturePack& pack, ProcessingLevel level)
if (mcmeta_file_info.isFile()) { if (mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath()); QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return false;
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data)); bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!packTXT_result) {
return false;
}
} else {
return false;
} }
if (level == ProcessingLevel::BasicInfoOnly) if (level == ProcessingLevel::BasicInfoOnly)
return; return true;
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) { if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath()); QFile mcmeta_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return false;
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data)); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!packPNG_result) {
return false;
}
} else {
return false;
} }
return true;
} }
void processZIP(TexturePack& pack, ProcessingLevel level) bool processZIP(TexturePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::ZIPFILE); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath()); QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return; return false;
QuaZipFile file(&zip); QuaZipFile file(&zip);
@ -91,51 +101,62 @@ void processZIP(TexturePack& pack, ProcessingLevel level)
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return false;
} }
auto data = file.readAll(); auto data = file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data)); bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
file.close(); file.close();
if (!packTXT_result) {
return false;
}
} }
if (level == ProcessingLevel::BasicInfoOnly) { if (level == ProcessingLevel::BasicInfoOnly) {
zip.close(); zip.close();
return; return true;
} }
if (zip.setCurrentFile("pack.png")) { if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return false;
} }
auto data = file.readAll(); auto data = file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data)); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
if (!packPNG_result) {
return false;
}
} }
zip.close(); zip.close();
return true;
} }
void processPackTXT(TexturePack& pack, QByteArray&& raw_data) bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
{ {
pack.setDescription(QString(raw_data)); pack.setDescription(QString(raw_data));
return true;
} }
void processPackPNG(TexturePack& pack, QByteArray&& raw_data) bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
{ {
auto img = QImage::fromData(raw_data); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
pack.setImage(img); pack.setImage(img);
} else { } else {
qWarning() << "Failed to parse pack.png."; qWarning() << "Failed to parse pack.png.";
return false;
} }
return true;
} }
bool validate(QFileInfo file) bool validate(QFileInfo file)

View File

@ -32,11 +32,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processPackTXT(TexturePack& pack, QByteArray&& raw_data); bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
void processPackPNG(TexturePack& pack, QByteArray&& raw_data); bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a texture pack or not. */ /** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -0,0 +1,190 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LocalWorldSaveParseTask.h"
#include "FileSystem.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include <QDir>
#include <QFileInfo>
namespace WorldSaveUtils {
bool process(WorldSave& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
return WorldSaveUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
return WorldSaveUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for world save parse task!";
return false;
}
}
/// @brief checks a folder structure to see if it contains a level.dat
/// @param dir the path to check
/// @param saves used in recursive call if a "saves" dir was found
/// @return std::tuple of (
/// bool <found level.dat>,
/// QString <name of folder containing level.dat>,
/// bool <saves folder found>
/// )
static std::tuple<bool, QString, bool> contains_level_dat(QDir dir, bool saves = false)
{
for (auto const& entry : dir.entryInfoList()) {
if (!entry.isDir()) {
continue;
}
if (!saves && entry.fileName() == "saves") {
return contains_level_dat(QDir(entry.filePath()), true);
}
QFileInfo level_dat(FS::PathCombine(entry.filePath(), "level.dat"));
if (level_dat.exists() && level_dat.isFile()) {
return std::make_tuple(true, entry.fileName(), saves);
}
}
return std::make_tuple(false, "", saves);
}
bool processFolder(WorldSave& save, ProcessingLevel level)
{
Q_ASSERT(save.type() == ResourceType::FOLDER);
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(QDir(save.fileinfo().filePath()));
if (!found) {
return false;
}
save.setSaveDirName(save_dir_name);
if (found_saves_dir) {
save.setSaveFormat(WorldSaveFormat::MULTI);
} else {
save.setSaveFormat(WorldSaveFormat::SINGLE);
}
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
// reserved for more intensive processing
return true; // all tests passed
}
/// @brief checks a folder structure to see if it contains a level.dat
/// @param zip the zip file to check
/// @return std::tuple of (
/// bool <found level.dat>,
/// QString <name of folder containing level.dat>,
/// bool <saves folder found>
/// )
static std::tuple<bool, QString, bool> contains_level_dat(QuaZip& zip)
{
bool saves = false;
QuaZipDir zipDir(&zip);
if (zipDir.exists("/saves")) {
saves = true;
zipDir.cd("/saves");
}
for (auto const& entry : zipDir.entryList()) {
zipDir.cd(entry);
if (zipDir.exists("level.dat")) {
return std::make_tuple(true, entry, saves);
}
zipDir.cd("..");
}
return std::make_tuple(false, "", saves);
}
bool processZIP(WorldSave& save, ProcessingLevel level)
{
Q_ASSERT(save.type() == ResourceType::ZIPFILE);
QuaZip zip(save.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip);
if (save_dir_name.endsWith("/")) {
save_dir_name.chop(1);
}
if (!found) {
return false;
}
save.setSaveDirName(save_dir_name);
if (found_saves_dir) {
save.setSaveFormat(WorldSaveFormat::MULTI);
} else {
save.setSaveFormat(WorldSaveFormat::SINGLE);
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return true; // only need basic info already checked
}
// reserved for more intensive processing
zip.close();
return true;
}
bool validate(QFileInfo file)
{
WorldSave sp{ file };
return WorldSaveUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
}
} // namespace WorldSaveUtils
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {}
bool LocalWorldSaveParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalWorldSaveParseTask::executeTask()
{
if (!WorldSaveUtils::process(m_save))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDebug>
#include <QObject>
#include "minecraft/mod/WorldSave.h"
#include "tasks/Task.h"
namespace WorldSaveUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(WorldSave& save, ProcessingLevel level = ProcessingLevel::Full);
bool processZIP(WorldSave& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processFolder(WorldSave& pack, ProcessingLevel level = ProcessingLevel::Full);
bool validate(QFileInfo file);
} // namespace WorldSaveUtils
class LocalWorldSaveParseTask : public Task {
Q_OBJECT
public:
LocalWorldSaveParseTask(int token, WorldSave& save);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
int m_token;
WorldSave& m_save;
bool m_aborted = false;
};

View File

@ -72,14 +72,14 @@ void ModFolderLoadTask::executeTask()
delete mod; delete mod;
} }
else { else {
m_result->mods[mod->internal_id()] = mod; m_result->mods[mod->internal_id()].reset(std::move(mod));
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
} }
} }
else { else {
QString chopped_id = mod->internal_id().chopped(9); QString chopped_id = mod->internal_id().chopped(9);
if (m_result->mods.contains(chopped_id)) { if (m_result->mods.contains(chopped_id)) {
m_result->mods[mod->internal_id()] = mod; m_result->mods[mod->internal_id()].reset(std::move(mod));
auto metadata = m_result->mods[chopped_id]->metadata(); auto metadata = m_result->mods[chopped_id]->metadata();
if (metadata) { if (metadata) {
@ -90,7 +90,7 @@ void ModFolderLoadTask::executeTask()
} }
} }
else { else {
m_result->mods[mod->internal_id()] = mod; m_result->mods[mod->internal_id()].reset(std::move(mod));
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
} }
} }
@ -130,6 +130,6 @@ void ModFolderLoadTask::getFromMetadata()
auto* mod = new Mod(m_mods_dir, metadata); auto* mod = new Mod(m_mods_dir, metadata);
mod->setStatus(ModStatus::NotInstalled); mod->setStatus(ModStatus::NotInstalled);
m_result->mods[mod->internal_id()] = mod; m_result->mods[mod->internal_id()].reset(std::move(mod));
} }
} }

View File

@ -24,7 +24,7 @@ void AssetUpdateTask::executeTask()
auto assets = profile->getMinecraftAssets(); auto assets = profile->getMinecraftAssets();
QUrl indexUrl = assets->url; QUrl indexUrl = assets->url;
QString localPath = assets->id + ".json"; QString localPath = assets->id + ".json";
auto job = new NetJob( auto job = makeShared<NetJob>(
tr("Asset index for %1").arg(m_inst->name()), tr("Asset index for %1").arg(m_inst->name()),
APPLICATION->network() APPLICATION->network()
); );

View File

@ -61,7 +61,7 @@ void FMLLibrariesTask::executeTask()
// download missing libs to our place // download missing libs to our place
setStatus(tr("Downloading FML libraries...")); setStatus(tr("Downloading FML libraries..."));
auto dljob = new NetJob("FML libraries", APPLICATION->network()); NetJob::Ptr dljob{ new NetJob("FML libraries", APPLICATION->network()) };
auto metacache = APPLICATION->metacache(); auto metacache = APPLICATION->metacache();
Net::Download::Options options = Net::Download::Option::MakeEternal; Net::Download::Options options = Net::Download::Option::MakeEternal;
for (auto &lib : fmlLibsToProcess) for (auto &lib : fmlLibsToProcess)
@ -71,10 +71,10 @@ void FMLLibrariesTask::executeTask()
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options)); dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
} }
connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
connect(dljob, &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
downloadJob.reset(dljob); downloadJob.reset(dljob);
downloadJob->start(); downloadJob->start();
} }

View File

@ -20,7 +20,7 @@ void LibrariesTask::executeTask()
auto components = inst->getPackProfile(); auto components = inst->getPackProfile();
auto profile = components->getProfile(); auto profile = components->getProfile();
auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()), APPLICATION->network()); NetJob::Ptr job{ new NetJob(tr("Libraries for instance %1").arg(inst->name()), APPLICATION->network()) };
downloadJob.reset(job); downloadJob.reset(job);
auto metacache = APPLICATION->metacache(); auto metacache = APPLICATION->metacache();

View File

@ -1,18 +1,18 @@
#pragma once #pragma once
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "modplatform/ModAPI.h" #include "modplatform/ResourceAPI.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "tasks/Task.h" #include "tasks/Task.h"
class ModDownloadTask; class ResourceDownloadTask;
class ModFolderModel; class ModFolderModel;
class CheckUpdateTask : public Task { class CheckUpdateTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder) CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {};
struct UpdatableMod { struct UpdatableMod {
@ -21,11 +21,11 @@ class CheckUpdateTask : public Task {
QString old_version; QString old_version;
QString new_version; QString new_version;
QString changelog; QString changelog;
ModPlatform::Provider provider; ModPlatform::ResourceProvider provider;
ModDownloadTask* download; shared_qobject_ptr<ResourceDownloadTask> download;
public: public:
UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t) UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr<ResourceDownloadTask> t)
: name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t)
{} {}
}; };
@ -44,7 +44,7 @@ class CheckUpdateTask : public Task {
protected: protected:
QList<Mod*>& m_mods; QList<Mod*>& m_mods;
std::list<Version>& m_game_versions; std::list<Version>& m_game_versions;
ModAPI::ModLoaderTypes m_loaders; std::optional<ResourceAPI::ModLoaderTypes> m_loaders;
std::shared_ptr<ModFolderModel> m_mods_folder; std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable; std::vector<UpdatableMod> m_updatable;

View File

@ -13,14 +13,12 @@
#include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h" #include "modplatform/modrinth/ModrinthPackIndex.h"
#include "net/NetJob.h"
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
static ModrinthAPI modrinth_api; static ModrinthAPI modrinth_api;
static FlameAPI flame_api; static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr)
{ {
auto hash_task = createNewHash(mod); auto hash_task = createNewHash(mod);
@ -31,10 +29,10 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider
hash_task->start(); hash_task->start();
} }
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::Provider prov) EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{ {
m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10));
for (auto* mod : mods) { for (auto* mod : mods) {
auto hash_task = createNewHash(mod); auto hash_task = createNewHash(mod);
if (!hash_task) if (!hash_task)
@ -107,13 +105,13 @@ void EnsureMetadataTask::executeTask()
} }
} }
NetJob::Ptr version_task; Task::Ptr version_task;
switch (m_provider) { switch (m_provider) {
case (ModPlatform::Provider::MODRINTH): case (ModPlatform::ResourceProvider::MODRINTH):
version_task = modrinthVersionsTask(); version_task = modrinthVersionsTask();
break; break;
case (ModPlatform::Provider::FLAME): case (ModPlatform::ResourceProvider::FLAME):
version_task = flameVersionsTask(); version_task = flameVersionsTask();
break; break;
} }
@ -127,13 +125,13 @@ void EnsureMetadataTask::executeTask()
}; };
connect(version_task.get(), &Task::finished, this, [this, invalidade_leftover] { connect(version_task.get(), &Task::finished, this, [this, invalidade_leftover] {
NetJob::Ptr project_task; Task::Ptr project_task;
switch (m_provider) { switch (m_provider) {
case (ModPlatform::Provider::MODRINTH): case (ModPlatform::ResourceProvider::MODRINTH):
project_task = modrinthProjectsTask(); project_task = modrinthProjectsTask();
break; break;
case (ModPlatform::Provider::FLAME): case (ModPlatform::ResourceProvider::FLAME):
project_task = flameProjectsTask(); project_task = flameProjectsTask();
break; break;
} }
@ -149,7 +147,7 @@ void EnsureMetadataTask::executeTask()
m_current_task = nullptr; m_current_task = nullptr;
}); });
m_current_task = project_task.get(); m_current_task = project_task;
project_task->start(); project_task->start();
}); });
@ -164,7 +162,7 @@ void EnsureMetadataTask::executeTask()
setStatus(tr("Requesting metadata information from %1 for '%2'...") setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name())); .arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name()));
m_current_task = version_task.get(); m_current_task = version_task;
version_task->start(); version_task->start();
} }
@ -210,18 +208,18 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
// Modrinth // Modrinth
NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
{ {
auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto* response = new QByteArray(); auto* response = new QByteArray();
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!ver_task) if (!ver_task)
return {}; return Task::Ptr{nullptr};
connect(ver_task.get(), &NetJob::succeeded, this, [this, response] { connect(ver_task.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
@ -260,14 +258,14 @@ NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask()
return ver_task; return ver_task;
} }
NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
{ {
QHash<QString, QString> addonIds; QHash<QString, QString> addonIds;
for (auto const& data : m_temp_versions) for (auto const& data : m_temp_versions)
addonIds.insert(data.addonId.toString(), data.hash); addonIds.insert(data.addonId.toString(), data.hash);
auto response = new QByteArray(); auto response = new QByteArray();
NetJob::Ptr proj_task; Task::Ptr proj_task;
if (addonIds.isEmpty()) { if (addonIds.isEmpty()) {
qWarning() << "No addonId found!"; qWarning() << "No addonId found!";
@ -279,9 +277,9 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask()
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!proj_task) if (!proj_task)
return {}; return Task::Ptr{nullptr};
connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] { connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error); auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
@ -291,51 +289,61 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask()
return; return;
} }
QJsonArray entries;
try { try {
QJsonArray entries;
if (addonIds.size() == 1) if (addonIds.size() == 1)
entries = { doc.object() }; entries = { doc.object() };
else else
entries = Json::requireArray(doc); entries = Json::requireArray(doc);
for (auto entry : entries) {
auto entry_obj = Json::requireObject(entry);
ModPlatform::IndexedPack pack;
Modrinth::loadIndexedPack(pack, entry_obj);
auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* mod = mod_iter.value();
try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
}
}
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qDebug() << e.cause(); qDebug() << e.cause();
qDebug() << doc; qDebug() << doc;
} }
for (auto entry : entries) {
ModPlatform::IndexedPack pack;
try {
auto entry_obj = Json::requireObject(entry);
Modrinth::loadIndexedPack(pack, entry_obj);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << doc;
// Skip this entry, since it has problems
continue;
}
auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* mod = mod_iter.value();
try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
}
}
}); });
return proj_task; return proj_task;
} }
// Flame // Flame
NetJob::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameVersionsTask()
{ {
auto* response = new QByteArray(); auto* response = new QByteArray();
@ -400,7 +408,7 @@ NetJob::Ptr EnsureMetadataTask::flameVersionsTask()
return ver_task; return ver_task;
} }
NetJob::Ptr EnsureMetadataTask::flameProjectsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask()
{ {
QHash<QString, QString> addonIds; QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) { for (auto const& hash : m_mods.keys()) {
@ -414,7 +422,7 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask()
} }
auto response = new QByteArray(); auto response = new QByteArray();
NetJob::Ptr proj_task; Task::Ptr proj_task;
if (addonIds.isEmpty()) { if (addonIds.isEmpty()) {
qWarning() << "No addonId found!"; qWarning() << "No addonId found!";
@ -426,9 +434,9 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask()
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!proj_task) if (!proj_task)
return {}; return Task::Ptr{nullptr};
connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] { connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error); auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {

View File

@ -14,8 +14,8 @@ class EnsureMetadataTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
EnsureMetadataTask(Mod*, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
~EnsureMetadataTask() = default; ~EnsureMetadataTask() = default;
@ -28,11 +28,11 @@ class EnsureMetadataTask : public Task {
private: private:
// FIXME: Move to their own namespace // FIXME: Move to their own namespace
auto modrinthVersionsTask() -> NetJob::Ptr; auto modrinthVersionsTask() -> Task::Ptr;
auto modrinthProjectsTask() -> NetJob::Ptr; auto modrinthProjectsTask() -> Task::Ptr;
auto flameVersionsTask() -> NetJob::Ptr; auto flameVersionsTask() -> Task::Ptr;
auto flameProjectsTask() -> NetJob::Ptr; auto flameProjectsTask() -> Task::Ptr;
// Helpers // Helpers
enum class RemoveFromList { enum class RemoveFromList {
@ -57,9 +57,9 @@ class EnsureMetadataTask : public Task {
private: private:
QHash<QString, Mod*> m_mods; QHash<QString, Mod*> m_mods;
QDir m_index_dir; QDir m_index_dir;
ModPlatform::Provider m_provider; ModPlatform::ResourceProvider m_provider;
QHash<QString, ModPlatform::IndexedVersion> m_temp_versions; QHash<QString, ModPlatform::IndexedVersion> m_temp_versions;
ConcurrentTask* m_hashing_task; ConcurrentTask::Ptr m_hashing_task;
NetJob* m_current_task; Task::Ptr m_current_task;
}; };

View File

@ -1,118 +0,0 @@
// 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.
*/
#pragma once
#include <QString>
#include <QList>
#include <list>
#include "../Version.h"
#include "net/NetJob.h"
namespace ModPlatform {
class ListModel;
struct IndexedPack;
}
class ModAPI {
protected:
using CallerType = ModPlatform::ListModel;
public:
virtual ~ModAPI() = default;
enum ModLoaderType {
Unspecified = 0,
Forge = 1 << 0,
Cauldron = 1 << 1,
LiteLoader = 1 << 2,
Fabric = 1 << 3,
Quilt = 1 << 4
};
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SearchArgs {
int offset;
QString search;
QString sorting;
ModLoaderTypes loaders;
std::list<Version> versions;
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) = 0;
virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0;
virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0;
struct VersionSearchArgs {
QString addonId;
std::list<Version> mcVersions;
ModLoaderTypes loaders;
};
virtual void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const = 0;
static auto getModLoaderString(ModLoaderType type) -> const QString {
switch (type) {
case Unspecified:
break;
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
}
return "";
}
protected:
inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString
{
QString s;
for(auto& ver : mcVersions){
s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s;
}
};

View File

@ -24,47 +24,47 @@
namespace ModPlatform { namespace ModPlatform {
auto ProviderCapabilities::name(Provider p) -> const char* auto ProviderCapabilities::name(ResourceProvider p) -> const char*
{ {
switch (p) { switch (p) {
case Provider::MODRINTH: case ResourceProvider::MODRINTH:
return "modrinth"; return "modrinth";
case Provider::FLAME: case ResourceProvider::FLAME:
return "curseforge"; return "curseforge";
} }
return {}; return {};
} }
auto ProviderCapabilities::readableName(Provider p) -> QString auto ProviderCapabilities::readableName(ResourceProvider p) -> QString
{ {
switch (p) { switch (p) {
case Provider::MODRINTH: case ResourceProvider::MODRINTH:
return "Modrinth"; return "Modrinth";
case Provider::FLAME: case ResourceProvider::FLAME:
return "CurseForge"; return "CurseForge";
} }
return {}; return {};
} }
auto ProviderCapabilities::hashType(Provider p) -> QStringList auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList
{ {
switch (p) { switch (p) {
case Provider::MODRINTH: case ResourceProvider::MODRINTH:
return { "sha512", "sha1" }; return { "sha512", "sha1" };
case Provider::FLAME: case ResourceProvider::FLAME:
// Try newer formats first, fall back to old format // Try newer formats first, fall back to old format
return { "sha1", "md5", "murmur2" }; return { "sha1", "md5", "murmur2" };
} }
return {}; return {};
} }
auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString
{ {
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1;
switch (p) { switch (p) {
case Provider::MODRINTH: { case ResourceProvider::MODRINTH: {
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512;
break; break;
} }
case Provider::FLAME: case ResourceProvider::FLAME:
algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5;
break; break;
} }

View File

@ -28,17 +28,16 @@ class QIODevice;
namespace ModPlatform { namespace ModPlatform {
enum class Provider { enum class ResourceProvider { MODRINTH, FLAME };
MODRINTH,
FLAME enum class ResourceType { MOD, RESOURCE_PACK };
};
class ProviderCapabilities { class ProviderCapabilities {
public: public:
auto name(Provider) -> const char*; auto name(ResourceProvider) -> const char*;
auto readableName(Provider) -> QString; auto readableName(ResourceProvider) -> QString;
auto hashType(Provider) -> QStringList; auto hashType(ResourceProvider) -> QStringList;
auto hash(Provider, QIODevice*, QString type = "") -> QString; auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString;
}; };
struct ModpackAuthor { struct ModpackAuthor {
@ -66,6 +65,10 @@ struct IndexedVersion {
QString hash; QString hash;
bool is_preferred = true; bool is_preferred = true;
QString changelog; QString changelog;
// For internal use, not provided by APIs
bool is_currently_selected = false;
QString custom_target_folder;
}; };
struct ExtraPackData { struct ExtraPackData {
@ -81,7 +84,7 @@ struct ExtraPackData {
struct IndexedPack { struct IndexedPack {
QVariant addonId; QVariant addonId;
Provider provider; ResourceProvider provider;
QString name; QString name;
QString slug; QString slug;
QString description; QString description;
@ -96,9 +99,26 @@ struct IndexedPack {
// Don't load by default, since some modplatform don't have that info // Don't load by default, since some modplatform don't have that info
bool extraDataLoaded = true; bool extraDataLoaded = true;
ExtraPackData extraData; ExtraPackData extraData;
// For internal use, not provided by APIs
[[nodiscard]] bool isVersionSelected(size_t index) const
{
if (!versionsLoaded)
return false;
return versions.at(index).is_currently_selected;
}
[[nodiscard]] bool isAnyVersionSelected() const
{
if (!versionsLoaded)
return false;
return std::any_of(versions.constBegin(), versions.constEnd(),
[](auto const& v) { return v.is_currently_selected; });
}
}; };
} // namespace ModPlatform } // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack) Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
Q_DECLARE_METATYPE(ModPlatform::Provider) Q_DECLARE_METATYPE(ModPlatform::ResourceProvider)

View File

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
* 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.
*/
#pragma once
#include <QDebug>
#include <QList>
#include <QString>
#include <list>
#include <optional>
#include "../Version.h"
#include "modplatform/ModIndex.h"
#include "tasks/Task.h"
/* Simple class with a common interface for interacting with APIs */
class ResourceAPI {
public:
virtual ~ResourceAPI() = default;
enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request.
unsigned int index;
// The real name of the sorting, as used in the respective API specification.
// Used by Modrinth in the API request.
QString name;
// The human-readable name of the sorting, used for display in the UI.
QString readable_name;
};
struct SearchArgs {
ModPlatform::ResourceType type{};
int offset = 0;
std::optional<QString> search;
std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions;
};
struct SearchCallbacks {
std::function<void(QJsonDocument&)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
std::function<void()> on_abort;
};
struct VersionSearchArgs {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions;
std::optional<ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other)
{
pack = other.pack;
mcVersions = other.mcVersions;
loaders = other.loaders;
}
};
struct VersionSearchCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
};
struct ProjectInfoArgs {
ModPlatform::IndexedPack pack;
ProjectInfoArgs(ProjectInfoArgs const&) = default;
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
struct ProjectInfoCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
};
public:
/** Gets a list of available sorting methods for this API. */
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
public slots:
[[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const
{
qWarning() << "TODO";
return nullptr;
}
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
default:
break;
}
return "";
}
protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; }
[[nodiscard]] inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString
{
QString s;
for (auto& ver : mcVersions) {
s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); // remove last comma
return s;
}
};

View File

@ -81,16 +81,17 @@ bool PackInstallTask::abort()
void PackInstallTask::executeTask() void PackInstallTask::executeTask()
{ {
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
.arg(m_pack_safe_name).arg(m_version_name); .arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
} }
void PackInstallTask::onDownloadSucceeded() void PackInstallTask::onDownloadSucceeded()
@ -552,7 +553,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
profile->appendComponent(new Component(profile.get(), target_id, f)); profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) });
return true; return true;
} }
@ -641,7 +642,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
profile->appendComponent(new Component(profile.get(), target_id, f)); profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) });
return true; return true;
} }
@ -649,7 +650,7 @@ void PackInstallTask::installConfigs()
{ {
qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId(); qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId();
setStatus(tr("Downloading configs...")); setStatus(tr("Downloading configs..."));
jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); jobPtr.reset(new NetJob(tr("Config download"), APPLICATION->network()));
auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).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") auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip")
@ -747,7 +748,7 @@ void PackInstallTask::downloadMods()
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));
jarmods.clear(); jarmods.clear();
jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
for(const auto& mod : m_version.mods) { for(const auto& mod : m_version.mods) {
// skip non-client mods // skip non-client mods
if(!mod.client) continue; if(!mod.client) continue;

View File

@ -23,7 +23,7 @@ void Flame::FileResolvingTask::executeTask()
{ {
setStatus(tr("Resolving mod IDs...")); setStatus(tr("Resolving mod IDs..."));
setProgress(0, 3); setProgress(0, 3);
m_dljob = new NetJob("Mod id resolver", m_network); m_dljob.reset(new NetJob("Mod id resolver", m_network));
result.reset(new QByteArray()); result.reset(new QByteArray());
//build json data to send //build json data to send
QJsonObject object; QJsonObject object;
@ -43,7 +43,7 @@ void Flame::FileResolvingTask::netJobFinished()
{ {
setProgress(1, 3); setProgress(1, 3);
// job to check modrinth for blocked projects // job to check modrinth for blocked projects
m_checkJob = new NetJob("Modrinth check", m_network); m_checkJob.reset(new NetJob("Modrinth check", m_network));
blockedProjects = QMap<File *,QByteArray *>(); blockedProjects = QMap<File *,QByteArray *>();
QJsonDocument doc; QJsonDocument doc;

View File

@ -1,15 +1,19 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#include "FlameAPI.h" #include "FlameAPI.h"
#include "FlameModIndex.h" #include "FlameModIndex.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/NetJob.h"
#include "net/Upload.h" #include "net/Upload.h"
auto FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response)
{ {
auto* netJob = new NetJob(QString("Flame::MatchFingerprints"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::MatchFingerprints"), APPLICATION->network());
QJsonObject body_obj; QJsonObject body_obj;
QJsonArray fingerprints_arr; QJsonArray fingerprints_arr;
@ -24,7 +28,7 @@ auto FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* re
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
@ -106,13 +110,19 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion
{ {
auto versions_url_optional = getVersionsURL(args);
if (!versions_url_optional.has_value())
return {};
auto versions_url = versions_url_optional.value();
QEventLoop loop; QEventLoop loop;
auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
auto response = new QByteArray(); auto response = new QByteArray();
ModPlatform::IndexedVersion ver; ModPlatform::IndexedVersion ver;
netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] { QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -161,9 +171,9 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
return ver; return ver;
} }
auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const
{ {
auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
QJsonObject body_obj; QJsonObject body_obj;
QJsonArray addons_arr; QJsonArray addons_arr;
@ -178,15 +188,15 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const ->
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob; return netJob;
} }
auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const
{ {
auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::GetFiles"), APPLICATION->network());
QJsonObject body_obj; QJsonObject body_obj;
QJsonArray files_arr; QJsonArray files_arr;
@ -201,8 +211,23 @@ auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob; return netJob;
} }
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
{ 3, "LastUpdated", QObject::tr("Sort by Last Updated") },
{ 4, "Name", QObject::tr("Sort by Name") },
{ 5, "Author", QObject::tr("Sort by Author") },
{ 6, "TotalDownloads", QObject::tr("Sort by Downloads") },
{ 7, "Category", QObject::tr("Sort by Category") },
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
{
return s_sorts;
}

View File

@ -1,75 +1,36 @@
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
#pragma once #pragma once
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h" #include "modplatform/helpers/NetworkResourceAPI.h"
class FlameAPI : public NetworkModAPI { class FlameAPI : public NetworkResourceAPI {
public: public:
auto matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr;
auto getModFileChangelog(int modId, int fileId) -> QString; auto getModFileChangelog(int modId, int fileId) -> QString;
auto getModDescription(int modId) -> QString; auto getModDescription(int modId) -> QString;
auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion;
auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response);
Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
private: private:
inline auto getSortFieldInt(QString sortString) const -> int static int getClassId(ModPlatform::ResourceType type)
{ {
return sortString == "Featured" ? 1 switch (type) {
: sortString == "Popularity" ? 2 default:
: sortString == "LastUpdated" ? 3 case ModPlatform::ResourceType::MOD:
: sortString == "Name" ? 4 return 6;
: sortString == "Author" ? 5 }
: sortString == "TotalDownloads" ? 6
: sortString == "Category" ? 7
: sortString == "GameVersion" ? 8
: 1;
} }
private: static int getMappedModLoader(ModLoaderTypes loaders)
inline auto getModSearchURL(SearchArgs& args) const -> QString override
{
auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString();
return QString(
"https://api.curseforge.com/v1/mods/search?"
"gameId=432&"
"classId=6&"
"index=%1&"
"pageSize=25&"
"searchFilter=%2&"
"sortField=%3&"
"sortOrder=desc&"
"modLoaderType=%4&"
"%5")
.arg(args.offset)
.arg(args.search)
.arg(getSortFieldInt(args.sorting))
.arg(getMappedModLoader(args.loaders))
.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()) : "";
QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders));
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3")
.arg(args.addonId)
.arg(gameVersionQuery)
.arg(modLoaderQuery);
};
public:
static auto getMappedModLoader(const ModLoaderTypes loaders) -> int
{ {
// https://docs.curseforge.com/?http#tocS_ModLoaderType // https://docs.curseforge.com/?http#tocS_ModLoaderType
if (loaders & Forge) if (loaders & Forge)
@ -81,4 +42,43 @@ class FlameAPI : public NetworkModAPI {
return 4; // Quilt would probably be 5 return 4; // Quilt would probably be 5
return 0; return 0;
} }
private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{
auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
QStringList get_arguments;
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
get_arguments.append(QString("index=%1").arg(args.offset));
get_arguments.append("pageSize=25");
if (args.search.has_value())
get_arguments.append(QString("searchFilter=%1").arg(args.search.value()));
if (args.sorting.has_value())
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc");
if (args.loaders.has_value())
get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
};
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
{
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
};
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{
QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())};
QStringList get_parameters;
if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
if (args.loaders.has_value())
get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
return url + get_parameters.join('&');
};
}; };

View File

@ -7,7 +7,10 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "ModDownloadTask.h" #include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
static FlameAPI api; static FlameAPI api;
@ -126,7 +129,8 @@ void FlameCheckUpdate::executeTask()
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size()); setProgress(i++, m_mods.size());
auto latest_ver = api.getLatestVersion({ mod->metadata()->project_id.toString(), m_game_versions, m_loaders }); ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() };
auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders });
// Check if we were aborted while getting the latest version // Check if we were aborted while getting the latest version
if (m_was_aborted) { if (m_was_aborted) {
@ -160,7 +164,7 @@ void FlameCheckUpdate::executeTask()
for (auto& author : mod->authors()) for (auto& author : mod->authors())
pack.authors.append({ author }); pack.authors.append({ author });
pack.description = mod->description(); pack.description = mod->description();
pack.provider = ModPlatform::Provider::FLAME; pack.provider = ModPlatform::ResourceProvider::FLAME;
auto old_version = mod->version(); auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
@ -168,10 +172,10 @@ void FlameCheckUpdate::executeTask()
old_version = current_ver.version; old_version = current_ver.version;
} }
auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder); auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version,
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
ModPlatform::Provider::FLAME, download_task); ModPlatform::ResourceProvider::FLAME, download_task);
} }
} }

View File

@ -8,7 +8,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
Q_OBJECT Q_OBJECT
public: public:
FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder) FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{} {}

View File

@ -53,6 +53,13 @@
#include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/BlockedModsDialog.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include <QDebug>
#include <QFileInfo>
#include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" }, const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" },
{ "1.4.2", "6.0.1.355" }, { "1.4.2", "6.0.1.355" },
{ "1.4.7", "6.6.2.534" }, { "1.4.7", "6.6.2.534" },
@ -176,7 +183,7 @@ bool FlameCreationTask::updateInstance()
QEventLoop loop; QEventLoop loop;
connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
// Parse the API response // Parse the API response
QJsonParseError parse_error{}; QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); auto doc = QJsonDocument::fromJson(*raw_response, &parse_error);
@ -218,7 +225,7 @@ bool FlameCreationTask::updateInstance()
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
} }
}); });
connect(job, &NetJob::finished, &loop, &QEventLoop::quit); connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = job; m_process_update_file_info_job = job;
job->start(); job->start();
@ -366,7 +373,7 @@ bool FlameCreationTask::createInstance()
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
instance.setName(name()); instance.setName(name());
m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
@ -401,6 +408,10 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
QList<BlockedMod> blocked_mods; QList<BlockedMod> blocked_mods;
auto anyBlocked = false; auto anyBlocked = false;
for (const auto& result : results.files.values()) { for (const auto& result : results.files.values()) {
if (result.fileName.endsWith(".zip")) {
m_ZIP_resources.append(std::make_pair(result.fileName, result.targetFolder));
}
if (!result.resolved || result.url.isEmpty()) { if (!result.resolved || result.url.isEmpty()) {
BlockedMod blocked_mod; BlockedMod blocked_mod;
blocked_mod.name = result.fileName; blocked_mod.name = result.fileName;
@ -439,41 +450,9 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
} }
} }
/// @brief copy the matched blocked mods to the instance staging area
/// @param blocked_mods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
{
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = blocked_mods.length();
setProgress(i, total);
for (auto const& mod : blocked_mods) {
if (!mod.matched) {
qDebug() << mod.name << "was not matched to a local file, skipping copy";
continue;
}
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
if (!FS::copy(mod.localPath, dest_path)()) {
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
}
i++;
setProgress(i, total);
}
setAbortable(true);
}
void FlameCreationTask::setupDownloadJob(QEventLoop& loop) void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{ {
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
for (const auto& result : m_mod_id_resolver->getResults().files) { for (const auto& result : m_mod_id_resolver->getResults().files) {
QString filename = result.fileName; QString filename = result.fileName;
if (!result.required) { if (!result.required) {
@ -509,7 +488,10 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
} }
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); }); connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
m_files_job.reset();
validateZIPResouces();
});
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset(); m_files_job.reset();
setError(reason); setError(reason);
@ -520,3 +502,103 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));
m_files_job->start(); m_files_job->start();
} }
/// @brief copy the matched blocked mods to the instance staging area
/// @param blocked_mods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
{
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = blocked_mods.length();
setProgress(i, total);
for (auto const& mod : blocked_mods) {
if (!mod.matched) {
qDebug() << mod.name << "was not matched to a local file, skipping copy";
continue;
}
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
if (!FS::copy(mod.localPath, destPath)()) {
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
}
i++;
setProgress(i, total);
}
setAbortable(true);
}
void FlameCreationTask::validateZIPResouces()
{
qDebug() << "Validating whether resources stored as .zip are in the right place";
for (auto [fileName, targetFolder] : m_ZIP_resources) {
qDebug() << "Checking" << fileName << "...";
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
/// @brief check the target and move the the file
/// @return path where file can now be found
auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) {
if (targetFolder != realTarget) {
qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget;
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName);
qDebug() << "Moving" << localPath << "to" << destPath;
if (FS::move(localPath, destPath)) {
return destPath;
}
}
return localPath;
};
auto installWorld = [this](QString worldPath){
qDebug() << "Installing World from" << worldPath;
QFileInfo worldFileInfo(worldPath);
World w(worldFileInfo);
if (!w.isValid()) {
qDebug() << "World at" << worldPath << "is not valid, skipping install.";
} else {
w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves"));
}
};
QFileInfo localFileInfo(localPath);
auto type = ResourceUtils::identify(localFileInfo);
QString worldPath;
switch (type) {
case PackedResourceType::ResourcePack :
validatePath(fileName, targetFolder, "resourcepacks");
break;
case PackedResourceType::TexturePack :
validatePath(fileName, targetFolder, "texturepacks");
break;
case PackedResourceType::DataPack :
validatePath(fileName, targetFolder, "datapacks");
break;
case PackedResourceType::Mod :
validatePath(fileName, targetFolder, "mods");
break;
case PackedResourceType::ShaderPack :
// in theroy flame API can't do this but who knows, that *may* change ?
// better to handle it if it *does* occure in the future
validatePath(fileName, targetFolder, "shaderpacks");
break;
case PackedResourceType::WorldSave :
worldPath = validatePath(fileName, targetFolder, "saves");
installWorld(worldPath);
break;
case PackedResourceType::UNKNOWN :
default :
qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is.";
break;
}
}
}

View File

@ -77,6 +77,7 @@ class FlameCreationTask final : public InstanceCreationTask {
void idResolverSucceeded(QEventLoop&); void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&); void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods); void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void validateZIPResouces();
private: private:
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;
@ -85,10 +86,12 @@ class FlameCreationTask final : public InstanceCreationTask {
Flame::Manifest m_pack; Flame::Manifest m_pack;
// Handle to allow aborting // Handle to allow aborting
NetJob* m_process_update_file_info_job = nullptr; Task::Ptr m_process_update_file_info_job = nullptr;
NetJob::Ptr m_files_job = nullptr; NetJob::Ptr m_files_job = nullptr;
QString m_managed_id, m_managed_version_id; QString m_managed_id, m_managed_version_id;
QList<std::pair<QString, QString>> m_ZIP_resources;
std::optional<InstancePtr> m_instance; std::optional<InstancePtr> m_instance;
}; };

View File

@ -11,7 +11,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps;
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{ {
pack.addonId = Json::requireInteger(obj, "id"); pack.addonId = Json::requireInteger(obj, "id");
pack.provider = ModPlatform::Provider::FLAME; pack.provider = ModPlatform::ResourceProvider::FLAME;
pack.name = Json::requireString(obj, "name"); pack.name = Json::requireString(obj, "name");
pack.slug = Json::requireString(obj, "slug"); pack.slug = Json::requireString(obj, "slug");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
@ -76,10 +76,10 @@ static QString enumToString(int hash_algorithm)
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr, QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst) const BaseInstance* inst)
{ {
QVector<ModPlatform::IndexedVersion> unsortedVersions; QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile(); auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft"); QString mcVersion = profile->getComponentVersion("net.minecraft");
for (auto versionIter : arr) { for (auto versionIter : arr) {
@ -127,7 +127,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
auto hash_list = Json::ensureArray(obj, "hashes"); auto hash_list = Json::ensureArray(obj, "hashes");
for (auto h : hash_list) { for (auto h : hash_list) {
auto hash_entry = Json::ensureObject(h); auto hash_entry = Json::ensureObject(h);
auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME);
auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm"));
if (hash_types.contains(hash_algo)) { if (hash_types.contains(hash_algo)) {
file.hash = Json::requireString(hash_entry, "value"); file.hash = Json::requireString(hash_entry, "value");

View File

@ -17,7 +17,7 @@ void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr, QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst); const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
} // namespace FlameMod } // namespace FlameMod

Some files were not shown because too many files have changed in this diff Show More