Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into metadata2

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2024-07-24 11:40:23 +03:00
108 changed files with 1550 additions and 576 deletions

View File

@ -42,6 +42,20 @@
#include <net/ApiDownload.h>
#include <net/ChecksumValidator.h>
/**
* @brief Collect applicable files for the library.
*
* Depending on whether the library is native or not, it adds paths to the
* appropriate lists for jar files, native libraries for 32-bit, and native
* libraries for 64-bit.
*
* @param runtimeContext The current runtime context.
* @param jar List to store paths for jar files.
* @param native List to store paths for native libraries.
* @param native32 List to store paths for 32-bit native libraries.
* @param native64 List to store paths for 64-bit native libraries.
* @param overridePath Optional path to override the default storage path.
*/
void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
QStringList& jar,
QStringList& native,
@ -50,6 +64,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
const QString& overridePath) const
{
bool local = isLocal();
// Lambda function to get the absolute file path
auto actualPath = [&](QString relPath) {
relPath = FS::RemoveInvalidPathChars(relPath);
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
@ -59,6 +74,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
}
return out.absoluteFilePath();
};
QString raw_storage = storageSuffix(runtimeContext);
if (isNative()) {
if (raw_storage.contains("${arch}")) {
@ -76,6 +92,19 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
}
}
/**
* @brief Get download requests for the library files.
*
* Depending on whether the library is native or not, and the current runtime context,
* this function prepares download requests for the necessary files. It handles both local
* and remote files, checks for stale cache entries, and adds checksummed downloads.
*
* @param runtimeContext The current runtime context.
* @param cache Pointer to the HTTP meta cache.
* @param failedLocalFiles List to store paths for failed local files.
* @param overridePath Optional path to override the default storage path.
* @return QList<Net::NetRequest::Ptr> List of download requests.
*/
QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
class HttpMetaCache* cache,
QStringList& failedLocalFiles,
@ -85,6 +114,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
bool stale = isAlwaysStale();
bool local = isLocal();
// Lambda function to check if a local file exists
auto check_local_file = [&](QString storage) {
QFileInfo fileinfo(storage);
QString fileName = fileinfo.fileName();
@ -97,6 +127,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
return true;
};
// Lambda function to add a download request
auto add_download = [&](QString storage, QString url, QString sha1) {
if (local) {
return check_local_file(storage);
@ -197,6 +228,15 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
return out;
}
/**
* @brief Check if the library is active in the given runtime context.
*
* This function evaluates rules to determine if the library should be active,
* considering both general rules and native compatibility.
*
* @param runtimeContext The current runtime context.
* @return bool True if the library is active, false otherwise.
*/
bool Library::isActive(const RuntimeContext& runtimeContext) const
{
bool result = true;
@ -217,16 +257,35 @@ bool Library::isActive(const RuntimeContext& runtimeContext) const
return result;
}
/**
* @brief Check if the library is considered local.
*
* @return bool True if the library is local, false otherwise.
*/
bool Library::isLocal() const
{
return m_hint == "local";
}
/**
* @brief Check if the library is always considered stale.
*
* @return bool True if the library is always stale, false otherwise.
*/
bool Library::isAlwaysStale() const
{
return m_hint == "always-stale";
}
/**
* @brief Get the compatible native classifier for the current runtime context.
*
* This function attempts to match the current runtime context with the appropriate
* native classifier.
*
* @param runtimeContext The current runtime context.
* @return QString The compatible native classifier, or an empty string if none is found.
*/
QString Library::getCompatibleNative(const RuntimeContext& runtimeContext) const
{
// try to match precise classifier "[os]-[arch]"
@ -241,16 +300,31 @@ QString Library::getCompatibleNative(const RuntimeContext& runtimeContext) const
return entry.value();
}
/**
* @brief Set the storage prefix for the library.
*
* @param prefix The storage prefix to set.
*/
void Library::setStoragePrefix(QString prefix)
{
m_storagePrefix = prefix;
}
/**
* @brief Get the default storage prefix for libraries.
*
* @return QString The default storage prefix.
*/
QString Library::defaultStoragePrefix()
{
return "libraries/";
}
/**
* @brief Get the current storage prefix for the library.
*
* @return QString The current storage prefix.
*/
QString Library::storagePrefix() const
{
if (m_storagePrefix.isEmpty()) {
@ -259,6 +333,15 @@ QString Library::storagePrefix() const
return m_storagePrefix;
}
/**
* @brief Get the filename for the library in the current runtime context.
*
* This function determines the appropriate filename for the library, taking into
* account native classifiers if applicable.
*
* @param runtimeContext The current runtime context.
* @return QString The filename of the library.
*/
QString Library::filename(const RuntimeContext& runtimeContext) const
{
if (!m_filename.isEmpty()) {
@ -280,6 +363,15 @@ QString Library::filename(const RuntimeContext& runtimeContext) const
return nativeSpec.getFileName();
}
/**
* @brief Get the display name for the library in the current runtime context.
*
* This function returns the display name for the library, defaulting to the filename
* if no display name is set.
*
* @param runtimeContext The current runtime context.
* @return QString The display name of the library.
*/
QString Library::displayName(const RuntimeContext& runtimeContext) const
{
if (!m_displayname.isEmpty())
@ -287,6 +379,15 @@ QString Library::displayName(const RuntimeContext& runtimeContext) const
return filename(runtimeContext);
}
/**
* @brief Get the storage suffix for the library in the current runtime context.
*
* This function determines the appropriate storage suffix for the library, taking into
* account native classifiers if applicable.
*
* @param runtimeContext The current runtime context.
* @return QString The storage suffix of the library.
*/
QString Library::storageSuffix(const RuntimeContext& runtimeContext) const
{
// non-native? use only the gradle specifier

View File

@ -58,7 +58,6 @@
#include "ComponentUpdateTask.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
@ -181,7 +180,7 @@ static bool loadPackProfile(PackProfile* parent,
}
if (!componentsFile.open(QFile::ReadOnly)) {
qCritical() << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
qWarning() << "Ignoring overriden order";
qWarning() << "Ignoring overridden order";
return false;
}
@ -190,7 +189,7 @@ static bool loadPackProfile(PackProfile* parent,
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
qWarning() << "Ignoring overriden order";
qWarning() << "Ignoring overridden order";
return false;
}
@ -1022,3 +1021,23 @@ std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
loaders |= ModPlatform::Forge;
return loaders;
}
QList<ModPlatform::ModLoaderType> PackProfile::getModLoadersList()
{
QList<ModPlatform::ModLoaderType> result;
for (auto c : d->components) {
if (c->isEnabled() && modloaderMapping.contains(c->getID())) {
result.append(modloaderMapping[c->getID()]);
}
}
// TODO: remove this or add version condition once Quilt drops official Fabric support
if (result.contains(ModPlatform::Quilt) && !result.contains(ModPlatform::Fabric)) {
result.append(ModPlatform::Fabric);
}
if (getComponentVersion("net.minecraft") == "1.20.1" && result.contains(ModPlatform::NeoForge) &&
!result.contains(ModPlatform::Forge)) {
result.append(ModPlatform::Forge);
}
return result;
}

View File

@ -146,6 +146,7 @@ class PackProfile : public QAbstractListModel {
std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
// this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
QList<ModPlatform::ModLoaderType> getModLoadersList();
private:
void scheduleSave();

View File

@ -57,7 +57,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
}
if (!orderFile.open(QFile::ReadOnly)) {
qCritical() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString();
qWarning() << "Ignoring overriden order";
qWarning() << "Ignoring overridden order";
return false;
}
@ -66,7 +66,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
qWarning() << "Ignoring overriden order";
qWarning() << "Ignoring overridden order";
return false;
}
@ -84,7 +84,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
}
} catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
qWarning() << "Ignoring overriden order";
qWarning() << "Ignoring overridden order";
order.clear();
return false;
}

View File

@ -59,6 +59,9 @@ void AuthFlow::executeTask()
void AuthFlow::nextStep()
{
if (!Task::isRunning()) {
return;
}
if (m_steps.size() == 0) {
// we got to the end without an incident... assume this is all.
m_currentStep.reset();
@ -143,4 +146,11 @@ bool AuthFlow::changeState(AccountTaskState newState, QString reason)
return false;
}
}
}
bool AuthFlow::abort()
{
emitAborted();
if (m_currentStep)
m_currentStep->abort();
return true;
}

View File

@ -24,6 +24,9 @@ class AuthFlow : public Task {
AccountTaskState taskState() { return m_taskState; }
public slots:
bool abort() override;
signals:
void authorizeWithBrowser(const QUrl& url);
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);

View File

@ -34,6 +34,7 @@ class AuthStep : public QObject {
public slots:
virtual void perform() = 0;
virtual void abort() {}
signals:
void finished(AccountTaskState resultingState, QString message);

View File

@ -10,6 +10,7 @@
#include "Logging.h"
#include "minecraft/auth/Parsers.h"
#include "net/Download.h"
#include "net/NetJob.h"
#include "net/StaticHeaderProxy.h"
#include "tasks/Task.h"
@ -31,12 +32,15 @@ void EntitlementsStep::perform()
{ "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
m_response.reset(new QByteArray());
m_task = Net::Download::makeByteArray(url, m_response);
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Download::makeByteArray(url, m_response);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &EntitlementsStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting entitlements...";
}

View File

@ -4,6 +4,7 @@
#include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
#include "net/NetJob.h"
class EntitlementsStep : public AuthStep {
Q_OBJECT
@ -22,5 +23,6 @@ class EntitlementsStep : public AuthStep {
private:
QString m_entitlements_request_id;
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
Net::Download::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -17,17 +17,20 @@ void GetSkinStep::perform()
QUrl url(m_data->minecraftProfile.skin.url);
m_response.reset(new QByteArray());
m_task = Net::Download::makeByteArray(url, m_response);
m_request = Net::Download::makeByteArray(url, m_response);
m_task.reset(new NetJob("GetSkinStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &GetSkinStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
}
void GetSkinStep::onRequestDone()
{
if (m_task->error() == QNetworkReply::NoError)
if (m_request->error() == QNetworkReply::NoError)
m_data->minecraftProfile.skin.data = *m_response;
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin"));
emit finished(AccountTaskState::STATE_WORKING, tr("Got skin"));
}

View File

@ -4,6 +4,7 @@
#include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
#include "net/NetJob.h"
class GetSkinStep : public AuthStep {
Q_OBJECT
@ -21,5 +22,6 @@ class GetSkinStep : public AuthStep {
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
Net::Download::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -37,12 +37,15 @@ void LauncherLoginStep::perform()
};
m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8());
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8());
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &LauncherLoginStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting Minecraft access token...";
}
@ -50,12 +53,13 @@ void LauncherLoginStep::perform()
void LauncherLoginStep::onRequestDone()
{
qCDebug(authCredentials()) << *m_response;
if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_task->error();
if (Net::isApplicationError(m_task->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error();
if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get Minecraft access token: %1").arg(m_request->errorString()));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_task->errorString()));
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString()));
}
return;
}

View File

@ -3,6 +3,7 @@
#include <memory>
#include "minecraft/auth/AuthStep.h"
#include "net/NetJob.h"
#include "net/Upload.h"
class LauncherLoginStep : public AuthStep {
@ -21,5 +22,6 @@ class LauncherLoginStep : public AuthStep {
private:
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
Net::Upload::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -46,6 +46,8 @@
MSADeviceCodeStep::MSADeviceCodeStep(AccountData* data) : AuthStep(data)
{
m_clientId = APPLICATION->getMSAClientID();
connect(&m_expiration_timer, &QTimer::timeout, this, &MSADeviceCodeStep::abort);
connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser);
}
QString MSADeviceCodeStep::describe()
@ -65,12 +67,15 @@ void MSADeviceCodeStep::perform()
{ "Accept", "application/json" },
};
m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, payload);
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Upload::makeByteArray(url, m_response, payload);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished);
m_task->setNetwork(APPLICATION->network());
m_task->start();
}
@ -115,7 +120,7 @@ void MSADeviceCodeStep::deviceAutorizationFinished()
tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
return;
}
if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) {
if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization"));
qDebug() << *m_response;
return;
@ -133,9 +138,7 @@ void MSADeviceCodeStep::deviceAutorizationFinished()
m_expiration_timer.setTimerType(Qt::VeryCoarseTimer);
m_expiration_timer.setInterval(rsp.expires_in * 1000);
m_expiration_timer.setSingleShot(true);
connect(&m_expiration_timer, &QTimer::timeout, this, &MSADeviceCodeStep::abort);
m_expiration_timer.start();
m_pool_timer.setTimerType(Qt::VeryCoarseTimer);
m_pool_timer.setSingleShot(true);
startPoolTimer();
@ -145,8 +148,8 @@ void MSADeviceCodeStep::abort()
{
m_expiration_timer.stop();
m_pool_timer.stop();
if (m_task) {
m_task->abort();
if (m_request) {
m_request->abort();
}
m_is_aborted = true;
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Task aborted"));
@ -157,8 +160,12 @@ void MSADeviceCodeStep::startPoolTimer()
if (m_is_aborted) {
return;
}
if (m_expiration_timer.remainingTime() < interval * 1000) {
perform();
return;
}
m_pool_timer.setInterval(interval * 1000);
connect(&m_pool_timer, &QTimer::timeout, this, &MSADeviceCodeStep::authenticateUser);
m_pool_timer.start();
}
@ -175,13 +182,13 @@ void MSADeviceCodeStep::authenticateUser()
{ "Accept", "application/json" },
};
m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, payload);
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Upload::makeByteArray(url, m_response, payload);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished);
connect(m_request.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished);
m_task->setNetwork(APPLICATION->network());
m_task->start();
m_request->setNetwork(APPLICATION->network());
m_request->start();
}
struct AuthenticationResponse {
@ -221,7 +228,7 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
void MSADeviceCodeStep::authenticationFinished()
{
if (m_task->error() == QNetworkReply::TimeoutError) {
if (m_request->error() == QNetworkReply::TimeoutError) {
// rfc8628#section-3.5
// "On encountering a connection timeout, clients MUST unilaterally
// reduce their polling frequency before retrying. The use of an
@ -254,7 +261,7 @@ void MSADeviceCodeStep::authenticationFinished()
tr("Device Access failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
return;
}
if (!m_task->wasSuccessful() || m_task->error() != QNetworkReply::NoError) {
if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) {
startPoolTimer(); // it failed so just try again without increasing the interval
return;
}

View File

@ -38,6 +38,7 @@
#include <QTimer>
#include "minecraft/auth/AuthStep.h"
#include "net/NetJob.h"
#include "net/Upload.h"
class MSADeviceCodeStep : public AuthStep {
@ -51,7 +52,7 @@ class MSADeviceCodeStep : public AuthStep {
QString describe() override;
public slots:
void abort();
void abort() override;
signals:
void authorizeWithBrowser(QString url, QString code, int expiresIn);
@ -72,5 +73,6 @@ class MSADeviceCodeStep : public AuthStep {
QTimer m_expiration_timer;
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
Net::Upload::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -35,22 +35,74 @@
#include "MSAStep.h"
#include <QtNetworkAuth/qoauthhttpserverreplyhandler.h>
#include <QAbstractOAuth2>
#include <QNetworkRequest>
#include <QOAuthHttpServerReplyHandler>
#include <QOAuthOobReplyHandler>
#include "Application.h"
#include "BuildConfig.h"
#include "FileSystem.h"
#include <QProcess>
#include <QSettings>
#include <QStandardPaths>
bool isSchemeHandlerRegistered()
{
#ifdef Q_OS_LINUX
QProcess process;
process.start("xdg-mime", { "query", "default", "x-scheme-handler/" + BuildConfig.LAUNCHER_APP_BINARY_NAME });
process.waitForFinished();
QString output = process.readAllStandardOutput().trimmed();
return output.contains(BuildConfig.LAUNCHER_APP_BINARY_NAME);
#elif defined(Q_OS_WIN)
QString regPath = QString("HKEY_CURRENT_USER\\Software\\Classes\\%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
QSettings settings(regPath, QSettings::NativeFormat);
return settings.contains("shell/open/command/.");
#endif
return true;
}
class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler {
Q_OBJECT
public:
explicit CustomOAuthOobReplyHandler(QObject* parent = nullptr) : QOAuthOobReplyHandler(parent)
{
connect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived);
}
~CustomOAuthOobReplyHandler() override
{
disconnect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived);
}
QString callback() const override { return BuildConfig.LAUNCHER_APP_BINARY_NAME + "://oauth/microsoft"; }
};
MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent)
{
m_clientId = APPLICATION->getMSAClientID();
if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") ||
QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered())
auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
replyHandler->setCallbackText(
" <iframe src=\"https://prismlauncher.org/successful-login\" title=\"PrismLauncher Microsoft login\" style=\"position:fixed; "
"top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; "
"z-index:999999;\"/> ");
oauth2.setReplyHandler(replyHandler);
{
auto replyHandler = new QOAuthHttpServerReplyHandler(this);
replyHandler->setCallbackText(R"XXX(
<noscript>
<meta http-equiv="Refresh" content="0; URL=https://prismlauncher.org/successful-login" />
</noscript>
Login Successful, redirecting...
<script>
window.location.replace("https://prismlauncher.org/successful-login");
</script>
)XXX");
oauth2.setReplyHandler(replyHandler);
} else {
oauth2.setReplyHandler(new CustomOAuthOobReplyHandler(this));
}
oauth2.setAuthorizationUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize"));
oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token"));
oauth2.setScope("XboxLive.SignIn XboxLive.offline_access");
@ -67,9 +119,27 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile
emit finished(AccountTaskState::STATE_WORKING, tr("Got "));
});
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &MSAStep::authorizeWithBrowser);
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this](const QAbstractOAuth2::Error err) {
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Microsoft user authentication failed."));
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, [this, silent](const QAbstractOAuth2::Error err) {
auto state = AccountTaskState::STATE_FAILED_HARD;
if (oauth2.status() == QAbstractOAuth::Status::Granted || silent) {
if (err == QAbstractOAuth2::Error::NetworkError) {
state = AccountTaskState::STATE_OFFLINE;
} else {
state = AccountTaskState::STATE_FAILED_SOFT;
}
}
auto message = tr("Microsoft user authentication failed.");
if (silent) {
message = tr("Failed to refresh token.");
}
qWarning() << message;
emit finished(state, message);
});
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::error, this,
[this](const QString& error, const QString& errorDescription, const QUrl& uri) {
qWarning() << "Failed to login because" << error << errorDescription;
emit finished(AccountTaskState::STATE_FAILED_HARD, errorDescription);
});
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::extraTokensChanged, this,
[this](const QVariantMap& tokens) { m_data->msaToken.extra = tokens; });
@ -89,20 +159,27 @@ void MSAStep::perform()
if (m_data->msaClientID != m_clientId) {
emit finished(AccountTaskState::STATE_DISABLED,
tr("Microsoft user authentication failed - client identification has changed."));
return;
}
if (m_data->msaToken.refresh_token.isEmpty()) {
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - refresh token is empty."));
return;
}
oauth2.setRefreshToken(m_data->msaToken.refresh_token);
oauth2.refreshAccessToken();
} else {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // QMultiMap param changed in 6.0
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) {
oauth2.setModifyParametersFunction(
[](QAbstractOAuth::Stage stage, QMultiMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
#else
oauth2.setModifyParametersFunction([](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) {
oauth2.setModifyParametersFunction(
[](QAbstractOAuth::Stage stage, QMap<QString, QVariant>* map) { map->insert("prompt", "select_account"); });
#endif
map->insert("prompt", "select_account");
});
*m_data = AccountData();
m_data->msaClientID = m_clientId;
oauth2.grant();
}
}
#include "MSAStep.moc"

View File

@ -22,37 +22,41 @@ void MinecraftProfileStep::perform()
{ "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } };
m_response.reset(new QByteArray());
m_task = Net::Download::makeByteArray(url, m_response);
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Download::makeByteArray(url, m_response);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &MinecraftProfileStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
}
void MinecraftProfileStep::onRequestDone()
{
if (m_task->error() == QNetworkReply::ContentNotFoundError) {
if (m_request->error() == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
m_data->minecraftProfile = MinecraftProfile();
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no Minecraft profile."));
return;
}
if (m_task->error() != QNetworkReply::NoError) {
if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Error getting profile:";
qWarning() << " HTTP Status: " << m_task->replyStatusCode();
qWarning() << " Internal error no.: " << m_task->error();
qWarning() << " Error string: " << m_task->errorString();
qWarning() << " HTTP Status: " << m_request->replyStatusCode();
qWarning() << " Internal error no.: " << m_request->error();
qWarning() << " Error string: " << m_request->errorString();
qWarning() << " Response:";
qWarning() << QString::fromUtf8(*m_response);
if (Net::isApplicationError(m_task->error())) {
if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_task->errorString()));
emit finished(AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
}
return;
}

View File

@ -4,6 +4,7 @@
#include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
#include "net/NetJob.h"
class MinecraftProfileStep : public AuthStep {
Q_OBJECT
@ -21,5 +22,6 @@ class MinecraftProfileStep : public AuthStep {
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
Net::Download::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -42,12 +42,15 @@ void XboxAuthorizationStep::perform()
{ "Accept", "application/json" },
};
m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &XboxAuthorizationStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting authorization token for " << m_relyingParty;
}
@ -55,19 +58,19 @@ void XboxAuthorizationStep::perform()
void XboxAuthorizationStep::onRequestDone()
{
qCDebug(authCredentials()) << *m_response;
if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_task->error();
if (Net::isApplicationError(m_task->error())) {
if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error();
if (Net::isApplicationError(m_request->error())) {
if (!processSTSError()) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_task->error()));
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_request->error()));
} else {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
}
} else {
emit finished(AccountTaskState::STATE_OFFLINE,
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_task->errorString()));
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
}
return;
}
@ -92,7 +95,7 @@ void XboxAuthorizationStep::onRequestDone()
bool XboxAuthorizationStep::processSTSError()
{
if (m_task->error() == QNetworkReply::AuthenticationRequiredError) {
if (m_request->error() == QNetworkReply::AuthenticationRequiredError) {
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
if (jsonError.error) {

View File

@ -3,6 +3,7 @@
#include <memory>
#include "minecraft/auth/AuthStep.h"
#include "net/NetJob.h"
#include "net/Upload.h"
class XboxAuthorizationStep : public AuthStep {
@ -28,5 +29,6 @@ class XboxAuthorizationStep : public AuthStep {
QString m_authorizationKind;
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
Net::Upload::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -34,25 +34,28 @@ void XboxProfileStep::perform()
};
m_response.reset(new QByteArray());
m_task = Net::Download::makeByteArray(url, m_response);
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Download::makeByteArray(url, m_response);
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("XboxProfileStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &XboxProfileStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "Getting Xbox profile...";
}
void XboxProfileStep::onRequestDone()
{
if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_task->error();
if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error();
qCDebug(authCredentials()) << *m_response;
if (Net::isApplicationError(m_task->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_request->errorString()));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_task->errorString()));
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_request->errorString()));
}
return;
}

View File

@ -4,6 +4,7 @@
#include "minecraft/auth/AuthStep.h"
#include "net/Download.h"
#include "net/NetJob.h"
class XboxProfileStep : public AuthStep {
Q_OBJECT
@ -21,5 +22,6 @@ class XboxProfileStep : public AuthStep {
private:
std::shared_ptr<QByteArray> m_response;
Net::Download::Ptr m_task;
Net::Download::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -38,24 +38,27 @@ void XboxUserStep::perform()
{ "x-xbl-contract-version", "1" }
};
m_response.reset(new QByteArray());
m_task = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8());
m_request->addHeaderProxy(new Net::StaticHeaderProxy(headers));
m_task.reset(new NetJob("XboxUserStep", APPLICATION->network()));
m_task->setAskRetry(false);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &XboxUserStep::onRequestDone);
m_task->setNetwork(APPLICATION->network());
m_task->start();
qDebug() << "First layer of XBox auth ... commencing.";
}
void XboxUserStep::onRequestDone()
{
if (m_task->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_task->error();
if (Net::isApplicationError(m_task->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error();
if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(m_request->errorString()));
} else {
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(m_task->errorString()));
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(m_request->errorString()));
}
return;
}

View File

@ -3,6 +3,7 @@
#include <memory>
#include "minecraft/auth/AuthStep.h"
#include "net/NetJob.h"
#include "net/Upload.h"
class XboxUserStep : public AuthStep {
@ -21,5 +22,6 @@ class XboxUserStep : public AuthStep {
private:
std::shared_ptr<QByteArray> m_response;
Net::Upload::Ptr m_task;
Net::Upload::Ptr m_request;
NetJob::Ptr m_task;
};

View File

@ -159,15 +159,14 @@ bool Resource::enable(EnableAction action)
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!file.rename(path))
return false;
} else {
path += ".disabled";
if (!file.rename(path))
return false;
if (QFile::exists(path)) {
path = FS::getUniqueResourceName(path);
}
}
if (!file.rename(path))
return false;
setFile(QFileInfo(path));

View File

@ -215,9 +215,6 @@ bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, Ena
}
auto new_id = resource->internal_id();
if (m_resources_index.contains(new_id)) {
// FIXME: https://github.com/PolyMC/PolyMC/issues/550
}
m_resources_index.remove(old_id);
m_resources_index[new_id] = row;

View File

@ -7,6 +7,7 @@
#include <memory>
#include "FileSystem.h"
#include "minecraft/mod/Resource.h"
#include "tasks/Task.h"
@ -50,6 +51,12 @@ class BasicFolderLoadTask : public Task {
m_dir.refresh();
for (auto entry : m_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath();
auto newFilePath = FS::getUniqueResourceName(filePath);
if (newFilePath != filePath) {
FS::move(filePath, newFilePath);
entry = QFileInfo(newFilePath);
}
auto resource = m_create_func(entry);
resource->moveToThread(m_thread_to_spawn_into);
m_result->resources.insert(resource->internal_id(), resource);

View File

@ -29,7 +29,6 @@
#include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "tasks/ConcurrentTask.h"
#include "tasks/SequentialTask.h"
#include "ui/pages/modplatform/ModModel.h"
#include "ui/pages/modplatform/flame/FlameResourceModels.h"

View File

@ -36,6 +36,7 @@
#include "ModFolderLoadTask.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"
#include <QThread>
@ -63,6 +64,12 @@ void ModFolderLoadTask::executeTask()
// Read JAR files that don't have metadata
m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath();
auto newFilePath = FS::getUniqueResourceName(filePath);
if (newFilePath != filePath) {
FS::move(filePath, newFilePath);
entry = QFileInfo(newFilePath);
}
Mod* mod(new Mod(entry));
if (mod->enabled()) {