chore: reformat

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
This commit is contained in:
Sefa Eyeoglu
2023-08-14 18:16:53 +02:00
parent 779f70057b
commit 91ba4cf75e
603 changed files with 15840 additions and 16257 deletions

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
};

View File

@ -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);

View File

@ -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;
};

View File

@ -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"

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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));
}
}

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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");

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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)); }
};

View File

@ -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());

View File

@ -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());

View File

@ -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;
};

View File

@ -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");
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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";

View File

@ -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_;
};

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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);
};

View File

@ -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

View File

@ -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

View File

@ -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."));
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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;
};

View File

@ -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"));

View File

@ -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);
};

View File

@ -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));

View File

@ -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;
};

View File

@ -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));
}

View File

@ -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);
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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) {

View File

@ -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>);
};

View File

@ -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(""));

View File

@ -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>);
};

View File

@ -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:

View File

@ -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;
};

View File

@ -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) {

View File

@ -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>);
};

View File

@ -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."));
}

View File

@ -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>);
};

View File

@ -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."));
}

View File

@ -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>);
};

View File

@ -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."));
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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>);
};

View File

@ -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;

View File

@ -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>);
};

View File

@ -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.");
}

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;
};

View File

@ -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();

View File

@ -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; }
};

View File

@ -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));
}

View File

@ -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