@ -9,28 +9,21 @@ class Agent;
|
||||
typedef std::shared_ptr<Agent> AgentPtr;
|
||||
|
||||
class Agent {
|
||||
public:
|
||||
Agent(LibraryPtr library, const QString &argument)
|
||||
public:
|
||||
Agent(LibraryPtr library, const QString& argument)
|
||||
{
|
||||
m_library = library;
|
||||
m_argument = argument;
|
||||
}
|
||||
|
||||
public: /* methods */
|
||||
|
||||
LibraryPtr library() {
|
||||
return m_library;
|
||||
}
|
||||
QString argument() {
|
||||
return m_argument;
|
||||
}
|
||||
|
||||
protected: /* data */
|
||||
public: /* methods */
|
||||
LibraryPtr library() { return m_library; }
|
||||
QString argument() { return m_argument; }
|
||||
|
||||
protected: /* data */
|
||||
/// The library pointing to the jar this Java agent is contained within
|
||||
LibraryPtr m_library;
|
||||
|
||||
/// The argument to the Java agent, passed after an = if present
|
||||
QString m_argument;
|
||||
|
||||
};
|
||||
|
@ -33,21 +33,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonParseError>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QVariant>
|
||||
#include <QDebug>
|
||||
|
||||
#include "AssetsUtils.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "FileSystem.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@ -56,37 +56,32 @@ QSet<QString> collectPathsFromDir(QString dirPath)
|
||||
{
|
||||
QFileInfo dirInfo(dirPath);
|
||||
|
||||
if (!dirInfo.exists())
|
||||
{
|
||||
if (!dirInfo.exists()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QSet<QString> out;
|
||||
|
||||
QDirIterator iter(dirPath, QDirIterator::Subdirectories);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
while (iter.hasNext()) {
|
||||
QString value = iter.next();
|
||||
QFileInfo info(value);
|
||||
if(info.isFile())
|
||||
{
|
||||
if (info.isFile()) {
|
||||
out.insert(value);
|
||||
qDebug() << value;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace AssetsUtils
|
||||
{
|
||||
namespace AssetsUtils {
|
||||
|
||||
/*
|
||||
* Returns true on success, with index populated
|
||||
* index is undefined otherwise
|
||||
*/
|
||||
bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index)
|
||||
bool loadAssetsIndexJson(const QString& assetsId, const QString& path, AssetsIndex& index)
|
||||
{
|
||||
/*
|
||||
{
|
||||
@ -105,8 +100,7 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to read assets index file" << path;
|
||||
return false;
|
||||
}
|
||||
@ -120,16 +114,14 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
// Fail if the JSON is invalid.
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
qCritical() << "Failed to parse assets index file:" << parseError.errorString()
|
||||
<< "at offset " << QString::number(parseError.offset);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCritical() << "Failed to parse assets index file:" << parseError.errorString() << "at offset "
|
||||
<< QString::number(parseError.offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the root is an object.
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
if (!jsonDoc.isObject()) {
|
||||
qCritical() << "Invalid assets index JSON: Root should be an array.";
|
||||
return false;
|
||||
}
|
||||
@ -137,22 +129,19 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
|
||||
QJsonObject root = jsonDoc.object();
|
||||
|
||||
QJsonValue isVirtual = root.value("virtual");
|
||||
if (!isVirtual.isUndefined())
|
||||
{
|
||||
if (!isVirtual.isUndefined()) {
|
||||
index.isVirtual = isVirtual.toBool(false);
|
||||
}
|
||||
|
||||
QJsonValue mapToResources = root.value("map_to_resources");
|
||||
if (!mapToResources.isUndefined())
|
||||
{
|
||||
if (!mapToResources.isUndefined()) {
|
||||
index.mapToResources = mapToResources.toBool(false);
|
||||
}
|
||||
|
||||
QJsonValue objects = root.value("objects");
|
||||
QVariantMap map = objects.toVariant().toMap();
|
||||
|
||||
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
|
||||
{
|
||||
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) {
|
||||
// qDebug() << iter.key();
|
||||
|
||||
QVariant variant = iter.value();
|
||||
@ -160,19 +149,14 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
|
||||
|
||||
AssetObject object;
|
||||
|
||||
for (QVariantMap::const_iterator nested_iter = nested_objects.begin();
|
||||
nested_iter != nested_objects.end(); ++nested_iter)
|
||||
{
|
||||
for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); nested_iter != nested_objects.end(); ++nested_iter) {
|
||||
// qDebug() << nested_iter.key() << nested_iter.value().toString();
|
||||
QString key = nested_iter.key();
|
||||
QVariant value = nested_iter.value();
|
||||
|
||||
if (key == "hash")
|
||||
{
|
||||
if (key == "hash") {
|
||||
object.hash = value.toString();
|
||||
}
|
||||
else if (key == "size")
|
||||
{
|
||||
} else if (key == "size") {
|
||||
object.size = value.toDouble();
|
||||
}
|
||||
}
|
||||
@ -184,7 +168,7 @@ bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsInd
|
||||
}
|
||||
|
||||
// FIXME: ugly code duplication
|
||||
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
|
||||
QDir getAssetsDir(const QString& assetsId, const QString& resourcesFolder)
|
||||
{
|
||||
QDir assetsDir = QDir("assets/");
|
||||
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
|
||||
@ -195,26 +179,21 @@ QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
|
||||
QFile indexFile(indexPath);
|
||||
QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
|
||||
|
||||
if (!indexFile.exists())
|
||||
{
|
||||
if (!indexFile.exists()) {
|
||||
qCritical() << "No assets index file" << indexPath << "; can't determine assets path!";
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
AssetsIndex index;
|
||||
if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
|
||||
{
|
||||
if (!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) {
|
||||
qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!";
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
QString targetPath;
|
||||
if(index.isVirtual)
|
||||
{
|
||||
if (index.isVirtual) {
|
||||
return virtualRoot;
|
||||
}
|
||||
else if(index.mapToResources)
|
||||
{
|
||||
} else if (index.mapToResources) {
|
||||
return QDir(resourcesFolder);
|
||||
}
|
||||
return virtualRoot;
|
||||
@ -232,8 +211,7 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
||||
QFile indexFile(indexPath);
|
||||
QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
|
||||
|
||||
if (!indexFile.exists())
|
||||
{
|
||||
if (!indexFile.exists()) {
|
||||
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!";
|
||||
return false;
|
||||
}
|
||||
@ -241,31 +219,25 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
||||
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path();
|
||||
|
||||
AssetsIndex index;
|
||||
if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
|
||||
{
|
||||
if (!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) {
|
||||
qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString targetPath;
|
||||
bool removeLeftovers = false;
|
||||
if(index.isVirtual)
|
||||
{
|
||||
if (index.isVirtual) {
|
||||
targetPath = virtualRoot.path();
|
||||
removeLeftovers = true;
|
||||
qDebug() << "Reconstructing virtual assets folder at" << targetPath;
|
||||
}
|
||||
else if(index.mapToResources)
|
||||
{
|
||||
} else if (index.mapToResources) {
|
||||
targetPath = resourcesFolder;
|
||||
qDebug() << "Reconstructing resources folder at" << targetPath;
|
||||
}
|
||||
|
||||
if (!targetPath.isNull())
|
||||
{
|
||||
if (!targetPath.isNull()) {
|
||||
auto presentFiles = collectPathsFromDir(targetPath);
|
||||
for (QString map : index.objects.keys())
|
||||
{
|
||||
for (QString map : index.objects.keys()) {
|
||||
AssetObject asset_object = index.objects.value(map);
|
||||
QString target_path = FS::PathCombine(targetPath, map);
|
||||
QFile target(target_path);
|
||||
@ -279,8 +251,7 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
||||
|
||||
presentFiles.remove(target_path);
|
||||
|
||||
if (!target.exists())
|
||||
{
|
||||
if (!target.exists()) {
|
||||
QFileInfo info(target_path);
|
||||
QDir target_dir = info.dir();
|
||||
|
||||
@ -293,10 +264,8 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
||||
}
|
||||
|
||||
// TODO: Write last used time to virtualRoot/.lastused
|
||||
if(removeLeftovers)
|
||||
{
|
||||
for(auto & file: presentFiles)
|
||||
{
|
||||
if (removeLeftovers) {
|
||||
for (auto& file : presentFiles) {
|
||||
qDebug() << "Would remove" << file;
|
||||
}
|
||||
}
|
||||
@ -304,16 +273,14 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace AssetsUtils
|
||||
|
||||
NetAction::Ptr AssetObject::getDownloadAction()
|
||||
{
|
||||
QFileInfo objectFile(getLocalPath());
|
||||
if ((!objectFile.isFile()) || (objectFile.size() != size))
|
||||
{
|
||||
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
||||
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
|
||||
if(hash.size())
|
||||
{
|
||||
if (hash.size()) {
|
||||
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
||||
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
|
||||
}
|
||||
@ -341,15 +308,13 @@ QString AssetObject::getRelPath()
|
||||
NetJob::Ptr AssetsIndex::getDownloadJob()
|
||||
{
|
||||
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();
|
||||
if(dl)
|
||||
{
|
||||
if (dl) {
|
||||
job->addNetAction(dl);
|
||||
}
|
||||
}
|
||||
if(job->size())
|
||||
if (job->size())
|
||||
return job;
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -15,13 +15,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include "net/NetAction.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
struct AssetObject
|
||||
{
|
||||
struct AssetObject {
|
||||
QString getRelPath();
|
||||
QUrl getUrl();
|
||||
QString getLocalPath();
|
||||
@ -31,8 +30,7 @@ struct AssetObject
|
||||
qint64 size;
|
||||
};
|
||||
|
||||
struct AssetsIndex
|
||||
{
|
||||
struct AssetsIndex {
|
||||
NetJob::Ptr getDownloadJob();
|
||||
|
||||
QString id;
|
||||
@ -42,12 +40,11 @@ struct AssetsIndex
|
||||
};
|
||||
|
||||
/// FIXME: this is absolutely horrendous. REDO!!!!
|
||||
namespace AssetsUtils
|
||||
{
|
||||
bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index);
|
||||
namespace AssetsUtils {
|
||||
bool loadAssetsIndexJson(const QString& id, const QString& file, AssetsIndex& index);
|
||||
|
||||
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder);
|
||||
QDir getAssetsDir(const QString& assetsId, const QString& resourcesFolder);
|
||||
|
||||
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
|
||||
bool reconstructAssets(QString assetsId, QString resourcesFolder);
|
||||
}
|
||||
} // namespace AssetsUtils
|
||||
|
@ -33,22 +33,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <meta/VersionList.h>
|
||||
#include <meta/Index.h>
|
||||
#include "Component.h"
|
||||
#include <meta/Index.h>
|
||||
#include <meta/VersionList.h>
|
||||
|
||||
#include <QSaveFile>
|
||||
|
||||
#include "meta/Version.h"
|
||||
#include "VersionFile.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include "Application.h"
|
||||
#include "VersionFile.h"
|
||||
#include "meta/Version.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
Component::Component(PackProfile * parent, const QString& uid)
|
||||
Component::Component(PackProfile* parent, const QString& uid)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
@ -56,7 +56,7 @@ Component::Component(PackProfile * parent, const QString& uid)
|
||||
m_uid = uid;
|
||||
}
|
||||
|
||||
Component::Component(PackProfile * parent, std::shared_ptr<Meta::Version> version)
|
||||
Component::Component(PackProfile* parent, std::shared_ptr<Meta::Version> version)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
@ -68,7 +68,7 @@ Component::Component(PackProfile * parent, std::shared_ptr<Meta::Version> versio
|
||||
m_loaded = version->isLoaded();
|
||||
}
|
||||
|
||||
Component::Component(PackProfile * parent, const QString& uid, std::shared_ptr<VersionFile> file)
|
||||
Component::Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
@ -88,33 +88,25 @@ std::shared_ptr<Meta::Version> Component::getMeta()
|
||||
void Component::applyTo(LaunchProfile* profile)
|
||||
{
|
||||
// do not apply disabled components
|
||||
if(!isEnabled())
|
||||
{
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
if (vfile) {
|
||||
vfile->applyTo(profile, m_parent->runtimeContext());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
profile->applyProblemSeverity(getProblemSeverity());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class VersionFile> Component::getVersionFile() const
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(!m_metaVersion->isLoaded())
|
||||
{
|
||||
if (m_metaVersion) {
|
||||
if (!m_metaVersion->isLoaded()) {
|
||||
m_metaVersion->load(Net::Mode::Online);
|
||||
}
|
||||
return m_metaVersion->data();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return m_file;
|
||||
}
|
||||
}
|
||||
@ -122,8 +114,7 @@ std::shared_ptr<class VersionFile> Component::getVersionFile() const
|
||||
std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
|
||||
{
|
||||
// FIXME: what if the metadata index isn't loaded yet?
|
||||
if(APPLICATION->metadataIndex()->hasUid(m_uid))
|
||||
{
|
||||
if (APPLICATION->metadataIndex()->hasUid(m_uid)) {
|
||||
return APPLICATION->metadataIndex()->get(m_uid);
|
||||
}
|
||||
return nullptr;
|
||||
@ -131,12 +122,11 @@ std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
|
||||
|
||||
int Component::getOrder()
|
||||
{
|
||||
if(m_orderOverride)
|
||||
if (m_orderOverride)
|
||||
return m_order;
|
||||
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
if (vfile) {
|
||||
return vfile->order;
|
||||
}
|
||||
return 0;
|
||||
@ -166,13 +156,11 @@ QString Component::getFilename()
|
||||
}
|
||||
QDateTime Component::getReleaseDateTime()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if (m_metaVersion) {
|
||||
return m_metaVersion->time();
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
if (vfile) {
|
||||
return vfile->releaseTime;
|
||||
}
|
||||
// FIXME: fake
|
||||
@ -192,12 +180,10 @@ bool Component::canBeDisabled()
|
||||
bool Component::setEnabled(bool state)
|
||||
{
|
||||
bool intendedDisabled = !state;
|
||||
if (!canBeDisabled())
|
||||
{
|
||||
if (!canBeDisabled()) {
|
||||
intendedDisabled = false;
|
||||
}
|
||||
if(intendedDisabled != m_disabled)
|
||||
{
|
||||
if (intendedDisabled != m_disabled) {
|
||||
m_disabled = intendedDisabled;
|
||||
emit dataChanged();
|
||||
return true;
|
||||
@ -212,10 +198,8 @@ bool Component::isCustom()
|
||||
|
||||
bool Component::isCustomizable()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(getVersionFile())
|
||||
{
|
||||
if (m_metaVersion) {
|
||||
if (getVersionFile()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -227,10 +211,8 @@ bool Component::isRemovable()
|
||||
}
|
||||
bool Component::isRevertible()
|
||||
{
|
||||
if (isCustom())
|
||||
{
|
||||
if(APPLICATION->metadataIndex()->hasUid(m_uid))
|
||||
{
|
||||
if (isCustom()) {
|
||||
if (APPLICATION->metadataIndex()->hasUid(m_uid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -244,10 +226,8 @@ bool Component::isMoveable()
|
||||
bool Component::isVersionChangeable()
|
||||
{
|
||||
auto list = getVersionList();
|
||||
if(list)
|
||||
{
|
||||
if(!list->isLoaded())
|
||||
{
|
||||
if (list) {
|
||||
if (!list->isLoaded()) {
|
||||
list->load(Net::Mode::Online);
|
||||
}
|
||||
return list->count() != 0;
|
||||
@ -257,8 +237,7 @@ bool Component::isVersionChangeable()
|
||||
|
||||
void Component::setImportant(bool state)
|
||||
{
|
||||
if(m_important != state)
|
||||
{
|
||||
if (m_important != state) {
|
||||
m_important = state;
|
||||
emit dataChanged();
|
||||
}
|
||||
@ -267,8 +246,7 @@ void Component::setImportant(bool state)
|
||||
ProblemSeverity Component::getProblemSeverity() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
if (file) {
|
||||
return file->getProblemSeverity();
|
||||
}
|
||||
return ProblemSeverity::Error;
|
||||
@ -277,49 +255,38 @@ ProblemSeverity Component::getProblemSeverity() const
|
||||
const QList<PatchProblem> Component::getProblems() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
if (file) {
|
||||
return file->getProblems();
|
||||
}
|
||||
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
|
||||
return { { ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.") } };
|
||||
}
|
||||
|
||||
void Component::setVersion(const QString& version)
|
||||
{
|
||||
if(version == m_version)
|
||||
{
|
||||
if (version == m_version) {
|
||||
return;
|
||||
}
|
||||
m_version = version;
|
||||
if(m_loaded)
|
||||
{
|
||||
if (m_loaded) {
|
||||
// we are loaded and potentially have state to invalidate
|
||||
if(m_file)
|
||||
{
|
||||
if (m_file) {
|
||||
// we have a file... explicit version has been changed and there is nothing else to do.
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// we don't have a file, therefore we are loaded with metadata
|
||||
m_cachedVersion = version;
|
||||
// see if the meta version is loaded
|
||||
auto metaVersion = APPLICATION->metadataIndex()->get(m_uid, version);
|
||||
if(metaVersion->isLoaded())
|
||||
{
|
||||
if (metaVersion->isLoaded()) {
|
||||
// if yes, we can continue with that.
|
||||
m_metaVersion = metaVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// if not, we need loading
|
||||
m_metaVersion.reset();
|
||||
m_loaded = false;
|
||||
}
|
||||
updateCachedData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// not loaded... assume it will be sorted out later by the update task
|
||||
}
|
||||
emit dataChanged();
|
||||
@ -327,41 +294,33 @@ void Component::setVersion(const QString& version)
|
||||
|
||||
bool Component::customize()
|
||||
{
|
||||
if(isCustom())
|
||||
{
|
||||
if (isCustom()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto filename = getFilename();
|
||||
if(!FS::ensureFilePathExists(filename))
|
||||
{
|
||||
if (!FS::ensureFilePathExists(filename)) {
|
||||
return false;
|
||||
}
|
||||
// FIXME: get rid of this try-catch.
|
||||
try
|
||||
{
|
||||
try {
|
||||
QSaveFile jsonFile(filename);
|
||||
if(!jsonFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!jsonFile.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(!vfile)
|
||||
{
|
||||
if (!vfile) {
|
||||
return false;
|
||||
}
|
||||
auto document = OneSixVersionFormat::versionFileToJson(vfile);
|
||||
jsonFile.write(document.toJson());
|
||||
if(!jsonFile.commit())
|
||||
{
|
||||
if (!jsonFile.commit()) {
|
||||
return false;
|
||||
}
|
||||
m_file = vfile;
|
||||
m_metaVersion.reset();
|
||||
emit dataChanged();
|
||||
}
|
||||
catch (const Exception &error)
|
||||
{
|
||||
} catch (const Exception& error) {
|
||||
qWarning() << "Version could not be loaded:" << error.cause();
|
||||
}
|
||||
return true;
|
||||
@ -369,31 +328,25 @@ bool Component::customize()
|
||||
|
||||
bool Component::revert()
|
||||
{
|
||||
if(!isCustom())
|
||||
{
|
||||
if (!isCustom()) {
|
||||
// already not custom
|
||||
return true;
|
||||
}
|
||||
auto filename = getFilename();
|
||||
bool result = true;
|
||||
// just kill the file and reload
|
||||
if(QFile::exists(filename))
|
||||
{
|
||||
if (QFile::exists(filename)) {
|
||||
result = QFile::remove(filename);
|
||||
}
|
||||
if(result)
|
||||
{
|
||||
if (result) {
|
||||
// file gone...
|
||||
m_file.reset();
|
||||
|
||||
// check local cache for metadata...
|
||||
auto version = APPLICATION->metadataIndex()->get(m_uid, m_version);
|
||||
if(version->isLoaded())
|
||||
{
|
||||
if (version->isLoaded()) {
|
||||
m_metaVersion = version;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
m_metaVersion.reset();
|
||||
m_loaded = false;
|
||||
}
|
||||
@ -407,23 +360,19 @@ bool Component::revert()
|
||||
* By default, only uids are compared for set operations.
|
||||
* This compares all fields of the Require structs in the sets.
|
||||
*/
|
||||
static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b)
|
||||
static bool deepCompare(const std::set<Meta::Require>& a, const std::set<Meta::Require>& b)
|
||||
{
|
||||
// NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
|
||||
if(a.size() != b.size())
|
||||
{
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
for(const auto & reqA :a)
|
||||
{
|
||||
const auto &iter2 = b.find(reqA);
|
||||
if(iter2 == b.cend())
|
||||
{
|
||||
for (const auto& reqA : a) {
|
||||
const auto& iter2 = b.find(reqA);
|
||||
if (iter2 == b.cend()) {
|
||||
return false;
|
||||
}
|
||||
const auto & reqB = *iter2;
|
||||
if(!reqA.deepEquals(reqB))
|
||||
{
|
||||
const auto& reqB = *iter2;
|
||||
if (!reqA.deepEquals(reqB)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -433,41 +382,32 @@ static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::
|
||||
void Component::updateCachedData()
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
if (file) {
|
||||
bool changed = false;
|
||||
if(m_cachedName != file->name)
|
||||
{
|
||||
if (m_cachedName != file->name) {
|
||||
m_cachedName = file->name;
|
||||
changed = true;
|
||||
}
|
||||
if(m_cachedVersion != file->version)
|
||||
{
|
||||
if (m_cachedVersion != file->version) {
|
||||
m_cachedVersion = file->version;
|
||||
changed = true;
|
||||
}
|
||||
if(m_cachedVolatile != file->m_volatile)
|
||||
{
|
||||
if (m_cachedVolatile != file->m_volatile) {
|
||||
m_cachedVolatile = file->m_volatile;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedRequires, file->m_requires))
|
||||
{
|
||||
if (!deepCompare(m_cachedRequires, file->m_requires)) {
|
||||
m_cachedRequires = file->m_requires;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
||||
{
|
||||
if (!deepCompare(m_cachedConflicts, file->conflicts)) {
|
||||
m_cachedConflicts = file->conflicts;
|
||||
changed = true;
|
||||
}
|
||||
if(changed)
|
||||
{
|
||||
if (changed) {
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// in case we removed all the metadata
|
||||
m_cachedRequires.clear();
|
||||
m_cachedConflicts.clear();
|
||||
|
@ -1,38 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QList>
|
||||
#include <QJsonDocument>
|
||||
#include <QDateTime>
|
||||
#include "meta/JsonFormat.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QList>
|
||||
#include <memory>
|
||||
#include "ProblemProvider.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "meta/JsonFormat.h"
|
||||
|
||||
class PackProfile;
|
||||
class LaunchProfile;
|
||||
namespace Meta
|
||||
{
|
||||
class Version;
|
||||
class VersionList;
|
||||
}
|
||||
namespace Meta {
|
||||
class Version;
|
||||
class VersionList;
|
||||
} // namespace Meta
|
||||
class VersionFile;
|
||||
|
||||
class Component : public QObject, public ProblemProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Component(PackProfile * parent, const QString &uid);
|
||||
class Component : public QObject, public ProblemProvider {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Component(PackProfile* parent, const QString& uid);
|
||||
|
||||
// DEPRECATED: remove these constructors?
|
||||
Component(PackProfile * parent, std::shared_ptr<Meta::Version> version);
|
||||
Component(PackProfile * parent, const QString & uid, std::shared_ptr<VersionFile> file);
|
||||
Component(PackProfile* parent, std::shared_ptr<Meta::Version> version);
|
||||
Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file);
|
||||
|
||||
virtual ~Component(){}
|
||||
virtual ~Component() {}
|
||||
|
||||
void applyTo(LaunchProfile *profile);
|
||||
void applyTo(LaunchProfile* profile);
|
||||
|
||||
bool isEnabled();
|
||||
bool setEnabled (bool state);
|
||||
bool setEnabled(bool state);
|
||||
bool canBeDisabled();
|
||||
|
||||
bool isMoveable();
|
||||
@ -57,23 +55,22 @@ public:
|
||||
std::shared_ptr<class VersionFile> getVersionFile() const;
|
||||
std::shared_ptr<class Meta::VersionList> getVersionList() const;
|
||||
|
||||
void setImportant (bool state);
|
||||
|
||||
void setImportant(bool state);
|
||||
|
||||
const QList<PatchProblem> getProblems() const override;
|
||||
ProblemSeverity getProblemSeverity() const override;
|
||||
|
||||
void setVersion(const QString & version);
|
||||
void setVersion(const QString& version);
|
||||
bool customize();
|
||||
bool revert();
|
||||
|
||||
void updateCachedData();
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
||||
public: /* data */
|
||||
PackProfile * m_parent;
|
||||
public: /* data */
|
||||
PackProfile* m_parent;
|
||||
|
||||
// BEGIN: persistent component list properties
|
||||
/// ID of the component
|
||||
|
@ -1,16 +1,16 @@
|
||||
#include "ComponentUpdateTask.h"
|
||||
|
||||
#include "PackProfile_p.h"
|
||||
#include "PackProfile.h"
|
||||
#include "Component.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "meta/Version.h"
|
||||
#include "ComponentUpdateTask_p.h"
|
||||
#include "cassert"
|
||||
#include "Version.h"
|
||||
#include "net/Mode.h"
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include "PackProfile.h"
|
||||
#include "PackProfile_p.h"
|
||||
#include "Version.h"
|
||||
#include "cassert"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@ -32,8 +32,7 @@
|
||||
* If the component list changes, start over.
|
||||
*/
|
||||
|
||||
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent)
|
||||
: Task(parent)
|
||||
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
|
||||
{
|
||||
d.reset(new ComponentUpdateTaskData);
|
||||
d->m_list = list;
|
||||
@ -41,9 +40,7 @@ ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfi
|
||||
d->netmode = netmode;
|
||||
}
|
||||
|
||||
ComponentUpdateTask::~ComponentUpdateTask()
|
||||
{
|
||||
}
|
||||
ComponentUpdateTask::~ComponentUpdateTask() {}
|
||||
|
||||
void ComponentUpdateTask::executeTask()
|
||||
{
|
||||
@ -51,19 +48,12 @@ void ComponentUpdateTask::executeTask()
|
||||
loadComponents();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class LoadResult
|
||||
{
|
||||
LoadedLocal,
|
||||
RequiresRemote,
|
||||
Failed
|
||||
};
|
||||
namespace {
|
||||
enum class LoadResult { LoadedLocal, RequiresRemote, Failed };
|
||||
|
||||
LoadResult composeLoadResult(LoadResult a, LoadResult b)
|
||||
{
|
||||
if (a < b)
|
||||
{
|
||||
if (a < b) {
|
||||
return b;
|
||||
}
|
||||
return a;
|
||||
@ -71,28 +61,24 @@ LoadResult composeLoadResult(LoadResult a, LoadResult b)
|
||||
|
||||
static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net::Mode netmode)
|
||||
{
|
||||
if(component->m_loaded)
|
||||
{
|
||||
if (component->m_loaded) {
|
||||
qDebug() << component->getName() << "is already loaded";
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
|
||||
LoadResult result = LoadResult::Failed;
|
||||
auto customPatchFilename = component->getFilename();
|
||||
if(QFile::exists(customPatchFilename))
|
||||
{
|
||||
if (QFile::exists(customPatchFilename)) {
|
||||
// if local file exists...
|
||||
|
||||
// check for uid problems inside...
|
||||
bool fileChanged = false;
|
||||
auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false);
|
||||
if(file->uid != component->m_uid)
|
||||
{
|
||||
if (file->uid != component->m_uid) {
|
||||
file->uid = component->m_uid;
|
||||
fileChanged = true;
|
||||
}
|
||||
if(fileChanged)
|
||||
{
|
||||
if (fileChanged) {
|
||||
// FIXME: @QUALITY do not ignore return value
|
||||
ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename);
|
||||
}
|
||||
@ -100,21 +86,16 @@ static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net
|
||||
component->m_file = file;
|
||||
component->m_loaded = true;
|
||||
result = LoadResult::LoadedLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
auto metaVersion = APPLICATION->metadataIndex()->get(component->m_uid, component->m_version);
|
||||
component->m_metaVersion = metaVersion;
|
||||
if(metaVersion->isLoaded())
|
||||
{
|
||||
if (metaVersion->isLoaded()) {
|
||||
component->m_loaded = true;
|
||||
result = LoadResult::LoadedLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
metaVersion->load(netmode);
|
||||
loadTask = metaVersion->getCurrentTask();
|
||||
if(loadTask)
|
||||
if (loadTask)
|
||||
result = LoadResult::RequiresRemote;
|
||||
else if (metaVersion->isLoaded())
|
||||
result = LoadResult::LoadedLocal;
|
||||
@ -155,21 +136,19 @@ static LoadResult loadPackProfile(ComponentPtr component, Task::Ptr& loadTask, N
|
||||
static LoadResult loadIndex(Task::Ptr& loadTask, Net::Mode netmode)
|
||||
{
|
||||
// FIXME: DECIDE. do we want to run the update task anyway?
|
||||
if(APPLICATION->metadataIndex()->isLoaded())
|
||||
{
|
||||
if (APPLICATION->metadataIndex()->isLoaded()) {
|
||||
qDebug() << "Index is already loaded";
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
APPLICATION->metadataIndex()->load(netmode);
|
||||
loadTask = APPLICATION->metadataIndex()->getCurrentTask();
|
||||
if(loadTask)
|
||||
{
|
||||
if (loadTask) {
|
||||
return LoadResult::RequiresRemote;
|
||||
}
|
||||
// FIXME: this is assuming the load succeeded... did it really?
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComponentUpdateTask::loadComponents()
|
||||
{
|
||||
@ -183,34 +162,24 @@ void ComponentUpdateTask::loadComponents()
|
||||
Task::Ptr indexLoadTask;
|
||||
auto singleResult = loadIndex(indexLoadTask, d->netmode);
|
||||
result = composeLoadResult(result, singleResult);
|
||||
if(indexLoadTask)
|
||||
{
|
||||
if (indexLoadTask) {
|
||||
qDebug() << "Remote loading is being run for metadata index";
|
||||
RemoteLoadStatus status;
|
||||
status.type = RemoteLoadStatus::Type::Index;
|
||||
d->remoteLoadStatusList.append(status);
|
||||
connect(indexLoadTask.get(), &Task::succeeded, [=]()
|
||||
{
|
||||
remoteLoadSucceeded(taskIndex);
|
||||
});
|
||||
connect(indexLoadTask.get(), &Task::failed, [=](const QString & error)
|
||||
{
|
||||
remoteLoadFailed(taskIndex, error);
|
||||
});
|
||||
connect(indexLoadTask.get(), &Task::aborted, [=]()
|
||||
{
|
||||
remoteLoadFailed(taskIndex, tr("Aborted"));
|
||||
});
|
||||
connect(indexLoadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
|
||||
connect(indexLoadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
||||
connect(indexLoadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
||||
taskIndex++;
|
||||
}
|
||||
}
|
||||
// load all the components OR their lists...
|
||||
for (auto component: d->m_list->d->components)
|
||||
{
|
||||
for (auto component : d->m_list->d->components) {
|
||||
Task::Ptr loadTask;
|
||||
LoadResult singleResult;
|
||||
RemoteLoadStatus::Type loadType;
|
||||
// FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that...
|
||||
// FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now,
|
||||
// ignore all that...
|
||||
#if 0
|
||||
switch(d->mode)
|
||||
{
|
||||
@ -231,26 +200,15 @@ void ComponentUpdateTask::loadComponents()
|
||||
singleResult = loadComponent(component, loadTask, d->netmode);
|
||||
loadType = RemoteLoadStatus::Type::Version;
|
||||
#endif
|
||||
if(singleResult == LoadResult::LoadedLocal)
|
||||
{
|
||||
if (singleResult == LoadResult::LoadedLocal) {
|
||||
component->updateCachedData();
|
||||
}
|
||||
result = composeLoadResult(result, singleResult);
|
||||
if (loadTask)
|
||||
{
|
||||
if (loadTask) {
|
||||
qDebug() << "Remote loading is being run for" << component->getName();
|
||||
connect(loadTask.get(), &Task::succeeded, [=]()
|
||||
{
|
||||
remoteLoadSucceeded(taskIndex);
|
||||
});
|
||||
connect(loadTask.get(), &Task::failed, [=](const QString & error)
|
||||
{
|
||||
remoteLoadFailed(taskIndex, error);
|
||||
});
|
||||
connect(loadTask.get(), &Task::aborted, [=]()
|
||||
{
|
||||
remoteLoadFailed(taskIndex, tr("Aborted"));
|
||||
});
|
||||
connect(loadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
|
||||
connect(loadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
||||
connect(loadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
||||
RemoteLoadStatus status;
|
||||
status.type = loadType;
|
||||
status.PackProfileIndex = componentIndex;
|
||||
@ -260,95 +218,73 @@ void ComponentUpdateTask::loadComponents()
|
||||
componentIndex++;
|
||||
}
|
||||
d->remoteTasksInProgress = taskIndex;
|
||||
switch(result)
|
||||
{
|
||||
case LoadResult::LoadedLocal:
|
||||
{
|
||||
switch (result) {
|
||||
case LoadResult::LoadedLocal: {
|
||||
// Everything got loaded. Advance to dependency resolution.
|
||||
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
|
||||
break;
|
||||
}
|
||||
case LoadResult::RequiresRemote:
|
||||
{
|
||||
case LoadResult::RequiresRemote: {
|
||||
// we wait for signals.
|
||||
break;
|
||||
}
|
||||
case LoadResult::Failed:
|
||||
{
|
||||
case LoadResult::Failed: {
|
||||
emitFailed(tr("Some component metadata load tasks failed."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct RequireEx : public Meta::Require
|
||||
{
|
||||
size_t indexOfFirstDependee = 0;
|
||||
};
|
||||
struct RequireCompositionResult
|
||||
{
|
||||
bool ok;
|
||||
RequireEx outcome;
|
||||
};
|
||||
using RequireExSet = std::set<RequireEx>;
|
||||
}
|
||||
namespace {
|
||||
struct RequireEx : public Meta::Require {
|
||||
size_t indexOfFirstDependee = 0;
|
||||
};
|
||||
struct RequireCompositionResult {
|
||||
bool ok;
|
||||
RequireEx outcome;
|
||||
};
|
||||
using RequireExSet = std::set<RequireEx>;
|
||||
} // namespace
|
||||
|
||||
static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b)
|
||||
static RequireCompositionResult composeRequirement(const RequireEx& a, const RequireEx& b)
|
||||
{
|
||||
assert(a.uid == b.uid);
|
||||
RequireEx out;
|
||||
out.uid = a.uid;
|
||||
out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee);
|
||||
if(a.equalsVersion.isEmpty())
|
||||
{
|
||||
if (a.equalsVersion.isEmpty()) {
|
||||
out.equalsVersion = b.equalsVersion;
|
||||
}
|
||||
else if (b.equalsVersion.isEmpty())
|
||||
{
|
||||
} else if (b.equalsVersion.isEmpty()) {
|
||||
out.equalsVersion = a.equalsVersion;
|
||||
}
|
||||
else if (a.equalsVersion == b.equalsVersion)
|
||||
{
|
||||
} else if (a.equalsVersion == b.equalsVersion) {
|
||||
out.equalsVersion = a.equalsVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// FIXME: mark error as explicit version conflict
|
||||
return {false, out};
|
||||
return { false, out };
|
||||
}
|
||||
|
||||
if(a.suggests.isEmpty())
|
||||
{
|
||||
if (a.suggests.isEmpty()) {
|
||||
out.suggests = b.suggests;
|
||||
}
|
||||
else if (b.suggests.isEmpty())
|
||||
{
|
||||
} else if (b.suggests.isEmpty()) {
|
||||
out.suggests = a.suggests;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
Version aVer(a.suggests);
|
||||
Version bVer(b.suggests);
|
||||
out.suggests = (aVer < bVer ? b.suggests : a.suggests);
|
||||
}
|
||||
return {true, out};
|
||||
return { true, out };
|
||||
}
|
||||
|
||||
// gather the requirements from all components, finding any obvious conflicts
|
||||
static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output)
|
||||
static bool gatherRequirementsFromComponents(const ComponentContainer& input, RequireExSet& output)
|
||||
{
|
||||
bool succeeded = true;
|
||||
size_t componentNum = 0;
|
||||
for(auto component: input)
|
||||
{
|
||||
auto &componentRequires = component->m_cachedRequires;
|
||||
for(const auto & componentRequire: componentRequires)
|
||||
{
|
||||
auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){
|
||||
return req.uid == componentRequire.uid;
|
||||
});
|
||||
for (auto component : input) {
|
||||
auto& componentRequires = component->m_cachedRequires;
|
||||
for (const auto& componentRequire : componentRequires) {
|
||||
auto found = std::find_if(output.cbegin(), output.cend(),
|
||||
[componentRequire](const Meta::Require& req) { return req.uid == componentRequire.uid; });
|
||||
|
||||
RequireEx componenRequireEx;
|
||||
componenRequireEx.uid = componentRequire.uid;
|
||||
@ -356,29 +292,18 @@ static bool gatherRequirementsFromComponents(const ComponentContainer & input, R
|
||||
componenRequireEx.equalsVersion = componentRequire.equalsVersion;
|
||||
componenRequireEx.indexOfFirstDependee = componentNum;
|
||||
|
||||
if(found != output.cend())
|
||||
{
|
||||
if (found != output.cend()) {
|
||||
// found... process it further
|
||||
auto result = composeRequirement(componenRequireEx, *found);
|
||||
if(result.ok)
|
||||
{
|
||||
if (result.ok) {
|
||||
output.erase(componenRequireEx);
|
||||
output.insert(result.outcome);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical()
|
||||
<< "Conflicting requirements:"
|
||||
<< componentRequire.uid
|
||||
<< "versions:"
|
||||
<< componentRequire.equalsVersion
|
||||
<< ";"
|
||||
<< (*found).equalsVersion;
|
||||
} else {
|
||||
qCritical() << "Conflicting requirements:" << componentRequire.uid << "versions:" << componentRequire.equalsVersion
|
||||
<< ";" << (*found).equalsVersion;
|
||||
}
|
||||
succeeded &= result.ok;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// not found, accumulate
|
||||
output.insert(componenRequireEx);
|
||||
}
|
||||
@ -389,19 +314,17 @@ static bool gatherRequirementsFromComponents(const ComponentContainer & input, R
|
||||
}
|
||||
|
||||
/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps)
|
||||
static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove)
|
||||
static void getTrivialRemovals(const ComponentContainer& components, const RequireExSet& reqs, QStringList& toRemove)
|
||||
{
|
||||
for(const auto & component: components)
|
||||
{
|
||||
if(!component->m_dependencyOnly)
|
||||
for (const auto& component : components) {
|
||||
if (!component->m_dependencyOnly)
|
||||
continue;
|
||||
if(!component->m_cachedVolatile)
|
||||
if (!component->m_cachedVolatile)
|
||||
continue;
|
||||
RequireEx reqNeedle;
|
||||
reqNeedle.uid = component->m_uid;
|
||||
const auto iter = reqs.find(reqNeedle);
|
||||
if(iter == reqs.cend())
|
||||
{
|
||||
if (iter == reqs.cend()) {
|
||||
toRemove.append(component->m_uid);
|
||||
}
|
||||
}
|
||||
@ -415,60 +338,40 @@ static void getTrivialRemovals(const ComponentContainer & components, const Requ
|
||||
* toAdd - set of requirements than mean adding a new component
|
||||
* toChange - set of requirements that mean changing version of an existing component
|
||||
*/
|
||||
static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange)
|
||||
static bool getTrivialComponentChanges(const ComponentIndex& index, const RequireExSet& input, RequireExSet& toAdd, RequireExSet& toChange)
|
||||
{
|
||||
enum class Decision
|
||||
{
|
||||
Undetermined,
|
||||
Met,
|
||||
Missing,
|
||||
VersionNotSame,
|
||||
LockedVersionNotSame
|
||||
} decision = Decision::Undetermined;
|
||||
enum class Decision { Undetermined, Met, Missing, VersionNotSame, LockedVersionNotSame } decision = Decision::Undetermined;
|
||||
|
||||
QString reqStr;
|
||||
bool succeeded = true;
|
||||
// list the composed requirements and say if they are met or unmet
|
||||
for(auto & req: input)
|
||||
{
|
||||
do
|
||||
{
|
||||
if(req.equalsVersion.isEmpty())
|
||||
{
|
||||
for (auto& req : input) {
|
||||
do {
|
||||
if (req.equalsVersion.isEmpty()) {
|
||||
reqStr = QString("Req: %1").arg(req.uid);
|
||||
if(index.contains(req.uid))
|
||||
{
|
||||
if (index.contains(req.uid)) {
|
||||
decision = Decision::Met;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
toAdd.insert(req);
|
||||
decision = Decision::Missing;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion);
|
||||
const auto & compIter = index.find(req.uid);
|
||||
if(compIter == index.cend())
|
||||
{
|
||||
const auto& compIter = index.find(req.uid);
|
||||
if (compIter == index.cend()) {
|
||||
toAdd.insert(req);
|
||||
decision = Decision::Missing;
|
||||
break;
|
||||
}
|
||||
auto & comp = (*compIter);
|
||||
if(comp->getVersion() != req.equalsVersion)
|
||||
{
|
||||
if(comp->isCustom()) {
|
||||
auto& comp = (*compIter);
|
||||
if (comp->getVersion() != req.equalsVersion) {
|
||||
if (comp->isCustom()) {
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
} else {
|
||||
if(comp->m_dependencyOnly)
|
||||
{
|
||||
if (comp->m_dependencyOnly) {
|
||||
decision = Decision::VersionNotSame;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
}
|
||||
}
|
||||
@ -476,9 +379,8 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi
|
||||
}
|
||||
decision = Decision::Met;
|
||||
}
|
||||
} while(false);
|
||||
switch(decision)
|
||||
{
|
||||
} while (false);
|
||||
switch (decision) {
|
||||
case Decision::Undetermined:
|
||||
qCritical() << "No decision for" << reqStr;
|
||||
succeeded = false;
|
||||
@ -520,26 +422,22 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
*
|
||||
* NOTE: this is a placeholder and should eventually be replaced with something 'serious'
|
||||
*/
|
||||
auto & components = d->m_list->d->components;
|
||||
auto & componentIndex = d->m_list->d->componentIndex;
|
||||
auto& components = d->m_list->d->components;
|
||||
auto& componentIndex = d->m_list->d->componentIndex;
|
||||
|
||||
RequireExSet allRequires;
|
||||
QStringList toRemove;
|
||||
do
|
||||
{
|
||||
do {
|
||||
allRequires.clear();
|
||||
toRemove.clear();
|
||||
if(!gatherRequirementsFromComponents(components, allRequires))
|
||||
{
|
||||
if (!gatherRequirementsFromComponents(components, allRequires)) {
|
||||
emitFailed(tr("Conflicting requirements detected during dependency checking!"));
|
||||
return;
|
||||
}
|
||||
getTrivialRemovals(components, allRequires, toRemove);
|
||||
if(!toRemove.isEmpty())
|
||||
{
|
||||
if (!toRemove.isEmpty()) {
|
||||
qDebug() << "Removing obsolete components...";
|
||||
for(auto & remove : toRemove)
|
||||
{
|
||||
for (auto& remove : toRemove) {
|
||||
qDebug() << "Removing" << remove;
|
||||
d->m_list->remove(remove);
|
||||
}
|
||||
@ -548,69 +446,50 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
RequireExSet toAdd;
|
||||
RequireExSet toChange;
|
||||
bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
|
||||
if(!succeeded)
|
||||
{
|
||||
if (!succeeded) {
|
||||
emitFailed(tr("Instance has conflicting dependencies."));
|
||||
return;
|
||||
}
|
||||
if(checkOnly)
|
||||
{
|
||||
if(toAdd.size() || toChange.size())
|
||||
{
|
||||
if (checkOnly) {
|
||||
if (toAdd.size() || toChange.size()) {
|
||||
emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool recursionNeeded = false;
|
||||
if(toAdd.size())
|
||||
{
|
||||
if (toAdd.size()) {
|
||||
// add stuff...
|
||||
for(auto &add: toAdd)
|
||||
{
|
||||
for (auto& add : toAdd) {
|
||||
auto component = makeShared<Component>(d->m_list, add.uid);
|
||||
if(!add.equalsVersion.isEmpty())
|
||||
{
|
||||
if (!add.equalsVersion.isEmpty()) {
|
||||
// exact version
|
||||
qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
|
||||
component->m_version = add.equalsVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// version needs to be decided
|
||||
qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
|
||||
// ############################################################################################################
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
if(!add.suggests.isEmpty())
|
||||
{
|
||||
// ############################################################################################################
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
if (!add.suggests.isEmpty()) {
|
||||
component->m_version = add.suggests;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(add.uid == "org.lwjgl")
|
||||
{
|
||||
} else {
|
||||
if (add.uid == "org.lwjgl") {
|
||||
component->m_version = "2.9.1";
|
||||
}
|
||||
else if (add.uid == "org.lwjgl3")
|
||||
{
|
||||
} else if (add.uid == "org.lwjgl3") {
|
||||
component->m_version = "3.1.2";
|
||||
}
|
||||
else if (add.uid == "net.fabricmc.intermediary" || add.uid == "org.quiltmc.hashed")
|
||||
{
|
||||
auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){
|
||||
return cmp->getID() == "net.minecraft";
|
||||
});
|
||||
if(minecraft != components.end()) {
|
||||
} else if (add.uid == "net.fabricmc.intermediary" || add.uid == "org.quiltmc.hashed") {
|
||||
auto minecraft = std::find_if(components.begin(), components.end(),
|
||||
[](ComponentPtr& cmp) { return cmp->getID() == "net.minecraft"; });
|
||||
if (minecraft != components.end()) {
|
||||
component->m_version = (*minecraft)->getVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
// ############################################################################################################
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
// ############################################################################################################
|
||||
}
|
||||
component->m_dependencyOnly = true;
|
||||
// FIXME: this should not work directly with the component list
|
||||
@ -619,11 +498,9 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
}
|
||||
recursionNeeded = true;
|
||||
}
|
||||
if(toChange.size())
|
||||
{
|
||||
if (toChange.size()) {
|
||||
// change a version of something that exists
|
||||
for(auto &change: toChange)
|
||||
{
|
||||
for (auto& change : toChange) {
|
||||
// FIXME: this should not work directly with the component list
|
||||
qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion;
|
||||
auto component = componentIndex[change.uid];
|
||||
@ -632,31 +509,26 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
recursionNeeded = true;
|
||||
}
|
||||
|
||||
if(recursionNeeded)
|
||||
{
|
||||
if (recursionNeeded) {
|
||||
loadComponents();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
||||
{
|
||||
auto &taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||
if(taskSlot.finished)
|
||||
{
|
||||
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||
if (taskSlot.finished) {
|
||||
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
||||
return;
|
||||
}
|
||||
qDebug() << "Remote task" << taskIndex << "succeeded";
|
||||
taskSlot.succeeded = false;
|
||||
taskSlot.finished = true;
|
||||
d->remoteTasksInProgress --;
|
||||
d->remoteTasksInProgress--;
|
||||
// update the cached data of the component from the downloaded version file.
|
||||
if (taskSlot.type == RemoteLoadStatus::Type::Version)
|
||||
{
|
||||
if (taskSlot.type == RemoteLoadStatus::Type::Version) {
|
||||
auto component = d->m_list->getComponent(taskSlot.PackProfileIndex);
|
||||
component->m_loaded = true;
|
||||
component->updateCachedData();
|
||||
@ -664,12 +536,10 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
||||
checkIfAllFinished();
|
||||
}
|
||||
|
||||
|
||||
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
|
||||
{
|
||||
auto &taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||
if(taskSlot.finished)
|
||||
{
|
||||
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||
if (taskSlot.finished) {
|
||||
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
||||
return;
|
||||
}
|
||||
@ -678,31 +548,25 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
|
||||
taskSlot.succeeded = false;
|
||||
taskSlot.finished = true;
|
||||
taskSlot.error = msg;
|
||||
d->remoteTasksInProgress --;
|
||||
d->remoteTasksInProgress--;
|
||||
checkIfAllFinished();
|
||||
}
|
||||
|
||||
void ComponentUpdateTask::checkIfAllFinished()
|
||||
{
|
||||
if(d->remoteTasksInProgress)
|
||||
{
|
||||
if (d->remoteTasksInProgress) {
|
||||
// not yet...
|
||||
return;
|
||||
}
|
||||
if(d->remoteLoadSuccessful)
|
||||
{
|
||||
if (d->remoteLoadSuccessful) {
|
||||
// nothing bad happened... clear the temp load status and proceed with looking at dependencies
|
||||
d->remoteLoadStatusList.clear();
|
||||
resolveDependencies(d->mode == Mode::Launch);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// remote load failed... report error and bail
|
||||
QStringList allErrorsList;
|
||||
for(auto & item: d->remoteLoadStatusList)
|
||||
{
|
||||
if(!item.succeeded)
|
||||
{
|
||||
for (auto& item : d->remoteLoadStatusList) {
|
||||
if (!item.succeeded) {
|
||||
allErrorsList.append(item.error);
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/Mode.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include <memory>
|
||||
class PackProfile;
|
||||
struct ComponentUpdateTaskData;
|
||||
|
||||
class ComponentUpdateTask : public Task
|
||||
{
|
||||
class ComponentUpdateTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Launch,
|
||||
Resolution
|
||||
};
|
||||
public:
|
||||
enum class Mode { Launch, Resolution };
|
||||
|
||||
public:
|
||||
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile * list, QObject *parent = 0);
|
||||
public:
|
||||
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0);
|
||||
virtual ~ComponentUpdateTask();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void executeTask();
|
||||
|
||||
private:
|
||||
private:
|
||||
void loadComponents();
|
||||
void resolveDependencies(bool checkOnly);
|
||||
|
||||
void remoteLoadSucceeded(size_t index);
|
||||
void remoteLoadFailed(size_t index, const QString &msg);
|
||||
void remoteLoadFailed(size_t index, const QString& msg);
|
||||
void checkIfAllFinished();
|
||||
|
||||
private:
|
||||
private:
|
||||
std::unique_ptr<ComponentUpdateTaskData> d;
|
||||
};
|
||||
|
@ -1,29 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <cstddef>
|
||||
#include "net/Mode.h"
|
||||
|
||||
class PackProfile;
|
||||
|
||||
struct RemoteLoadStatus
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
Index,
|
||||
List,
|
||||
Version
|
||||
} type = Type::Version;
|
||||
struct RemoteLoadStatus {
|
||||
enum class Type { Index, List, Version } type = Type::Version;
|
||||
size_t PackProfileIndex = 0;
|
||||
bool finished = false;
|
||||
bool succeeded = false;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct ComponentUpdateTaskData
|
||||
{
|
||||
PackProfile * m_list = nullptr;
|
||||
struct ComponentUpdateTaskData {
|
||||
PackProfile* m_list = nullptr;
|
||||
QList<RemoteLoadStatus> remoteLoadStatusList;
|
||||
bool remoteLoadSuccessful = true;
|
||||
size_t remoteTasksInProgress = 0;
|
||||
|
@ -35,22 +35,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
#include "DefaultVariable.h"
|
||||
|
||||
struct GradleSpecifier
|
||||
{
|
||||
GradleSpecifier()
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
GradleSpecifier(QString value)
|
||||
{
|
||||
operator=(value);
|
||||
}
|
||||
GradleSpecifier & operator =(const QString & value)
|
||||
struct GradleSpecifier {
|
||||
GradleSpecifier() { m_valid = false; }
|
||||
GradleSpecifier(QString value) { operator=(value); }
|
||||
GradleSpecifier& operator=(const QString& value)
|
||||
{
|
||||
/*
|
||||
org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar
|
||||
@ -61,10 +54,13 @@ struct GradleSpecifier
|
||||
4 "jdk15"
|
||||
5 "jar"
|
||||
*/
|
||||
QRegularExpression matcher(QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?"));
|
||||
QRegularExpression matcher(
|
||||
QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)"
|
||||
"(?::([^:@]+))?"
|
||||
"(?:@([^:@]+))?"));
|
||||
QRegularExpressionMatch match = matcher.match(value);
|
||||
m_valid = match.hasMatch();
|
||||
if(!m_valid) {
|
||||
if (!m_valid) {
|
||||
m_invalidValue = value;
|
||||
return *this;
|
||||
}
|
||||
@ -73,53 +69,46 @@ struct GradleSpecifier
|
||||
m_artifactId = match.captured(2);
|
||||
m_version = match.captured(3);
|
||||
m_classifier = match.captured(4);
|
||||
if(match.lastCapturedIndex() >= 5)
|
||||
{
|
||||
if (match.lastCapturedIndex() >= 5) {
|
||||
m_extension = match.captured(5);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
QString serialize() const
|
||||
{
|
||||
if(!m_valid) {
|
||||
if (!m_valid) {
|
||||
return m_invalidValue;
|
||||
}
|
||||
QString retval = m_groupId + ":" + m_artifactId + ":" + m_version;
|
||||
if(!m_classifier.isEmpty())
|
||||
{
|
||||
if (!m_classifier.isEmpty()) {
|
||||
retval += ":" + m_classifier;
|
||||
}
|
||||
if(m_extension.isExplicit())
|
||||
{
|
||||
if (m_extension.isExplicit()) {
|
||||
retval += "@" + m_extension;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
QString getFileName() const
|
||||
{
|
||||
if(!m_valid) {
|
||||
if (!m_valid) {
|
||||
return QString();
|
||||
}
|
||||
QString filename = m_artifactId + '-' + m_version;
|
||||
if(!m_classifier.isEmpty())
|
||||
{
|
||||
if (!m_classifier.isEmpty()) {
|
||||
filename += "-" + m_classifier;
|
||||
}
|
||||
filename += "." + m_extension;
|
||||
return filename;
|
||||
}
|
||||
QString toPath(const QString & filenameOverride = QString()) const
|
||||
QString toPath(const QString& filenameOverride = QString()) const
|
||||
{
|
||||
if(!m_valid) {
|
||||
if (!m_valid) {
|
||||
return QString();
|
||||
}
|
||||
QString filename;
|
||||
if(filenameOverride.isEmpty())
|
||||
{
|
||||
if (filenameOverride.isEmpty()) {
|
||||
filename = getFileName();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
filename = filenameOverride;
|
||||
}
|
||||
QString path = m_groupId;
|
||||
@ -127,57 +116,34 @@ struct GradleSpecifier
|
||||
path += '/' + m_artifactId + '/' + m_version + '/' + filename;
|
||||
return path;
|
||||
}
|
||||
inline bool valid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
inline QString version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
inline QString groupId() const
|
||||
{
|
||||
return m_groupId;
|
||||
}
|
||||
inline QString artifactId() const
|
||||
{
|
||||
return m_artifactId;
|
||||
}
|
||||
inline void setClassifier(const QString & classifier)
|
||||
{
|
||||
m_classifier = classifier;
|
||||
}
|
||||
inline QString classifier() const
|
||||
{
|
||||
return m_classifier;
|
||||
}
|
||||
inline QString extension() const
|
||||
{
|
||||
return m_extension;
|
||||
}
|
||||
inline QString artifactPrefix() const
|
||||
{
|
||||
return m_groupId + ":" + m_artifactId;
|
||||
}
|
||||
bool matchName(const GradleSpecifier & other) const
|
||||
inline bool valid() const { return m_valid; }
|
||||
inline QString version() const { return m_version; }
|
||||
inline QString groupId() const { return m_groupId; }
|
||||
inline QString artifactId() const { return m_artifactId; }
|
||||
inline void setClassifier(const QString& classifier) { m_classifier = classifier; }
|
||||
inline QString classifier() const { return m_classifier; }
|
||||
inline QString extension() const { return m_extension; }
|
||||
inline QString artifactPrefix() const { return m_groupId + ":" + m_artifactId; }
|
||||
bool matchName(const GradleSpecifier& other) const
|
||||
{
|
||||
return other.artifactId() == artifactId() && other.groupId() == groupId() && other.classifier() == classifier();
|
||||
}
|
||||
bool operator==(const GradleSpecifier & other) const
|
||||
bool operator==(const GradleSpecifier& other) const
|
||||
{
|
||||
if(m_groupId != other.m_groupId)
|
||||
if (m_groupId != other.m_groupId)
|
||||
return false;
|
||||
if(m_artifactId != other.m_artifactId)
|
||||
if (m_artifactId != other.m_artifactId)
|
||||
return false;
|
||||
if(m_version != other.m_version)
|
||||
if (m_version != other.m_version)
|
||||
return false;
|
||||
if(m_classifier != other.m_classifier)
|
||||
if (m_classifier != other.m_classifier)
|
||||
return false;
|
||||
if(m_extension != other.m_extension)
|
||||
if (m_extension != other.m_extension)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
|
||||
private:
|
||||
QString m_invalidValue;
|
||||
QString m_groupId;
|
||||
QString m_artifactId;
|
||||
|
@ -55,9 +55,9 @@ void LaunchProfile::clear()
|
||||
m_problemSeverity = ProblemSeverity::None;
|
||||
}
|
||||
|
||||
static void applyString(const QString & from, QString & to)
|
||||
static void applyString(const QString& from, QString& to)
|
||||
{
|
||||
if(from.isEmpty())
|
||||
if (from.isEmpty())
|
||||
return;
|
||||
to = from;
|
||||
}
|
||||
@ -94,8 +94,7 @@ void LaunchProfile::applyMinecraftVersionType(const QString& type)
|
||||
|
||||
void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets)
|
||||
{
|
||||
if(assets)
|
||||
{
|
||||
if (assets) {
|
||||
m_minecraftAssets = assets;
|
||||
}
|
||||
}
|
||||
@ -109,10 +108,8 @@ void LaunchProfile::applyTweakers(const QStringList& tweakers)
|
||||
{
|
||||
// if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence
|
||||
QStringList newTweakers;
|
||||
for(auto & tweaker: m_tweakers)
|
||||
{
|
||||
if (tweakers.contains(tweaker))
|
||||
{
|
||||
for (auto& tweaker : m_tweakers) {
|
||||
if (tweakers.contains(tweaker)) {
|
||||
continue;
|
||||
}
|
||||
newTweakers.append(tweaker);
|
||||
@ -127,13 +124,11 @@ void LaunchProfile::applyJarMods(const QList<LibraryPtr>& jarMods)
|
||||
this->m_jarMods.append(jarMods);
|
||||
}
|
||||
|
||||
static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle)
|
||||
static int findLibraryByName(QList<LibraryPtr>* haystack, const GradleSpecifier& needle)
|
||||
{
|
||||
int retval = -1;
|
||||
for (int i = 0; i < haystack->size(); ++i)
|
||||
{
|
||||
if (haystack->at(i)->rawName().matchName(needle))
|
||||
{
|
||||
for (int i = 0; i < haystack->size(); ++i) {
|
||||
if (haystack->at(i)->rawName().matchName(needle)) {
|
||||
// only one is allowed.
|
||||
if (retval != -1)
|
||||
return -1;
|
||||
@ -145,24 +140,21 @@ static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier
|
||||
|
||||
void LaunchProfile::applyMods(const QList<LibraryPtr>& mods)
|
||||
{
|
||||
QList<LibraryPtr> * list = &m_mods;
|
||||
for(auto & mod: mods)
|
||||
{
|
||||
QList<LibraryPtr>* list = &m_mods;
|
||||
for (auto& mod : mods) {
|
||||
auto modCopy = Library::limitedCopy(mod);
|
||||
|
||||
// find the mod by name.
|
||||
const int index = findLibraryByName(list, mod->rawName());
|
||||
// mod not found? just add it.
|
||||
if (index < 0)
|
||||
{
|
||||
if (index < 0) {
|
||||
list->append(modCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
auto existingLibrary = list->at(index);
|
||||
// if we are higher it means we should update
|
||||
if (Version(mod->version()) > Version(existingLibrary->version()))
|
||||
{
|
||||
if (Version(mod->version()) > Version(existingLibrary->version())) {
|
||||
list->replace(index, modCopy);
|
||||
}
|
||||
}
|
||||
@ -173,16 +165,14 @@ void LaunchProfile::applyCompatibleJavaMajors(QList<int>& javaMajor)
|
||||
m_compatibleJavaMajors.append(javaMajor);
|
||||
}
|
||||
|
||||
void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext)
|
||||
void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext)
|
||||
{
|
||||
if(!library->isActive(runtimeContext))
|
||||
{
|
||||
if (!library->isActive(runtimeContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<LibraryPtr> * list = &m_libraries;
|
||||
if(library->isNative())
|
||||
{
|
||||
QList<LibraryPtr>* list = &m_libraries;
|
||||
if (library->isNative()) {
|
||||
list = &m_nativeLibraries;
|
||||
}
|
||||
|
||||
@ -191,29 +181,25 @@ void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runt
|
||||
// find the library by name.
|
||||
const int index = findLibraryByName(list, library->rawName());
|
||||
// library not found? just add it.
|
||||
if (index < 0)
|
||||
{
|
||||
if (index < 0) {
|
||||
list->append(libraryCopy);
|
||||
return;
|
||||
}
|
||||
|
||||
auto existingLibrary = list->at(index);
|
||||
// if we are higher it means we should update
|
||||
if (Version(library->version()) > Version(existingLibrary->version()))
|
||||
{
|
||||
if (Version(library->version()) > Version(existingLibrary->version())) {
|
||||
list->replace(index, libraryCopy);
|
||||
}
|
||||
}
|
||||
|
||||
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext & runtimeContext)
|
||||
void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext& runtimeContext)
|
||||
{
|
||||
if(!mavenFile->isActive(runtimeContext))
|
||||
{
|
||||
if (!mavenFile->isActive(runtimeContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(mavenFile->isNative())
|
||||
{
|
||||
if (mavenFile->isNative()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -221,16 +207,14 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext &
|
||||
m_mavenFiles.append(Library::limitedCopy(mavenFile));
|
||||
}
|
||||
|
||||
void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext)
|
||||
void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext)
|
||||
{
|
||||
auto lib = agent->library();
|
||||
if(!lib->isActive(runtimeContext))
|
||||
{
|
||||
if (!lib->isActive(runtimeContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(lib->isNative())
|
||||
{
|
||||
if (lib->isNative()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -244,16 +228,14 @@ const LibraryPtr LaunchProfile::getMainJar() const
|
||||
|
||||
void LaunchProfile::applyMainJar(LibraryPtr jar)
|
||||
{
|
||||
if(jar)
|
||||
{
|
||||
if (jar) {
|
||||
m_mainJar = jar;
|
||||
}
|
||||
}
|
||||
|
||||
void LaunchProfile::applyProblemSeverity(ProblemSeverity severity)
|
||||
{
|
||||
if (m_problemSeverity < severity)
|
||||
{
|
||||
if (m_problemSeverity < severity) {
|
||||
m_problemSeverity = severity;
|
||||
}
|
||||
}
|
||||
@ -279,12 +261,12 @@ QString LaunchProfile::getMainClass() const
|
||||
return m_mainClass;
|
||||
}
|
||||
|
||||
const QSet<QString> &LaunchProfile::getTraits() const
|
||||
const QSet<QString>& LaunchProfile::getTraits() const
|
||||
{
|
||||
return m_traits;
|
||||
}
|
||||
|
||||
const QStringList & LaunchProfile::getTweakers() const
|
||||
const QStringList& LaunchProfile::getTweakers() const
|
||||
{
|
||||
return m_tweakers;
|
||||
}
|
||||
@ -306,8 +288,7 @@ QString LaunchProfile::getMinecraftVersionType() const
|
||||
|
||||
std::shared_ptr<MojangAssetIndexInfo> LaunchProfile::getMinecraftAssets() const
|
||||
{
|
||||
if(!m_minecraftAssets)
|
||||
{
|
||||
if (!m_minecraftAssets) {
|
||||
return std::make_shared<MojangAssetIndexInfo>("legacy");
|
||||
}
|
||||
return m_minecraftAssets;
|
||||
@ -318,80 +299,69 @@ QString LaunchProfile::getMinecraftArguments() const
|
||||
return m_minecraftArguments;
|
||||
}
|
||||
|
||||
const QStringList & LaunchProfile::getAddnJvmArguments() const
|
||||
const QStringList& LaunchProfile::getAddnJvmArguments() const
|
||||
{
|
||||
return m_addnJvmArguments;
|
||||
}
|
||||
|
||||
const QList<LibraryPtr> & LaunchProfile::getJarMods() const
|
||||
const QList<LibraryPtr>& LaunchProfile::getJarMods() const
|
||||
{
|
||||
return m_jarMods;
|
||||
}
|
||||
|
||||
const QList<LibraryPtr> & LaunchProfile::getLibraries() const
|
||||
const QList<LibraryPtr>& LaunchProfile::getLibraries() const
|
||||
{
|
||||
return m_libraries;
|
||||
}
|
||||
|
||||
const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const
|
||||
const QList<LibraryPtr>& LaunchProfile::getNativeLibraries() const
|
||||
{
|
||||
return m_nativeLibraries;
|
||||
}
|
||||
|
||||
const QList<LibraryPtr> & LaunchProfile::getMavenFiles() const
|
||||
const QList<LibraryPtr>& LaunchProfile::getMavenFiles() const
|
||||
{
|
||||
return m_mavenFiles;
|
||||
}
|
||||
|
||||
const QList<AgentPtr> & LaunchProfile::getAgents() const
|
||||
const QList<AgentPtr>& LaunchProfile::getAgents() const
|
||||
{
|
||||
return m_agents;
|
||||
}
|
||||
|
||||
const QList<int> & LaunchProfile::getCompatibleJavaMajors() const
|
||||
const QList<int>& LaunchProfile::getCompatibleJavaMajors() const
|
||||
{
|
||||
return m_compatibleJavaMajors;
|
||||
}
|
||||
|
||||
void LaunchProfile::getLibraryFiles(
|
||||
const RuntimeContext & runtimeContext,
|
||||
QStringList& jars,
|
||||
QStringList& nativeJars,
|
||||
const QString& overridePath,
|
||||
const QString& tempPath
|
||||
) const
|
||||
void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext,
|
||||
QStringList& jars,
|
||||
QStringList& nativeJars,
|
||||
const QString& overridePath,
|
||||
const QString& tempPath) const
|
||||
{
|
||||
QStringList native32, native64;
|
||||
jars.clear();
|
||||
nativeJars.clear();
|
||||
for (auto lib : getLibraries())
|
||||
{
|
||||
for (auto lib : getLibraries()) {
|
||||
lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
|
||||
}
|
||||
// NOTE: order is important here, add main jar last to the lists
|
||||
if(m_mainJar)
|
||||
{
|
||||
if (m_mainJar) {
|
||||
// FIXME: HACK!! jar modding is weird and unsystematic!
|
||||
if(m_jarMods.size())
|
||||
{
|
||||
if (m_jarMods.size()) {
|
||||
QDir tempDir(tempPath);
|
||||
jars.append(tempDir.absoluteFilePath("minecraft.jar"));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
m_mainJar->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
|
||||
}
|
||||
}
|
||||
for (auto lib : getNativeLibraries())
|
||||
{
|
||||
for (auto lib : getNativeLibraries()) {
|
||||
lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath);
|
||||
}
|
||||
if(runtimeContext.javaArchitecture == "32")
|
||||
{
|
||||
if (runtimeContext.javaArchitecture == "32") {
|
||||
nativeJars.append(native32);
|
||||
}
|
||||
else if(runtimeContext.javaArchitecture == "64")
|
||||
{
|
||||
} else if (runtimeContext.javaArchitecture == "64") {
|
||||
nativeJars.append(native64);
|
||||
}
|
||||
}
|
||||
|
@ -34,17 +34,16 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include "Library.h"
|
||||
#include "Agent.h"
|
||||
#include <ProblemProvider.h>
|
||||
#include <QString>
|
||||
#include "Agent.h"
|
||||
#include "Library.h"
|
||||
|
||||
class LaunchProfile: public ProblemProvider
|
||||
{
|
||||
public:
|
||||
class LaunchProfile : public ProblemProvider {
|
||||
public:
|
||||
virtual ~LaunchProfile() {}
|
||||
|
||||
public: /* application of profile variables from patches */
|
||||
public: /* application of profile variables from patches */
|
||||
void applyMinecraftVersion(const QString& id);
|
||||
void applyMainClass(const QString& mainClass);
|
||||
void applyAppletClass(const QString& appletClass);
|
||||
@ -52,48 +51,46 @@ public: /* application of profile variables from patches */
|
||||
void applyAddnJvmArguments(const QStringList& minecraftArguments);
|
||||
void applyMinecraftVersionType(const QString& type);
|
||||
void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets);
|
||||
void applyTraits(const QSet<QString> &traits);
|
||||
void applyTweakers(const QStringList &tweakers);
|
||||
void applyJarMods(const QList<LibraryPtr> &jarMods);
|
||||
void applyMods(const QList<LibraryPtr> &jarMods);
|
||||
void applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext);
|
||||
void applyMavenFile(LibraryPtr library, const RuntimeContext & runtimeContext);
|
||||
void applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext);
|
||||
void applyTraits(const QSet<QString>& traits);
|
||||
void applyTweakers(const QStringList& tweakers);
|
||||
void applyJarMods(const QList<LibraryPtr>& jarMods);
|
||||
void applyMods(const QList<LibraryPtr>& jarMods);
|
||||
void applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext);
|
||||
void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext);
|
||||
void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext);
|
||||
void applyCompatibleJavaMajors(QList<int>& javaMajor);
|
||||
void applyMainJar(LibraryPtr jar);
|
||||
void applyProblemSeverity(ProblemSeverity severity);
|
||||
/// clear the profile
|
||||
void clear();
|
||||
|
||||
public: /* getters for profile variables */
|
||||
public: /* getters for profile variables */
|
||||
QString getMinecraftVersion() const;
|
||||
QString getMainClass() const;
|
||||
QString getAppletClass() const;
|
||||
QString getMinecraftVersionType() const;
|
||||
MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
|
||||
QString getMinecraftArguments() const;
|
||||
const QStringList & getAddnJvmArguments() const;
|
||||
const QSet<QString> & getTraits() const;
|
||||
const QStringList & getTweakers() const;
|
||||
const QList<LibraryPtr> & getJarMods() const;
|
||||
const QList<LibraryPtr> & getLibraries() const;
|
||||
const QList<LibraryPtr> & getNativeLibraries() const;
|
||||
const QList<LibraryPtr> & getMavenFiles() const;
|
||||
const QList<AgentPtr> & getAgents() const;
|
||||
const QList<int> & getCompatibleJavaMajors() const;
|
||||
const QStringList& getAddnJvmArguments() const;
|
||||
const QSet<QString>& getTraits() const;
|
||||
const QStringList& getTweakers() const;
|
||||
const QList<LibraryPtr>& getJarMods() const;
|
||||
const QList<LibraryPtr>& getLibraries() const;
|
||||
const QList<LibraryPtr>& getNativeLibraries() const;
|
||||
const QList<LibraryPtr>& getMavenFiles() const;
|
||||
const QList<AgentPtr>& getAgents() const;
|
||||
const QList<int>& getCompatibleJavaMajors() const;
|
||||
const LibraryPtr getMainJar() const;
|
||||
void getLibraryFiles(
|
||||
const RuntimeContext & runtimeContext,
|
||||
QStringList & jars,
|
||||
QStringList & nativeJars,
|
||||
const QString & overridePath,
|
||||
const QString & tempPath
|
||||
) const;
|
||||
bool hasTrait(const QString & trait) const;
|
||||
void getLibraryFiles(const RuntimeContext& runtimeContext,
|
||||
QStringList& jars,
|
||||
QStringList& nativeJars,
|
||||
const QString& overridePath,
|
||||
const QString& tempPath) const;
|
||||
bool hasTrait(const QString& trait) const;
|
||||
ProblemSeverity getProblemSeverity() const override;
|
||||
const QList<PatchProblem> getProblems() const override;
|
||||
|
||||
private:
|
||||
private:
|
||||
/// the version of Minecraft - jar to use
|
||||
QString m_minecraftVersion;
|
||||
|
||||
@ -154,5 +151,4 @@ private:
|
||||
QList<int> m_compatibleJavaMajors;
|
||||
|
||||
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
|
||||
|
||||
};
|
||||
|
@ -36,106 +36,90 @@
|
||||
#include "Library.h"
|
||||
#include "MinecraftInstance.h"
|
||||
|
||||
#include <BuildConfig.h>
|
||||
#include <FileSystem.h>
|
||||
#include <net/ApiDownload.h>
|
||||
#include <net/ChecksumValidator.h>
|
||||
#include <FileSystem.h>
|
||||
#include <BuildConfig.h>
|
||||
|
||||
|
||||
void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringList& jar, QStringList& native, QStringList& native32,
|
||||
QStringList& native64, const QString &overridePath) const
|
||||
void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
||||
QStringList& jar,
|
||||
QStringList& native,
|
||||
QStringList& native32,
|
||||
QStringList& native64,
|
||||
const QString& overridePath) const
|
||||
{
|
||||
bool local = isLocal();
|
||||
auto actualPath = [&](QString relPath)
|
||||
{
|
||||
auto actualPath = [&](QString relPath) {
|
||||
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
|
||||
if(local && !overridePath.isEmpty())
|
||||
{
|
||||
if (local && !overridePath.isEmpty()) {
|
||||
QString fileName = out.fileName();
|
||||
return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath();
|
||||
}
|
||||
return out.absoluteFilePath();
|
||||
};
|
||||
QString raw_storage = storageSuffix(runtimeContext);
|
||||
if(isNative())
|
||||
{
|
||||
if (raw_storage.contains("${arch}"))
|
||||
{
|
||||
if (isNative()) {
|
||||
if (raw_storage.contains("${arch}")) {
|
||||
auto nat32Storage = raw_storage;
|
||||
nat32Storage.replace("${arch}", "32");
|
||||
auto nat64Storage = raw_storage;
|
||||
nat64Storage.replace("${arch}", "64");
|
||||
native32 += actualPath(nat32Storage);
|
||||
native64 += actualPath(nat64Storage);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
native += actualPath(raw_storage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
jar += actualPath(raw_storage);
|
||||
}
|
||||
}
|
||||
|
||||
QList<NetAction::Ptr> Library::getDownloads(
|
||||
const RuntimeContext & runtimeContext,
|
||||
class HttpMetaCache* cache,
|
||||
QStringList& failedLocalFiles,
|
||||
const QString & overridePath
|
||||
) const
|
||||
QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
||||
class HttpMetaCache* cache,
|
||||
QStringList& failedLocalFiles,
|
||||
const QString& overridePath) const
|
||||
{
|
||||
QList<NetAction::Ptr> out;
|
||||
bool stale = isAlwaysStale();
|
||||
bool local = isLocal();
|
||||
|
||||
auto check_local_file = [&](QString storage)
|
||||
{
|
||||
auto check_local_file = [&](QString storage) {
|
||||
QFileInfo fileinfo(storage);
|
||||
QString fileName = fileinfo.fileName();
|
||||
auto fullPath = FS::PathCombine(overridePath, fileName);
|
||||
QFileInfo localFileInfo(fullPath);
|
||||
if(!localFileInfo.exists())
|
||||
{
|
||||
if (!localFileInfo.exists()) {
|
||||
failedLocalFiles.append(localFileInfo.filePath());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto add_download = [&](QString storage, QString url, QString sha1)
|
||||
{
|
||||
if(local)
|
||||
{
|
||||
auto add_download = [&](QString storage, QString url, QString sha1) {
|
||||
if (local) {
|
||||
return check_local_file(storage);
|
||||
}
|
||||
auto entry = cache->resolveEntry("libraries", storage);
|
||||
if(stale)
|
||||
{
|
||||
if (stale) {
|
||||
entry->setStale(true);
|
||||
}
|
||||
if (!entry->isStale())
|
||||
return true;
|
||||
Net::Download::Options options;
|
||||
if(stale)
|
||||
{
|
||||
if (stale) {
|
||||
options |= Net::Download::Option::AcceptLocalFiles;
|
||||
}
|
||||
|
||||
// Don't add a time limit for the libraries cache entry validity
|
||||
options |= Net::Download::Option::MakeEternal;
|
||||
|
||||
if(sha1.size())
|
||||
{
|
||||
if (sha1.size()) {
|
||||
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry, options);
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||
out.append(dl);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
out.append(Net::ApiDownload::makeCached(url, entry, options));
|
||||
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||
}
|
||||
@ -143,121 +127,89 @@ QList<NetAction::Ptr> Library::getDownloads(
|
||||
};
|
||||
|
||||
QString raw_storage = storageSuffix(runtimeContext);
|
||||
if(m_mojangDownloads)
|
||||
{
|
||||
if(isNative())
|
||||
{
|
||||
if (m_mojangDownloads) {
|
||||
if (isNative()) {
|
||||
auto nativeClassifier = getCompatibleNative(runtimeContext);
|
||||
if(!nativeClassifier.isNull())
|
||||
{
|
||||
if(nativeClassifier.contains("${arch}"))
|
||||
{
|
||||
if (!nativeClassifier.isNull()) {
|
||||
if (nativeClassifier.contains("${arch}")) {
|
||||
auto nat32Classifier = nativeClassifier;
|
||||
nat32Classifier.replace("${arch}", "32");
|
||||
auto nat64Classifier = nativeClassifier;
|
||||
nat64Classifier.replace("${arch}", "64");
|
||||
auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier);
|
||||
if(nat32info)
|
||||
{
|
||||
if (nat32info) {
|
||||
auto cooked_storage = raw_storage;
|
||||
cooked_storage.replace("${arch}", "32");
|
||||
add_download(cooked_storage, nat32info->url, nat32info->sha1);
|
||||
}
|
||||
auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
|
||||
if(nat64info)
|
||||
{
|
||||
if (nat64info) {
|
||||
auto cooked_storage = raw_storage;
|
||||
cooked_storage.replace("${arch}", "64");
|
||||
add_download(cooked_storage, nat64info->url, nat64info->sha1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
|
||||
if(info)
|
||||
{
|
||||
if (info) {
|
||||
add_download(raw_storage, info->url, info->sha1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "Ignoring native library" << m_name.serialize() << "because it has no classifier for current OS";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_mojangDownloads->artifact)
|
||||
{
|
||||
} else {
|
||||
if (m_mojangDownloads->artifact) {
|
||||
auto artifact = m_mojangDownloads->artifact;
|
||||
add_download(raw_storage, artifact->url, artifact->sha1);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "Ignoring java library" << m_name.serialize() << "because it has no artifact";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto raw_dl = [&]()
|
||||
{
|
||||
if (!m_absoluteURL.isEmpty())
|
||||
{
|
||||
} else {
|
||||
auto raw_dl = [&]() {
|
||||
if (!m_absoluteURL.isEmpty()) {
|
||||
return m_absoluteURL;
|
||||
}
|
||||
|
||||
if (m_repositoryURL.isEmpty())
|
||||
{
|
||||
if (m_repositoryURL.isEmpty()) {
|
||||
return BuildConfig.LIBRARY_BASE + raw_storage;
|
||||
}
|
||||
|
||||
if(m_repositoryURL.endsWith('/'))
|
||||
{
|
||||
if (m_repositoryURL.endsWith('/')) {
|
||||
return m_repositoryURL + raw_storage;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return m_repositoryURL + QChar('/') + raw_storage;
|
||||
}
|
||||
}();
|
||||
if (raw_storage.contains("${arch}"))
|
||||
{
|
||||
if (raw_storage.contains("${arch}")) {
|
||||
QString cooked_storage = raw_storage;
|
||||
QString cooked_dl = raw_dl;
|
||||
add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"), QString());
|
||||
cooked_storage = raw_storage;
|
||||
cooked_dl = raw_dl;
|
||||
add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"), QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
add_download(raw_storage, raw_dl, QString());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Library::isActive(const RuntimeContext & runtimeContext) const
|
||||
bool Library::isActive(const RuntimeContext& runtimeContext) const
|
||||
{
|
||||
bool result = true;
|
||||
if (m_rules.empty())
|
||||
{
|
||||
if (m_rules.empty()) {
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
RuleAction ruleResult = Disallow;
|
||||
for (auto rule : m_rules)
|
||||
{
|
||||
for (auto rule : m_rules) {
|
||||
RuleAction temp = rule->apply(this, runtimeContext);
|
||||
if (temp != Defer)
|
||||
ruleResult = temp;
|
||||
}
|
||||
result = result && (ruleResult == Allow);
|
||||
}
|
||||
if (isNative())
|
||||
{
|
||||
if (isNative()) {
|
||||
result = result && !getCompatibleNative(runtimeContext).isNull();
|
||||
}
|
||||
return result;
|
||||
@ -273,7 +225,8 @@ bool Library::isAlwaysStale() const
|
||||
return m_hint == "always-stale";
|
||||
}
|
||||
|
||||
QString Library::getCompatibleNative(const RuntimeContext & runtimeContext) const {
|
||||
QString Library::getCompatibleNative(const RuntimeContext& runtimeContext) const
|
||||
{
|
||||
// try to match precise classifier "[os]-[arch]"
|
||||
auto entry = m_nativeClassifiers.constFind(runtimeContext.getClassifier());
|
||||
// try to match imprecise classifier on legacy architectures "[os]"
|
||||
@ -298,63 +251,53 @@ QString Library::defaultStoragePrefix()
|
||||
|
||||
QString Library::storagePrefix() const
|
||||
{
|
||||
if(m_storagePrefix.isEmpty())
|
||||
{
|
||||
if (m_storagePrefix.isEmpty()) {
|
||||
return defaultStoragePrefix();
|
||||
}
|
||||
return m_storagePrefix;
|
||||
}
|
||||
|
||||
QString Library::filename(const RuntimeContext & runtimeContext) const
|
||||
QString Library::filename(const RuntimeContext& runtimeContext) const
|
||||
{
|
||||
if(!m_filename.isEmpty())
|
||||
{
|
||||
if (!m_filename.isEmpty()) {
|
||||
return m_filename;
|
||||
}
|
||||
// non-native? use only the gradle specifier
|
||||
if (!isNative())
|
||||
{
|
||||
if (!isNative()) {
|
||||
return m_name.getFileName();
|
||||
}
|
||||
|
||||
// otherwise native, override classifiers. Mojang HACK!
|
||||
GradleSpecifier nativeSpec = m_name;
|
||||
QString nativeClassifier = getCompatibleNative(runtimeContext);
|
||||
if (!nativeClassifier.isNull())
|
||||
{
|
||||
if (!nativeClassifier.isNull()) {
|
||||
nativeSpec.setClassifier(nativeClassifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
nativeSpec.setClassifier("INVALID");
|
||||
}
|
||||
return nativeSpec.getFileName();
|
||||
}
|
||||
|
||||
QString Library::displayName(const RuntimeContext & runtimeContext) const
|
||||
QString Library::displayName(const RuntimeContext& runtimeContext) const
|
||||
{
|
||||
if(!m_displayname.isEmpty())
|
||||
if (!m_displayname.isEmpty())
|
||||
return m_displayname;
|
||||
return filename(runtimeContext);
|
||||
}
|
||||
|
||||
QString Library::storageSuffix(const RuntimeContext & runtimeContext) const
|
||||
QString Library::storageSuffix(const RuntimeContext& runtimeContext) const
|
||||
{
|
||||
// non-native? use only the gradle specifier
|
||||
if (!isNative())
|
||||
{
|
||||
if (!isNative()) {
|
||||
return m_name.toPath(m_filename);
|
||||
}
|
||||
|
||||
// otherwise native, override classifiers. Mojang HACK!
|
||||
GradleSpecifier nativeSpec = m_name;
|
||||
QString nativeClassifier = getCompatibleNative(runtimeContext);
|
||||
if (!nativeClassifier.isNull())
|
||||
{
|
||||
if (!nativeClassifier.isNull()) {
|
||||
nativeSpec.setClassifier(nativeClassifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
nativeSpec.setClassifier("INVALID");
|
||||
}
|
||||
return nativeSpec.toPath(m_filename);
|
||||
|
@ -34,20 +34,19 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <net/NetAction.h>
|
||||
#include <QPair>
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <memory>
|
||||
|
||||
#include "Rule.h"
|
||||
#include "GradleSpecifier.h"
|
||||
#include "MojangDownloadInfo.h"
|
||||
#include "Rule.h"
|
||||
#include "RuntimeContext.h"
|
||||
|
||||
class Library;
|
||||
@ -55,19 +54,14 @@ class MinecraftInstance;
|
||||
|
||||
typedef std::shared_ptr<Library> LibraryPtr;
|
||||
|
||||
class Library
|
||||
{
|
||||
class Library {
|
||||
friend class OneSixVersionFormat;
|
||||
friend class MojangVersionFormat;
|
||||
friend class LibraryTest;
|
||||
public:
|
||||
Library()
|
||||
{
|
||||
}
|
||||
Library(const QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
public:
|
||||
Library() {}
|
||||
Library(const QString& name) { m_name = name; }
|
||||
/// limited copy without some data. TODO: why?
|
||||
static LibraryPtr limitedCopy(LibraryPtr base)
|
||||
{
|
||||
@ -85,98 +79,60 @@ public:
|
||||
return newlib;
|
||||
}
|
||||
|
||||
public: /* methods */
|
||||
public: /* methods */
|
||||
/// Returns the raw name field
|
||||
const GradleSpecifier & rawName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
const GradleSpecifier& rawName() const { return m_name; }
|
||||
|
||||
void setRawName(const GradleSpecifier & spec)
|
||||
{
|
||||
m_name = spec;
|
||||
}
|
||||
void setRawName(const GradleSpecifier& spec) { m_name = spec; }
|
||||
|
||||
void setClassifier(const QString & spec)
|
||||
{
|
||||
m_name.setClassifier(spec);
|
||||
}
|
||||
void setClassifier(const QString& spec) { m_name.setClassifier(spec); }
|
||||
|
||||
/// returns the full group and artifact prefix
|
||||
QString artifactPrefix() const
|
||||
{
|
||||
return m_name.artifactPrefix();
|
||||
}
|
||||
QString artifactPrefix() const { return m_name.artifactPrefix(); }
|
||||
|
||||
/// get the artifact ID
|
||||
QString artifactId() const
|
||||
{
|
||||
return m_name.artifactId();
|
||||
}
|
||||
QString artifactId() const { return m_name.artifactId(); }
|
||||
|
||||
/// get the artifact version
|
||||
QString version() const
|
||||
{
|
||||
return m_name.version();
|
||||
}
|
||||
QString version() const { return m_name.version(); }
|
||||
|
||||
/// Returns true if the library is native
|
||||
bool isNative() const
|
||||
{
|
||||
return m_nativeClassifiers.size() != 0;
|
||||
}
|
||||
bool isNative() const { return m_nativeClassifiers.size() != 0; }
|
||||
|
||||
void setStoragePrefix(QString prefix = QString());
|
||||
|
||||
/// Set the url base for downloads
|
||||
void setRepositoryURL(const QString &base_url)
|
||||
{
|
||||
m_repositoryURL = base_url;
|
||||
}
|
||||
void setRepositoryURL(const QString& base_url) { m_repositoryURL = base_url; }
|
||||
|
||||
void getApplicableFiles(const RuntimeContext & runtimeContext, QStringList & jar, QStringList & native,
|
||||
QStringList & native32, QStringList & native64, const QString & overridePath) const;
|
||||
void getApplicableFiles(const RuntimeContext& runtimeContext,
|
||||
QStringList& jar,
|
||||
QStringList& native,
|
||||
QStringList& native32,
|
||||
QStringList& native64,
|
||||
const QString& overridePath) const;
|
||||
|
||||
void setAbsoluteUrl(const QString &absolute_url)
|
||||
{
|
||||
m_absoluteURL = absolute_url;
|
||||
}
|
||||
void setAbsoluteUrl(const QString& absolute_url) { m_absoluteURL = absolute_url; }
|
||||
|
||||
void setFilename(const QString &filename)
|
||||
{
|
||||
m_filename = filename;
|
||||
}
|
||||
void setFilename(const QString& filename) { m_filename = filename; }
|
||||
|
||||
/// Get the file name of the library
|
||||
QString filename(const RuntimeContext & runtimeContext) const;
|
||||
QString filename(const RuntimeContext& runtimeContext) const;
|
||||
|
||||
// DEPRECATED: set a display name, used by jar mods only
|
||||
void setDisplayName(const QString & displayName)
|
||||
{
|
||||
m_displayname = displayName;
|
||||
}
|
||||
void setDisplayName(const QString& displayName) { m_displayname = displayName; }
|
||||
|
||||
/// Get the file name of the library
|
||||
QString displayName(const RuntimeContext & runtimeContext) const;
|
||||
QString displayName(const RuntimeContext& runtimeContext) const;
|
||||
|
||||
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
|
||||
{
|
||||
m_mojangDownloads = info;
|
||||
}
|
||||
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) { m_mojangDownloads = info; }
|
||||
|
||||
void setHint(const QString &hint)
|
||||
{
|
||||
m_hint = hint;
|
||||
}
|
||||
void setHint(const QString& hint) { m_hint = hint; }
|
||||
|
||||
/// Set the load rules
|
||||
void setRules(QList<std::shared_ptr<Rule>> rules)
|
||||
{
|
||||
m_rules = rules;
|
||||
}
|
||||
void setRules(QList<std::shared_ptr<Rule>> rules) { m_rules = rules; }
|
||||
|
||||
/// Returns true if the library should be loaded (or extracted, in case of natives)
|
||||
bool isActive(const RuntimeContext & runtimeContext) const;
|
||||
bool isActive(const RuntimeContext& runtimeContext) const;
|
||||
|
||||
/// Returns true if the library is contained in an instance and false if it is shared
|
||||
bool isLocal() const;
|
||||
@ -188,12 +144,14 @@ public: /* methods */
|
||||
bool isForge() const;
|
||||
|
||||
// Get a list of downloads for this library
|
||||
QList<NetAction::Ptr> getDownloads(const RuntimeContext & runtimeContext, class HttpMetaCache * cache,
|
||||
QStringList & failedLocalFiles, const QString & overridePath) const;
|
||||
QList<NetAction::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
||||
class HttpMetaCache* cache,
|
||||
QStringList& failedLocalFiles,
|
||||
const QString& overridePath) const;
|
||||
|
||||
QString getCompatibleNative(const RuntimeContext & runtimeContext) const;
|
||||
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
||||
|
||||
private: /* methods */
|
||||
private: /* methods */
|
||||
/// the default storage prefix used by Prism Launcher
|
||||
static QString defaultStoragePrefix();
|
||||
|
||||
@ -201,14 +159,11 @@ private: /* methods */
|
||||
QString storagePrefix() const;
|
||||
|
||||
/// Get the relative file path where the library should be saved
|
||||
QString storageSuffix(const RuntimeContext & runtimeContext) const;
|
||||
QString storageSuffix(const RuntimeContext& runtimeContext) const;
|
||||
|
||||
QString hint() const
|
||||
{
|
||||
return m_hint;
|
||||
}
|
||||
QString hint() const { return m_hint; }
|
||||
|
||||
protected: /* data */
|
||||
protected: /* data */
|
||||
/// the basic gradle dependency specifier.
|
||||
GradleSpecifier m_name;
|
||||
|
||||
@ -253,4 +208,3 @@ protected: /* data */
|
||||
/// MOJANG: container with Mojang style download info
|
||||
MojangLibraryDownloadInfo::Ptr m_mojangDownloads;
|
||||
};
|
||||
|
||||
|
@ -37,32 +37,32 @@
|
||||
*/
|
||||
|
||||
#include "MinecraftInstance.h"
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "minecraft/launch/CreateGameFolders.h"
|
||||
#include "minecraft/launch/ExtractNatives.h"
|
||||
#include "minecraft/launch/PrintInstanceInfo.h"
|
||||
#include "settings/Setting.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include "pathmatcher/MultiMatcher.h"
|
||||
#include "FileSystem.h"
|
||||
#include "java/JavaVersion.h"
|
||||
#include "MMCTime.h"
|
||||
#include "java/JavaVersion.h"
|
||||
#include "pathmatcher/MultiMatcher.h"
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "launch/steps/CheckJava.h"
|
||||
#include "launch/steps/LookupServerAddress.h"
|
||||
#include "launch/steps/PostLaunchCommand.h"
|
||||
#include "launch/steps/Update.h"
|
||||
#include "launch/steps/PreLaunchCommand.h"
|
||||
#include "launch/steps/TextPrint.h"
|
||||
#include "launch/steps/CheckJava.h"
|
||||
#include "launch/steps/QuitAfterGameStop.h"
|
||||
#include "launch/steps/TextPrint.h"
|
||||
#include "launch/steps/Update.h"
|
||||
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
#include "minecraft/launch/LauncherPartLaunch.h"
|
||||
#include "minecraft/launch/ModMinecraftJar.h"
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
#include "minecraft/launch/ReconstructAssets.h"
|
||||
#include "minecraft/launch/ScanModFolders.h"
|
||||
#include "minecraft/launch/VerifyJavaInstall.h"
|
||||
@ -81,10 +81,10 @@
|
||||
|
||||
#include "WorldList.h"
|
||||
|
||||
#include "PackProfile.h"
|
||||
#include "AssetsUtils.h"
|
||||
#include "MinecraftUpdate.h"
|
||||
#include "MinecraftLoadAndCheck.h"
|
||||
#include "MinecraftUpdate.h"
|
||||
#include "PackProfile.h"
|
||||
#include "minecraft/gameoptions/GameOptions.h"
|
||||
#include "minecraft/update/FoldersTask.h"
|
||||
|
||||
@ -96,14 +96,10 @@
|
||||
|
||||
// all of this because keeping things compatible with deprecated old settings
|
||||
// if either of the settings {a, b} is true, this also resolves to true
|
||||
class OrSetting : public Setting
|
||||
{
|
||||
class OrSetting : public Setting {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b)
|
||||
:Setting({id}, false), m_a(a), m_b(b)
|
||||
{
|
||||
}
|
||||
public:
|
||||
OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b) : Setting({ id }, false), m_a(a), m_b(b) {}
|
||||
virtual QVariant get() const
|
||||
{
|
||||
bool a = m_a->get().toBool();
|
||||
@ -112,12 +108,13 @@ public:
|
||||
}
|
||||
virtual void reset() {}
|
||||
virtual void set(QVariant value) {}
|
||||
private:
|
||||
|
||||
private:
|
||||
std::shared_ptr<Setting> m_a;
|
||||
std::shared_ptr<Setting> m_b;
|
||||
};
|
||||
|
||||
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
|
||||
: BaseInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
m_components.reset(new PackProfile(this));
|
||||
@ -222,14 +219,12 @@ std::shared_ptr<PackProfile> MinecraftInstance::getPackProfile() const
|
||||
QSet<QString> MinecraftInstance::traits() const
|
||||
{
|
||||
auto components = getPackProfile();
|
||||
if (!components)
|
||||
{
|
||||
return {"version-incomplete"};
|
||||
if (!components) {
|
||||
return { "version-incomplete" };
|
||||
}
|
||||
auto profile = components->getProfile();
|
||||
if (!profile)
|
||||
{
|
||||
return {"version-incomplete"};
|
||||
if (!profile) {
|
||||
return { "version-incomplete" };
|
||||
}
|
||||
return profile->getTraits();
|
||||
}
|
||||
@ -264,7 +259,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
|
||||
|
||||
bool MinecraftInstance::supportsDemo() const
|
||||
{
|
||||
Version instance_ver { getPackProfile()->getComponentVersion("net.minecraft") };
|
||||
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
|
||||
// Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
|
||||
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
|
||||
return instance_ver >= Version("1.3.1");
|
||||
@ -375,21 +370,18 @@ QStringList MinecraftInstance::extraArguments()
|
||||
if (!version)
|
||||
return list;
|
||||
auto jarMods = getJarMods();
|
||||
if (!jarMods.isEmpty())
|
||||
{
|
||||
list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
|
||||
"-Dfml.ignorePatchDiscrepancies=true"});
|
||||
if (!jarMods.isEmpty()) {
|
||||
list.append({ "-Dfml.ignoreInvalidMinecraftCertificates=true", "-Dfml.ignorePatchDiscrepancies=true" });
|
||||
}
|
||||
auto addn = m_components->getProfile()->getAddnJvmArguments();
|
||||
if (!addn.isEmpty()) {
|
||||
list.append(addn);
|
||||
}
|
||||
auto agents = m_components->getProfile()->getAgents();
|
||||
for (auto agent : agents)
|
||||
{
|
||||
for (auto agent : agents) {
|
||||
QStringList jar, temp1, temp2, temp3;
|
||||
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
|
||||
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
|
||||
list.append("-javaagent:" + jar[0] + (agent->argument().isEmpty() ? "" : "=" + agent->argument()));
|
||||
}
|
||||
|
||||
{
|
||||
@ -416,38 +408,33 @@ QStringList MinecraftInstance::javaArguments()
|
||||
// HACK: fix issues on macOS with 1.13 snapshots
|
||||
// NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them
|
||||
#ifdef Q_OS_MAC
|
||||
if(traits_.contains("FirstThreadOnMacOS"))
|
||||
{
|
||||
if (traits_.contains("FirstThreadOnMacOS")) {
|
||||
args << QString("-XstartOnFirstThread");
|
||||
}
|
||||
#endif
|
||||
|
||||
// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
|
||||
#ifdef Q_OS_WIN32
|
||||
args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
|
||||
"minecraft.exe.heapdump");
|
||||
args << QString(
|
||||
"-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
|
||||
"minecraft.exe.heapdump");
|
||||
#endif
|
||||
|
||||
int min = settings()->get("MinMemAlloc").toInt();
|
||||
int max = settings()->get("MaxMemAlloc").toInt();
|
||||
if(min < max)
|
||||
{
|
||||
if (min < max) {
|
||||
args << QString("-Xms%1m").arg(min);
|
||||
args << QString("-Xmx%1m").arg(max);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
args << QString("-Xms%1m").arg(max);
|
||||
args << QString("-Xmx%1m").arg(min);
|
||||
}
|
||||
|
||||
// No PermGen in newer java.
|
||||
JavaVersion javaVersion = getJavaVersion();
|
||||
if(javaVersion.requiresPermGen())
|
||||
{
|
||||
if (javaVersion.requiresPermGen()) {
|
||||
auto permgen = settings()->get("PermGen").toInt();
|
||||
if (permgen != 64)
|
||||
{
|
||||
if (permgen != 64) {
|
||||
args << QString("-XX:PermSize=%1m").arg(permgen);
|
||||
}
|
||||
}
|
||||
@ -487,8 +474,7 @@ QProcessEnvironment MinecraftInstance::createEnvironment()
|
||||
|
||||
// export some infos
|
||||
auto variables = getVariables();
|
||||
for (auto it = variables.begin(); it != variables.end(); ++it)
|
||||
{
|
||||
for (auto it = variables.begin(); it != variables.end(); ++it) {
|
||||
env.insert(it.key(), it.value());
|
||||
}
|
||||
return env;
|
||||
@ -500,15 +486,12 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||
QProcessEnvironment env = createEnvironment();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
|
||||
{
|
||||
|
||||
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
|
||||
auto preloadList = env.value("LD_PRELOAD").split(QLatin1String(":"));
|
||||
auto libPaths = env.value("LD_LIBRARY_PATH").split(QLatin1String(":"));
|
||||
|
||||
auto mangoHudLibString = MangoHud::getLibraryString();
|
||||
if (!mangoHudLibString.isEmpty())
|
||||
{
|
||||
if (!mangoHudLibString.isEmpty()) {
|
||||
QFileInfo mangoHudLib(mangoHudLibString);
|
||||
|
||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||
@ -521,8 +504,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||
env.insert("MANGOHUD", "1");
|
||||
}
|
||||
|
||||
if (settings()->get("UseDiscreteGpu").toBool())
|
||||
{
|
||||
if (settings()->get("UseDiscreteGpu").toBool()) {
|
||||
// Open Source Drivers
|
||||
env.insert("DRI_PRIME", "1");
|
||||
// Proprietary Nvidia Drivers
|
||||
@ -543,14 +525,12 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
||||
QStringList list;
|
||||
QRegularExpressionMatchIterator i = token_regexp.globalMatch(text);
|
||||
int lastCapturedEnd = 0;
|
||||
while (i.hasNext())
|
||||
{
|
||||
while (i.hasNext()) {
|
||||
QRegularExpressionMatch match = i.next();
|
||||
result.append(text.mid(lastCapturedEnd, match.capturedStart()));
|
||||
QString key = match.captured(1);
|
||||
auto iter = with.find(key);
|
||||
if (iter != with.end())
|
||||
{
|
||||
if (iter != with.end()) {
|
||||
result.append(*iter);
|
||||
}
|
||||
lastCapturedEnd = match.capturedEnd();
|
||||
@ -559,25 +539,22 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList MinecraftInstance::processMinecraftArgs(
|
||||
AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const
|
||||
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
QString args_pattern = profile->getMinecraftArguments();
|
||||
for (auto tweaker : profile->getTweakers())
|
||||
{
|
||||
for (auto tweaker : profile->getTweakers()) {
|
||||
args_pattern += " --tweakClass " + tweaker;
|
||||
}
|
||||
|
||||
if (serverToJoin && !serverToJoin->address.isEmpty())
|
||||
{
|
||||
if (serverToJoin && !serverToJoin->address.isEmpty()) {
|
||||
args_pattern += " --server " + serverToJoin->address;
|
||||
args_pattern += " --port " + QString::number(serverToJoin->port);
|
||||
}
|
||||
|
||||
QMap<QString, QString> token_mapping;
|
||||
// yggdrasil!
|
||||
if(session) {
|
||||
if (session) {
|
||||
// token_mapping["auth_username"] = session->username;
|
||||
token_mapping["auth_session"] = session->session;
|
||||
token_mapping["auth_access_token"] = session->access_token;
|
||||
@ -585,7 +562,7 @@ QStringList MinecraftInstance::processMinecraftArgs(
|
||||
token_mapping["auth_uuid"] = session->uuid;
|
||||
token_mapping["user_properties"] = session->serializeUserProperties();
|
||||
token_mapping["user_type"] = session->user_type;
|
||||
if(session->demo) {
|
||||
if (session->demo) {
|
||||
args_pattern += " --demo";
|
||||
}
|
||||
}
|
||||
@ -609,8 +586,7 @@ QStringList MinecraftInstance::processMinecraftArgs(
|
||||
#else
|
||||
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
|
||||
#endif
|
||||
for (int i = 0; i < parts.length(); i++)
|
||||
{
|
||||
for (int i = 0; i < parts.length(); i++) {
|
||||
parts[i] = replaceTokensIn(parts[i], token_mapping);
|
||||
}
|
||||
return parts;
|
||||
@ -623,32 +599,26 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
if (!m_components)
|
||||
return QString();
|
||||
auto profile = m_components->getProfile();
|
||||
if(!profile)
|
||||
if (!profile)
|
||||
return QString();
|
||||
|
||||
auto mainClass = getMainClass();
|
||||
if (!mainClass.isEmpty())
|
||||
{
|
||||
if (!mainClass.isEmpty()) {
|
||||
launchScript += "mainClass " + mainClass + "\n";
|
||||
}
|
||||
auto appletClass = profile->getAppletClass();
|
||||
if (!appletClass.isEmpty())
|
||||
{
|
||||
if (!appletClass.isEmpty()) {
|
||||
launchScript += "appletClass " + appletClass + "\n";
|
||||
}
|
||||
|
||||
if (serverToJoin && !serverToJoin->address.isEmpty())
|
||||
{
|
||||
if (serverToJoin && !serverToJoin->address.isEmpty()) {
|
||||
launchScript += "serverAddress " + serverToJoin->address + "\n";
|
||||
launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n";
|
||||
}
|
||||
|
||||
// generic minecraft params
|
||||
for (auto param : processMinecraftArgs(
|
||||
session,
|
||||
nullptr /* When using a launch script, the server parameters are handled by it*/
|
||||
))
|
||||
{
|
||||
for (auto param : processMinecraftArgs(session, nullptr /* When using a launch script, the server parameters are handled by it*/
|
||||
)) {
|
||||
launchScript += "param " + param + "\n";
|
||||
}
|
||||
|
||||
@ -658,22 +628,19 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
if (settings()->get("LaunchMaximized").toBool())
|
||||
windowParams = "max";
|
||||
else
|
||||
windowParams = QString("%1x%2")
|
||||
.arg(settings()->get("MinecraftWinWidth").toInt())
|
||||
.arg(settings()->get("MinecraftWinHeight").toInt());
|
||||
windowParams =
|
||||
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
|
||||
launchScript += "windowTitle " + windowTitle() + "\n";
|
||||
launchScript += "windowParams " + windowParams + "\n";
|
||||
}
|
||||
|
||||
// legacy auth
|
||||
if(session)
|
||||
{
|
||||
if (session) {
|
||||
launchScript += "userName " + session->player_name + "\n";
|
||||
launchScript += "sessionId " + session->session + "\n";
|
||||
}
|
||||
|
||||
for (auto trait : profile->getTraits())
|
||||
{
|
||||
for (auto trait : profile->getTraits()) {
|
||||
launchScript += "traits " + trait + "\n";
|
||||
}
|
||||
|
||||
@ -686,17 +653,17 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
||||
{
|
||||
QStringList out;
|
||||
out << "Main Class:" << " " + getMainClass() << "";
|
||||
out << "Native path:" << " " + getNativePath() << "";
|
||||
out << "Main Class:"
|
||||
<< " " + getMainClass() << "";
|
||||
out << "Native path:"
|
||||
<< " " + getNativePath() << "";
|
||||
|
||||
auto profile = m_components->getProfile();
|
||||
|
||||
auto alltraits = traits();
|
||||
if(alltraits.size())
|
||||
{
|
||||
if (alltraits.size()) {
|
||||
out << "Traits:";
|
||||
for (auto trait : alltraits)
|
||||
{
|
||||
for (auto trait : alltraits) {
|
||||
out << "traits " + trait;
|
||||
}
|
||||
out << "";
|
||||
@ -705,8 +672,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
auto settings = this->settings();
|
||||
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
|
||||
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
|
||||
if (nativeOpenAL || nativeGLFW)
|
||||
{
|
||||
if (nativeOpenAL || nativeGLFW) {
|
||||
if (nativeOpenAL)
|
||||
out << "Using system OpenAL.";
|
||||
if (nativeGLFW)
|
||||
@ -719,34 +685,27 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
out << "Libraries:";
|
||||
QStringList jars, nativeJars;
|
||||
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||
auto printLibFile = [&](const QString & path)
|
||||
{
|
||||
auto printLibFile = [&](const QString& path) {
|
||||
QFileInfo info(path);
|
||||
if(info.exists())
|
||||
{
|
||||
if (info.exists()) {
|
||||
out << " " + path;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
out << " " + path + " (missing)";
|
||||
}
|
||||
};
|
||||
for(auto file: jars)
|
||||
{
|
||||
for (auto file : jars) {
|
||||
printLibFile(file);
|
||||
}
|
||||
out << "";
|
||||
out << "Native libraries:";
|
||||
for(auto file: nativeJars)
|
||||
{
|
||||
for (auto file : nativeJars) {
|
||||
printLibFile(file);
|
||||
}
|
||||
out << "";
|
||||
}
|
||||
|
||||
auto printModList = [&](const QString & label, ModFolderModel & model) {
|
||||
if(model.size())
|
||||
{
|
||||
auto printModList = [&](const QString& label, ModFolderModel& model) {
|
||||
if (model.size()) {
|
||||
out << QString("%1:").arg(label);
|
||||
auto modList = model.allMods();
|
||||
std::sort(modList.begin(), modList.end(), [](auto a, auto b) {
|
||||
@ -754,21 +713,17 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
auto bName = b->fileinfo().completeBaseName();
|
||||
return aName.localeAwareCompare(bName) < 0;
|
||||
});
|
||||
for(auto mod: modList)
|
||||
{
|
||||
if(mod->type() == ResourceType::FOLDER)
|
||||
{
|
||||
for (auto mod : modList) {
|
||||
if (mod->type() == ResourceType::FOLDER) {
|
||||
out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(mod->enabled()) {
|
||||
if (mod->enabled()) {
|
||||
out << u8" [✔] " + mod->fileinfo().completeBaseName();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
out << u8" [✘] " + mod->fileinfo().completeBaseName() + " (disabled)";
|
||||
}
|
||||
|
||||
}
|
||||
out << "";
|
||||
}
|
||||
@ -777,20 +732,15 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
printModList("Mods", *(loaderModList().get()));
|
||||
printModList("Core Mods", *(coreModList().get()));
|
||||
|
||||
auto & jarMods = profile->getJarMods();
|
||||
if(jarMods.size())
|
||||
{
|
||||
auto& jarMods = profile->getJarMods();
|
||||
if (jarMods.size()) {
|
||||
out << "Jar Mods:";
|
||||
for(auto & jarmod: jarMods)
|
||||
{
|
||||
for (auto& jarmod : jarMods) {
|
||||
auto displayname = jarmod->displayName(runtimeContext());
|
||||
auto realname = jarmod->filename(runtimeContext());
|
||||
if(displayname != realname)
|
||||
{
|
||||
if (displayname != realname) {
|
||||
out << " " + displayname + " (" + realname + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
out << " " + realname;
|
||||
}
|
||||
}
|
||||
@ -803,12 +753,9 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
out << "";
|
||||
|
||||
QString windowParams;
|
||||
if (settings->get("LaunchMaximized").toBool())
|
||||
{
|
||||
if (settings->get("LaunchMaximized").toBool()) {
|
||||
out << "Window size: max (if available)";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
auto width = settings->get("MinecraftWinWidth").toInt();
|
||||
auto height = settings->get("MinecraftWinHeight").toInt();
|
||||
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
|
||||
@ -821,27 +768,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
|
||||
QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session)
|
||||
{
|
||||
if(!session)
|
||||
{
|
||||
if (!session) {
|
||||
return QMap<QString, QString>();
|
||||
}
|
||||
auto & sessionRef = *session.get();
|
||||
auto& sessionRef = *session.get();
|
||||
QMap<QString, QString> filter;
|
||||
auto addToFilter = [&filter](QString key, QString value)
|
||||
{
|
||||
if(key.trimmed().size())
|
||||
{
|
||||
auto addToFilter = [&filter](QString key, QString value) {
|
||||
if (key.trimmed().size()) {
|
||||
filter[key] = value;
|
||||
}
|
||||
};
|
||||
if (sessionRef.session != "-")
|
||||
{
|
||||
if (sessionRef.session != "-") {
|
||||
addToFilter(sessionRef.session, tr("<SESSION ID>"));
|
||||
}
|
||||
if (sessionRef.access_token != "0") {
|
||||
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
||||
}
|
||||
if(sessionRef.client_token.size()) {
|
||||
if (sessionRef.client_token.size()) {
|
||||
addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
|
||||
}
|
||||
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
|
||||
@ -849,31 +792,28 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||
return filter;
|
||||
}
|
||||
|
||||
MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level)
|
||||
{
|
||||
QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
|
||||
auto match = re.match(line);
|
||||
if(match.hasMatch())
|
||||
{
|
||||
if (match.hasMatch()) {
|
||||
// New style logs from log4j
|
||||
QString timestamp = match.captured("timestamp");
|
||||
QString levelStr = match.captured("level");
|
||||
if(levelStr == "INFO")
|
||||
if (levelStr == "INFO")
|
||||
level = MessageLevel::Message;
|
||||
if(levelStr == "WARN")
|
||||
if (levelStr == "WARN")
|
||||
level = MessageLevel::Warning;
|
||||
if(levelStr == "ERROR")
|
||||
if (levelStr == "ERROR")
|
||||
level = MessageLevel::Error;
|
||||
if(levelStr == "FATAL")
|
||||
if (levelStr == "FATAL")
|
||||
level = MessageLevel::Fatal;
|
||||
if(levelStr == "TRACE" || levelStr == "DEBUG")
|
||||
if (levelStr == "TRACE" || levelStr == "DEBUG")
|
||||
level = MessageLevel::Debug;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Old style forge logs
|
||||
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") ||
|
||||
line.contains("[FINER]") || line.contains("[FINEST]"))
|
||||
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") ||
|
||||
line.contains("[FINEST]"))
|
||||
level = MessageLevel::Message;
|
||||
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
|
||||
level = MessageLevel::Error;
|
||||
@ -884,14 +824,12 @@ MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLev
|
||||
}
|
||||
if (line.contains("overwriting existing"))
|
||||
return MessageLevel::Fatal;
|
||||
//NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
|
||||
// NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
|
||||
static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*";
|
||||
if (line.contains("Exception in thread")
|
||||
|| line.contains(QRegularExpression("\\s+at " + javaSymbol))
|
||||
|| line.contains(QRegularExpression("Caused by: " + javaSymbol))
|
||||
|| line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)"))
|
||||
|| line.contains(QRegularExpression("... \\d+ more$"))
|
||||
)
|
||||
if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at " + javaSymbol)) ||
|
||||
line.contains(QRegularExpression("Caused by: " + javaSymbol)) ||
|
||||
line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) ||
|
||||
line.contains(QRegularExpression("... \\d+ more$")))
|
||||
return MessageLevel::Error;
|
||||
return level;
|
||||
}
|
||||
@ -914,14 +852,12 @@ QString MinecraftInstance::getLogFileRoot()
|
||||
QString MinecraftInstance::getStatusbarDescription()
|
||||
{
|
||||
QStringList traits;
|
||||
if (hasVersionBroken())
|
||||
{
|
||||
if (hasVersionBroken()) {
|
||||
traits.append(tr("broken"));
|
||||
}
|
||||
|
||||
QString mcVersion = m_components->getComponentVersion("net.minecraft");
|
||||
if (mcVersion.isEmpty())
|
||||
{
|
||||
if (mcVersion.isEmpty()) {
|
||||
// Load component info if needed
|
||||
m_components->reload(Net::Mode::Offline);
|
||||
mcVersion = m_components->getComponentVersion("net.minecraft");
|
||||
@ -929,8 +865,7 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
|
||||
QString description;
|
||||
description.append(tr("Minecraft %1").arg(mcVersion));
|
||||
if(m_settings->get("ShowGameTime").toBool())
|
||||
{
|
||||
if (m_settings->get("ShowGameTime").toBool()) {
|
||||
if (lastTimePlayed() > 0) {
|
||||
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
|
||||
description.append(tr(", last played on %1 for %2")
|
||||
@ -942,8 +877,7 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
|
||||
}
|
||||
}
|
||||
if(hasCrashed())
|
||||
{
|
||||
if (hasCrashed()) {
|
||||
description.append(tr(", has crashed."));
|
||||
}
|
||||
return description;
|
||||
@ -952,14 +886,11 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
|
||||
{
|
||||
updateRuntimeContext();
|
||||
switch (mode)
|
||||
{
|
||||
case Net::Mode::Offline:
|
||||
{
|
||||
switch (mode) {
|
||||
case Net::Mode::Offline: {
|
||||
return Task::Ptr(new MinecraftLoadAndCheck(this));
|
||||
}
|
||||
case Net::Mode::Online:
|
||||
{
|
||||
case Net::Mode::Online: {
|
||||
return Task::Ptr(new MinecraftUpdate(this));
|
||||
}
|
||||
}
|
||||
@ -990,14 +921,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
process->appendStep(makeShared<CreateGameFolders>(pptr));
|
||||
}
|
||||
|
||||
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool())
|
||||
{
|
||||
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) {
|
||||
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
|
||||
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
|
||||
}
|
||||
|
||||
if(serverToJoin && serverToJoin->port == 25565)
|
||||
{
|
||||
if (serverToJoin && serverToJoin->port == 25565) {
|
||||
// Resolve server address to join on launch
|
||||
auto step = makeShared<LookupServerAddress>(pptr);
|
||||
step->setLookupAddress(serverToJoin->address);
|
||||
@ -1006,23 +935,19 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
}
|
||||
|
||||
// run pre-launch command if that's needed
|
||||
if(getPreLaunchCommand().size())
|
||||
{
|
||||
if (getPreLaunchCommand().size()) {
|
||||
auto step = makeShared<PreLaunchCommand>(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
process->appendStep(step);
|
||||
}
|
||||
|
||||
// if we aren't in offline mode,.
|
||||
if(session->status != AuthSession::PlayableOffline)
|
||||
{
|
||||
if(!session->demo) {
|
||||
if (session->status != AuthSession::PlayableOffline) {
|
||||
if (!session->demo) {
|
||||
process->appendStep(makeShared<ClaimAccount>(pptr, session));
|
||||
}
|
||||
process->appendStep(makeShared<Update>(pptr, Net::Mode::Online));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
process->appendStep(makeShared<Update>(pptr, Net::Mode::Offline));
|
||||
}
|
||||
|
||||
@ -1066,18 +991,15 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
}
|
||||
|
||||
// run post-exit command if that's needed
|
||||
if(getPostExitCommand().size())
|
||||
{
|
||||
if (getPostExitCommand().size()) {
|
||||
auto step = makeShared<PostLaunchCommand>(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
process->appendStep(step);
|
||||
}
|
||||
if (session)
|
||||
{
|
||||
if (session) {
|
||||
process->setCensorFilter(createCensorFilterFromSession(session));
|
||||
}
|
||||
if(m_settings->get("QuitAfterGameStop").toBool())
|
||||
{
|
||||
if (m_settings->get("QuitAfterGameStop").toBool()) {
|
||||
process->appendStep(makeShared<QuitAfterGameStop>(pptr));
|
||||
}
|
||||
m_launchProcess = process;
|
||||
@ -1119,8 +1041,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
|
||||
|
||||
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
if (!m_resource_pack_list) {
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
|
||||
}
|
||||
return m_resource_pack_list;
|
||||
@ -1128,8 +1049,7 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||
|
||||
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
if (!m_texture_pack_list) {
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
|
||||
}
|
||||
return m_texture_pack_list;
|
||||
@ -1137,8 +1057,7 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||
|
||||
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||
{
|
||||
if (!m_shader_pack_list)
|
||||
{
|
||||
if (!m_shader_pack_list) {
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
|
||||
}
|
||||
return m_shader_pack_list;
|
||||
@ -1146,8 +1065,7 @@ std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||
|
||||
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||
{
|
||||
if (!m_world_list)
|
||||
{
|
||||
if (!m_world_list) {
|
||||
m_world_list.reset(new WorldList(worldDir(), this));
|
||||
}
|
||||
return m_world_list;
|
||||
@ -1155,8 +1073,7 @@ std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||
|
||||
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel()
|
||||
{
|
||||
if (!m_game_options)
|
||||
{
|
||||
if (!m_game_options) {
|
||||
m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt")));
|
||||
}
|
||||
return m_game_options;
|
||||
@ -1166,8 +1083,7 @@ QList<Mod*> MinecraftInstance::getJarMods() const
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
QList<Mod*> mods;
|
||||
for (auto jarmod : profile->getJarMods())
|
||||
{
|
||||
for (auto jarmod : profile->getJarMods()) {
|
||||
QStringList jar, temp1, temp2, temp3;
|
||||
jarmod->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
|
||||
// QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
|
||||
@ -1176,5 +1092,4 @@ QList<Mod*> MinecraftInstance::getJarMods() const
|
||||
return mods;
|
||||
}
|
||||
|
||||
|
||||
#include "MinecraftInstance.moc"
|
||||
|
@ -35,12 +35,12 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
#include <java/JavaVersion.h>
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
|
||||
class ModFolderModel;
|
||||
class ResourceFolderModel;
|
||||
@ -52,12 +52,11 @@ class GameOptions;
|
||||
class LaunchStep;
|
||||
class PackProfile;
|
||||
|
||||
class MinecraftInstance: public BaseInstance
|
||||
{
|
||||
class MinecraftInstance : public BaseInstance {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
virtual ~MinecraftInstance() {};
|
||||
public:
|
||||
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
|
||||
virtual ~MinecraftInstance(){};
|
||||
virtual void saveNow() override;
|
||||
|
||||
void loadSpecificSettings() override;
|
||||
@ -67,15 +66,9 @@ public:
|
||||
// FIXME: remove
|
||||
QSet<QString> traits() const override;
|
||||
|
||||
bool canEdit() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool canEdit() const override { return true; }
|
||||
|
||||
bool canExport() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool canExport() const override { return true; }
|
||||
|
||||
////// Directories and files //////
|
||||
QString jarModsDir() const;
|
||||
@ -143,7 +136,7 @@ public:
|
||||
QProcessEnvironment createLaunchEnvironment() override;
|
||||
|
||||
/// guess log level from a line of minecraft log
|
||||
MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
|
||||
MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override;
|
||||
|
||||
IPathMatcher::Ptr getLogFileMatcher() override;
|
||||
|
||||
@ -163,10 +156,10 @@ public:
|
||||
|
||||
virtual JavaVersion getJavaVersion();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||
|
||||
protected: // data
|
||||
protected: // data
|
||||
std::shared_ptr<PackProfile> m_components;
|
||||
mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
|
||||
mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
|
||||
|
@ -2,9 +2,7 @@
|
||||
#include "MinecraftInstance.h"
|
||||
#include "PackProfile.h"
|
||||
|
||||
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {}
|
||||
|
||||
void MinecraftLoadAndCheck::executeTask()
|
||||
{
|
||||
@ -13,14 +11,13 @@ void MinecraftLoadAndCheck::executeTask()
|
||||
components->reload(Net::Mode::Offline);
|
||||
m_task = components->getCurrentTask();
|
||||
|
||||
if(!m_task)
|
||||
{
|
||||
if (!m_task) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded);
|
||||
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
||||
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
|
||||
connect(m_task.get(), &Task::aborted, this, [this] { subtaskFailed(tr("Aborted")); });
|
||||
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
||||
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress);
|
||||
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
||||
@ -28,8 +25,7 @@ void MinecraftLoadAndCheck::executeTask()
|
||||
|
||||
void MinecraftLoadAndCheck::subtaskSucceeded()
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
if (isFinished()) {
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
return;
|
||||
}
|
||||
@ -38,8 +34,7 @@ void MinecraftLoadAndCheck::subtaskSucceeded()
|
||||
|
||||
void MinecraftLoadAndCheck::subtaskFailed(QString error)
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
if (isFinished()) {
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
return;
|
||||
}
|
||||
|
@ -15,34 +15,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include <quazip/quazip.h>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class MinecraftVersion;
|
||||
class MinecraftInstance;
|
||||
|
||||
class MinecraftLoadAndCheck : public Task
|
||||
{
|
||||
class MinecraftLoadAndCheck : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
|
||||
virtual ~MinecraftLoadAndCheck() {};
|
||||
public:
|
||||
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0);
|
||||
virtual ~MinecraftLoadAndCheck(){};
|
||||
void executeTask() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void subtaskSucceeded();
|
||||
void subtaskFailed(QString error);
|
||||
|
||||
private:
|
||||
MinecraftInstance *m_inst = nullptr;
|
||||
private:
|
||||
MinecraftInstance* m_inst = nullptr;
|
||||
Task::Ptr m_task;
|
||||
QString m_preFailure;
|
||||
QString m_fail_reason;
|
||||
};
|
||||
|
||||
|
@ -16,27 +16,25 @@
|
||||
#include "MinecraftUpdate.h"
|
||||
#include "MinecraftInstance.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QTextStream>
|
||||
#include <QDataStream>
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/Library.h"
|
||||
#include <FileSystem.h>
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/Library.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
|
||||
#include "update/AssetUpdateTask.h"
|
||||
#include "update/FMLLibrariesTask.h"
|
||||
#include "update/FoldersTask.h"
|
||||
#include "update/LibrariesTask.h"
|
||||
#include "update/FMLLibrariesTask.h"
|
||||
#include "update/AssetUpdateTask.h"
|
||||
|
||||
#include <meta/Index.h>
|
||||
#include <meta/Version.h>
|
||||
|
||||
MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {}
|
||||
|
||||
void MinecraftUpdate::executeTask()
|
||||
{
|
||||
@ -51,8 +49,7 @@ void MinecraftUpdate::executeTask()
|
||||
auto components = m_inst->getPackProfile();
|
||||
components->reload(Net::Mode::Online);
|
||||
auto task = components->getCurrentTask();
|
||||
if(task)
|
||||
{
|
||||
if (task) {
|
||||
m_tasks.append(task);
|
||||
}
|
||||
}
|
||||
@ -72,8 +69,7 @@ void MinecraftUpdate::executeTask()
|
||||
m_tasks.append(makeShared<AssetUpdateTask>(m_inst));
|
||||
}
|
||||
|
||||
if(!m_preFailure.isEmpty())
|
||||
{
|
||||
if (!m_preFailure.isEmpty()) {
|
||||
emitFailed(m_preFailure);
|
||||
return;
|
||||
}
|
||||
@ -82,19 +78,16 @@ void MinecraftUpdate::executeTask()
|
||||
|
||||
void MinecraftUpdate::next()
|
||||
{
|
||||
if(m_abort)
|
||||
{
|
||||
if (m_abort) {
|
||||
emitFailed(tr("Aborted by user."));
|
||||
return;
|
||||
}
|
||||
if(m_failed_out_of_order)
|
||||
{
|
||||
if (m_failed_out_of_order) {
|
||||
emitFailed(m_fail_reason);
|
||||
return;
|
||||
}
|
||||
m_currentTask ++;
|
||||
if(m_currentTask > 0)
|
||||
{
|
||||
m_currentTask++;
|
||||
if (m_currentTask > 0) {
|
||||
auto task = m_tasks[m_currentTask - 1];
|
||||
disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
|
||||
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||
@ -104,15 +97,13 @@ void MinecraftUpdate::next()
|
||||
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||
}
|
||||
if(m_currentTask == m_tasks.size())
|
||||
{
|
||||
if (m_currentTask == m_tasks.size()) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
auto task = m_tasks[m_currentTask];
|
||||
// if the task is already finished by the time we look at it, skip it
|
||||
if(task->isFinished())
|
||||
{
|
||||
if (task->isFinished()) {
|
||||
qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
|
||||
next();
|
||||
}
|
||||
@ -124,23 +115,20 @@ void MinecraftUpdate::next()
|
||||
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||
// if the task is already running, do not start it again
|
||||
if(!task->isRunning())
|
||||
{
|
||||
if (!task->isRunning()) {
|
||||
task->start();
|
||||
}
|
||||
}
|
||||
|
||||
void MinecraftUpdate::subtaskSucceeded()
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
if (isFinished()) {
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
return;
|
||||
}
|
||||
auto senderTask = QObject::sender();
|
||||
auto currentTask = m_tasks[m_currentTask].get();
|
||||
if(senderTask != currentTask)
|
||||
{
|
||||
if (senderTask != currentTask) {
|
||||
qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order.";
|
||||
return;
|
||||
}
|
||||
@ -149,15 +137,13 @@ void MinecraftUpdate::subtaskSucceeded()
|
||||
|
||||
void MinecraftUpdate::subtaskFailed(QString error)
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
if (isFinished()) {
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
return;
|
||||
}
|
||||
auto senderTask = QObject::sender();
|
||||
auto currentTask = m_tasks[m_currentTask].get();
|
||||
if(senderTask != currentTask)
|
||||
{
|
||||
if (senderTask != currentTask) {
|
||||
qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order.";
|
||||
m_failed_out_of_order = true;
|
||||
m_fail_reason = error;
|
||||
@ -166,15 +152,12 @@ void MinecraftUpdate::subtaskFailed(QString error)
|
||||
emitFailed(error);
|
||||
}
|
||||
|
||||
|
||||
bool MinecraftUpdate::abort()
|
||||
{
|
||||
if(!m_abort)
|
||||
{
|
||||
if (!m_abort) {
|
||||
m_abort = true;
|
||||
auto task = m_tasks[m_currentTask];
|
||||
if(task->canAbort())
|
||||
{
|
||||
if (task->canAbort()) {
|
||||
return task->abort();
|
||||
}
|
||||
}
|
||||
|
@ -15,41 +15,39 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
#include <quazip/quazip.h>
|
||||
|
||||
class MinecraftVersion;
|
||||
class MinecraftInstance;
|
||||
|
||||
// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^)
|
||||
|
||||
class MinecraftUpdate : public Task
|
||||
{
|
||||
class MinecraftUpdate : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MinecraftUpdate(MinecraftInstance *inst, QObject *parent = 0);
|
||||
virtual ~MinecraftUpdate() {};
|
||||
public:
|
||||
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
|
||||
virtual ~MinecraftUpdate(){};
|
||||
|
||||
void executeTask() override;
|
||||
bool canAbort() const override;
|
||||
|
||||
private
|
||||
slots:
|
||||
private slots:
|
||||
bool abort() override;
|
||||
void subtaskSucceeded();
|
||||
void subtaskFailed(QString error);
|
||||
|
||||
private:
|
||||
private:
|
||||
void next();
|
||||
|
||||
private:
|
||||
MinecraftInstance *m_inst = nullptr;
|
||||
private:
|
||||
MinecraftInstance* m_inst = nullptr;
|
||||
QList<Task::Ptr> m_tasks;
|
||||
QString m_preFailure;
|
||||
int m_currentTask = -1;
|
||||
|
@ -1,10 +1,9 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
struct MojangDownloadInfo
|
||||
{
|
||||
struct MojangDownloadInfo {
|
||||
// types
|
||||
typedef std::shared_ptr<MojangDownloadInfo> Ptr;
|
||||
|
||||
@ -19,24 +18,20 @@ struct MojangDownloadInfo
|
||||
int size;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct MojangLibraryDownloadInfo
|
||||
{
|
||||
MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact_): artifact(artifact_) {}
|
||||
struct MojangLibraryDownloadInfo {
|
||||
MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact_) : artifact(artifact_) {}
|
||||
MojangLibraryDownloadInfo() {}
|
||||
|
||||
// types
|
||||
typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr;
|
||||
|
||||
// methods
|
||||
MojangDownloadInfo *getDownloadInfo(QString classifier)
|
||||
MojangDownloadInfo* getDownloadInfo(QString classifier)
|
||||
{
|
||||
if (classifier.isNull())
|
||||
{
|
||||
if (classifier.isNull()) {
|
||||
return artifact.get();
|
||||
}
|
||||
|
||||
|
||||
return classifiers[classifier].get();
|
||||
}
|
||||
|
||||
@ -45,17 +40,12 @@ struct MojangLibraryDownloadInfo
|
||||
QMap<QString, MojangDownloadInfo::Ptr> classifiers;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct MojangAssetIndexInfo : public MojangDownloadInfo
|
||||
{
|
||||
struct MojangAssetIndexInfo : public MojangDownloadInfo {
|
||||
// types
|
||||
typedef std::shared_ptr<MojangAssetIndexInfo> Ptr;
|
||||
|
||||
// methods
|
||||
MojangAssetIndexInfo()
|
||||
{
|
||||
}
|
||||
MojangAssetIndexInfo() {}
|
||||
|
||||
MojangAssetIndexInfo(QString id_)
|
||||
{
|
||||
@ -63,13 +53,11 @@ struct MojangAssetIndexInfo : public MojangDownloadInfo
|
||||
// HACK: ignore assets from other version files than Minecraft
|
||||
// workaround for stupid assets issue caused by amazon:
|
||||
// https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/
|
||||
if(id_ == "legacy")
|
||||
{
|
||||
if (id_ == "legacy") {
|
||||
url = "https://piston-meta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
|
||||
}
|
||||
// HACK
|
||||
else
|
||||
{
|
||||
else {
|
||||
url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id_ + ".json";
|
||||
}
|
||||
known = false;
|
||||
|
@ -34,34 +34,32 @@
|
||||
*/
|
||||
|
||||
#include "MojangVersionFormat.h"
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include "MojangDownloadInfo.h"
|
||||
#include "OneSixVersionFormat.h"
|
||||
|
||||
#include "Json.h"
|
||||
using namespace Json;
|
||||
#include "ParseUtils.h"
|
||||
#include <BuildConfig.h>
|
||||
#include "ParseUtils.h"
|
||||
|
||||
static const int CURRENT_MINIMUM_LAUNCHER_VERSION = 18;
|
||||
|
||||
static MojangAssetIndexInfo::Ptr assetIndexFromJson (const QJsonObject &obj);
|
||||
static MojangDownloadInfo::Ptr downloadInfoFromJson (const QJsonObject &obj);
|
||||
static MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson (const QJsonObject &libObj);
|
||||
static QJsonObject assetIndexToJson (MojangAssetIndexInfo::Ptr assetidxinfo);
|
||||
static QJsonObject libDownloadInfoToJson (MojangLibraryDownloadInfo::Ptr libinfo);
|
||||
static QJsonObject downloadInfoToJson (MojangDownloadInfo::Ptr info);
|
||||
static MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject& obj);
|
||||
static MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject& obj);
|
||||
static MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject& libObj);
|
||||
static QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr assetidxinfo);
|
||||
static QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo);
|
||||
static QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info);
|
||||
|
||||
namespace Bits
|
||||
namespace Bits {
|
||||
static void readString(const QJsonObject& root, const QString& key, QString& variable)
|
||||
{
|
||||
static void readString(const QJsonObject &root, const QString &key, QString &variable)
|
||||
{
|
||||
if (root.contains(key))
|
||||
{
|
||||
if (root.contains(key)) {
|
||||
variable = requireString(root.value(key));
|
||||
}
|
||||
}
|
||||
|
||||
static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj)
|
||||
static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject& obj)
|
||||
{
|
||||
// optional, not used
|
||||
readString(obj, "path", out->path);
|
||||
@ -71,22 +69,22 @@ static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj
|
||||
out->size = requireInteger(obj, "size");
|
||||
}
|
||||
|
||||
static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject &obj)
|
||||
static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject& obj)
|
||||
{
|
||||
out->totalSize = requireInteger(obj, "totalSize");
|
||||
out->id = requireString(obj, "id");
|
||||
// out->known = true;
|
||||
}
|
||||
}
|
||||
} // namespace Bits
|
||||
|
||||
MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject &obj)
|
||||
MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject& obj)
|
||||
{
|
||||
auto out = std::make_shared<MojangDownloadInfo>();
|
||||
Bits::readDownloadInfo(out, obj);
|
||||
return out;
|
||||
}
|
||||
|
||||
MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj)
|
||||
MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject& obj)
|
||||
{
|
||||
auto out = std::make_shared<MojangAssetIndexInfo>();
|
||||
Bits::readDownloadInfo(out, obj);
|
||||
@ -97,8 +95,7 @@ MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj)
|
||||
QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info)
|
||||
{
|
||||
QJsonObject out;
|
||||
if(!info->path.isNull())
|
||||
{
|
||||
if (!info->path.isNull()) {
|
||||
out.insert("path", info->path);
|
||||
}
|
||||
out.insert("sha1", info->sha1);
|
||||
@ -107,19 +104,16 @@ QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info)
|
||||
return out;
|
||||
}
|
||||
|
||||
MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj)
|
||||
MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject& libObj)
|
||||
{
|
||||
auto out = std::make_shared<MojangLibraryDownloadInfo>();
|
||||
auto dlObj = requireObject(libObj.value("downloads"));
|
||||
if(dlObj.contains("artifact"))
|
||||
{
|
||||
if (dlObj.contains("artifact")) {
|
||||
out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact"));
|
||||
}
|
||||
if(dlObj.contains("classifiers"))
|
||||
{
|
||||
if (dlObj.contains("classifiers")) {
|
||||
auto classifiersObj = requireObject(dlObj, "classifiers");
|
||||
for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++)
|
||||
{
|
||||
for (auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) {
|
||||
auto classifier = iter.key();
|
||||
auto classifierObj = requireObject(iter.value());
|
||||
out->classifiers[classifier] = downloadInfoFromJson(classifierObj);
|
||||
@ -131,15 +125,12 @@ MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj
|
||||
QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
|
||||
{
|
||||
QJsonObject out;
|
||||
if(libinfo->artifact)
|
||||
{
|
||||
if (libinfo->artifact) {
|
||||
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
|
||||
}
|
||||
if(!libinfo->classifiers.isEmpty())
|
||||
{
|
||||
if (!libinfo->classifiers.isEmpty()) {
|
||||
QJsonObject classifiersOut;
|
||||
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
|
||||
{
|
||||
for (auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) {
|
||||
classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value()));
|
||||
}
|
||||
out.insert("classifiers", classifiersOut);
|
||||
@ -150,8 +141,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
|
||||
QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info)
|
||||
{
|
||||
QJsonObject out;
|
||||
if(!info->path.isNull())
|
||||
{
|
||||
if (!info->path.isNull()) {
|
||||
out.insert("path", info->path);
|
||||
}
|
||||
out.insert("sha1", info->sha1);
|
||||
@ -162,76 +152,57 @@ QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info)
|
||||
return out;
|
||||
}
|
||||
|
||||
void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFile *out)
|
||||
void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFile* out)
|
||||
{
|
||||
Bits::readString(in, "id", out->minecraftVersion);
|
||||
Bits::readString(in, "mainClass", out->mainClass);
|
||||
Bits::readString(in, "minecraftArguments", out->minecraftArguments);
|
||||
if(out->minecraftArguments.isEmpty())
|
||||
{
|
||||
if (out->minecraftArguments.isEmpty()) {
|
||||
QString processArguments;
|
||||
Bits::readString(in, "processArguments", processArguments);
|
||||
QString toCompare = processArguments.toLower();
|
||||
if (toCompare == "legacy")
|
||||
{
|
||||
if (toCompare == "legacy") {
|
||||
out->minecraftArguments = " ${auth_player_name} ${auth_session}";
|
||||
}
|
||||
else if (toCompare == "username_session")
|
||||
{
|
||||
} else if (toCompare == "username_session") {
|
||||
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
|
||||
}
|
||||
else if (toCompare == "username_session_version")
|
||||
{
|
||||
} else if (toCompare == "username_session_version") {
|
||||
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
|
||||
}
|
||||
else if (!toCompare.isEmpty())
|
||||
{
|
||||
} else if (!toCompare.isEmpty()) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments));
|
||||
}
|
||||
}
|
||||
Bits::readString(in, "type", out->type);
|
||||
|
||||
Bits::readString(in, "assets", out->assets);
|
||||
if(in.contains("assetIndex"))
|
||||
{
|
||||
if (in.contains("assetIndex")) {
|
||||
out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex"));
|
||||
}
|
||||
else if (!out->assets.isNull())
|
||||
{
|
||||
} else if (!out->assets.isNull()) {
|
||||
out->mojangAssetIndex = std::make_shared<MojangAssetIndexInfo>(out->assets);
|
||||
}
|
||||
|
||||
out->releaseTime = timeFromS3Time(in.value("releaseTime").toString(""));
|
||||
out->updateTime = timeFromS3Time(in.value("time").toString(""));
|
||||
|
||||
if (in.contains("minimumLauncherVersion"))
|
||||
{
|
||||
if (in.contains("minimumLauncherVersion")) {
|
||||
out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion"));
|
||||
if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION)
|
||||
{
|
||||
out->addProblem(
|
||||
ProblemSeverity::Warning,
|
||||
QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by %3 (%2). It might not work properly!")
|
||||
.arg(out->minimumLauncherVersion)
|
||||
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION)
|
||||
.arg(BuildConfig.LAUNCHER_DISPLAYNAME)
|
||||
);
|
||||
if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) {
|
||||
out->addProblem(ProblemSeverity::Warning, QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than "
|
||||
"supported by %3 (%2). It might not work properly!")
|
||||
.arg(out->minimumLauncherVersion)
|
||||
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION)
|
||||
.arg(BuildConfig.LAUNCHER_DISPLAYNAME));
|
||||
}
|
||||
}
|
||||
|
||||
if (in.contains("compatibleJavaMajors"))
|
||||
{
|
||||
for (auto compatible : requireArray(in.value("compatibleJavaMajors")))
|
||||
{
|
||||
if (in.contains("compatibleJavaMajors")) {
|
||||
for (auto compatible : requireArray(in.value("compatibleJavaMajors"))) {
|
||||
out->compatibleJavaMajors.append(requireInteger(compatible));
|
||||
}
|
||||
}
|
||||
|
||||
if(in.contains("downloads"))
|
||||
{
|
||||
if (in.contains("downloads")) {
|
||||
auto downloadsObj = requireObject(in, "downloads");
|
||||
for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++)
|
||||
{
|
||||
for (auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) {
|
||||
auto classifier = iter.key();
|
||||
auto classifierObj = requireObject(iter.value());
|
||||
out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj);
|
||||
@ -239,15 +210,13 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFi
|
||||
}
|
||||
}
|
||||
|
||||
VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename)
|
||||
VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument& doc, const QString& filename)
|
||||
{
|
||||
VersionFilePtr out(new VersionFile());
|
||||
if (doc.isEmpty() || doc.isNull())
|
||||
{
|
||||
if (doc.isEmpty() || doc.isNull()) {
|
||||
throw JSONValidationError(filename + " is empty or null");
|
||||
}
|
||||
if (!doc.isObject())
|
||||
{
|
||||
if (!doc.isObject()) {
|
||||
throw JSONValidationError(filename + " is not an object");
|
||||
}
|
||||
|
||||
@ -260,11 +229,8 @@ VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
out->version = out->minecraftVersion;
|
||||
// out->filename = filename;
|
||||
|
||||
|
||||
if (root.contains("libraries"))
|
||||
{
|
||||
for (auto libVal : requireArray(root.value("libraries")))
|
||||
{
|
||||
if (root.contains("libraries")) {
|
||||
for (auto libVal : requireArray(root.value("libraries"))) {
|
||||
auto libObj = requireObject(libVal);
|
||||
|
||||
auto lib = MojangVersionFormat::libraryFromJson(*out, libObj, filename);
|
||||
@ -280,52 +246,42 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
||||
writeString(out, "mainClass", in->mainClass);
|
||||
writeString(out, "minecraftArguments", in->minecraftArguments);
|
||||
writeString(out, "type", in->type);
|
||||
if(!in->releaseTime.isNull())
|
||||
{
|
||||
if (!in->releaseTime.isNull()) {
|
||||
writeString(out, "releaseTime", timeToS3Time(in->releaseTime));
|
||||
}
|
||||
if(!in->updateTime.isNull())
|
||||
{
|
||||
if (!in->updateTime.isNull()) {
|
||||
writeString(out, "time", timeToS3Time(in->updateTime));
|
||||
}
|
||||
if(in->minimumLauncherVersion != -1)
|
||||
{
|
||||
if (in->minimumLauncherVersion != -1) {
|
||||
out.insert("minimumLauncherVersion", in->minimumLauncherVersion);
|
||||
}
|
||||
writeString(out, "assets", in->assets);
|
||||
if(in->mojangAssetIndex && in->mojangAssetIndex->known)
|
||||
{
|
||||
if (in->mojangAssetIndex && in->mojangAssetIndex->known) {
|
||||
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
|
||||
}
|
||||
if(!in->mojangDownloads.isEmpty())
|
||||
{
|
||||
if (!in->mojangDownloads.isEmpty()) {
|
||||
QJsonObject downloadsOut;
|
||||
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
|
||||
{
|
||||
for (auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) {
|
||||
downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value()));
|
||||
}
|
||||
out.insert("downloads", downloadsOut);
|
||||
}
|
||||
if(!in->compatibleJavaMajors.isEmpty())
|
||||
{
|
||||
if (!in->compatibleJavaMajors.isEmpty()) {
|
||||
QJsonArray compatibleJavaMajorsOut;
|
||||
for(auto compatibleJavaMajor : in->compatibleJavaMajors)
|
||||
{
|
||||
for (auto compatibleJavaMajor : in->compatibleJavaMajors) {
|
||||
compatibleJavaMajorsOut.append(compatibleJavaMajor);
|
||||
}
|
||||
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
|
||||
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr& patch)
|
||||
{
|
||||
QJsonObject root;
|
||||
writeVersionProperties(patch.get(), root);
|
||||
if (!patch->libraries.isEmpty())
|
||||
{
|
||||
if (!patch->libraries.isEmpty()) {
|
||||
QJsonArray array;
|
||||
for (auto value: patch->libraries)
|
||||
{
|
||||
for (auto value : patch->libraries) {
|
||||
array.append(MojangVersionFormat::libraryToJson(value.get()));
|
||||
}
|
||||
root.insert("libraries", array);
|
||||
@ -339,96 +295,80 @@ QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
}
|
||||
}
|
||||
|
||||
LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename)
|
||||
LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename)
|
||||
{
|
||||
LibraryPtr out(new Library());
|
||||
if (!libObj.contains("name"))
|
||||
{
|
||||
if (!libObj.contains("name")) {
|
||||
throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field");
|
||||
}
|
||||
auto rawName = libObj.value("name").toString();
|
||||
out->m_name = rawName;
|
||||
if(!out->m_name.valid()) {
|
||||
if (!out->m_name.valid()) {
|
||||
problems.addProblem(ProblemSeverity::Error, QObject::tr("Library %1 name is broken and cannot be processed.").arg(rawName));
|
||||
}
|
||||
|
||||
Bits::readString(libObj, "url", out->m_repositoryURL);
|
||||
if (libObj.contains("extract"))
|
||||
{
|
||||
if (libObj.contains("extract")) {
|
||||
out->m_hasExcludes = true;
|
||||
auto extractObj = requireObject(libObj.value("extract"));
|
||||
for (auto excludeVal : requireArray(extractObj.value("exclude")))
|
||||
{
|
||||
for (auto excludeVal : requireArray(extractObj.value("exclude"))) {
|
||||
out->m_extractExcludes.append(requireString(excludeVal));
|
||||
}
|
||||
}
|
||||
if (libObj.contains("natives"))
|
||||
{
|
||||
if (libObj.contains("natives")) {
|
||||
QJsonObject nativesObj = requireObject(libObj.value("natives"));
|
||||
for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
|
||||
{
|
||||
if (!it.value().isString())
|
||||
{
|
||||
for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) {
|
||||
if (!it.value().isString()) {
|
||||
qWarning() << filename << "contains an invalid native (skipping)";
|
||||
}
|
||||
// FIXME: Skip unknown platforms
|
||||
out->m_nativeClassifiers[it.key()] = it.value().toString();
|
||||
}
|
||||
}
|
||||
if (libObj.contains("rules"))
|
||||
{
|
||||
if (libObj.contains("rules")) {
|
||||
out->applyRules = true;
|
||||
out->m_rules = rulesFromJsonV4(libObj);
|
||||
}
|
||||
if (libObj.contains("downloads"))
|
||||
{
|
||||
if (libObj.contains("downloads")) {
|
||||
out->m_mojangDownloads = libDownloadInfoFromJson(libObj);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QJsonObject MojangVersionFormat::libraryToJson(Library *library)
|
||||
QJsonObject MojangVersionFormat::libraryToJson(Library* library)
|
||||
{
|
||||
QJsonObject libRoot;
|
||||
libRoot.insert("name", library->m_name.serialize());
|
||||
if (!library->m_repositoryURL.isEmpty())
|
||||
{
|
||||
if (!library->m_repositoryURL.isEmpty()) {
|
||||
libRoot.insert("url", library->m_repositoryURL);
|
||||
}
|
||||
if (library->isNative())
|
||||
{
|
||||
if (library->isNative()) {
|
||||
QJsonObject nativeList;
|
||||
auto iter = library->m_nativeClassifiers.begin();
|
||||
while (iter != library->m_nativeClassifiers.end())
|
||||
{
|
||||
while (iter != library->m_nativeClassifiers.end()) {
|
||||
nativeList.insert(iter.key(), iter.value());
|
||||
iter++;
|
||||
}
|
||||
libRoot.insert("natives", nativeList);
|
||||
if (!library->m_extractExcludes.isEmpty())
|
||||
{
|
||||
if (!library->m_extractExcludes.isEmpty()) {
|
||||
QJsonArray excludes;
|
||||
QJsonObject extract;
|
||||
for (auto exclude : library->m_extractExcludes)
|
||||
{
|
||||
for (auto exclude : library->m_extractExcludes) {
|
||||
excludes.append(exclude);
|
||||
}
|
||||
extract.insert("exclude", excludes);
|
||||
libRoot.insert("extract", extract);
|
||||
}
|
||||
}
|
||||
if (!library->m_rules.isEmpty())
|
||||
{
|
||||
if (!library->m_rules.isEmpty()) {
|
||||
QJsonArray allRules;
|
||||
for (auto &rule : library->m_rules)
|
||||
{
|
||||
for (auto& rule : library->m_rules) {
|
||||
QJsonObject ruleObj = rule->toJson();
|
||||
allRules.append(ruleObj);
|
||||
}
|
||||
libRoot.insert("rules", allRules);
|
||||
}
|
||||
if(library->m_mojangDownloads)
|
||||
{
|
||||
if (library->m_mojangDownloads) {
|
||||
auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads);
|
||||
libRoot.insert("downloads", downloadsObj);
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <minecraft/VersionFile.h>
|
||||
#include <minecraft/Library.h>
|
||||
#include <QJsonDocument>
|
||||
#include <ProblemProvider.h>
|
||||
#include <minecraft/Library.h>
|
||||
#include <minecraft/VersionFile.h>
|
||||
#include <QJsonDocument>
|
||||
|
||||
class MojangVersionFormat
|
||||
{
|
||||
friend class OneSixVersionFormat;
|
||||
protected:
|
||||
class MojangVersionFormat {
|
||||
friend class OneSixVersionFormat;
|
||||
|
||||
protected:
|
||||
// does not include libraries
|
||||
static void readVersionProperties(const QJsonObject& in, VersionFile* out);
|
||||
// does not include libraries
|
||||
static void writeVersionProperties(const VersionFile* in, QJsonObject& out);
|
||||
public:
|
||||
|
||||
public:
|
||||
// version files / profile patches
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
|
||||
|
||||
// libraries
|
||||
static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
|
||||
static QJsonObject libraryToJson(Library *library);
|
||||
static LibraryPtr libraryFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename);
|
||||
static QJsonObject libraryToJson(Library* library);
|
||||
};
|
||||
|
@ -35,23 +35,22 @@
|
||||
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include <Json.h>
|
||||
#include <minecraft/MojangVersionFormat.h>
|
||||
#include "minecraft/Agent.h"
|
||||
#include "minecraft/ParseUtils.h"
|
||||
#include <minecraft/MojangVersionFormat.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
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)
|
||||
{
|
||||
if (root.contains(key))
|
||||
{
|
||||
if (root.contains(key)) {
|
||||
variable = requireString(root.value(key));
|
||||
}
|
||||
}
|
||||
|
||||
LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename)
|
||||
LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename)
|
||||
{
|
||||
LibraryPtr out = MojangVersionFormat::libraryFromJson(problems, libObj, filename);
|
||||
readString(libObj, "MMC-hint", out->m_hint);
|
||||
@ -62,7 +61,7 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con
|
||||
return out;
|
||||
}
|
||||
|
||||
QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
|
||||
QJsonObject OneSixVersionFormat::libraryToJson(Library* library)
|
||||
{
|
||||
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
|
||||
if (!library->m_absoluteURL.isEmpty())
|
||||
@ -76,37 +75,30 @@ QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
|
||||
return libRoot;
|
||||
}
|
||||
|
||||
VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder)
|
||||
VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc, const QString& filename, const bool requireOrder)
|
||||
{
|
||||
VersionFilePtr out(new VersionFile());
|
||||
if (doc.isEmpty() || doc.isNull())
|
||||
{
|
||||
if (doc.isEmpty() || doc.isNull()) {
|
||||
throw JSONValidationError(filename + " is empty or null");
|
||||
}
|
||||
if (!doc.isObject())
|
||||
{
|
||||
if (!doc.isObject()) {
|
||||
throw JSONValidationError(filename + " is not an object");
|
||||
}
|
||||
|
||||
QJsonObject root = doc.object();
|
||||
|
||||
Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false);
|
||||
switch(formatVersion)
|
||||
{
|
||||
switch (formatVersion) {
|
||||
case Meta::MetadataVersion::InitialRelease:
|
||||
break;
|
||||
case Meta::MetadataVersion::Invalid:
|
||||
throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format.");
|
||||
}
|
||||
|
||||
if (requireOrder)
|
||||
{
|
||||
if (root.contains("order"))
|
||||
{
|
||||
if (requireOrder) {
|
||||
if (root.contains("order")) {
|
||||
out->order = requireInteger(root.value("order"));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// FIXME: evaluate if we don't want to throw exceptions here instead
|
||||
qCritical() << filename << "doesn't contain an order field";
|
||||
}
|
||||
@ -114,22 +106,18 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
|
||||
out->name = root.value("name").toString();
|
||||
|
||||
if(root.contains("uid"))
|
||||
{
|
||||
if (root.contains("uid")) {
|
||||
out->uid = root.value("uid").toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
out->uid = root.value("fileId").toString();
|
||||
}
|
||||
|
||||
const QRegularExpression valid_uid_regex{ QRegularExpression::anchoredPattern(QStringLiteral(R"([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*)")) };
|
||||
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->addProblem(ProblemSeverity::Error,
|
||||
QObject::tr("The component's 'uid' contains illegal characters! This can cause security issues."));
|
||||
}
|
||||
|
||||
out->version = root.value("version").toString();
|
||||
@ -139,46 +127,35 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
// added for legacy Minecraft window embedding, TODO: remove
|
||||
readString(root, "appletClass", out->appletClass);
|
||||
|
||||
if (root.contains("+tweakers"))
|
||||
{
|
||||
for (auto tweakerVal : requireArray(root.value("+tweakers")))
|
||||
{
|
||||
if (root.contains("+tweakers")) {
|
||||
for (auto tweakerVal : requireArray(root.value("+tweakers"))) {
|
||||
out->addTweakers.append(requireString(tweakerVal));
|
||||
}
|
||||
}
|
||||
|
||||
if (root.contains("+traits"))
|
||||
{
|
||||
for (auto tweakerVal : requireArray(root.value("+traits")))
|
||||
{
|
||||
if (root.contains("+traits")) {
|
||||
for (auto tweakerVal : requireArray(root.value("+traits"))) {
|
||||
out->traits.insert(requireString(tweakerVal));
|
||||
}
|
||||
}
|
||||
|
||||
if (root.contains("+jvmArgs"))
|
||||
{
|
||||
for (auto arg : requireArray(root.value("+jvmArgs")))
|
||||
{
|
||||
if (root.contains("+jvmArgs")) {
|
||||
for (auto arg : requireArray(root.value("+jvmArgs"))) {
|
||||
out->addnJvmArguments.append(requireString(arg));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (root.contains("jarMods"))
|
||||
{
|
||||
for (auto libVal : requireArray(root.value("jarMods")))
|
||||
{
|
||||
if (root.contains("jarMods")) {
|
||||
for (auto libVal : requireArray(root.value("jarMods"))) {
|
||||
QJsonObject libObj = requireObject(libVal);
|
||||
// parse the jarmod
|
||||
auto lib = OneSixVersionFormat::jarModFromJson(*out, libObj, filename);
|
||||
// and add to jar mods
|
||||
out->jarMods.append(lib);
|
||||
}
|
||||
}
|
||||
else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility
|
||||
} else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility
|
||||
{
|
||||
for (auto libVal : requireArray(root.value("+jarMods")))
|
||||
{
|
||||
for (auto libVal : requireArray(root.value("+jarMods"))) {
|
||||
QJsonObject libObj = requireObject(libVal);
|
||||
// parse the jarmod
|
||||
auto lib = OneSixVersionFormat::plusJarModFromJson(*out, libObj, filename, out->name);
|
||||
@ -187,10 +164,8 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
}
|
||||
}
|
||||
|
||||
if (root.contains("mods"))
|
||||
{
|
||||
for (auto libVal : requireArray(root.value("mods")))
|
||||
{
|
||||
if (root.contains("mods")) {
|
||||
for (auto libVal : requireArray(root.value("mods"))) {
|
||||
QJsonObject libObj = requireObject(libVal);
|
||||
// parse the jarmod
|
||||
auto lib = OneSixVersionFormat::modFromJson(*out, libObj, filename);
|
||||
@ -199,10 +174,8 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
}
|
||||
}
|
||||
|
||||
auto readLibs = [&](const char * which, QList<LibraryPtr> & outList)
|
||||
{
|
||||
for (auto libVal : requireArray(root.value(which)))
|
||||
{
|
||||
auto readLibs = [&](const char* which, QList<LibraryPtr>& outList) {
|
||||
for (auto libVal : requireArray(root.value(which))) {
|
||||
QJsonObject libObj = requireObject(libVal);
|
||||
// parse the library
|
||||
auto lib = libraryFromJson(*out, libObj, filename);
|
||||
@ -211,29 +184,23 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
};
|
||||
bool hasPlusLibs = root.contains("+libraries");
|
||||
bool hasLibs = root.contains("libraries");
|
||||
if (hasPlusLibs && hasLibs)
|
||||
{
|
||||
if (hasPlusLibs && hasLibs) {
|
||||
out->addProblem(ProblemSeverity::Warning,
|
||||
QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported."));
|
||||
readLibs("libraries", out->libraries);
|
||||
readLibs("+libraries", out->libraries);
|
||||
}
|
||||
else if (hasLibs)
|
||||
{
|
||||
} else if (hasLibs) {
|
||||
readLibs("libraries", out->libraries);
|
||||
}
|
||||
else if(hasPlusLibs)
|
||||
{
|
||||
} else if (hasPlusLibs) {
|
||||
readLibs("+libraries", out->libraries);
|
||||
}
|
||||
|
||||
if(root.contains("mavenFiles")) {
|
||||
if (root.contains("mavenFiles")) {
|
||||
readLibs("mavenFiles", out->mavenFiles);
|
||||
}
|
||||
|
||||
if(root.contains("+agents")) {
|
||||
for (auto agentVal : requireArray(root.value("+agents")))
|
||||
{
|
||||
if (root.contains("+agents")) {
|
||||
for (auto agentVal : requireArray(root.value("+agents"))) {
|
||||
QJsonObject agentObj = requireObject(agentVal);
|
||||
auto lib = libraryFromJson(*out, agentObj, filename);
|
||||
|
||||
@ -246,83 +213,68 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
}
|
||||
|
||||
// if we have mainJar, just use it
|
||||
if(root.contains("mainJar"))
|
||||
{
|
||||
if (root.contains("mainJar")) {
|
||||
QJsonObject libObj = requireObject(root, "mainJar");
|
||||
out->mainJar = libraryFromJson(*out, libObj, filename);
|
||||
}
|
||||
// else reconstruct it from downloads and id ... if that's available
|
||||
else if(!out->minecraftVersion.isEmpty())
|
||||
{
|
||||
else if (!out->minecraftVersion.isEmpty()) {
|
||||
auto lib = std::make_shared<Library>();
|
||||
lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion)));
|
||||
// we have a reliable client download, use it.
|
||||
if(out->mojangDownloads.contains("client"))
|
||||
{
|
||||
if (out->mojangDownloads.contains("client")) {
|
||||
auto LibDLInfo = std::make_shared<MojangLibraryDownloadInfo>();
|
||||
LibDLInfo->artifact = out->mojangDownloads["client"];
|
||||
lib->setMojangDownloadInfo(LibDLInfo);
|
||||
}
|
||||
// we got nothing...
|
||||
else
|
||||
{
|
||||
else {
|
||||
out->addProblem(
|
||||
ProblemSeverity::Error,
|
||||
QObject::tr("URL for the main jar could not be determined - Mojang removed the server that we used as fallback.")
|
||||
);
|
||||
QObject::tr("URL for the main jar could not be determined - Mojang removed the server that we used as fallback."));
|
||||
}
|
||||
out->mainJar = lib;
|
||||
}
|
||||
|
||||
if (root.contains("requires"))
|
||||
{
|
||||
if (root.contains("requires")) {
|
||||
Meta::parseRequires(root, &out->m_requires);
|
||||
}
|
||||
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
|
||||
if(!dependsOnMinecraftVersion.isEmpty())
|
||||
{
|
||||
if (!dependsOnMinecraftVersion.isEmpty()) {
|
||||
Meta::Require mcReq;
|
||||
mcReq.uid = "net.minecraft";
|
||||
mcReq.equalsVersion = dependsOnMinecraftVersion;
|
||||
if (out->m_requires.count(mcReq) == 0)
|
||||
{
|
||||
if (out->m_requires.count(mcReq) == 0) {
|
||||
out->m_requires.insert(mcReq);
|
||||
}
|
||||
}
|
||||
if (root.contains("conflicts"))
|
||||
{
|
||||
if (root.contains("conflicts")) {
|
||||
Meta::parseRequires(root, &out->conflicts);
|
||||
}
|
||||
if (root.contains("volatile"))
|
||||
{
|
||||
if (root.contains("volatile")) {
|
||||
out->m_volatile = requireBoolean(root, "volatile");
|
||||
}
|
||||
|
||||
/* removed features that shouldn't be used */
|
||||
if (root.contains("tweakers"))
|
||||
{
|
||||
if (root.contains("tweakers")) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'"));
|
||||
}
|
||||
if (root.contains("-libraries"))
|
||||
{
|
||||
if (root.contains("-libraries")) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'"));
|
||||
}
|
||||
if (root.contains("-tweakers"))
|
||||
{
|
||||
if (root.contains("-tweakers")) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'"));
|
||||
}
|
||||
if (root.contains("-minecraftArguments"))
|
||||
{
|
||||
if (root.contains("-minecraftArguments")) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'"));
|
||||
}
|
||||
if (root.contains("+minecraftArguments"))
|
||||
{
|
||||
if (root.contains("+minecraftArguments")) {
|
||||
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'"));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch)
|
||||
QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr& patch)
|
||||
{
|
||||
QJsonObject root;
|
||||
writeString(root, "name", patch->name);
|
||||
@ -335,19 +287,16 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
|
||||
MojangVersionFormat::writeVersionProperties(patch.get(), root);
|
||||
|
||||
if(patch->mainJar)
|
||||
{
|
||||
if (patch->mainJar) {
|
||||
root.insert("mainJar", libraryToJson(patch->mainJar.get()));
|
||||
}
|
||||
writeString(root, "appletClass", patch->appletClass);
|
||||
writeStringList(root, "+tweakers", patch->addTweakers);
|
||||
writeStringList(root, "+traits", patch->traits.values());
|
||||
writeStringList(root, "+jvmArgs", patch->addnJvmArguments);
|
||||
if (!patch->agents.isEmpty())
|
||||
{
|
||||
if (!patch->agents.isEmpty()) {
|
||||
QJsonArray array;
|
||||
for (auto value: patch->agents)
|
||||
{
|
||||
for (auto value : patch->agents) {
|
||||
QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get());
|
||||
if (!value->argument().isEmpty())
|
||||
agentOut.insert("argument", value->argument());
|
||||
@ -356,52 +305,41 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
}
|
||||
root.insert("+agents", array);
|
||||
}
|
||||
if (!patch->libraries.isEmpty())
|
||||
{
|
||||
if (!patch->libraries.isEmpty()) {
|
||||
QJsonArray array;
|
||||
for (auto value: patch->libraries)
|
||||
{
|
||||
for (auto value : patch->libraries) {
|
||||
array.append(OneSixVersionFormat::libraryToJson(value.get()));
|
||||
}
|
||||
root.insert("libraries", array);
|
||||
}
|
||||
if (!patch->mavenFiles.isEmpty())
|
||||
{
|
||||
if (!patch->mavenFiles.isEmpty()) {
|
||||
QJsonArray array;
|
||||
for (auto value: patch->mavenFiles)
|
||||
{
|
||||
for (auto value : patch->mavenFiles) {
|
||||
array.append(OneSixVersionFormat::libraryToJson(value.get()));
|
||||
}
|
||||
root.insert("mavenFiles", array);
|
||||
}
|
||||
if (!patch->jarMods.isEmpty())
|
||||
{
|
||||
if (!patch->jarMods.isEmpty()) {
|
||||
QJsonArray array;
|
||||
for (auto value: patch->jarMods)
|
||||
{
|
||||
for (auto value : patch->jarMods) {
|
||||
array.append(OneSixVersionFormat::jarModtoJson(value.get()));
|
||||
}
|
||||
root.insert("jarMods", array);
|
||||
}
|
||||
if (!patch->mods.isEmpty())
|
||||
{
|
||||
if (!patch->mods.isEmpty()) {
|
||||
QJsonArray array;
|
||||
for (auto value: patch->jarMods)
|
||||
{
|
||||
for (auto value : patch->jarMods) {
|
||||
array.append(OneSixVersionFormat::modtoJson(value.get()));
|
||||
}
|
||||
root.insert("mods", array);
|
||||
}
|
||||
if(!patch->m_requires.empty())
|
||||
{
|
||||
if (!patch->m_requires.empty()) {
|
||||
Meta::serializeRequires(root, &patch->m_requires, "requires");
|
||||
}
|
||||
if(!patch->conflicts.empty())
|
||||
{
|
||||
if (!patch->conflicts.empty()) {
|
||||
Meta::serializeRequires(root, &patch->conflicts, "conflicts");
|
||||
}
|
||||
if(patch->m_volatile)
|
||||
{
|
||||
if (patch->m_volatile) {
|
||||
root.insert("volatile", true);
|
||||
}
|
||||
// write the contents to a json document.
|
||||
@ -412,17 +350,14 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
}
|
||||
}
|
||||
|
||||
LibraryPtr OneSixVersionFormat::plusJarModFromJson(
|
||||
[[maybe_unused]] ProblemContainer & problems,
|
||||
const QJsonObject &libObj,
|
||||
const QString &filename,
|
||||
const QString &originalName
|
||||
) {
|
||||
LibraryPtr OneSixVersionFormat::plusJarModFromJson([[maybe_unused]] ProblemContainer& problems,
|
||||
const QJsonObject& libObj,
|
||||
const QString& filename,
|
||||
const QString& originalName)
|
||||
{
|
||||
LibraryPtr out(new Library());
|
||||
if (!libObj.contains("name"))
|
||||
{
|
||||
throw JSONValidationError(filename +
|
||||
"contains a jarmod that doesn't have a 'name' field");
|
||||
if (!libObj.contains("name")) {
|
||||
throw JSONValidationError(filename + "contains a jarmod that doesn't have a 'name' field");
|
||||
}
|
||||
|
||||
// just make up something unique on the spot for the library name.
|
||||
@ -439,36 +374,32 @@ LibraryPtr OneSixVersionFormat::plusJarModFromJson(
|
||||
// read the original name if present - some versions did not set it
|
||||
// it is the original jar mod filename before it got renamed at the point of addition
|
||||
auto displayName = libObj.value("originalName").toString();
|
||||
if(displayName.isEmpty())
|
||||
{
|
||||
if (displayName.isEmpty()) {
|
||||
auto fixed = originalName;
|
||||
fixed.remove(" (jar mod)");
|
||||
out->setDisplayName(fixed);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
out->setDisplayName(displayName);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
LibraryPtr OneSixVersionFormat::jarModFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename)
|
||||
LibraryPtr OneSixVersionFormat::jarModFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename)
|
||||
{
|
||||
return libraryFromJson(problems, libObj, filename);
|
||||
}
|
||||
|
||||
|
||||
QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod)
|
||||
QJsonObject OneSixVersionFormat::jarModtoJson(Library* jarmod)
|
||||
{
|
||||
return libraryToJson(jarmod);
|
||||
}
|
||||
|
||||
LibraryPtr OneSixVersionFormat::modFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename)
|
||||
LibraryPtr OneSixVersionFormat::modFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename)
|
||||
{
|
||||
return libraryFromJson(problems, libObj, filename);
|
||||
return libraryFromJson(problems, libObj, filename);
|
||||
}
|
||||
|
||||
QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod)
|
||||
QJsonObject OneSixVersionFormat::modtoJson(Library* jarmod)
|
||||
{
|
||||
return libraryToJson(jarmod);
|
||||
}
|
||||
|
@ -1,30 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <minecraft/VersionFile.h>
|
||||
#include <minecraft/PackProfile.h>
|
||||
#include <minecraft/Library.h>
|
||||
#include <QJsonDocument>
|
||||
#include <ProblemProvider.h>
|
||||
#include <minecraft/Library.h>
|
||||
#include <minecraft/PackProfile.h>
|
||||
#include <minecraft/VersionFile.h>
|
||||
#include <QJsonDocument>
|
||||
|
||||
class OneSixVersionFormat
|
||||
{
|
||||
public:
|
||||
class OneSixVersionFormat {
|
||||
public:
|
||||
// version files / profile patches
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, const bool requireOrder);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
|
||||
|
||||
// libraries
|
||||
static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
|
||||
static QJsonObject libraryToJson(Library *library);
|
||||
static LibraryPtr libraryFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename);
|
||||
static QJsonObject libraryToJson(Library* library);
|
||||
|
||||
// DEPRECATED: old 'plus' jar mods generated by the application
|
||||
static LibraryPtr plusJarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename, const QString &originalName);
|
||||
static LibraryPtr plusJarModFromJson(ProblemContainer& problems,
|
||||
const QJsonObject& libObj,
|
||||
const QString& filename,
|
||||
const QString& originalName);
|
||||
|
||||
// new jar mods derived from libraries
|
||||
static LibraryPtr jarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
|
||||
static QJsonObject jarModtoJson(Library * jarmod);
|
||||
static LibraryPtr jarModFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename);
|
||||
static QJsonObject jarModtoJson(Library* jarmod);
|
||||
|
||||
// mods, also derived from libraries
|
||||
static LibraryPtr modFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
|
||||
static QJsonObject modtoJson(Library * jarmod);
|
||||
static LibraryPtr modFromJson(ProblemContainer& problems, const QJsonObject& libObj, const QString& filename);
|
||||
static QJsonObject modtoJson(Library* jarmod);
|
||||
};
|
||||
|
@ -37,40 +37,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QFile>
|
||||
#include <QCryptographicHash>
|
||||
#include <Version.h>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QSaveFile>
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include "Exception.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ProfileUtils.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "minecraft/ProfileUtils.h"
|
||||
|
||||
#include "ComponentUpdateTask.h"
|
||||
#include "PackProfile.h"
|
||||
#include "PackProfile_p.h"
|
||||
#include "ComponentUpdateTask.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
|
||||
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
|
||||
{"net.minecraftforge", ResourceAPI::Forge},
|
||||
{"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
|
||||
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt},
|
||||
{"com.mumfrey.liteloader", ResourceAPI::LiteLoader}
|
||||
};
|
||||
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge },
|
||||
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
|
||||
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
|
||||
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
|
||||
|
||||
PackProfile::PackProfile(MinecraftInstance * instance)
|
||||
: QAbstractListModel()
|
||||
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
|
||||
{
|
||||
d.reset(new PackProfileData);
|
||||
d->m_instance = instance;
|
||||
@ -95,42 +92,35 @@ static QJsonObject componentToJsonV1(ComponentPtr component)
|
||||
QJsonObject obj;
|
||||
// critical
|
||||
obj.insert("uid", component->m_uid);
|
||||
if(!component->m_version.isEmpty())
|
||||
{
|
||||
if (!component->m_version.isEmpty()) {
|
||||
obj.insert("version", component->m_version);
|
||||
}
|
||||
if(component->m_dependencyOnly)
|
||||
{
|
||||
if (component->m_dependencyOnly) {
|
||||
obj.insert("dependencyOnly", true);
|
||||
}
|
||||
if(component->m_important)
|
||||
{
|
||||
if (component->m_important) {
|
||||
obj.insert("important", true);
|
||||
}
|
||||
if(component->m_disabled)
|
||||
{
|
||||
if (component->m_disabled) {
|
||||
obj.insert("disabled", true);
|
||||
}
|
||||
|
||||
// cached
|
||||
if(!component->m_cachedVersion.isEmpty())
|
||||
{
|
||||
if (!component->m_cachedVersion.isEmpty()) {
|
||||
obj.insert("cachedVersion", component->m_cachedVersion);
|
||||
}
|
||||
if(!component->m_cachedName.isEmpty())
|
||||
{
|
||||
if (!component->m_cachedName.isEmpty()) {
|
||||
obj.insert("cachedName", component->m_cachedName);
|
||||
}
|
||||
Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires");
|
||||
Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
|
||||
if(component->m_cachedVolatile)
|
||||
{
|
||||
if (component->m_cachedVolatile) {
|
||||
obj.insert("cachedVolatile", true);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & componentJsonPattern, const QJsonObject &obj)
|
||||
static ComponentPtr componentFromJsonV1(PackProfile* parent, const QString& componentJsonPattern, const QJsonObject& obj)
|
||||
{
|
||||
// critical
|
||||
auto uid = Json::requireString(obj.value("uid"));
|
||||
@ -153,51 +143,44 @@ static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & co
|
||||
}
|
||||
|
||||
// Save the given component container data to a file
|
||||
static bool savePackProfile(const QString & filename, const ComponentContainer & container)
|
||||
static bool savePackProfile(const QString& filename, const ComponentContainer& container)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert("formatVersion", currentComponentsFileVersion);
|
||||
QJsonArray orderArray;
|
||||
for(auto component: container)
|
||||
{
|
||||
for (auto component : container) {
|
||||
orderArray.append(componentToJsonV1(component));
|
||||
}
|
||||
obj.insert("components", orderArray);
|
||||
QSaveFile outFile(filename);
|
||||
if (!outFile.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Couldn't open" << outFile.fileName()
|
||||
<< "for writing:" << outFile.errorString();
|
||||
if (!outFile.open(QFile::WriteOnly)) {
|
||||
qCritical() << "Couldn't open" << outFile.fileName() << "for writing:" << outFile.errorString();
|
||||
return false;
|
||||
}
|
||||
auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
|
||||
if(outFile.write(data) != data.size())
|
||||
{
|
||||
qCritical() << "Couldn't write all the data into" << outFile.fileName()
|
||||
<< "because:" << outFile.errorString();
|
||||
if (outFile.write(data) != data.size()) {
|
||||
qCritical() << "Couldn't write all the data into" << outFile.fileName() << "because:" << outFile.errorString();
|
||||
return false;
|
||||
}
|
||||
if(!outFile.commit())
|
||||
{
|
||||
qCritical() << "Couldn't save" << outFile.fileName()
|
||||
<< "because:" << outFile.errorString();
|
||||
if (!outFile.commit()) {
|
||||
qCritical() << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read the given file into component containers
|
||||
static bool loadPackProfile(PackProfile * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container)
|
||||
static bool loadPackProfile(PackProfile* parent,
|
||||
const QString& filename,
|
||||
const QString& componentJsonPattern,
|
||||
ComponentContainer& container)
|
||||
{
|
||||
QFile componentsFile(filename);
|
||||
if (!componentsFile.exists())
|
||||
{
|
||||
if (!componentsFile.exists()) {
|
||||
qWarning() << "Components file doesn't exist. This should never happen.";
|
||||
return false;
|
||||
}
|
||||
if (!componentsFile.open(QFile::ReadOnly))
|
||||
{
|
||||
qCritical() << "Couldn't open" << componentsFile.fileName()
|
||||
<< " for reading:" << componentsFile.errorString();
|
||||
if (!componentsFile.open(QFile::ReadOnly)) {
|
||||
qCritical() << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
|
||||
qWarning() << "Ignoring overriden order";
|
||||
return false;
|
||||
}
|
||||
@ -205,33 +188,26 @@ static bool loadPackProfile(PackProfile * parent, const QString & filename, cons
|
||||
// and it's valid JSON
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
|
||||
qWarning() << "Ignoring overriden order";
|
||||
return false;
|
||||
}
|
||||
|
||||
// and then read it and process it if all above is true.
|
||||
try
|
||||
{
|
||||
try {
|
||||
auto obj = Json::requireObject(doc);
|
||||
// check order file version.
|
||||
auto version = Json::requireInteger(obj.value("formatVersion"));
|
||||
if (version != currentComponentsFileVersion)
|
||||
{
|
||||
throw JSONValidationError(QObject::tr("Invalid component file version, expected %1")
|
||||
.arg(currentComponentsFileVersion));
|
||||
if (version != currentComponentsFileVersion) {
|
||||
throw JSONValidationError(QObject::tr("Invalid component file version, expected %1").arg(currentComponentsFileVersion));
|
||||
}
|
||||
auto orderArray = Json::requireArray(obj.value("components"));
|
||||
for(auto item: orderArray)
|
||||
{
|
||||
for (auto item : orderArray) {
|
||||
auto comp_obj = Json::requireObject(item, "Component must be an object.");
|
||||
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
|
||||
}
|
||||
}
|
||||
catch ([[maybe_unused]] const JSONValidationError &err)
|
||||
{
|
||||
} catch ([[maybe_unused]] const JSONValidationError& err) {
|
||||
qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
|
||||
container.clear();
|
||||
return false;
|
||||
@ -245,8 +221,7 @@ static bool loadPackProfile(PackProfile * parent, const QString & filename, cons
|
||||
|
||||
void PackProfile::saveNow()
|
||||
{
|
||||
if(saveIsScheduled())
|
||||
{
|
||||
if (saveIsScheduled()) {
|
||||
d->m_saveTimer.stop();
|
||||
save_internal();
|
||||
}
|
||||
@ -265,13 +240,11 @@ void PackProfile::buildingFromScratch()
|
||||
|
||||
void PackProfile::scheduleSave()
|
||||
{
|
||||
if(!d->loaded)
|
||||
{
|
||||
if (!d->loaded) {
|
||||
qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name();
|
||||
return;
|
||||
}
|
||||
if(!d->dirty)
|
||||
{
|
||||
if (!d->dirty) {
|
||||
d->dirty = true;
|
||||
qDebug() << "Component list save is scheduled for" << d->m_instance->name();
|
||||
}
|
||||
@ -312,26 +285,20 @@ bool PackProfile::load()
|
||||
|
||||
// load the new component list and swap it with the current one...
|
||||
ComponentContainer newComponents;
|
||||
if(!loadPackProfile(this, filename, patchesPattern(), newComponents))
|
||||
{
|
||||
if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) {
|
||||
qCritical() << "Failed to load the component config for instance" << d->m_instance->name();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// FIXME: actually use fine-grained updates, not this...
|
||||
beginResetModel();
|
||||
// disconnect all the old components
|
||||
for(auto component: d->components)
|
||||
{
|
||||
for (auto component : d->components) {
|
||||
disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
|
||||
}
|
||||
d->components.clear();
|
||||
d->componentIndex.clear();
|
||||
for(auto component: newComponents)
|
||||
{
|
||||
if(d->componentIndex.contains(component->m_uid))
|
||||
{
|
||||
for (auto component : newComponents) {
|
||||
if (d->componentIndex.contains(component->m_uid)) {
|
||||
qWarning() << "Ignoring duplicate component entry" << component->m_uid;
|
||||
continue;
|
||||
}
|
||||
@ -348,8 +315,7 @@ bool PackProfile::load()
|
||||
void PackProfile::reload(Net::Mode netmode)
|
||||
{
|
||||
// Do not reload when the update/resolve task is running. It is in control.
|
||||
if(d->m_updateTask)
|
||||
{
|
||||
if (d->m_updateTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -359,8 +325,7 @@ void PackProfile::reload(Net::Mode netmode)
|
||||
// FIXME: differentiate when a reapply is required by propagating state from components
|
||||
invalidateLaunchProfile();
|
||||
|
||||
if(load())
|
||||
{
|
||||
if (load()) {
|
||||
resolve(netmode);
|
||||
}
|
||||
}
|
||||
@ -376,11 +341,10 @@ void PackProfile::resolve(Net::Mode netmode)
|
||||
d->m_updateTask.reset(updateTask);
|
||||
connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded);
|
||||
connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed);
|
||||
connect(updateTask, &ComponentUpdateTask::aborted, this, [this]{ updateFailed(tr("Aborted")); });
|
||||
connect(updateTask, &ComponentUpdateTask::aborted, this, [this] { updateFailed(tr("Aborted")); });
|
||||
d->m_updateTask->start();
|
||||
}
|
||||
|
||||
|
||||
void PackProfile::updateSucceeded()
|
||||
{
|
||||
qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
|
||||
@ -405,13 +369,11 @@ void PackProfile::appendComponent(ComponentPtr component)
|
||||
void PackProfile::insertComponent(size_t index, ComponentPtr component)
|
||||
{
|
||||
auto id = component->getID();
|
||||
if(id.isEmpty())
|
||||
{
|
||||
if (id.isEmpty()) {
|
||||
qWarning() << "Attempt to add a component with empty ID!";
|
||||
return;
|
||||
}
|
||||
if(d->componentIndex.contains(id))
|
||||
{
|
||||
if (d->componentIndex.contains(id)) {
|
||||
qWarning() << "Attempt to add a component that is already present!";
|
||||
return;
|
||||
}
|
||||
@ -425,21 +387,18 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component)
|
||||
|
||||
void PackProfile::componentDataChanged()
|
||||
{
|
||||
auto objPtr = qobject_cast<Component *>(sender());
|
||||
if(!objPtr)
|
||||
{
|
||||
auto objPtr = qobject_cast<Component*>(sender());
|
||||
if (!objPtr) {
|
||||
qWarning() << "PackProfile got dataChanged signal from a non-Component!";
|
||||
return;
|
||||
}
|
||||
if(objPtr->getID() == "net.minecraft") {
|
||||
if (objPtr->getID() == "net.minecraft") {
|
||||
emit minecraftChanged();
|
||||
}
|
||||
// figure out which one is it... in a seriously dumb way.
|
||||
int index = 0;
|
||||
for (auto component: d->components)
|
||||
{
|
||||
if(component.get() == objPtr)
|
||||
{
|
||||
for (auto component : d->components) {
|
||||
if (component.get() == objPtr) {
|
||||
emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
|
||||
scheduleSave();
|
||||
return;
|
||||
@ -452,14 +411,12 @@ void PackProfile::componentDataChanged()
|
||||
bool PackProfile::remove(const int index)
|
||||
{
|
||||
auto patch = getComponent(index);
|
||||
if (!patch->isRemovable())
|
||||
{
|
||||
if (!patch->isRemovable()) {
|
||||
qWarning() << "Patch" << patch->getID() << "is non-removable";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!removeComponent_internal(patch))
|
||||
{
|
||||
if (!removeComponent_internal(patch)) {
|
||||
qCritical() << "Patch" << patch->getID() << "could not be removed";
|
||||
return false;
|
||||
}
|
||||
@ -476,10 +433,8 @@ bool PackProfile::remove(const int index)
|
||||
bool PackProfile::remove(const QString id)
|
||||
{
|
||||
int i = 0;
|
||||
for (auto patch : d->components)
|
||||
{
|
||||
if (patch->getID() == id)
|
||||
{
|
||||
for (auto patch : d->components) {
|
||||
if (patch->getID() == id) {
|
||||
return remove(i);
|
||||
}
|
||||
i++;
|
||||
@ -490,13 +445,11 @@ bool PackProfile::remove(const QString id)
|
||||
bool PackProfile::customize(int index)
|
||||
{
|
||||
auto patch = getComponent(index);
|
||||
if (!patch->isCustomizable())
|
||||
{
|
||||
if (!patch->isCustomizable()) {
|
||||
qDebug() << "Patch" << patch->getID() << "is not customizable";
|
||||
return false;
|
||||
}
|
||||
if(!patch->customize())
|
||||
{
|
||||
if (!patch->customize()) {
|
||||
qCritical() << "Patch" << patch->getID() << "could not be customized";
|
||||
return false;
|
||||
}
|
||||
@ -508,13 +461,11 @@ bool PackProfile::customize(int index)
|
||||
bool PackProfile::revertToBase(int index)
|
||||
{
|
||||
auto patch = getComponent(index);
|
||||
if (!patch->isRevertible())
|
||||
{
|
||||
if (!patch->isRevertible()) {
|
||||
qDebug() << "Patch" << patch->getID() << "is not revertible";
|
||||
return false;
|
||||
}
|
||||
if(!patch->revert())
|
||||
{
|
||||
if (!patch->revert()) {
|
||||
qCritical() << "Patch" << patch->getID() << "could not be reverted";
|
||||
return false;
|
||||
}
|
||||
@ -523,11 +474,10 @@ bool PackProfile::revertToBase(int index)
|
||||
return true;
|
||||
}
|
||||
|
||||
ComponentPtr PackProfile::getComponent(const QString &id)
|
||||
ComponentPtr PackProfile::getComponent(const QString& id)
|
||||
{
|
||||
auto iter = d->componentIndex.find(id);
|
||||
if (iter == d->componentIndex.end())
|
||||
{
|
||||
if (iter == d->componentIndex.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return (*iter);
|
||||
@ -535,14 +485,13 @@ ComponentPtr PackProfile::getComponent(const QString &id)
|
||||
|
||||
ComponentPtr PackProfile::getComponent(size_t index)
|
||||
{
|
||||
if(index >= static_cast<size_t>(d->components.size()))
|
||||
{
|
||||
if (index >= static_cast<size_t>(d->components.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
return d->components[index];
|
||||
}
|
||||
|
||||
QVariant PackProfile::data(const QModelIndex &index, int role) const
|
||||
QVariant PackProfile::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@ -555,72 +504,58 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
|
||||
|
||||
auto patch = d->components.at(row);
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::CheckStateRole:
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn: {
|
||||
return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn:
|
||||
return patch->getName();
|
||||
case VersionColumn:
|
||||
{
|
||||
if(patch->isCustom())
|
||||
{
|
||||
return QString("%1 (Custom)").arg(patch->getVersion());
|
||||
}
|
||||
else
|
||||
{
|
||||
return patch->getVersion();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
{
|
||||
if (column == NameColumn) {
|
||||
auto severity = patch->getProblemSeverity();
|
||||
switch (severity)
|
||||
{
|
||||
case ProblemSeverity::Warning:
|
||||
return "warning";
|
||||
case ProblemSeverity::Error:
|
||||
return "error";
|
||||
switch (role) {
|
||||
case Qt::CheckStateRole: {
|
||||
switch (column) {
|
||||
case NameColumn: {
|
||||
return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
switch (column) {
|
||||
case NameColumn:
|
||||
return patch->getName();
|
||||
case VersionColumn: {
|
||||
if (patch->isCustom()) {
|
||||
return QString("%1 (Custom)").arg(patch->getVersion());
|
||||
} else {
|
||||
return patch->getVersion();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NameColumn) {
|
||||
auto severity = patch->getProblemSeverity();
|
||||
switch (severity) {
|
||||
case ProblemSeverity::Warning:
|
||||
return "warning";
|
||||
case ProblemSeverity::Error:
|
||||
return "error";
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool PackProfile::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (role == Qt::CheckStateRole) {
|
||||
auto component = d->components[index.row()];
|
||||
if (component->setEnabled(!component->isEnabled()))
|
||||
{
|
||||
if (component->setEnabled(!component->isEnabled())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -629,18 +564,15 @@ bool PackProfile::setData(const QModelIndex& index, [[maybe_unused]] const QVari
|
||||
|
||||
QVariant PackProfile::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal)
|
||||
{
|
||||
if (role == Qt::DisplayRole)
|
||||
{
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case VersionColumn:
|
||||
return tr("Version");
|
||||
default:
|
||||
return QVariant();
|
||||
if (orientation == Qt::Horizontal) {
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case VersionColumn:
|
||||
return tr("Version");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -648,7 +580,7 @@ QVariant PackProfile::headerData(int section, Qt::Orientation orientation, int r
|
||||
}
|
||||
|
||||
// FIXME: zero precision mess
|
||||
Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags PackProfile::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
@ -664,19 +596,18 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
|
||||
|
||||
auto patch = d->components.at(row);
|
||||
// TODO: this will need fine-tuning later...
|
||||
if(patch->canBeDisabled() && !d->interactionDisabled)
|
||||
{
|
||||
if (patch->canBeDisabled() && !d->interactionDisabled) {
|
||||
outFlags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
return outFlags;
|
||||
}
|
||||
|
||||
int PackProfile::rowCount(const QModelIndex &parent) const
|
||||
int PackProfile::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : d->components.size();
|
||||
}
|
||||
|
||||
int PackProfile::columnCount(const QModelIndex &parent) const
|
||||
int PackProfile::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
@ -684,12 +615,9 @@ int PackProfile::columnCount(const QModelIndex &parent) const
|
||||
void PackProfile::move(const int index, const MoveDirection direction)
|
||||
{
|
||||
int theirIndex;
|
||||
if (direction == MoveUp)
|
||||
{
|
||||
if (direction == MoveUp) {
|
||||
theirIndex = index - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
theirIndex = index + 1;
|
||||
}
|
||||
|
||||
@ -706,8 +634,7 @@ void PackProfile::move(const int index, const MoveDirection direction)
|
||||
auto from = getComponent(index);
|
||||
auto to = getComponent(theirIndex);
|
||||
|
||||
if (!from || !to || !to->isMoveable() || !from->isMoveable())
|
||||
{
|
||||
if (!from || !to || !to->isMoveable() || !from->isMoveable()) {
|
||||
return;
|
||||
}
|
||||
beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
|
||||
@ -775,8 +702,7 @@ void PackProfile::installAgents(QStringList selectedFiles)
|
||||
bool PackProfile::installEmpty(const QString& uid, const QString& name)
|
||||
{
|
||||
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(patchDir)) {
|
||||
return false;
|
||||
}
|
||||
auto f = std::make_shared<VersionFile>();
|
||||
@ -785,10 +711,8 @@ bool PackProfile::installEmpty(const QString& uid, const QString& name)
|
||||
f->version = "1";
|
||||
QString patchFileName = FS::PathCombine(patchDir, uid + ".json");
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
if (!file.open(QFile::WriteOnly)) {
|
||||
qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
@ -805,31 +729,25 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
|
||||
bool ok = true;
|
||||
// first, remove the patch file. this ensures it's not used anymore
|
||||
auto fileName = patch->getFilename();
|
||||
if(fileName.size())
|
||||
{
|
||||
if (fileName.size()) {
|
||||
QFile patchFile(fileName);
|
||||
if(patchFile.exists() && !patchFile.remove())
|
||||
{
|
||||
if (patchFile.exists() && !patchFile.remove()) {
|
||||
qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we need a generic way of removing local resources, not just jar mods...
|
||||
auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool
|
||||
{
|
||||
if (!jarMod->isLocal())
|
||||
{
|
||||
auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool {
|
||||
if (!jarMod->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
QStringList jar, temp1, temp2, temp3;
|
||||
jarMod->getApplicableFiles(d->m_instance->runtimeContext(), jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
|
||||
QFileInfo finfo (jar[0]);
|
||||
if(finfo.exists())
|
||||
{
|
||||
QFileInfo finfo(jar[0]);
|
||||
if (finfo.exists()) {
|
||||
QFile jarModFile(jar[0]);
|
||||
if(!jarModFile.remove())
|
||||
{
|
||||
if (!jarModFile.remove()) {
|
||||
qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString();
|
||||
return false;
|
||||
}
|
||||
@ -839,11 +757,9 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
|
||||
};
|
||||
|
||||
auto vFile = patch->getVersionFile();
|
||||
if(vFile)
|
||||
{
|
||||
auto &jarMods = vFile->jarMods;
|
||||
for(auto &jarmod: jarMods)
|
||||
{
|
||||
if (vFile) {
|
||||
auto& jarMods = vFile->jarMods;
|
||||
for (auto& jarmod : jarMods) {
|
||||
ok &= preRemoveJarMod(jarmod);
|
||||
}
|
||||
}
|
||||
@ -853,18 +769,15 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
|
||||
bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
{
|
||||
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(patchDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir()))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(auto filepath:filepaths)
|
||||
{
|
||||
for (auto filepath : filepaths) {
|
||||
QFileInfo sourceInfo(filepath);
|
||||
QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
QString target_filename = id + ".jar";
|
||||
@ -875,8 +788,7 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
QFileInfo targetInfo(finalPath);
|
||||
Q_ASSERT(!targetInfo.exists());
|
||||
|
||||
if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
|
||||
{
|
||||
if (!QFile::copy(sourceInfo.absoluteFilePath(), QFileInfo(finalPath).absoluteFilePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -892,10 +804,8 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
|
||||
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
if (!file.open(QFile::WriteOnly)) {
|
||||
qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
@ -911,14 +821,12 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
{
|
||||
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(patchDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString libDir = d->m_instance->getLocalLibraryPath();
|
||||
if (!FS::ensureFolderPathExists(libDir))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(libDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -930,15 +838,12 @@ bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
QString finalPath = FS::PathCombine(libDir, target_filename);
|
||||
|
||||
QFileInfo jarInfo(finalPath);
|
||||
if (jarInfo.exists())
|
||||
{
|
||||
if(!QFile::remove(finalPath))
|
||||
{
|
||||
if (jarInfo.exists()) {
|
||||
if (!QFile::remove(finalPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!QFile::copy(filepath, finalPath))
|
||||
{
|
||||
if (!QFile::copy(filepath, finalPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -953,10 +858,8 @@ bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
|
||||
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
if (!file.open(QFile::WriteOnly)) {
|
||||
qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
@ -1029,20 +932,15 @@ bool PackProfile::installAgents_internal(QStringList filepaths)
|
||||
|
||||
std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
|
||||
{
|
||||
if(!d->m_profile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!d->m_profile) {
|
||||
try {
|
||||
auto profile = std::make_shared<LaunchProfile>();
|
||||
for(auto file: d->components)
|
||||
{
|
||||
for (auto file : d->components) {
|
||||
qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
|
||||
file->applyTo(profile.get());
|
||||
}
|
||||
d->m_profile = profile;
|
||||
}
|
||||
catch (const Exception &error)
|
||||
{
|
||||
} catch (const Exception& error) {
|
||||
qWarning() << "Couldn't apply profile patches because: " << error.cause();
|
||||
}
|
||||
}
|
||||
@ -1052,20 +950,16 @@ std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
|
||||
bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important)
|
||||
{
|
||||
auto iter = d->componentIndex.find(uid);
|
||||
if(iter != d->componentIndex.end())
|
||||
{
|
||||
if (iter != d->componentIndex.end()) {
|
||||
ComponentPtr component = *iter;
|
||||
// set existing
|
||||
if(component->revert())
|
||||
{
|
||||
if (component->revert()) {
|
||||
component->setVersion(version);
|
||||
component->setImportant(important);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// add new
|
||||
auto component = makeShared<Component>(this, uid);
|
||||
component->m_version = version;
|
||||
@ -1078,8 +972,7 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
|
||||
QString PackProfile::getComponentVersion(const QString& uid) const
|
||||
{
|
||||
const auto iter = d->componentIndex.find(uid);
|
||||
if (iter != d->componentIndex.end())
|
||||
{
|
||||
if (iter != d->componentIndex.end()) {
|
||||
return (*iter)->getVersion();
|
||||
}
|
||||
return QString();
|
||||
@ -1087,10 +980,10 @@ QString PackProfile::getComponentVersion(const QString& uid) const
|
||||
|
||||
void PackProfile::disableInteraction(bool disable)
|
||||
{
|
||||
if(d->interactionDisabled != disable) {
|
||||
if (d->interactionDisabled != disable) {
|
||||
d->interactionDisabled = disable;
|
||||
auto size = d->components.size();
|
||||
if(size) {
|
||||
if (size) {
|
||||
emit dataChanged(index(0), index(size - 1));
|
||||
}
|
||||
}
|
||||
|
@ -41,44 +41,39 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
#include "Library.h"
|
||||
#include "LaunchProfile.h"
|
||||
#include "Component.h"
|
||||
#include "ProfileUtils.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "Component.h"
|
||||
#include "LaunchProfile.h"
|
||||
#include "Library.h"
|
||||
#include "MojangDownloadInfo.h"
|
||||
#include "net/Mode.h"
|
||||
#include "ProfileUtils.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
class MinecraftInstance;
|
||||
struct PackProfileData;
|
||||
class ComponentUpdateTask;
|
||||
|
||||
class PackProfile : public QAbstractListModel
|
||||
{
|
||||
class PackProfile : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
friend ComponentUpdateTask;
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn = 0,
|
||||
VersionColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
explicit PackProfile(MinecraftInstance * instance);
|
||||
public:
|
||||
enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS };
|
||||
|
||||
explicit PackProfile(MinecraftInstance* instance);
|
||||
virtual ~PackProfile();
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
virtual int columnCount(const QModelIndex &parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
virtual int columnCount(const QModelIndex& parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
/// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
|
||||
void buildingFromScratch();
|
||||
@ -121,15 +116,15 @@ public:
|
||||
std::shared_ptr<LaunchProfile> getProfile() const;
|
||||
|
||||
// NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config
|
||||
void setOldConfigVersion(const QString &uid, const QString &version);
|
||||
void setOldConfigVersion(const QString& uid, const QString& version);
|
||||
|
||||
QString getComponentVersion(const QString &uid) const;
|
||||
QString getComponentVersion(const QString& uid) const;
|
||||
|
||||
bool setComponentVersion(const QString &uid, const QString &version, bool important = false);
|
||||
bool setComponentVersion(const QString& uid, const QString& version, bool important = false);
|
||||
|
||||
bool installEmpty(const QString &uid, const QString &name);
|
||||
bool installEmpty(const QString& uid, const QString& name);
|
||||
|
||||
QString patchFilePathForUid(const QString &uid) const;
|
||||
QString patchFilePathForUid(const QString& uid) const;
|
||||
|
||||
/// if there is a save scheduled, do it now.
|
||||
void saveNow();
|
||||
@ -137,12 +132,12 @@ public:
|
||||
/// helper method, returns RuntimeContext of instance
|
||||
RuntimeContext runtimeContext();
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void minecraftChanged();
|
||||
|
||||
public:
|
||||
public:
|
||||
/// get the profile component by id
|
||||
ComponentPtr getComponent(const QString &id);
|
||||
ComponentPtr getComponent(const QString& id);
|
||||
|
||||
/// get the profile component by index
|
||||
ComponentPtr getComponent(size_t index);
|
||||
@ -153,7 +148,7 @@ public:
|
||||
|
||||
std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
|
||||
|
||||
private:
|
||||
private:
|
||||
void scheduleSave();
|
||||
bool saveIsScheduled() const;
|
||||
|
||||
@ -166,21 +161,20 @@ private:
|
||||
QString componentsFilePath() const;
|
||||
QString patchesPattern() const;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void save_internal();
|
||||
void updateSucceeded();
|
||||
void updateFailed(const QString & error);
|
||||
void updateFailed(const QString& error);
|
||||
void componentDataChanged();
|
||||
void disableInteraction(bool disable);
|
||||
|
||||
private:
|
||||
private:
|
||||
bool load();
|
||||
bool installJarMods_internal(QStringList filepaths);
|
||||
bool installCustomJar_internal(QString filepath);
|
||||
bool installAgents_internal(QStringList filepaths);
|
||||
bool removeComponent_internal(ComponentPtr patch);
|
||||
|
||||
private: /* data */
|
||||
|
||||
private: /* data */
|
||||
std::unique_ptr<PackProfileData> d;
|
||||
};
|
||||
|
@ -1,19 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "Component.h"
|
||||
#include <map>
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
#include <map>
|
||||
#include "Component.h"
|
||||
|
||||
class MinecraftInstance;
|
||||
using ComponentContainer = QList<ComponentPtr>;
|
||||
using ComponentIndex = QMap<QString, ComponentPtr>;
|
||||
|
||||
struct PackProfileData
|
||||
{
|
||||
struct PackProfileData {
|
||||
// the instance this belongs to
|
||||
MinecraftInstance *m_instance;
|
||||
MinecraftInstance* m_instance;
|
||||
|
||||
// the launch profile (volatile, temporary thing created on demand)
|
||||
std::shared_ptr<LaunchProfile> m_profile;
|
||||
@ -27,4 +26,3 @@ struct PackProfileData
|
||||
bool loaded = false;
|
||||
bool interactionDisabled = true;
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include "ParseUtils.h"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <cstdlib>
|
||||
|
||||
QDateTime timeFromS3Time(QString str)
|
||||
@ -22,7 +22,7 @@ QString timeToS3Time(QDateTime time)
|
||||
int offsetMinutes = offsetAbs % 3600;
|
||||
offsetAbs -= offsetMinutes;
|
||||
offsetMinutes /= 60;
|
||||
|
||||
|
||||
int offsetHours = offsetAbs / 3600;
|
||||
|
||||
QString raw = time.toString("yyyy-MM-ddTHH:mm:ss");
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
/// take the timestamp used by S3 and turn it into QDateTime
|
||||
QDateTime timeFromS3Time(QString str);
|
||||
|
@ -34,33 +34,29 @@
|
||||
*/
|
||||
|
||||
#include "ProfileUtils.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "Json.h"
|
||||
#include <QDebug>
|
||||
#include "Json.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QRegularExpression>
|
||||
#include <QSaveFile>
|
||||
|
||||
namespace ProfileUtils
|
||||
{
|
||||
namespace ProfileUtils {
|
||||
|
||||
static const int currentOrderFileVersion = 1;
|
||||
|
||||
bool readOverrideOrders(QString path, PatchOrder &order)
|
||||
bool readOverrideOrders(QString path, PatchOrder& order)
|
||||
{
|
||||
QFile orderFile(path);
|
||||
if (!orderFile.exists())
|
||||
{
|
||||
if (!orderFile.exists()) {
|
||||
qWarning() << "Order file doesn't exist. Ignoring.";
|
||||
return false;
|
||||
}
|
||||
if (!orderFile.open(QFile::ReadOnly))
|
||||
{
|
||||
qCritical() << "Couldn't open" << orderFile.fileName()
|
||||
<< " for reading:" << orderFile.errorString();
|
||||
if (!orderFile.open(QFile::ReadOnly)) {
|
||||
qCritical() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString();
|
||||
qWarning() << "Ignoring overriden order";
|
||||
return false;
|
||||
}
|
||||
@ -68,32 +64,25 @@ bool readOverrideOrders(QString path, PatchOrder &order)
|
||||
// and it's valid JSON
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
|
||||
qWarning() << "Ignoring overriden order";
|
||||
return false;
|
||||
}
|
||||
|
||||
// and then read it and process it if all above is true.
|
||||
try
|
||||
{
|
||||
try {
|
||||
auto obj = Json::requireObject(doc);
|
||||
// check order file version.
|
||||
auto version = Json::requireInteger(obj.value("version"));
|
||||
if (version != currentOrderFileVersion)
|
||||
{
|
||||
throw JSONValidationError(QObject::tr("Invalid order file version, expected %1")
|
||||
.arg(currentOrderFileVersion));
|
||||
if (version != currentOrderFileVersion) {
|
||||
throw JSONValidationError(QObject::tr("Invalid order file version, expected %1").arg(currentOrderFileVersion));
|
||||
}
|
||||
auto orderArray = Json::requireArray(obj.value("order"));
|
||||
for(auto item: orderArray)
|
||||
{
|
||||
for (auto item : orderArray) {
|
||||
order.append(Json::requireString(item));
|
||||
}
|
||||
}
|
||||
catch ([[maybe_unused]] const JSONValidationError &err)
|
||||
{
|
||||
} catch ([[maybe_unused]] const JSONValidationError& err) {
|
||||
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
|
||||
qWarning() << "Ignoring overriden order";
|
||||
order.clear();
|
||||
@ -111,23 +100,19 @@ static VersionFilePtr createErrorVersionFile(QString fileId, QString filepath, Q
|
||||
return outError;
|
||||
}
|
||||
|
||||
static VersionFilePtr guardedParseJson(const QJsonDocument & doc,const QString &fileId,const QString &filepath,const bool &requireOrder)
|
||||
static VersionFilePtr guardedParseJson(const QJsonDocument& doc, const QString& fileId, const QString& filepath, const bool& requireOrder)
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
} catch (const Exception& e) {
|
||||
return createErrorVersionFile(fileId, filepath, e.cause());
|
||||
}
|
||||
}
|
||||
|
||||
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
|
||||
VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder)
|
||||
{
|
||||
QFile file(fileInfo.absoluteFilePath());
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString());
|
||||
return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr);
|
||||
}
|
||||
@ -135,14 +120,11 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
|
||||
auto data = file.readAll();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
file.close();
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
int line = 1;
|
||||
int column = 0;
|
||||
for(int i = 0; i < error.offset; i++)
|
||||
{
|
||||
if(data[i] == '\n')
|
||||
{
|
||||
for (int i = 0; i < error.offset; i++) {
|
||||
if (data[i] == '\n') {
|
||||
line++;
|
||||
column = 0;
|
||||
continue;
|
||||
@ -150,26 +132,25 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
|
||||
column++;
|
||||
}
|
||||
auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.")
|
||||
.arg(fileInfo.fileName(), error.errorString())
|
||||
.arg(line).arg(column);
|
||||
.arg(fileInfo.fileName(), error.errorString())
|
||||
.arg(line)
|
||||
.arg(column);
|
||||
return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr);
|
||||
}
|
||||
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
|
||||
}
|
||||
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString & filename)
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString& filename)
|
||||
{
|
||||
auto data = doc.toJson();
|
||||
QSaveFile jsonFile(filename);
|
||||
if(!jsonFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!jsonFile.open(QIODevice::WriteOnly)) {
|
||||
jsonFile.cancelWriting();
|
||||
qWarning() << "Couldn't open" << filename << "for writing";
|
||||
return false;
|
||||
}
|
||||
jsonFile.write(data);
|
||||
if(!jsonFile.commit())
|
||||
{
|
||||
if (!jsonFile.commit()) {
|
||||
qWarning() << "Couldn't save" << filename;
|
||||
return false;
|
||||
}
|
||||
@ -178,13 +159,10 @@ bool saveJsonFile(const QJsonDocument doc, const QString & filename)
|
||||
|
||||
void removeLwjglFromPatch(VersionFilePtr patch)
|
||||
{
|
||||
auto filter = [](QList<LibraryPtr>& libs)
|
||||
{
|
||||
auto filter = [](QList<LibraryPtr>& libs) {
|
||||
QList<LibraryPtr> filteredLibs;
|
||||
for (auto lib : libs)
|
||||
{
|
||||
if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix()))
|
||||
{
|
||||
for (auto lib : libs) {
|
||||
if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) {
|
||||
filteredLibs.append(lib);
|
||||
}
|
||||
}
|
||||
@ -192,4 +170,4 @@ void removeLwjglFromPatch(VersionFilePtr patch)
|
||||
};
|
||||
filter(patch->libraries);
|
||||
}
|
||||
}
|
||||
} // namespace ProfileUtils
|
||||
|
@ -37,24 +37,22 @@
|
||||
#include "Library.h"
|
||||
#include "VersionFile.h"
|
||||
|
||||
namespace ProfileUtils
|
||||
{
|
||||
namespace ProfileUtils {
|
||||
typedef QStringList PatchOrder;
|
||||
|
||||
/// Read and parse a OneSix format order file
|
||||
bool readOverrideOrders(QString path, PatchOrder &order);
|
||||
bool readOverrideOrders(QString path, PatchOrder& order);
|
||||
|
||||
/// Write a OneSix format order file
|
||||
bool writeOverrideOrders(QString path, const PatchOrder &order);
|
||||
|
||||
bool writeOverrideOrders(QString path, const PatchOrder& order);
|
||||
|
||||
/// Parse a version file in JSON format
|
||||
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder);
|
||||
VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder);
|
||||
|
||||
/// Save a JSON file (in any format)
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString & filename);
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString& filename);
|
||||
|
||||
/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
|
||||
void removeLwjglFromPatch(VersionFilePtr patch);
|
||||
|
||||
}
|
||||
} // namespace ProfileUtils
|
||||
|
@ -33,8 +33,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "Rule.h"
|
||||
|
||||
@ -47,7 +47,7 @@ RuleAction RuleAction_fromString(QString name)
|
||||
return Defer;
|
||||
}
|
||||
|
||||
QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
|
||||
QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject& objectWithRules)
|
||||
{
|
||||
QList<std::shared_ptr<Rule>> rules;
|
||||
auto rulesVal = objectWithRules.value("rules");
|
||||
@ -55,8 +55,7 @@ QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
|
||||
return rules;
|
||||
|
||||
QJsonArray ruleList = rulesVal.toArray();
|
||||
for (auto ruleVal : ruleList)
|
||||
{
|
||||
for (auto ruleVal : ruleList) {
|
||||
std::shared_ptr<Rule> rule;
|
||||
if (!ruleVal.isObject())
|
||||
continue;
|
||||
@ -69,8 +68,7 @@ QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
|
||||
continue;
|
||||
|
||||
auto osVal = ruleObj.value("os");
|
||||
if (!osVal.isObject())
|
||||
{
|
||||
if (!osVal.isObject()) {
|
||||
// add a new implicit action rule
|
||||
rules.append(ImplicitRule::create(action));
|
||||
continue;
|
||||
@ -102,12 +100,10 @@ QJsonObject OsRule::toJson()
|
||||
QJsonObject osObj;
|
||||
{
|
||||
osObj.insert("name", m_system);
|
||||
if(!m_version_regexp.isEmpty())
|
||||
{
|
||||
if (!m_version_regexp.isEmpty()) {
|
||||
osObj.insert("version", m_version_regexp);
|
||||
}
|
||||
}
|
||||
ruleObj.insert("os", osObj);
|
||||
return ruleObj;
|
||||
}
|
||||
|
||||
|
@ -35,37 +35,29 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include "RuntimeContext.h"
|
||||
|
||||
class Library;
|
||||
class Rule;
|
||||
|
||||
enum RuleAction
|
||||
{
|
||||
Allow,
|
||||
Disallow,
|
||||
Defer
|
||||
};
|
||||
enum RuleAction { Allow, Disallow, Defer };
|
||||
|
||||
QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules);
|
||||
QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject& objectWithRules);
|
||||
|
||||
class Rule
|
||||
{
|
||||
protected:
|
||||
class Rule {
|
||||
protected:
|
||||
RuleAction m_result;
|
||||
virtual bool applies(const Library *parent, const RuntimeContext & runtimeContext) = 0;
|
||||
virtual bool applies(const Library* parent, const RuntimeContext& runtimeContext) = 0;
|
||||
|
||||
public:
|
||||
Rule(RuleAction result) : m_result(result)
|
||||
{
|
||||
}
|
||||
public:
|
||||
Rule(RuleAction result) : m_result(result) {}
|
||||
virtual ~Rule() {}
|
||||
virtual QJsonObject toJson() = 0;
|
||||
RuleAction apply(const Library *parent, const RuntimeContext & runtimeContext)
|
||||
RuleAction apply(const Library* parent, const RuntimeContext& runtimeContext)
|
||||
{
|
||||
if (applies(parent, runtimeContext))
|
||||
return m_result;
|
||||
@ -74,48 +66,31 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class OsRule : public Rule
|
||||
{
|
||||
private:
|
||||
class OsRule : public Rule {
|
||||
private:
|
||||
// the OS
|
||||
QString m_system;
|
||||
// the OS version regexp
|
||||
QString m_version_regexp;
|
||||
|
||||
protected:
|
||||
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
|
||||
{
|
||||
return runtimeContext.classifierMatches(m_system);
|
||||
}
|
||||
OsRule(RuleAction result, QString system, QString version_regexp)
|
||||
: Rule(result), m_system(system), m_version_regexp(version_regexp)
|
||||
{
|
||||
}
|
||||
protected:
|
||||
virtual bool applies(const Library*, const RuntimeContext& runtimeContext) { return runtimeContext.classifierMatches(m_system); }
|
||||
OsRule(RuleAction result, QString system, QString version_regexp) : Rule(result), m_system(system), m_version_regexp(version_regexp) {}
|
||||
|
||||
public:
|
||||
public:
|
||||
virtual QJsonObject toJson();
|
||||
static std::shared_ptr<OsRule> create(RuleAction result, QString system,
|
||||
QString version_regexp)
|
||||
static std::shared_ptr<OsRule> create(RuleAction result, QString system, QString version_regexp)
|
||||
{
|
||||
return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp));
|
||||
}
|
||||
};
|
||||
|
||||
class ImplicitRule : public Rule
|
||||
{
|
||||
protected:
|
||||
virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
ImplicitRule(RuleAction result) : Rule(result)
|
||||
{
|
||||
}
|
||||
class ImplicitRule : public Rule {
|
||||
protected:
|
||||
virtual bool applies(const Library*, [[maybe_unused]] const RuntimeContext& runtimeContext) { return true; }
|
||||
ImplicitRule(RuleAction result) : Rule(result) {}
|
||||
|
||||
public:
|
||||
public:
|
||||
virtual QJsonObject toJson();
|
||||
static std::shared_ptr<ImplicitRule> create(RuleAction result)
|
||||
{
|
||||
return std::shared_ptr<ImplicitRule>(new ImplicitRule(result));
|
||||
}
|
||||
static std::shared_ptr<ImplicitRule> create(RuleAction result) { return std::shared_ptr<ImplicitRule>(new ImplicitRule(result)); }
|
||||
};
|
||||
|
@ -8,7 +8,11 @@
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version)
|
||||
: InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version))
|
||||
: InstanceCreationTask()
|
||||
, m_version(std::move(version))
|
||||
, m_using_loader(true)
|
||||
, m_loader(std::move(loader))
|
||||
, m_loader_version(std::move(loader_version))
|
||||
{}
|
||||
|
||||
bool VanillaCreationTask::createInstance()
|
||||
@ -22,7 +26,7 @@ bool VanillaCreationTask::createInstance()
|
||||
auto components = inst.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
|
||||
if(m_using_loader)
|
||||
if (m_using_loader)
|
||||
components->setComponentVersion(m_loader, m_loader_version->descriptor());
|
||||
|
||||
inst.setName(name());
|
||||
|
@ -39,23 +39,22 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "minecraft/VersionFile.h"
|
||||
#include "ParseUtils.h"
|
||||
#include "minecraft/Library.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "ParseUtils.h"
|
||||
#include "minecraft/VersionFile.h"
|
||||
|
||||
#include <Version.h>
|
||||
|
||||
static bool isMinecraftVersion(const QString &uid)
|
||||
static bool isMinecraftVersion(const QString& uid)
|
||||
{
|
||||
return uid == "net.minecraft";
|
||||
}
|
||||
|
||||
void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtimeContext)
|
||||
void VersionFile::applyTo(LaunchProfile* profile, const RuntimeContext& runtimeContext)
|
||||
{
|
||||
// Only real Minecraft can set those. Don't let anything override them.
|
||||
if (isMinecraftVersion(uid))
|
||||
{
|
||||
if (isMinecraftVersion(uid)) {
|
||||
profile->applyMinecraftVersion(version);
|
||||
profile->applyMinecraftVersionType(type);
|
||||
// HACK: ignore assets from other version files than Minecraft
|
||||
@ -75,16 +74,13 @@ void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtime
|
||||
profile->applyTraits(traits);
|
||||
profile->applyCompatibleJavaMajors(compatibleJavaMajors);
|
||||
|
||||
for (auto library : libraries)
|
||||
{
|
||||
for (auto library : libraries) {
|
||||
profile->applyLibrary(library, runtimeContext);
|
||||
}
|
||||
for (auto mavenFile : mavenFiles)
|
||||
{
|
||||
for (auto mavenFile : mavenFiles) {
|
||||
profile->applyMavenFile(mavenFile, runtimeContext);
|
||||
}
|
||||
for (auto agent : agents)
|
||||
{
|
||||
for (auto agent : agents) {
|
||||
profile->applyAgent(agent, runtimeContext);
|
||||
}
|
||||
profile->applyProblemSeverity(getProblemSeverity());
|
||||
|
@ -35,17 +35,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QDateTime>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <memory>
|
||||
#include "minecraft/Rule.h"
|
||||
#include "ProblemProvider.h"
|
||||
#include "Library.h"
|
||||
#include "Agent.h"
|
||||
#include <meta/JsonFormat.h>
|
||||
#include <memory>
|
||||
#include "Agent.h"
|
||||
#include "Library.h"
|
||||
#include "ProblemProvider.h"
|
||||
#include "minecraft/Rule.h"
|
||||
|
||||
class PackProfile;
|
||||
class VersionFile;
|
||||
@ -54,14 +54,14 @@ struct MojangDownloadInfo;
|
||||
struct MojangAssetIndexInfo;
|
||||
|
||||
using VersionFilePtr = std::shared_ptr<VersionFile>;
|
||||
class VersionFile : public ProblemContainer
|
||||
{
|
||||
class VersionFile : public ProblemContainer {
|
||||
friend class MojangVersionFormat;
|
||||
friend class OneSixVersionFormat;
|
||||
public: /* methods */
|
||||
void applyTo(LaunchProfile* profile, const RuntimeContext & runtimeContext);
|
||||
|
||||
public: /* data */
|
||||
public: /* methods */
|
||||
void applyTo(LaunchProfile* profile, const RuntimeContext& runtimeContext);
|
||||
|
||||
public: /* data */
|
||||
/// Prism Launcher: order hint for this version file if no explicit order is set
|
||||
int order = 0;
|
||||
|
||||
@ -149,11 +149,10 @@ public: /* data */
|
||||
/// is volatile -- may be removed as soon as it is no longer needed by something else
|
||||
bool m_volatile = false;
|
||||
|
||||
public:
|
||||
public:
|
||||
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
|
||||
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
|
||||
QMap<QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
|
||||
|
||||
// Mojang: extended asset index download information
|
||||
std::shared_ptr<MojangAssetIndexInfo> mojangAssetIndex;
|
||||
};
|
||||
|
||||
|
@ -6,19 +6,17 @@ VersionFilterData g_VersionFilterData = VersionFilterData();
|
||||
VersionFilterData::VersionFilterData()
|
||||
{
|
||||
// 1.3.*
|
||||
auto libs13 =
|
||||
QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"},
|
||||
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"},
|
||||
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}};
|
||||
auto libs13 = QList<FMLlib>{ { "argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b" },
|
||||
{ "guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f" },
|
||||
{ "asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82" } };
|
||||
|
||||
fmlLibsMapping["1.3.2"] = libs13;
|
||||
|
||||
// 1.4.*
|
||||
auto libs14 = QList<FMLlib>{
|
||||
{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"},
|
||||
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"},
|
||||
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"},
|
||||
{"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb"}};
|
||||
auto libs14 = QList<FMLlib>{ { "argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b" },
|
||||
{ "guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f" },
|
||||
{ "asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82" },
|
||||
{ "bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb" } };
|
||||
|
||||
fmlLibsMapping["1.4"] = libs14;
|
||||
fmlLibsMapping["1.4.1"] = libs14;
|
||||
@ -30,43 +28,38 @@ VersionFilterData::VersionFilterData()
|
||||
fmlLibsMapping["1.4.7"] = libs14;
|
||||
|
||||
// 1.5
|
||||
fmlLibsMapping["1.5"] = QList<FMLlib>{
|
||||
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"},
|
||||
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"},
|
||||
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"},
|
||||
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"},
|
||||
{"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8"},
|
||||
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}};
|
||||
fmlLibsMapping["1.5"] = QList<FMLlib>{ { "argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51" },
|
||||
{ "guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a" },
|
||||
{ "asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58" },
|
||||
{ "bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65" },
|
||||
{ "deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8" },
|
||||
{ "scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85" } };
|
||||
|
||||
// 1.5.1
|
||||
fmlLibsMapping["1.5.1"] = QList<FMLlib>{
|
||||
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"},
|
||||
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"},
|
||||
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"},
|
||||
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"},
|
||||
{"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6"},
|
||||
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}};
|
||||
fmlLibsMapping["1.5.1"] = QList<FMLlib>{ { "argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51" },
|
||||
{ "guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a" },
|
||||
{ "asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58" },
|
||||
{ "bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65" },
|
||||
{ "deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6" },
|
||||
{ "scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85" } };
|
||||
|
||||
// 1.5.2
|
||||
fmlLibsMapping["1.5.2"] = QList<FMLlib>{
|
||||
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"},
|
||||
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"},
|
||||
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"},
|
||||
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"},
|
||||
{"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"},
|
||||
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}};
|
||||
fmlLibsMapping["1.5.2"] = QList<FMLlib>{ { "argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51" },
|
||||
{ "guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a" },
|
||||
{ "asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58" },
|
||||
{ "bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65" },
|
||||
{ "deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9" },
|
||||
{ "scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85" } };
|
||||
|
||||
// don't use installers for those.
|
||||
forgeInstallerBlacklist = QSet<QString>({"1.5.2"});
|
||||
forgeInstallerBlacklist = QSet<QString>({ "1.5.2" });
|
||||
|
||||
// FIXME: remove, used for deciding when core mods should display
|
||||
legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00");
|
||||
lwjglWhitelist =
|
||||
QSet<QString>{"net.java.jinput:jinput", "net.java.jinput:jinput-platform",
|
||||
"net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl",
|
||||
"org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"};
|
||||
lwjglWhitelist = QSet<QString>{ "net.java.jinput:jinput", "net.java.jinput:jinput-platform", "net.java.jutils:jutils",
|
||||
"org.lwjgl.lwjgl:lwjgl", "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform" };
|
||||
|
||||
java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00");
|
||||
java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00");
|
||||
java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00");
|
||||
java17BeginsDate = timeFromS3Time("2021-11-16T17:04:48+00:00");
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
#pragma once
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QSet>
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
struct FMLlib
|
||||
{
|
||||
struct FMLlib {
|
||||
QString filename;
|
||||
QString checksum;
|
||||
};
|
||||
|
||||
struct VersionFilterData
|
||||
{
|
||||
struct VersionFilterData {
|
||||
VersionFilterData();
|
||||
// mapping between minecraft versions and FML libraries required
|
||||
QMap<QString, QList<FMLlib>> fmlLibsMapping;
|
||||
|
@ -34,23 +34,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <QSaveFile>
|
||||
#include <QDirIterator>
|
||||
#include "World.h"
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QSaveFile>
|
||||
#include <QString>
|
||||
|
||||
#include "GZip.h"
|
||||
#include <MMCZip.h>
|
||||
#include <FileSystem.h>
|
||||
#include <sstream>
|
||||
#include <MMCZip.h>
|
||||
#include <io/stream_reader.h>
|
||||
#include <tag_string.h>
|
||||
#include <tag_primitive.h>
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <tag_primitive.h>
|
||||
#include <tag_string.h>
|
||||
#include <sstream>
|
||||
#include "GZip.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
@ -58,16 +58,15 @@
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
using std::optional;
|
||||
using std::nullopt;
|
||||
using std::optional;
|
||||
|
||||
GameType::GameType(std::optional<int> original):
|
||||
original(original)
|
||||
GameType::GameType(std::optional<int> original) : original(original)
|
||||
{
|
||||
if(!original) {
|
||||
if (!original) {
|
||||
return;
|
||||
}
|
||||
switch(*original) {
|
||||
switch (*original) {
|
||||
case 0:
|
||||
type = GameType::Survival;
|
||||
break;
|
||||
@ -87,8 +86,7 @@ GameType::GameType(std::optional<int> original):
|
||||
|
||||
QString GameType::toTranslatedString() const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
switch (type) {
|
||||
case GameType::Survival:
|
||||
return QCoreApplication::translate("GameType", "Survival");
|
||||
case GameType::Creative:
|
||||
@ -100,7 +98,7 @@ QString GameType::toTranslatedString() const
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(original) {
|
||||
if (original) {
|
||||
return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original);
|
||||
}
|
||||
return QCoreApplication::translate("GameType", "Undefined");
|
||||
@ -108,8 +106,7 @@ QString GameType::toTranslatedString() const
|
||||
|
||||
QString GameType::toLogString() const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
switch (type) {
|
||||
case GameType::Survival:
|
||||
return "Survival";
|
||||
case GameType::Creative:
|
||||
@ -121,108 +118,94 @@ QString GameType::toLogString() const
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(original) {
|
||||
if (original) {
|
||||
return QString("Unknown (%1)").arg(*original);
|
||||
}
|
||||
return "Undefined";
|
||||
}
|
||||
|
||||
std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data)
|
||||
std::unique_ptr<nbt::tag_compound> parseLevelDat(QByteArray data)
|
||||
{
|
||||
QByteArray output;
|
||||
if(!GZip::unzip(data, output))
|
||||
{
|
||||
if (!GZip::unzip(data, output)) {
|
||||
return nullptr;
|
||||
}
|
||||
std::istringstream foo(std::string(output.constData(), output.size()));
|
||||
try {
|
||||
auto pair = nbt::io::read_compound(foo);
|
||||
|
||||
if(pair.first != "")
|
||||
if (pair.first != "")
|
||||
return nullptr;
|
||||
|
||||
if(pair.second == nullptr)
|
||||
if (pair.second == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return std::move(pair.second);
|
||||
}
|
||||
catch (const nbt::io::input_error &e)
|
||||
{
|
||||
} catch (const nbt::io::input_error& e) {
|
||||
qWarning() << "Unable to parse level.dat:" << e.what();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray serializeLevelDat(nbt::tag_compound * levelInfo)
|
||||
QByteArray serializeLevelDat(nbt::tag_compound* levelInfo)
|
||||
{
|
||||
std::ostringstream s;
|
||||
nbt::io::write_tag("", *levelInfo, s);
|
||||
QByteArray val( s.str().data(), (int) s.str().size() );
|
||||
QByteArray val(s.str().data(), (int)s.str().size());
|
||||
return val;
|
||||
}
|
||||
|
||||
QString getLevelDatFromFS(const QFileInfo &file)
|
||||
QString getLevelDatFromFS(const QFileInfo& file)
|
||||
{
|
||||
QDir worldDir(file.filePath());
|
||||
if(!file.isDir() || !worldDir.exists("level.dat"))
|
||||
{
|
||||
if (!file.isDir() || !worldDir.exists("level.dat")) {
|
||||
return QString();
|
||||
}
|
||||
return worldDir.absoluteFilePath("level.dat");
|
||||
}
|
||||
|
||||
QByteArray getLevelDatDataFromFS(const QFileInfo &file)
|
||||
QByteArray getLevelDatDataFromFS(const QFileInfo& file)
|
||||
{
|
||||
auto fullFilePath = getLevelDatFromFS(file);
|
||||
if(fullFilePath.isNull())
|
||||
{
|
||||
if (fullFilePath.isNull()) {
|
||||
return QByteArray();
|
||||
}
|
||||
QFile f(fullFilePath);
|
||||
if(!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QByteArray();
|
||||
}
|
||||
return f.readAll();
|
||||
}
|
||||
|
||||
bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data)
|
||||
bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
|
||||
{
|
||||
auto fullFilePath = getLevelDatFromFS(file);
|
||||
if(fullFilePath.isNull())
|
||||
{
|
||||
auto fullFilePath = getLevelDatFromFS(file);
|
||||
if (fullFilePath.isNull()) {
|
||||
return false;
|
||||
}
|
||||
QSaveFile f(fullFilePath);
|
||||
if(!f.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
QByteArray compressed;
|
||||
if(!GZip::zip(data, compressed))
|
||||
{
|
||||
if (!GZip::zip(data, compressed)) {
|
||||
return false;
|
||||
}
|
||||
if(f.write(compressed) != compressed.size())
|
||||
{
|
||||
if (f.write(compressed) != compressed.size()) {
|
||||
f.cancelWriting();
|
||||
return false;
|
||||
}
|
||||
return f.commit();
|
||||
}
|
||||
|
||||
int64_t calculateWorldSize(const QFileInfo &file)
|
||||
int64_t calculateWorldSize(const QFileInfo& file)
|
||||
{
|
||||
if (file.isFile() && file.suffix() == "zip")
|
||||
{
|
||||
if (file.isFile() && file.suffix() == "zip") {
|
||||
return file.size();
|
||||
}
|
||||
else if(file.isDir())
|
||||
{
|
||||
} else if (file.isDir()) {
|
||||
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
|
||||
int64_t total = 0;
|
||||
while (it.hasNext())
|
||||
{
|
||||
while (it.hasNext()) {
|
||||
total += it.fileInfo().size();
|
||||
it.next();
|
||||
}
|
||||
@ -231,25 +214,22 @@ int64_t calculateWorldSize(const QFileInfo &file)
|
||||
return -1;
|
||||
}
|
||||
|
||||
World::World(const QFileInfo &file)
|
||||
World::World(const QFileInfo& file)
|
||||
{
|
||||
repath(file);
|
||||
}
|
||||
|
||||
void World::repath(const QFileInfo &file)
|
||||
void World::repath(const QFileInfo& file)
|
||||
{
|
||||
m_containerFile = file;
|
||||
m_folderName = file.fileName();
|
||||
m_size = calculateWorldSize(file);
|
||||
if(file.isFile() && file.suffix() == "zip")
|
||||
{
|
||||
if (file.isFile() && file.suffix() == "zip") {
|
||||
m_iconFile = QString();
|
||||
readFromZip(file);
|
||||
}
|
||||
else if(file.isDir())
|
||||
{
|
||||
} else if (file.isDir()) {
|
||||
QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png");
|
||||
if(assumedIconPath.exists()) {
|
||||
if (assumedIconPath.exists()) {
|
||||
m_iconFile = assumedIconPath.absoluteFilePath();
|
||||
}
|
||||
readFromFS(file);
|
||||
@ -258,21 +238,20 @@ void World::repath(const QFileInfo &file)
|
||||
|
||||
bool World::resetIcon()
|
||||
{
|
||||
if(m_iconFile.isNull()) {
|
||||
if (m_iconFile.isNull()) {
|
||||
return false;
|
||||
}
|
||||
if(QFile(m_iconFile).remove()) {
|
||||
if (QFile(m_iconFile).remove()) {
|
||||
m_iconFile = QString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void World::readFromFS(const QFileInfo &file)
|
||||
void World::readFromFS(const QFileInfo& file)
|
||||
{
|
||||
auto bytes = getLevelDatDataFromFS(file);
|
||||
if(bytes.isEmpty())
|
||||
{
|
||||
if (bytes.isEmpty()) {
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
@ -280,104 +259,88 @@ void World::readFromFS(const QFileInfo &file)
|
||||
levelDatTime = file.lastModified();
|
||||
}
|
||||
|
||||
void World::readFromZip(const QFileInfo &file)
|
||||
void World::readFromZip(const QFileInfo& file)
|
||||
{
|
||||
QuaZip zip(file.absoluteFilePath());
|
||||
is_valid = zip.open(QuaZip::mdUnzip);
|
||||
if (!is_valid)
|
||||
{
|
||||
if (!is_valid) {
|
||||
return;
|
||||
}
|
||||
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
|
||||
is_valid = !location.isEmpty();
|
||||
if (!is_valid)
|
||||
{
|
||||
if (!is_valid) {
|
||||
return;
|
||||
}
|
||||
m_containerOffsetPath = location;
|
||||
QuaZipFile zippedFile(&zip);
|
||||
// read the install profile
|
||||
is_valid = zip.setCurrentFile(location + "level.dat");
|
||||
if (!is_valid)
|
||||
{
|
||||
if (!is_valid) {
|
||||
return;
|
||||
}
|
||||
is_valid = zippedFile.open(QIODevice::ReadOnly);
|
||||
QuaZipFileInfo64 levelDatInfo;
|
||||
zippedFile.getFileInfo(&levelDatInfo);
|
||||
auto modTime = levelDatInfo.getNTFSmTime();
|
||||
if(!modTime.isValid())
|
||||
{
|
||||
if (!modTime.isValid()) {
|
||||
modTime = levelDatInfo.dateTime;
|
||||
}
|
||||
levelDatTime = modTime;
|
||||
if (!is_valid)
|
||||
{
|
||||
if (!is_valid) {
|
||||
return;
|
||||
}
|
||||
loadFromLevelDat(zippedFile.readAll());
|
||||
zippedFile.close();
|
||||
}
|
||||
|
||||
bool World::install(const QString &to, const QString &name)
|
||||
bool World::install(const QString& to, const QString& name)
|
||||
{
|
||||
auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to));
|
||||
if(!FS::ensureFolderPathExists(finalPath))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(finalPath)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = false;
|
||||
if(m_containerFile.isFile())
|
||||
{
|
||||
if (m_containerFile.isFile()) {
|
||||
QuaZip zip(m_containerFile.absoluteFilePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
return false;
|
||||
}
|
||||
ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath);
|
||||
}
|
||||
else if(m_containerFile.isDir())
|
||||
{
|
||||
} else if (m_containerFile.isDir()) {
|
||||
QString from = m_containerFile.filePath();
|
||||
ok = FS::copy(from, finalPath)();
|
||||
}
|
||||
|
||||
if(ok && !name.isEmpty() && m_actualName != name)
|
||||
{
|
||||
if (ok && !name.isEmpty() && m_actualName != name) {
|
||||
QFileInfo finalPathInfo(finalPath);
|
||||
World newWorld(finalPathInfo);
|
||||
if(newWorld.isValid())
|
||||
{
|
||||
if (newWorld.isValid()) {
|
||||
newWorld.rename(name);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool World::rename(const QString &newName)
|
||||
bool World::rename(const QString& newName)
|
||||
{
|
||||
if(m_containerFile.isFile())
|
||||
{
|
||||
if (m_containerFile.isFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto data = getLevelDatDataFromFS(m_containerFile);
|
||||
if(data.isEmpty())
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto worldData = parseLevelDat(data);
|
||||
if(!worldData)
|
||||
{
|
||||
if (!worldData) {
|
||||
return false;
|
||||
}
|
||||
auto &val = worldData->at("Data");
|
||||
if(val.get_type() != nbt::tag_type::Compound)
|
||||
{
|
||||
auto& val = worldData->at("Data");
|
||||
if (val.get_type() != nbt::tag_type::Compound) {
|
||||
return false;
|
||||
}
|
||||
auto &dataCompound = val.as<nbt::tag_compound>();
|
||||
auto& dataCompound = val.as<nbt::tag_compound>();
|
||||
dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data()));
|
||||
data = serializeLevelDat(worldData.get());
|
||||
|
||||
@ -396,112 +359,93 @@ bool World::rename(const QString &newName)
|
||||
|
||||
namespace {
|
||||
|
||||
optional<QString> read_string (nbt::value& parent, const char * name)
|
||||
optional<QString> read_string(nbt::value& parent, const char* name)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto &namedValue = parent.at(name);
|
||||
if(namedValue.get_type() != nbt::tag_type::String)
|
||||
{
|
||||
try {
|
||||
auto& namedValue = parent.at(name);
|
||||
if (namedValue.get_type() != nbt::tag_type::String) {
|
||||
return nullopt;
|
||||
}
|
||||
auto & tag_str = namedValue.as<nbt::tag_string>();
|
||||
auto& tag_str = namedValue.as<nbt::tag_string>();
|
||||
return QString::fromStdString(tag_str.get());
|
||||
}
|
||||
catch ([[maybe_unused]] const std::out_of_range &e)
|
||||
{
|
||||
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||
// fallback for old world formats
|
||||
qWarning() << "String NBT tag" << name << "could not be found.";
|
||||
return nullopt;
|
||||
}
|
||||
catch ([[maybe_unused]] const std::bad_cast &e)
|
||||
{
|
||||
} catch ([[maybe_unused]] const std::bad_cast& e) {
|
||||
// type mismatch
|
||||
qWarning() << "NBT tag" << name << "could not be converted to string.";
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
optional<int64_t> read_long (nbt::value& parent, const char * name)
|
||||
optional<int64_t> read_long(nbt::value& parent, const char* name)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto &namedValue = parent.at(name);
|
||||
if(namedValue.get_type() != nbt::tag_type::Long)
|
||||
{
|
||||
try {
|
||||
auto& namedValue = parent.at(name);
|
||||
if (namedValue.get_type() != nbt::tag_type::Long) {
|
||||
return nullopt;
|
||||
}
|
||||
auto & tag_str = namedValue.as<nbt::tag_long>();
|
||||
auto& tag_str = namedValue.as<nbt::tag_long>();
|
||||
return tag_str.get();
|
||||
}
|
||||
catch ([[maybe_unused]] const std::out_of_range &e)
|
||||
{
|
||||
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||
// fallback for old world formats
|
||||
qWarning() << "Long NBT tag" << name << "could not be found.";
|
||||
return nullopt;
|
||||
}
|
||||
catch ([[maybe_unused]] const std::bad_cast &e)
|
||||
{
|
||||
} catch ([[maybe_unused]] const std::bad_cast& e) {
|
||||
// type mismatch
|
||||
qWarning() << "NBT tag" << name << "could not be converted to long.";
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
optional<int> read_int (nbt::value& parent, const char * name)
|
||||
optional<int> read_int(nbt::value& parent, const char* name)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto &namedValue = parent.at(name);
|
||||
if(namedValue.get_type() != nbt::tag_type::Int)
|
||||
{
|
||||
try {
|
||||
auto& namedValue = parent.at(name);
|
||||
if (namedValue.get_type() != nbt::tag_type::Int) {
|
||||
return nullopt;
|
||||
}
|
||||
auto & tag_str = namedValue.as<nbt::tag_int>();
|
||||
auto& tag_str = namedValue.as<nbt::tag_int>();
|
||||
return tag_str.get();
|
||||
}
|
||||
catch ([[maybe_unused]] const std::out_of_range &e)
|
||||
{
|
||||
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||
// fallback for old world formats
|
||||
qWarning() << "Int NBT tag" << name << "could not be found.";
|
||||
return nullopt;
|
||||
}
|
||||
catch ([[maybe_unused]] const std::bad_cast &e)
|
||||
{
|
||||
} catch ([[maybe_unused]] const std::bad_cast& e) {
|
||||
// type mismatch
|
||||
qWarning() << "NBT tag" << name << "could not be converted to int.";
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
GameType read_gametype(nbt::value& parent, const char * name) {
|
||||
GameType read_gametype(nbt::value& parent, const char* name)
|
||||
{
|
||||
return GameType(read_int(parent, name));
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void World::loadFromLevelDat(QByteArray data)
|
||||
{
|
||||
auto levelData = parseLevelDat(data);
|
||||
if(!levelData)
|
||||
{
|
||||
if (!levelData) {
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
nbt::value * valPtr = nullptr;
|
||||
nbt::value* valPtr = nullptr;
|
||||
try {
|
||||
valPtr = &levelData->at("Data");
|
||||
}
|
||||
catch (const std::out_of_range &e) {
|
||||
} catch (const std::out_of_range& e) {
|
||||
qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what();
|
||||
is_valid = false;
|
||||
return;
|
||||
}
|
||||
nbt::value &val = *valPtr;
|
||||
nbt::value& val = *valPtr;
|
||||
|
||||
is_valid = val.get_type() == nbt::tag_type::Compound;
|
||||
if(!is_valid)
|
||||
if (!is_valid)
|
||||
return;
|
||||
|
||||
auto name = read_string(val, "LevelName");
|
||||
@ -514,31 +458,30 @@ void World::loadFromLevelDat(QByteArray data)
|
||||
|
||||
optional<int64_t> randomSeed;
|
||||
try {
|
||||
auto &WorldGen_val = val.at("WorldGenSettings");
|
||||
auto& WorldGen_val = val.at("WorldGenSettings");
|
||||
randomSeed = read_long(WorldGen_val, "seed");
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
catch (const std::out_of_range &) {}
|
||||
if(!randomSeed) {
|
||||
if (!randomSeed) {
|
||||
randomSeed = read_long(val, "RandomSeed");
|
||||
}
|
||||
m_randomSeed = randomSeed ? *randomSeed : 0;
|
||||
|
||||
qDebug() << "World Name:" << m_actualName;
|
||||
qDebug() << "Last Played:" << m_lastPlayed.toString();
|
||||
if(randomSeed) {
|
||||
if (randomSeed) {
|
||||
qDebug() << "Seed:" << *randomSeed;
|
||||
}
|
||||
qDebug() << "Size:" << m_size;
|
||||
qDebug() << "GameType:" << m_gameType.toLogString();
|
||||
}
|
||||
|
||||
bool World::replace(World &with)
|
||||
bool World::replace(World& with)
|
||||
{
|
||||
if (!destroy())
|
||||
return false;
|
||||
bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())();
|
||||
if (success)
|
||||
{
|
||||
if (success) {
|
||||
m_folderName = with.m_folderName;
|
||||
m_containerFile.refresh();
|
||||
}
|
||||
@ -547,25 +490,23 @@ bool World::replace(World &with)
|
||||
|
||||
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());
|
||||
return d.removeRecursively();
|
||||
}
|
||||
else if(m_containerFile.isFile())
|
||||
{
|
||||
} else if (m_containerFile.isFile()) {
|
||||
QFile file(m_containerFile.absoluteFilePath());
|
||||
return file.remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool World::operator==(const World &other) const
|
||||
bool World::operator==(const World& other) const
|
||||
{
|
||||
return is_valid == other.is_valid && folderName() == other.folderName();
|
||||
}
|
||||
@ -585,8 +526,7 @@ bool World::isSymLinkUnder(const QString& instPath) const
|
||||
|
||||
bool World::isMoreThanOneHardLink() const
|
||||
{
|
||||
if (m_containerFile.isDir())
|
||||
{
|
||||
if (m_containerFile.isDir()) {
|
||||
return FS::hardLinkCount(QDir(m_containerFile.absoluteFilePath()).filePath("level.dat")) > 1;
|
||||
}
|
||||
return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1;
|
||||
|
@ -14,95 +14,57 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <optional>
|
||||
|
||||
struct GameType {
|
||||
GameType() = default;
|
||||
GameType (std::optional<int> original);
|
||||
GameType(std::optional<int> original);
|
||||
|
||||
QString toTranslatedString() const;
|
||||
QString toLogString() const;
|
||||
|
||||
enum
|
||||
{
|
||||
Unknown = -1,
|
||||
Survival,
|
||||
Creative,
|
||||
Adventure,
|
||||
Spectator
|
||||
} type = Unknown;
|
||||
enum { Unknown = -1, Survival, Creative, Adventure, Spectator } type = Unknown;
|
||||
std::optional<int> original;
|
||||
};
|
||||
|
||||
class World
|
||||
{
|
||||
public:
|
||||
World(const QFileInfo &file);
|
||||
QString folderName() const
|
||||
{
|
||||
return m_folderName;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
return m_actualName;
|
||||
}
|
||||
QString iconFile() const
|
||||
{
|
||||
return m_iconFile;
|
||||
}
|
||||
int64_t bytes() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
QDateTime lastPlayed() const
|
||||
{
|
||||
return m_lastPlayed;
|
||||
}
|
||||
GameType gameType() const
|
||||
{
|
||||
return m_gameType;
|
||||
}
|
||||
int64_t seed() const
|
||||
{
|
||||
return m_randomSeed;
|
||||
}
|
||||
bool isValid() const
|
||||
{
|
||||
return is_valid;
|
||||
}
|
||||
bool isOnFS() const
|
||||
{
|
||||
return m_containerFile.isDir();
|
||||
}
|
||||
QFileInfo container() const
|
||||
{
|
||||
return m_containerFile;
|
||||
}
|
||||
class World {
|
||||
public:
|
||||
World(const QFileInfo& file);
|
||||
QString folderName() const { return m_folderName; }
|
||||
QString name() const { return m_actualName; }
|
||||
QString iconFile() const { return m_iconFile; }
|
||||
int64_t bytes() const { return m_size; }
|
||||
QDateTime lastPlayed() const { return m_lastPlayed; }
|
||||
GameType gameType() const { return m_gameType; }
|
||||
int64_t seed() const { return m_randomSeed; }
|
||||
bool isValid() const { return is_valid; }
|
||||
bool isOnFS() const { return m_containerFile.isDir(); }
|
||||
QFileInfo container() const { return m_containerFile; }
|
||||
// delete all the files of this world
|
||||
bool destroy();
|
||||
// replace this world with a copy of the other
|
||||
bool replace(World &with);
|
||||
bool replace(World& with);
|
||||
// change the world's filesystem path (used by world lists for *MAGIC* purposes)
|
||||
void repath(const QFileInfo &file);
|
||||
void repath(const QFileInfo& file);
|
||||
// remove the icon file, if any
|
||||
bool resetIcon();
|
||||
|
||||
bool rename(const QString &to);
|
||||
bool install(const QString &to, const QString &name= QString());
|
||||
bool rename(const QString& to);
|
||||
bool install(const QString& to, const QString& name = QString());
|
||||
|
||||
// WEAK compare operator - used for replacing worlds
|
||||
bool operator==(const World &other) const;
|
||||
bool operator==(const World& other) const;
|
||||
|
||||
[[nodiscard]] auto isSymLink() const -> bool{ return m_containerFile.isSymLink(); }
|
||||
[[nodiscard]] auto isSymLink() const -> bool { return m_containerFile.isSymLink(); }
|
||||
|
||||
/**
|
||||
* @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance
|
||||
*
|
||||
*
|
||||
* @param instPath path to an instance directory
|
||||
* @return true
|
||||
* @return false
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
[[nodiscard]] bool isSymLinkUnder(const QString& instPath) const;
|
||||
|
||||
@ -110,13 +72,12 @@ public:
|
||||
|
||||
QString canonicalFilePath() const { return m_containerFile.canonicalFilePath(); }
|
||||
|
||||
private:
|
||||
void readFromZip(const QFileInfo &file);
|
||||
void readFromFS(const QFileInfo &file);
|
||||
private:
|
||||
void readFromZip(const QFileInfo& file);
|
||||
void readFromFS(const QFileInfo& file);
|
||||
void loadFromLevelDat(QByteArray data);
|
||||
|
||||
protected:
|
||||
|
||||
protected:
|
||||
QFileInfo m_containerFile;
|
||||
QString m_containerOffsetPath;
|
||||
QString m_folderName;
|
||||
|
@ -35,18 +35,17 @@
|
||||
|
||||
#include "WorldList.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include <FileSystem.h>
|
||||
#include <Qt>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMimeData>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDebug>
|
||||
#include <Qt>
|
||||
#include "Application.h"
|
||||
|
||||
WorldList::WorldList(const QString &dir, BaseInstance* instance)
|
||||
: QAbstractListModel(), m_instance(instance), m_dir(dir)
|
||||
WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir)
|
||||
{
|
||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
|
||||
@ -58,35 +57,27 @@ WorldList::WorldList(const QString &dir, BaseInstance* instance)
|
||||
|
||||
void WorldList::startWatching()
|
||||
{
|
||||
if(is_watching)
|
||||
{
|
||||
if (is_watching) {
|
||||
return;
|
||||
}
|
||||
update();
|
||||
is_watching = m_watcher->addPath(m_dir.absolutePath());
|
||||
if (is_watching)
|
||||
{
|
||||
if (is_watching) {
|
||||
qDebug() << "Started watching " << m_dir.absolutePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "Failed to start watching " << m_dir.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldList::stopWatching()
|
||||
{
|
||||
if(!is_watching)
|
||||
{
|
||||
if (!is_watching) {
|
||||
return;
|
||||
}
|
||||
is_watching = !m_watcher->removePath(m_dir.absolutePath());
|
||||
if (!is_watching)
|
||||
{
|
||||
if (!is_watching) {
|
||||
qDebug() << "Stopped watching " << m_dir.absolutePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
|
||||
}
|
||||
}
|
||||
@ -100,14 +91,12 @@ bool WorldList::update()
|
||||
m_dir.refresh();
|
||||
auto folderContents = m_dir.entryInfoList();
|
||||
// if there are any untracked files...
|
||||
for (QFileInfo entry : folderContents)
|
||||
{
|
||||
if(!entry.isDir())
|
||||
for (QFileInfo entry : folderContents) {
|
||||
if (!entry.isDir())
|
||||
continue;
|
||||
|
||||
World w(entry);
|
||||
if(w.isValid())
|
||||
{
|
||||
if (w.isValid()) {
|
||||
newWorlds.append(w);
|
||||
}
|
||||
}
|
||||
@ -127,7 +116,8 @@ bool WorldList::isValid()
|
||||
return m_dir.exists() && m_dir.isReadable();
|
||||
}
|
||||
|
||||
QString WorldList::instDirPath() const {
|
||||
QString WorldList::instDirPath() const
|
||||
{
|
||||
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
|
||||
}
|
||||
|
||||
@ -135,9 +125,8 @@ bool WorldList::deleteWorld(int index)
|
||||
{
|
||||
if (index >= worlds.size() || index < 0)
|
||||
return false;
|
||||
World &m = worlds[index];
|
||||
if (m.destroy())
|
||||
{
|
||||
World& m = worlds[index];
|
||||
if (m.destroy()) {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
worlds.removeAt(index);
|
||||
endRemoveRows();
|
||||
@ -149,9 +138,8 @@ bool WorldList::deleteWorld(int index)
|
||||
|
||||
bool WorldList::deleteWorlds(int first, int last)
|
||||
{
|
||||
for (int i = first; i <= last; i++)
|
||||
{
|
||||
World &m = worlds[i];
|
||||
for (int i = first; i <= last; i++) {
|
||||
World& m = worlds[i];
|
||||
m.destroy();
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), first, last);
|
||||
@ -165,21 +153,20 @@ bool WorldList::resetIcon(int row)
|
||||
{
|
||||
if (row >= worlds.size() || row < 0)
|
||||
return false;
|
||||
World &m = worlds[row];
|
||||
if(m.resetIcon()) {
|
||||
emit dataChanged(index(row), index(row), {WorldList::IconFileRole});
|
||||
World& m = worlds[row];
|
||||
if (m.resetIcon()) {
|
||||
emit dataChanged(index(row), index(row), { WorldList::IconFileRole });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int WorldList::columnCount(const QModelIndex &parent) const
|
||||
int WorldList::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid()? 0 : 5;
|
||||
return parent.isValid() ? 0 : 5;
|
||||
}
|
||||
|
||||
QVariant WorldList::data(const QModelIndex &index, int role) const
|
||||
QVariant WorldList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@ -192,133 +179,120 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
|
||||
|
||||
QLocale locale;
|
||||
|
||||
auto & world = worlds[row];
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (column)
|
||||
{
|
||||
case NameColumn:
|
||||
auto& world = worlds[row];
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (column) {
|
||||
case NameColumn:
|
||||
return world.name();
|
||||
|
||||
case GameModeColumn:
|
||||
return world.gameType().toTranslatedString();
|
||||
|
||||
case LastPlayedColumn:
|
||||
return world.lastPlayed();
|
||||
|
||||
case SizeColumn:
|
||||
return locale.formattedDataSize(world.bytes());
|
||||
|
||||
case InfoColumn:
|
||||
if (world.isSymLinkUnder(instDirPath())) {
|
||||
return tr("This world is symbolically linked from elsewhere.");
|
||||
}
|
||||
if (world.isMoreThanOneHardLink()) {
|
||||
return tr("\nThis world is hard linked elsewhere.");
|
||||
}
|
||||
return "";
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::UserRole:
|
||||
switch (column) {
|
||||
case SizeColumn:
|
||||
return QVariant::fromValue<qlonglong>(world.bytes());
|
||||
|
||||
default:
|
||||
return data(index, Qt::DisplayRole);
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole: {
|
||||
if (column == InfoColumn) {
|
||||
if (world.isSymLinkUnder(instDirPath())) {
|
||||
return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(world.canonicalFilePath());
|
||||
}
|
||||
if (world.isMoreThanOneHardLink()) {
|
||||
return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
}
|
||||
return world.folderName();
|
||||
}
|
||||
case ObjectRole: {
|
||||
return QVariant::fromValue<void*>((void*)&world);
|
||||
}
|
||||
case FolderRole: {
|
||||
return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName()));
|
||||
}
|
||||
case SeedRole: {
|
||||
return QVariant::fromValue<qlonglong>(world.seed());
|
||||
}
|
||||
case NameRole: {
|
||||
return world.name();
|
||||
|
||||
case GameModeColumn:
|
||||
return world.gameType().toTranslatedString();
|
||||
|
||||
case LastPlayedColumn:
|
||||
}
|
||||
case LastPlayedRole: {
|
||||
return world.lastPlayed();
|
||||
|
||||
case SizeColumn:
|
||||
return locale.formattedDataSize(world.bytes());
|
||||
|
||||
case InfoColumn:
|
||||
if (world.isSymLinkUnder(instDirPath())) {
|
||||
return tr("This world is symbolically linked from elsewhere.");
|
||||
}
|
||||
if (world.isMoreThanOneHardLink()) {
|
||||
return tr("\nThis world is hard linked elsewhere.");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
case SizeRole: {
|
||||
return QVariant::fromValue<qlonglong>(world.bytes());
|
||||
}
|
||||
case IconFileRole: {
|
||||
return world.iconFile();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::UserRole:
|
||||
switch (column)
|
||||
{
|
||||
case SizeColumn:
|
||||
return QVariant::fromValue<qlonglong>(world.bytes());
|
||||
|
||||
default:
|
||||
return data(index, Qt::DisplayRole);
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
if (column == InfoColumn) {
|
||||
if (world.isSymLinkUnder(instDirPath())) {
|
||||
return tr("Warning: This world is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1").arg(world.canonicalFilePath());
|
||||
}
|
||||
if (world.isMoreThanOneHardLink()) {
|
||||
return tr("Warning: This world is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
}
|
||||
return world.folderName();
|
||||
}
|
||||
case ObjectRole:
|
||||
{
|
||||
return QVariant::fromValue<void *>((void *)&world);
|
||||
}
|
||||
case FolderRole:
|
||||
{
|
||||
return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName()));
|
||||
}
|
||||
case SeedRole:
|
||||
{
|
||||
return QVariant::fromValue<qlonglong>(world.seed());
|
||||
}
|
||||
case NameRole:
|
||||
{
|
||||
return world.name();
|
||||
}
|
||||
case LastPlayedRole:
|
||||
{
|
||||
return world.lastPlayed();
|
||||
}
|
||||
case SizeRole:
|
||||
{
|
||||
return QVariant::fromValue<qlonglong>(world.bytes());
|
||||
}
|
||||
case IconFileRole:
|
||||
{
|
||||
return world.iconFile();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant WorldList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case GameModeColumn:
|
||||
return tr("Game Mode");
|
||||
case LastPlayedColumn:
|
||||
return tr("Last Played");
|
||||
case SizeColumn:
|
||||
//: World size on disk
|
||||
return tr("Size");
|
||||
case InfoColumn:
|
||||
//: special warnings?
|
||||
return tr("Info");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case GameModeColumn:
|
||||
return tr("Game Mode");
|
||||
case LastPlayedColumn:
|
||||
return tr("Last Played");
|
||||
case SizeColumn:
|
||||
//: World size on disk
|
||||
return tr("Size");
|
||||
case InfoColumn:
|
||||
//: special warnings?
|
||||
return tr("Info");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return tr("The name of the world.");
|
||||
case GameModeColumn:
|
||||
return tr("Game mode of the world.");
|
||||
case LastPlayedColumn:
|
||||
return tr("Date and time the world was last played.");
|
||||
case SizeColumn:
|
||||
return tr("Size of the world on disk.");
|
||||
case InfoColumn:
|
||||
return tr("Information and warnings about the world.");
|
||||
case Qt::ToolTipRole:
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return tr("The name of the world.");
|
||||
case GameModeColumn:
|
||||
return tr("Game mode of the world.");
|
||||
case LastPlayedColumn:
|
||||
return tr("Date and time the world was last played.");
|
||||
case SizeColumn:
|
||||
return tr("Size of the world on disk.");
|
||||
case InfoColumn:
|
||||
return tr("Information and warnings about the world.");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,32 +303,23 @@ QStringList WorldList::mimeTypes() const
|
||||
return types;
|
||||
}
|
||||
|
||||
class WorldMimeData : public QMimeData
|
||||
{
|
||||
Q_OBJECT
|
||||
class WorldMimeData : public QMimeData {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WorldMimeData(QList<World> worlds)
|
||||
{
|
||||
m_worlds = worlds;
|
||||
public:
|
||||
WorldMimeData(QList<World> worlds) { m_worlds = worlds; }
|
||||
QStringList formats() const { return QMimeData::formats() << "text/uri-list"; }
|
||||
|
||||
}
|
||||
QStringList formats() const
|
||||
{
|
||||
return QMimeData::formats() << "text/uri-list";
|
||||
}
|
||||
|
||||
protected:
|
||||
protected:
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QVariant retrieveData(const QString &mimetype, QMetaType type) const
|
||||
QVariant retrieveData(const QString& mimetype, QMetaType type) const
|
||||
#else
|
||||
QVariant retrieveData(const QString &mimetype, QVariant::Type type) const
|
||||
QVariant retrieveData(const QString& mimetype, QVariant::Type type) const
|
||||
#endif
|
||||
{
|
||||
QList<QUrl> urls;
|
||||
for(auto &world: m_worlds)
|
||||
{
|
||||
if(!world.isValid() || !world.isOnFS())
|
||||
for (auto& world : m_worlds) {
|
||||
if (!world.isValid() || !world.isOnFS())
|
||||
continue;
|
||||
QString worldPath = world.container().absoluteFilePath();
|
||||
qDebug() << worldPath;
|
||||
@ -363,38 +328,36 @@ protected:
|
||||
const_cast<WorldMimeData*>(this)->setUrls(urls);
|
||||
return QMimeData::retrieveData(mimetype, type);
|
||||
}
|
||||
private:
|
||||
|
||||
private:
|
||||
QList<World> m_worlds;
|
||||
};
|
||||
|
||||
QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const
|
||||
QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const
|
||||
{
|
||||
if (indexes.size() == 0)
|
||||
return new QMimeData();
|
||||
|
||||
QList<World> worlds_;
|
||||
for(auto idx : indexes)
|
||||
{
|
||||
if(idx.column() != 0)
|
||||
for (auto idx : indexes) {
|
||||
if (idx.column() != 0)
|
||||
continue;
|
||||
int row = idx.row();
|
||||
if (row < 0 || row >= this->worlds.size())
|
||||
continue;
|
||||
worlds_.append(this->worlds[row]);
|
||||
}
|
||||
if(!worlds_.size())
|
||||
{
|
||||
if (!worlds_.size()) {
|
||||
return new QMimeData();
|
||||
}
|
||||
return new WorldMimeData(worlds_);
|
||||
}
|
||||
|
||||
Qt::ItemFlags WorldList::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags WorldList::flags(const QModelIndex& index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
|
||||
defaultFlags;
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
@ -415,15 +378,17 @@ void WorldList::installWorld(QFileInfo filename)
|
||||
{
|
||||
qDebug() << "installing: " << filename.absoluteFilePath();
|
||||
World w(filename);
|
||||
if(!w.isValid())
|
||||
{
|
||||
if (!w.isValid()) {
|
||||
return;
|
||||
}
|
||||
w.install(m_dir.absolutePath());
|
||||
}
|
||||
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex &parent)
|
||||
bool WorldList::dropMimeData(const QMimeData* data,
|
||||
Qt::DropAction action,
|
||||
[[maybe_unused]] int row,
|
||||
[[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex& parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
@ -431,14 +396,12 @@ bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[may
|
||||
if (!data || !(action & supportedDropActions()))
|
||||
return false;
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls())
|
||||
{
|
||||
if (data->hasUrls()) {
|
||||
bool was_watching = is_watching;
|
||||
if (was_watching)
|
||||
stopWatching();
|
||||
auto urls = data->urls();
|
||||
for (auto url : urls)
|
||||
{
|
||||
for (auto url : urls) {
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
@ -446,8 +409,7 @@ bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[may
|
||||
|
||||
QFileInfo worldInfo(filename);
|
||||
|
||||
if(!m_dir.entryInfoList().contains(worldInfo))
|
||||
{
|
||||
if (!m_dir.entryInfoList().contains(worldInfo)) {
|
||||
installWorld(worldInfo);
|
||||
}
|
||||
}
|
||||
|
@ -15,65 +15,34 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QAbstractListModel>
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QMimeData>
|
||||
#include "minecraft/World.h"
|
||||
#include <QString>
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/World.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class WorldList : public QAbstractListModel
|
||||
{
|
||||
class WorldList : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn,
|
||||
GameModeColumn,
|
||||
LastPlayedColumn,
|
||||
SizeColumn,
|
||||
InfoColumn
|
||||
};
|
||||
public:
|
||||
enum Columns { NameColumn, GameModeColumn, LastPlayedColumn, SizeColumn, InfoColumn };
|
||||
|
||||
enum Roles
|
||||
{
|
||||
ObjectRole = Qt::UserRole + 1,
|
||||
FolderRole,
|
||||
SeedRole,
|
||||
NameRole,
|
||||
GameModeRole,
|
||||
LastPlayedRole,
|
||||
SizeRole,
|
||||
IconFileRole
|
||||
};
|
||||
enum Roles { ObjectRole = Qt::UserRole + 1, FolderRole, SeedRole, NameRole, GameModeRole, LastPlayedRole, SizeRole, IconFileRole };
|
||||
|
||||
WorldList(const QString &dir, BaseInstance* instance);
|
||||
WorldList(const QString& dir, BaseInstance* instance);
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
|
||||
{
|
||||
return parent.isValid() ? 0 : static_cast<int>(size());
|
||||
};
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const { return parent.isValid() ? 0 : static_cast<int>(size()); };
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
virtual int columnCount(const QModelIndex& parent) const;
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return worlds.size();
|
||||
};
|
||||
bool empty() const
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
World &operator[](size_t index)
|
||||
{
|
||||
return worlds[index];
|
||||
}
|
||||
size_t size() const { return worlds.size(); };
|
||||
bool empty() const { return size() == 0; }
|
||||
World& operator[](size_t index) { return worlds[index]; }
|
||||
|
||||
/// Reloads the mod list and returns true if the list changed.
|
||||
virtual bool update();
|
||||
@ -91,13 +60,13 @@ public:
|
||||
virtual bool deleteWorlds(int first, int last);
|
||||
|
||||
/// flags, mostly to support drag&drop
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||
/// get data for drag action
|
||||
virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
virtual QMimeData* mimeData(const QModelIndexList& indexes) const;
|
||||
/// get the supported mime types
|
||||
virtual QStringList mimeTypes() const;
|
||||
/// process data from drop action
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
|
||||
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
|
||||
/// what drag actions do we support?
|
||||
virtual Qt::DropActions supportedDragActions() const;
|
||||
|
||||
@ -109,27 +78,21 @@ public:
|
||||
|
||||
virtual bool isValid();
|
||||
|
||||
QDir dir() const
|
||||
{
|
||||
return m_dir;
|
||||
}
|
||||
QDir dir() const { return m_dir; }
|
||||
|
||||
QString instDirPath() const;
|
||||
|
||||
const QList<World> &allWorlds() const
|
||||
{
|
||||
return worlds;
|
||||
}
|
||||
const QList<World>& allWorlds() const { return worlds; }
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void directoryChanged(QString path);
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
BaseInstance* m_instance;
|
||||
QFileSystemWatcher *m_watcher;
|
||||
QFileSystemWatcher* m_watcher;
|
||||
bool is_watching;
|
||||
QDir m_dir;
|
||||
QList<World> worlds;
|
||||
|
@ -34,87 +34,90 @@
|
||||
*/
|
||||
|
||||
#include "AccountData.h"
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
|
||||
namespace {
|
||||
void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
|
||||
if(!t.persistent) {
|
||||
void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName)
|
||||
{
|
||||
if (!t.persistent) {
|
||||
return;
|
||||
}
|
||||
QJsonObject out;
|
||||
if(t.issueInstant.isValid()) {
|
||||
if (t.issueInstant.isValid()) {
|
||||
out["iat"] = QJsonValue(t.issueInstant.toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
if(t.notAfter.isValid()) {
|
||||
if (t.notAfter.isValid()) {
|
||||
out["exp"] = QJsonValue(t.notAfter.toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
bool save = false;
|
||||
if(!t.token.isEmpty()) {
|
||||
if (!t.token.isEmpty()) {
|
||||
out["token"] = QJsonValue(t.token);
|
||||
save = true;
|
||||
}
|
||||
if(!t.refresh_token.isEmpty()) {
|
||||
if (!t.refresh_token.isEmpty()) {
|
||||
out["refresh_token"] = QJsonValue(t.refresh_token);
|
||||
save = true;
|
||||
}
|
||||
if(t.extra.size()) {
|
||||
if (t.extra.size()) {
|
||||
out["extra"] = QJsonObject::fromVariantMap(t.extra);
|
||||
save = true;
|
||||
}
|
||||
if(save) {
|
||||
if (save) {
|
||||
parent[tokenName] = out;
|
||||
}
|
||||
}
|
||||
|
||||
Katabasis::Token tokenFromJSONV3(const QJsonObject &parent, const char * tokenName) {
|
||||
Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
||||
{
|
||||
Katabasis::Token out;
|
||||
auto tokenObject = parent.value(tokenName).toObject();
|
||||
if(tokenObject.isEmpty()) {
|
||||
if (tokenObject.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
auto issueInstant = tokenObject.value("iat");
|
||||
if(issueInstant.isDouble()) {
|
||||
out.issueInstant = QDateTime::fromMSecsSinceEpoch(((int64_t) issueInstant.toDouble()) * 1000);
|
||||
if (issueInstant.isDouble()) {
|
||||
out.issueInstant = QDateTime::fromMSecsSinceEpoch(((int64_t)issueInstant.toDouble()) * 1000);
|
||||
}
|
||||
|
||||
auto notAfter = tokenObject.value("exp");
|
||||
if(notAfter.isDouble()) {
|
||||
out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t) notAfter.toDouble()) * 1000);
|
||||
if (notAfter.isDouble()) {
|
||||
out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t)notAfter.toDouble()) * 1000);
|
||||
}
|
||||
|
||||
auto token = tokenObject.value("token");
|
||||
if(token.isString()) {
|
||||
if (token.isString()) {
|
||||
out.token = token.toString();
|
||||
out.validity = Katabasis::Validity::Assumed;
|
||||
}
|
||||
|
||||
auto refresh_token = tokenObject.value("refresh_token");
|
||||
if(refresh_token.isString()) {
|
||||
if (refresh_token.isString()) {
|
||||
out.refresh_token = refresh_token.toString();
|
||||
}
|
||||
|
||||
auto extra = tokenObject.value("extra");
|
||||
if(extra.isObject()) {
|
||||
if (extra.isObject()) {
|
||||
out.extra = extra.toObject().toVariantMap();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * tokenName) {
|
||||
if(p.id.isEmpty()) {
|
||||
void profileToJSONV3(QJsonObject& parent, MinecraftProfile p, const char* tokenName)
|
||||
{
|
||||
if (p.id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QJsonObject out;
|
||||
out["id"] = QJsonValue(p.id);
|
||||
out["name"] = QJsonValue(p.name);
|
||||
if(!p.currentCape.isEmpty()) {
|
||||
if (!p.currentCape.isEmpty()) {
|
||||
out["cape"] = p.currentCape;
|
||||
}
|
||||
|
||||
@ -123,19 +126,19 @@ void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * token
|
||||
skinObj["id"] = p.skin.id;
|
||||
skinObj["url"] = p.skin.url;
|
||||
skinObj["variant"] = p.skin.variant;
|
||||
if(p.skin.data.size()) {
|
||||
if (p.skin.data.size()) {
|
||||
skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64());
|
||||
}
|
||||
out["skin"] = skinObj;
|
||||
}
|
||||
|
||||
QJsonArray capesArray;
|
||||
for(auto & cape: p.capes) {
|
||||
for (auto& cape : p.capes) {
|
||||
QJsonObject capeObj;
|
||||
capeObj["id"] = cape.id;
|
||||
capeObj["url"] = cape.url;
|
||||
capeObj["alias"] = cape.alias;
|
||||
if(cape.data.size()) {
|
||||
if (cape.data.size()) {
|
||||
capeObj["data"] = QString::fromLatin1(cape.data.toBase64());
|
||||
}
|
||||
capesArray.push_back(capeObj);
|
||||
@ -144,16 +147,17 @@ void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * token
|
||||
parent[tokenName] = out;
|
||||
}
|
||||
|
||||
MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * tokenName) {
|
||||
MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
||||
{
|
||||
MinecraftProfile out;
|
||||
auto tokenObject = parent.value(tokenName).toObject();
|
||||
if(tokenObject.isEmpty()) {
|
||||
if (tokenObject.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
{
|
||||
auto idV = tokenObject.value("id");
|
||||
auto nameV = tokenObject.value("name");
|
||||
if(!idV.isString() || !nameV.isString()) {
|
||||
if (!idV.isString() || !nameV.isString()) {
|
||||
qWarning() << "mandatory profile attributes are missing or of unexpected type";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -163,7 +167,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
{
|
||||
auto skinV = tokenObject.value("skin");
|
||||
if(!skinV.isObject()) {
|
||||
if (!skinV.isObject()) {
|
||||
qWarning() << "skin is missing";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -171,7 +175,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
auto idV = skinObj.value("id");
|
||||
auto urlV = skinObj.value("url");
|
||||
auto variantV = skinObj.value("variant");
|
||||
if(!idV.isString() || !urlV.isString() || !variantV.isString()) {
|
||||
if (!idV.isString() || !urlV.isString() || !variantV.isString()) {
|
||||
qWarning() << "mandatory skin attributes are missing or of unexpected type";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -181,11 +185,10 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
// data for skin is optional
|
||||
auto dataV = skinObj.value("data");
|
||||
if(dataV.isString()) {
|
||||
if (dataV.isString()) {
|
||||
// TODO: validate base64
|
||||
out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1());
|
||||
}
|
||||
else if (!dataV.isUndefined()) {
|
||||
} else if (!dataV.isUndefined()) {
|
||||
qWarning() << "skin data is something unexpected";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -193,13 +196,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
{
|
||||
auto capesV = tokenObject.value("capes");
|
||||
if(!capesV.isArray()) {
|
||||
if (!capesV.isArray()) {
|
||||
qWarning() << "capes is not an array!";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
auto capesArray = capesV.toArray();
|
||||
for(auto capeV: capesArray) {
|
||||
if(!capeV.isObject()) {
|
||||
for (auto capeV : capesArray) {
|
||||
if (!capeV.isObject()) {
|
||||
qWarning() << "cape is not an object!";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -207,7 +210,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
auto idV = capeObj.value("id");
|
||||
auto urlV = capeObj.value("url");
|
||||
auto aliasV = capeObj.value("alias");
|
||||
if(!idV.isString() || !urlV.isString() || !aliasV.isString()) {
|
||||
if (!idV.isString() || !urlV.isString() || !aliasV.isString()) {
|
||||
qWarning() << "mandatory skin attributes are missing or of unexpected type";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -218,11 +221,10 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
// data for cape is optional.
|
||||
auto dataV = capeObj.value("data");
|
||||
if(dataV.isString()) {
|
||||
if (dataV.isString()) {
|
||||
// TODO: validate base64
|
||||
cape.data = QByteArray::fromBase64(dataV.toString().toLatin1());
|
||||
}
|
||||
else if (!dataV.isUndefined()) {
|
||||
} else if (!dataV.isUndefined()) {
|
||||
qWarning() << "cape data is something unexpected";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -232,9 +234,9 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
// current cape
|
||||
{
|
||||
auto capeV = tokenObject.value("cape");
|
||||
if(capeV.isString()) {
|
||||
if (capeV.isString()) {
|
||||
auto currentCape = capeV.toString();
|
||||
if(out.capes.contains(currentCape)) {
|
||||
if (out.capes.contains(currentCape)) {
|
||||
out.currentCape = currentCape;
|
||||
}
|
||||
}
|
||||
@ -243,8 +245,9 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
return out;
|
||||
}
|
||||
|
||||
void entitlementToJSONV3(QJsonObject &parent, MinecraftEntitlement p) {
|
||||
if(p.validity == Katabasis::Validity::None) {
|
||||
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
|
||||
{
|
||||
if (p.validity == Katabasis::Validity::None) {
|
||||
return;
|
||||
}
|
||||
QJsonObject out;
|
||||
@ -253,15 +256,16 @@ void entitlementToJSONV3(QJsonObject &parent, MinecraftEntitlement p) {
|
||||
parent["entitlement"] = out;
|
||||
}
|
||||
|
||||
bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out) {
|
||||
bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
|
||||
{
|
||||
auto entitlementObject = parent.value("entitlement").toObject();
|
||||
if(entitlementObject.isEmpty()) {
|
||||
if (entitlementObject.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
auto ownsMinecraftV = entitlementObject.value("ownsMinecraft");
|
||||
auto canPlayMinecraftV = entitlementObject.value("canPlayMinecraft");
|
||||
if(!ownsMinecraftV.isBool() || !canPlayMinecraftV.isBool()) {
|
||||
if (!ownsMinecraftV.isBool() || !canPlayMinecraftV.isBool()) {
|
||||
qWarning() << "mandatory attributes are missing or of unexpected type";
|
||||
return false;
|
||||
}
|
||||
@ -272,12 +276,12 @@ bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
bool AccountData::resumeStateFromV2(QJsonObject data)
|
||||
{
|
||||
// The JSON object must at least have a username for it to be valid.
|
||||
if (!data.value("username").isString())
|
||||
{
|
||||
if (!data.value("username").isString()) {
|
||||
qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
|
||||
return false;
|
||||
}
|
||||
@ -287,14 +291,12 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
QString accessToken = data.value("accessToken").toString("");
|
||||
|
||||
QJsonArray profileArray = data.value("profiles").toArray();
|
||||
if (profileArray.size() < 1)
|
||||
{
|
||||
if (profileArray.size() < 1) {
|
||||
qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
struct AccountProfile
|
||||
{
|
||||
struct AccountProfile {
|
||||
QString id;
|
||||
QString name;
|
||||
bool legacy;
|
||||
@ -304,24 +306,22 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
int currentProfileIndex = 0;
|
||||
int index = -1;
|
||||
QString currentProfile = data.value("activeProfile").toString("");
|
||||
for (QJsonValue profileVal : profileArray)
|
||||
{
|
||||
for (QJsonValue profileVal : profileArray) {
|
||||
index++;
|
||||
QJsonObject profileObject = profileVal.toObject();
|
||||
QString id = profileObject.value("id").toString("");
|
||||
QString name = profileObject.value("name").toString("");
|
||||
bool legacy_ = profileObject.value("legacy").toBool(false);
|
||||
if (id.isEmpty() || name.isEmpty())
|
||||
{
|
||||
if (id.isEmpty() || name.isEmpty()) {
|
||||
qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
|
||||
continue;
|
||||
}
|
||||
if(id == currentProfile) {
|
||||
if (id == currentProfile) {
|
||||
currentProfileIndex = index;
|
||||
}
|
||||
profiles.append({id, name, legacy_});
|
||||
profiles.append({ id, name, legacy_ });
|
||||
}
|
||||
auto & profile = profiles[currentProfileIndex];
|
||||
auto& profile = profiles[currentProfileIndex];
|
||||
|
||||
type = AccountType::Mojang;
|
||||
legacy = profile.legacy;
|
||||
@ -339,14 +339,15 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
bool AccountData::resumeStateFromV3(QJsonObject data)
|
||||
{
|
||||
auto typeV = data.value("type");
|
||||
if(!typeV.isString()) {
|
||||
if (!typeV.isString()) {
|
||||
qWarning() << "Failed to parse account data: type is missing.";
|
||||
return false;
|
||||
}
|
||||
auto typeS = typeV.toString();
|
||||
if(typeS == "MSA") {
|
||||
if (typeS == "MSA") {
|
||||
type = AccountType::MSA;
|
||||
} else if (typeS == "Mojang") {
|
||||
type = AccountType::Mojang;
|
||||
@ -357,16 +358,16 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(type == AccountType::Mojang) {
|
||||
if (type == AccountType::Mojang) {
|
||||
legacy = data.value("legacy").toBool(false);
|
||||
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
|
||||
}
|
||||
|
||||
if(type == AccountType::MSA) {
|
||||
if (type == AccountType::MSA) {
|
||||
auto clientIDV = data.value("msa-client-id");
|
||||
if (clientIDV.isString()) {
|
||||
msaClientID = clientIDV.toString();
|
||||
} // leave msaClientID empty if it doesn't exist or isn't a string
|
||||
} // leave msaClientID empty if it doesn't exist or isn't a string
|
||||
msaToken = tokenFromJSONV3(data, "msa");
|
||||
userToken = tokenFromJSONV3(data, "utoken");
|
||||
xboxApiToken = tokenFromJSONV3(data, "xrp-main");
|
||||
@ -379,8 +380,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
yggdrasilToken.token = "0";
|
||||
|
||||
minecraftProfile = profileFromJSONV3(data, "profile");
|
||||
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||
if(minecraftProfile.validity != Katabasis::Validity::None) {
|
||||
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||
if (minecraftProfile.validity != Katabasis::Validity::None) {
|
||||
minecraftEntitlement.canPlayMinecraft = true;
|
||||
minecraftEntitlement.ownsMinecraft = true;
|
||||
minecraftEntitlement.validity = Katabasis::Validity::Assumed;
|
||||
@ -391,26 +392,25 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject AccountData::saveState() const {
|
||||
QJsonObject AccountData::saveState() const
|
||||
{
|
||||
QJsonObject output;
|
||||
if(type == AccountType::Mojang) {
|
||||
if (type == AccountType::Mojang) {
|
||||
output["type"] = "Mojang";
|
||||
if(legacy) {
|
||||
if (legacy) {
|
||||
output["legacy"] = true;
|
||||
}
|
||||
if(canMigrateToMSA) {
|
||||
if (canMigrateToMSA) {
|
||||
output["canMigrateToMSA"] = true;
|
||||
}
|
||||
}
|
||||
else if (type == AccountType::MSA) {
|
||||
} else if (type == AccountType::MSA) {
|
||||
output["type"] = "MSA";
|
||||
output["msa-client-id"] = msaClientID;
|
||||
tokenToJSONV3(output, msaToken, "msa");
|
||||
tokenToJSONV3(output, userToken, "utoken");
|
||||
tokenToJSONV3(output, xboxApiToken, "xrp-main");
|
||||
tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
|
||||
}
|
||||
else if (type == AccountType::Offline) {
|
||||
} else if (type == AccountType::Offline) {
|
||||
output["type"] = "Offline";
|
||||
}
|
||||
|
||||
@ -420,60 +420,68 @@ QJsonObject AccountData::saveState() const {
|
||||
return output;
|
||||
}
|
||||
|
||||
QString AccountData::userName() const {
|
||||
if(type == AccountType::MSA) {
|
||||
QString AccountData::userName() const
|
||||
{
|
||||
if (type == AccountType::MSA) {
|
||||
return QString();
|
||||
}
|
||||
return yggdrasilToken.extra["userName"].toString();
|
||||
}
|
||||
|
||||
QString AccountData::accessToken() const {
|
||||
QString AccountData::accessToken() const
|
||||
{
|
||||
return yggdrasilToken.token;
|
||||
}
|
||||
|
||||
QString AccountData::clientToken() const {
|
||||
if(type != AccountType::Mojang) {
|
||||
QString AccountData::clientToken() const
|
||||
{
|
||||
if (type != AccountType::Mojang) {
|
||||
return QString();
|
||||
}
|
||||
return yggdrasilToken.extra["clientToken"].toString();
|
||||
}
|
||||
|
||||
void AccountData::setClientToken(QString clientToken) {
|
||||
if(type != AccountType::Mojang) {
|
||||
void AccountData::setClientToken(QString clientToken)
|
||||
{
|
||||
if (type != AccountType::Mojang) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = clientToken;
|
||||
}
|
||||
|
||||
void AccountData::generateClientTokenIfMissing() {
|
||||
if(yggdrasilToken.extra.contains("clientToken")) {
|
||||
void AccountData::generateClientTokenIfMissing()
|
||||
{
|
||||
if (yggdrasilToken.extra.contains("clientToken")) {
|
||||
return;
|
||||
}
|
||||
invalidateClientToken();
|
||||
}
|
||||
|
||||
void AccountData::invalidateClientToken() {
|
||||
if(type != AccountType::Mojang) {
|
||||
void AccountData::invalidateClientToken()
|
||||
{
|
||||
if (type != AccountType::Mojang) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
||||
}
|
||||
|
||||
QString AccountData::profileId() const {
|
||||
QString AccountData::profileId() const
|
||||
{
|
||||
return minecraftProfile.id;
|
||||
}
|
||||
|
||||
QString AccountData::profileName() const {
|
||||
if(minecraftProfile.name.size() == 0) {
|
||||
QString AccountData::profileName() const
|
||||
{
|
||||
if (minecraftProfile.name.size() == 0) {
|
||||
return QObject::tr("No profile (%1)").arg(accountDisplayString());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return minecraftProfile.name;
|
||||
}
|
||||
}
|
||||
|
||||
QString AccountData::accountDisplayString() const {
|
||||
switch(type) {
|
||||
QString AccountData::accountDisplayString() const
|
||||
{
|
||||
switch (type) {
|
||||
case AccountType::Mojang: {
|
||||
return userName();
|
||||
}
|
||||
@ -481,7 +489,7 @@ QString AccountData::accountDisplayString() const {
|
||||
return QObject::tr("<Offline>");
|
||||
}
|
||||
case AccountType::MSA: {
|
||||
if(xboxApiToken.extra.contains("gtg")) {
|
||||
if (xboxApiToken.extra.contains("gtg")) {
|
||||
return xboxApiToken.extra["gtg"].toString();
|
||||
}
|
||||
return "Xbox profile missing";
|
||||
@ -492,6 +500,7 @@ QString AccountData::accountDisplayString() const {
|
||||
}
|
||||
}
|
||||
|
||||
QString AccountData::lastError() const {
|
||||
QString AccountData::lastError() const
|
||||
{
|
||||
return errorString;
|
||||
}
|
||||
|
@ -34,11 +34,11 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QVector>
|
||||
#include <katabasis/Bits.h>
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
struct Skin {
|
||||
QString id;
|
||||
@ -71,22 +71,9 @@ struct MinecraftProfile {
|
||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
||||
};
|
||||
|
||||
enum class AccountType {
|
||||
MSA,
|
||||
Mojang,
|
||||
Offline
|
||||
};
|
||||
enum class AccountType { MSA, Mojang, Offline };
|
||||
|
||||
enum class AccountState {
|
||||
Unchecked,
|
||||
Offline,
|
||||
Working,
|
||||
Online,
|
||||
Disabled,
|
||||
Errored,
|
||||
Expired,
|
||||
Gone
|
||||
};
|
||||
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
|
||||
|
||||
struct AccountData {
|
||||
QJsonObject saveState() const;
|
||||
|
@ -37,14 +37,14 @@
|
||||
#include "AccountData.h"
|
||||
#include "AccountTask.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QIODevice>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QDebug>
|
||||
@ -54,12 +54,10 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
enum AccountListVersion {
|
||||
MojangOnly = 2,
|
||||
MojangMSA = 3
|
||||
};
|
||||
enum AccountListVersion { MojangOnly = 2, MojangMSA = 3 };
|
||||
|
||||
AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) {
|
||||
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
|
||||
{
|
||||
m_refreshTimer = new QTimer(this);
|
||||
m_refreshTimer->setSingleShot(true);
|
||||
connect(m_refreshTimer, &QTimer::timeout, this, &AccountList::fillQueue);
|
||||
@ -70,7 +68,8 @@ AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) {
|
||||
|
||||
AccountList::~AccountList() noexcept {}
|
||||
|
||||
int AccountList::findAccountByProfileId(const QString& profileId) const {
|
||||
int AccountList::findAccountByProfileId(const QString& profileId) const
|
||||
{
|
||||
for (int i = 0; i < count(); i++) {
|
||||
MinecraftAccountPtr account = at(i);
|
||||
if (account->profileId() == profileId) {
|
||||
@ -80,7 +79,8 @@ int AccountList::findAccountByProfileId(const QString& profileId) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr AccountList::getAccountByProfileName(const QString& profileName) const {
|
||||
MinecraftAccountPtr AccountList::getAccountByProfileName(const QString& profileName) const
|
||||
{
|
||||
for (int i = 0; i < count(); i++) {
|
||||
MinecraftAccountPtr account = at(i);
|
||||
if (account->profileName() == profileName) {
|
||||
@ -95,11 +95,12 @@ const MinecraftAccountPtr AccountList::at(int i) const
|
||||
return MinecraftAccountPtr(m_accounts.at(i));
|
||||
}
|
||||
|
||||
QStringList AccountList::profileNames() const {
|
||||
QStringList AccountList::profileNames() const
|
||||
{
|
||||
QStringList out;
|
||||
for(auto & account: m_accounts) {
|
||||
auto profileName = account->profileName();
|
||||
if(profileName.isEmpty()) {
|
||||
for (auto& account : m_accounts) {
|
||||
auto profileName = account->profileName();
|
||||
if (profileName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
out.append(profileName);
|
||||
@ -122,14 +123,14 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
|
||||
|
||||
// override/replace existing account with the same profileId
|
||||
auto profileId = account->profileId();
|
||||
if(profileId.size()) {
|
||||
if (profileId.size()) {
|
||||
auto existingAccount = findAccountByProfileId(profileId);
|
||||
if(existingAccount != -1) {
|
||||
if (existingAccount != -1) {
|
||||
qDebug() << "Replacing old account with a new one with the same profile ID!";
|
||||
|
||||
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
|
||||
m_accounts[existingAccount] = account;
|
||||
if(m_defaultAccount == existingAccountPtr) {
|
||||
if (m_defaultAccount == existingAccountPtr) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
// disconnect notifications for changes in the account being replaced
|
||||
@ -154,11 +155,9 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
|
||||
void AccountList::removeAccount(QModelIndex index)
|
||||
{
|
||||
int row = index.row();
|
||||
if(index.isValid() && row >= 0 && row < m_accounts.size())
|
||||
{
|
||||
auto & account = m_accounts[row];
|
||||
if(account == m_defaultAccount)
|
||||
{
|
||||
if (index.isValid() && row >= 0 && row < m_accounts.size()) {
|
||||
auto& account = m_accounts[row];
|
||||
if (account == m_defaultAccount) {
|
||||
m_defaultAccount = nullptr;
|
||||
onDefaultAccountChanged();
|
||||
}
|
||||
@ -178,43 +177,34 @@ MinecraftAccountPtr AccountList::defaultAccount() const
|
||||
|
||||
void AccountList::setDefaultAccount(MinecraftAccountPtr newAccount)
|
||||
{
|
||||
if (!newAccount && m_defaultAccount)
|
||||
{
|
||||
if (!newAccount && m_defaultAccount) {
|
||||
int idx = 0;
|
||||
auto previousDefaultAccount = m_defaultAccount;
|
||||
m_defaultAccount = nullptr;
|
||||
for (MinecraftAccountPtr account : m_accounts)
|
||||
{
|
||||
if (account == previousDefaultAccount)
|
||||
{
|
||||
for (MinecraftAccountPtr account : m_accounts) {
|
||||
if (account == previousDefaultAccount) {
|
||||
emit dataChanged(index(idx), index(idx, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
idx ++;
|
||||
idx++;
|
||||
}
|
||||
onDefaultAccountChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
auto currentDefaultAccount = m_defaultAccount;
|
||||
int currentDefaultAccountIdx = -1;
|
||||
auto newDefaultAccount = m_defaultAccount;
|
||||
int newDefaultAccountIdx = -1;
|
||||
int idx = 0;
|
||||
for (MinecraftAccountPtr account : m_accounts)
|
||||
{
|
||||
if (account == newAccount)
|
||||
{
|
||||
for (MinecraftAccountPtr account : m_accounts) {
|
||||
if (account == newAccount) {
|
||||
newDefaultAccount = account;
|
||||
newDefaultAccountIdx = idx;
|
||||
}
|
||||
if(currentDefaultAccount == account)
|
||||
{
|
||||
if (currentDefaultAccount == account) {
|
||||
currentDefaultAccountIdx = idx;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
if(currentDefaultAccount != newDefaultAccount)
|
||||
{
|
||||
if (currentDefaultAccount != newDefaultAccount) {
|
||||
emit dataChanged(index(currentDefaultAccountIdx), index(currentDefaultAccountIdx, columnCount(QModelIndex()) - 1));
|
||||
emit dataChanged(index(newDefaultAccountIdx), index(newDefaultAccountIdx, columnCount(QModelIndex()) - 1));
|
||||
m_defaultAccount = newDefaultAccount;
|
||||
@ -231,27 +221,25 @@ void AccountList::accountChanged()
|
||||
|
||||
void AccountList::accountActivityChanged(bool active)
|
||||
{
|
||||
MinecraftAccount *account = qobject_cast<MinecraftAccount *>(sender());
|
||||
MinecraftAccount* account = qobject_cast<MinecraftAccount*>(sender());
|
||||
bool found = false;
|
||||
for (int i = 0; i < count(); i++) {
|
||||
if (at(i).get() == account) {
|
||||
emit dataChanged(index(i), index(i, columnCount(QModelIndex()) - 1));
|
||||
emit dataChanged(index(i), index(i, columnCount(QModelIndex()) - 1));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found) {
|
||||
if (found) {
|
||||
emit listActivityChanged();
|
||||
if(active) {
|
||||
if (active) {
|
||||
beginActivity();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
endActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AccountList::onListChanged()
|
||||
{
|
||||
if (m_autosave)
|
||||
@ -274,7 +262,7 @@ int AccountList::count() const
|
||||
return m_accounts.count();
|
||||
}
|
||||
|
||||
QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@ -284,70 +272,67 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
|
||||
MinecraftAccountPtr account = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ProfileNameColumn: {
|
||||
return account->profileName();
|
||||
}
|
||||
switch (index.column()) {
|
||||
case ProfileNameColumn: {
|
||||
return account->profileName();
|
||||
}
|
||||
|
||||
case NameColumn:
|
||||
return account->accountDisplayString();
|
||||
case NameColumn:
|
||||
return account->accountDisplayString();
|
||||
|
||||
case TypeColumn: {
|
||||
auto typeStr = account->typeString();
|
||||
typeStr[0] = typeStr[0].toUpper();
|
||||
return typeStr;
|
||||
}
|
||||
case TypeColumn: {
|
||||
auto typeStr = account->typeString();
|
||||
typeStr[0] = typeStr[0].toUpper();
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
case StatusColumn: {
|
||||
switch(account->accountState()) {
|
||||
case AccountState::Unchecked: {
|
||||
return tr("Unchecked", "Account status");
|
||||
}
|
||||
case AccountState::Offline: {
|
||||
return tr("Offline", "Account status");
|
||||
}
|
||||
case AccountState::Online: {
|
||||
return tr("Ready", "Account status");
|
||||
}
|
||||
case AccountState::Working: {
|
||||
return tr("Working", "Account status");
|
||||
}
|
||||
case AccountState::Errored: {
|
||||
return tr("Errored", "Account status");
|
||||
}
|
||||
case AccountState::Expired: {
|
||||
return tr("Expired", "Account status");
|
||||
}
|
||||
case AccountState::Disabled: {
|
||||
return tr("Disabled", "Account status");
|
||||
}
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
case StatusColumn: {
|
||||
switch (account->accountState()) {
|
||||
case AccountState::Unchecked: {
|
||||
return tr("Unchecked", "Account status");
|
||||
}
|
||||
case AccountState::Offline: {
|
||||
return tr("Offline", "Account status");
|
||||
}
|
||||
case AccountState::Online: {
|
||||
return tr("Ready", "Account status");
|
||||
}
|
||||
case AccountState::Working: {
|
||||
return tr("Working", "Account status");
|
||||
}
|
||||
case AccountState::Errored: {
|
||||
return tr("Errored", "Account status");
|
||||
}
|
||||
case AccountState::Expired: {
|
||||
return tr("Expired", "Account status");
|
||||
}
|
||||
case AccountState::Disabled: {
|
||||
return tr("Disabled", "Account status");
|
||||
}
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case MigrationColumn: {
|
||||
if(account->isMSA() || account->isOffline()) {
|
||||
return tr("N/A", "Can Migrate");
|
||||
case MigrationColumn: {
|
||||
if (account->isMSA() || account->isOffline()) {
|
||||
return tr("N/A", "Can Migrate");
|
||||
}
|
||||
if (account->canMigrate()) {
|
||||
return tr("Yes", "Can Migrate");
|
||||
} else {
|
||||
return tr("No", "Can Migrate");
|
||||
}
|
||||
}
|
||||
if (account->canMigrate()) {
|
||||
return tr("Yes", "Can Migrate");
|
||||
}
|
||||
else {
|
||||
return tr("No", "Can Migrate");
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
@ -362,7 +347,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
@ -371,79 +355,72 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
|
||||
QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return tr("Username");
|
||||
case NameColumn:
|
||||
return tr("Account");
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
case StatusColumn:
|
||||
return tr("Status");
|
||||
case MigrationColumn:
|
||||
return tr("Can Migrate?");
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case ProfileNameColumn:
|
||||
return tr("Username");
|
||||
case NameColumn:
|
||||
return tr("Account");
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
case StatusColumn:
|
||||
return tr("Status");
|
||||
case MigrationColumn:
|
||||
return tr("Can Migrate?");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section) {
|
||||
case ProfileNameColumn:
|
||||
return tr("Minecraft username associated with the account.");
|
||||
case NameColumn:
|
||||
return tr("User name of the account.");
|
||||
case TypeColumn:
|
||||
return tr("Type of the account - Mojang or MSA.");
|
||||
case StatusColumn:
|
||||
return tr("Current status of the account.");
|
||||
case MigrationColumn:
|
||||
return tr("Can this account migrate to a Microsoft account?");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return tr("Minecraft username associated with the account.");
|
||||
case NameColumn:
|
||||
return tr("User name of the account.");
|
||||
case TypeColumn:
|
||||
return tr("Type of the account - Mojang or MSA.");
|
||||
case StatusColumn:
|
||||
return tr("Current status of the account.");
|
||||
case MigrationColumn:
|
||||
return tr("Can this account migrate to a Microsoft account?");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int AccountList::rowCount(const QModelIndex &parent) const
|
||||
int AccountList::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
// Return count
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int AccountList::columnCount(const QModelIndex &parent) const
|
||||
int AccountList::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
bool AccountList::setData(const QModelIndex &idx, const QVariant &value, int role)
|
||||
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
||||
{
|
||||
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid())
|
||||
{
|
||||
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(role == Qt::CheckStateRole)
|
||||
{
|
||||
if(value == Qt::Checked)
|
||||
{
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if (value == Qt::Checked) {
|
||||
MinecraftAccountPtr account = at(idx.row());
|
||||
setDefaultAccount(account);
|
||||
}
|
||||
@ -455,8 +432,7 @@ bool AccountList::setData(const QModelIndex &idx, const QVariant &value, int rol
|
||||
|
||||
bool AccountList::loadList()
|
||||
{
|
||||
if (m_listFilePath.isEmpty())
|
||||
{
|
||||
if (m_listFilePath.isEmpty()) {
|
||||
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
|
||||
return false;
|
||||
}
|
||||
@ -465,8 +441,7 @@ bool AccountList::loadList()
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
|
||||
return false;
|
||||
}
|
||||
@ -479,17 +454,15 @@ bool AccountList::loadList()
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
// Fail if the JSON is invalid.
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCritical() << QString("Failed to parse account list file: %1 at offset %2")
|
||||
.arg(parseError.errorString(), QString::number(parseError.offset))
|
||||
.toUtf8();
|
||||
.arg(parseError.errorString(), QString::number(parseError.offset))
|
||||
.toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the root is an object.
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
if (!jsonDoc.isObject()) {
|
||||
qCritical() << "Invalid account list JSON: Root should be an array.";
|
||||
return false;
|
||||
}
|
||||
@ -498,15 +471,13 @@ bool AccountList::loadList()
|
||||
|
||||
// Make sure the format version matches.
|
||||
auto listVersion = root.value("formatVersion").toVariant().toInt();
|
||||
switch(listVersion) {
|
||||
switch (listVersion) {
|
||||
case AccountListVersion::MojangOnly: {
|
||||
return loadV2(root);
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountListVersion::MojangMSA: {
|
||||
return loadV3(root);
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
default: {
|
||||
QString newName = "accounts-old.json";
|
||||
qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
|
||||
@ -517,21 +488,20 @@ bool AccountList::loadList()
|
||||
}
|
||||
}
|
||||
|
||||
bool AccountList::loadV2(QJsonObject& root) {
|
||||
bool AccountList::loadV2(QJsonObject& root)
|
||||
{
|
||||
beginResetModel();
|
||||
auto defaultUserName = root.value("activeAccount").toString("");
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
for (QJsonValue accountVal : accounts)
|
||||
{
|
||||
for (QJsonValue accountVal : accounts) {
|
||||
QJsonObject accountObj = accountVal.toObject();
|
||||
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
|
||||
if (account.get() != nullptr)
|
||||
{
|
||||
if (account.get() != nullptr) {
|
||||
auto profileId = account->profileId();
|
||||
if(!profileId.size()) {
|
||||
if (!profileId.size()) {
|
||||
continue;
|
||||
}
|
||||
if(findAccountByProfileId(profileId) != -1) {
|
||||
if (findAccountByProfileId(profileId) != -1) {
|
||||
continue;
|
||||
}
|
||||
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
|
||||
@ -540,9 +510,7 @@ bool AccountList::loadV2(QJsonObject& root) {
|
||||
if (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to load an account.";
|
||||
}
|
||||
}
|
||||
@ -550,30 +518,27 @@ bool AccountList::loadV2(QJsonObject& root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountList::loadV3(QJsonObject& root) {
|
||||
bool AccountList::loadV3(QJsonObject& root)
|
||||
{
|
||||
beginResetModel();
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
for (QJsonValue accountVal : accounts)
|
||||
{
|
||||
for (QJsonValue accountVal : accounts) {
|
||||
QJsonObject accountObj = accountVal.toObject();
|
||||
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV3(accountObj);
|
||||
if (account.get() != nullptr)
|
||||
{
|
||||
if (account.get() != nullptr) {
|
||||
auto profileId = account->profileId();
|
||||
if(profileId.size()) {
|
||||
if(findAccountByProfileId(profileId) != -1) {
|
||||
if (profileId.size()) {
|
||||
if (findAccountByProfileId(profileId) != -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
|
||||
connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
|
||||
m_accounts.append(account);
|
||||
if(accountObj.value("active").toBool(false)) {
|
||||
if (accountObj.value("active").toBool(false)) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to load an account.";
|
||||
}
|
||||
}
|
||||
@ -581,23 +546,20 @@ bool AccountList::loadV3(QJsonObject& root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AccountList::saveList()
|
||||
{
|
||||
if (m_listFilePath.isEmpty())
|
||||
{
|
||||
if (m_listFilePath.isEmpty()) {
|
||||
qCritical() << "Can't save Mojang account list. No file path given and no default set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the parent folder exists
|
||||
if(!FS::ensureFilePathExists(m_listFilePath))
|
||||
if (!FS::ensureFilePathExists(m_listFilePath))
|
||||
return false;
|
||||
|
||||
// make sure the file wasn't overwritten with a folder before (fixes a bug)
|
||||
QFileInfo finfo(m_listFilePath);
|
||||
if(finfo.isDir())
|
||||
{
|
||||
if (finfo.isDir()) {
|
||||
QDir badDir(m_listFilePath);
|
||||
badDir.removeRecursively();
|
||||
}
|
||||
@ -613,10 +575,9 @@ bool AccountList::saveList()
|
||||
// Build a list of accounts.
|
||||
qDebug() << "Building account array.";
|
||||
QJsonArray accounts;
|
||||
for (MinecraftAccountPtr account : m_accounts)
|
||||
{
|
||||
for (MinecraftAccountPtr account : m_accounts) {
|
||||
QJsonObject accountObj = account->saveToJson();
|
||||
if(m_defaultAccount == account) {
|
||||
if (m_defaultAccount == account) {
|
||||
accountObj["active"] = true;
|
||||
}
|
||||
accounts.append(accountObj);
|
||||
@ -634,20 +595,18 @@ bool AccountList::saveList()
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the JSON to the file.
|
||||
file.write(doc.toJson());
|
||||
file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
|
||||
if(file.commit()) {
|
||||
file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
||||
if (file.commit()) {
|
||||
qDebug() << "Saved account list to" << m_listFilePath;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
qDebug() << "Failed to save accounts to" << m_listFilePath;
|
||||
return false;
|
||||
}
|
||||
@ -661,30 +620,29 @@ void AccountList::setListFilePath(QString path, bool autosave)
|
||||
|
||||
bool AccountList::anyAccountIsValid()
|
||||
{
|
||||
for(auto account: m_accounts)
|
||||
{
|
||||
if(account->ownsMinecraft()) {
|
||||
for (auto account : m_accounts) {
|
||||
if (account->ownsMinecraft()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AccountList::fillQueue() {
|
||||
|
||||
if(m_defaultAccount && m_defaultAccount->shouldRefresh()) {
|
||||
void AccountList::fillQueue()
|
||||
{
|
||||
if (m_defaultAccount && m_defaultAccount->shouldRefresh()) {
|
||||
auto idToRefresh = m_defaultAccount->internalId();
|
||||
m_refreshQueue.push_back(idToRefresh);
|
||||
qDebug() << "AccountList: Queued default account with internal ID " << idToRefresh << " to refresh first";
|
||||
}
|
||||
|
||||
for(int i = 0; i < count(); i++) {
|
||||
for (int i = 0; i < count(); i++) {
|
||||
auto account = at(i);
|
||||
if(account == m_defaultAccount) {
|
||||
if (account == m_defaultAccount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(account->shouldRefresh()) {
|
||||
if (account->shouldRefresh()) {
|
||||
auto idToRefresh = account->internalId();
|
||||
queueRefresh(idToRefresh);
|
||||
}
|
||||
@ -692,40 +650,43 @@ void AccountList::fillQueue() {
|
||||
tryNext();
|
||||
}
|
||||
|
||||
void AccountList::requestRefresh(QString accountId) {
|
||||
void AccountList::requestRefresh(QString accountId)
|
||||
{
|
||||
auto index = m_refreshQueue.indexOf(accountId);
|
||||
if(index != -1) {
|
||||
if (index != -1) {
|
||||
m_refreshQueue.removeAt(index);
|
||||
}
|
||||
m_refreshQueue.push_front(accountId);
|
||||
qDebug() << "AccountList: Pushed account with internal ID " << accountId << " to the front of the queue";
|
||||
if(!isActive()) {
|
||||
if (!isActive()) {
|
||||
tryNext();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountList::queueRefresh(QString accountId) {
|
||||
if(m_refreshQueue.indexOf(accountId) != -1) {
|
||||
void AccountList::queueRefresh(QString accountId)
|
||||
{
|
||||
if (m_refreshQueue.indexOf(accountId) != -1) {
|
||||
return;
|
||||
}
|
||||
m_refreshQueue.push_back(accountId);
|
||||
qDebug() << "AccountList: Queued account with internal ID " << accountId << " to refresh";
|
||||
}
|
||||
|
||||
|
||||
void AccountList::tryNext() {
|
||||
void AccountList::tryNext()
|
||||
{
|
||||
while (m_refreshQueue.length()) {
|
||||
auto accountId = m_refreshQueue.front();
|
||||
m_refreshQueue.pop_front();
|
||||
for(int i = 0; i < count(); i++) {
|
||||
for (int i = 0; i < count(); i++) {
|
||||
auto account = at(i);
|
||||
if(account->internalId() == accountId) {
|
||||
if (account->internalId() == accountId) {
|
||||
m_currentTask = account->refresh();
|
||||
if(m_currentTask) {
|
||||
if (m_currentTask) {
|
||||
connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded);
|
||||
connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed);
|
||||
m_currentTask->start();
|
||||
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID " << accountId;
|
||||
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
|
||||
<< accountId;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -736,38 +697,43 @@ void AccountList::tryNext() {
|
||||
m_refreshTimer->start(1000 * 3600);
|
||||
}
|
||||
|
||||
void AccountList::authSucceeded() {
|
||||
void AccountList::authSucceeded()
|
||||
{
|
||||
qDebug() << "RefreshSchedule: Background account refresh succeeded";
|
||||
m_currentTask.reset();
|
||||
m_nextTimer->start(1000 * 20);
|
||||
}
|
||||
|
||||
void AccountList::authFailed(QString reason) {
|
||||
void AccountList::authFailed(QString reason)
|
||||
{
|
||||
qDebug() << "RefreshSchedule: Background account refresh failed: " << reason;
|
||||
m_currentTask.reset();
|
||||
m_nextTimer->start(1000 * 20);
|
||||
}
|
||||
|
||||
bool AccountList::isActive() const {
|
||||
bool AccountList::isActive() const
|
||||
{
|
||||
return m_activityCount != 0;
|
||||
}
|
||||
|
||||
void AccountList::beginActivity() {
|
||||
void AccountList::beginActivity()
|
||||
{
|
||||
bool activating = m_activityCount == 0;
|
||||
m_activityCount++;
|
||||
if(activating) {
|
||||
if (activating) {
|
||||
emit activityChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountList::endActivity() {
|
||||
if(m_activityCount == 0) {
|
||||
void AccountList::endActivity()
|
||||
{
|
||||
if (m_activityCount == 0) {
|
||||
qWarning() << m_name << " - Activity count would become below zero";
|
||||
return;
|
||||
}
|
||||
bool deactivating = m_activityCount == 1;
|
||||
m_activityCount--;
|
||||
if(deactivating) {
|
||||
if (deactivating) {
|
||||
emit activityChanged(false);
|
||||
}
|
||||
}
|
||||
|
@ -37,26 +37,21 @@
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
|
||||
/*!
|
||||
* List of available Mojang accounts.
|
||||
* This should be loaded in the background by Prism Launcher on startup.
|
||||
*/
|
||||
class AccountList : public QAbstractListModel
|
||||
{
|
||||
class AccountList : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
PointerRole = 0x34B1CB48
|
||||
};
|
||||
public:
|
||||
enum ModelRoles { PointerRole = 0x34B1CB48 };
|
||||
|
||||
enum VListColumns
|
||||
{
|
||||
enum VListColumns {
|
||||
// TODO: Add icon column.
|
||||
ProfileNameColumn = 0,
|
||||
NameColumn,
|
||||
@ -67,24 +62,24 @@ public:
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
explicit AccountList(QObject *parent = 0);
|
||||
explicit AccountList(QObject* parent = 0);
|
||||
virtual ~AccountList() noexcept;
|
||||
|
||||
const MinecraftAccountPtr at(int i) const;
|
||||
int count() const;
|
||||
|
||||
//////// List Model Functions ////////
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
virtual int rowCount(const QModelIndex &parent) const override;
|
||||
virtual int columnCount(const QModelIndex &parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
virtual int rowCount(const QModelIndex& parent) const override;
|
||||
virtual int columnCount(const QModelIndex& parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
||||
|
||||
void addAccount(const MinecraftAccountPtr account);
|
||||
void removeAccount(QModelIndex index);
|
||||
int findAccountByProfileId(const QString &profileId) const;
|
||||
MinecraftAccountPtr getAccountByProfileName(const QString &profileName) const;
|
||||
int findAccountByProfileId(const QString& profileId) const;
|
||||
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
|
||||
QStringList profileNames() const;
|
||||
|
||||
// requesting a refresh pushes it to the front of the queue
|
||||
@ -102,8 +97,8 @@ public:
|
||||
void setListFilePath(QString path, bool autosave = false);
|
||||
|
||||
bool loadList();
|
||||
bool loadV2(QJsonObject &root);
|
||||
bool loadV3(QJsonObject &root);
|
||||
bool loadV2(QJsonObject& root);
|
||||
bool loadV3(QJsonObject& root);
|
||||
bool saveList();
|
||||
|
||||
MinecraftAccountPtr defaultAccount() const;
|
||||
@ -112,20 +107,20 @@ public:
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void beginActivity();
|
||||
void endActivity();
|
||||
|
||||
private:
|
||||
private:
|
||||
const char* m_name;
|
||||
uint32_t m_activityCount = 0;
|
||||
signals:
|
||||
signals:
|
||||
void listChanged();
|
||||
void listActivityChanged();
|
||||
void defaultAccountChanged();
|
||||
void activityChanged(bool active);
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
/**
|
||||
* This is called when one of the accounts changes and the list needs to be updated
|
||||
*/
|
||||
@ -141,16 +136,16 @@ public slots:
|
||||
*/
|
||||
void fillQueue();
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void tryNext();
|
||||
|
||||
void authSucceeded();
|
||||
void authFailed(QString reason);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
QList<QString> m_refreshQueue;
|
||||
QTimer *m_refreshTimer;
|
||||
QTimer *m_nextTimer;
|
||||
QTimer* m_refreshTimer;
|
||||
QTimer* m_nextTimer;
|
||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
||||
|
||||
/*!
|
||||
@ -178,4 +173,3 @@ protected:
|
||||
*/
|
||||
bool m_autosave = false;
|
||||
};
|
||||
|
||||
|
@ -36,43 +36,41 @@
|
||||
#include "AccountTask.h"
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
AccountTask::AccountTask(AccountData *data, QObject *parent)
|
||||
: Task(parent), m_data(data)
|
||||
AccountTask::AccountTask(AccountData* data, QObject* parent) : Task(parent), m_data(data)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_CREATED);
|
||||
}
|
||||
|
||||
QString AccountTask::getStateMessage() const
|
||||
{
|
||||
switch (m_taskState)
|
||||
{
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
return "Waiting...";
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
return tr("Sending request to auth servers...");
|
||||
case AccountTaskState::STATE_SUCCEEDED:
|
||||
return tr("Authentication task succeeded.");
|
||||
case AccountTaskState::STATE_OFFLINE:
|
||||
return tr("Failed to contact the authentication server.");
|
||||
case AccountTaskState::STATE_DISABLED:
|
||||
return tr("Client ID has changed. New session needs to be created.");
|
||||
case AccountTaskState::STATE_FAILED_SOFT:
|
||||
return tr("Encountered an error during authentication.");
|
||||
case AccountTaskState::STATE_FAILED_HARD:
|
||||
return tr("Failed to authenticate. The session has expired.");
|
||||
case AccountTaskState::STATE_FAILED_GONE:
|
||||
return tr("Failed to authenticate. The account no longer exists.");
|
||||
default:
|
||||
return tr("...");
|
||||
switch (m_taskState) {
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
return "Waiting...";
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
return tr("Sending request to auth servers...");
|
||||
case AccountTaskState::STATE_SUCCEEDED:
|
||||
return tr("Authentication task succeeded.");
|
||||
case AccountTaskState::STATE_OFFLINE:
|
||||
return tr("Failed to contact the authentication server.");
|
||||
case AccountTaskState::STATE_DISABLED:
|
||||
return tr("Client ID has changed. New session needs to be created.");
|
||||
case AccountTaskState::STATE_FAILED_SOFT:
|
||||
return tr("Encountered an error during authentication.");
|
||||
case AccountTaskState::STATE_FAILED_HARD:
|
||||
return tr("Failed to authenticate. The session has expired.");
|
||||
case AccountTaskState::STATE_FAILED_GONE:
|
||||
return tr("Failed to authenticate. The account no longer exists.");
|
||||
default:
|
||||
return tr("...");
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +80,7 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
|
||||
// FIXME: virtual method invoked in constructor.
|
||||
// We want that behavior, but maybe make it less weird?
|
||||
setStatus(getStateMessage());
|
||||
switch(newState) {
|
||||
switch (newState) {
|
||||
case AccountTaskState::STATE_CREATED: {
|
||||
m_data->errorString.clear();
|
||||
return true;
|
||||
|
@ -37,10 +37,10 @@
|
||||
|
||||
#include <tasks/Task.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <qsslerror.h>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
@ -50,37 +50,32 @@ class QNetworkReply;
|
||||
* Enum for describing the state of the current task.
|
||||
* Used by the getStateMessage function to determine what the status message should be.
|
||||
*/
|
||||
enum class AccountTaskState
|
||||
{
|
||||
enum class AccountTaskState {
|
||||
STATE_CREATED,
|
||||
STATE_WORKING,
|
||||
STATE_SUCCEEDED,
|
||||
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
||||
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
||||
};
|
||||
|
||||
class AccountTask : public Task
|
||||
{
|
||||
class AccountTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AccountTask(AccountData * data, QObject *parent = 0);
|
||||
virtual ~AccountTask() {};
|
||||
public:
|
||||
explicit AccountTask(AccountData* data, QObject* parent = 0);
|
||||
virtual ~AccountTask(){};
|
||||
|
||||
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
||||
|
||||
AccountTaskState taskState() {
|
||||
return m_taskState;
|
||||
}
|
||||
AccountTaskState taskState() { return m_taskState; }
|
||||
|
||||
signals:
|
||||
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
|
||||
signals:
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
protected:
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns the state message for the given state.
|
||||
* Used to set the status message for the task.
|
||||
@ -88,10 +83,10 @@ protected:
|
||||
*/
|
||||
virtual QString getStateMessage() const;
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
// NOTE: true -> non-terminal state, false -> terminal state
|
||||
bool changeState(AccountTaskState newState, QString reason = QString());
|
||||
|
||||
protected:
|
||||
AccountData *m_data = nullptr;
|
||||
protected:
|
||||
AccountData* m_data = nullptr;
|
||||
};
|
||||
|
@ -35,44 +35,44 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QBuffer>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AuthRequest.h"
|
||||
#include "katabasis/Globals.h"
|
||||
|
||||
AuthRequest::AuthRequest(QObject *parent): QObject(parent) {
|
||||
}
|
||||
AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {}
|
||||
|
||||
AuthRequest::~AuthRequest() {
|
||||
}
|
||||
AuthRequest::~AuthRequest() {}
|
||||
|
||||
void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
|
||||
void AuthRequest::get(const QNetworkRequest& req, int timeout /* = 60*1000*/)
|
||||
{
|
||||
setup(req, QNetworkAccessManager::GetOperation);
|
||||
reply_ = APPLICATION->network()->get(request_);
|
||||
status_ = Requesting;
|
||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||
#endif
|
||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
||||
}
|
||||
|
||||
void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) {
|
||||
void AuthRequest::post(const QNetworkRequest& req, const QByteArray& data, int timeout /* = 60*1000*/)
|
||||
{
|
||||
setup(req, QNetworkAccessManager::PostOperation);
|
||||
data_ = data;
|
||||
status_ = Requesting;
|
||||
reply_ = APPLICATION->network()->post(request_, data_);
|
||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||
#endif
|
||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||
@ -80,35 +80,39 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
|
||||
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
|
||||
}
|
||||
|
||||
void AuthRequest::onRequestFinished() {
|
||||
void AuthRequest::onRequestFinished()
|
||||
{
|
||||
if (status_ == Idle) {
|
||||
return;
|
||||
}
|
||||
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
|
||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
||||
return;
|
||||
}
|
||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
finish();
|
||||
}
|
||||
|
||||
void AuthRequest::onRequestError(QNetworkReply::NetworkError error) {
|
||||
void AuthRequest::onRequestError(QNetworkReply::NetworkError error)
|
||||
{
|
||||
qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
|
||||
if (status_ == Idle) {
|
||||
return;
|
||||
}
|
||||
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
|
||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
||||
return;
|
||||
}
|
||||
errorString_ = reply_->errorString();
|
||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
error_ = error;
|
||||
qWarning() << "AuthRequest::onRequestError: Error string: " << errorString_;
|
||||
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_ << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_
|
||||
<< reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
|
||||
// QTimer::singleShot(10, this, SLOT(finish()));
|
||||
}
|
||||
|
||||
void AuthRequest::onSslErrors(QList<QSslError> errors) {
|
||||
void AuthRequest::onSslErrors(QList<QSslError> errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
||||
@ -118,23 +122,25 @@ void AuthRequest::onSslErrors(QList<QSslError> errors) {
|
||||
}
|
||||
}
|
||||
|
||||
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total) {
|
||||
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total)
|
||||
{
|
||||
if (status_ == Idle) {
|
||||
qWarning() << "AuthRequest::onUploadProgress: No pending request";
|
||||
return;
|
||||
}
|
||||
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
|
||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
||||
return;
|
||||
}
|
||||
// Restart timeout because request in progress
|
||||
Katabasis::Reply *o2Reply = timedReplies_.find(reply_);
|
||||
if(o2Reply) {
|
||||
Katabasis::Reply* o2Reply = timedReplies_.find(reply_);
|
||||
if (o2Reply) {
|
||||
o2Reply->start();
|
||||
}
|
||||
emit uploadProgress(uploaded, total);
|
||||
}
|
||||
|
||||
void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) {
|
||||
void AuthRequest::setup(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& verb)
|
||||
{
|
||||
request_ = req;
|
||||
operation_ = operation;
|
||||
url_ = req.url();
|
||||
@ -152,7 +158,8 @@ void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Opera
|
||||
httpStatus_ = 0;
|
||||
}
|
||||
|
||||
void AuthRequest::finish() {
|
||||
void AuthRequest::finish()
|
||||
{
|
||||
QByteArray data;
|
||||
if (status_ == Idle) {
|
||||
qWarning() << "AuthRequest::finish: No pending request";
|
||||
|
@ -1,27 +1,26 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QUrl>
|
||||
#include <QByteArray>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include "katabasis/Reply.h"
|
||||
|
||||
/// Makes authentication requests.
|
||||
class AuthRequest: public QObject {
|
||||
class AuthRequest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuthRequest(QObject *parent = 0);
|
||||
public:
|
||||
explicit AuthRequest(QObject* parent = 0);
|
||||
~AuthRequest();
|
||||
|
||||
public slots:
|
||||
void get(const QNetworkRequest &req, int timeout = 60*1000);
|
||||
void post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
|
||||
public slots:
|
||||
void get(const QNetworkRequest& req, int timeout = 60 * 1000);
|
||||
void post(const QNetworkRequest& req, const QByteArray& data, int timeout = 60 * 1000);
|
||||
|
||||
|
||||
signals:
|
||||
signals:
|
||||
|
||||
/// Emitted when a request has been completed or failed.
|
||||
void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
||||
@ -29,7 +28,7 @@ signals:
|
||||
/// Emitted when an upload has progressed.
|
||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
|
||||
/// Handle request finished.
|
||||
void onRequestFinished();
|
||||
@ -46,25 +45,23 @@ protected slots:
|
||||
/// Handle upload progress.
|
||||
void onUploadProgress(qint64 uploaded, qint64 total);
|
||||
|
||||
public:
|
||||
public:
|
||||
QNetworkReply::NetworkError error_;
|
||||
int httpStatus_ = 0;
|
||||
QString errorString_;
|
||||
|
||||
protected:
|
||||
void setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray());
|
||||
protected:
|
||||
void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray());
|
||||
|
||||
enum Status {
|
||||
Idle, Requesting, ReRequesting
|
||||
};
|
||||
enum Status { Idle, Requesting, ReRequesting };
|
||||
|
||||
QNetworkRequest request_;
|
||||
QByteArray data_;
|
||||
QNetworkReply *reply_;
|
||||
QNetworkReply* reply_;
|
||||
Status status_;
|
||||
QNetworkAccessManager::Operation operation_;
|
||||
QUrl url_;
|
||||
Katabasis::ReplyList timedReplies_;
|
||||
|
||||
QTimer *timer_;
|
||||
QTimer* timer_;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "AuthSession.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
|
||||
QString AuthSession::serializeUserProperties()
|
||||
@ -16,13 +16,11 @@ QString AuthSession::serializeUserProperties()
|
||||
*/
|
||||
QJsonDocument value(userAttrs);
|
||||
return value.toJson(QJsonDocument::Compact);
|
||||
|
||||
}
|
||||
|
||||
bool AuthSession::MakeOffline(QString offline_playername)
|
||||
{
|
||||
if (status != PlayableOffline && status != PlayableOnline)
|
||||
{
|
||||
if (status != PlayableOffline && status != PlayableOnline) {
|
||||
return false;
|
||||
}
|
||||
session = "-";
|
||||
@ -32,7 +30,8 @@ bool AuthSession::MakeOffline(QString offline_playername)
|
||||
return true;
|
||||
}
|
||||
|
||||
void AuthSession::MakeDemo() {
|
||||
void AuthSession::MakeDemo()
|
||||
{
|
||||
player_name = "Player";
|
||||
demo = true;
|
||||
}
|
||||
|
@ -1,22 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QMultiMap>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class MinecraftAccount;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
struct AuthSession
|
||||
{
|
||||
struct AuthSession {
|
||||
bool MakeOffline(QString offline_playername);
|
||||
void MakeDemo();
|
||||
|
||||
QString serializeUserProperties();
|
||||
|
||||
enum Status
|
||||
{
|
||||
enum Status {
|
||||
Undetermined,
|
||||
RequiresOAuth,
|
||||
RequiresPassword,
|
||||
@ -45,7 +43,7 @@ struct AuthSession
|
||||
// Did the user request online mode?
|
||||
bool wants_online = true;
|
||||
|
||||
//Is this a demo session?
|
||||
// Is this a demo session?
|
||||
bool demo = false;
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
#include "AuthStep.h"
|
||||
|
||||
AuthStep::AuthStep(AccountData *data) : QObject(nullptr), m_data(data) {
|
||||
}
|
||||
AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {}
|
||||
|
||||
AuthStep::~AuthStep() noexcept = default;
|
||||
|
||||
|
@ -1,33 +1,33 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include "AccountTask.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "AccountTask.h"
|
||||
|
||||
class AuthStep : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<AuthStep>;
|
||||
|
||||
public:
|
||||
explicit AuthStep(AccountData *data);
|
||||
public:
|
||||
explicit AuthStep(AccountData* data);
|
||||
virtual ~AuthStep() noexcept;
|
||||
|
||||
virtual QString describe() = 0;
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
virtual void perform() = 0;
|
||||
virtual void rehydrate() = 0;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void finished(AccountTaskState resultingState, QString message);
|
||||
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
protected:
|
||||
AccountData *m_data;
|
||||
protected:
|
||||
AccountData* m_data;
|
||||
};
|
||||
|
@ -38,12 +38,12 @@
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QUuid>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -53,28 +53,30 @@
|
||||
#include "flows/Mojang.h"
|
||||
#include "flows/Offline.h"
|
||||
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||
{
|
||||
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
}
|
||||
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json) {
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json)
|
||||
{
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
if(account->data.resumeStateFromV2(json)) {
|
||||
if (account->data.resumeStateFromV2(json)) {
|
||||
return account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
||||
{
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
if(account->data.resumeStateFromV3(json)) {
|
||||
if (account->data.resumeStateFromV3(json)) {
|
||||
return account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username)
|
||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
|
||||
{
|
||||
auto account = makeShared<MinecraftAccount>();
|
||||
account->data.type = AccountType::Mojang;
|
||||
@ -90,7 +92,7 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA()
|
||||
return account;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
|
||||
MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
|
||||
{
|
||||
auto account = makeShared<MinecraftAccount>();
|
||||
account->data.type = AccountType::Offline;
|
||||
@ -107,19 +109,20 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
QJsonObject MinecraftAccount::saveToJson() const
|
||||
{
|
||||
return data.saveState();
|
||||
}
|
||||
|
||||
AccountState MinecraftAccount::accountState() const {
|
||||
AccountState MinecraftAccount::accountState() const
|
||||
{
|
||||
return data.accountState;
|
||||
}
|
||||
|
||||
QPixmap MinecraftAccount::getFace() const {
|
||||
QPixmap MinecraftAccount::getFace() const
|
||||
{
|
||||
QPixmap skinTexture;
|
||||
if(!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) {
|
||||
if (!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) {
|
||||
return QPixmap();
|
||||
}
|
||||
QPixmap skin = QPixmap(8, 8);
|
||||
@ -129,67 +132,68 @@ QPixmap MinecraftAccount::getFace() const {
|
||||
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password)
|
||||
{
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MojangLogin(&data, password));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
||||
{
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MSAInteractive(&data));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline() {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline()
|
||||
{
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new OfflineLogin(&data));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
if(m_currentTask) {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
|
||||
{
|
||||
if (m_currentTask) {
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
if(data.type == AccountType::MSA) {
|
||||
if (data.type == AccountType::MSA) {
|
||||
m_currentTask.reset(new MSASilent(&data));
|
||||
}
|
||||
else if(data.type == AccountType::Offline) {
|
||||
} else if (data.type == AccountType::Offline) {
|
||||
m_currentTask.reset(new OfflineRefresh(&data));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
m_currentTask.reset(new MojangRefresh(&data));
|
||||
}
|
||||
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask() {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask()
|
||||
{
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
|
||||
void MinecraftAccount::authSucceeded()
|
||||
{
|
||||
m_currentTask.reset();
|
||||
@ -206,28 +210,24 @@ void MinecraftAccount::authFailed(QString reason)
|
||||
}
|
||||
case AccountTaskState::STATE_FAILED_SOFT: {
|
||||
// NOTE: this doesn't do much. There was an error of some sort.
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountTaskState::STATE_FAILED_HARD: {
|
||||
if(isMSA()) {
|
||||
if (isMSA()) {
|
||||
data.msaToken.token = QString();
|
||||
data.msaToken.refresh_token = QString();
|
||||
data.msaToken.validity = Katabasis::Validity::None;
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
data.yggdrasilToken.token = QString();
|
||||
data.yggdrasilToken.validity = Katabasis::Validity::None;
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountTaskState::STATE_FAILED_GONE: {
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
emit changed();
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
case AccountTaskState::STATE_SUCCEEDED: {
|
||||
@ -238,21 +238,23 @@ void MinecraftAccount::authFailed(QString reason)
|
||||
emit activityChanged(false);
|
||||
}
|
||||
|
||||
bool MinecraftAccount::isActive() const {
|
||||
bool MinecraftAccount::isActive() const
|
||||
{
|
||||
return !m_currentTask.isNull();
|
||||
}
|
||||
|
||||
bool MinecraftAccount::shouldRefresh() const {
|
||||
bool MinecraftAccount::shouldRefresh() const
|
||||
{
|
||||
/*
|
||||
* Never refresh accounts that are being used by the game, it breaks the game session.
|
||||
* Always refresh accounts that have not been refreshed yet during this session.
|
||||
* Don't refresh broken accounts.
|
||||
* Refresh accounts that would expire in the next 12 hours (fresh token validity is 24 hours).
|
||||
*/
|
||||
if(isInUse()) {
|
||||
if (isInUse()) {
|
||||
return false;
|
||||
}
|
||||
switch(data.validity_) {
|
||||
switch (data.validity_) {
|
||||
case Katabasis::Validity::Certain: {
|
||||
break;
|
||||
}
|
||||
@ -267,7 +269,7 @@ bool MinecraftAccount::shouldRefresh() const {
|
||||
auto issuedTimestamp = data.yggdrasilToken.issueInstant;
|
||||
auto expiresTimestamp = data.yggdrasilToken.notAfter;
|
||||
|
||||
if(!expiresTimestamp.isValid()) {
|
||||
if (!expiresTimestamp.isValid()) {
|
||||
expiresTimestamp = issuedTimestamp.addSecs(24 * 3600);
|
||||
}
|
||||
if (now.secsTo(expiresTimestamp) < (12 * 3600)) {
|
||||
@ -278,14 +280,12 @@ bool MinecraftAccount::shouldRefresh() const {
|
||||
|
||||
void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
{
|
||||
if(ownsMinecraft() && !hasProfile()) {
|
||||
if (ownsMinecraft() && !hasProfile()) {
|
||||
session->status = AuthSession::RequiresProfileSetup;
|
||||
}
|
||||
else {
|
||||
if(session->wants_online) {
|
||||
} else {
|
||||
if (session->wants_online) {
|
||||
session->status = AuthSession::PlayableOnline;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
session->status = AuthSession::PlayableOffline;
|
||||
}
|
||||
}
|
||||
@ -303,12 +303,9 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
session->uuid = data.profileId();
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
session->user_type = typeString();
|
||||
if (!session->access_token.isEmpty())
|
||||
{
|
||||
if (!session->access_token.isEmpty()) {
|
||||
session->session = "token:" + data.accessToken() + ":" + data.profileId();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
session->session = "-";
|
||||
}
|
||||
}
|
||||
@ -316,8 +313,7 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
void MinecraftAccount::decrementUses()
|
||||
{
|
||||
Usable::decrementUses();
|
||||
if(!isInUse())
|
||||
{
|
||||
if (!isInUse()) {
|
||||
emit changed();
|
||||
// FIXME: we now need a better way to identify accounts...
|
||||
qWarning() << "Profile" << data.profileId() << "is no longer in use.";
|
||||
@ -328,39 +324,31 @@ void MinecraftAccount::incrementUses()
|
||||
{
|
||||
bool wasInUse = isInUse();
|
||||
Usable::incrementUses();
|
||||
if(!wasInUse)
|
||||
{
|
||||
if (!wasInUse) {
|
||||
emit changed();
|
||||
// FIXME: we now need a better way to identify accounts...
|
||||
qWarning() << "Profile" << data.profileId() << "is now in use.";
|
||||
}
|
||||
}
|
||||
|
||||
QUuid MinecraftAccount::uuidFromUsername(QString username) {
|
||||
QUuid MinecraftAccount::uuidFromUsername(QString username)
|
||||
{
|
||||
auto input = QString("OfflinePlayer:%1").arg(username).toUtf8();
|
||||
|
||||
// basically a reimplementation of Java's UUID#nameUUIDFromBytes
|
||||
QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
auto bOr = [](QByteArray& array, int index, char value) {
|
||||
array[index] = array.at(index) | value;
|
||||
};
|
||||
auto bAnd = [](QByteArray& array, int index, char value) {
|
||||
array[index] = array.at(index) & value;
|
||||
};
|
||||
auto bOr = [](QByteArray& array, int index, char value) { array[index] = array.at(index) | value; };
|
||||
auto bAnd = [](QByteArray& array, int index, char value) { array[index] = array.at(index) & value; };
|
||||
#else
|
||||
auto bOr = [](QByteArray& array, qsizetype index, char value) {
|
||||
array[index] |= value;
|
||||
};
|
||||
auto bAnd = [](QByteArray& array, qsizetype index, char value) {
|
||||
array[index] &= value;
|
||||
};
|
||||
auto bOr = [](QByteArray& array, qsizetype index, char value) { array[index] |= value; };
|
||||
auto bAnd = [](QByteArray& array, qsizetype index, char value) { array[index] &= value; };
|
||||
#endif
|
||||
bAnd(digest, 6, (char) 0x0f); // clear version
|
||||
bOr(digest, 6, (char) 0x30); // set to version 3
|
||||
bAnd(digest, 8, (char) 0x3f); // clear variant
|
||||
bOr(digest, 8, (char) 0x80); // set to IETF variant
|
||||
bAnd(digest, 6, (char)0x0f); // clear version
|
||||
bOr(digest, 6, (char)0x30); // set to version 3
|
||||
bAnd(digest, 8, (char)0x3f); // clear variant
|
||||
bOr(digest, 8, (char)0x80); // set to IETF variant
|
||||
|
||||
return QUuid::fromRfc4122(digest);
|
||||
}
|
||||
|
@ -35,20 +35,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QJsonObject>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "AuthSession.h"
|
||||
#include "Usable.h"
|
||||
#include "AccountData.h"
|
||||
#include "AuthSession.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "Usable.h"
|
||||
|
||||
class Task;
|
||||
class AccountTask;
|
||||
@ -64,8 +64,7 @@ Q_DECLARE_METATYPE(MinecraftAccountPtr)
|
||||
* but we might as well add some things for it in Prism Launcher right now so
|
||||
* we don't have to rip the code to pieces to add it later.
|
||||
*/
|
||||
struct AccountProfile
|
||||
{
|
||||
struct AccountProfile {
|
||||
QString id;
|
||||
QString name;
|
||||
bool legacy;
|
||||
@ -77,34 +76,30 @@ struct AccountProfile
|
||||
* Said information may include things such as that account's username, client token, and access
|
||||
* token if the user chose to stay logged in.
|
||||
*/
|
||||
class MinecraftAccount :
|
||||
public QObject,
|
||||
public Usable
|
||||
{
|
||||
class MinecraftAccount : public QObject, public Usable {
|
||||
Q_OBJECT
|
||||
public: /* construction */
|
||||
public: /* construction */
|
||||
//! Do not copy accounts. ever.
|
||||
explicit MinecraftAccount(const MinecraftAccount &other, QObject *parent) = delete;
|
||||
explicit MinecraftAccount(const MinecraftAccount& other, QObject* parent) = delete;
|
||||
|
||||
//! Default constructor
|
||||
explicit MinecraftAccount(QObject *parent = 0);
|
||||
explicit MinecraftAccount(QObject* parent = 0);
|
||||
|
||||
static MinecraftAccountPtr createFromUsername(const QString &username);
|
||||
static MinecraftAccountPtr createFromUsername(const QString& username);
|
||||
|
||||
static MinecraftAccountPtr createBlankMSA();
|
||||
|
||||
static MinecraftAccountPtr createOffline(const QString &username);
|
||||
static MinecraftAccountPtr createOffline(const QString& username);
|
||||
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject& json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
|
||||
|
||||
static QUuid uuidFromUsername(QString username);
|
||||
|
||||
//! Saves a MinecraftAccount to a JSON object and returns it.
|
||||
QJsonObject saveToJson() const;
|
||||
|
||||
public: /* manipulation */
|
||||
|
||||
public: /* manipulation */
|
||||
/**
|
||||
* Attempt to login. Empty password means we use the token.
|
||||
* If the attempt fails because we already are performing some task, it returns false.
|
||||
@ -119,70 +114,46 @@ public: /* manipulation */
|
||||
|
||||
shared_qobject_ptr<AccountTask> currentTask();
|
||||
|
||||
public: /* queries */
|
||||
QString internalId() const {
|
||||
return data.internalId;
|
||||
}
|
||||
public: /* queries */
|
||||
QString internalId() const { return data.internalId; }
|
||||
|
||||
QString accountDisplayString() const {
|
||||
return data.accountDisplayString();
|
||||
}
|
||||
QString accountDisplayString() const { return data.accountDisplayString(); }
|
||||
|
||||
QString mojangUserName() const {
|
||||
return data.userName();
|
||||
}
|
||||
QString mojangUserName() const { return data.userName(); }
|
||||
|
||||
QString accessToken() const {
|
||||
return data.accessToken();
|
||||
}
|
||||
QString accessToken() const { return data.accessToken(); }
|
||||
|
||||
QString profileId() const {
|
||||
return data.profileId();
|
||||
}
|
||||
QString profileId() const { return data.profileId(); }
|
||||
|
||||
QString profileName() const {
|
||||
return data.profileName();
|
||||
}
|
||||
QString profileName() const { return data.profileName(); }
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
bool canMigrate() const {
|
||||
return data.canMigrateToMSA;
|
||||
}
|
||||
bool canMigrate() const { return data.canMigrateToMSA; }
|
||||
|
||||
bool isMSA() const {
|
||||
return data.type == AccountType::MSA;
|
||||
}
|
||||
bool isMSA() const { return data.type == AccountType::MSA; }
|
||||
|
||||
bool isOffline() const {
|
||||
return data.type == AccountType::Offline;
|
||||
}
|
||||
bool isOffline() const { return data.type == AccountType::Offline; }
|
||||
|
||||
bool ownsMinecraft() const {
|
||||
return data.minecraftEntitlement.ownsMinecraft;
|
||||
}
|
||||
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
|
||||
|
||||
bool hasProfile() const {
|
||||
return data.profileId().size() != 0;
|
||||
}
|
||||
bool hasProfile() const { return data.profileId().size() != 0; }
|
||||
|
||||
QString typeString() const {
|
||||
switch(data.type) {
|
||||
QString typeString() const
|
||||
{
|
||||
switch (data.type) {
|
||||
case AccountType::Mojang: {
|
||||
if(data.legacy) {
|
||||
if (data.legacy) {
|
||||
return "legacy";
|
||||
}
|
||||
return "mojang";
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountType::MSA: {
|
||||
return "msa";
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountType::Offline: {
|
||||
return "offline";
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
default: {
|
||||
return "unknown";
|
||||
}
|
||||
@ -194,19 +165,15 @@ public: /* queries */
|
||||
//! Returns the current state of the account
|
||||
AccountState accountState() const;
|
||||
|
||||
AccountData * accountData() {
|
||||
return &data;
|
||||
}
|
||||
AccountData* accountData() { return &data; }
|
||||
|
||||
bool shouldRefresh() const;
|
||||
|
||||
void fillSession(AuthSessionPtr session);
|
||||
|
||||
QString lastError() const {
|
||||
return data.lastError();
|
||||
}
|
||||
QString lastError() const { return data.lastError(); }
|
||||
|
||||
signals:
|
||||
signals:
|
||||
/**
|
||||
* This signal is emitted when the account changes
|
||||
*/
|
||||
@ -216,20 +183,17 @@ signals:
|
||||
|
||||
// TODO: better signalling for the various possible state changes - especially errors
|
||||
|
||||
protected: /* variables */
|
||||
protected: /* variables */
|
||||
AccountData data;
|
||||
|
||||
// current task we are executing here
|
||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
||||
|
||||
protected: /* methods */
|
||||
|
||||
protected: /* methods */
|
||||
void incrementUses() override;
|
||||
void decrementUses() override;
|
||||
|
||||
private
|
||||
slots:
|
||||
private slots:
|
||||
void authSucceeded();
|
||||
void authFailed(QString reason);
|
||||
};
|
||||
|
||||
|
@ -2,46 +2,51 @@
|
||||
#include "Json.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace Parsers {
|
||||
|
||||
bool getDateTime(QJsonValue value, QDateTime & out) {
|
||||
if(!value.isString()) {
|
||||
bool getDateTime(QJsonValue value, QDateTime& out)
|
||||
{
|
||||
if (!value.isString()) {
|
||||
return false;
|
||||
}
|
||||
out = QDateTime::fromString(value.toString(), Qt::ISODate);
|
||||
return out.isValid();
|
||||
}
|
||||
|
||||
bool getString(QJsonValue value, QString & out) {
|
||||
if(!value.isString()) {
|
||||
bool getString(QJsonValue value, QString& out)
|
||||
{
|
||||
if (!value.isString()) {
|
||||
return false;
|
||||
}
|
||||
out = value.toString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getNumber(QJsonValue value, double & out) {
|
||||
if(!value.isDouble()) {
|
||||
bool getNumber(QJsonValue value, double& out)
|
||||
{
|
||||
if (!value.isDouble()) {
|
||||
return false;
|
||||
}
|
||||
out = value.toDouble();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getNumber(QJsonValue value, int64_t & out) {
|
||||
if(!value.isDouble()) {
|
||||
bool getNumber(QJsonValue value, int64_t& out)
|
||||
{
|
||||
if (!value.isDouble()) {
|
||||
return false;
|
||||
}
|
||||
out = (int64_t) value.toDouble();
|
||||
out = (int64_t)value.toDouble();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getBool(QJsonValue value, bool & out) {
|
||||
if(!value.isBool()) {
|
||||
bool getBool(QJsonValue value, bool& out)
|
||||
{
|
||||
if (!value.isBool()) {
|
||||
return false;
|
||||
}
|
||||
out = value.toBool();
|
||||
@ -74,49 +79,50 @@ bool getBool(QJsonValue value, bool & out) {
|
||||
// 2148916238 = child account not linked to a family
|
||||
*/
|
||||
|
||||
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
|
||||
qDebug() << "Parsing" << name <<":";
|
||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name)
|
||||
{
|
||||
qDebug() << "Parsing" << name << ":";
|
||||
qCDebug(authCredentials()) << data;
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
|
||||
if (!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
|
||||
qWarning() << "User IssueInstant is not a timestamp";
|
||||
return false;
|
||||
}
|
||||
if(!getDateTime(obj.value("NotAfter"), output.notAfter)) {
|
||||
if (!getDateTime(obj.value("NotAfter"), output.notAfter)) {
|
||||
qWarning() << "User NotAfter is not a timestamp";
|
||||
return false;
|
||||
}
|
||||
if(!getString(obj.value("Token"), output.token)) {
|
||||
if (!getString(obj.value("Token"), output.token)) {
|
||||
qWarning() << "User Token is not a string";
|
||||
return false;
|
||||
}
|
||||
auto arrayVal = obj.value("DisplayClaims").toObject().value("xui");
|
||||
if(!arrayVal.isArray()) {
|
||||
if (!arrayVal.isArray()) {
|
||||
qWarning() << "Missing xui claims array";
|
||||
return false;
|
||||
}
|
||||
bool foundUHS = false;
|
||||
for(auto item: arrayVal.toArray()) {
|
||||
if(!item.isObject()) {
|
||||
for (auto item : arrayVal.toArray()) {
|
||||
if (!item.isObject()) {
|
||||
continue;
|
||||
}
|
||||
auto obj_ = item.toObject();
|
||||
if(obj_.contains("uhs")) {
|
||||
if (obj_.contains("uhs")) {
|
||||
foundUHS = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
// consume all 'display claims' ... whatever that means
|
||||
for(auto iter = obj_.begin(); iter != obj_.end(); iter++) {
|
||||
for (auto iter = obj_.begin(); iter != obj_.end(); iter++) {
|
||||
QString claim;
|
||||
if(!getString(obj_.value(iter.key()), claim)) {
|
||||
if (!getString(obj_.value(iter.key()), claim)) {
|
||||
qWarning() << "display claim " << iter.key() << " is not a string...";
|
||||
return false;
|
||||
}
|
||||
@ -125,7 +131,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
|
||||
|
||||
break;
|
||||
}
|
||||
if(!foundUHS) {
|
||||
if (!foundUHS) {
|
||||
qWarning() << "Missing uhs";
|
||||
return false;
|
||||
}
|
||||
@ -134,46 +140,47 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
||||
{
|
||||
qDebug() << "Parsing Minecraft profile...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
if(!getString(obj.value("id"), output.id)) {
|
||||
if (!getString(obj.value("id"), output.id)) {
|
||||
qWarning() << "Minecraft profile id is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!getString(obj.value("name"), output.name)) {
|
||||
if (!getString(obj.value("name"), output.name)) {
|
||||
qWarning() << "Minecraft profile name is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto skinsArray = obj.value("skins").toArray();
|
||||
for(auto skin: skinsArray) {
|
||||
for (auto skin : skinsArray) {
|
||||
auto skinObj = skin.toObject();
|
||||
Skin skinOut;
|
||||
if(!getString(skinObj.value("id"), skinOut.id)) {
|
||||
if (!getString(skinObj.value("id"), skinOut.id)) {
|
||||
continue;
|
||||
}
|
||||
QString state;
|
||||
if(!getString(skinObj.value("state"), state)) {
|
||||
if (!getString(skinObj.value("state"), state)) {
|
||||
continue;
|
||||
}
|
||||
if(state != "ACTIVE") {
|
||||
if (state != "ACTIVE") {
|
||||
continue;
|
||||
}
|
||||
if(!getString(skinObj.value("url"), skinOut.url)) {
|
||||
if (!getString(skinObj.value("url"), skinOut.url)) {
|
||||
continue;
|
||||
}
|
||||
if(!getString(skinObj.value("variant"), skinOut.variant)) {
|
||||
if (!getString(skinObj.value("variant"), skinOut.variant)) {
|
||||
continue;
|
||||
}
|
||||
// we deal with only the active skin
|
||||
@ -183,23 +190,23 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
auto capesArray = obj.value("capes").toArray();
|
||||
|
||||
QString currentCape;
|
||||
for(auto cape: capesArray) {
|
||||
for (auto cape : capesArray) {
|
||||
auto capeObj = cape.toObject();
|
||||
Cape capeOut;
|
||||
if(!getString(capeObj.value("id"), capeOut.id)) {
|
||||
if (!getString(capeObj.value("id"), capeOut.id)) {
|
||||
continue;
|
||||
}
|
||||
QString state;
|
||||
if(!getString(capeObj.value("state"), state)) {
|
||||
if (!getString(capeObj.value("state"), state)) {
|
||||
continue;
|
||||
}
|
||||
if(state == "ACTIVE") {
|
||||
if (state == "ACTIVE") {
|
||||
currentCape = capeOut.id;
|
||||
}
|
||||
if(!getString(capeObj.value("url"), capeOut.url)) {
|
||||
if (!getString(capeObj.value("url"), capeOut.url)) {
|
||||
continue;
|
||||
}
|
||||
if(!getString(capeObj.value("alias"), capeOut.alias)) {
|
||||
if (!getString(capeObj.value("alias"), capeOut.alias)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -211,30 +218,33 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
|
||||
// they are needed because the session server doesn't return skin urls for default skins
|
||||
static const QString SKIN_URL_STEVE = "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
|
||||
static const QString SKIN_URL_ALEX = "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
|
||||
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
|
||||
// they are needed because the session server doesn't return skin urls for default skins
|
||||
static const QString SKIN_URL_STEVE =
|
||||
"http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
|
||||
static const QString SKIN_URL_ALEX =
|
||||
"http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
|
||||
|
||||
bool isDefaultModelSteve(QString uuid) {
|
||||
// need to calculate *Java* hashCode of UUID
|
||||
// if number is even, skin/model is steve, otherwise it is alex
|
||||
bool isDefaultModelSteve(QString uuid)
|
||||
{
|
||||
// need to calculate *Java* hashCode of UUID
|
||||
// if number is even, skin/model is steve, otherwise it is alex
|
||||
|
||||
// just in case dashes are in the id
|
||||
uuid.remove('-');
|
||||
// just in case dashes are in the id
|
||||
uuid.remove('-');
|
||||
|
||||
if (uuid.size() != 32) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// qulonglong is guaranteed to be 64 bits
|
||||
// we need to use unsigned numbers to guarantee truncation below
|
||||
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
|
||||
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
|
||||
qulonglong xored = most ^ least;
|
||||
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
|
||||
if (uuid.size() != 32) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// qulonglong is guaranteed to be 64 bits
|
||||
// we need to use unsigned numbers to guarantee truncation below
|
||||
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
|
||||
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
|
||||
qulonglong xored = most ^ least;
|
||||
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
Uses session server for skin/cape lookup instead of profile,
|
||||
@ -270,31 +280,32 @@ decoded base64 "value":
|
||||
}
|
||||
*/
|
||||
|
||||
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
||||
{
|
||||
qDebug() << "Parsing Minecraft profile...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = Json::requireObject(doc, "mojang minecraft profile");
|
||||
if(!getString(obj.value("id"), output.id)) {
|
||||
if (!getString(obj.value("id"), output.id)) {
|
||||
qWarning() << "Minecraft profile id is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!getString(obj.value("name"), output.name)) {
|
||||
if (!getString(obj.value("name"), output.name)) {
|
||||
qWarning() << "Minecraft profile name is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto propsArray = obj.value("properties").toArray();
|
||||
QByteArray texturePayload;
|
||||
for( auto p : propsArray) {
|
||||
for (auto p : propsArray) {
|
||||
auto pObj = p.toObject();
|
||||
auto name = pObj.value("name");
|
||||
if (!name.isString() || name.toString() != "textures") {
|
||||
@ -321,7 +332,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
}
|
||||
|
||||
doc = QJsonDocument::fromJson(texturePayload, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
@ -357,8 +368,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
// might not be present
|
||||
getString(meta.value("model"), skinOut.variant);
|
||||
}
|
||||
}
|
||||
else if (idx.key() == "CAPE") {
|
||||
} else if (idx.key() == "CAPE") {
|
||||
auto cape = idx->toObject();
|
||||
if (!getString(cape.value("url"), capeOut.url)) {
|
||||
qWarning() << "Cape url is not a string";
|
||||
@ -374,7 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
|
||||
output.skin = skinOut;
|
||||
if (capeOut.alias == "cape") {
|
||||
output.capes = QMap<QString, Cape>({{capeOut.alias, capeOut}});
|
||||
output.capes = QMap<QString, Cape>({ { capeOut.alias, capeOut } });
|
||||
output.currentCape = capeOut.alias;
|
||||
}
|
||||
|
||||
@ -382,13 +392,14 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
|
||||
bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output)
|
||||
{
|
||||
qDebug() << "Parsing Minecraft entitlements...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
@ -398,16 +409,16 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
||||
output.ownsMinecraft = false;
|
||||
|
||||
auto itemsArray = obj.value("items").toArray();
|
||||
for(auto item: itemsArray) {
|
||||
for (auto item : itemsArray) {
|
||||
auto itemObj = item.toObject();
|
||||
QString name;
|
||||
if(!getString(itemObj.value("name"), name)) {
|
||||
if (!getString(itemObj.value("name"), name)) {
|
||||
continue;
|
||||
}
|
||||
if(name == "game_minecraft") {
|
||||
if (name == "game_minecraft") {
|
||||
output.canPlayMinecraft = true;
|
||||
}
|
||||
if(name == "product_minecraft") {
|
||||
if (name == "product_minecraft") {
|
||||
output.ownsMinecraft = true;
|
||||
}
|
||||
}
|
||||
@ -415,47 +426,50 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseRolloutResponse(QByteArray & data, bool& result) {
|
||||
bool parseRolloutResponse(QByteArray& data, bool& result)
|
||||
{
|
||||
qDebug() << "Parsing Rollout response...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: " << jsonError.errorString();
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: "
|
||||
<< jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
QString feature;
|
||||
if(!getString(obj.value("feature"), feature)) {
|
||||
if (!getString(obj.value("feature"), feature)) {
|
||||
qWarning() << "Rollout feature is not a string";
|
||||
return false;
|
||||
}
|
||||
if(feature != "msamigration") {
|
||||
if (feature != "msamigration") {
|
||||
qWarning() << "Rollout feature is not what we expected (msamigration), but is instead \"" << feature << "\"";
|
||||
return false;
|
||||
}
|
||||
if(!getBool(obj.value("rollout"), result)) {
|
||||
if (!getBool(obj.value("rollout"), result)) {
|
||||
qWarning() << "Rollout feature is not a string";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
qDebug() << "Parsing Mojang response...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
double expires_in = 0;
|
||||
if(!getNumber(obj.value("expires_in"), expires_in)) {
|
||||
if (!getNumber(obj.value("expires_in"), expires_in)) {
|
||||
qWarning() << "expires_in is not a valid number";
|
||||
return false;
|
||||
}
|
||||
@ -464,13 +478,13 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
output.notAfter = currentTime.addSecs(expires_in);
|
||||
|
||||
QString username;
|
||||
if(!getString(obj.value("username"), username)) {
|
||||
if (!getString(obj.value("username"), username)) {
|
||||
qWarning() << "username is not valid";
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: it's a JWT... validate it?
|
||||
if(!getString(obj.value("access_token"), output.token)) {
|
||||
if (!getString(obj.value("access_token"), output.token)) {
|
||||
qWarning() << "access_token is not valid";
|
||||
return false;
|
||||
}
|
||||
@ -479,4 +493,4 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Parsers
|
||||
|
@ -2,19 +2,18 @@
|
||||
|
||||
#include "AccountData.h"
|
||||
|
||||
namespace Parsers
|
||||
{
|
||||
bool getDateTime(QJsonValue value, QDateTime & out);
|
||||
bool getString(QJsonValue value, QString & out);
|
||||
bool getNumber(QJsonValue value, double & out);
|
||||
bool getNumber(QJsonValue value, int64_t & out);
|
||||
bool getBool(QJsonValue value, bool & out);
|
||||
namespace Parsers {
|
||||
bool getDateTime(QJsonValue value, QDateTime& out);
|
||||
bool getString(QJsonValue value, QString& out);
|
||||
bool getNumber(QJsonValue value, double& out);
|
||||
bool getNumber(QJsonValue value, int64_t& out);
|
||||
bool getBool(QJsonValue value, bool& out);
|
||||
|
||||
bool parseXTokenResponse(QByteArray &data, Katabasis::Token &output, QString name);
|
||||
bool parseMojangResponse(QByteArray &data, Katabasis::Token &output);
|
||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name);
|
||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output);
|
||||
|
||||
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
|
||||
bool parseMinecraftProfileMojang(QByteArray &data, MinecraftProfile &output);
|
||||
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
|
||||
bool parseRolloutResponse(QByteArray &data, bool& result);
|
||||
}
|
||||
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
|
||||
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
|
||||
bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output);
|
||||
bool parseRolloutResponse(QByteArray& data, bool& result);
|
||||
} // namespace Parsers
|
||||
|
@ -16,24 +16,24 @@
|
||||
#include "Yggdrasil.h"
|
||||
#include "AccountData.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
|
||||
: AccountTask(data, parent)
|
||||
Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_CREATED);
|
||||
}
|
||||
|
||||
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
|
||||
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_WORKING);
|
||||
|
||||
QNetworkRequest netRequest(endpoint);
|
||||
@ -52,10 +52,10 @@ void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
|
||||
connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
|
||||
}
|
||||
|
||||
void Yggdrasil::executeTask() {
|
||||
}
|
||||
void Yggdrasil::executeTask() {}
|
||||
|
||||
void Yggdrasil::refresh() {
|
||||
void Yggdrasil::refresh()
|
||||
{
|
||||
start();
|
||||
/*
|
||||
* {
|
||||
@ -90,7 +90,8 @@ void Yggdrasil::refresh() {
|
||||
sendRequest(reqUrl, requestData);
|
||||
}
|
||||
|
||||
void Yggdrasil::login(QString password) {
|
||||
void Yggdrasil::login(QString password)
|
||||
{
|
||||
start();
|
||||
/*
|
||||
* {
|
||||
@ -136,20 +137,21 @@ void Yggdrasil::login(QString password) {
|
||||
sendRequest(reqUrl, requestData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Yggdrasil::refreshTimers(qint64, qint64) {
|
||||
void Yggdrasil::refreshTimers(qint64, qint64)
|
||||
{
|
||||
timeout_keeper.stop();
|
||||
timeout_keeper.start(timeout_max);
|
||||
progress(count = 0, timeout_max);
|
||||
}
|
||||
|
||||
void Yggdrasil::heartbeat() {
|
||||
void Yggdrasil::heartbeat()
|
||||
{
|
||||
count += time_step;
|
||||
progress(count, timeout_max);
|
||||
}
|
||||
|
||||
bool Yggdrasil::abort() {
|
||||
bool Yggdrasil::abort()
|
||||
{
|
||||
progress(timeout_max, timeout_max);
|
||||
// TODO: actually use this in a meaningful way
|
||||
m_aborted = Yggdrasil::BY_USER;
|
||||
@ -157,14 +159,16 @@ bool Yggdrasil::abort() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Yggdrasil::abortByTimeout() {
|
||||
void Yggdrasil::abortByTimeout()
|
||||
{
|
||||
progress(timeout_max, timeout_max);
|
||||
// TODO: actually use this in a meaningful way
|
||||
m_aborted = Yggdrasil::BY_TIMEOUT;
|
||||
m_netReply->abort();
|
||||
}
|
||||
|
||||
void Yggdrasil::sslErrors(QList<QSslError> errors) {
|
||||
void Yggdrasil::sslErrors(QList<QSslError> errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
||||
@ -174,7 +178,8 @@ void Yggdrasil::sslErrors(QList<QSslError> errors) {
|
||||
}
|
||||
}
|
||||
|
||||
void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
void Yggdrasil::processResponse(QJsonObject responseData)
|
||||
{
|
||||
// Read the response data. We need to get the client token, access token, and the selected
|
||||
// profile.
|
||||
qDebug() << "Processing authentication response.";
|
||||
@ -188,11 +193,11 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
|
||||
return;
|
||||
}
|
||||
if(m_data->clientToken().isEmpty()) {
|
||||
if (m_data->clientToken().isEmpty()) {
|
||||
m_data->setClientToken(clientToken);
|
||||
}
|
||||
else if(clientToken != m_data->clientToken()) {
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
|
||||
} else if (clientToken != m_data->clientToken()) {
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD,
|
||||
tr("Authentication server attempted to change the client token. This isn't supported."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -220,8 +225,7 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
|
||||
if (i.key() == "name" && i.value().isString()) {
|
||||
m_data->minecraftProfile.name = i->toString();
|
||||
}
|
||||
else if (i.key() == "id" && i.value().isString()) {
|
||||
} else if (i.key() == "id" && i.value().isString()) {
|
||||
m_data->minecraftProfile.id = i->toString();
|
||||
}
|
||||
}
|
||||
@ -237,50 +241,43 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
changeState(AccountTaskState::STATE_SUCCEEDED);
|
||||
}
|
||||
|
||||
void Yggdrasil::processReply() {
|
||||
void Yggdrasil::processReply()
|
||||
{
|
||||
changeState(AccountTaskState::STATE_WORKING);
|
||||
|
||||
switch (m_netReply->error())
|
||||
{
|
||||
case QNetworkReply::NoError:
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
|
||||
return;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
|
||||
return;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr(
|
||||
"<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
|
||||
"<ul>"
|
||||
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
|
||||
"<li>Some device on your network is interfering with SSL traffic. In that case, "
|
||||
"you have bigger worries than Minecraft not starting.</li>"
|
||||
"<li>Possibly something else. Check the log file for details</li>"
|
||||
"</ul>"
|
||||
)
|
||||
);
|
||||
return;
|
||||
// used for invalid credentials and similar errors. Fall through.
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError: {
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Authentication operation failed due to a network error: %1 (%2)").arg(m_netReply->errorString()).arg(m_netReply->error())
|
||||
);
|
||||
return;
|
||||
switch (m_netReply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
|
||||
return;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
|
||||
return;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
|
||||
"<ul>"
|
||||
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
|
||||
"<li>Some device on your network is interfering with SSL traffic. In that case, "
|
||||
"you have bigger worries than Minecraft not starting.</li>"
|
||||
"<li>Possibly something else. Check the log file for details</li>"
|
||||
"</ul>"));
|
||||
return;
|
||||
// used for invalid credentials and similar errors. Fall through.
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError: {
|
||||
changeState(AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
|
||||
.arg(m_netReply->errorString())
|
||||
.arg(m_netReply->error()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to parse the response regardless of the response code.
|
||||
@ -299,12 +296,11 @@ void Yggdrasil::processReply() {
|
||||
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
|
||||
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
|
||||
return;
|
||||
}
|
||||
else {
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)
|
||||
);
|
||||
} else {
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
|
||||
.arg(jsonError.errorString())
|
||||
.arg(jsonError.offset));
|
||||
qCritical() << replyData;
|
||||
}
|
||||
return;
|
||||
@ -320,34 +316,26 @@ void Yggdrasil::processReply() {
|
||||
// stuff there.
|
||||
qDebug() << "The request failed, but the server gave us an error message. Processing error.";
|
||||
processError(doc.object());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// The server didn't say anything regarding the error. Give the user an unknown
|
||||
// error.
|
||||
qDebug() << "The request failed and the server gave no error message. Unknown error.";
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString())
|
||||
);
|
||||
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
void Yggdrasil::processError(QJsonObject responseData) {
|
||||
void Yggdrasil::processError(QJsonObject responseData)
|
||||
{
|
||||
QJsonValue errorVal = responseData.value("error");
|
||||
QJsonValue errorMessageValue = responseData.value("errorMessage");
|
||||
QJsonValue causeVal = responseData.value("cause");
|
||||
|
||||
if (errorVal.isString() && errorMessageValue.isString()) {
|
||||
m_error = std::shared_ptr<Error>(
|
||||
new Error {
|
||||
errorVal.toString(""),
|
||||
errorMessageValue.toString(""),
|
||||
causeVal.toString("")
|
||||
}
|
||||
);
|
||||
m_error = std::shared_ptr<Error>(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Error is not in standard format. Don't set m_error and return unknown error.
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
|
||||
}
|
||||
|
@ -17,10 +17,10 @@
|
||||
|
||||
#include "AccountTask.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <qsslerror.h>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
@ -30,35 +30,25 @@ class QNetworkReply;
|
||||
/**
|
||||
* A Yggdrasil task is a task that performs an operation on a given mojang account.
|
||||
*/
|
||||
class Yggdrasil : public AccountTask
|
||||
{
|
||||
class Yggdrasil : public AccountTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Yggdrasil(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit Yggdrasil(AccountData* data, QObject* parent = 0);
|
||||
virtual ~Yggdrasil() = default;
|
||||
|
||||
void refresh();
|
||||
void login(QString password);
|
||||
|
||||
struct Error
|
||||
{
|
||||
struct Error {
|
||||
QString m_errorMessageShort;
|
||||
QString m_errorMessageVerbose;
|
||||
QString m_cause;
|
||||
};
|
||||
std::shared_ptr<Error> m_error;
|
||||
|
||||
enum AbortedBy
|
||||
{
|
||||
BY_NOTHING,
|
||||
BY_USER,
|
||||
BY_TIMEOUT
|
||||
} m_aborted = BY_NOTHING;
|
||||
enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
||||
/**
|
||||
@ -78,24 +68,24 @@ protected:
|
||||
*/
|
||||
virtual void processError(QJsonObject responseData);
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
void processReply();
|
||||
void refreshTimers(qint64, qint64);
|
||||
void heartbeat();
|
||||
void sslErrors(QList<QSslError>);
|
||||
void abortByTimeout();
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
virtual bool abort() override;
|
||||
|
||||
private:
|
||||
private:
|
||||
void sendRequest(QUrl endpoint, QByteArray content);
|
||||
|
||||
protected:
|
||||
QNetworkReply *m_netReply = nullptr;
|
||||
protected:
|
||||
QNetworkReply* m_netReply = nullptr;
|
||||
QTimer timeout_keeper;
|
||||
QTimer counter;
|
||||
int count = 0; // num msec since time reset
|
||||
int count = 0; // num msec since time reset
|
||||
|
||||
const int timeout_max = 30000;
|
||||
const int time_step = 50;
|
||||
|
@ -1,36 +1,33 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "AuthFlow.h"
|
||||
#include "katabasis/Globals.h"
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
AuthFlow::AuthFlow(AccountData * data, QObject *parent) :
|
||||
AccountTask(data, parent)
|
||||
AuthFlow::AuthFlow(AccountData* data, QObject* parent) : AccountTask(data, parent) {}
|
||||
|
||||
void AuthFlow::succeed()
|
||||
{
|
||||
}
|
||||
|
||||
void AuthFlow::succeed() {
|
||||
m_data->validity_ = Katabasis::Validity::Certain;
|
||||
changeState(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
tr("Finished all authentication steps")
|
||||
);
|
||||
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
||||
}
|
||||
|
||||
void AuthFlow::executeTask() {
|
||||
if(m_currentStep) {
|
||||
void AuthFlow::executeTask()
|
||||
{
|
||||
if (m_currentStep) {
|
||||
return;
|
||||
}
|
||||
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
||||
nextStep();
|
||||
}
|
||||
|
||||
void AuthFlow::nextStep() {
|
||||
if(m_steps.size() == 0) {
|
||||
void AuthFlow::nextStep()
|
||||
{
|
||||
if (m_steps.size() == 0) {
|
||||
// we got to the end without an incident... assume this is all.
|
||||
m_currentStep.reset();
|
||||
succeed();
|
||||
@ -46,15 +43,13 @@ void AuthFlow::nextStep() {
|
||||
m_currentStep->perform();
|
||||
}
|
||||
|
||||
|
||||
QString AuthFlow::getStateMessage() const {
|
||||
switch (m_taskState)
|
||||
{
|
||||
QString AuthFlow::getStateMessage() const
|
||||
{
|
||||
switch (m_taskState) {
|
||||
case AccountTaskState::STATE_WORKING: {
|
||||
if(m_currentStep) {
|
||||
if (m_currentStep) {
|
||||
return m_currentStep->describe();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return tr("Working...");
|
||||
}
|
||||
}
|
||||
@ -64,8 +59,9 @@ QString AuthFlow::getStateMessage() const {
|
||||
}
|
||||
}
|
||||
|
||||
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) {
|
||||
if(changeState(resultingState, message)) {
|
||||
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
||||
{
|
||||
if (changeState(resultingState, message)) {
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QSet>
|
||||
#include <QNetworkReply>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include <katabasis/DeviceFlow.h>
|
||||
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
|
||||
class AuthFlow : public AccountTask
|
||||
{
|
||||
class AuthFlow : public AccountTask {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuthFlow(AccountData * data, QObject *parent = 0);
|
||||
public:
|
||||
explicit AuthFlow(AccountData* data, QObject* parent = 0);
|
||||
|
||||
Katabasis::Validity validity() {
|
||||
return m_data->validity_;
|
||||
};
|
||||
Katabasis::Validity validity() { return m_data->validity_; };
|
||||
|
||||
QString getStateMessage() const override;
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void activityChanged(Katabasis::Activity activity);
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void stepFinished(AccountTaskState resultingState, QString message);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void succeed();
|
||||
void nextStep();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
QList<AuthStep::Ptr> m_steps;
|
||||
AuthStep::Ptr m_currentStep;
|
||||
};
|
||||
|
@ -1,15 +1,16 @@
|
||||
#include "MSA.h"
|
||||
|
||||
#include "minecraft/auth/steps/MSAStep.h"
|
||||
#include "minecraft/auth/steps/XboxUserStep.h"
|
||||
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||
#include "minecraft/auth/steps/XboxProfileStep.h"
|
||||
#include "minecraft/auth/steps/EntitlementsStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||
#include "minecraft/auth/steps/MSAStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
||||
#include "minecraft/auth/steps/XboxProfileStep.h"
|
||||
#include "minecraft/auth/steps/XboxUserStep.h"
|
||||
|
||||
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) {
|
||||
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Refresh));
|
||||
m_steps.append(makeShared<XboxUserStep>(m_data));
|
||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||
@ -21,10 +22,8 @@ MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
||||
|
||||
MSAInteractive::MSAInteractive(
|
||||
AccountData* data,
|
||||
QObject* parent
|
||||
) : AuthFlow(data, parent) {
|
||||
MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Login));
|
||||
m_steps.append(makeShared<XboxUserStep>(m_data));
|
||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||
|
@ -1,22 +1,14 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class MSAInteractive : public AuthFlow
|
||||
{
|
||||
class MSAInteractive : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MSAInteractive(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MSAInteractive(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
||||
class MSASilent : public AuthFlow
|
||||
{
|
||||
class MSASilent : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MSASilent(
|
||||
AccountData * data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MSASilent(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
@ -1,25 +1,20 @@
|
||||
#include "Mojang.h"
|
||||
|
||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||
|
||||
MojangRefresh::MojangRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
|
||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
||||
|
||||
MojangLogin::MojangLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent
|
||||
): AuthFlow(data, parent), m_password(password) {
|
||||
MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
|
||||
{
|
||||
m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
|
||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
||||
|
@ -1,26 +1,17 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class MojangRefresh : public AuthFlow
|
||||
{
|
||||
class MojangRefresh : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MojangRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MojangRefresh(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
||||
class MojangLogin : public AuthFlow
|
||||
{
|
||||
class MojangLogin : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MojangLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
|
||||
|
||||
private:
|
||||
private:
|
||||
QString m_password;
|
||||
};
|
||||
|
@ -2,16 +2,12 @@
|
||||
|
||||
#include "minecraft/auth/steps/OfflineStep.h"
|
||||
|
||||
OfflineRefresh::OfflineRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
OfflineRefresh::OfflineRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
||||
}
|
||||
|
||||
OfflineLogin::OfflineLogin(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
||||
}
|
||||
|
@ -1,22 +1,14 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class OfflineRefresh : public AuthFlow
|
||||
{
|
||||
class OfflineRefresh : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit OfflineRefresh(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
||||
class OfflineLogin : public AuthFlow
|
||||
{
|
||||
class OfflineLogin : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineLogin(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit OfflineLogin(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
@ -11,12 +11,13 @@ EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
EntitlementsStep::~EntitlementsStep() noexcept = default;
|
||||
|
||||
QString EntitlementsStep::describe() {
|
||||
QString EntitlementsStep::describe()
|
||||
{
|
||||
return tr("Determining game ownership.");
|
||||
}
|
||||
|
||||
|
||||
void EntitlementsStep::perform() {
|
||||
void EntitlementsStep::perform()
|
||||
{
|
||||
auto uuid = QUuid::createUuid();
|
||||
m_entitlementsRequestId = uuid.toString().remove('{').remove('}');
|
||||
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId;
|
||||
@ -24,22 +25,22 @@ void EntitlementsStep::perform() {
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
qDebug() << "Getting entitlements...";
|
||||
}
|
||||
|
||||
void EntitlementsStep::rehydrate() {
|
||||
void EntitlementsStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void EntitlementsStep::onRequestDone(
|
||||
[[maybe_unused]] QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class EntitlementsStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EntitlementsStep(AccountData *data);
|
||||
public:
|
||||
explicit EntitlementsStep(AccountData* data);
|
||||
virtual ~EntitlementsStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,9 +16,9 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
|
||||
private:
|
||||
private:
|
||||
QString m_entitlementsRequestId;
|
||||
};
|
||||
|
@ -6,34 +6,32 @@
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
GetSkinStep::~GetSkinStep() noexcept = default;
|
||||
|
||||
QString GetSkinStep::describe() {
|
||||
QString GetSkinStep::describe()
|
||||
{
|
||||
return tr("Getting skin.");
|
||||
}
|
||||
|
||||
void GetSkinStep::perform() {
|
||||
void GetSkinStep::perform()
|
||||
{
|
||||
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void GetSkinStep::rehydrate() {
|
||||
void GetSkinStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now.
|
||||
}
|
||||
|
||||
void GetSkinStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void GetSkinStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError) {
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class GetSkinStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GetSkinStep(AccountData *data);
|
||||
public:
|
||||
explicit GetSkinStep(AccountData* data);
|
||||
virtual ~GetSkinStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -8,17 +8,17 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
LauncherLoginStep::~LauncherLoginStep() noexcept = default;
|
||||
|
||||
QString LauncherLoginStep::describe() {
|
||||
QString LauncherLoginStep::describe()
|
||||
{
|
||||
return tr("Accessing Mojang services.");
|
||||
}
|
||||
|
||||
void LauncherLoginStep::perform() {
|
||||
void LauncherLoginStep::perform()
|
||||
{
|
||||
auto requestURL = "https://api.minecraftservices.com/launcher/login";
|
||||
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
||||
auto xToken = m_data->mojangservicesToken.token;
|
||||
@ -34,22 +34,20 @@ void LauncherLoginStep::perform() {
|
||||
QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone);
|
||||
requestor->post(request, requestBody.toUtf8());
|
||||
qDebug() << "Getting Minecraft access token...";
|
||||
}
|
||||
|
||||
void LauncherLoginStep::rehydrate() {
|
||||
void LauncherLoginStep::rehydrate()
|
||||
{
|
||||
// TODO: check the token validity
|
||||
}
|
||||
|
||||
void LauncherLoginStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
@ -57,27 +55,17 @@ void LauncherLoginStep::onRequestDone(
|
||||
qWarning() << "Reply error:" << error;
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
||||
if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
||||
qWarning() << "Could not parse login_with_xbox response...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse the Minecraft access token response.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response."));
|
||||
return;
|
||||
}
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr(""));
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class LauncherLoginStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LauncherLoginStep(AccountData *data);
|
||||
public:
|
||||
explicit LauncherLoginStep(AccountData* data);
|
||||
virtual ~LauncherLoginStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -47,7 +47,8 @@
|
||||
using OAuth2 = Katabasis::DeviceFlow;
|
||||
using Activity = Katabasis::Activity;
|
||||
|
||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) {
|
||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action)
|
||||
{
|
||||
m_clientId = APPLICATION->getMSAClientID();
|
||||
OAuth2::Options opts;
|
||||
opts.scope = "XboxLive.signin offline_access";
|
||||
@ -64,13 +65,14 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac
|
||||
|
||||
MSAStep::~MSAStep() noexcept = default;
|
||||
|
||||
QString MSAStep::describe() {
|
||||
QString MSAStep::describe()
|
||||
{
|
||||
return tr("Logging in with Microsoft account.");
|
||||
}
|
||||
|
||||
|
||||
void MSAStep::rehydrate() {
|
||||
switch(m_action) {
|
||||
void MSAStep::rehydrate()
|
||||
{
|
||||
switch (m_action) {
|
||||
case Refresh: {
|
||||
// TODO: check the tokens and see if they are old (older than a day)
|
||||
return;
|
||||
@ -82,12 +84,14 @@ void MSAStep::rehydrate() {
|
||||
}
|
||||
}
|
||||
|
||||
void MSAStep::perform() {
|
||||
switch(m_action) {
|
||||
void MSAStep::perform()
|
||||
{
|
||||
switch (m_action) {
|
||||
case Refresh: {
|
||||
if (m_data->msaClientID != m_clientId) {
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed."));
|
||||
emit finished(AccountTaskState::STATE_DISABLED,
|
||||
tr("Microsoft user authentication failed - client identification has changed."));
|
||||
}
|
||||
m_oauth2->refresh();
|
||||
return;
|
||||
@ -105,8 +109,9 @@ void MSAStep::perform() {
|
||||
}
|
||||
}
|
||||
|
||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
|
||||
switch(activity) {
|
||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity)
|
||||
{
|
||||
switch (activity) {
|
||||
case Katabasis::Activity::Idle:
|
||||
case Katabasis::Activity::LoggingIn:
|
||||
case Katabasis::Activity::Refreshing:
|
||||
|
@ -43,13 +43,11 @@
|
||||
|
||||
class MSAStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Action {
|
||||
Refresh,
|
||||
Login
|
||||
};
|
||||
public:
|
||||
explicit MSAStep(AccountData *data, Action action);
|
||||
public:
|
||||
enum Action { Refresh, Login };
|
||||
|
||||
public:
|
||||
explicit MSAStep(AccountData* data, Action action);
|
||||
virtual ~MSAStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -57,11 +55,11 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onOAuthActivityChanged(Katabasis::Activity activity);
|
||||
|
||||
private:
|
||||
Katabasis::DeviceFlow *m_oauth2 = nullptr;
|
||||
private:
|
||||
Katabasis::DeviceFlow* m_oauth2 = nullptr;
|
||||
Action m_action;
|
||||
QString m_clientId;
|
||||
};
|
||||
|
@ -5,37 +5,37 @@
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
|
||||
|
||||
QString MigrationEligibilityStep::describe() {
|
||||
QString MigrationEligibilityStep::describe()
|
||||
{
|
||||
return tr("Checking for migration eligibility.");
|
||||
}
|
||||
|
||||
void MigrationEligibilityStep::perform() {
|
||||
void MigrationEligibilityStep::perform()
|
||||
{
|
||||
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
||||
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void MigrationEligibilityStep::rehydrate() {
|
||||
void MigrationEligibilityStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void MigrationEligibilityStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void MigrationEligibilityStep::onRequestDone(QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError) {
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MigrationEligibilityStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MigrationEligibilityStep(AccountData *data);
|
||||
public:
|
||||
explicit MigrationEligibilityStep(AccountData* data);
|
||||
virtual ~MigrationEligibilityStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -7,52 +7,46 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
MinecraftProfileStep::~MinecraftProfileStep() noexcept = default;
|
||||
|
||||
QString MinecraftProfileStep::describe() {
|
||||
QString MinecraftProfileStep::describe()
|
||||
{
|
||||
return tr("Fetching the Minecraft profile.");
|
||||
}
|
||||
|
||||
|
||||
void MinecraftProfileStep::perform() {
|
||||
void MinecraftProfileStep::perform()
|
||||
{
|
||||
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
|
||||
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void MinecraftProfileStep::rehydrate() {
|
||||
void MinecraftProfileStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void MinecraftProfileStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error == QNetworkReply::ContentNotFoundError) {
|
||||
// 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) {
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
||||
}
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
tr("Account has no Minecraft profile.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
||||
return;
|
||||
}
|
||||
if (error != QNetworkReply::NoError) {
|
||||
@ -65,35 +59,24 @@ void MinecraftProfileStep::onRequestDone(
|
||||
qWarning() << QString::fromUtf8(data);
|
||||
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
||||
if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile response could not be parsed")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
if (m_data->type == AccountType::Mojang) {
|
||||
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
|
||||
}
|
||||
emit finished(
|
||||
AccountTaskState::STATE_WORKING,
|
||||
tr("Minecraft Java profile acquisition succeeded.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MinecraftProfileStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MinecraftProfileStep(AccountData *data);
|
||||
public:
|
||||
explicit MinecraftProfileStep(AccountData* data);
|
||||
virtual ~MinecraftProfileStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -7,18 +7,17 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
|
||||
|
||||
QString MinecraftProfileStepMojang::describe() {
|
||||
QString MinecraftProfileStepMojang::describe()
|
||||
{
|
||||
return tr("Fetching the Minecraft profile.");
|
||||
}
|
||||
|
||||
|
||||
void MinecraftProfileStepMojang::perform() {
|
||||
void MinecraftProfileStepMojang::perform()
|
||||
{
|
||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
|
||||
return;
|
||||
@ -27,35 +26,32 @@ void MinecraftProfileStepMojang::perform() {
|
||||
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
|
||||
QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
|
||||
QNetworkRequest req = QNetworkRequest(url);
|
||||
AuthRequest *request = new AuthRequest(this);
|
||||
AuthRequest* request = new AuthRequest(this);
|
||||
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
|
||||
request->get(req);
|
||||
}
|
||||
|
||||
void MinecraftProfileStepMojang::rehydrate() {
|
||||
void MinecraftProfileStepMojang::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void MinecraftProfileStepMojang::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error == QNetworkReply::ContentNotFoundError) {
|
||||
// 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) {
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
||||
}
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
tr("Account has no Minecraft profile.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
||||
return;
|
||||
}
|
||||
if (error != QNetworkReply::NoError) {
|
||||
@ -68,35 +64,24 @@ void MinecraftProfileStepMojang::onRequestDone(
|
||||
qWarning() << QString::fromUtf8(data);
|
||||
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
||||
if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile response could not be parsed")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
if (m_data->type == AccountType::Mojang) {
|
||||
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
|
||||
}
|
||||
emit finished(
|
||||
AccountTaskState::STATE_WORKING,
|
||||
tr("Minecraft Java profile acquisition succeeded.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MinecraftProfileStepMojang : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MinecraftProfileStepMojang(AccountData *data);
|
||||
public:
|
||||
explicit MinecraftProfileStepMojang(AccountData* data);
|
||||
virtual ~MinecraftProfileStepMojang() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -5,14 +5,17 @@
|
||||
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
|
||||
OfflineStep::~OfflineStep() noexcept = default;
|
||||
|
||||
QString OfflineStep::describe() {
|
||||
QString OfflineStep::describe()
|
||||
{
|
||||
return tr("Creating offline account.");
|
||||
}
|
||||
|
||||
void OfflineStep::rehydrate() {
|
||||
void OfflineStep::rehydrate()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
void OfflineStep::perform() {
|
||||
void OfflineStep::perform()
|
||||
{
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));
|
||||
}
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
class OfflineStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineStep(AccountData *data);
|
||||
public:
|
||||
explicit OfflineStep(AccountData* data);
|
||||
virtual ~OfflineStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
|
@ -1,33 +1,32 @@
|
||||
#include "XboxAuthorizationStep.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token *token, QString relyingParty, QString authorizationKind):
|
||||
AuthStep(data),
|
||||
m_token(token),
|
||||
m_relyingParty(relyingParty),
|
||||
m_authorizationKind(authorizationKind)
|
||||
{
|
||||
}
|
||||
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind)
|
||||
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
|
||||
{}
|
||||
|
||||
XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default;
|
||||
|
||||
QString XboxAuthorizationStep::describe() {
|
||||
QString XboxAuthorizationStep::describe()
|
||||
{
|
||||
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
|
||||
}
|
||||
|
||||
void XboxAuthorizationStep::rehydrate() {
|
||||
void XboxAuthorizationStep::rehydrate()
|
||||
{
|
||||
// FIXME: check if the tokens are good?
|
||||
}
|
||||
|
||||
void XboxAuthorizationStep::perform() {
|
||||
void XboxAuthorizationStep::perform()
|
||||
{
|
||||
QString xbox_auth_template = R"XXX(
|
||||
{
|
||||
"Properties": {
|
||||
@ -41,129 +40,98 @@ void XboxAuthorizationStep::perform() {
|
||||
}
|
||||
)XXX";
|
||||
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
|
||||
// http://xboxlive.com
|
||||
// http://xboxlive.com
|
||||
QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &XboxAuthorizationStep::onRequestDone);
|
||||
requestor->post(request, xbox_auth_data.toUtf8());
|
||||
qDebug() << "Getting authorization token for " << m_relyingParty;
|
||||
}
|
||||
|
||||
void XboxAuthorizationStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
if (Net::isApplicationError(error)) {
|
||||
if(!processSTSError(error, data, headers)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error)
|
||||
);
|
||||
if (!processSTSError(error, data, headers)) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
|
||||
);
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Katabasis::Token temp;
|
||||
if(!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)
|
||||
);
|
||||
if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind));
|
||||
return;
|
||||
}
|
||||
|
||||
if(temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Server has changed %1 authorization user hash in the reply. Something is wrong.").arg(m_authorizationKind)
|
||||
);
|
||||
if (temp.extra["uhs"] != m_data->userToken.extra["uhs"]) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Server has changed %1 authorization user hash in the reply. Something is wrong.").arg(m_authorizationKind));
|
||||
return;
|
||||
}
|
||||
auto & token = *m_token;
|
||||
auto& token = *m_token;
|
||||
token = temp;
|
||||
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty));
|
||||
}
|
||||
|
||||
|
||||
bool XboxAuthorizationStep::processSTSError(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
if(error == QNetworkReply::AuthenticationRequiredError) {
|
||||
bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
if (error == QNetworkReply::AuthenticationRequiredError) {
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Cannot parse error XSTS response as JSON: " << jsonError.errorString();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString())
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString()));
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t errorCode = -1;
|
||||
auto obj = doc.object();
|
||||
if(!Parsers::getNumber(obj.value("XErr"), errorCode)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XErr element is missing from %1 authorization error response.").arg(m_authorizationKind)
|
||||
);
|
||||
if (!Parsers::getNumber(obj.value("XErr"), errorCode)) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XErr element is missing from %1 authorization error response.").arg(m_authorizationKind));
|
||||
return true;
|
||||
}
|
||||
switch(errorCode) {
|
||||
case 2148916233:{
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("This Microsoft account does not have an XBox Live profile. Buy the game on %1 first.")
|
||||
.arg("<a href=\"https://www.minecraft.net/en-us/store/minecraft-java-edition\">minecraft.net</a>")
|
||||
);
|
||||
switch (errorCode) {
|
||||
case 2148916233: {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("This Microsoft account does not have an XBox Live profile. Buy the game on %1 first.")
|
||||
.arg("<a href=\"https://www.minecraft.net/en-us/store/minecraft-java-edition\">minecraft.net</a>"));
|
||||
return true;
|
||||
}
|
||||
case 2148916235: {
|
||||
// NOTE: this is the Grulovia error
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XBox Live is not available in your country. You've been blocked.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox Live is not available in your country. You've been blocked."));
|
||||
return true;
|
||||
}
|
||||
case 2148916238: {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
|
||||
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>")
|
||||
);
|
||||
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>"));
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorCode)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorCode));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class XboxAuthorizationStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XboxAuthorizationStep(AccountData *data, Katabasis::Token *token, QString relyingParty, QString authorizationKind);
|
||||
public:
|
||||
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind);
|
||||
virtual ~XboxAuthorizationStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,18 +16,14 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private:
|
||||
bool processSTSError(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
);
|
||||
private:
|
||||
bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
|
||||
private:
|
||||
Katabasis::Token *m_token;
|
||||
private:
|
||||
Katabasis::Token* m_token;
|
||||
QString m_relyingParty;
|
||||
QString m_authorizationKind;
|
||||
};
|
||||
|
@ -8,66 +8,56 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
XboxProfileStep::~XboxProfileStep() noexcept = default;
|
||||
|
||||
QString XboxProfileStep::describe() {
|
||||
QString XboxProfileStep::describe()
|
||||
{
|
||||
return tr("Fetching Xbox profile.");
|
||||
}
|
||||
|
||||
void XboxProfileStep::rehydrate() {
|
||||
void XboxProfileStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void XboxProfileStep::perform() {
|
||||
void XboxProfileStep::perform()
|
||||
{
|
||||
auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings");
|
||||
QUrlQuery q;
|
||||
q.addQueryItem(
|
||||
"settings",
|
||||
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
||||
"PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,"
|
||||
"UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,"
|
||||
"PreferredColor,Location,Bio,Watermarks,"
|
||||
"RealName,RealNameOverride,IsQuarantined"
|
||||
);
|
||||
q.addQueryItem("settings",
|
||||
"GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw,"
|
||||
"PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix,"
|
||||
"UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep,"
|
||||
"PreferredColor,Location,Bio,Watermarks,"
|
||||
"RealName,RealNameOverride,IsQuarantined");
|
||||
url.setQuery(q);
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
request.setRawHeader("x-xbl-contract-version", "3");
|
||||
request.setRawHeader("Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
request.setRawHeader("Authorization",
|
||||
QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8());
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &XboxProfileStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
qDebug() << "Getting Xbox profile...";
|
||||
}
|
||||
|
||||
void XboxProfileStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class XboxProfileStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XboxProfileStep(AccountData *data);
|
||||
public:
|
||||
explicit XboxProfileStep(AccountData* data);
|
||||
virtual ~XboxProfileStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -6,22 +6,22 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
XboxUserStep::~XboxUserStep() noexcept = default;
|
||||
|
||||
QString XboxUserStep::describe() {
|
||||
QString XboxUserStep::describe()
|
||||
{
|
||||
return tr("Logging in as an Xbox user.");
|
||||
}
|
||||
|
||||
|
||||
void XboxUserStep::rehydrate() {
|
||||
void XboxUserStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void XboxUserStep::perform() {
|
||||
void XboxUserStep::perform()
|
||||
{
|
||||
QString xbox_auth_template = R"XXX(
|
||||
{
|
||||
"Properties": {
|
||||
@ -40,40 +40,31 @@ void XboxUserStep::perform() {
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
// set contract-verison header (prevent err 400 bad-request?)
|
||||
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
||||
request.setRawHeader("x-xbl-contract-version", "1");
|
||||
request.setRawHeader("x-xbl-contract-version", "1");
|
||||
|
||||
auto *requestor = new AuthRequest(this);
|
||||
auto* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
|
||||
requestor->post(request, xbox_auth_data.toUtf8());
|
||||
qDebug() << "First layer of XBox auth ... commencing.";
|
||||
}
|
||||
|
||||
void XboxUserStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Katabasis::Token temp;
|
||||
if(!Parsers::parseXTokenResponse(data, temp, "UToken")) {
|
||||
if (!Parsers::parseXTokenResponse(data, temp, "UToken")) {
|
||||
qWarning() << "Could not parse user authentication response...";
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication response could not be understood."));
|
||||
return;
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class XboxUserStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XboxUserStep(AccountData *data);
|
||||
public:
|
||||
explicit XboxUserStep(AccountData* data);
|
||||
virtual ~XboxUserStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -4,7 +4,8 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
|
||||
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password) {
|
||||
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
|
||||
{
|
||||
m_yggdrasil = new Yggdrasil(m_data, this);
|
||||
|
||||
connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
|
||||
@ -14,28 +15,32 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat
|
||||
|
||||
YggdrasilStep::~YggdrasilStep() noexcept = default;
|
||||
|
||||
QString YggdrasilStep::describe() {
|
||||
QString YggdrasilStep::describe()
|
||||
{
|
||||
return tr("Logging in with Mojang account.");
|
||||
}
|
||||
|
||||
void YggdrasilStep::rehydrate() {
|
||||
void YggdrasilStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now.
|
||||
}
|
||||
|
||||
void YggdrasilStep::perform() {
|
||||
if(m_password.size()) {
|
||||
void YggdrasilStep::perform()
|
||||
{
|
||||
if (m_password.size()) {
|
||||
m_yggdrasil->login(m_password);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
m_yggdrasil->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthSucceeded() {
|
||||
void YggdrasilStep::onAuthSucceeded()
|
||||
{
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthFailed() {
|
||||
void YggdrasilStep::onAuthFailed()
|
||||
{
|
||||
// TODO: hook these in again, expand to MSA
|
||||
// m_error = m_yggdrasil->m_error;
|
||||
// m_aborted = m_yggdrasil->m_aborted;
|
||||
@ -44,7 +49,7 @@ void YggdrasilStep::onAuthFailed() {
|
||||
QString errorMessage = tr("Mojang user authentication failed.");
|
||||
|
||||
// NOTE: soft error in the first step means 'offline'
|
||||
if(state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
if (state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
state = AccountTaskState::STATE_OFFLINE;
|
||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ class Yggdrasil;
|
||||
class YggdrasilStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit YggdrasilStep(AccountData *data, QString password);
|
||||
public:
|
||||
explicit YggdrasilStep(AccountData* data, QString password);
|
||||
virtual ~YggdrasilStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -18,11 +18,11 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onAuthSucceeded();
|
||||
void onAuthFailed();
|
||||
|
||||
private:
|
||||
Yggdrasil *m_yggdrasil = nullptr;
|
||||
private:
|
||||
Yggdrasil* m_yggdrasil = nullptr;
|
||||
QString m_password;
|
||||
};
|
||||
|
@ -1,59 +1,51 @@
|
||||
#include "GameOptions.h"
|
||||
#include "FileSystem.h"
|
||||
#include <QDebug>
|
||||
#include <QSaveFile>
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace {
|
||||
bool load(const QString& path, std::vector<GameOptionItem> &contents, int & version)
|
||||
bool load(const QString& path, std::vector<GameOptionItem>& contents, int& version)
|
||||
{
|
||||
contents.clear();
|
||||
QFile file(path);
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qWarning() << "Failed to read options file.";
|
||||
return false;
|
||||
}
|
||||
version = 0;
|
||||
while(!file.atEnd())
|
||||
{
|
||||
while (!file.atEnd()) {
|
||||
auto line = file.readLine();
|
||||
if(line.endsWith('\n'))
|
||||
{
|
||||
if (line.endsWith('\n')) {
|
||||
line.chop(1);
|
||||
}
|
||||
auto separatorIndex = line.indexOf(':');
|
||||
if(separatorIndex == -1)
|
||||
{
|
||||
if (separatorIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
auto key = QString::fromUtf8(line.data(), separatorIndex);
|
||||
auto value = QString::fromUtf8(line.data() + separatorIndex + 1, line.size() - 1 - separatorIndex);
|
||||
qDebug() << "!!" << key << "!!";
|
||||
if(key == "version")
|
||||
{
|
||||
if (key == "version") {
|
||||
version = value.toInt();
|
||||
continue;
|
||||
}
|
||||
contents.emplace_back(GameOptionItem{key, value});
|
||||
contents.emplace_back(GameOptionItem{ key, value });
|
||||
}
|
||||
qDebug() << "Loaded" << path << "with version:" << version;
|
||||
return true;
|
||||
}
|
||||
bool save(const QString& path, std::vector<GameOptionItem> &mapping, int version)
|
||||
bool save(const QString& path, std::vector<GameOptionItem>& mapping, int version)
|
||||
{
|
||||
QSaveFile out(path);
|
||||
if(!out.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!out.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
if(version != 0)
|
||||
{
|
||||
if (version != 0) {
|
||||
QString versionLine = QString("version:%1\n").arg(version);
|
||||
out.write(versionLine.toUtf8());
|
||||
}
|
||||
auto iter = mapping.begin();
|
||||
while (iter != mapping.end())
|
||||
{
|
||||
while (iter != mapping.end()) {
|
||||
out.write(iter->key.toUtf8());
|
||||
out.write(":");
|
||||
out.write(iter->value.toUtf8());
|
||||
@ -62,22 +54,19 @@ bool save(const QString& path, std::vector<GameOptionItem> &mapping, int version
|
||||
}
|
||||
return out.commit();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
GameOptions::GameOptions(const QString& path):
|
||||
path(path)
|
||||
GameOptions::GameOptions(const QString& path) : path(path)
|
||||
{
|
||||
reload();
|
||||
}
|
||||
|
||||
QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(role != Qt::DisplayRole)
|
||||
{
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QAbstractListModel::headerData(section, orientation, role);
|
||||
}
|
||||
switch(section)
|
||||
{
|
||||
switch (section) {
|
||||
case 0:
|
||||
return tr("Key");
|
||||
case 1:
|
||||
@ -98,19 +87,15 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const
|
||||
if (row < 0 || row >= int(contents.size()))
|
||||
return QVariant();
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
if(column == 0)
|
||||
{
|
||||
return contents[row].key;
|
||||
}
|
||||
else
|
||||
{
|
||||
return contents[row].value;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
if (column == 0) {
|
||||
return contents[row].key;
|
||||
} else {
|
||||
return contents[row].value;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,32 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <QString>
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
#include <map>
|
||||
|
||||
struct GameOptionItem
|
||||
{
|
||||
struct GameOptionItem {
|
||||
QString key;
|
||||
QString value;
|
||||
};
|
||||
|
||||
class GameOptions : public QAbstractListModel
|
||||
{
|
||||
class GameOptions : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
public:
|
||||
explicit GameOptions(const QString& path);
|
||||
virtual ~GameOptions() = default;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex & parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
bool isLoaded() const;
|
||||
bool reload();
|
||||
bool save();
|
||||
|
||||
private:
|
||||
private:
|
||||
std::vector<GameOptionItem> contents;
|
||||
bool loaded = false;
|
||||
QString path;
|
||||
|
@ -4,10 +4,9 @@
|
||||
#include "Application.h"
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
|
||||
ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchStep(parent)
|
||||
ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session) : LaunchStep(parent)
|
||||
{
|
||||
if(session->status == AuthSession::Status::PlayableOnline && !session->demo)
|
||||
{
|
||||
if (session->status == AuthSession::Status::PlayableOnline && !session->demo) {
|
||||
auto accounts = APPLICATION->accounts();
|
||||
m_account = accounts->getAccountByProfileName(session->player_name);
|
||||
}
|
||||
@ -15,8 +14,7 @@ ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchSt
|
||||
|
||||
void ClaimAccount::executeTask()
|
||||
{
|
||||
if(m_account)
|
||||
{
|
||||
if (m_account) {
|
||||
lock.reset(new UseLock(m_account));
|
||||
emitSucceeded();
|
||||
}
|
||||
|
@ -18,20 +18,17 @@
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
|
||||
class ClaimAccount: public LaunchStep
|
||||
{
|
||||
class ClaimAccount : public LaunchStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session);
|
||||
virtual ~ClaimAccount() {};
|
||||
public:
|
||||
explicit ClaimAccount(LaunchTask* parent, AuthSessionPtr session);
|
||||
virtual ~ClaimAccount(){};
|
||||
|
||||
void executeTask() override;
|
||||
void finalize() override;
|
||||
bool canAbort() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
bool canAbort() const override { return false; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<UseLock> lock;
|
||||
MinecraftAccountPtr m_account;
|
||||
};
|
||||
|
@ -1,27 +1,23 @@
|
||||
#include "CreateGameFolders.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "FileSystem.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
|
||||
CreateGameFolders::CreateGameFolders(LaunchTask* parent): LaunchStep(parent)
|
||||
{
|
||||
}
|
||||
CreateGameFolders::CreateGameFolders(LaunchTask* parent) : LaunchStep(parent) {}
|
||||
|
||||
void CreateGameFolders::executeTask()
|
||||
{
|
||||
auto instance = m_parent->instance();
|
||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
||||
|
||||
if(!FS::ensureFolderPathExists(minecraftInstance->gameRoot()))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) {
|
||||
emit logLine("Couldn't create the main game folder", MessageLevel::Error);
|
||||
emitFailed(tr("Couldn't create the main game folder"));
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
|
||||
if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs")))
|
||||
{
|
||||
if (!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) {
|
||||
emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error);
|
||||
}
|
||||
emitSucceeded();
|
||||
|
@ -15,23 +15,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <LoggedProcess.h>
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <minecraft/auth/AuthSession.h>
|
||||
|
||||
// Create the main .minecraft for the instance and any other necessary folders
|
||||
class CreateGameFolders: public LaunchStep
|
||||
{
|
||||
class CreateGameFolders : public LaunchStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CreateGameFolders(LaunchTask *parent);
|
||||
virtual ~CreateGameFolders() {};
|
||||
public:
|
||||
explicit CreateGameFolders(LaunchTask* parent);
|
||||
virtual ~CreateGameFolders(){};
|
||||
|
||||
virtual void executeTask();
|
||||
virtual bool canAbort() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool canAbort() const { return false; }
|
||||
};
|
||||
|
||||
|
||||
|
@ -14,26 +14,25 @@
|
||||
*/
|
||||
|
||||
#include "ExtractNatives.h"
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
#include <launch/LaunchTask.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
#include <QDir>
|
||||
#include "FileSystem.h"
|
||||
#include "MMCZip.h"
|
||||
|
||||
#ifdef major
|
||||
#undef major
|
||||
#undef major
|
||||
#endif
|
||||
#ifdef minor
|
||||
#undef minor
|
||||
#undef minor
|
||||
#endif
|
||||
|
||||
static QString replaceSuffix (QString target, const QString &suffix, const QString &replacement)
|
||||
static QString replaceSuffix(QString target, const QString& suffix, const QString& replacement)
|
||||
{
|
||||
if (!target.endsWith(suffix))
|
||||
{
|
||||
if (!target.endsWith(suffix)) {
|
||||
return target;
|
||||
}
|
||||
target.resize(target.length() - suffix.length());
|
||||
@ -43,17 +42,14 @@ static QString replaceSuffix (QString target, const QString &suffix, const QStri
|
||||
static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW)
|
||||
{
|
||||
QuaZip zip(source);
|
||||
if(!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
return false;
|
||||
}
|
||||
QDir directory(targetFolder);
|
||||
if (!zip.goToFirstFile())
|
||||
{
|
||||
if (!zip.goToFirstFile()) {
|
||||
return false;
|
||||
}
|
||||
do
|
||||
{
|
||||
do {
|
||||
QString name = zip.getCurrentFileName();
|
||||
auto lowercase = name.toLower();
|
||||
if (nativeGLFW && name.contains("glfw")) {
|
||||
@ -62,19 +58,16 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
|
||||
if (nativeOpenAL && name.contains("openal")) {
|
||||
continue;
|
||||
}
|
||||
if(applyJnilibHack)
|
||||
{
|
||||
if (applyJnilibHack) {
|
||||
name = replaceSuffix(name, ".jnilib", ".dylib");
|
||||
}
|
||||
QString absFilePath = directory.absoluteFilePath(name);
|
||||
if (!JlCompress::extractFile(&zip, "", absFilePath))
|
||||
{
|
||||
if (!JlCompress::extractFile(&zip, "", absFilePath)) {
|
||||
return false;
|
||||
}
|
||||
} while (zip.goToNextFile());
|
||||
zip.close();
|
||||
if(zip.getZipError()!=0)
|
||||
{
|
||||
if (zip.getZipError() != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -85,8 +78,7 @@ void ExtractNatives::executeTask()
|
||||
auto instance = m_parent->instance();
|
||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
||||
auto toExtract = minecraftInstance->getNativeJars();
|
||||
if(toExtract.isEmpty())
|
||||
{
|
||||
if (toExtract.isEmpty()) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
@ -94,14 +86,12 @@ void ExtractNatives::executeTask()
|
||||
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
|
||||
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
|
||||
|
||||
auto outputPath = minecraftInstance->getNativePath();
|
||||
auto outputPath = minecraftInstance->getNativePath();
|
||||
auto javaVersion = minecraftInstance->getJavaVersion();
|
||||
bool jniHackEnabled = javaVersion.major() >= 8;
|
||||
for(const auto &source: toExtract)
|
||||
{
|
||||
if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW))
|
||||
{
|
||||
const char *reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
|
||||
for (const auto& source : toExtract) {
|
||||
if (!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) {
|
||||
const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
|
||||
emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
|
||||
emitFailed(tr(reason).arg(source, outputPath));
|
||||
}
|
||||
|
@ -20,19 +20,13 @@
|
||||
#include "minecraft/auth/AuthSession.h"
|
||||
|
||||
// FIXME: temporary wrapper for existing task.
|
||||
class ExtractNatives: public LaunchStep
|
||||
{
|
||||
class ExtractNatives : public LaunchStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){};
|
||||
public:
|
||||
explicit ExtractNatives(LaunchTask* parent) : LaunchStep(parent){};
|
||||
virtual ~ExtractNatives(){};
|
||||
|
||||
void executeTask() override;
|
||||
bool canAbort() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool canAbort() const override { return false; }
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user