NOISSUE tabs -> spaces

This commit is contained in:
Petr Mrázek
2018-07-15 14:51:05 +02:00
parent 03280cc62e
commit bbb3b3e6f6
577 changed files with 51938 additions and 51938 deletions

View File

@ -38,200 +38,200 @@ namespace AssetsUtils
*/
bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
{
/*
{
"objects": {
"icons/icon_16x16.png": {
"hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
"size": 3665
},
...
}
}
}
*/
/*
{
"objects": {
"icons/icon_16x16.png": {
"hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
"size": 3665
},
...
}
}
}
*/
QFile file(path);
QFile file(path);
// 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))
{
qCritical() << "Failed to read assets index file" << path;
return false;
}
index->id = assetsId;
// 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))
{
qCritical() << "Failed to read assets index file" << path;
return false;
}
index->id = assetsId;
// Read the file and close it.
QByteArray jsonData = file.readAll();
file.close();
// Read the file and close it.
QByteArray jsonData = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
QJsonParseError parseError;
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);
return false;
}
// 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);
return false;
}
// Make sure the root is an object.
if (!jsonDoc.isObject())
{
qCritical() << "Invalid assets index JSON: Root should be an array.";
return false;
}
// Make sure the root is an object.
if (!jsonDoc.isObject())
{
qCritical() << "Invalid assets index JSON: Root should be an array.";
return false;
}
QJsonObject root = jsonDoc.object();
QJsonObject root = jsonDoc.object();
QJsonValue isVirtual = root.value("virtual");
if (!isVirtual.isUndefined())
{
index->isVirtual = isVirtual.toBool(false);
}
QJsonValue isVirtual = root.value("virtual");
if (!isVirtual.isUndefined())
{
index->isVirtual = isVirtual.toBool(false);
}
QJsonValue objects = root.value("objects");
QVariantMap map = objects.toVariant().toMap();
QJsonValue objects = root.value("objects");
QVariantMap map = objects.toVariant().toMap();
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
// qDebug() << iter.key();
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
// qDebug() << iter.key();
QVariant variant = iter.value();
QVariantMap nested_objects = variant.toMap();
QVariant variant = iter.value();
QVariantMap nested_objects = variant.toMap();
AssetObject object;
AssetObject object;
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();
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")
{
object.hash = value.toString();
}
else if (key == "size")
{
object.size = value.toDouble();
}
}
if (key == "hash")
{
object.hash = value.toString();
}
else if (key == "size")
{
object.size = value.toDouble();
}
}
index->objects.insert(iter.key(), object);
}
index->objects.insert(iter.key(), object);
}
return true;
return true;
}
QDir reconstructAssets(QString assetsId)
{
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects"));
QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual"));
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects"));
QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual"));
QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json");
QFile indexFile(indexPath);
QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json");
QFile indexFile(indexPath);
QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
if (!indexFile.exists())
{
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
return virtualRoot;
}
if (!indexFile.exists())
{
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
return virtualRoot;
}
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index;
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
AssetsIndex index;
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
if (loadAssetsIndex && index.isVirtual)
{
qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
if (loadAssetsIndex && index.isVirtual)
{
qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
for (QString map : index.objects.keys())
{
AssetObject asset_object = index.objects.value(map);
QString target_path = FS::PathCombine(virtualRoot.path(), map);
QFile target(target_path);
for (QString map : index.objects.keys())
{
AssetObject asset_object = index.objects.value(map);
QString target_path = FS::PathCombine(virtualRoot.path(), map);
QFile target(target_path);
QString tlk = asset_object.hash.left(2);
QString tlk = asset_object.hash.left(2);
QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash);
QFile original(original_path);
if (!original.exists())
continue;
if (!target.exists())
{
QFileInfo info(target_path);
QDir target_dir = info.dir();
// qDebug() << target_dir;
if (!target_dir.exists())
QDir("").mkpath(target_dir.path());
QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash);
QFile original(original_path);
if (!original.exists())
continue;
if (!target.exists())
{
QFileInfo info(target_path);
QDir target_dir = info.dir();
// qDebug() << target_dir;
if (!target_dir.exists())
QDir("").mkpath(target_dir.path());
bool couldCopy = original.copy(target_path);
qDebug() << " Copying" << original_path << "to" << target_path
<< QString::number(couldCopy); // << original.errorString();
}
}
bool couldCopy = original.copy(target_path);
qDebug() << " Copying" << original_path << "to" << target_path
<< QString::number(couldCopy); // << original.errorString();
}
}
// TODO: Write last used time to virtualRoot/.lastused
}
// TODO: Write last used time to virtualRoot/.lastused
}
return virtualRoot;
return virtualRoot;
}
}
NetActionPtr AssetObject::getDownloadAction()
{
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size))
{
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
if(hash.size())
{
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
}
objectDL->m_total_progress = size;
return objectDL;
}
return nullptr;
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size))
{
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
if(hash.size())
{
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
}
objectDL->m_total_progress = size;
return objectDL;
}
return nullptr;
}
QString AssetObject::getLocalPath()
{
return "assets/objects/" + getRelPath();
return "assets/objects/" + getRelPath();
}
QUrl AssetObject::getUrl()
{
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
}
QString AssetObject::getRelPath()
{
return hash.left(2) + "/" + hash;
return hash.left(2) + "/" + hash;
}
NetJobPtr AssetsIndex::getDownloadJob()
{
auto job = new NetJob(QObject::tr("Assets for %1").arg(id));
for (auto &object : objects.values())
{
auto dl = object.getDownloadAction();
if(dl)
{
job->addNetAction(dl);
}
}
if(job->size())
return job;
return nullptr;
auto job = new NetJob(QObject::tr("Assets for %1").arg(id));
for (auto &object : objects.values())
{
auto dl = object.getDownloadAction();
if(dl)
{
job->addNetAction(dl);
}
}
if(job->size())
return job;
return nullptr;
}

View File

@ -22,22 +22,22 @@
struct AssetObject
{
QString getRelPath();
QUrl getUrl();
QString getLocalPath();
NetActionPtr getDownloadAction();
QString getRelPath();
QUrl getUrl();
QString getLocalPath();
NetActionPtr getDownloadAction();
QString hash;
qint64 size;
QString hash;
qint64 size;
};
struct AssetsIndex
{
NetJobPtr getDownloadJob();
NetJobPtr getDownloadJob();
QString id;
QMap<QString, AssetObject> objects;
bool isVirtual = false;
QString id;
QMap<QString, AssetObject> objects;
bool isVirtual = false;
};
namespace AssetsUtils

View File

@ -13,356 +13,356 @@
Component::Component(ComponentList * parent, const QString& uid)
{
assert(parent);
m_parent = parent;
assert(parent);
m_parent = parent;
m_uid = uid;
m_uid = uid;
}
Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
{
assert(parent);
m_parent = parent;
assert(parent);
m_parent = parent;
m_metaVersion = version;
m_uid = version->uid();
m_version = m_cachedVersion = version->version();
m_cachedName = version->name();
m_loaded = version->isLoaded();
m_metaVersion = version;
m_uid = version->uid();
m_version = m_cachedVersion = version->version();
m_cachedName = version->name();
m_loaded = version->isLoaded();
}
Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
{
assert(parent);
m_parent = parent;
assert(parent);
m_parent = parent;
m_file = file;
m_uid = uid;
m_cachedVersion = m_file->version;
m_cachedName = m_file->name;
m_loaded = true;
m_file = file;
m_uid = uid;
m_cachedVersion = m_file->version;
m_cachedName = m_file->name;
m_loaded = true;
}
std::shared_ptr<Meta::Version> Component::getMeta()
{
return m_metaVersion;
return m_metaVersion;
}
void Component::applyTo(LaunchProfile* profile)
{
// do not apply disabled components
if(!isEnabled())
{
return;
}
auto vfile = getVersionFile();
if(vfile)
{
vfile->applyTo(profile);
}
else
{
profile->applyProblemSeverity(getProblemSeverity());
}
// do not apply disabled components
if(!isEnabled())
{
return;
}
auto vfile = getVersionFile();
if(vfile)
{
vfile->applyTo(profile);
}
else
{
profile->applyProblemSeverity(getProblemSeverity());
}
}
std::shared_ptr<class VersionFile> Component::getVersionFile() const
{
if(m_metaVersion)
{
if(!m_metaVersion->isLoaded())
{
m_metaVersion->load(Net::Mode::Online);
}
return m_metaVersion->data();
}
else
{
return m_file;
}
if(m_metaVersion)
{
if(!m_metaVersion->isLoaded())
{
m_metaVersion->load(Net::Mode::Online);
}
return m_metaVersion->data();
}
else
{
return m_file;
}
}
std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
{
// FIXME: what if the metadata index isn't loaded yet?
if(ENV.metadataIndex()->hasUid(m_uid))
{
return ENV.metadataIndex()->get(m_uid);
}
return nullptr;
// FIXME: what if the metadata index isn't loaded yet?
if(ENV.metadataIndex()->hasUid(m_uid))
{
return ENV.metadataIndex()->get(m_uid);
}
return nullptr;
}
int Component::getOrder()
{
if(m_orderOverride)
return m_order;
if(m_orderOverride)
return m_order;
auto vfile = getVersionFile();
if(vfile)
{
return vfile->order;
}
return 0;
auto vfile = getVersionFile();
if(vfile)
{
return vfile->order;
}
return 0;
}
void Component::setOrder(int order)
{
m_orderOverride = true;
m_order = order;
m_orderOverride = true;
m_order = order;
}
QString Component::getID()
{
return m_uid;
return m_uid;
}
QString Component::getName()
{
if (!m_cachedName.isEmpty())
return m_cachedName;
return m_uid;
if (!m_cachedName.isEmpty())
return m_cachedName;
return m_uid;
}
QString Component::getVersion()
{
return m_cachedVersion;
return m_cachedVersion;
}
QString Component::getFilename()
{
return m_parent->patchFilePathForUid(m_uid);
return m_parent->patchFilePathForUid(m_uid);
}
QDateTime Component::getReleaseDateTime()
{
if(m_metaVersion)
{
return m_metaVersion->time();
}
auto vfile = getVersionFile();
if(vfile)
{
return vfile->releaseTime;
}
// FIXME: fake
return QDateTime::currentDateTime();
if(m_metaVersion)
{
return m_metaVersion->time();
}
auto vfile = getVersionFile();
if(vfile)
{
return vfile->releaseTime;
}
// FIXME: fake
return QDateTime::currentDateTime();
}
bool Component::isEnabled()
{
return !canBeDisabled() || !m_disabled;
return !canBeDisabled() || !m_disabled;
}
bool Component::canBeDisabled()
{
return isRemovable() && !m_dependencyOnly;
return isRemovable() && !m_dependencyOnly;
}
bool Component::setEnabled(bool state)
{
bool intendedDisabled = !state;
if (!canBeDisabled())
{
intendedDisabled = false;
}
if(intendedDisabled != m_disabled)
{
m_disabled = intendedDisabled;
emit dataChanged();
return true;
}
return false;
bool intendedDisabled = !state;
if (!canBeDisabled())
{
intendedDisabled = false;
}
if(intendedDisabled != m_disabled)
{
m_disabled = intendedDisabled;
emit dataChanged();
return true;
}
return false;
}
bool Component::isCustom()
{
return m_file != nullptr;
return m_file != nullptr;
}
bool Component::isCustomizable()
{
if(m_metaVersion)
{
if(getVersionFile())
{
return true;
}
}
return false;
if(m_metaVersion)
{
if(getVersionFile())
{
return true;
}
}
return false;
}
bool Component::isRemovable()
{
return !m_important;
return !m_important;
}
bool Component::isRevertible()
{
if (isCustom())
{
if(ENV.metadataIndex()->hasUid(m_uid))
{
return true;
}
}
return false;
if (isCustom())
{
if(ENV.metadataIndex()->hasUid(m_uid))
{
return true;
}
}
return false;
}
bool Component::isMoveable()
{
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
return true;
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
return true;
}
bool Component::isVersionChangeable()
{
auto list = getVersionList();
if(list)
{
if(!list->isLoaded())
{
list->load(Net::Mode::Online);
}
return list->count() != 0;
}
return false;
auto list = getVersionList();
if(list)
{
if(!list->isLoaded())
{
list->load(Net::Mode::Online);
}
return list->count() != 0;
}
return false;
}
void Component::setImportant(bool state)
{
if(m_important != state)
{
m_important = state;
emit dataChanged();
}
if(m_important != state)
{
m_important = state;
emit dataChanged();
}
}
ProblemSeverity Component::getProblemSeverity() const
{
auto file = getVersionFile();
if(file)
{
return file->getProblemSeverity();
}
return ProblemSeverity::Error;
auto file = getVersionFile();
if(file)
{
return file->getProblemSeverity();
}
return ProblemSeverity::Error;
}
const QList<PatchProblem> Component::getProblems() const
{
auto file = getVersionFile();
if(file)
{
return file->getProblems();
}
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
auto file = getVersionFile();
if(file)
{
return file->getProblems();
}
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
}
void Component::setVersion(const QString& version)
{
if(version == m_version)
{
return;
}
m_version = version;
if(m_loaded)
{
// we are loaded and potentially have state to invalidate
if(m_file)
{
// we have a file... explicit version has been changed and there is nothing else to do.
}
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 = ENV.metadataIndex()->get(m_uid, version);
if(metaVersion->isLoaded())
{
// if yes, we can continue with that.
m_metaVersion = metaVersion;
}
else
{
// if not, we need loading
m_metaVersion.reset();
m_loaded = false;
}
updateCachedData();
}
}
else
{
// not loaded... assume it will be sorted out later by the update task
}
emit dataChanged();
if(version == m_version)
{
return;
}
m_version = version;
if(m_loaded)
{
// we are loaded and potentially have state to invalidate
if(m_file)
{
// we have a file... explicit version has been changed and there is nothing else to do.
}
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 = ENV.metadataIndex()->get(m_uid, version);
if(metaVersion->isLoaded())
{
// if yes, we can continue with that.
m_metaVersion = metaVersion;
}
else
{
// if not, we need loading
m_metaVersion.reset();
m_loaded = false;
}
updateCachedData();
}
}
else
{
// not loaded... assume it will be sorted out later by the update task
}
emit dataChanged();
}
bool Component::customize()
{
if(isCustom())
{
return false;
}
if(isCustom())
{
return false;
}
auto filename = getFilename();
if(!FS::ensureFilePathExists(filename))
{
return false;
}
// FIXME: get rid of this try-catch.
try
{
QSaveFile jsonFile(filename);
if(!jsonFile.open(QIODevice::WriteOnly))
{
return false;
}
auto vfile = getVersionFile();
if(!vfile)
{
return false;
}
auto document = OneSixVersionFormat::versionFileToJson(vfile);
jsonFile.write(document.toJson());
if(!jsonFile.commit())
{
return false;
}
m_file = vfile;
m_metaVersion.reset();
emit dataChanged();
}
catch (const Exception &error)
{
qWarning() << "Version could not be loaded:" << error.cause();
}
return true;
auto filename = getFilename();
if(!FS::ensureFilePathExists(filename))
{
return false;
}
// FIXME: get rid of this try-catch.
try
{
QSaveFile jsonFile(filename);
if(!jsonFile.open(QIODevice::WriteOnly))
{
return false;
}
auto vfile = getVersionFile();
if(!vfile)
{
return false;
}
auto document = OneSixVersionFormat::versionFileToJson(vfile);
jsonFile.write(document.toJson());
if(!jsonFile.commit())
{
return false;
}
m_file = vfile;
m_metaVersion.reset();
emit dataChanged();
}
catch (const Exception &error)
{
qWarning() << "Version could not be loaded:" << error.cause();
}
return true;
}
bool Component::revert()
{
if(!isCustom())
{
// already not custom
return true;
}
auto filename = getFilename();
bool result = true;
// just kill the file and reload
if(QFile::exists(filename))
{
result = QFile::remove(filename);
}
if(result)
{
// file gone...
m_file.reset();
if(!isCustom())
{
// already not custom
return true;
}
auto filename = getFilename();
bool result = true;
// just kill the file and reload
if(QFile::exists(filename))
{
result = QFile::remove(filename);
}
if(result)
{
// file gone...
m_file.reset();
// check local cache for metadata...
auto version = ENV.metadataIndex()->get(m_uid, m_version);
if(version->isLoaded())
{
m_metaVersion = version;
}
else
{
m_metaVersion.reset();
m_loaded = false;
}
emit dataChanged();
}
return result;
// check local cache for metadata...
auto version = ENV.metadataIndex()->get(m_uid, m_version);
if(version->isLoaded())
{
m_metaVersion = version;
}
else
{
m_metaVersion.reset();
m_loaded = false;
}
emit dataChanged();
}
return result;
}
/**
@ -372,68 +372,68 @@ bool Component::revert()
*/
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())
{
return false;
}
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))
{
return false;
}
}
return true;
// NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
if(a.size() != b.size())
{
return false;
}
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))
{
return false;
}
}
return true;
}
void Component::updateCachedData()
{
auto file = getVersionFile();
if(file)
{
bool changed = false;
if(m_cachedName != file->name)
{
m_cachedName = file->name;
changed = true;
}
if(m_cachedVersion != file->version)
{
m_cachedVersion = file->version;
changed = true;
}
if(m_cachedVolatile != file->m_volatile)
{
m_cachedVolatile = file->m_volatile;
changed = true;
}
if(!deepCompare(m_cachedRequires, file->requires))
{
m_cachedRequires = file->requires;
changed = true;
}
if(!deepCompare(m_cachedConflicts, file->conflicts))
{
m_cachedConflicts = file->conflicts;
changed = true;
}
if(changed)
{
emit dataChanged();
}
}
else
{
// in case we removed all the metadata
m_cachedRequires.clear();
m_cachedConflicts.clear();
emit dataChanged();
}
auto file = getVersionFile();
if(file)
{
bool changed = false;
if(m_cachedName != file->name)
{
m_cachedName = file->name;
changed = true;
}
if(m_cachedVersion != file->version)
{
m_cachedVersion = file->version;
changed = true;
}
if(m_cachedVolatile != file->m_volatile)
{
m_cachedVolatile = file->m_volatile;
changed = true;
}
if(!deepCompare(m_cachedRequires, file->requires))
{
m_cachedRequires = file->requires;
changed = true;
}
if(!deepCompare(m_cachedConflicts, file->conflicts))
{
m_cachedConflicts = file->conflicts;
changed = true;
}
if(changed)
{
emit dataChanged();
}
}
else
{
// in case we removed all the metadata
m_cachedRequires.clear();
m_cachedConflicts.clear();
emit dataChanged();
}
}

View File

@ -13,8 +13,8 @@ class ComponentList;
class LaunchProfile;
namespace Meta
{
class Version;
class VersionList;
class Version;
class VersionList;
}
class VersionFile;
@ -22,90 +22,90 @@ class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider
{
Q_OBJECT
public:
Component(ComponentList * parent, const QString &uid);
Component(ComponentList * parent, const QString &uid);
// DEPRECATED: remove these constructors?
Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
// DEPRECATED: remove these constructors?
Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
virtual ~Component(){};
void applyTo(LaunchProfile *profile);
virtual ~Component(){};
void applyTo(LaunchProfile *profile);
bool isEnabled();
bool setEnabled (bool state);
bool canBeDisabled();
bool isEnabled();
bool setEnabled (bool state);
bool canBeDisabled();
bool isMoveable();
bool isCustomizable();
bool isRevertible();
bool isRemovable();
bool isCustom();
bool isVersionChangeable();
bool isMoveable();
bool isCustomizable();
bool isRevertible();
bool isRemovable();
bool isCustom();
bool isVersionChangeable();
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
void setOrder(int order);
int getOrder();
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
void setOrder(int order);
int getOrder();
QString getID();
QString getName();
QString getVersion();
std::shared_ptr<Meta::Version> getMeta();
QDateTime getReleaseDateTime();
QString getID();
QString getName();
QString getVersion();
std::shared_ptr<Meta::Version> getMeta();
QDateTime getReleaseDateTime();
QString getFilename();
QString getFilename();
std::shared_ptr<class VersionFile> getVersionFile() const;
std::shared_ptr<class Meta::VersionList> getVersionList() const;
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;
const QList<PatchProblem> getProblems() const override;
ProblemSeverity getProblemSeverity() const override;
void setVersion(const QString & version);
bool customize();
bool revert();
void setVersion(const QString & version);
bool customize();
bool revert();
void updateCachedData();
void updateCachedData();
signals:
void dataChanged();
void dataChanged();
public: /* data */
ComponentList * m_parent;
ComponentList * m_parent;
// BEGIN: persistent component list properties
/// ID of the component
QString m_uid;
/// version of the component - when there's a custom json override, this is also the version the component reverts to
QString m_version;
/// if true, this has been added automatically to satisfy dependencies and may be automatically removed
bool m_dependencyOnly = false;
/// if true, the component is either the main component of the instance, or otherwise important and cannot be removed.
bool m_important = false;
/// if true, the component is disabled
bool m_disabled = false;
// BEGIN: persistent component list properties
/// ID of the component
QString m_uid;
/// version of the component - when there's a custom json override, this is also the version the component reverts to
QString m_version;
/// if true, this has been added automatically to satisfy dependencies and may be automatically removed
bool m_dependencyOnly = false;
/// if true, the component is either the main component of the instance, or otherwise important and cannot be removed.
bool m_important = false;
/// if true, the component is disabled
bool m_disabled = false;
/// cached name for display purposes, taken from the version file (meta or local override)
QString m_cachedName;
/// cached version for display AND other purposes, taken from the version file (meta or local override)
QString m_cachedVersion;
/// cached set of requirements, taken from the version file (meta or local override)
Meta::RequireSet m_cachedRequires;
Meta::RequireSet m_cachedConflicts;
/// if true, the component is volatile and may be automatically removed when no longer needed
bool m_cachedVolatile = false;
// END: persistent component list properties
/// cached name for display purposes, taken from the version file (meta or local override)
QString m_cachedName;
/// cached version for display AND other purposes, taken from the version file (meta or local override)
QString m_cachedVersion;
/// cached set of requirements, taken from the version file (meta or local override)
Meta::RequireSet m_cachedRequires;
Meta::RequireSet m_cachedConflicts;
/// if true, the component is volatile and may be automatically removed when no longer needed
bool m_cachedVolatile = false;
// END: persistent component list properties
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
bool m_orderOverride = false;
int m_order = 0;
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
bool m_orderOverride = false;
int m_order = 0;
// load state
std::shared_ptr<Meta::Version> m_metaVersion;
std::shared_ptr<VersionFile> m_file;
bool m_loaded = false;
// load state
std::shared_ptr<Meta::Version> m_metaVersion;
std::shared_ptr<VersionFile> m_file;
bool m_loaded = false;
};
typedef shared_qobject_ptr<Component> ComponentPtr;

File diff suppressed because it is too large Load Diff

View File

@ -36,111 +36,111 @@ class ComponentUpdateTask;
class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel
{
Q_OBJECT
friend ComponentUpdateTask;
Q_OBJECT
friend ComponentUpdateTask;
public:
enum Columns
{
NameColumn = 0,
VersionColumn,
NUM_COLUMNS
};
enum Columns
{
NameColumn = 0,
VersionColumn,
NUM_COLUMNS
};
explicit ComponentList(MinecraftInstance * instance);
virtual ~ComponentList();
explicit ComponentList(MinecraftInstance * instance);
virtual ~ComponentList();
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 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;
/// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
void buildingFromScratch();
/// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
void buildingFromScratch();
/// install more jar mods
void installJarMods(QStringList selectedFiles);
/// install more jar mods
void installJarMods(QStringList selectedFiles);
/// install a jar/zip as a replacement for the main jar
void installCustomJar(QString selectedFile);
/// install a jar/zip as a replacement for the main jar
void installCustomJar(QString selectedFile);
enum MoveDirection { MoveUp, MoveDown };
/// move component file # up or down the list
void move(const int index, const MoveDirection direction);
enum MoveDirection { MoveUp, MoveDown };
/// move component file # up or down the list
void move(const int index, const MoveDirection direction);
/// remove component file # - including files/records
bool remove(const int index);
/// remove component file # - including files/records
bool remove(const int index);
/// remove component file by id - including files/records
bool remove(const QString id);
/// remove component file by id - including files/records
bool remove(const QString id);
bool customize(int index);
bool customize(int index);
bool revertToBase(int index);
bool revertToBase(int index);
/// reload the list, reload all components, resolve dependencies
void reload(Net::Mode netmode);
/// reload the list, reload all components, resolve dependencies
void reload(Net::Mode netmode);
// reload all components, resolve dependencies
void resolve(Net::Mode netmode);
// reload all components, resolve dependencies
void resolve(Net::Mode netmode);
/// get current running task...
shared_qobject_ptr<Task> getCurrentTask();
/// get current running task...
shared_qobject_ptr<Task> getCurrentTask();
std::shared_ptr<LaunchProfile> getProfile() const;
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);
// NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config
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();
/// if there is a save scheduled, do it now.
void saveNow();
public:
/// get the profile component by id
Component * getComponent(const QString &id);
/// get the profile component by id
Component * getComponent(const QString &id);
/// get the profile component by index
Component * getComponent(int index);
/// get the profile component by index
Component * getComponent(int index);
private:
void scheduleSave();
bool saveIsScheduled() const;
void scheduleSave();
bool saveIsScheduled() const;
/// apply the component patches. Catches all the errors and returns true/false for success/failure
void invalidateLaunchProfile();
/// apply the component patches. Catches all the errors and returns true/false for success/failure
void invalidateLaunchProfile();
/// Add the component to the internal list of patches
void appendComponent(ComponentPtr component);
/// insert component so that its index is ideally the specified one (returns real index)
void insertComponent(size_t index, ComponentPtr component);
/// Add the component to the internal list of patches
void appendComponent(ComponentPtr component);
/// insert component so that its index is ideally the specified one (returns real index)
void insertComponent(size_t index, ComponentPtr component);
QString componentsFilePath() const;
QString patchesPattern() const;
QString componentsFilePath() const;
QString patchesPattern() const;
private slots:
void save_internal();
void updateSucceeded();
void updateFailed(const QString & error);
void componentDataChanged();
void save_internal();
void updateSucceeded();
void updateFailed(const QString & error);
void componentDataChanged();
private:
bool load();
bool installJarMods_internal(QStringList filepaths);
bool load();
bool installJarMods_internal(QStringList filepaths);
bool installCustomJar_internal(QString filepath);
bool removeComponent_internal(ComponentPtr patch);
bool removeComponent_internal(ComponentPtr patch);
bool migratePreComponentConfig();
bool migratePreComponentConfig();
private: /* data */
std::unique_ptr<ComponentListData> d;
std::unique_ptr<ComponentListData> d;
};

View File

@ -13,30 +13,30 @@ using ConnectionList = QList<QMetaObject::Connection>;
struct ComponentListData
{
// the instance this belongs to
MinecraftInstance *m_instance;
// the instance this belongs to
MinecraftInstance *m_instance;
// the launch profile (volatile, temporary thing created on demand)
std::shared_ptr<LaunchProfile> m_profile;
// the launch profile (volatile, temporary thing created on demand)
std::shared_ptr<LaunchProfile> m_profile;
// version information migrated from instance.cfg file. Single use on migration!
std::map<QString, QString> m_oldConfigVersions;
QString getOldConfigVersion(const QString& uid) const
{
const auto iter = m_oldConfigVersions.find(uid);
if(iter != m_oldConfigVersions.cend())
{
return (*iter).second;
}
return QString();
}
// version information migrated from instance.cfg file. Single use on migration!
std::map<QString, QString> m_oldConfigVersions;
QString getOldConfigVersion(const QString& uid) const
{
const auto iter = m_oldConfigVersions.find(uid);
if(iter != m_oldConfigVersions.cend())
{
return (*iter).second;
}
return QString();
}
// persistent list of components and related machinery
ComponentContainer components;
ComponentIndex componentIndex;
bool dirty = false;
QTimer m_saveTimer;
shared_qobject_ptr<Task> m_updateTask;
bool loaded = false;
// persistent list of components and related machinery
ComponentContainer components;
ComponentIndex componentIndex;
bool dirty = false;
QTimer m_saveTimer;
shared_qobject_ptr<Task> m_updateTask;
bool loaded = false;
};

File diff suppressed because it is too large Load Diff

View File

@ -9,29 +9,29 @@ struct ComponentUpdateTaskData;
class ComponentUpdateTask : public Task
{
Q_OBJECT
Q_OBJECT
public:
enum class Mode
{
Launch,
Resolution
};
enum class Mode
{
Launch,
Resolution
};
public:
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
virtual ~ComponentUpdateTask();
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
virtual ~ComponentUpdateTask();
protected:
void executeTask();
void executeTask();
private:
void loadComponents();
void resolveDependencies(bool checkOnly);
void loadComponents();
void resolveDependencies(bool checkOnly);
void remoteLoadSucceeded(size_t index);
void remoteLoadFailed(size_t index, const QString &msg);
void checkIfAllFinished();
void remoteLoadSucceeded(size_t index);
void remoteLoadFailed(size_t index, const QString &msg);
void checkIfAllFinished();
private:
std::unique_ptr<ComponentUpdateTaskData> d;
std::unique_ptr<ComponentUpdateTaskData> d;
};

View File

@ -9,24 +9,24 @@ class ComponentList;
struct RemoteLoadStatus
{
enum class Type
{
Index,
List,
Version
} type = Type::Version;
size_t componentListIndex = 0;
bool finished = false;
bool succeeded = false;
QString error;
enum class Type
{
Index,
List,
Version
} type = Type::Version;
size_t componentListIndex = 0;
bool finished = false;
bool succeeded = false;
QString error;
};
struct ComponentUpdateTaskData
{
ComponentList * m_list = nullptr;
QList<RemoteLoadStatus> remoteLoadStatusList;
bool remoteLoadSuccessful = true;
size_t remoteTasksInProgress = 0;
ComponentUpdateTask::Mode mode;
Net::Mode netmode;
ComponentList * m_list = nullptr;
QList<RemoteLoadStatus> remoteLoadStatusList;
bool remoteLoadSuccessful = true;
size_t remoteTasksInProgress = 0;
ComponentUpdateTask::Mode mode;
Net::Mode netmode;
};

View File

@ -6,138 +6,138 @@
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
DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar"
DEBUG 1 "org.gradle.test.classifiers"
DEBUG 2 "service"
DEBUG 3 "1.0"
DEBUG 4 ":jdk15"
DEBUG 5 "jdk15"
DEBUG 6 "@jar"
DEBUG 7 "jar"
*/
QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?");
m_valid = matcher.exactMatch(value);
auto elements = matcher.capturedTexts();
m_groupId = elements[1];
m_artifactId = elements[2];
m_version = elements[3];
m_classifier = elements[5];
if(!elements[7].isEmpty())
{
m_extension = elements[7];
}
return *this;
}
operator QString() const
{
if(!m_valid)
return "INVALID";
QString retval = m_groupId + ":" + m_artifactId + ":" + m_version;
if(!m_classifier.isEmpty())
{
retval += ":" + m_classifier;
}
if(m_extension.isExplicit())
{
retval += "@" + m_extension;
}
return retval;
}
QString getFileName() const
{
QString filename = m_artifactId + '-' + m_version;
if(!m_classifier.isEmpty())
{
filename += "-" + m_classifier;
}
filename += "." + m_extension;
return filename;
}
QString toPath(const QString & filenameOverride = QString()) const
{
if(!m_valid)
return "INVALID";
QString filename;
if(filenameOverride.isEmpty())
{
filename = getFileName();
}
else
{
filename = filenameOverride;
}
QString path = m_groupId;
path.replace('.', '/');
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
{
return other.artifactId() == artifactId() && other.groupId() == groupId();
}
bool operator==(const GradleSpecifier & other) const
{
if(m_groupId != other.m_groupId)
return false;
if(m_artifactId != other.m_artifactId)
return false;
if(m_version != other.m_version)
return false;
if(m_classifier != other.m_classifier)
return false;
if(m_extension != other.m_extension)
return false;
return true;
}
GradleSpecifier()
{
m_valid = false;
}
GradleSpecifier(QString value)
{
operator=(value);
}
GradleSpecifier & operator =(const QString & value)
{
/*
org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar
DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar"
DEBUG 1 "org.gradle.test.classifiers"
DEBUG 2 "service"
DEBUG 3 "1.0"
DEBUG 4 ":jdk15"
DEBUG 5 "jdk15"
DEBUG 6 "@jar"
DEBUG 7 "jar"
*/
QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?");
m_valid = matcher.exactMatch(value);
auto elements = matcher.capturedTexts();
m_groupId = elements[1];
m_artifactId = elements[2];
m_version = elements[3];
m_classifier = elements[5];
if(!elements[7].isEmpty())
{
m_extension = elements[7];
}
return *this;
}
operator QString() const
{
if(!m_valid)
return "INVALID";
QString retval = m_groupId + ":" + m_artifactId + ":" + m_version;
if(!m_classifier.isEmpty())
{
retval += ":" + m_classifier;
}
if(m_extension.isExplicit())
{
retval += "@" + m_extension;
}
return retval;
}
QString getFileName() const
{
QString filename = m_artifactId + '-' + m_version;
if(!m_classifier.isEmpty())
{
filename += "-" + m_classifier;
}
filename += "." + m_extension;
return filename;
}
QString toPath(const QString & filenameOverride = QString()) const
{
if(!m_valid)
return "INVALID";
QString filename;
if(filenameOverride.isEmpty())
{
filename = getFileName();
}
else
{
filename = filenameOverride;
}
QString path = m_groupId;
path.replace('.', '/');
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
{
return other.artifactId() == artifactId() && other.groupId() == groupId();
}
bool operator==(const GradleSpecifier & other) const
{
if(m_groupId != other.m_groupId)
return false;
if(m_artifactId != other.m_artifactId)
return false;
if(m_version != other.m_version)
return false;
if(m_classifier != other.m_classifier)
return false;
if(m_extension != other.m_extension)
return false;
return true;
}
private:
QString m_groupId;
QString m_artifactId;
QString m_version;
QString m_classifier;
DefaultVariable<QString> m_extension = DefaultVariable<QString>("jar");
bool m_valid = false;
QString m_groupId;
QString m_artifactId;
QString m_version;
QString m_classifier;
DefaultVariable<QString> m_extension = DefaultVariable<QString>("jar");
bool m_valid = false;
};

View File

@ -5,71 +5,71 @@
class GradleSpecifierTest : public QObject
{
Q_OBJECT
Q_OBJECT
private
slots:
void initTestCase()
{
void initTestCase()
{
}
void cleanupTestCase()
{
}
void cleanupTestCase()
{
}
}
void test_Positive_data()
{
QTest::addColumn<QString>("through");
void test_Positive_data()
{
QTest::addColumn<QString>("through");
QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0";
QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15";
QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar";
QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar";
QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz";
}
void test_Positive()
{
QFETCH(QString, through);
QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0";
QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15";
QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar";
QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar";
QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz";
}
void test_Positive()
{
QFETCH(QString, through);
QString converted = GradleSpecifier(through);
QString converted = GradleSpecifier(through);
QCOMPARE(converted, through);
}
QCOMPARE(converted, through);
}
void test_Path_data()
{
QTest::addColumn<QString>("spec");
QTest::addColumn<QString>("expected");
void test_Path_data()
{
QTest::addColumn<QString>("spec");
QTest::addColumn<QString>("expected");
QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar";
QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad";
}
void test_Path()
{
QFETCH(QString, spec);
QFETCH(QString, expected);
QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar";
QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad";
}
void test_Path()
{
QFETCH(QString, spec);
QFETCH(QString, expected);
QString converted = GradleSpecifier(spec).toPath();
QString converted = GradleSpecifier(spec).toPath();
QCOMPARE(converted, expected);
}
void test_Negative_data()
{
QTest::addColumn<QString>("input");
QCOMPARE(converted, expected);
}
void test_Negative_data()
{
QTest::addColumn<QString>("input");
QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::";
QTest::newRow("nonsense") << "I like turtles";
QTest::newRow("empty string") << "";
QTest::newRow("missing version") << "herp.derp:artifact";
}
void test_Negative()
{
QFETCH(QString, input);
QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::";
QTest::newRow("nonsense") << "I like turtles";
QTest::newRow("empty string") << "";
QTest::newRow("missing version") << "herp.derp:artifact";
}
void test_Negative()
{
QFETCH(QString, input);
GradleSpecifier spec(input);
QVERIFY(!spec.valid());
QCOMPARE(spec.operator QString(), QString("INVALID"));
}
GradleSpecifier spec(input);
QVERIFY(!spec.valid());
QCOMPARE(spec.operator QString(), QString("INVALID"));
}
};
QTEST_GUILESS_MAIN(GradleSpecifierTest)

View File

@ -3,295 +3,295 @@
void LaunchProfile::clear()
{
m_minecraftVersion.clear();
m_minecraftVersionType.clear();
m_minecraftAssets.reset();
m_minecraftArguments.clear();
m_tweakers.clear();
m_mainClass.clear();
m_appletClass.clear();
m_libraries.clear();
m_traits.clear();
m_jarMods.clear();
m_mainJar.reset();
m_problemSeverity = ProblemSeverity::None;
m_minecraftVersion.clear();
m_minecraftVersionType.clear();
m_minecraftAssets.reset();
m_minecraftArguments.clear();
m_tweakers.clear();
m_mainClass.clear();
m_appletClass.clear();
m_libraries.clear();
m_traits.clear();
m_jarMods.clear();
m_mainJar.reset();
m_problemSeverity = ProblemSeverity::None;
}
static void applyString(const QString & from, QString & to)
{
if(from.isEmpty())
return;
to = from;
if(from.isEmpty())
return;
to = from;
}
void LaunchProfile::applyMinecraftVersion(const QString& id)
{
applyString(id, this->m_minecraftVersion);
applyString(id, this->m_minecraftVersion);
}
void LaunchProfile::applyAppletClass(const QString& appletClass)
{
applyString(appletClass, this->m_appletClass);
applyString(appletClass, this->m_appletClass);
}
void LaunchProfile::applyMainClass(const QString& mainClass)
{
applyString(mainClass, this->m_mainClass);
applyString(mainClass, this->m_mainClass);
}
void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments)
{
applyString(minecraftArguments, this->m_minecraftArguments);
applyString(minecraftArguments, this->m_minecraftArguments);
}
void LaunchProfile::applyMinecraftVersionType(const QString& type)
{
applyString(type, this->m_minecraftVersionType);
applyString(type, this->m_minecraftVersionType);
}
void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets)
{
if(assets)
{
m_minecraftAssets = assets;
}
if(assets)
{
m_minecraftAssets = assets;
}
}
void LaunchProfile::applyTraits(const QSet<QString>& traits)
{
this->m_traits.unite(traits);
this->m_traits.unite(traits);
}
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))
{
continue;
}
newTweakers.append(tweaker);
}
// then just append the new tweakers (or moved original ones)
newTweakers += tweakers;
m_tweakers = newTweakers;
// 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))
{
continue;
}
newTweakers.append(tweaker);
}
// then just append the new tweakers (or moved original ones)
newTweakers += tweakers;
m_tweakers = newTweakers;
}
void LaunchProfile::applyJarMods(const QList<LibraryPtr>& jarMods)
{
this->m_jarMods.append(jarMods);
this->m_jarMods.append(jarMods);
}
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))
{
// only one is allowed.
if (retval != -1)
return -1;
retval = i;
}
}
return retval;
int retval = -1;
for (int i = 0; i < haystack->size(); ++i)
{
if (haystack->at(i)->rawName().matchName(needle))
{
// only one is allowed.
if (retval != -1)
return -1;
retval = i;
}
}
return retval;
}
void LaunchProfile::applyMods(const QList<LibraryPtr>& mods)
{
QList<LibraryPtr> * list = &m_mods;
for(auto & mod: mods)
{
auto modCopy = Library::limitedCopy(mod);
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)
{
list->append(modCopy);
return;
}
// find the mod by name.
const int index = findLibraryByName(list, mod->rawName());
// mod not found? just add it.
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()))
{
list->replace(index, modCopy);
}
}
auto existingLibrary = list->at(index);
// if we are higher it means we should update
if (Version(mod->version()) > Version(existingLibrary->version()))
{
list->replace(index, modCopy);
}
}
}
void LaunchProfile::applyLibrary(LibraryPtr library)
{
if(!library->isActive())
{
return;
}
if(!library->isActive())
{
return;
}
QList<LibraryPtr> * list = &m_libraries;
if(library->isNative())
{
list = &m_nativeLibraries;
}
QList<LibraryPtr> * list = &m_libraries;
if(library->isNative())
{
list = &m_nativeLibraries;
}
auto libraryCopy = Library::limitedCopy(library);
auto libraryCopy = Library::limitedCopy(library);
// find the library by name.
const int index = findLibraryByName(list, library->rawName());
// library not found? just add it.
if (index < 0)
{
list->append(libraryCopy);
return;
}
// find the library by name.
const int index = findLibraryByName(list, library->rawName());
// library not found? just add it.
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()))
{
list->replace(index, libraryCopy);
}
auto existingLibrary = list->at(index);
// if we are higher it means we should update
if (Version(library->version()) > Version(existingLibrary->version()))
{
list->replace(index, libraryCopy);
}
}
const LibraryPtr LaunchProfile::getMainJar() const
{
return m_mainJar;
return m_mainJar;
}
void LaunchProfile::applyMainJar(LibraryPtr jar)
{
if(jar)
{
m_mainJar = jar;
}
if(jar)
{
m_mainJar = jar;
}
}
void LaunchProfile::applyProblemSeverity(ProblemSeverity severity)
{
if (m_problemSeverity < severity)
{
m_problemSeverity = severity;
}
if (m_problemSeverity < severity)
{
m_problemSeverity = severity;
}
}
const QList<PatchProblem> LaunchProfile::getProblems() const
{
// FIXME: implement something that actually makes sense here
return {};
// FIXME: implement something that actually makes sense here
return {};
}
QString LaunchProfile::getMinecraftVersion() const
{
return m_minecraftVersion;
return m_minecraftVersion;
}
QString LaunchProfile::getAppletClass() const
{
return m_appletClass;
return m_appletClass;
}
QString LaunchProfile::getMainClass() const
{
return m_mainClass;
return m_mainClass;
}
const QSet<QString> &LaunchProfile::getTraits() const
{
return m_traits;
return m_traits;
}
const QStringList & LaunchProfile::getTweakers() const
{
return m_tweakers;
return m_tweakers;
}
bool LaunchProfile::hasTrait(const QString& trait) const
{
return m_traits.contains(trait);
return m_traits.contains(trait);
}
ProblemSeverity LaunchProfile::getProblemSeverity() const
{
return m_problemSeverity;
return m_problemSeverity;
}
QString LaunchProfile::getMinecraftVersionType() const
{
return m_minecraftVersionType;
return m_minecraftVersionType;
}
std::shared_ptr<MojangAssetIndexInfo> LaunchProfile::getMinecraftAssets() const
{
if(!m_minecraftAssets)
{
return std::make_shared<MojangAssetIndexInfo>("legacy");
}
return m_minecraftAssets;
if(!m_minecraftAssets)
{
return std::make_shared<MojangAssetIndexInfo>("legacy");
}
return m_minecraftAssets;
}
QString LaunchProfile::getMinecraftArguments() const
{
return m_minecraftArguments;
return m_minecraftArguments;
}
const QList<LibraryPtr> & LaunchProfile::getJarMods() const
{
return m_jarMods;
return m_jarMods;
}
const QList<LibraryPtr> & LaunchProfile::getLibraries() const
{
return m_libraries;
return m_libraries;
}
const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const
{
return m_nativeLibraries;
return m_nativeLibraries;
}
void LaunchProfile::getLibraryFiles(
const QString& architecture,
QStringList& jars,
QStringList& nativeJars,
const QString& overridePath,
const QString& tempPath
const QString& architecture,
QStringList& jars,
QStringList& nativeJars,
const QString& overridePath,
const QString& tempPath
) const
{
QStringList native32, native64;
jars.clear();
nativeJars.clear();
for (auto lib : getLibraries())
{
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
// NOTE: order is important here, add main jar last to the lists
if(m_mainJar)
{
// FIXME: HACK!! jar modding is weird and unsystematic!
if(m_jarMods.size())
{
QDir tempDir(tempPath);
jars.append(tempDir.absoluteFilePath("minecraft.jar"));
}
else
{
m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
}
for (auto lib : getNativeLibraries())
{
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
if(architecture == "32")
{
nativeJars.append(native32);
}
else if(architecture == "64")
{
nativeJars.append(native64);
}
QStringList native32, native64;
jars.clear();
nativeJars.clear();
for (auto lib : getLibraries())
{
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
// NOTE: order is important here, add main jar last to the lists
if(m_mainJar)
{
// FIXME: HACK!! jar modding is weird and unsystematic!
if(m_jarMods.size())
{
QDir tempDir(tempPath);
jars.append(tempDir.absoluteFilePath("minecraft.jar"));
}
else
{
m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
}
for (auto lib : getNativeLibraries())
{
lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
}
if(architecture == "32")
{
nativeJars.append(native32);
}
else if(architecture == "64")
{
nativeJars.append(native64);
}
}

View File

@ -6,94 +6,94 @@
class LaunchProfile: public ProblemProvider
{
public:
virtual ~LaunchProfile() {};
virtual ~LaunchProfile() {};
public: /* application of profile variables from patches */
void applyMinecraftVersion(const QString& id);
void applyMainClass(const QString& mainClass);
void applyAppletClass(const QString& appletClass);
void applyMinecraftArguments(const QString& 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);
void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile
void clear();
void applyMinecraftVersion(const QString& id);
void applyMainClass(const QString& mainClass);
void applyAppletClass(const QString& appletClass);
void applyMinecraftArguments(const QString& 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);
void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile
void clear();
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 QSet<QString> & getTraits() const;
const QStringList & getTweakers() const;
const QList<LibraryPtr> & getJarMods() const;
const QList<LibraryPtr> & getLibraries() const;
const QList<LibraryPtr> & getNativeLibraries() const;
const LibraryPtr getMainJar() const;
void getLibraryFiles(
const QString & architecture,
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;
QString getMinecraftVersion() const;
QString getMainClass() const;
QString getAppletClass() const;
QString getMinecraftVersionType() const;
MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
QString getMinecraftArguments() 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 LibraryPtr getMainJar() const;
void getLibraryFiles(
const QString & architecture,
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:
/// the version of Minecraft - jar to use
QString m_minecraftVersion;
/// the version of Minecraft - jar to use
QString m_minecraftVersion;
/// Release type - "release" or "snapshot"
QString m_minecraftVersionType;
/// Release type - "release" or "snapshot"
QString m_minecraftVersionType;
/// Assets type - "legacy" or a version ID
MojangAssetIndexInfo::Ptr m_minecraftAssets;
/// Assets type - "legacy" or a version ID
MojangAssetIndexInfo::Ptr m_minecraftAssets;
/**
* arguments that should be used for launching minecraft
*
* ex: "--username ${auth_player_name} --session ${auth_session}
* --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}"
*/
QString m_minecraftArguments;
/**
* arguments that should be used for launching minecraft
*
* ex: "--username ${auth_player_name} --session ${auth_session}
* --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}"
*/
QString m_minecraftArguments;
/// A list of all tweaker classes
QStringList m_tweakers;
/// A list of all tweaker classes
QStringList m_tweakers;
/// The main class to load first
QString m_mainClass;
/// The main class to load first
QString m_mainClass;
/// The applet class, for some very old minecraft releases
QString m_appletClass;
/// The applet class, for some very old minecraft releases
QString m_appletClass;
/// the list of libraries
QList<LibraryPtr> m_libraries;
/// the list of libraries
QList<LibraryPtr> m_libraries;
/// the main jar
LibraryPtr m_mainJar;
/// the main jar
LibraryPtr m_mainJar;
/// the list of libraries
QList<LibraryPtr> m_nativeLibraries;
/// the list of libraries
QList<LibraryPtr> m_nativeLibraries;
/// traits, collected from all the version files (version files can only add)
QSet<QString> m_traits;
/// traits, collected from all the version files (version files can only add)
QSet<QString> m_traits;
/// A list of jar mods. version files can add those.
QList<LibraryPtr> m_jarMods;
/// A list of jar mods. version files can add those.
QList<LibraryPtr> m_jarMods;
/// the list of mods
QList<LibraryPtr> m_mods;
/// the list of mods
QList<LibraryPtr> m_mods;
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
};

View File

@ -9,316 +9,316 @@
void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32,
QStringList& native64, const QString &overridePath) const
QStringList& native64, const QString &overridePath) const
{
bool local = isLocal();
auto actualPath = [&](QString relPath)
{
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
if(local && !overridePath.isEmpty())
{
QString fileName = out.fileName();
auto fullPath = FS::PathCombine(overridePath, fileName);
qDebug() << fullPath;
QFileInfo fileinfo(fullPath);
if(fileinfo.exists())
{
return fileinfo.absoluteFilePath();
}
}
return out.absoluteFilePath();
};
QString raw_storage = storageSuffix(system);
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
{
native += actualPath(raw_storage);
}
}
else
{
jar += actualPath(raw_storage);
}
bool local = isLocal();
auto actualPath = [&](QString relPath)
{
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
if(local && !overridePath.isEmpty())
{
QString fileName = out.fileName();
auto fullPath = FS::PathCombine(overridePath, fileName);
qDebug() << fullPath;
QFileInfo fileinfo(fullPath);
if(fileinfo.exists())
{
return fileinfo.absoluteFilePath();
}
}
return out.absoluteFilePath();
};
QString raw_storage = storageSuffix(system);
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
{
native += actualPath(raw_storage);
}
}
else
{
jar += actualPath(raw_storage);
}
}
QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class HttpMetaCache* cache,
QStringList& failedFiles, const QString & overridePath) const
QStringList& failedFiles, const QString & overridePath) const
{
QList<NetActionPtr> out;
bool isAlwaysStale = (hint() == "always-stale");
bool local = isLocal();
bool isForge = (hint() == "forge-pack-xz");
QList<NetActionPtr> out;
bool isAlwaysStale = (hint() == "always-stale");
bool local = isLocal();
bool isForge = (hint() == "forge-pack-xz");
auto add_download = [&](QString storage, QString url, QString sha1)
{
auto entry = cache->resolveEntry("libraries", storage);
if(isAlwaysStale)
{
entry->setStale(true);
}
if (!entry->isStale())
return true;
if(local)
{
if(!overridePath.isEmpty())
{
QString fileName;
int position = storage.lastIndexOf('/');
if(position == -1)
{
fileName = storage;
}
else
{
fileName = storage.mid(position);
}
auto fullPath = FS::PathCombine(overridePath, fileName);
QFileInfo fileinfo(fullPath);
if(fileinfo.exists())
{
return true;
}
}
QFileInfo fileinfo(entry->getFullPath());
if(!fileinfo.exists())
{
failedFiles.append(entry->getFullPath());
return false;
}
return true;
}
Net::Download::Options options;
if(isAlwaysStale)
{
options |= Net::Download::Option::AcceptLocalFiles;
}
if (isForge)
{
qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url;
out.append(ForgeXzDownload::make(storage, entry));
}
else
{
if(sha1.size())
{
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::Download::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url;
out.append(dl);
}
else
{
out.append(Net::Download::makeCached(url, entry, options));
qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url;
}
}
return true;
};
auto add_download = [&](QString storage, QString url, QString sha1)
{
auto entry = cache->resolveEntry("libraries", storage);
if(isAlwaysStale)
{
entry->setStale(true);
}
if (!entry->isStale())
return true;
if(local)
{
if(!overridePath.isEmpty())
{
QString fileName;
int position = storage.lastIndexOf('/');
if(position == -1)
{
fileName = storage;
}
else
{
fileName = storage.mid(position);
}
auto fullPath = FS::PathCombine(overridePath, fileName);
QFileInfo fileinfo(fullPath);
if(fileinfo.exists())
{
return true;
}
}
QFileInfo fileinfo(entry->getFullPath());
if(!fileinfo.exists())
{
failedFiles.append(entry->getFullPath());
return false;
}
return true;
}
Net::Download::Options options;
if(isAlwaysStale)
{
options |= Net::Download::Option::AcceptLocalFiles;
}
if (isForge)
{
qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url;
out.append(ForgeXzDownload::make(storage, entry));
}
else
{
if(sha1.size())
{
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::Download::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url;
out.append(dl);
}
else
{
out.append(Net::Download::makeCached(url, entry, options));
qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url;
}
}
return true;
};
QString raw_storage = storageSuffix(system);
if(m_mojangDownloads)
{
if(isNative())
{
if(m_nativeClassifiers.contains(system))
{
auto nativeClassifier = m_nativeClassifiers[system];
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)
{
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)
{
auto cooked_storage = raw_storage;
cooked_storage.replace("${arch}", "64");
add_download(cooked_storage, nat64info->url, nat64info->sha1);
}
}
else
{
auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
if(info)
{
add_download(raw_storage, info->url, info->sha1);
}
}
}
else
{
qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS";
}
}
else
{
if(m_mojangDownloads->artifact)
{
auto artifact = m_mojangDownloads->artifact;
add_download(raw_storage, artifact->url, artifact->sha1);
}
else
{
qDebug() << "Ignoring java library" << m_name << "because it has no artifact";
}
}
}
else
{
auto raw_dl = [&](){
if (!m_absoluteURL.isEmpty())
{
return m_absoluteURL;
}
QString raw_storage = storageSuffix(system);
if(m_mojangDownloads)
{
if(isNative())
{
if(m_nativeClassifiers.contains(system))
{
auto nativeClassifier = m_nativeClassifiers[system];
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)
{
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)
{
auto cooked_storage = raw_storage;
cooked_storage.replace("${arch}", "64");
add_download(cooked_storage, nat64info->url, nat64info->sha1);
}
}
else
{
auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
if(info)
{
add_download(raw_storage, info->url, info->sha1);
}
}
}
else
{
qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS";
}
}
else
{
if(m_mojangDownloads->artifact)
{
auto artifact = m_mojangDownloads->artifact;
add_download(raw_storage, artifact->url, artifact->sha1);
}
else
{
qDebug() << "Ignoring java library" << m_name << "because it has no artifact";
}
}
}
else
{
auto raw_dl = [&](){
if (!m_absoluteURL.isEmpty())
{
return m_absoluteURL;
}
if (m_repositoryURL.isEmpty())
{
return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage;
}
if (m_repositoryURL.isEmpty())
{
return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage;
}
if(m_repositoryURL.endsWith('/'))
{
return m_repositoryURL + raw_storage;
}
else
{
return m_repositoryURL + QChar('/') + raw_storage;
}
}();
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
{
add_download(raw_storage, raw_dl, QString());
}
}
return out;
if(m_repositoryURL.endsWith('/'))
{
return m_repositoryURL + raw_storage;
}
else
{
return m_repositoryURL + QChar('/') + raw_storage;
}
}();
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
{
add_download(raw_storage, raw_dl, QString());
}
}
return out;
}
bool Library::isActive() const
{
bool result = true;
if (m_rules.empty())
{
result = true;
}
else
{
RuleAction ruleResult = Disallow;
for (auto rule : m_rules)
{
RuleAction temp = rule->apply(this);
if (temp != Defer)
ruleResult = temp;
}
result = result && (ruleResult == Allow);
}
if (isNative())
{
result = result && m_nativeClassifiers.contains(currentSystem);
}
return result;
bool result = true;
if (m_rules.empty())
{
result = true;
}
else
{
RuleAction ruleResult = Disallow;
for (auto rule : m_rules)
{
RuleAction temp = rule->apply(this);
if (temp != Defer)
ruleResult = temp;
}
result = result && (ruleResult == Allow);
}
if (isNative())
{
result = result && m_nativeClassifiers.contains(currentSystem);
}
return result;
}
bool Library::isLocal() const
{
return m_hint == "local";
return m_hint == "local";
}
void Library::setStoragePrefix(QString prefix)
{
m_storagePrefix = prefix;
m_storagePrefix = prefix;
}
QString Library::defaultStoragePrefix()
{
return "libraries/";
return "libraries/";
}
QString Library::storagePrefix() const
{
if(m_storagePrefix.isEmpty())
{
return defaultStoragePrefix();
}
return m_storagePrefix;
if(m_storagePrefix.isEmpty())
{
return defaultStoragePrefix();
}
return m_storagePrefix;
}
QString Library::filename(OpSys system) const
{
if(!m_filename.isEmpty())
{
return m_filename;
}
// non-native? use only the gradle specifier
if (!isNative())
{
return m_name.getFileName();
}
if(!m_filename.isEmpty())
{
return m_filename;
}
// non-native? use only the gradle specifier
if (!isNative())
{
return m_name.getFileName();
}
// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{
nativeSpec.setClassifier("INVALID");
}
return nativeSpec.getFileName();
// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{
nativeSpec.setClassifier("INVALID");
}
return nativeSpec.getFileName();
}
QString Library::displayName(OpSys system) const
{
if(!m_displayname.isEmpty())
return m_displayname;
return filename(system);
if(!m_displayname.isEmpty())
return m_displayname;
return filename(system);
}
QString Library::storageSuffix(OpSys system) const
{
// non-native? use only the gradle specifier
if (!isNative())
{
return m_name.toPath(m_filename);
}
// non-native? use only the gradle specifier
if (!isNative())
{
return m_name.toPath(m_filename);
}
// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{
nativeSpec.setClassifier("INVALID");
}
return nativeSpec.toPath(m_filename);
// otherwise native, override classifiers. Mojang HACK!
GradleSpecifier nativeSpec = m_name;
if (m_nativeClassifiers.contains(system))
{
nativeSpec.setClassifier(m_nativeClassifiers[system]);
}
else
{
nativeSpec.setClassifier("INVALID");
}
return nativeSpec.toPath(m_filename);
}

View File

@ -24,191 +24,191 @@ typedef std::shared_ptr<Library> LibraryPtr;
class MULTIMC_LOGIC_EXPORT Library
{
friend class OneSixVersionFormat;
friend class MojangVersionFormat;
friend class LibraryTest;
friend class OneSixVersionFormat;
friend class MojangVersionFormat;
friend class LibraryTest;
public:
Library()
{
}
Library(const QString &name)
{
m_name = name;
}
/// limited copy without some data. TODO: why?
static LibraryPtr limitedCopy(LibraryPtr base)
{
auto newlib = std::make_shared<Library>();
newlib->m_name = base->m_name;
newlib->m_repositoryURL = base->m_repositoryURL;
newlib->m_hint = base->m_hint;
newlib->m_absoluteURL = base->m_absoluteURL;
newlib->m_extractExcludes = base->m_extractExcludes;
newlib->m_nativeClassifiers = base->m_nativeClassifiers;
newlib->m_rules = base->m_rules;
newlib->m_storagePrefix = base->m_storagePrefix;
newlib->m_mojangDownloads = base->m_mojangDownloads;
newlib->m_filename = base->m_filename;
return newlib;
}
Library()
{
}
Library(const QString &name)
{
m_name = name;
}
/// limited copy without some data. TODO: why?
static LibraryPtr limitedCopy(LibraryPtr base)
{
auto newlib = std::make_shared<Library>();
newlib->m_name = base->m_name;
newlib->m_repositoryURL = base->m_repositoryURL;
newlib->m_hint = base->m_hint;
newlib->m_absoluteURL = base->m_absoluteURL;
newlib->m_extractExcludes = base->m_extractExcludes;
newlib->m_nativeClassifiers = base->m_nativeClassifiers;
newlib->m_rules = base->m_rules;
newlib->m_storagePrefix = base->m_storagePrefix;
newlib->m_mojangDownloads = base->m_mojangDownloads;
newlib->m_filename = base->m_filename;
return newlib;
}
public: /* methods */
/// Returns the raw name field
const GradleSpecifier & rawName() const
{
return m_name;
}
/// Returns the raw name field
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();
}
/// returns the full group and artifact prefix
QString artifactPrefix() const
{
return m_name.artifactPrefix();
}
/// get the artifact ID
QString artifactId() const
{
return m_name.artifactId();
}
/// get the artifact ID
QString artifactId() const
{
return m_name.artifactId();
}
/// get the artifact version
QString version() const
{
return m_name.version();
}
/// get the artifact version
QString version() const
{
return m_name.version();
}
/// Returns true if the library is native
bool isNative() const
{
return m_nativeClassifiers.size() != 0;
}
/// Returns true if the library is native
bool isNative() const
{
return m_nativeClassifiers.size() != 0;
}
void setStoragePrefix(QString prefix = QString());
void setStoragePrefix(QString prefix = QString());
/// Set the url base for downloads
void setRepositoryURL(const QString &base_url)
{
m_repositoryURL = base_url;
}
/// Set the url base for downloads
void setRepositoryURL(const QString &base_url)
{
m_repositoryURL = base_url;
}
void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native,
QStringList & native32, QStringList & native64, const QString & overridePath) const;
void getApplicableFiles(OpSys system, 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(OpSys system) const;
/// Get the file name of the library
QString filename(OpSys system) const;
// DEPRECATED: set a display name, used by jar mods only
void setDisplayName(const QString & displayName)
{
m_displayname = displayName;
}
// DEPRECATED: set a display name, used by jar mods only
void setDisplayName(const QString & displayName)
{
m_displayname = displayName;
}
/// Get the file name of the library
QString displayName(OpSys system) const;
/// Get the file name of the library
QString displayName(OpSys system) 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;
}
/// Set the load 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;
/// Returns true if the library should be loaded (or extracted, in case of natives)
bool isActive() const;
/// Returns true if the library is contained in an instance and false if it is shared
bool isLocal() const;
/// Returns true if the library is contained in an instance and false if it is shared
bool isLocal() const;
// Get a list of downloads for this library
QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache,
QStringList & failedFiles, const QString & overridePath) const;
// Get a list of downloads for this library
QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache,
QStringList & failedFiles, const QString & overridePath) const;
private: /* methods */
/// the default storage prefix used by MultiMC
static QString defaultStoragePrefix();
/// the default storage prefix used by MultiMC
static QString defaultStoragePrefix();
/// Get the prefix - root of the storage to be used
QString storagePrefix() const;
/// Get the prefix - root of the storage to be used
QString storagePrefix() const;
/// Get the relative file path where the library should be saved
QString storageSuffix(OpSys system) const;
/// Get the relative file path where the library should be saved
QString storageSuffix(OpSys system) const;
QString hint() const
{
return m_hint;
}
QString hint() const
{
return m_hint;
}
protected: /* data */
/// the basic gradle dependency specifier.
GradleSpecifier m_name;
/// the basic gradle dependency specifier.
GradleSpecifier m_name;
/// DEPRECATED URL prefix of the maven repo where the file can be downloaded
QString m_repositoryURL;
/// DEPRECATED URL prefix of the maven repo where the file can be downloaded
QString m_repositoryURL;
/// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
QString m_absoluteURL;
/// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
QString m_absoluteURL;
/// MultiMC extension - filename override
QString m_filename;
/// MultiMC extension - filename override
QString m_filename;
/// DEPRECATED MultiMC extension - display name
QString m_displayname;
/// DEPRECATED MultiMC extension - display name
QString m_displayname;
/**
* MultiMC-specific type hint - modifies how the library is treated
*/
QString m_hint;
/**
* MultiMC-specific type hint - modifies how the library is treated
*/
QString m_hint;
/**
* storage - by default the local libraries folder in multimc, but could be elsewhere
* MultiMC specific, because of FTB.
*/
QString m_storagePrefix;
/**
* storage - by default the local libraries folder in multimc, but could be elsewhere
* MultiMC specific, because of FTB.
*/
QString m_storagePrefix;
/// true if the library had an extract/excludes section (even empty)
bool m_hasExcludes = false;
/// true if the library had an extract/excludes section (even empty)
bool m_hasExcludes = false;
/// a list of files that shouldn't be extracted from the library
QStringList m_extractExcludes;
/// a list of files that shouldn't be extracted from the library
QStringList m_extractExcludes;
/// native suffixes per OS
QMap<OpSys, QString> m_nativeClassifiers;
/// native suffixes per OS
QMap<OpSys, QString> m_nativeClassifiers;
/// true if the library had a rules section (even empty)
bool applyRules = false;
/// true if the library had a rules section (even empty)
bool applyRules = false;
/// rules associated with the library
QList<std::shared_ptr<Rule>> m_rules;
/// rules associated with the library
QList<std::shared_ptr<Rule>> m_rules;
/// MOJANG: container with Mojang style download info
MojangLibraryDownloadInfo::Ptr m_mojangDownloads;
/// MOJANG: container with Mojang style download info
MojangLibraryDownloadInfo::Ptr m_mojangDownloads;
};

View File

@ -9,261 +9,261 @@
class LibraryTest : public QObject
{
Q_OBJECT
Q_OBJECT
private:
LibraryPtr readMojangJson(const char *file)
{
auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file);
}
// get absolute path to expected storage, assuming default cache prefix
QStringList getStorage(QString relative)
{
return {FS::PathCombine(cache->getBasePath("libraries"), relative)};
}
LibraryPtr readMojangJson(const char *file)
{
auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file);
}
// get absolute path to expected storage, assuming default cache prefix
QStringList getStorage(QString relative)
{
return {FS::PathCombine(cache->getBasePath("libraries"), relative)};
}
private
slots:
void initTestCase()
{
cache.reset(new HttpMetaCache());
cache->addBase("libraries", QDir("libraries").absolutePath());
dataDir = QDir("data").absolutePath();
}
void test_legacy()
{
Library test("test.package:testname:testversion");
QCOMPARE(test.artifactPrefix(), QString("test.package:testname"));
QCOMPARE(test.isNative(), false);
void initTestCase()
{
cache.reset(new HttpMetaCache());
cache->addBase("libraries", QDir("libraries").absolutePath());
dataDir = QDir("data").absolutePath();
}
void test_legacy()
{
Library test("test.package:testname:testversion");
QCOMPARE(test.artifactPrefix(), QString("test.package:testname"));
QCOMPARE(test.isNative(), false);
QStringList jar, native, native32, native64;
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_url()
{
QStringList failedFiles;
Library test("test.package:testname:testversion");
test.setRepositoryURL("file://foo/bar");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 1);
QCOMPARE(failedFiles, {});
NetActionPtr dl = downloads[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
}
void test_legacy_url_local_broken()
{
Library test("test.package:testname:testversion");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 0);
QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar"));
}
void test_legacy_url_local_override()
{
Library test("com.paulscode:codecwav:20101023");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data"));
QCOMPARE(downloads.size(), 0);
qDebug() << failedFiles;
QCOMPARE(failedFiles.size(), 0);
QStringList jar, native, native32, native64;
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_url()
{
QStringList failedFiles;
Library test("test.package:testname:testversion");
test.setRepositoryURL("file://foo/bar");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 1);
QCOMPARE(failedFiles, {});
NetActionPtr dl = downloads[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar"));
}
void test_legacy_url_local_broken()
{
Library test("test.package:testname:testversion");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 0);
QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar"));
}
void test_legacy_url_local_override()
{
Library test("com.paulscode:codecwav:20101023");
QCOMPARE(test.isNative(), false);
QStringList failedFiles;
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data"));
QCOMPARE(downloads.size(), 0);
qDebug() << failedFiles;
QCOMPARE(failedFiles.size(), 0);
QStringList jar, native, native32, native64;
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_native()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
auto dl = dls[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
}
}
void test_legacy_native_arch()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}";
test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
}
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
}
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
}
}
void test_legacy_native_arch_local_override()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.setHint("local");
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()});
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")});
}
}
void test_onenine()
{
auto test = readMojangJson("data/lib-simple.json");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
}
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_local_override()
{
auto test = readMojangJson("data/lib-simple.json");
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_native()
{
auto test = readMojangJson("data/lib-native.json");
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, QStringList());
QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
}
void test_onenine_native_arch()
{
auto test = readMojangJson("data/lib-native-arch.json");
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
QStringList failedFiles;
auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
}
QStringList jar, native, native32, native64;
test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
void test_legacy_native()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
auto dl = dls[0];
QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar"));
}
}
void test_legacy_native_arch()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}";
test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}";
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar"));
}
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar"));
}
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar"));
}
}
void test_legacy_native_arch_local_override()
{
Library test("test.package:testname:testversion");
test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}";
test.setHint("local");
QCOMPARE(test.isNative(), true);
test.setRepositoryURL("file://foo/bar");
{
QStringList jar, native, native32, native64;
test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()});
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")});
}
}
void test_onenine()
{
auto test = readMojangJson("data/lib-simple.json");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"));
}
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_local_override()
{
auto test = readMojangJson("data/lib-simple.json");
test->setHint("local");
{
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data"));
QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()});
QCOMPARE(native, {});
QCOMPARE(native32, {});
QCOMPARE(native64, {});
}
{
QStringList failedFiles;
auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
QCOMPARE(failedFiles, {});
}
}
void test_onenine_native()
{
auto test = readMojangJson("data/lib-native.json");
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString());
QCOMPARE(jar, QStringList());
QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
QCOMPARE(native32, {});
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
}
void test_onenine_native_arch()
{
auto test = readMojangJson("data/lib-native-arch.json");
QStringList jar, native, native32, native64;
test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString());
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
QStringList failedFiles;
auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString());
QCOMPARE(dls.size(), 2);
QCOMPARE(failedFiles, {});
QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"));
QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"));
}
private:
std::unique_ptr<HttpMetaCache> cache;
QString dataDir;
std::unique_ptr<HttpMetaCache> cache;
QString dataDir;
};
QTEST_GUILESS_MAIN(LibraryTest)

File diff suppressed because it is too large Load Diff

View File

@ -14,115 +14,115 @@ class ComponentList;
class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
{
Q_OBJECT
Q_OBJECT
public:
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
virtual ~MinecraftInstance() {};
virtual void init() override;
virtual void saveNow() override;
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
virtual ~MinecraftInstance() {};
virtual void init() override;
virtual void saveNow() override;
// FIXME: remove
QString typeName() const override;
// FIXME: remove
QSet<QString> traits() const override;
// FIXME: remove
QString typeName() const override;
// 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;
QString resourcePacksDir() const;
QString texturePacksDir() const;
QString loaderModsDir() const;
QString coreModsDir() const;
////// Directories and files //////
QString jarModsDir() const;
QString resourcePacksDir() const;
QString texturePacksDir() const;
QString loaderModsDir() const;
QString coreModsDir() const;
QString modsCacheLocation() const;
QString libDir() const;
QString worldDir() const;
QDir jarmodsPath() const;
QDir librariesPath() const;
QDir versionsPath() const;
QString instanceConfigFolder() const override;
QString minecraftRoot() const; // Path to the instance's minecraft directory.
QString binRoot() const; // Path to the instance's minecraft bin directory.
QString getNativePath() const; // where to put the natives during/before launch
QString getLocalLibraryPath() const; // where the instance-local libraries should be
QString libDir() const;
QString worldDir() const;
QDir jarmodsPath() const;
QDir librariesPath() const;
QDir versionsPath() const;
QString instanceConfigFolder() const override;
QString minecraftRoot() const; // Path to the instance's minecraft directory.
QString binRoot() const; // Path to the instance's minecraft bin directory.
QString getNativePath() const; // where to put the natives during/before launch
QString getLocalLibraryPath() const; // where the instance-local libraries should be
////// Profile management //////
std::shared_ptr<ComponentList> getComponentList() const;
////// Profile management //////
std::shared_ptr<ComponentList> getComponentList() const;
////// Mod Lists //////
////// Mod Lists //////
std::shared_ptr<ModsModel> modsModel() const;
std::shared_ptr<SimpleModList> loaderModList() const;
std::shared_ptr<SimpleModList> coreModList() const;
std::shared_ptr<SimpleModList> resourcePackList() const;
std::shared_ptr<SimpleModList> texturePackList() const;
std::shared_ptr<WorldList> worldList() const;
std::shared_ptr<SimpleModList> loaderModList() const;
std::shared_ptr<SimpleModList> coreModList() const;
std::shared_ptr<SimpleModList> resourcePackList() const;
std::shared_ptr<SimpleModList> texturePackList() const;
std::shared_ptr<WorldList> worldList() const;
////// Launch stuff //////
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
QStringList extraArguments() const override;
QStringList verboseDescription(AuthSessionPtr session) override;
QList<Mod> getJarMods() const;
QString createLaunchScript(AuthSessionPtr session);
/// get arguments passed to java
QStringList javaArguments() const;
////// Launch stuff //////
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
QStringList extraArguments() const override;
QStringList verboseDescription(AuthSessionPtr session) override;
QList<Mod> getJarMods() const;
QString createLaunchScript(AuthSessionPtr session);
/// get arguments passed to java
QStringList javaArguments() const;
/// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() const override;
/// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() const override;
/// create an environment for launching processes
QProcessEnvironment createEnvironment() override;
/// create an environment for launching processes
QProcessEnvironment createEnvironment() override;
/// guess log level from a line of minecraft log
MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
/// guess log level from a line of minecraft log
MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
IPathMatcher::Ptr getLogFileMatcher() override;
IPathMatcher::Ptr getLogFileMatcher() override;
QString getLogFileRoot() override;
QString getLogFileRoot() override;
QString getStatusbarDescription() override;
QString getStatusbarDescription() override;
// FIXME: remove
virtual QStringList getClassPath() const;
// FIXME: remove
virtual QStringList getNativeJars() const;
// FIXME: remove
virtual QString getMainClass() const;
// FIXME: remove
virtual QStringList getClassPath() const;
// FIXME: remove
virtual QStringList getNativeJars() const;
// FIXME: remove
virtual QString getMainClass() const;
// FIXME: remove
virtual QStringList processMinecraftArgs(AuthSessionPtr account) const;
// FIXME: remove
virtual QStringList processMinecraftArgs(AuthSessionPtr account) const;
virtual JavaVersion getJavaVersion() const;
virtual JavaVersion getJavaVersion() const;
signals:
void versionReloaded();
void versionReloaded();
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
QStringList validLaunchMethods();
QString launchMethod();
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
QStringList validLaunchMethods();
QString launchMethod();
private:
QString prettifyTimeDuration(int64_t duration);
QString prettifyTimeDuration(int64_t duration);
protected: // data
std::shared_ptr<ComponentList> m_components;
mutable std::shared_ptr<ModsModel> m_mods_model;
mutable std::shared_ptr<SimpleModList> m_loader_mod_list;
mutable std::shared_ptr<SimpleModList> m_core_mod_list;
mutable std::shared_ptr<SimpleModList> m_resource_pack_list;
mutable std::shared_ptr<SimpleModList> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
std::shared_ptr<ComponentList> m_components;
mutable std::shared_ptr<ModsModel> m_mods_model;
mutable std::shared_ptr<SimpleModList> m_loader_mod_list;
mutable std::shared_ptr<SimpleModList> m_core_mod_list;
mutable std::shared_ptr<SimpleModList> m_resource_pack_list;
mutable std::shared_ptr<SimpleModList> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
};
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;

View File

@ -8,38 +8,38 @@ MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *p
void MinecraftLoadAndCheck::executeTask()
{
// add offline metadata load task
auto components = m_inst->getComponentList();
components->reload(Net::Mode::Offline);
m_task = components->getCurrentTask();
// add offline metadata load task
auto components = m_inst->getComponentList();
components->reload(Net::Mode::Offline);
m_task = components->getCurrentTask();
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::progress, this, &MinecraftLoadAndCheck::progress);
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
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::progress, this, &MinecraftLoadAndCheck::progress);
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
}
void MinecraftLoadAndCheck::subtaskSucceeded()
{
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
emitSucceeded();
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
emitSucceeded();
}
void MinecraftLoadAndCheck::subtaskFailed(QString error)
{
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
emitFailed(error);
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
emitFailed(error);
}

View File

@ -29,20 +29,20 @@ class MinecraftInstance;
class MinecraftLoadAndCheck : public Task
{
Q_OBJECT
Q_OBJECT
public:
explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
virtual ~MinecraftLoadAndCheck() {};
void executeTask() override;
explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
virtual ~MinecraftLoadAndCheck() {};
void executeTask() override;
private slots:
void subtaskSucceeded();
void subtaskFailed(QString error);
void subtaskSucceeded();
void subtaskFailed(QString error);
private:
MinecraftInstance *m_inst = nullptr;
shared_qobject_ptr<Task> m_task;
QString m_preFailure;
QString m_fail_reason;
MinecraftInstance *m_inst = nullptr;
shared_qobject_ptr<Task> m_task;
QString m_preFailure;
QString m_fail_reason;
};

View File

@ -43,142 +43,142 @@ OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(pare
void OneSixUpdate::executeTask()
{
m_tasks.clear();
// create folders
{
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
}
m_tasks.clear();
// create folders
{
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
}
// add metadata update task if necessary
{
auto components = m_inst->getComponentList();
components->reload(Net::Mode::Online);
auto task = components->getCurrentTask();
if(task)
{
m_tasks.append(task.unwrap());
}
}
// add metadata update task if necessary
{
auto components = m_inst->getComponentList();
components->reload(Net::Mode::Online);
auto task = components->getCurrentTask();
if(task)
{
m_tasks.append(task.unwrap());
}
}
// libraries download
{
m_tasks.append(std::make_shared<LibrariesTask>(m_inst));
}
// libraries download
{
m_tasks.append(std::make_shared<LibrariesTask>(m_inst));
}
// FML libraries download and copy into the instance
{
m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst));
}
// FML libraries download and copy into the instance
{
m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst));
}
// assets update
{
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
}
// assets update
{
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
}
if(!m_preFailure.isEmpty())
{
emitFailed(m_preFailure);
return;
}
next();
if(!m_preFailure.isEmpty())
{
emitFailed(m_preFailure);
return;
}
next();
}
void OneSixUpdate::next()
{
if(m_abort)
{
emitFailed(tr("Aborted by user."));
return;
}
if(m_failed_out_of_order)
{
emitFailed(m_fail_reason);
return;
}
m_currentTask ++;
if(m_currentTask > 0)
{
auto task = m_tasks[m_currentTask - 1];
disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
}
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())
{
qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
next();
}
connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
connect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
// if the task is already running, do not start it again
if(!task->isRunning())
{
task->start();
}
if(m_abort)
{
emitFailed(tr("Aborted by user."));
return;
}
if(m_failed_out_of_order)
{
emitFailed(m_fail_reason);
return;
}
m_currentTask ++;
if(m_currentTask > 0)
{
auto task = m_tasks[m_currentTask - 1];
disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
}
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())
{
qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
next();
}
connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
connect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
// if the task is already running, do not start it again
if(!task->isRunning())
{
task->start();
}
}
void OneSixUpdate::subtaskSucceeded()
{
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if(senderTask != currentTask)
{
qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order.";
return;
}
next();
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if(senderTask != currentTask)
{
qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order.";
return;
}
next();
}
void OneSixUpdate::subtaskFailed(QString error)
{
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if(senderTask != currentTask)
{
qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order.";
m_failed_out_of_order = true;
m_fail_reason = error;
return;
}
emitFailed(error);
if(isFinished())
{
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if(senderTask != currentTask)
{
qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order.";
m_failed_out_of_order = true;
m_fail_reason = error;
return;
}
emitFailed(error);
}
bool OneSixUpdate::abort()
{
if(!m_abort)
{
m_abort = true;
auto task = m_tasks[m_currentTask];
if(task->canAbort())
{
return task->abort();
}
}
return true;
if(!m_abort)
{
m_abort = true;
auto task = m_tasks[m_currentTask];
if(task->canAbort())
{
return task->abort();
}
}
return true;
}
bool OneSixUpdate::canAbort() const
{
return true;
return true;
}

View File

@ -29,29 +29,29 @@ class MinecraftInstance;
class OneSixUpdate : public Task
{
Q_OBJECT
Q_OBJECT
public:
explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0);
virtual ~OneSixUpdate() {};
explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0);
virtual ~OneSixUpdate() {};
void executeTask() override;
bool canAbort() const override;
void executeTask() override;
bool canAbort() const override;
private
slots:
bool abort() override;
void subtaskSucceeded();
void subtaskFailed(QString error);
bool abort() override;
void subtaskSucceeded();
void subtaskFailed(QString error);
private:
void next();
void next();
private:
MinecraftInstance *m_inst = nullptr;
QList<std::shared_ptr<Task>> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;
bool m_failed_out_of_order = false;
QString m_fail_reason;
MinecraftInstance *m_inst = nullptr;
QList<std::shared_ptr<Task>> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;
bool m_failed_out_of_order = false;
QString m_fail_reason;
};

View File

@ -29,124 +29,124 @@
Mod::Mod(const QFileInfo &file)
{
repath(file);
m_changedDateTime = file.lastModified();
repath(file);
m_changedDateTime = file.lastModified();
}
void Mod::repath(const QFileInfo &file)
{
m_file = file;
QString name_base = file.fileName();
m_file = file;
QString name_base = file.fileName();
m_type = Mod::MOD_UNKNOWN;
m_type = Mod::MOD_UNKNOWN;
if (m_file.isDir())
{
m_type = MOD_FOLDER;
m_name = name_base;
m_mmc_id = name_base;
}
else if (m_file.isFile())
{
if (name_base.endsWith(".disabled"))
{
m_enabled = false;
name_base.chop(9);
}
else
{
m_enabled = true;
}
m_mmc_id = name_base;
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
{
m_type = MOD_ZIPFILE;
name_base.chop(4);
}
else if (name_base.endsWith(".litemod"))
{
m_type = MOD_LITEMOD;
name_base.chop(8);
}
else
{
m_type = MOD_SINGLEFILE;
}
m_name = name_base;
}
if (m_file.isDir())
{
m_type = MOD_FOLDER;
m_name = name_base;
m_mmc_id = name_base;
}
else if (m_file.isFile())
{
if (name_base.endsWith(".disabled"))
{
m_enabled = false;
name_base.chop(9);
}
else
{
m_enabled = true;
}
m_mmc_id = name_base;
if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
{
m_type = MOD_ZIPFILE;
name_base.chop(4);
}
else if (name_base.endsWith(".litemod"))
{
m_type = MOD_LITEMOD;
name_base.chop(8);
}
else
{
m_type = MOD_SINGLEFILE;
}
m_name = name_base;
}
if (m_type == MOD_ZIPFILE)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
if (m_type == MOD_ZIPFILE)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
QuaZipFile file(&zip);
if (zip.setCurrentFile("mcmod.info"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
if (zip.setCurrentFile("mcmod.info"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadMCModInfo(file.readAll());
file.close();
zip.close();
return;
}
else if (zip.setCurrentFile("forgeversion.properties"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadMCModInfo(file.readAll());
file.close();
zip.close();
return;
}
else if (zip.setCurrentFile("forgeversion.properties"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadForgeInfo(file.readAll());
file.close();
zip.close();
return;
}
ReadForgeInfo(file.readAll());
file.close();
zip.close();
return;
}
zip.close();
}
else if (m_type == MOD_FOLDER)
{
QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info"));
if (mcmod_info.isFile())
{
QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly))
return;
auto data = mcmod.readAll();
if (data.isEmpty() || data.isNull())
return;
ReadMCModInfo(data);
}
}
else if (m_type == MOD_LITEMOD)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
zip.close();
}
else if (m_type == MOD_FOLDER)
{
QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info"));
if (mcmod_info.isFile())
{
QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly))
return;
auto data = mcmod.readAll();
if (data.isEmpty() || data.isNull())
return;
ReadMCModInfo(data);
}
}
else if (m_type == MOD_LITEMOD)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
if (zip.setCurrentFile("litemod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadLiteModInfo(file.readAll());
file.close();
}
zip.close();
}
ReadLiteModInfo(file.readAll());
file.close();
}
zip.close();
}
}
// NEW format
@ -156,223 +156,223 @@ void Mod::repath(const QFileInfo &file)
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
void Mod::ReadMCModInfo(QByteArray contents)
{
auto getInfoFromArray = [&](QJsonArray arr)->void
{
if (!arr.at(0).isObject())
return;
auto firstObj = arr.at(0).toObject();
m_mod_id = firstObj.value("modid").toString();
m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString();
m_updateurl = firstObj.value("updateUrl").toString();
m_homeurl = m_homeurl.trimmed();
if(!m_homeurl.isEmpty())
{
// fix up url.
if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
!m_homeurl.startsWith("ftp://"))
{
m_homeurl.prepend("http://");
}
}
m_description = firstObj.value("description").toString();
QJsonArray authors = firstObj.value("authorList").toArray();
if (authors.size() == 0)
authors = firstObj.value("authors").toArray();
auto getInfoFromArray = [&](QJsonArray arr)->void
{
if (!arr.at(0).isObject())
return;
auto firstObj = arr.at(0).toObject();
m_mod_id = firstObj.value("modid").toString();
m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString();
m_updateurl = firstObj.value("updateUrl").toString();
m_homeurl = m_homeurl.trimmed();
if(!m_homeurl.isEmpty())
{
// fix up url.
if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
!m_homeurl.startsWith("ftp://"))
{
m_homeurl.prepend("http://");
}
}
m_description = firstObj.value("description").toString();
QJsonArray authors = firstObj.value("authorList").toArray();
if (authors.size() == 0)
authors = firstObj.value("authors").toArray();
if (authors.size() == 0)
m_authors = "";
else if (authors.size() >= 1)
{
m_authors = authors.at(0).toString();
for (int i = 1; i < authors.size(); i++)
{
m_authors += ", " + authors.at(i).toString();
}
}
m_credits = firstObj.value("credits").toString();
return;
}
;
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array
if (jsonDoc.isArray())
{
getInfoFromArray(jsonDoc.array());
}
else if (jsonDoc.isObject())
{
auto val = jsonDoc.object().value("modinfoversion");
if(val.isUndefined())
val = jsonDoc.object().value("modListVersion");
int version = val.toDouble();
if (version != 2)
{
qCritical() << "BAD stuff happened to mod json:";
qCritical() << contents;
return;
}
auto arrVal = jsonDoc.object().value("modlist");
if(arrVal.isUndefined())
arrVal = jsonDoc.object().value("modList");
if (arrVal.isArray())
{
getInfoFromArray(arrVal.toArray());
}
}
if (authors.size() == 0)
m_authors = "";
else if (authors.size() >= 1)
{
m_authors = authors.at(0).toString();
for (int i = 1; i < authors.size(); i++)
{
m_authors += ", " + authors.at(i).toString();
}
}
m_credits = firstObj.value("credits").toString();
return;
}
;
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array
if (jsonDoc.isArray())
{
getInfoFromArray(jsonDoc.array());
}
else if (jsonDoc.isObject())
{
auto val = jsonDoc.object().value("modinfoversion");
if(val.isUndefined())
val = jsonDoc.object().value("modListVersion");
int version = val.toDouble();
if (version != 2)
{
qCritical() << "BAD stuff happened to mod json:";
qCritical() << contents;
return;
}
auto arrVal = jsonDoc.object().value("modlist");
if(arrVal.isUndefined())
arrVal = jsonDoc.object().value("modList");
if (arrVal.isArray())
{
getInfoFromArray(arrVal.toArray());
}
}
}
void Mod::ReadForgeInfo(QByteArray contents)
{
// Read the data
m_name = "Minecraft Forge";
m_mod_id = "Forge";
m_homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
if (!ini.loadFile(contents))
return;
// Read the data
m_name = "Minecraft Forge";
m_mod_id = "Forge";
m_homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
if (!ini.loadFile(contents))
return;
QString major = ini.get("forge.major.number", "0").toString();
QString minor = ini.get("forge.minor.number", "0").toString();
QString revision = ini.get("forge.revision.number", "0").toString();
QString build = ini.get("forge.build.number", "0").toString();
QString major = ini.get("forge.major.number", "0").toString();
QString minor = ini.get("forge.minor.number", "0").toString();
QString revision = ini.get("forge.revision.number", "0").toString();
QString build = ini.get("forge.build.number", "0").toString();
m_version = major + "." + minor + "." + revision + "." + build;
m_version = major + "." + minor + "." + revision + "." + build;
}
void Mod::ReadLiteModInfo(QByteArray contents)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object();
if (object.contains("name"))
{
m_mod_id = m_name = object.value("name").toString();
}
if (object.contains("version"))
{
m_version = object.value("version").toString("");
}
else
{
m_version = object.value("revision").toString("");
}
m_mcversion = object.value("mcversion").toString();
m_authors = object.value("author").toString();
m_description = object.value("description").toString();
m_homeurl = object.value("url").toString();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
auto object = jsonDoc.object();
if (object.contains("name"))
{
m_mod_id = m_name = object.value("name").toString();
}
if (object.contains("version"))
{
m_version = object.value("version").toString("");
}
else
{
m_version = object.value("revision").toString("");
}
m_mcversion = object.value("mcversion").toString();
m_authors = object.value("author").toString();
m_description = object.value("description").toString();
m_homeurl = object.value("url").toString();
}
bool Mod::replace(Mod &with)
{
if (!destroy())
return false;
bool success = false;
auto t = with.type();
if (!destroy())
return false;
bool success = false;
auto t = with.type();
if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD)
{
qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
success = QFile::copy(with.m_file.filePath(), m_file.filePath());
}
if (t == MOD_FOLDER)
{
success = FS::copy(with.m_file.filePath(), m_file.path())();
}
if (success)
{
m_name = with.m_name;
m_mmc_id = with.m_mmc_id;
m_mod_id = with.m_mod_id;
m_version = with.m_version;
m_mcversion = with.m_mcversion;
m_description = with.m_description;
m_authors = with.m_authors;
m_credits = with.m_credits;
m_homeurl = with.m_homeurl;
m_type = with.m_type;
m_file.refresh();
}
return success;
if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD)
{
qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
success = QFile::copy(with.m_file.filePath(), m_file.filePath());
}
if (t == MOD_FOLDER)
{
success = FS::copy(with.m_file.filePath(), m_file.path())();
}
if (success)
{
m_name = with.m_name;
m_mmc_id = with.m_mmc_id;
m_mod_id = with.m_mod_id;
m_version = with.m_version;
m_mcversion = with.m_mcversion;
m_description = with.m_description;
m_authors = with.m_authors;
m_credits = with.m_credits;
m_homeurl = with.m_homeurl;
m_type = with.m_type;
m_file.refresh();
}
return success;
}
bool Mod::destroy()
{
if (m_type == MOD_FOLDER)
{
QDir d(m_file.filePath());
if (d.removeRecursively())
{
m_type = MOD_UNKNOWN;
return true;
}
return false;
}
else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
{
QFile f(m_file.filePath());
if (f.remove())
{
m_type = MOD_UNKNOWN;
return true;
}
return false;
}
return true;
if (m_type == MOD_FOLDER)
{
QDir d(m_file.filePath());
if (d.removeRecursively())
{
m_type = MOD_UNKNOWN;
return true;
}
return false;
}
else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
{
QFile f(m_file.filePath());
if (f.remove())
{
m_type = MOD_UNKNOWN;
return true;
}
return false;
}
return true;
}
QString Mod::version() const
{
switch (type())
{
case MOD_ZIPFILE:
case MOD_LITEMOD:
return m_version;
case MOD_FOLDER:
return "Folder";
case MOD_SINGLEFILE:
return "File";
default:
return "VOID";
}
switch (type())
{
case MOD_ZIPFILE:
case MOD_LITEMOD:
return m_version;
case MOD_FOLDER:
return "Folder";
case MOD_SINGLEFILE:
return "File";
default:
return "VOID";
}
}
bool Mod::enable(bool value)
{
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
return false;
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
return false;
if (m_enabled == value)
return false;
if (m_enabled == value)
return false;
QString path = m_file.absoluteFilePath();
if (value)
{
QFile foo(path);
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!foo.rename(path))
return false;
}
else
{
QFile foo(path);
path += ".disabled";
if (!foo.rename(path))
return false;
}
m_file = QFileInfo(path);
m_enabled = value;
return true;
QString path = m_file.absoluteFilePath();
if (value)
{
QFile foo(path);
if (!path.endsWith(".disabled"))
return false;
path.chop(9);
if (!foo.rename(path))
return false;
}
else
{
QFile foo(path);
path += ".disabled";
if (!foo.rename(path))
return false;
}
m_file = QFileInfo(path);
m_enabled = value;
return true;
}
bool Mod::operator==(const Mod &other) const
{
return mmc_id() == other.mmc_id();
return mmc_id() == other.mmc_id();
}
bool Mod::strongCompare(const Mod &other) const
{
return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
}

View File

@ -20,123 +20,123 @@
class Mod
{
public:
enum ModType
{
MOD_UNKNOWN, //!< Indicates an unspecified mod type.
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
MOD_LITEMOD, //!< The mod is a litemod
};
enum ModType
{
MOD_UNKNOWN, //!< Indicates an unspecified mod type.
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
MOD_LITEMOD, //!< The mod is a litemod
};
Mod(const QFileInfo &file);
Mod(const QFileInfo &file);
QFileInfo filename() const
{
return m_file;
}
QString mmc_id() const
{
return m_mmc_id;
}
QString mod_id() const
{
return m_mod_id;
}
ModType type() const
{
return m_type;
}
QString mcversion() const
{
return m_mcversion;
}
;
bool valid()
{
return m_type != MOD_UNKNOWN;
}
QString name() const
{
QString name = m_name.trimmed();
if(name.isEmpty() || name == "Example Mod")
{
return m_mmc_id;
}
return m_name;
}
QFileInfo filename() const
{
return m_file;
}
QString mmc_id() const
{
return m_mmc_id;
}
QString mod_id() const
{
return m_mod_id;
}
ModType type() const
{
return m_type;
}
QString mcversion() const
{
return m_mcversion;
}
;
bool valid()
{
return m_type != MOD_UNKNOWN;
}
QString name() const
{
QString name = m_name.trimmed();
if(name.isEmpty() || name == "Example Mod")
{
return m_mmc_id;
}
return m_name;
}
QString version() const;
QString version() const;
QString homeurl() const
{
return m_homeurl;
}
QString homeurl() const
{
return m_homeurl;
}
QString description() const
{
return m_description;
}
QString description() const
{
return m_description;
}
QString authors() const
{
return m_authors;
}
QString authors() const
{
return m_authors;
}
QString credits() const
{
return m_credits;
}
QString credits() const
{
return m_credits;
}
QDateTime dateTimeChanged() const
{
return m_changedDateTime;
}
QDateTime dateTimeChanged() const
{
return m_changedDateTime;
}
bool enabled() const
{
return m_enabled;
}
bool enabled() const
{
return m_enabled;
}
bool enable(bool value);
bool enable(bool value);
// delete all the files of this mod
bool destroy();
// replace this mod with a copy of the other
bool replace(Mod &with);
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
// delete all the files of this mod
bool destroy();
// replace this mod with a copy of the other
bool replace(Mod &with);
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
// WEAK compare operator - used for replacing mods
bool operator==(const Mod &other) const;
bool strongCompare(const Mod &other) const;
// WEAK compare operator - used for replacing mods
bool operator==(const Mod &other) const;
bool strongCompare(const Mod &other) const;
private:
void ReadMCModInfo(QByteArray contents);
void ReadForgeInfo(QByteArray contents);
void ReadLiteModInfo(QByteArray contents);
void ReadMCModInfo(QByteArray contents);
void ReadForgeInfo(QByteArray contents);
void ReadLiteModInfo(QByteArray contents);
protected:
// FIXME: what do do with those? HMM...
/*
void ReadModInfoData(QString info);
void ReadForgeInfoData(QString infoFileData);
*/
// FIXME: what do do with those? HMM...
/*
void ReadModInfoData(QString info);
void ReadForgeInfoData(QString infoFileData);
*/
QFileInfo m_file;
QDateTime m_changedDateTime;
QString m_mmc_id;
QString m_mod_id;
bool m_enabled = true;
QString m_name;
QString m_version;
QString m_mcversion;
QString m_homeurl;
QString m_updateurl;
QString m_description;
QString m_authors;
QString m_credits;
QFileInfo m_file;
QDateTime m_changedDateTime;
QString m_mmc_id;
QString m_mod_id;
bool m_enabled = true;
QString m_name;
QString m_version;
QString m_mcversion;
QString m_homeurl;
QString m_updateurl;
QString m_description;
QString m_authors;
QString m_credits;
ModType m_type;
ModType m_type;
};

View File

@ -25,350 +25,350 @@
ModsModel::ModsModel(const QString &mainDir, const QString &coreDir, const QString &cacheLocation)
:QAbstractListModel(), m_mainDir(mainDir), m_coreDir(coreDir)
{
FS::ensureFolderPathExists(m_mainDir.absolutePath());
m_mainDir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
FS::ensureFolderPathExists(m_mainDir.absolutePath());
m_mainDir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_mainDir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
m_mainDir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
}
void ModsModel::startWatching()
{
if(is_watching)
return;
if(is_watching)
return;
update();
update();
is_watching = m_watcher->addPath(m_mainDir.absolutePath());
if (is_watching)
{
qDebug() << "Started watching " << m_mainDir.absolutePath();
}
else
{
qDebug() << "Failed to start watching " << m_mainDir.absolutePath();
}
is_watching = m_watcher->addPath(m_mainDir.absolutePath());
if (is_watching)
{
qDebug() << "Started watching " << m_mainDir.absolutePath();
}
else
{
qDebug() << "Failed to start watching " << m_mainDir.absolutePath();
}
}
void ModsModel::stopWatching()
{
if(!is_watching)
return;
if(!is_watching)
return;
is_watching = !m_watcher->removePath(m_mainDir.absolutePath());
if (!is_watching)
{
qDebug() << "Stopped watching " << m_mainDir.absolutePath();
}
else
{
qDebug() << "Failed to stop watching " << m_mainDir.absolutePath();
}
is_watching = !m_watcher->removePath(m_mainDir.absolutePath());
if (!is_watching)
{
qDebug() << "Stopped watching " << m_mainDir.absolutePath();
}
else
{
qDebug() << "Failed to stop watching " << m_mainDir.absolutePath();
}
}
bool ModsModel::update()
{
if (!isValid())
return false;
if (!isValid())
return false;
QList<Mod> orderedMods;
QList<Mod> newMods;
m_mainDir.refresh();
auto folderContents = m_mainDir.entryInfoList();
bool orderOrStateChanged = false;
QList<Mod> orderedMods;
QList<Mod> newMods;
m_mainDir.refresh();
auto folderContents = m_mainDir.entryInfoList();
bool orderOrStateChanged = false;
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents)
{
newMods.append(Mod(entry));
}
orderedMods.append(newMods);
orderOrStateChanged = true;
}
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else
for (int i = 0; i < mods.size(); i++)
{
if (!mods[i].strongCompare(orderedMods[i]))
{
orderOrStateChanged = true;
break;
}
}
}
beginResetModel();
mods.swap(orderedMods);
endResetModel();
if (orderOrStateChanged)
{
emit changed();
}
return true;
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents)
{
newMods.append(Mod(entry));
}
orderedMods.append(newMods);
orderOrStateChanged = true;
}
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else
for (int i = 0; i < mods.size(); i++)
{
if (!mods[i].strongCompare(orderedMods[i]))
{
orderOrStateChanged = true;
break;
}
}
}
beginResetModel();
mods.swap(orderedMods);
endResetModel();
if (orderOrStateChanged)
{
emit changed();
}
return true;
}
void ModsModel::directoryChanged(QString path)
{
update();
update();
}
bool ModsModel::isValid()
{
return m_mainDir.exists() && m_mainDir.isReadable();
return m_mainDir.exists() && m_mainDir.isReadable();
}
bool ModsModel::installMod(const QString &filename)
{
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
QFileInfo fileinfo(FS::NormalizePath(filename));
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
QFileInfo fileinfo(FS::NormalizePath(filename));
qDebug() << "installing: " << fileinfo.absoluteFilePath();
qDebug() << "installing: " << fileinfo.absoluteFilePath();
if (!fileinfo.exists() || !fileinfo.isReadable())
{
return false;
}
Mod m(fileinfo);
if (!m.valid())
return false;
if (!fileinfo.exists() || !fileinfo.isReadable())
{
return false;
}
Mod m(fileinfo);
if (!m.valid())
return false;
auto type = m.type();
if (type == Mod::MOD_UNKNOWN)
return false;
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
{
QString newpath = FS::PathCombine(m_mainDir.path(), fileinfo.fileName());
if (!QFile::copy(fileinfo.filePath(), newpath))
return false;
FS::updateTimestamp(newpath);
m.repath(newpath);
update();
return true;
}
else if (type == Mod::MOD_FOLDER)
{
QString from = fileinfo.filePath();
QString to = FS::PathCombine(m_mainDir.path(), fileinfo.fileName());
if (!FS::copy(from, to)())
return false;
m.repath(to);
update();
return true;
}
return false;
auto type = m.type();
if (type == Mod::MOD_UNKNOWN)
return false;
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
{
QString newpath = FS::PathCombine(m_mainDir.path(), fileinfo.fileName());
if (!QFile::copy(fileinfo.filePath(), newpath))
return false;
FS::updateTimestamp(newpath);
m.repath(newpath);
update();
return true;
}
else if (type == Mod::MOD_FOLDER)
{
QString from = fileinfo.filePath();
QString to = FS::PathCombine(m_mainDir.path(), fileinfo.fileName());
if (!FS::copy(from, to)())
return false;
m.repath(to);
update();
return true;
}
return false;
}
bool ModsModel::enableMods(const QModelIndexList& indexes, bool enable)
{
if(indexes.isEmpty())
return true;
if(indexes.isEmpty())
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.enable(enable);
emit dataChanged(i, i);
}
emit changed();
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.enable(enable);
emit dataChanged(i, i);
}
emit changed();
return true;
}
bool ModsModel::deleteMods(const QModelIndexList& indexes)
{
if(indexes.isEmpty())
return true;
if(indexes.isEmpty())
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.destroy();
}
emit changed();
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.destroy();
}
emit changed();
return true;
}
int ModsModel::columnCount(const QModelIndex &parent) const
{
return NUM_COLUMNS;
return NUM_COLUMNS;
}
QVariant ModsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
int row = index.row();
int column = index.column();
if (row < 0 || row >= mods.size())
return QVariant();
if (row < 0 || row >= mods.size())
return QVariant();
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
return mods[row].name();
case VersionColumn:
return mods[row].version();
case DateColumn:
return mods[row].dateTimeChanged();
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
return mods[row].name();
case VersionColumn:
return mods[row].version();
case DateColumn:
return mods[row].dateTimeChanged();
case LocationColumn:
return "Unknown";
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
return mods[row].mmc_id();
case Qt::ToolTipRole:
return mods[row].mmc_id();
case Qt::CheckStateRole:
switch (column)
{
case ActiveColumn:
return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::CheckStateRole:
switch (column)
{
case ActiveColumn:
return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
default:
return QVariant();
}
}
bool ModsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (role == Qt::CheckStateRole)
{
auto &mod = mods[index.row()];
if (mod.enable(!mod.enabled()))
{
emit dataChanged(index, index);
return true;
}
}
return false;
if (role == Qt::CheckStateRole)
{
auto &mod = mods[index.row()];
if (mod.enable(!mod.enabled()))
{
emit dataChanged(index, index);
return true;
}
}
return false;
}
QVariant ModsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return QString();
case NameColumn:
return tr("Name");
case VersionColumn:
return tr("Version");
case DateColumn:
return tr("Last changed");
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return QString();
case NameColumn:
return tr("Name");
case VersionColumn:
return tr("Version");
case DateColumn:
return tr("Last changed");
case LocationColumn:
return tr("Location");
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case ActiveColumn:
return tr("Is the mod enabled?");
case NameColumn:
return tr("The name of the mod.");
case VersionColumn:
return tr("The version of the mod.");
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
case Qt::ToolTipRole:
switch (section)
{
case ActiveColumn:
return tr("Is the mod enabled?");
case NameColumn:
return tr("The name of the mod.");
case VersionColumn:
return tr("The version of the mod.");
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
case LocationColumn:
return tr("Where the mod is located (inside or outside the instance).");
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
}
Qt::ItemFlags ModsModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
Qt::DropActions ModsModel::supportedDropActions() const
{
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
}
QStringList ModsModel::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
QStringList types;
types << "text/uri-list";
return types;
}
bool ModsModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
{
if (action == Qt::IgnoreAction)
{
return true;
}
if (action == Qt::IgnoreAction)
{
return true;
}
// check if the action is supported
if (!data || !(action & supportedDropActions()))
{
return false;
}
// check if the action is supported
if (!data || !(action & supportedDropActions()))
{
return false;
}
// files dropped from outside?
if (data->hasUrls())
{
bool was_watching = is_watching;
if (was_watching)
{
stopWatching();
}
auto urls = data->urls();
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
{
continue;
}
// TODO: implement not only copy, but also move
// FIXME: handle errors here
installMod(url.toLocalFile());
}
if (was_watching)
{
startWatching();
}
return true;
}
return false;
// files dropped from outside?
if (data->hasUrls())
{
bool was_watching = is_watching;
if (was_watching)
{
stopWatching();
}
auto urls = data->urls();
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
{
continue;
}
// TODO: implement not only copy, but also move
// FIXME: handle errors here
installMod(url.toLocalFile());
}
if (was_watching)
{
startWatching();
}
return true;
}
return false;
}

View File

@ -34,90 +34,90 @@ class QFileSystemWatcher;
*/
class MULTIMC_LOGIC_EXPORT ModsModel : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum Columns
{
ActiveColumn = 0,
NameColumn,
DateColumn,
VersionColumn,
enum Columns
{
ActiveColumn = 0,
NameColumn,
DateColumn,
VersionColumn,
LocationColumn,
NUM_COLUMNS
};
ModsModel(const QString &mainDir, const QString &coreDir, const QString &cacheFile);
NUM_COLUMNS
};
ModsModel(const QString &mainDir, const QString &coreDir, const QString &cacheFile);
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;
Qt::DropActions supportedDropActions() const 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;
Qt::DropActions supportedDropActions() const override;
/// flags, mostly to support drag&drop
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
/// flags, mostly to support drag&drop
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
virtual int rowCount(const QModelIndex &) const override
{
return size();
}
;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual int columnCount(const QModelIndex &parent) const override;
virtual int rowCount(const QModelIndex &) const override
{
return size();
}
;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual int columnCount(const QModelIndex &parent) const override;
size_t size() const
{
return mods.size();
}
;
bool empty() const
{
return size() == 0;
}
Mod &operator[](size_t index)
{
return mods[index];
}
size_t size() const
{
return mods.size();
}
;
bool empty() const
{
return size() == 0;
}
Mod &operator[](size_t index)
{
return mods[index];
}
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
/**
* Adds the given mod to the list at the given index - if the list supports custom ordering
*/
bool installMod(const QString& filename);
/**
* Adds the given mod to the list at the given index - if the list supports custom ordering
*/
bool installMod(const QString& filename);
/// Deletes all the selected mods
virtual bool deleteMods(const QModelIndexList &indexes);
/// Deletes all the selected mods
virtual bool deleteMods(const QModelIndexList &indexes);
/// Enable or disable listed mods
virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
/// Enable or disable listed mods
virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
void startWatching();
void stopWatching();
void startWatching();
void stopWatching();
virtual bool isValid();
virtual bool isValid();
QDir dir()
{
return m_mainDir;
}
QDir dir()
{
return m_mainDir;
}
const QList<Mod> & allMods()
{
return mods;
}
const QList<Mod> & allMods()
{
return mods;
}
private
slots:
void directoryChanged(QString path);
void directoryChanged(QString path);
signals:
void changed();
void changed();
protected:
QFileSystemWatcher *m_watcher;
bool is_watching = false;
QDir m_mainDir;
QFileSystemWatcher *m_watcher;
bool is_watching = false;
QDir m_mainDir;
QDir m_coreDir;
QList<Mod> mods;
QList<Mod> mods;
};

View File

@ -5,78 +5,78 @@
struct MojangDownloadInfo
{
// types
typedef std::shared_ptr<MojangDownloadInfo> Ptr;
// types
typedef std::shared_ptr<MojangDownloadInfo> Ptr;
// data
/// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested!
QString path;
/// absolute URL of this file
QString url;
/// sha-1 checksum of the file
QString sha1;
/// size of the file in bytes
int size;
// data
/// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested!
QString path;
/// absolute URL of this file
QString url;
/// sha-1 checksum of the file
QString sha1;
/// size of the file in bytes
int size;
};
struct MojangLibraryDownloadInfo
{
MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {};
MojangLibraryDownloadInfo() {};
MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {};
MojangLibraryDownloadInfo() {};
// types
typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr;
// types
typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr;
// methods
MojangDownloadInfo *getDownloadInfo(QString classifier)
{
if (classifier.isNull())
{
return artifact.get();
}
return classifiers[classifier].get();
}
// methods
MojangDownloadInfo *getDownloadInfo(QString classifier)
{
if (classifier.isNull())
{
return artifact.get();
}
return classifiers[classifier].get();
}
// data
MojangDownloadInfo::Ptr artifact;
QMap<QString, MojangDownloadInfo::Ptr> classifiers;
// data
MojangDownloadInfo::Ptr artifact;
QMap<QString, MojangDownloadInfo::Ptr> classifiers;
};
struct MojangAssetIndexInfo : public MojangDownloadInfo
{
// types
typedef std::shared_ptr<MojangAssetIndexInfo> Ptr;
// types
typedef std::shared_ptr<MojangAssetIndexInfo> Ptr;
// methods
MojangAssetIndexInfo()
{
}
// methods
MojangAssetIndexInfo()
{
}
MojangAssetIndexInfo(QString id)
{
this->id = id;
// 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")
{
url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
}
// HACK
else
{
url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json";
}
known = false;
}
MojangAssetIndexInfo(QString id)
{
this->id = id;
// 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")
{
url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
}
// HACK
else
{
url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json";
}
known = false;
}
// data
int totalSize;
QString id;
bool known = true;
// data
int totalSize;
QString id;
bool known = true;
};

View File

@ -19,361 +19,361 @@ namespace Bits
{
static void readString(const QJsonObject &root, const QString &key, QString &variable)
{
if (root.contains(key))
{
variable = requireString(root.value(key));
}
if (root.contains(key))
{
variable = requireString(root.value(key));
}
}
static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj)
{
// optional, not used
readString(obj, "path", out->path);
// required!
out->sha1 = requireString(obj, "sha1");
out->url = requireString(obj, "url");
out->size = requireInteger(obj, "size");
// optional, not used
readString(obj, "path", out->path);
// required!
out->sha1 = requireString(obj, "sha1");
out->url = requireString(obj, "url");
out->size = requireInteger(obj, "size");
}
static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject &obj)
{
out->totalSize = requireInteger(obj, "totalSize");
out->id = requireString(obj, "id");
// out->known = true;
out->totalSize = requireInteger(obj, "totalSize");
out->id = requireString(obj, "id");
// out->known = true;
}
}
MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject &obj)
{
auto out = std::make_shared<MojangDownloadInfo>();
Bits::readDownloadInfo(out, obj);
return out;
auto out = std::make_shared<MojangDownloadInfo>();
Bits::readDownloadInfo(out, obj);
return out;
}
MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj)
{
auto out = std::make_shared<MojangAssetIndexInfo>();
Bits::readDownloadInfo(out, obj);
Bits::readAssetIndex(out, obj);
return out;
auto out = std::make_shared<MojangAssetIndexInfo>();
Bits::readDownloadInfo(out, obj);
Bits::readAssetIndex(out, obj);
return out;
}
QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info)
{
QJsonObject out;
if(!info->path.isNull())
{
out.insert("path", info->path);
}
out.insert("sha1", info->sha1);
out.insert("size", info->size);
out.insert("url", info->url);
return out;
QJsonObject out;
if(!info->path.isNull())
{
out.insert("path", info->path);
}
out.insert("sha1", info->sha1);
out.insert("size", info->size);
out.insert("url", info->url);
return out;
}
MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj)
{
auto out = std::make_shared<MojangLibraryDownloadInfo>();
auto dlObj = requireObject(libObj.value("downloads"));
if(dlObj.contains("artifact"))
{
out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact"));
}
if(dlObj.contains("classifiers"))
{
auto classifiersObj = requireObject(dlObj, "classifiers");
for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++)
{
auto classifier = iter.key();
auto classifierObj = requireObject(iter.value());
out->classifiers[classifier] = downloadInfoFromJson(classifierObj);
}
}
return out;
auto out = std::make_shared<MojangLibraryDownloadInfo>();
auto dlObj = requireObject(libObj.value("downloads"));
if(dlObj.contains("artifact"))
{
out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact"));
}
if(dlObj.contains("classifiers"))
{
auto classifiersObj = requireObject(dlObj, "classifiers");
for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++)
{
auto classifier = iter.key();
auto classifierObj = requireObject(iter.value());
out->classifiers[classifier] = downloadInfoFromJson(classifierObj);
}
}
return out;
}
QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
{
QJsonObject out;
if(libinfo->artifact)
{
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
}
if(libinfo->classifiers.size())
{
QJsonObject classifiersOut;
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
{
classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value()));
}
out.insert("classifiers", classifiersOut);
}
return out;
QJsonObject out;
if(libinfo->artifact)
{
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
}
if(libinfo->classifiers.size())
{
QJsonObject classifiersOut;
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
{
classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value()));
}
out.insert("classifiers", classifiersOut);
}
return out;
}
QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info)
{
QJsonObject out;
if(!info->path.isNull())
{
out.insert("path", info->path);
}
out.insert("sha1", info->sha1);
out.insert("size", info->size);
out.insert("url", info->url);
out.insert("totalSize", info->totalSize);
out.insert("id", info->id);
return out;
QJsonObject out;
if(!info->path.isNull())
{
out.insert("path", info->path);
}
out.insert("sha1", info->sha1);
out.insert("size", info->size);
out.insert("url", info->url);
out.insert("totalSize", info->totalSize);
out.insert("id", info->id);
return 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())
{
QString processArguments;
Bits::readString(in, "processArguments", processArguments);
QString toCompare = processArguments.toLower();
if (toCompare == "legacy")
{
out->minecraftArguments = " ${auth_player_name} ${auth_session}";
}
else if (toCompare == "username_session")
{
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
}
else if (toCompare == "username_session_version")
{
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
}
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, "id", out->minecraftVersion);
Bits::readString(in, "mainClass", out->mainClass);
Bits::readString(in, "minecraftArguments", out->minecraftArguments);
if(out->minecraftArguments.isEmpty())
{
QString processArguments;
Bits::readString(in, "processArguments", processArguments);
QString toCompare = processArguments.toLower();
if (toCompare == "legacy")
{
out->minecraftArguments = " ${auth_player_name} ${auth_session}";
}
else if (toCompare == "username_session")
{
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
}
else if (toCompare == "username_session_version")
{
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
}
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"))
{
out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex"));
}
else if (!out->assets.isNull())
{
out->mojangAssetIndex = std::make_shared<MojangAssetIndexInfo>(out->assets);
}
Bits::readString(in, "assets", out->assets);
if(in.contains("assetIndex"))
{
out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex"));
}
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(""));
out->releaseTime = timeFromS3Time(in.value("releaseTime").toString(""));
out->updateTime = timeFromS3Time(in.value("time").toString(""));
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 MultiMC (%2). It might not work properly!")
.arg(out->minimumLauncherVersion)
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION));
}
}
if(in.contains("downloads"))
{
auto downloadsObj = requireObject(in, "downloads");
for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++)
{
auto classifier = iter.key();
auto classifierObj = requireObject(iter.value());
out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj);
}
}
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 MultiMC (%2). It might not work properly!")
.arg(out->minimumLauncherVersion)
.arg(CURRENT_MINIMUM_LAUNCHER_VERSION));
}
}
if(in.contains("downloads"))
{
auto downloadsObj = requireObject(in, "downloads");
for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++)
{
auto classifier = iter.key();
auto classifierObj = requireObject(iter.value());
out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj);
}
}
}
VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename)
{
VersionFilePtr out(new VersionFile());
if (doc.isEmpty() || doc.isNull())
{
throw JSONValidationError(filename + " is empty or null");
}
if (!doc.isObject())
{
throw JSONValidationError(filename + " is not an object");
}
VersionFilePtr out(new VersionFile());
if (doc.isEmpty() || doc.isNull())
{
throw JSONValidationError(filename + " is empty or null");
}
if (!doc.isObject())
{
throw JSONValidationError(filename + " is not an object");
}
QJsonObject root = doc.object();
QJsonObject root = doc.object();
readVersionProperties(root, out.get());
readVersionProperties(root, out.get());
out->name = "Minecraft";
out->uid = "net.minecraft";
out->version = out->minecraftVersion;
// out->filename = filename;
out->name = "Minecraft";
out->uid = "net.minecraft";
out->version = out->minecraftVersion;
// out->filename = filename;
if (root.contains("libraries"))
{
for (auto libVal : requireArray(root.value("libraries")))
{
auto libObj = requireObject(libVal);
if (root.contains("libraries"))
{
for (auto libVal : requireArray(root.value("libraries")))
{
auto libObj = requireObject(libVal);
auto lib = MojangVersionFormat::libraryFromJson(libObj, filename);
out->libraries.append(lib);
}
}
return out;
auto lib = MojangVersionFormat::libraryFromJson(libObj, filename);
out->libraries.append(lib);
}
}
return out;
}
void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObject& out)
{
writeString(out, "id", in->minecraftVersion);
writeString(out, "mainClass", in->mainClass);
writeString(out, "minecraftArguments", in->minecraftArguments);
writeString(out, "type", in->type);
if(!in->releaseTime.isNull())
{
writeString(out, "releaseTime", timeToS3Time(in->releaseTime));
}
if(!in->updateTime.isNull())
{
writeString(out, "time", timeToS3Time(in->updateTime));
}
if(in->minimumLauncherVersion != -1)
{
out.insert("minimumLauncherVersion", in->minimumLauncherVersion);
}
writeString(out, "assets", in->assets);
if(in->mojangAssetIndex && in->mojangAssetIndex->known)
{
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
}
if(in->mojangDownloads.size())
{
QJsonObject downloadsOut;
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
{
downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value()));
}
out.insert("downloads", downloadsOut);
}
writeString(out, "id", in->minecraftVersion);
writeString(out, "mainClass", in->mainClass);
writeString(out, "minecraftArguments", in->minecraftArguments);
writeString(out, "type", in->type);
if(!in->releaseTime.isNull())
{
writeString(out, "releaseTime", timeToS3Time(in->releaseTime));
}
if(!in->updateTime.isNull())
{
writeString(out, "time", timeToS3Time(in->updateTime));
}
if(in->minimumLauncherVersion != -1)
{
out.insert("minimumLauncherVersion", in->minimumLauncherVersion);
}
writeString(out, "assets", in->assets);
if(in->mojangAssetIndex && in->mojangAssetIndex->known)
{
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
}
if(in->mojangDownloads.size())
{
QJsonObject downloadsOut;
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
{
downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value()));
}
out.insert("downloads", downloadsOut);
}
}
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
{
QJsonObject root;
writeVersionProperties(patch.get(), root);
if (!patch->libraries.isEmpty())
{
QJsonArray array;
for (auto value: patch->libraries)
{
array.append(MojangVersionFormat::libraryToJson(value.get()));
}
root.insert("libraries", array);
}
QJsonObject root;
writeVersionProperties(patch.get(), root);
if (!patch->libraries.isEmpty())
{
QJsonArray array;
for (auto value: patch->libraries)
{
array.append(MojangVersionFormat::libraryToJson(value.get()));
}
root.insert("libraries", array);
}
// write the contents to a json document.
{
QJsonDocument out;
out.setObject(root);
return out;
}
// write the contents to a json document.
{
QJsonDocument out;
out.setObject(root);
return out;
}
}
LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename)
{
LibraryPtr out(new Library());
if (!libObj.contains("name"))
{
throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field");
}
out->m_name = libObj.value("name").toString();
LibraryPtr out(new Library());
if (!libObj.contains("name"))
{
throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field");
}
out->m_name = libObj.value("name").toString();
Bits::readString(libObj, "url", out->m_repositoryURL);
if (libObj.contains("extract"))
{
out->m_hasExcludes = true;
auto extractObj = requireObject(libObj.value("extract"));
for (auto excludeVal : requireArray(extractObj.value("exclude")))
{
out->m_extractExcludes.append(requireString(excludeVal));
}
}
if (libObj.contains("natives"))
{
QJsonObject nativesObj = requireObject(libObj.value("natives"));
for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
{
if (!it.value().isString())
{
qWarning() << filename << "contains an invalid native (skipping)";
}
OpSys opSys = OpSys_fromString(it.key());
if (opSys != Os_Other)
{
out->m_nativeClassifiers[opSys] = it.value().toString();
}
}
}
if (libObj.contains("rules"))
{
out->applyRules = true;
out->m_rules = rulesFromJsonV4(libObj);
}
if (libObj.contains("downloads"))
{
out->m_mojangDownloads = libDownloadInfoFromJson(libObj);
}
return out;
Bits::readString(libObj, "url", out->m_repositoryURL);
if (libObj.contains("extract"))
{
out->m_hasExcludes = true;
auto extractObj = requireObject(libObj.value("extract"));
for (auto excludeVal : requireArray(extractObj.value("exclude")))
{
out->m_extractExcludes.append(requireString(excludeVal));
}
}
if (libObj.contains("natives"))
{
QJsonObject nativesObj = requireObject(libObj.value("natives"));
for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
{
if (!it.value().isString())
{
qWarning() << filename << "contains an invalid native (skipping)";
}
OpSys opSys = OpSys_fromString(it.key());
if (opSys != Os_Other)
{
out->m_nativeClassifiers[opSys] = it.value().toString();
}
}
}
if (libObj.contains("rules"))
{
out->applyRules = true;
out->m_rules = rulesFromJsonV4(libObj);
}
if (libObj.contains("downloads"))
{
out->m_mojangDownloads = libDownloadInfoFromJson(libObj);
}
return out;
}
QJsonObject MojangVersionFormat::libraryToJson(Library *library)
{
QJsonObject libRoot;
libRoot.insert("name", (QString)library->m_name);
if (!library->m_repositoryURL.isEmpty())
{
libRoot.insert("url", library->m_repositoryURL);
}
if (library->isNative())
{
QJsonObject nativeList;
auto iter = library->m_nativeClassifiers.begin();
while (iter != library->m_nativeClassifiers.end())
{
nativeList.insert(OpSys_toString(iter.key()), iter.value());
iter++;
}
libRoot.insert("natives", nativeList);
if (library->m_extractExcludes.size())
{
QJsonArray excludes;
QJsonObject extract;
for (auto exclude : library->m_extractExcludes)
{
excludes.append(exclude);
}
extract.insert("exclude", excludes);
libRoot.insert("extract", extract);
}
}
if (library->m_rules.size())
{
QJsonArray allRules;
for (auto &rule : library->m_rules)
{
QJsonObject ruleObj = rule->toJson();
allRules.append(ruleObj);
}
libRoot.insert("rules", allRules);
}
if(library->m_mojangDownloads)
{
auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads);
libRoot.insert("downloads", downloadsObj);
}
return libRoot;
QJsonObject libRoot;
libRoot.insert("name", (QString)library->m_name);
if (!library->m_repositoryURL.isEmpty())
{
libRoot.insert("url", library->m_repositoryURL);
}
if (library->isNative())
{
QJsonObject nativeList;
auto iter = library->m_nativeClassifiers.begin();
while (iter != library->m_nativeClassifiers.end())
{
nativeList.insert(OpSys_toString(iter.key()), iter.value());
iter++;
}
libRoot.insert("natives", nativeList);
if (library->m_extractExcludes.size())
{
QJsonArray excludes;
QJsonObject extract;
for (auto exclude : library->m_extractExcludes)
{
excludes.append(exclude);
}
extract.insert("exclude", excludes);
libRoot.insert("extract", extract);
}
}
if (library->m_rules.size())
{
QJsonArray allRules;
for (auto &rule : library->m_rules)
{
QJsonObject ruleObj = rule->toJson();
allRules.append(ruleObj);
}
libRoot.insert("rules", allRules);
}
if(library->m_mojangDownloads)
{
auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads);
libRoot.insert("downloads", downloadsObj);
}
return libRoot;
}

View File

@ -10,16 +10,16 @@ class MULTIMC_LOGIC_EXPORT 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);
// 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:
// version files / profile patches
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename);
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// version files / profile patches
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename);
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// libraries
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject libraryToJson(Library *library);
// libraries
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject libraryToJson(Library *library);
};

View File

@ -6,47 +6,47 @@
class MojangVersionFormatTest : public QObject
{
Q_OBJECT
Q_OBJECT
static QJsonDocument readJson(const char *file)
{
auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
return QJsonDocument::fromJson(data);
}
static void writeJson(const char *file, QJsonDocument doc)
{
QFile jsonFile(file);
jsonFile.open(QIODevice::WriteOnly | QIODevice::Text);
auto data = doc.toJson(QJsonDocument::Indented);
qDebug() << QString::fromUtf8(data);
jsonFile.write(data);
jsonFile.close();
}
static QJsonDocument readJson(const char *file)
{
auto path = QFINDTESTDATA(file);
QFile jsonFile(path);
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
return QJsonDocument::fromJson(data);
}
static void writeJson(const char *file, QJsonDocument doc)
{
QFile jsonFile(file);
jsonFile.open(QIODevice::WriteOnly | QIODevice::Text);
auto data = doc.toJson(QJsonDocument::Indented);
qDebug() << QString::fromUtf8(data);
jsonFile.write(data);
jsonFile.close();
}
private
slots:
void test_Through_Simple()
{
QJsonDocument doc = readJson("data/1.9-simple.json");
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-simple-passthorugh.json", doc2);
void test_Through_Simple()
{
QJsonDocument doc = readJson("data/1.9-simple.json");
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-simple-passthorugh.json", doc2);
QCOMPARE(doc.toJson(), doc2.toJson());
}
QCOMPARE(doc.toJson(), doc2.toJson());
}
void test_Through()
{
QJsonDocument doc = readJson("data/1.9.json");
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-passthorugh.json", doc2);
QCOMPARE(doc.toJson(), doc2.toJson());
}
void test_Through()
{
QJsonDocument doc = readJson("data/1.9.json");
auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json");
auto doc2 = MojangVersionFormat::versionFileToJson(vfile);
writeJson("1.9-passthorugh.json", doc2);
QCOMPARE(doc.toJson(), doc2.toJson());
}
};
QTEST_GUILESS_MAIN(MojangVersionFormatTest)

View File

@ -7,366 +7,366 @@ using namespace Json;
static void readString(const QJsonObject &root, const QString &key, QString &variable)
{
if (root.contains(key))
{
variable = requireString(root.value(key));
}
if (root.contains(key))
{
variable = requireString(root.value(key));
}
}
LibraryPtr OneSixVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename)
{
LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename);
readString(libObj, "MMC-hint", out->m_hint);
readString(libObj, "MMC-absulute_url", out->m_absoluteURL);
readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL);
readString(libObj, "MMC-filename", out->m_filename);
readString(libObj, "MMC-displayname", out->m_displayname);
return out;
LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename);
readString(libObj, "MMC-hint", out->m_hint);
readString(libObj, "MMC-absulute_url", out->m_absoluteURL);
readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL);
readString(libObj, "MMC-filename", out->m_filename);
readString(libObj, "MMC-displayname", out->m_displayname);
return out;
}
QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
{
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
if (library->m_absoluteURL.size())
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
if (library->m_hint.size())
libRoot.insert("MMC-hint", library->m_hint);
if (library->m_filename.size())
libRoot.insert("MMC-filename", library->m_filename);
if (library->m_displayname.size())
libRoot.insert("MMC-displayname", library->m_displayname);
return libRoot;
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
if (library->m_absoluteURL.size())
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
if (library->m_hint.size())
libRoot.insert("MMC-hint", library->m_hint);
if (library->m_filename.size())
libRoot.insert("MMC-filename", library->m_filename);
if (library->m_displayname.size())
libRoot.insert("MMC-displayname", library->m_displayname);
return libRoot;
}
VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder)
{
VersionFilePtr out(new VersionFile());
if (doc.isEmpty() || doc.isNull())
{
throw JSONValidationError(filename + " is empty or null");
}
if (!doc.isObject())
{
throw JSONValidationError(filename + " is not an object");
}
VersionFilePtr out(new VersionFile());
if (doc.isEmpty() || doc.isNull())
{
throw JSONValidationError(filename + " is empty or null");
}
if (!doc.isObject())
{
throw JSONValidationError(filename + " is not an object");
}
QJsonObject root = doc.object();
QJsonObject root = doc.object();
Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false);
switch(formatVersion)
{
case Meta::MetadataVersion::InitialRelease:
break;
case Meta::MetadataVersion::Invalid:
throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format.");
}
Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false);
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"))
{
out->order = requireInteger(root.value("order"));
}
else
{
// FIXME: evaluate if we don't want to throw exceptions here instead
qCritical() << filename << "doesn't contain an order field";
}
}
if (requireOrder)
{
if (root.contains("order"))
{
out->order = requireInteger(root.value("order"));
}
else
{
// FIXME: evaluate if we don't want to throw exceptions here instead
qCritical() << filename << "doesn't contain an order field";
}
}
out->name = root.value("name").toString();
out->name = root.value("name").toString();
if(root.contains("uid"))
{
out->uid = root.value("uid").toString();
}
else
{
out->uid = root.value("fileId").toString();
}
if(root.contains("uid"))
{
out->uid = root.value("uid").toString();
}
else
{
out->uid = root.value("fileId").toString();
}
out->version = root.value("version").toString();
out->version = root.value("version").toString();
MojangVersionFormat::readVersionProperties(root, out.get());
MojangVersionFormat::readVersionProperties(root, out.get());
// added for legacy Minecraft window embedding, TODO: remove
readString(root, "appletClass", out->appletClass);
// added for legacy Minecraft window embedding, TODO: remove
readString(root, "appletClass", out->appletClass);
if (root.contains("+tweakers"))
{
for (auto tweakerVal : requireArray(root.value("+tweakers")))
{
out->addTweakers.append(requireString(tweakerVal));
}
}
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")))
{
out->traits.insert(requireString(tweakerVal));
}
}
if (root.contains("+traits"))
{
for (auto tweakerVal : requireArray(root.value("+traits")))
{
out->traits.insert(requireString(tweakerVal));
}
}
if (root.contains("jarMods"))
{
for (auto libVal : requireArray(root.value("jarMods")))
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
auto lib = OneSixVersionFormat::jarModFromJson(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
{
for (auto libVal : requireArray(root.value("+jarMods")))
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name);
// and add to jar mods
out->jarMods.append(lib);
}
}
if (root.contains("jarMods"))
{
for (auto libVal : requireArray(root.value("jarMods")))
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
auto lib = OneSixVersionFormat::jarModFromJson(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
{
for (auto libVal : requireArray(root.value("+jarMods")))
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name);
// and add to jar mods
out->jarMods.append(lib);
}
}
if (root.contains("mods"))
{
for (auto libVal : requireArray(root.value("mods")))
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
auto lib = OneSixVersionFormat::modFromJson(libObj, filename);
// and add to jar mods
out->mods.append(lib);
}
}
if (root.contains("mods"))
{
for (auto libVal : requireArray(root.value("mods")))
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
auto lib = OneSixVersionFormat::modFromJson(libObj, filename);
// and add to jar mods
out->mods.append(lib);
}
}
auto readLibs = [&](const char * which)
{
for (auto libVal : requireArray(root.value(which)))
{
QJsonObject libObj = requireObject(libVal);
// parse the library
auto lib = libraryFromJson(libObj, filename);
out->libraries.append(lib);
}
};
bool hasPlusLibs = root.contains("+libraries");
bool hasLibs = root.contains("libraries");
if (hasPlusLibs && hasLibs)
{
out->addProblem(ProblemSeverity::Warning,
QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported."));
readLibs("libraries");
readLibs("+libraries");
}
else if (hasLibs)
{
readLibs("libraries");
}
else if(hasPlusLibs)
{
readLibs("+libraries");
}
auto readLibs = [&](const char * which)
{
for (auto libVal : requireArray(root.value(which)))
{
QJsonObject libObj = requireObject(libVal);
// parse the library
auto lib = libraryFromJson(libObj, filename);
out->libraries.append(lib);
}
};
bool hasPlusLibs = root.contains("+libraries");
bool hasLibs = root.contains("libraries");
if (hasPlusLibs && hasLibs)
{
out->addProblem(ProblemSeverity::Warning,
QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported."));
readLibs("libraries");
readLibs("+libraries");
}
else if (hasLibs)
{
readLibs("libraries");
}
else if(hasPlusLibs)
{
readLibs("+libraries");
}
// if we have mainJar, just use it
if(root.contains("mainJar"))
{
QJsonObject libObj = requireObject(root, "mainJar");
out->mainJar = libraryFromJson(libObj, filename);
}
// else reconstruct it from downloads and id ... if that's available
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"))
{
auto LibDLInfo = std::make_shared<MojangLibraryDownloadInfo>();
LibDLInfo->artifact = out->mojangDownloads["client"];
lib->setMojangDownloadInfo(LibDLInfo);
}
// we got nothing... guess based on ancient hardcoded Mojang behaviour
// FIXME: this will eventually break...
else
{
lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion));
}
out->mainJar = lib;
}
// if we have mainJar, just use it
if(root.contains("mainJar"))
{
QJsonObject libObj = requireObject(root, "mainJar");
out->mainJar = libraryFromJson(libObj, filename);
}
// else reconstruct it from downloads and id ... if that's available
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"))
{
auto LibDLInfo = std::make_shared<MojangLibraryDownloadInfo>();
LibDLInfo->artifact = out->mojangDownloads["client"];
lib->setMojangDownloadInfo(LibDLInfo);
}
// we got nothing... guess based on ancient hardcoded Mojang behaviour
// FIXME: this will eventually break...
else
{
lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion));
}
out->mainJar = lib;
}
if (root.contains("requires"))
{
Meta::parseRequires(root, &out->requires);
}
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
if(!dependsOnMinecraftVersion.isEmpty())
{
Meta::Require mcReq;
mcReq.uid = "net.minecraft";
mcReq.equalsVersion = dependsOnMinecraftVersion;
if (out->requires.count(mcReq) == 0)
{
out->requires.insert(mcReq);
}
}
if (root.contains("conflicts"))
{
Meta::parseRequires(root, &out->conflicts);
}
if (root.contains("volatile"))
{
out->m_volatile = requireBoolean(root, "volatile");
}
if (root.contains("requires"))
{
Meta::parseRequires(root, &out->requires);
}
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
if(!dependsOnMinecraftVersion.isEmpty())
{
Meta::Require mcReq;
mcReq.uid = "net.minecraft";
mcReq.equalsVersion = dependsOnMinecraftVersion;
if (out->requires.count(mcReq) == 0)
{
out->requires.insert(mcReq);
}
}
if (root.contains("conflicts"))
{
Meta::parseRequires(root, &out->conflicts);
}
if (root.contains("volatile"))
{
out->m_volatile = requireBoolean(root, "volatile");
}
/* removed features that shouldn't be used */
if (root.contains("tweakers"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'"));
}
if (root.contains("-libraries"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'"));
}
if (root.contains("-tweakers"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'"));
}
if (root.contains("-minecraftArguments"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'"));
}
if (root.contains("+minecraftArguments"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'"));
}
return out;
/* removed features that shouldn't be used */
if (root.contains("tweakers"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'"));
}
if (root.contains("-libraries"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'"));
}
if (root.contains("-tweakers"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'"));
}
if (root.contains("-minecraftArguments"))
{
out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-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)
{
QJsonObject root;
writeString(root, "name", patch->name);
QJsonObject root;
writeString(root, "name", patch->name);
writeString(root, "uid", patch->uid);
writeString(root, "uid", patch->uid);
writeString(root, "version", patch->version);
writeString(root, "version", patch->version);
Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease);
Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease);
MojangVersionFormat::writeVersionProperties(patch.get(), root);
MojangVersionFormat::writeVersionProperties(patch.get(), root);
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.toList());
if (!patch->libraries.isEmpty())
{
QJsonArray array;
for (auto value: patch->libraries)
{
array.append(OneSixVersionFormat::libraryToJson(value.get()));
}
root.insert("libraries", array);
}
if (!patch->jarMods.isEmpty())
{
QJsonArray array;
for (auto value: patch->jarMods)
{
array.append(OneSixVersionFormat::jarModtoJson(value.get()));
}
root.insert("jarMods", array);
}
if (!patch->mods.isEmpty())
{
QJsonArray array;
for (auto value: patch->jarMods)
{
array.append(OneSixVersionFormat::modtoJson(value.get()));
}
root.insert("mods", array);
}
if(!patch->requires.empty())
{
Meta::serializeRequires(root, &patch->requires, "requires");
}
if(!patch->conflicts.empty())
{
Meta::serializeRequires(root, &patch->conflicts, "conflicts");
}
if(patch->m_volatile)
{
root.insert("volatile", true);
}
// write the contents to a json document.
{
QJsonDocument out;
out.setObject(root);
return out;
}
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.toList());
if (!patch->libraries.isEmpty())
{
QJsonArray array;
for (auto value: patch->libraries)
{
array.append(OneSixVersionFormat::libraryToJson(value.get()));
}
root.insert("libraries", array);
}
if (!patch->jarMods.isEmpty())
{
QJsonArray array;
for (auto value: patch->jarMods)
{
array.append(OneSixVersionFormat::jarModtoJson(value.get()));
}
root.insert("jarMods", array);
}
if (!patch->mods.isEmpty())
{
QJsonArray array;
for (auto value: patch->jarMods)
{
array.append(OneSixVersionFormat::modtoJson(value.get()));
}
root.insert("mods", array);
}
if(!patch->requires.empty())
{
Meta::serializeRequires(root, &patch->requires, "requires");
}
if(!patch->conflicts.empty())
{
Meta::serializeRequires(root, &patch->conflicts, "conflicts");
}
if(patch->m_volatile)
{
root.insert("volatile", true);
}
// write the contents to a json document.
{
QJsonDocument out;
out.setObject(root);
return out;
}
}
LibraryPtr OneSixVersionFormat::plusJarModFromJson(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");
}
LibraryPtr out(new Library());
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.
auto uuid = QUuid::createUuid();
QString id = uuid.toString().remove('{').remove('}');
out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
// just make up something unique on the spot for the library name.
auto uuid = QUuid::createUuid();
QString id = uuid.toString().remove('{').remove('}');
out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
// filename override is the old name
out->setFilename(libObj.value("name").toString());
// filename override is the old name
out->setFilename(libObj.value("name").toString());
// it needs to be local, it is stored in the instance jarmods folder
out->setHint("local");
// it needs to be local, it is stored in the instance jarmods folder
out->setHint("local");
// 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())
{
auto fixed = originalName;
fixed.remove(" (jar mod)");
out->setDisplayName(fixed);
}
else
{
out->setDisplayName(displayName);
}
return out;
// 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())
{
auto fixed = originalName;
fixed.remove(" (jar mod)");
out->setDisplayName(fixed);
}
else
{
out->setDisplayName(displayName);
}
return out;
}
LibraryPtr OneSixVersionFormat::jarModFromJson(const QJsonObject& libObj, const QString& filename)
{
return libraryFromJson(libObj, filename);
return libraryFromJson(libObj, filename);
}
QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod)
{
return libraryToJson(jarmod);
return libraryToJson(jarmod);
}
LibraryPtr OneSixVersionFormat::modFromJson(const QJsonObject& libObj, const QString& filename)
{
return libraryFromJson(libObj, filename);
return libraryFromJson(libObj, filename);
}
QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod)
{
return libraryToJson(jarmod);
return libraryToJson(jarmod);
}

View File

@ -8,22 +8,22 @@
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);
// version files / profile patches
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder);
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// libraries
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject libraryToJson(Library *library);
// libraries
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject libraryToJson(Library *library);
// DEPRECATED: old 'plus' jar mods generated by the application
static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName);
// DEPRECATED: old 'plus' jar mods generated by the application
static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName);
// new jar mods derived from libraries
static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject jarModtoJson(Library * jarmod);
// new jar mods derived from libraries
static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject jarModtoJson(Library * jarmod);
// mods, also derived from libraries
static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject modtoJson(Library * jarmod);
// mods, also derived from libraries
static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename);
static QJsonObject modtoJson(Library * jarmod);
};

View File

@ -17,26 +17,26 @@
OpSys OpSys_fromString(QString name)
{
if (name == "linux")
return Os_Linux;
if (name == "windows")
return Os_Windows;
if (name == "osx")
return Os_OSX;
return Os_Other;
if (name == "linux")
return Os_Linux;
if (name == "windows")
return Os_Windows;
if (name == "osx")
return Os_OSX;
return Os_Other;
}
QString OpSys_toString(OpSys name)
{
switch (name)
{
case Os_Linux:
return "linux";
case Os_OSX:
return "osx";
case Os_Windows:
return "windows";
default:
return "other";
}
switch (name)
{
case Os_Linux:
return "linux";
case Os_OSX:
return "osx";
case Os_Windows:
return "windows";
default:
return "other";
}
}

View File

@ -17,10 +17,10 @@
#include <QString>
enum OpSys
{
Os_Windows,
Os_Linux,
Os_OSX,
Os_Other
Os_Windows,
Os_Linux,
Os_OSX,
Os_Other
};
OpSys OpSys_fromString(QString);

View File

@ -6,29 +6,29 @@
QDateTime timeFromS3Time(QString str)
{
return QDateTime::fromString(str, Qt::ISODate);
return QDateTime::fromString(str, Qt::ISODate);
}
QString timeToS3Time(QDateTime time)
{
// this all because Qt can't format timestamps right.
int offsetRaw = time.offsetFromUtc();
bool negative = offsetRaw < 0;
int offsetAbs = std::abs(offsetRaw);
// this all because Qt can't format timestamps right.
int offsetRaw = time.offsetFromUtc();
bool negative = offsetRaw < 0;
int offsetAbs = std::abs(offsetRaw);
int offsetSeconds = offsetAbs % 60;
offsetAbs -= offsetSeconds;
int offsetSeconds = offsetAbs % 60;
offsetAbs -= offsetSeconds;
int offsetMinutes = offsetAbs % 3600;
offsetAbs -= offsetMinutes;
offsetMinutes /= 60;
int offsetHours = offsetAbs / 3600;
int offsetMinutes = offsetAbs % 3600;
offsetAbs -= offsetMinutes;
offsetMinutes /= 60;
int offsetHours = offsetAbs / 3600;
QString raw = time.toString("yyyy-MM-ddTHH:mm:ss");
raw += (negative ? QChar('-') : QChar('+'));
raw += QString("%1").arg(offsetHours, 2, 10, QChar('0'));
raw += ":";
raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0'));
return raw;
QString raw = time.toString("yyyy-MM-ddTHH:mm:ss");
raw += (negative ? QChar('-') : QChar('+'));
raw += QString("%1").arg(offsetHours, 2, 10, QChar('0'));
raw += ":";
raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0'));
return raw;
}

View File

@ -5,37 +5,37 @@
class ParseUtilsTest : public QObject
{
Q_OBJECT
Q_OBJECT
private
slots:
void test_Through_data()
{
QTest::addColumn<QString>("timestamp");
const char * timestamps[] =
{
"2016-02-29T13:49:54+01:00",
"2016-02-26T15:21:11+00:01",
"2016-02-24T15:52:36+01:13",
"2016-02-18T17:41:00+00:00",
"2016-02-17T15:23:19+00:00",
"2016-02-16T15:22:39+09:22",
"2016-02-10T15:06:41+00:00",
"2016-02-04T15:28:02-05:33"
};
for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++)
{
QTest::newRow(timestamps[i]) << QString(timestamps[i]);
}
}
void test_Through()
{
QFETCH(QString, timestamp);
void test_Through_data()
{
QTest::addColumn<QString>("timestamp");
const char * timestamps[] =
{
"2016-02-29T13:49:54+01:00",
"2016-02-26T15:21:11+00:01",
"2016-02-24T15:52:36+01:13",
"2016-02-18T17:41:00+00:00",
"2016-02-17T15:23:19+00:00",
"2016-02-16T15:22:39+09:22",
"2016-02-10T15:06:41+00:00",
"2016-02-04T15:28:02-05:33"
};
for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++)
{
QTest::newRow(timestamps[i]) << QString(timestamps[i]);
}
}
void test_Through()
{
QFETCH(QString, timestamp);
auto time_parsed = timeFromS3Time(timestamp);
auto time_serialized = timeToS3Time(time_parsed);
QCOMPARE(time_serialized, timestamp);
}
auto time_parsed = timeFromS3Time(timestamp);
auto time_serialized = timeToS3Time(time_parsed);
QCOMPARE(time_serialized, timestamp);
}
};

View File

@ -16,163 +16,163 @@ static const int currentOrderFileVersion = 1;
bool readOverrideOrders(QString path, PatchOrder &order)
{
QFile orderFile(path);
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();
qWarning() << "Ignoring overriden order";
return false;
}
QFile orderFile(path);
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();
qWarning() << "Ignoring overriden order";
return false;
}
// and it's valid JSON
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
if (error.error != QJsonParseError::NoError)
{
qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
qWarning() << "Ignoring overriden order";
return false;
}
// and it's valid JSON
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
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
{
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));
}
auto orderArray = Json::requireArray(obj.value("order"));
for(auto item: orderArray)
{
order.append(Json::requireString(item));
}
}
catch (const JSONValidationError &err)
{
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
qWarning() << "Ignoring overriden order";
order.clear();
return false;
}
return true;
// and then read it and process it if all above is true.
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));
}
auto orderArray = Json::requireArray(obj.value("order"));
for(auto item: orderArray)
{
order.append(Json::requireString(item));
}
}
catch (const JSONValidationError &err)
{
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
qWarning() << "Ignoring overriden order";
order.clear();
return false;
}
return true;
}
static VersionFilePtr createErrorVersionFile(QString fileId, QString filepath, QString error)
{
auto outError = std::make_shared<VersionFile>();
outError->uid = outError->name = fileId;
// outError->filename = filepath;
outError->addProblem(ProblemSeverity::Error, error);
return outError;
auto outError = std::make_shared<VersionFile>();
outError->uid = outError->name = fileId;
// outError->filename = filepath;
outError->addProblem(ProblemSeverity::Error, error);
return outError;
}
static VersionFilePtr guardedParseJson(const QJsonDocument & doc,const QString &fileId,const QString &filepath,const bool &requireOrder)
{
try
{
return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder);
}
catch (const Exception &e)
{
return createErrorVersionFile(fileId, filepath, e.cause());
}
try
{
return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder);
}
catch (const Exception &e)
{
return createErrorVersionFile(fileId, filepath, e.cause());
}
}
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
{
QFile file(fileInfo.absoluteFilePath());
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);
}
QJsonParseError error;
auto data = file.readAll();
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
file.close();
if (error.error != QJsonParseError::NoError)
{
int line = 1;
int column = 0;
for(int i = 0; i < error.offset; i++)
{
if(data[i] == '\n')
{
line++;
column = 0;
continue;
}
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);
return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr);
}
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
QFile file(fileInfo.absoluteFilePath());
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);
}
QJsonParseError error;
auto data = file.readAll();
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
file.close();
if (error.error != QJsonParseError::NoError)
{
int line = 1;
int column = 0;
for(int i = 0; i < error.offset; i++)
{
if(data[i] == '\n')
{
line++;
column = 0;
continue;
}
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);
return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr);
}
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
}
bool saveJsonFile(const QJsonDocument doc, const QString & filename)
{
auto data = doc.toJson();
QSaveFile jsonFile(filename);
if(!jsonFile.open(QIODevice::WriteOnly))
{
jsonFile.cancelWriting();
qWarning() << "Couldn't open" << filename << "for writing";
return false;
}
jsonFile.write(data);
if(!jsonFile.commit())
{
qWarning() << "Couldn't save" << filename;
return false;
}
return true;
auto data = doc.toJson();
QSaveFile jsonFile(filename);
if(!jsonFile.open(QIODevice::WriteOnly))
{
jsonFile.cancelWriting();
qWarning() << "Couldn't open" << filename << "for writing";
return false;
}
jsonFile.write(data);
if(!jsonFile.commit())
{
qWarning() << "Couldn't save" << filename;
return false;
}
return true;
}
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo)
{
QFile file(fileInfo.absoluteFilePath());
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);
}
QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll());
file.close();
if (doc.isNull())
{
file.remove();
throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName()));
}
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false);
QFile file(fileInfo.absoluteFilePath());
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);
}
QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll());
file.close();
if (doc.isNull())
{
file.remove();
throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName()));
}
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false);
}
void removeLwjglFromPatch(VersionFilePtr patch)
{
auto filter = [](QList<LibraryPtr>& libs)
{
QList<LibraryPtr> filteredLibs;
for (auto lib : libs)
{
if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix()))
{
filteredLibs.append(lib);
}
}
libs = filteredLibs;
};
filter(patch->libraries);
auto filter = [](QList<LibraryPtr>& libs)
{
QList<LibraryPtr> filteredLibs;
for (auto lib : libs)
{
if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix()))
{
filteredLibs.append(lib);
}
}
libs = filteredLibs;
};
filter(patch->libraries);
}
}

View File

@ -20,74 +20,74 @@
RuleAction RuleAction_fromString(QString name)
{
if (name == "allow")
return Allow;
if (name == "disallow")
return Disallow;
return Defer;
if (name == "allow")
return Allow;
if (name == "disallow")
return Disallow;
return Defer;
}
QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
{
QList<std::shared_ptr<Rule>> rules;
auto rulesVal = objectWithRules.value("rules");
if (!rulesVal.isArray())
return rules;
QList<std::shared_ptr<Rule>> rules;
auto rulesVal = objectWithRules.value("rules");
if (!rulesVal.isArray())
return rules;
QJsonArray ruleList = rulesVal.toArray();
for (auto ruleVal : ruleList)
{
std::shared_ptr<Rule> rule;
if (!ruleVal.isObject())
continue;
auto ruleObj = ruleVal.toObject();
auto actionVal = ruleObj.value("action");
if (!actionVal.isString())
continue;
auto action = RuleAction_fromString(actionVal.toString());
if (action == Defer)
continue;
QJsonArray ruleList = rulesVal.toArray();
for (auto ruleVal : ruleList)
{
std::shared_ptr<Rule> rule;
if (!ruleVal.isObject())
continue;
auto ruleObj = ruleVal.toObject();
auto actionVal = ruleObj.value("action");
if (!actionVal.isString())
continue;
auto action = RuleAction_fromString(actionVal.toString());
if (action == Defer)
continue;
auto osVal = ruleObj.value("os");
if (!osVal.isObject())
{
// add a new implicit action rule
rules.append(ImplicitRule::create(action));
continue;
}
auto osVal = ruleObj.value("os");
if (!osVal.isObject())
{
// add a new implicit action rule
rules.append(ImplicitRule::create(action));
continue;
}
auto osObj = osVal.toObject();
auto osNameVal = osObj.value("name");
if (!osNameVal.isString())
continue;
OpSys requiredOs = OpSys_fromString(osNameVal.toString());
QString versionRegex = osObj.value("version").toString();
// add a new OS rule
rules.append(OsRule::create(action, requiredOs, versionRegex));
}
auto osObj = osVal.toObject();
auto osNameVal = osObj.value("name");
if (!osNameVal.isString())
continue;
OpSys requiredOs = OpSys_fromString(osNameVal.toString());
QString versionRegex = osObj.value("version").toString();
// add a new OS rule
rules.append(OsRule::create(action, requiredOs, versionRegex));
}
return rules;
}
QJsonObject ImplicitRule::toJson()
{
QJsonObject ruleObj;
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
return ruleObj;
QJsonObject ruleObj;
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
return ruleObj;
}
QJsonObject OsRule::toJson()
{
QJsonObject ruleObj;
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
QJsonObject osObj;
{
osObj.insert("name", OpSys_toString(m_system));
if(!m_version_regexp.isEmpty())
{
osObj.insert("version", m_version_regexp);
}
}
ruleObj.insert("os", osObj);
return ruleObj;
QJsonObject ruleObj;
ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow"));
QJsonObject osObj;
{
osObj.insert("name", OpSys_toString(m_system));
if(!m_version_regexp.isEmpty())
{
osObj.insert("version", m_version_regexp);
}
}
ruleObj.insert("os", osObj);
return ruleObj;
}

View File

@ -26,9 +26,9 @@ class Rule;
enum RuleAction
{
Allow,
Disallow,
Defer
Allow,
Disallow,
Defer
};
QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules);
@ -36,66 +36,66 @@ QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules)
class Rule
{
protected:
RuleAction m_result;
virtual bool applies(const Library *parent) = 0;
RuleAction m_result;
virtual bool applies(const Library *parent) = 0;
public:
Rule(RuleAction result) : m_result(result)
{
}
virtual ~Rule() {};
virtual QJsonObject toJson() = 0;
RuleAction apply(const Library *parent)
{
if (applies(parent))
return m_result;
else
return Defer;
}
Rule(RuleAction result) : m_result(result)
{
}
virtual ~Rule() {};
virtual QJsonObject toJson() = 0;
RuleAction apply(const Library *parent)
{
if (applies(parent))
return m_result;
else
return Defer;
}
};
class OsRule : public Rule
{
private:
// the OS
OpSys m_system;
// the OS version regexp
QString m_version_regexp;
// the OS
OpSys m_system;
// the OS version regexp
QString m_version_regexp;
protected:
virtual bool applies(const Library *)
{
return (m_system == currentSystem);
}
OsRule(RuleAction result, OpSys system, QString version_regexp)
: Rule(result), m_system(system), m_version_regexp(version_regexp)
{
}
virtual bool applies(const Library *)
{
return (m_system == currentSystem);
}
OsRule(RuleAction result, OpSys system, QString version_regexp)
: Rule(result), m_system(system), m_version_regexp(version_regexp)
{
}
public:
virtual QJsonObject toJson();
static std::shared_ptr<OsRule> create(RuleAction result, OpSys system,
QString version_regexp)
{
return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp));
}
virtual QJsonObject toJson();
static std::shared_ptr<OsRule> create(RuleAction result, OpSys 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 *)
{
return true;
}
ImplicitRule(RuleAction result) : Rule(result)
{
}
virtual bool applies(const Library *)
{
return true;
}
ImplicitRule(RuleAction result) : Rule(result)
{
}
public:
virtual QJsonObject toJson();
static std::shared_ptr<ImplicitRule> create(RuleAction result)
{
return std::shared_ptr<ImplicitRule>(new ImplicitRule(result));
}
virtual QJsonObject toJson();
static std::shared_ptr<ImplicitRule> create(RuleAction result)
{
return std::shared_ptr<ImplicitRule>(new ImplicitRule(result));
}
};

View File

@ -24,344 +24,344 @@
SimpleModList::SimpleModList(const QString &dir) : QAbstractListModel(), m_dir(dir)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
}
void SimpleModList::startWatching()
{
if(is_watching)
return;
if(is_watching)
return;
update();
update();
is_watching = m_watcher->addPath(m_dir.absolutePath());
if (is_watching)
{
qDebug() << "Started watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to start watching " << m_dir.absolutePath();
}
is_watching = m_watcher->addPath(m_dir.absolutePath());
if (is_watching)
{
qDebug() << "Started watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to start watching " << m_dir.absolutePath();
}
}
void SimpleModList::stopWatching()
{
if(!is_watching)
return;
if(!is_watching)
return;
is_watching = !m_watcher->removePath(m_dir.absolutePath());
if (!is_watching)
{
qDebug() << "Stopped watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
}
is_watching = !m_watcher->removePath(m_dir.absolutePath());
if (!is_watching)
{
qDebug() << "Stopped watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
}
}
bool SimpleModList::update()
{
if (!isValid())
return false;
if (!isValid())
return false;
QList<Mod> orderedMods;
QList<Mod> newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
bool orderOrStateChanged = false;
QList<Mod> orderedMods;
QList<Mod> newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
bool orderOrStateChanged = false;
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents)
{
newMods.append(Mod(entry));
}
orderedMods.append(newMods);
orderOrStateChanged = true;
}
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else
for (int i = 0; i < mods.size(); i++)
{
if (!mods[i].strongCompare(orderedMods[i]))
{
orderOrStateChanged = true;
break;
}
}
}
beginResetModel();
mods.swap(orderedMods);
endResetModel();
if (orderOrStateChanged)
{
emit changed();
}
return true;
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents)
{
newMods.append(Mod(entry));
}
orderedMods.append(newMods);
orderOrStateChanged = true;
}
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else
for (int i = 0; i < mods.size(); i++)
{
if (!mods[i].strongCompare(orderedMods[i]))
{
orderOrStateChanged = true;
break;
}
}
}
beginResetModel();
mods.swap(orderedMods);
endResetModel();
if (orderOrStateChanged)
{
emit changed();
}
return true;
}
void SimpleModList::directoryChanged(QString path)
{
update();
update();
}
bool SimpleModList::isValid()
{
return m_dir.exists() && m_dir.isReadable();
return m_dir.exists() && m_dir.isReadable();
}
bool SimpleModList::installMod(const QString &filename)
{
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
QFileInfo fileinfo(FS::NormalizePath(filename));
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
QFileInfo fileinfo(FS::NormalizePath(filename));
qDebug() << "installing: " << fileinfo.absoluteFilePath();
qDebug() << "installing: " << fileinfo.absoluteFilePath();
if (!fileinfo.exists() || !fileinfo.isReadable())
{
return false;
}
Mod m(fileinfo);
if (!m.valid())
return false;
if (!fileinfo.exists() || !fileinfo.isReadable())
{
return false;
}
Mod m(fileinfo);
if (!m.valid())
return false;
auto type = m.type();
if (type == Mod::MOD_UNKNOWN)
return false;
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
{
QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName());
if (!QFile::copy(fileinfo.filePath(), newpath))
return false;
FS::updateTimestamp(newpath);
m.repath(newpath);
update();
return true;
}
else if (type == Mod::MOD_FOLDER)
{
QString from = fileinfo.filePath();
QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName());
if (!FS::copy(from, to)())
return false;
m.repath(to);
update();
return true;
}
return false;
auto type = m.type();
if (type == Mod::MOD_UNKNOWN)
return false;
if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
{
QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName());
if (!QFile::copy(fileinfo.filePath(), newpath))
return false;
FS::updateTimestamp(newpath);
m.repath(newpath);
update();
return true;
}
else if (type == Mod::MOD_FOLDER)
{
QString from = fileinfo.filePath();
QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName());
if (!FS::copy(from, to)())
return false;
m.repath(to);
update();
return true;
}
return false;
}
bool SimpleModList::enableMods(const QModelIndexList& indexes, bool enable)
{
if(indexes.isEmpty())
return true;
if(indexes.isEmpty())
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.enable(enable);
emit dataChanged(i, i);
}
emit changed();
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.enable(enable);
emit dataChanged(i, i);
}
emit changed();
return true;
}
bool SimpleModList::deleteMods(const QModelIndexList& indexes)
{
if(indexes.isEmpty())
return true;
if(indexes.isEmpty())
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.destroy();
}
emit changed();
return true;
for (auto i: indexes)
{
Mod &m = mods[i.row()];
m.destroy();
}
emit changed();
return true;
}
int SimpleModList::columnCount(const QModelIndex &parent) const
{
return NUM_COLUMNS;
return NUM_COLUMNS;
}
QVariant SimpleModList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
int row = index.row();
int column = index.column();
if (row < 0 || row >= mods.size())
return QVariant();
if (row < 0 || row >= mods.size())
return QVariant();
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
return mods[row].name();
case VersionColumn:
return mods[row].version();
case DateColumn:
return mods[row].dateTimeChanged();
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
return mods[row].name();
case VersionColumn:
return mods[row].version();
case DateColumn:
return mods[row].dateTimeChanged();
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
return mods[row].mmc_id();
case Qt::ToolTipRole:
return mods[row].mmc_id();
case Qt::CheckStateRole:
switch (column)
{
case ActiveColumn:
return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::CheckStateRole:
switch (column)
{
case ActiveColumn:
return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
default:
return QVariant();
}
}
bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (role == Qt::CheckStateRole)
{
auto &mod = mods[index.row()];
if (mod.enable(!mod.enabled()))
{
emit dataChanged(index, index);
return true;
}
}
return false;
if (role == Qt::CheckStateRole)
{
auto &mod = mods[index.row()];
if (mod.enable(!mod.enabled()))
{
emit dataChanged(index, index);
return true;
}
}
return false;
}
QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return QString();
case NameColumn:
return tr("Name");
case VersionColumn:
return tr("Version");
case DateColumn:
return tr("Last changed");
default:
return QVariant();
}
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return QString();
case NameColumn:
return tr("Name");
case VersionColumn:
return tr("Version");
case DateColumn:
return tr("Last changed");
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case ActiveColumn:
return tr("Is the mod enabled?");
case NameColumn:
return tr("The name of the mod.");
case VersionColumn:
return tr("The version of the mod.");
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
case Qt::ToolTipRole:
switch (section)
{
case ActiveColumn:
return tr("Is the mod enabled?");
case NameColumn:
return tr("The name of the mod.");
case VersionColumn:
return tr("The version of the mod.");
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
}
Qt::ItemFlags SimpleModList::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
Qt::DropActions SimpleModList::supportedDropActions() const
{
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
}
QStringList SimpleModList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
QStringList types;
types << "text/uri-list";
return types;
}
bool SimpleModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
{
if (action == Qt::IgnoreAction)
{
return true;
}
if (action == Qt::IgnoreAction)
{
return true;
}
// check if the action is supported
if (!data || !(action & supportedDropActions()))
{
return false;
}
// check if the action is supported
if (!data || !(action & supportedDropActions()))
{
return false;
}
// files dropped from outside?
if (data->hasUrls())
{
bool was_watching = is_watching;
if (was_watching)
{
stopWatching();
}
auto urls = data->urls();
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
{
continue;
}
// TODO: implement not only copy, but also move
// FIXME: handle errors here
installMod(url.toLocalFile());
}
if (was_watching)
{
startWatching();
}
return true;
}
return false;
// files dropped from outside?
if (data->hasUrls())
{
bool was_watching = is_watching;
if (was_watching)
{
stopWatching();
}
auto urls = data->urls();
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
{
continue;
}
// TODO: implement not only copy, but also move
// FIXME: handle errors here
installMod(url.toLocalFile());
}
if (was_watching)
{
startWatching();
}
return true;
}
return false;
}

View File

@ -34,88 +34,88 @@ class QFileSystemWatcher;
*/
class MULTIMC_LOGIC_EXPORT SimpleModList : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum Columns
{
ActiveColumn = 0,
NameColumn,
DateColumn,
VersionColumn,
NUM_COLUMNS
};
SimpleModList(const QString &dir);
enum Columns
{
ActiveColumn = 0,
NameColumn,
DateColumn,
VersionColumn,
NUM_COLUMNS
};
SimpleModList(const QString &dir);
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;
Qt::DropActions supportedDropActions() const 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;
Qt::DropActions supportedDropActions() const override;
/// flags, mostly to support drag&drop
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
/// flags, mostly to support drag&drop
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
virtual int rowCount(const QModelIndex &) const override
{
return size();
}
;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual int columnCount(const QModelIndex &parent) const override;
virtual int rowCount(const QModelIndex &) const override
{
return size();
}
;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual int columnCount(const QModelIndex &parent) const override;
size_t size() const
{
return mods.size();
}
;
bool empty() const
{
return size() == 0;
}
Mod &operator[](size_t index)
{
return mods[index];
}
size_t size() const
{
return mods.size();
}
;
bool empty() const
{
return size() == 0;
}
Mod &operator[](size_t index)
{
return mods[index];
}
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
/**
* Adds the given mod to the list at the given index - if the list supports custom ordering
*/
bool installMod(const QString& filename);
/**
* Adds the given mod to the list at the given index - if the list supports custom ordering
*/
bool installMod(const QString& filename);
/// Deletes all the selected mods
virtual bool deleteMods(const QModelIndexList &indexes);
/// Deletes all the selected mods
virtual bool deleteMods(const QModelIndexList &indexes);
/// Enable or disable listed mods
virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
/// Enable or disable listed mods
virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
void startWatching();
void stopWatching();
void startWatching();
void stopWatching();
virtual bool isValid();
virtual bool isValid();
QDir dir()
{
return m_dir;
}
QDir dir()
{
return m_dir;
}
const QList<Mod> & allMods()
{
return mods;
}
const QList<Mod> & allMods()
{
return mods;
}
private
slots:
void directoryChanged(QString path);
void directoryChanged(QString path);
signals:
void changed();
void changed();
protected:
QFileSystemWatcher *m_watcher;
bool is_watching = false;
QDir m_dir;
QList<Mod> mods;
QFileSystemWatcher *m_watcher;
bool is_watching = false;
QDir m_dir;
QList<Mod> mods;
};

View File

@ -8,44 +8,44 @@
class SimpleModListTest : public QObject
{
Q_OBJECT
Q_OBJECT
private
slots:
// test for GH-1178 - install a folder with files to a mod list
void test_1178()
{
// source
QString source = QFINDTESTDATA("data/test_folder");
// test for GH-1178 - install a folder with files to a mod list
void test_1178()
{
// source
QString source = QFINDTESTDATA("data/test_folder");
// sanity check
QVERIFY(!source.endsWith('/'));
// sanity check
QVERIFY(!source.endsWith('/'));
auto verify = [](QString path)
{
QDir target_dir(FS::PathCombine(path, "test_folder"));
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
};
auto verify = [](QString path)
{
QDir target_dir(FS::PathCombine(path, "test_folder"));
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
};
// 1. test with no trailing /
{
QString folder = source;
QTemporaryDir tempDir;
SimpleModList m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
// 1. test with no trailing /
{
QString folder = source;
QTemporaryDir tempDir;
SimpleModList m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
// 2. test with trailing /
{
QString folder = source + '/';
QTemporaryDir tempDir;
SimpleModList m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
}
// 2. test with trailing /
{
QString folder = source + '/';
QTemporaryDir tempDir;
SimpleModList m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
}
};
QTEST_GUILESS_MAIN(SimpleModListTest)

View File

@ -4,66 +4,66 @@
#include <Env.h>
QByteArray getModelString(SkinUpload::Model model) {
switch (model) {
case SkinUpload::STEVE:
return "";
case SkinUpload::ALEX:
return "slim";
default:
qDebug() << "Unknown skin type!";
return "";
}
switch (model) {
case SkinUpload::STEVE:
return "";
case SkinUpload::ALEX:
return "slim";
default:
qDebug() << "Unknown skin type!";
return "";
}
}
SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, SkinUpload::Model model)
: Task(parent), m_model(model), m_skin(skin), m_session(session)
: Task(parent), m_model(model), m_skin(skin), m_session(session)
{
}
void SkinUpload::executeTask()
{
QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid)));
request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit());
QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid)));
request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit());
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart model;
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\""));
model.setBody(getModelString(m_model));
QHttpPart model;
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\""));
model.setBody(getModelString(m_model));
QHttpPart skin;
skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
skin.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
skin.setBody(m_skin);
QHttpPart skin;
skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
skin.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
skin.setBody(m_skin);
multiPart->append(model);
multiPart->append(skin);
multiPart->append(model);
multiPart->append(skin);
QNetworkReply *rep = ENV.qnam().put(request, multiPart);
m_reply = std::shared_ptr<QNetworkReply>(rep);
QNetworkReply *rep = ENV.qnam().put(request, multiPart);
m_reply = std::shared_ptr<QNetworkReply>(rep);
setStatus(tr("Uploading skin"));
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
setStatus(tr("Uploading skin"));
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
}
void SkinUpload::downloadError(QNetworkReply::NetworkError error)
{
// error happened during download.
qCritical() << "Network error: " << error;
emitFailed(m_reply->errorString());
// error happened during download.
qCritical() << "Network error: " << error;
emitFailed(m_reply->errorString());
}
void SkinUpload::downloadFinished()
{
// if the download failed
if (m_reply->error() != QNetworkReply::NetworkError::NoError)
{
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
m_reply.reset();
return;
}
emitSucceeded();
// if the download failed
if (m_reply->error() != QNetworkReply::NetworkError::NoError)
{
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
m_reply.reset();
return;
}
emitSucceeded();
}

View File

@ -11,29 +11,29 @@ typedef std::shared_ptr<class SkinUpload> SkinUploadPtr;
class MULTIMC_LOGIC_EXPORT SkinUpload : public Task
{
Q_OBJECT
Q_OBJECT
public:
enum Model
{
STEVE,
ALEX
};
enum Model
{
STEVE,
ALEX
};
// Note this class takes ownership of the file.
SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE);
virtual ~SkinUpload() {}
// Note this class takes ownership of the file.
SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE);
virtual ~SkinUpload() {}
private:
Model m_model;
QByteArray m_skin;
AuthSessionPtr m_session;
std::shared_ptr<QNetworkReply> m_reply;
Model m_model;
QByteArray m_skin;
AuthSessionPtr m_session;
std::shared_ptr<QNetworkReply> m_reply;
protected:
virtual void executeTask();
virtual void executeTask();
public slots:
void downloadError(QNetworkReply::NetworkError);
void downloadError(QNetworkReply::NetworkError);
void downloadFinished();
void downloadFinished();
};

View File

@ -12,45 +12,45 @@
static bool isMinecraftVersion(const QString &uid)
{
return uid == "net.minecraft";
return uid == "net.minecraft";
}
void VersionFile::applyTo(LaunchProfile *profile)
{
// Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid))
{
profile->applyMinecraftVersion(minecraftVersion);
profile->applyMinecraftVersionType(type);
// 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/
profile->applyMinecraftAssets(mojangAssetIndex);
}
// Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid))
{
profile->applyMinecraftVersion(minecraftVersion);
profile->applyMinecraftVersionType(type);
// 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/
profile->applyMinecraftAssets(mojangAssetIndex);
}
profile->applyMainJar(mainJar);
profile->applyMainClass(mainClass);
profile->applyAppletClass(appletClass);
profile->applyMinecraftArguments(minecraftArguments);
profile->applyTweakers(addTweakers);
profile->applyJarMods(jarMods);
profile->applyMods(mods);
profile->applyTraits(traits);
profile->applyMainJar(mainJar);
profile->applyMainClass(mainClass);
profile->applyAppletClass(appletClass);
profile->applyMinecraftArguments(minecraftArguments);
profile->applyTweakers(addTweakers);
profile->applyJarMods(jarMods);
profile->applyMods(mods);
profile->applyTraits(traits);
for (auto library : libraries)
{
profile->applyLibrary(library);
}
profile->applyProblemSeverity(getProblemSeverity());
for (auto library : libraries)
{
profile->applyLibrary(library);
}
profile->applyProblemSeverity(getProblemSeverity());
}
/*
auto theirVersion = profile->getMinecraftVersion();
if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull())
{
if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1)
{
throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion);
}
}
auto theirVersion = profile->getMinecraftVersion();
if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull())
{
if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1)
{
throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion);
}
}
*/

View File

@ -21,91 +21,91 @@ struct MojangAssetIndexInfo;
using VersionFilePtr = std::shared_ptr<VersionFile>;
class VersionFile : public ProblemContainer
{
friend class MojangVersionFormat;
friend class OneSixVersionFormat;
friend class MojangVersionFormat;
friend class OneSixVersionFormat;
public: /* methods */
void applyTo(LaunchProfile* profile);
void applyTo(LaunchProfile* profile);
public: /* data */
/// MultiMC: order hint for this version file if no explicit order is set
int order = 0;
/// MultiMC: order hint for this version file if no explicit order is set
int order = 0;
/// MultiMC: human readable name of this package
QString name;
/// MultiMC: human readable name of this package
QString name;
/// MultiMC: package ID of this package
QString uid;
/// MultiMC: package ID of this package
QString uid;
/// MultiMC: version of this package
QString version;
/// MultiMC: version of this package
QString version;
/// MultiMC: DEPRECATED dependency on a Minecraft version
QString dependsOnMinecraftVersion;
/// MultiMC: DEPRECATED dependency on a Minecraft version
QString dependsOnMinecraftVersion;
/// Mojang: DEPRECATED used to version the Mojang version format
int minimumLauncherVersion = -1;
/// Mojang: DEPRECATED used to version the Mojang version format
int minimumLauncherVersion = -1;
/// Mojang: DEPRECATED version of Minecraft this is
QString minecraftVersion;
/// Mojang: DEPRECATED version of Minecraft this is
QString minecraftVersion;
/// Mojang: class to launch Minecraft with
QString mainClass;
/// Mojang: class to launch Minecraft with
QString mainClass;
/// MultiMC: class to launch legacy Minecraft with (embed in a custom window)
QString appletClass;
/// MultiMC: class to launch legacy Minecraft with (embed in a custom window)
QString appletClass;
/// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution)
QString minecraftArguments;
/// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution)
QString minecraftArguments;
/// Mojang: type of the Minecraft version
QString type;
/// Mojang: type of the Minecraft version
QString type;
/// Mojang: the time this version was actually released by Mojang
QDateTime releaseTime;
/// Mojang: the time this version was actually released by Mojang
QDateTime releaseTime;
/// Mojang: DEPRECATED the time this version was last updated by Mojang
QDateTime updateTime;
/// Mojang: DEPRECATED the time this version was last updated by Mojang
QDateTime updateTime;
/// Mojang: DEPRECATED asset group to be used with Minecraft
QString assets;
/// Mojang: DEPRECATED asset group to be used with Minecraft
QString assets;
/// MultiMC: list of tweaker mod arguments for launchwrapper
QStringList addTweakers;
/// MultiMC: list of tweaker mod arguments for launchwrapper
QStringList addTweakers;
/// Mojang: list of libraries to add to the version
QList<LibraryPtr> libraries;
/// Mojang: list of libraries to add to the version
QList<LibraryPtr> libraries;
/// The main jar (Minecraft version library, normally)
LibraryPtr mainJar;
/// The main jar (Minecraft version library, normally)
LibraryPtr mainJar;
/// MultiMC: list of attached traits of this version file - used to enable features
QSet<QString> traits;
/// MultiMC: list of attached traits of this version file - used to enable features
QSet<QString> traits;
/// MultiMC: list of jar mods added to this version
QList<LibraryPtr> jarMods;
/// MultiMC: list of jar mods added to this version
QList<LibraryPtr> jarMods;
/// MultiMC: list of mods added to this version
QList<LibraryPtr> mods;
/// MultiMC: list of mods added to this version
QList<LibraryPtr> mods;
/**
* MultiMC: set of packages this depends on
* NOTE: this is shared with the meta format!!!
*/
Meta::RequireSet requires;
/**
* MultiMC: set of packages this depends on
* NOTE: this is shared with the meta format!!!
*/
Meta::RequireSet requires;
/**
* MultiMC: set of packages this conflicts with
* NOTE: this is shared with the meta format!!!
*/
Meta::RequireSet conflicts;
/**
* MultiMC: set of packages this conflicts with
* NOTE: this is shared with the meta format!!!
*/
Meta::RequireSet conflicts;
/// is volatile -- may be removed as soon as it is no longer needed by something else
bool m_volatile = false;
/// is volatile -- may be removed as soon as it is no longer needed by something else
bool m_volatile = false;
public:
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
// Mojang: extended asset index download information
std::shared_ptr<MojangAssetIndexInfo> mojangAssetIndex;
// Mojang: extended asset index download information
std::shared_ptr<MojangAssetIndexInfo> mojangAssetIndex;
};

View File

@ -5,64 +5,64 @@ VersionFilterData g_VersionFilterData = VersionFilterData();
VersionFilterData::VersionFilterData()
{
// 1.3.*
auto libs13 =
QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}};
// 1.3.*
auto libs13 =
QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}};
fmlLibsMapping["1.3.2"] = libs13;
fmlLibsMapping["1.3.2"] = libs13;
// 1.4.*
auto libs14 = QList<FMLlib>{
{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false},
{"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}};
// 1.4.*
auto libs14 = QList<FMLlib>{
{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false},
{"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}};
fmlLibsMapping["1.4"] = libs14;
fmlLibsMapping["1.4.1"] = libs14;
fmlLibsMapping["1.4.2"] = libs14;
fmlLibsMapping["1.4.3"] = libs14;
fmlLibsMapping["1.4.4"] = libs14;
fmlLibsMapping["1.4.5"] = libs14;
fmlLibsMapping["1.4.6"] = libs14;
fmlLibsMapping["1.4.7"] = libs14;
fmlLibsMapping["1.4"] = libs14;
fmlLibsMapping["1.4.1"] = libs14;
fmlLibsMapping["1.4.2"] = libs14;
fmlLibsMapping["1.4.3"] = libs14;
fmlLibsMapping["1.4.4"] = libs14;
fmlLibsMapping["1.4.5"] = libs14;
fmlLibsMapping["1.4.6"] = libs14;
fmlLibsMapping["1.4.7"] = libs14;
// 1.5
fmlLibsMapping["1.5"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
// 1.5
fmlLibsMapping["1.5"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
// 1.5.1
fmlLibsMapping["1.5.1"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
// 1.5.1
fmlLibsMapping["1.5.1"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
// 1.5.2
fmlLibsMapping["1.5.2"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
// 1.5.2
fmlLibsMapping["1.5.2"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
// don't use installers for those.
forgeInstallerBlacklist = QSet<QString>({"1.5.2"});
// don't use installers for those.
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"};
// 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"};
}

View File

@ -8,21 +8,21 @@
struct FMLlib
{
QString filename;
QString checksum;
bool ours;
QString filename;
QString checksum;
bool ours;
};
struct VersionFilterData
{
VersionFilterData();
// mapping between minecraft versions and FML libraries required
QMap<QString, QList<FMLlib>> fmlLibsMapping;
// set of minecraft versions for which using forge installers is blacklisted
QSet<QString> forgeInstallerBlacklist;
// no new versions below this date will be accepted from Mojang servers
QDateTime legacyCutoffDate;
// Libraries that belong to LWJGL
QSet<QString> lwjglWhitelist;
VersionFilterData();
// mapping between minecraft versions and FML libraries required
QMap<QString, QList<FMLlib>> fmlLibsMapping;
// set of minecraft versions for which using forge installers is blacklisted
QSet<QString> forgeInstallerBlacklist;
// no new versions below this date will be accepted from Mojang servers
QDateTime legacyCutoffDate;
// Libraries that belong to LWJGL
QSet<QString> lwjglWhitelist;
};
extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData;

View File

@ -32,354 +32,354 @@
std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data)
{
QByteArray output;
if(!GZip::unzip(data, output))
{
return nullptr;
}
std::istringstream foo(std::string(output.constData(), output.size()));
auto pair = nbt::io::read_compound(foo);
QByteArray output;
if(!GZip::unzip(data, output))
{
return nullptr;
}
std::istringstream foo(std::string(output.constData(), output.size()));
auto pair = nbt::io::read_compound(foo);
if(pair.first != "")
return nullptr;
if(pair.first != "")
return nullptr;
if(pair.second == nullptr)
return nullptr;
if(pair.second == nullptr)
return nullptr;
return std::move(pair.second);
return std::move(pair.second);
}
QByteArray serializeLevelDat(nbt::tag_compound * levelInfo)
{
std::ostringstream s;
nbt::io::write_tag("", *levelInfo, s);
QByteArray val( s.str().data(), (int) s.str().size() );
return val;
std::ostringstream s;
nbt::io::write_tag("", *levelInfo, s);
QByteArray val( s.str().data(), (int) s.str().size() );
return val;
}
QString getLevelDatFromFS(const QFileInfo &file)
{
QDir worldDir(file.filePath());
if(!file.isDir() || !worldDir.exists("level.dat"))
{
return QString();
}
return worldDir.absoluteFilePath("level.dat");
QDir worldDir(file.filePath());
if(!file.isDir() || !worldDir.exists("level.dat"))
{
return QString();
}
return worldDir.absoluteFilePath("level.dat");
}
QByteArray getLevelDatDataFromFS(const QFileInfo &file)
{
auto fullFilePath = getLevelDatFromFS(file);
if(fullFilePath.isNull())
{
return QByteArray();
}
QFile f(fullFilePath);
if(!f.open(QIODevice::ReadOnly))
{
return QByteArray();
}
return f.readAll();
auto fullFilePath = getLevelDatFromFS(file);
if(fullFilePath.isNull())
{
return QByteArray();
}
QFile f(fullFilePath);
if(!f.open(QIODevice::ReadOnly))
{
return QByteArray();
}
return f.readAll();
}
bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data)
{
auto fullFilePath = getLevelDatFromFS(file);
if(fullFilePath.isNull())
{
return false;
}
QSaveFile f(fullFilePath);
if(!f.open(QIODevice::WriteOnly))
{
return false;
}
QByteArray compressed;
if(!GZip::zip(data, compressed))
{
return false;
}
if(f.write(compressed) != compressed.size())
{
f.cancelWriting();
return false;
}
return f.commit();
auto fullFilePath = getLevelDatFromFS(file);
if(fullFilePath.isNull())
{
return false;
}
QSaveFile f(fullFilePath);
if(!f.open(QIODevice::WriteOnly))
{
return false;
}
QByteArray compressed;
if(!GZip::zip(data, compressed))
{
return false;
}
if(f.write(compressed) != compressed.size())
{
f.cancelWriting();
return false;
}
return f.commit();
}
World::World(const QFileInfo &file)
{
repath(file);
repath(file);
}
void World::repath(const QFileInfo &file)
{
m_containerFile = file;
m_folderName = file.fileName();
if(file.isFile() && file.suffix() == "zip")
{
readFromZip(file);
}
else if(file.isDir())
{
readFromFS(file);
}
m_containerFile = file;
m_folderName = file.fileName();
if(file.isFile() && file.suffix() == "zip")
{
readFromZip(file);
}
else if(file.isDir())
{
readFromFS(file);
}
}
void World::readFromFS(const QFileInfo &file)
{
auto bytes = getLevelDatDataFromFS(file);
if(bytes.isEmpty())
{
is_valid = false;
return;
}
loadFromLevelDat(bytes);
levelDatTime = file.lastModified();
auto bytes = getLevelDatDataFromFS(file);
if(bytes.isEmpty())
{
is_valid = false;
return;
}
loadFromLevelDat(bytes);
levelDatTime = file.lastModified();
}
void World::readFromZip(const QFileInfo &file)
{
QuaZip zip(file.absoluteFilePath());
is_valid = zip.open(QuaZip::mdUnzip);
if (!is_valid)
{
return;
}
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
is_valid = !location.isEmpty();
if (!is_valid)
{
return;
}
m_containerOffsetPath = location;
QuaZipFile zippedFile(&zip);
// read the install profile
is_valid = zip.setCurrentFile(location + "level.dat");
if (!is_valid)
{
return;
}
is_valid = zippedFile.open(QIODevice::ReadOnly);
QuaZipFileInfo64 levelDatInfo;
zippedFile.getFileInfo(&levelDatInfo);
auto modTime = levelDatInfo.getNTFSmTime();
if(!modTime.isValid())
{
modTime = levelDatInfo.dateTime;
}
levelDatTime = modTime;
if (!is_valid)
{
return;
}
loadFromLevelDat(zippedFile.readAll());
zippedFile.close();
QuaZip zip(file.absoluteFilePath());
is_valid = zip.open(QuaZip::mdUnzip);
if (!is_valid)
{
return;
}
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
is_valid = !location.isEmpty();
if (!is_valid)
{
return;
}
m_containerOffsetPath = location;
QuaZipFile zippedFile(&zip);
// read the install profile
is_valid = zip.setCurrentFile(location + "level.dat");
if (!is_valid)
{
return;
}
is_valid = zippedFile.open(QIODevice::ReadOnly);
QuaZipFileInfo64 levelDatInfo;
zippedFile.getFileInfo(&levelDatInfo);
auto modTime = levelDatInfo.getNTFSmTime();
if(!modTime.isValid())
{
modTime = levelDatInfo.dateTime;
}
levelDatTime = modTime;
if (!is_valid)
{
return;
}
loadFromLevelDat(zippedFile.readAll());
zippedFile.close();
}
bool World::install(const QString &to, const QString &name)
{
auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to));
if(!FS::ensureFolderPathExists(finalPath))
{
return false;
}
bool ok = false;
if(m_containerFile.isFile())
{
QuaZip zip(m_containerFile.absoluteFilePath());
if (!zip.open(QuaZip::mdUnzip))
{
return false;
}
ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty();
}
else if(m_containerFile.isDir())
{
QString from = m_containerFile.filePath();
ok = FS::copy(from, finalPath)();
}
auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to));
if(!FS::ensureFolderPathExists(finalPath))
{
return false;
}
bool ok = false;
if(m_containerFile.isFile())
{
QuaZip zip(m_containerFile.absoluteFilePath());
if (!zip.open(QuaZip::mdUnzip))
{
return false;
}
ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty();
}
else if(m_containerFile.isDir())
{
QString from = m_containerFile.filePath();
ok = FS::copy(from, finalPath)();
}
if(ok && !name.isEmpty() && m_actualName != name)
{
World newWorld(finalPath);
if(newWorld.isValid())
{
newWorld.rename(name);
}
}
return ok;
if(ok && !name.isEmpty() && m_actualName != name)
{
World newWorld(finalPath);
if(newWorld.isValid())
{
newWorld.rename(name);
}
}
return ok;
}
bool World::rename(const QString &newName)
{
if(m_containerFile.isFile())
{
return false;
}
if(m_containerFile.isFile())
{
return false;
}
auto data = getLevelDatDataFromFS(m_containerFile);
if(data.isEmpty())
{
return false;
}
auto data = getLevelDatDataFromFS(m_containerFile);
if(data.isEmpty())
{
return false;
}
auto worldData = parseLevelDat(data);
if(!worldData)
{
return false;
}
auto &val = worldData->at("Data");
if(val.get_type() != nbt::tag_type::Compound)
{
return false;
}
auto &dataCompound = val.as<nbt::tag_compound>();
dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data()));
data = serializeLevelDat(worldData.get());
auto worldData = parseLevelDat(data);
if(!worldData)
{
return false;
}
auto &val = worldData->at("Data");
if(val.get_type() != nbt::tag_type::Compound)
{
return false;
}
auto &dataCompound = val.as<nbt::tag_compound>();
dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data()));
data = serializeLevelDat(worldData.get());
putLevelDatDataToFS(m_containerFile, data);
putLevelDatDataToFS(m_containerFile, data);
m_actualName = newName;
m_actualName = newName;
QDir parentDir(m_containerFile.absoluteFilePath());
parentDir.cdUp();
QFile container(m_containerFile.absoluteFilePath());
auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath());
container.rename(parentDir.absoluteFilePath(dirName));
QDir parentDir(m_containerFile.absoluteFilePath());
parentDir.cdUp();
QFile container(m_containerFile.absoluteFilePath());
auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath());
container.rename(parentDir.absoluteFilePath(dirName));
return true;
return true;
}
static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString())
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::String)
{
return fallback;
}
auto & tag_str = namedValue.as<nbt::tag_string>();
return QString::fromStdString(tag_str.get());
}
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback;
return fallback;
}
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::String)
{
return fallback;
}
auto & tag_str = namedValue.as<nbt::tag_string>();
return QString::fromStdString(tag_str.get());
}
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback;
return fallback;
}
}
static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::Long)
{
return fallback;
}
auto & tag_str = namedValue.as<nbt::tag_long>();
return tag_str.get();
}
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback;
return fallback;
}
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::Long)
{
return fallback;
}
auto & tag_str = namedValue.as<nbt::tag_long>();
return tag_str.get();
}
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback;
return fallback;
}
}
void World::loadFromLevelDat(QByteArray data)
{
try
{
auto levelData = parseLevelDat(data);
if(!levelData)
{
is_valid = false;
return;
}
try
{
auto levelData = parseLevelDat(data);
if(!levelData)
{
is_valid = false;
return;
}
auto &val = levelData->at("Data");
is_valid = val.get_type() == nbt::tag_type::Compound;
if(!is_valid)
return;
auto &val = levelData->at("Data");
is_valid = val.get_type() == nbt::tag_type::Compound;
if(!is_valid)
return;
m_actualName = read_string(val, "LevelName", m_folderName);
m_actualName = read_string(val, "LevelName", m_folderName);
int64_t temp = read_long(val, "LastPlayed", 0);
if(temp == 0)
{
m_lastPlayed = levelDatTime;
}
else
{
m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp);
}
int64_t temp = read_long(val, "LastPlayed", 0);
if(temp == 0)
{
m_lastPlayed = levelDatTime;
}
else
{
m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp);
}
m_randomSeed = read_long(val, "RandomSeed", 0);
m_randomSeed = read_long(val, "RandomSeed", 0);
qDebug() << "World Name:" << m_actualName;
qDebug() << "Last Played:" << m_lastPlayed.toString();
qDebug() << "Seed:" << m_randomSeed;
}
catch (const nbt::io::input_error &e)
{
qWarning() << "Unable to load" << m_folderName << ":" << e.what();
is_valid = false;
return;
}
qDebug() << "World Name:" << m_actualName;
qDebug() << "Last Played:" << m_lastPlayed.toString();
qDebug() << "Seed:" << m_randomSeed;
}
catch (const nbt::io::input_error &e)
{
qWarning() << "Unable to load" << m_folderName << ":" << e.what();
is_valid = false;
return;
}
}
bool World::replace(World &with)
{
if (!destroy())
return false;
bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())();
if (success)
{
m_folderName = with.m_folderName;
m_containerFile.refresh();
}
return success;
if (!destroy())
return false;
bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())();
if (success)
{
m_folderName = with.m_folderName;
m_containerFile.refresh();
}
return success;
}
bool World::destroy()
{
if(!is_valid) return false;
if (m_containerFile.isDir())
{
QDir d(m_containerFile.filePath());
return d.removeRecursively();
}
else if(m_containerFile.isFile())
{
QFile file(m_containerFile.absoluteFilePath());
return file.remove();
}
return true;
if(!is_valid) return false;
if (m_containerFile.isDir())
{
QDir d(m_containerFile.filePath());
return d.removeRecursively();
}
else if(m_containerFile.isFile())
{
QFile file(m_containerFile.absoluteFilePath());
return file.remove();
}
return true;
}
bool World::operator==(const World &other) const
{
return is_valid == other.is_valid && folderName() == other.folderName();
return is_valid == other.is_valid && folderName() == other.folderName();
}
bool World::strongCompare(const World &other) const
{
return is_valid == other.is_valid && folderName() == other.folderName();
return is_valid == other.is_valid && folderName() == other.folderName();
}

View File

@ -22,62 +22,62 @@
class MULTIMC_LOGIC_EXPORT World
{
public:
World(const QFileInfo &file);
QString folderName() const
{
return m_folderName;
}
QString name() const
{
return m_actualName;
}
QDateTime lastPlayed() const
{
return m_lastPlayed;
}
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);
// change the world's filesystem path (used by world lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
World(const QFileInfo &file);
QString folderName() const
{
return m_folderName;
}
QString name() const
{
return m_actualName;
}
QDateTime lastPlayed() const
{
return m_lastPlayed;
}
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);
// change the world's filesystem path (used by world lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
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 strongCompare(const World &other) const;
// WEAK compare operator - used for replacing worlds
bool operator==(const World &other) const;
bool strongCompare(const World &other) const;
private:
void readFromZip(const QFileInfo &file);
void readFromFS(const QFileInfo &file);
void loadFromLevelDat(QByteArray data);
void readFromZip(const QFileInfo &file);
void readFromFS(const QFileInfo &file);
void loadFromLevelDat(QByteArray data);
protected:
QFileInfo m_containerFile;
QString m_containerOffsetPath;
QString m_folderName;
QString m_actualName;
QDateTime levelDatTime;
QDateTime m_lastPlayed;
int64_t m_randomSeed = 0;
bool is_valid = false;
QFileInfo m_containerFile;
QString m_containerOffsetPath;
QString m_folderName;
QString m_actualName;
QDateTime levelDatTime;
QDateTime m_lastPlayed;
int64_t m_randomSeed = 0;
bool is_valid = false;
};

View File

@ -23,216 +23,216 @@
#include <QDebug>
WorldList::WorldList(const QString &dir)
: QAbstractListModel(), m_dir(dir)
: QAbstractListModel(), m_dir(dir)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
is_watching = false;
connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
SLOT(directoryChanged(QString)));
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_watcher = new QFileSystemWatcher(this);
is_watching = false;
connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
SLOT(directoryChanged(QString)));
}
void WorldList::startWatching()
{
if(is_watching)
{
return;
}
update();
is_watching = m_watcher->addPath(m_dir.absolutePath());
if (is_watching)
{
qDebug() << "Started watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to start watching " << m_dir.absolutePath();
}
if(is_watching)
{
return;
}
update();
is_watching = m_watcher->addPath(m_dir.absolutePath());
if (is_watching)
{
qDebug() << "Started watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to start watching " << m_dir.absolutePath();
}
}
void WorldList::stopWatching()
{
if(!is_watching)
{
return;
}
is_watching = !m_watcher->removePath(m_dir.absolutePath());
if (!is_watching)
{
qDebug() << "Stopped watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
}
if(!is_watching)
{
return;
}
is_watching = !m_watcher->removePath(m_dir.absolutePath());
if (!is_watching)
{
qDebug() << "Stopped watching " << m_dir.absolutePath();
}
else
{
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
}
}
bool WorldList::update()
{
if (!isValid())
return false;
if (!isValid())
return false;
QList<World> newWorlds;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
// if there are any untracked files...
for (QFileInfo entry : folderContents)
{
if(!entry.isDir())
continue;
QList<World> newWorlds;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
// if there are any untracked files...
for (QFileInfo entry : folderContents)
{
if(!entry.isDir())
continue;
World w(entry);
if(w.isValid())
{
newWorlds.append(w);
}
}
beginResetModel();
worlds.swap(newWorlds);
endResetModel();
return true;
World w(entry);
if(w.isValid())
{
newWorlds.append(w);
}
}
beginResetModel();
worlds.swap(newWorlds);
endResetModel();
return true;
}
void WorldList::directoryChanged(QString path)
{
update();
update();
}
bool WorldList::isValid()
{
return m_dir.exists() && m_dir.isReadable();
return m_dir.exists() && m_dir.isReadable();
}
bool WorldList::deleteWorld(int index)
{
if (index >= worlds.size() || index < 0)
return false;
World &m = worlds[index];
if (m.destroy())
{
beginRemoveRows(QModelIndex(), index, index);
worlds.removeAt(index);
endRemoveRows();
emit changed();
return true;
}
return false;
if (index >= worlds.size() || index < 0)
return false;
World &m = worlds[index];
if (m.destroy())
{
beginRemoveRows(QModelIndex(), index, index);
worlds.removeAt(index);
endRemoveRows();
emit changed();
return true;
}
return false;
}
bool WorldList::deleteWorlds(int first, int last)
{
for (int i = first; i <= last; i++)
{
World &m = worlds[i];
m.destroy();
}
beginRemoveRows(QModelIndex(), first, last);
worlds.erase(worlds.begin() + first, worlds.begin() + last + 1);
endRemoveRows();
emit changed();
return true;
for (int i = first; i <= last; i++)
{
World &m = worlds[i];
m.destroy();
}
beginRemoveRows(QModelIndex(), first, last);
worlds.erase(worlds.begin() + first, worlds.begin() + last + 1);
endRemoveRows();
emit changed();
return true;
}
int WorldList::columnCount(const QModelIndex &parent) const
{
return 2;
return 2;
}
QVariant WorldList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
int row = index.row();
int column = index.column();
if (row < 0 || row >= worlds.size())
return QVariant();
if (row < 0 || row >= worlds.size())
return QVariant();
auto & world = worlds[row];
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
return world.name();
auto & world = worlds[row];
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
return world.name();
case LastPlayedColumn:
return world.lastPlayed();
case LastPlayedColumn:
return world.lastPlayed();
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
{
return world.folderName();
}
case ObjectRole:
{
return QVariant::fromValue<void *>((void *)&world);
}
case FolderRole:
{
return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName()));
}
case SeedRole:
{
return qVariantFromValue<qlonglong>(world.seed());
}
case NameRole:
{
return world.name();
}
case LastPlayedRole:
{
return world.lastPlayed();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
{
return world.folderName();
}
case ObjectRole:
{
return QVariant::fromValue<void *>((void *)&world);
}
case FolderRole:
{
return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName()));
}
case SeedRole:
{
return qVariantFromValue<qlonglong>(world.seed());
}
case NameRole:
{
return world.name();
}
case LastPlayedRole:
{
return world.lastPlayed();
}
default:
return QVariant();
}
}
QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case NameColumn:
return tr("Name");
case LastPlayedColumn:
return tr("Last Played");
default:
return QVariant();
}
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case NameColumn:
return tr("Name");
case LastPlayedColumn:
return tr("Last Played");
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return tr("The name of the world.");
case LastPlayedColumn:
return tr("Date and time the world was last played.");
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return tr("The name of the world.");
case LastPlayedColumn:
return tr("Date and time the world was last played.");
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
}
QStringList WorldList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
QStringList types;
types << "text/uri-list";
return types;
}
class WorldMimeData : public QMimeData
@ -240,124 +240,124 @@ class WorldMimeData : public QMimeData
Q_OBJECT
public:
WorldMimeData(QList<World> worlds)
{
m_worlds = worlds;
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:
QVariant retrieveData(const QString &mimetype, QVariant::Type type) const
{
QList<QUrl> urls;
for(auto &world: m_worlds)
{
if(!world.isValid() || !world.isOnFS())
continue;
QString worldPath = world.container().absoluteFilePath();
qDebug() << worldPath;
urls.append(QUrl::fromLocalFile(worldPath));
}
const_cast<WorldMimeData*>(this)->setUrls(urls);
return QMimeData::retrieveData(mimetype, type);
}
QVariant retrieveData(const QString &mimetype, QVariant::Type type) const
{
QList<QUrl> urls;
for(auto &world: m_worlds)
{
if(!world.isValid() || !world.isOnFS())
continue;
QString worldPath = world.container().absoluteFilePath();
qDebug() << worldPath;
urls.append(QUrl::fromLocalFile(worldPath));
}
const_cast<WorldMimeData*>(this)->setUrls(urls);
return QMimeData::retrieveData(mimetype, type);
}
private:
QList<World> m_worlds;
QList<World> m_worlds;
};
QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const
{
if (indexes.size() == 0)
return new QMimeData();
if (indexes.size() == 0)
return new QMimeData();
QList<World> worlds;
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())
{
return new QMimeData();
}
return new WorldMimeData(worlds);
QList<World> worlds;
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())
{
return new QMimeData();
}
return new WorldMimeData(worlds);
}
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;
else
return Qt::ItemIsDropEnabled | defaultFlags;
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
Qt::DropActions WorldList::supportedDragActions() const
{
// move to other mod lists or VOID
return Qt::MoveAction;
// move to other mod lists or VOID
return Qt::MoveAction;
}
Qt::DropActions WorldList::supportedDropActions() const
{
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
}
void WorldList::installWorld(QFileInfo filename)
{
qDebug() << "installing: " << filename.absoluteFilePath();
World w(filename);
if(!w.isValid())
{
return;
}
w.install(m_dir.absolutePath());
qDebug() << "installing: " << filename.absoluteFilePath();
World w(filename);
if(!w.isValid())
{
return;
}
w.install(m_dir.absolutePath());
}
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
// files dropped from outside?
if (data->hasUrls())
{
bool was_watching = is_watching;
if (was_watching)
stopWatching();
auto urls = data->urls();
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
continue;
QString filename = url.toLocalFile();
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
// files dropped from outside?
if (data->hasUrls())
{
bool was_watching = is_watching;
if (was_watching)
stopWatching();
auto urls = data->urls();
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
continue;
QString filename = url.toLocalFile();
QFileInfo worldInfo(filename);
QFileInfo worldInfo(filename);
if(!m_dir.entryInfoList().contains(worldInfo))
{
installWorld(worldInfo);
}
}
if (was_watching)
startWatching();
return true;
}
return false;
if(!m_dir.entryInfoList().contains(worldInfo))
{
installWorld(worldInfo);
}
}
if (was_watching)
startWatching();
return true;
}
return false;
}
#include "WorldList.moc"

View File

@ -28,98 +28,98 @@ class QFileSystemWatcher;
class MULTIMC_LOGIC_EXPORT WorldList : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum Columns
{
NameColumn,
LastPlayedColumn
};
enum Columns
{
NameColumn,
LastPlayedColumn
};
enum Roles
{
ObjectRole = Qt::UserRole + 1,
FolderRole,
SeedRole,
NameRole,
LastPlayedRole
};
enum Roles
{
ObjectRole = Qt::UserRole + 1,
FolderRole,
SeedRole,
NameRole,
LastPlayedRole
};
WorldList(const QString &dir);
WorldList(const QString &dir);
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 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 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();
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
/// Install a world from location
void installWorld(QFileInfo filename);
/// Install a world from location
void installWorld(QFileInfo filename);
/// Deletes the mod at the given index.
virtual bool deleteWorld(int index);
/// Deletes the mod at the given index.
virtual bool deleteWorld(int index);
/// Deletes all the selected mods
virtual bool deleteWorlds(int first, int last);
/// Deletes all the selected mods
virtual bool deleteWorlds(int first, int last);
/// flags, mostly to support drag&drop
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
/// get data for drag action
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);
/// what drag actions do we support?
virtual Qt::DropActions supportedDragActions() const;
/// flags, mostly to support drag&drop
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
/// get data for drag action
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);
/// what drag actions do we support?
virtual Qt::DropActions supportedDragActions() const;
/// what drop actions do we support?
virtual Qt::DropActions supportedDropActions() const;
/// what drop actions do we support?
virtual Qt::DropActions supportedDropActions() const;
void startWatching();
void stopWatching();
void startWatching();
void stopWatching();
virtual bool isValid();
virtual bool isValid();
QDir dir() const
{
return m_dir;
}
QDir dir() const
{
return m_dir;
}
const QList<World> &allWorlds() const
{
return worlds;
}
const QList<World> &allWorlds() const
{
return worlds;
}
private slots:
void directoryChanged(QString path);
void directoryChanged(QString path);
signals:
void changed();
void changed();
protected:
QFileSystemWatcher *m_watcher;
bool is_watching;
QDir m_dir;
QList<World> worlds;
QFileSystemWatcher *m_watcher;
bool is_watching;
QDir m_dir;
QList<World> worlds;
};

View File

@ -6,25 +6,25 @@
QString AuthSession::serializeUserProperties()
{
QJsonObject userAttrs;
for (auto key : u.properties.keys())
{
auto array = QJsonArray::fromStringList(u.properties.values(key));
userAttrs.insert(key, array);
}
QJsonDocument value(userAttrs);
return value.toJson(QJsonDocument::Compact);
QJsonObject userAttrs;
for (auto key : u.properties.keys())
{
auto array = QJsonArray::fromStringList(u.properties.values(key));
userAttrs.insert(key, array);
}
QJsonDocument value(userAttrs);
return value.toJson(QJsonDocument::Compact);
}
bool AuthSession::MakeOffline(QString offline_playername)
{
if (status != PlayableOffline && status != PlayableOnline)
{
return false;
}
session = "-";
player_name = offline_playername;
status = PlayableOffline;
return true;
if (status != PlayableOffline && status != PlayableOnline)
{
return false;
}
session = "-";
player_name = offline_playername;
status = PlayableOffline;
return true;
}

View File

@ -10,45 +10,45 @@ class MojangAccount;
struct User
{
QString id;
QMultiMap<QString, QString> properties;
QString id;
QMultiMap<QString, QString> properties;
};
struct MULTIMC_LOGIC_EXPORT AuthSession
{
bool MakeOffline(QString offline_playername);
bool MakeOffline(QString offline_playername);
QString serializeUserProperties();
QString serializeUserProperties();
enum Status
{
Undetermined,
RequiresPassword,
PlayableOffline,
PlayableOnline
} status = Undetermined;
enum Status
{
Undetermined,
RequiresPassword,
PlayableOffline,
PlayableOnline
} status = Undetermined;
User u;
User u;
// client token
QString client_token;
// account user name
QString username;
// combined session ID
QString session;
// volatile auth token
QString access_token;
// profile name
QString player_name;
// profile ID
QString uuid;
// 'legacy' or 'mojang', depending on account type
QString user_type;
// Did the auth server reply?
bool auth_server_online = false;
// Did the user request online mode?
bool wants_online = true;
std::shared_ptr<MojangAccount> m_accountPtr;
// client token
QString client_token;
// account user name
QString username;
// combined session ID
QString session;
// volatile auth token
QString access_token;
// profile name
QString player_name;
// profile ID
QString uuid;
// 'legacy' or 'mojang', depending on account type
QString user_type;
// Did the auth server reply?
bool auth_server_online = false;
// Did the user request online mode?
bool wants_online = true;
std::shared_ptr<MojangAccount> m_accountPtr;
};
typedef std::shared_ptr<AuthSession> AuthSessionPtr;

View File

@ -30,286 +30,286 @@
MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
{
// The JSON object must at least have a username for it to be valid.
if (!object.value("username").isString())
{
qCritical() << "Can't load Mojang account info from JSON object. Username field is "
"missing or of the wrong type.";
return nullptr;
}
// The JSON object must at least have a username for it to be valid.
if (!object.value("username").isString())
{
qCritical() << "Can't load Mojang account info from JSON object. Username field is "
"missing or of the wrong type.";
return nullptr;
}
QString username = object.value("username").toString("");
QString clientToken = object.value("clientToken").toString("");
QString accessToken = object.value("accessToken").toString("");
QString username = object.value("username").toString("");
QString clientToken = object.value("clientToken").toString("");
QString accessToken = object.value("accessToken").toString("");
QJsonArray profileArray = object.value("profiles").toArray();
if (profileArray.size() < 1)
{
qCritical() << "Can't load Mojang account with username \"" << username
<< "\". No profiles found.";
return nullptr;
}
QJsonArray profileArray = object.value("profiles").toArray();
if (profileArray.size() < 1)
{
qCritical() << "Can't load Mojang account with username \"" << username
<< "\". No profiles found.";
return nullptr;
}
QList<AccountProfile> profiles;
for (QJsonValue profileVal : profileArray)
{
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())
{
qWarning() << "Unable to load a profile because it was missing an ID or a name.";
continue;
}
profiles.append({id, name, legacy});
}
QList<AccountProfile> profiles;
for (QJsonValue profileVal : profileArray)
{
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())
{
qWarning() << "Unable to load a profile because it was missing an ID or a name.";
continue;
}
profiles.append({id, name, legacy});
}
MojangAccountPtr account(new MojangAccount());
if (object.value("user").isObject())
{
User u;
QJsonObject userStructure = object.value("user").toObject();
u.id = userStructure.value("id").toString();
/*
QJsonObject propMap = userStructure.value("properties").toObject();
for(auto key: propMap.keys())
{
auto values = propMap.operator[](key).toArray();
for(auto value: values)
u.properties.insert(key, value.toString());
}
*/
account->m_user = u;
}
account->m_username = username;
account->m_clientToken = clientToken;
account->m_accessToken = accessToken;
account->m_profiles = profiles;
MojangAccountPtr account(new MojangAccount());
if (object.value("user").isObject())
{
User u;
QJsonObject userStructure = object.value("user").toObject();
u.id = userStructure.value("id").toString();
/*
QJsonObject propMap = userStructure.value("properties").toObject();
for(auto key: propMap.keys())
{
auto values = propMap.operator[](key).toArray();
for(auto value: values)
u.properties.insert(key, value.toString());
}
*/
account->m_user = u;
}
account->m_username = username;
account->m_clientToken = clientToken;
account->m_accessToken = accessToken;
account->m_profiles = profiles;
// Get the currently selected profile.
QString currentProfile = object.value("activeProfile").toString("");
if (!currentProfile.isEmpty())
account->setCurrentProfile(currentProfile);
// Get the currently selected profile.
QString currentProfile = object.value("activeProfile").toString("");
if (!currentProfile.isEmpty())
account->setCurrentProfile(currentProfile);
return account;
return account;
}
MojangAccountPtr MojangAccount::createFromUsername(const QString &username)
{
MojangAccountPtr account(new MojangAccount());
account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
account->m_username = username;
return account;
MojangAccountPtr account(new MojangAccount());
account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
account->m_username = username;
return account;
}
QJsonObject MojangAccount::saveToJson() const
{
QJsonObject json;
json.insert("username", m_username);
json.insert("clientToken", m_clientToken);
json.insert("accessToken", m_accessToken);
QJsonObject json;
json.insert("username", m_username);
json.insert("clientToken", m_clientToken);
json.insert("accessToken", m_accessToken);
QJsonArray profileArray;
for (AccountProfile profile : m_profiles)
{
QJsonObject profileObj;
profileObj.insert("id", profile.id);
profileObj.insert("name", profile.name);
profileObj.insert("legacy", profile.legacy);
profileArray.append(profileObj);
}
json.insert("profiles", profileArray);
QJsonArray profileArray;
for (AccountProfile profile : m_profiles)
{
QJsonObject profileObj;
profileObj.insert("id", profile.id);
profileObj.insert("name", profile.name);
profileObj.insert("legacy", profile.legacy);
profileArray.append(profileObj);
}
json.insert("profiles", profileArray);
QJsonObject userStructure;
{
userStructure.insert("id", m_user.id);
/*
QJsonObject userAttrs;
for(auto key: m_user.properties.keys())
{
auto array = QJsonArray::fromStringList(m_user.properties.values(key));
userAttrs.insert(key, array);
}
userStructure.insert("properties", userAttrs);
*/
}
json.insert("user", userStructure);
QJsonObject userStructure;
{
userStructure.insert("id", m_user.id);
/*
QJsonObject userAttrs;
for(auto key: m_user.properties.keys())
{
auto array = QJsonArray::fromStringList(m_user.properties.values(key));
userAttrs.insert(key, array);
}
userStructure.insert("properties", userAttrs);
*/
}
json.insert("user", userStructure);
if (m_currentProfile != -1)
json.insert("activeProfile", currentProfile()->id);
if (m_currentProfile != -1)
json.insert("activeProfile", currentProfile()->id);
return json;
return json;
}
bool MojangAccount::setCurrentProfile(const QString &profileId)
{
for (int i = 0; i < m_profiles.length(); i++)
{
if (m_profiles[i].id == profileId)
{
m_currentProfile = i;
return true;
}
}
return false;
for (int i = 0; i < m_profiles.length(); i++)
{
if (m_profiles[i].id == profileId)
{
m_currentProfile = i;
return true;
}
}
return false;
}
const AccountProfile *MojangAccount::currentProfile() const
{
if (m_currentProfile == -1)
return nullptr;
return &m_profiles[m_currentProfile];
if (m_currentProfile == -1)
return nullptr;
return &m_profiles[m_currentProfile];
}
AccountStatus MojangAccount::accountStatus() const
{
if (m_accessToken.isEmpty())
return NotVerified;
else
return Verified;
if (m_accessToken.isEmpty())
return NotVerified;
else
return Verified;
}
std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password)
{
Q_ASSERT(m_currentTask.get() == nullptr);
Q_ASSERT(m_currentTask.get() == nullptr);
// take care of the true offline status
if (accountStatus() == NotVerified && password.isEmpty())
{
if (session)
{
session->status = AuthSession::RequiresPassword;
fillSession(session);
}
return nullptr;
}
// take care of the true offline status
if (accountStatus() == NotVerified && password.isEmpty())
{
if (session)
{
session->status = AuthSession::RequiresPassword;
fillSession(session);
}
return nullptr;
}
if(accountStatus() == Verified && !session->wants_online)
{
session->status = AuthSession::PlayableOffline;
session->auth_server_online = false;
fillSession(session);
return nullptr;
}
else
{
if (password.isEmpty())
{
m_currentTask.reset(new RefreshTask(this));
}
else
{
m_currentTask.reset(new AuthenticateTask(this, password));
}
m_currentTask->assignSession(session);
if(accountStatus() == Verified && !session->wants_online)
{
session->status = AuthSession::PlayableOffline;
session->auth_server_online = false;
fillSession(session);
return nullptr;
}
else
{
if (password.isEmpty())
{
m_currentTask.reset(new RefreshTask(this));
}
else
{
m_currentTask.reset(new AuthenticateTask(this, password));
}
m_currentTask->assignSession(session);
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
}
return m_currentTask;
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
}
return m_currentTask;
}
void MojangAccount::authSucceeded()
{
auto session = m_currentTask->getAssignedSession();
if (session)
{
session->status =
session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline;
fillSession(session);
session->auth_server_online = true;
}
m_currentTask.reset();
emit changed();
auto session = m_currentTask->getAssignedSession();
if (session)
{
session->status =
session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline;
fillSession(session);
session->auth_server_online = true;
}
m_currentTask.reset();
emit changed();
}
void MojangAccount::authFailed(QString reason)
{
auto session = m_currentTask->getAssignedSession();
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT)
{
if (session)
{
session->status = accountStatus() == Verified ? AuthSession::PlayableOffline
: AuthSession::RequiresPassword;
session->auth_server_online = false;
fillSession(session);
}
}
else
{
m_accessToken = QString();
emit changed();
if (session)
{
session->status = AuthSession::RequiresPassword;
session->auth_server_online = true;
fillSession(session);
}
}
m_currentTask.reset();
auto session = m_currentTask->getAssignedSession();
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT)
{
if (session)
{
session->status = accountStatus() == Verified ? AuthSession::PlayableOffline
: AuthSession::RequiresPassword;
session->auth_server_online = false;
fillSession(session);
}
}
else
{
m_accessToken = QString();
emit changed();
if (session)
{
session->status = AuthSession::RequiresPassword;
session->auth_server_online = true;
fillSession(session);
}
}
m_currentTask.reset();
}
void MojangAccount::fillSession(AuthSessionPtr session)
{
// the user name. you have to have an user name
session->username = m_username;
// volatile auth token
session->access_token = m_accessToken;
// the semi-permanent client token
session->client_token = m_clientToken;
if (currentProfile())
{
// profile name
session->player_name = currentProfile()->name;
// profile ID
session->uuid = currentProfile()->id;
// 'legacy' or 'mojang', depending on account type
session->user_type = currentProfile()->legacy ? "legacy" : "mojang";
if (!session->access_token.isEmpty())
{
session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
}
else
{
session->session = "-";
}
}
else
{
session->player_name = "Player";
session->session = "-";
}
session->u = user();
session->m_accountPtr = shared_from_this();
// the user name. you have to have an user name
session->username = m_username;
// volatile auth token
session->access_token = m_accessToken;
// the semi-permanent client token
session->client_token = m_clientToken;
if (currentProfile())
{
// profile name
session->player_name = currentProfile()->name;
// profile ID
session->uuid = currentProfile()->id;
// 'legacy' or 'mojang', depending on account type
session->user_type = currentProfile()->legacy ? "legacy" : "mojang";
if (!session->access_token.isEmpty())
{
session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id;
}
else
{
session->session = "-";
}
}
else
{
session->player_name = "Player";
session->session = "-";
}
session->u = user();
session->m_accountPtr = shared_from_this();
}
void MojangAccount::decrementUses()
{
Usable::decrementUses();
if(!isInUse())
{
emit changed();
qWarning() << "Account" << m_username << "is no longer in use.";
}
Usable::decrementUses();
if(!isInUse())
{
emit changed();
qWarning() << "Account" << m_username << "is no longer in use.";
}
}
void MojangAccount::incrementUses()
{
bool wasInUse = isInUse();
Usable::incrementUses();
if(!wasInUse)
{
emit changed();
qWarning() << "Account" << m_username << "is now in use.";
}
bool wasInUse = isInUse();
Usable::incrementUses();
if(!wasInUse)
{
emit changed();
qWarning() << "Account" << m_username << "is now in use.";
}
}
void MojangAccount::invalidateClientToken()
{
m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
emit changed();
m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
emit changed();
}

View File

@ -44,15 +44,15 @@ Q_DECLARE_METATYPE(MojangAccountPtr)
*/
struct AccountProfile
{
QString id;
QString name;
bool legacy;
QString id;
QString name;
bool legacy;
};
enum AccountStatus
{
NotVerified,
Verified
NotVerified,
Verified
};
/**
@ -62,121 +62,121 @@ enum AccountStatus
* token if the user chose to stay logged in.
*/
class MULTIMC_LOGIC_EXPORT MojangAccount :
public QObject,
public Usable,
public std::enable_shared_from_this<MojangAccount>
public QObject,
public Usable,
public std::enable_shared_from_this<MojangAccount>
{
Q_OBJECT
Q_OBJECT
public: /* construction */
//! Do not copy accounts. ever.
explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete;
//! Do not copy accounts. ever.
explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete;
//! Default constructor
explicit MojangAccount(QObject *parent = 0) : QObject(parent) {};
//! Default constructor
explicit MojangAccount(QObject *parent = 0) : QObject(parent) {};
//! Creates an empty account for the specified user name.
static MojangAccountPtr createFromUsername(const QString &username);
//! Creates an empty account for the specified user name.
static MojangAccountPtr createFromUsername(const QString &username);
//! Loads a MojangAccount from the given JSON object.
static MojangAccountPtr loadFromJson(const QJsonObject &json);
//! Loads a MojangAccount from the given JSON object.
static MojangAccountPtr loadFromJson(const QJsonObject &json);
//! Saves a MojangAccount to a JSON object and returns it.
QJsonObject saveToJson() const;
//! Saves a MojangAccount to a JSON object and returns it.
QJsonObject saveToJson() const;
public: /* manipulation */
/**
* Sets the currently selected profile to the profile with the given ID string.
* If profileId is not in the list of available profiles, the function will simply return
* false.
*/
bool setCurrentProfile(const QString &profileId);
/**
* Sets the currently selected profile to the profile with the given ID string.
* If profileId is not in the list of available profiles, the function will simply return
* false.
*/
bool setCurrentProfile(const QString &profileId);
/**
* Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
*/
std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, QString password = QString());
void invalidateClientToken();
/**
* Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
*/
std::shared_ptr<YggdrasilTask> login(AuthSessionPtr session, QString password = QString());
void invalidateClientToken();
public: /* queries */
const QString &username() const
{
return m_username;
}
const QString &username() const
{
return m_username;
}
const QString &clientToken() const
{
return m_clientToken;
}
const QString &clientToken() const
{
return m_clientToken;
}
const QString &accessToken() const
{
return m_accessToken;
}
const QString &accessToken() const
{
return m_accessToken;
}
const QList<AccountProfile> &profiles() const
{
return m_profiles;
}
const QList<AccountProfile> &profiles() const
{
return m_profiles;
}
const User &user()
{
return m_user;
}
const User &user()
{
return m_user;
}
//! Returns the currently selected profile (if none, returns nullptr)
const AccountProfile *currentProfile() const;
//! Returns the currently selected profile (if none, returns nullptr)
const AccountProfile *currentProfile() const;
//! Returns whether the account is NotVerified, Verified or Online
AccountStatus accountStatus() const;
//! Returns whether the account is NotVerified, Verified or Online
AccountStatus accountStatus() const;
signals:
/**
* This signal is emitted when the account changes
*/
void changed();
/**
* This signal is emitted when the account changes
*/
void changed();
// TODO: better signalling for the various possible state changes - especially errors
// TODO: better signalling for the various possible state changes - especially errors
protected: /* variables */
QString m_username;
QString m_username;
// Used to identify the client - the user can have multiple clients for the same account
// Think: different launchers, all connecting to the same account/profile
QString m_clientToken;
// Used to identify the client - the user can have multiple clients for the same account
// Think: different launchers, all connecting to the same account/profile
QString m_clientToken;
// Blank if not logged in.
QString m_accessToken;
// Blank if not logged in.
QString m_accessToken;
// Index of the selected profile within the list of available
// profiles. -1 if nothing is selected.
int m_currentProfile = -1;
// Index of the selected profile within the list of available
// profiles. -1 if nothing is selected.
int m_currentProfile = -1;
// List of available profiles.
QList<AccountProfile> m_profiles;
// List of available profiles.
QList<AccountProfile> m_profiles;
// the user structure, whatever it is.
User m_user;
// the user structure, whatever it is.
User m_user;
// current task we are executing here
std::shared_ptr<YggdrasilTask> m_currentTask;
// current task we are executing here
std::shared_ptr<YggdrasilTask> m_currentTask;
protected: /* methods */
void incrementUses() override;
void decrementUses() override;
void incrementUses() override;
void decrementUses() override;
private
slots:
void authSucceeded();
void authFailed(QString reason);
void authSucceeded();
void authFailed(QString reason);
private:
void fillSession(AuthSessionPtr session);
void fillSession(AuthSessionPtr session);
public:
friend class YggdrasilTask;
friend class AuthenticateTask;
friend class ValidateTask;
friend class RefreshTask;
friend class YggdrasilTask;
friend class AuthenticateTask;
friend class ValidateTask;
friend class RefreshTask;
};

View File

@ -37,432 +37,432 @@ MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(paren
MojangAccountPtr MojangAccountList::findAccount(const QString &username) const
{
for (int i = 0; i < count(); i++)
{
MojangAccountPtr account = at(i);
if (account->username() == username)
return account;
}
return nullptr;
for (int i = 0; i < count(); i++)
{
MojangAccountPtr account = at(i);
if (account->username() == username)
return account;
}
return nullptr;
}
const MojangAccountPtr MojangAccountList::at(int i) const
{
return MojangAccountPtr(m_accounts.at(i));
return MojangAccountPtr(m_accounts.at(i));
}
void MojangAccountList::addAccount(const MojangAccountPtr account)
{
int row = m_accounts.count();
beginInsertRows(QModelIndex(), row, row);
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
endInsertRows();
onListChanged();
int row = m_accounts.count();
beginInsertRows(QModelIndex(), row, row);
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
endInsertRows();
onListChanged();
}
void MojangAccountList::removeAccount(const QString &username)
{
int idx = 0;
for (auto account : m_accounts)
{
if (account->username() == username)
{
beginRemoveRows(QModelIndex(), idx, idx);
m_accounts.removeOne(account);
endRemoveRows();
return;
}
idx++;
}
onListChanged();
int idx = 0;
for (auto account : m_accounts)
{
if (account->username() == username)
{
beginRemoveRows(QModelIndex(), idx, idx);
m_accounts.removeOne(account);
endRemoveRows();
return;
}
idx++;
}
onListChanged();
}
void MojangAccountList::removeAccount(QModelIndex index)
{
int row = index.row();
if(index.isValid() && row >= 0 && row < m_accounts.size())
{
auto & account = m_accounts[row];
if(account == m_activeAccount)
{
m_activeAccount = nullptr;
onActiveChanged();
}
beginRemoveRows(QModelIndex(), row, row);
m_accounts.removeAt(index.row());
endRemoveRows();
onListChanged();
}
int row = index.row();
if(index.isValid() && row >= 0 && row < m_accounts.size())
{
auto & account = m_accounts[row];
if(account == m_activeAccount)
{
m_activeAccount = nullptr;
onActiveChanged();
}
beginRemoveRows(QModelIndex(), row, row);
m_accounts.removeAt(index.row());
endRemoveRows();
onListChanged();
}
}
MojangAccountPtr MojangAccountList::activeAccount() const
{
return m_activeAccount;
return m_activeAccount;
}
void MojangAccountList::setActiveAccount(const QString &username)
{
if (username.isEmpty() && m_activeAccount)
{
int idx = 0;
auto prevActiveAcc = m_activeAccount;
m_activeAccount = nullptr;
for (MojangAccountPtr account : m_accounts)
{
if (account == prevActiveAcc)
{
emit dataChanged(index(idx), index(idx));
}
idx ++;
}
onActiveChanged();
}
else
{
auto currentActiveAccount = m_activeAccount;
int currentActiveAccountIdx = -1;
auto newActiveAccount = m_activeAccount;
int newActiveAccountIdx = -1;
int idx = 0;
for (MojangAccountPtr account : m_accounts)
{
if (account->username() == username)
{
newActiveAccount = account;
newActiveAccountIdx = idx;
}
if(currentActiveAccount == account)
{
currentActiveAccountIdx = idx;
}
idx++;
}
if(currentActiveAccount != newActiveAccount)
{
emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx));
emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx));
m_activeAccount = newActiveAccount;
onActiveChanged();
}
}
if (username.isEmpty() && m_activeAccount)
{
int idx = 0;
auto prevActiveAcc = m_activeAccount;
m_activeAccount = nullptr;
for (MojangAccountPtr account : m_accounts)
{
if (account == prevActiveAcc)
{
emit dataChanged(index(idx), index(idx));
}
idx ++;
}
onActiveChanged();
}
else
{
auto currentActiveAccount = m_activeAccount;
int currentActiveAccountIdx = -1;
auto newActiveAccount = m_activeAccount;
int newActiveAccountIdx = -1;
int idx = 0;
for (MojangAccountPtr account : m_accounts)
{
if (account->username() == username)
{
newActiveAccount = account;
newActiveAccountIdx = idx;
}
if(currentActiveAccount == account)
{
currentActiveAccountIdx = idx;
}
idx++;
}
if(currentActiveAccount != newActiveAccount)
{
emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx));
emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx));
m_activeAccount = newActiveAccount;
onActiveChanged();
}
}
}
void MojangAccountList::accountChanged()
{
// the list changed. there is no doubt.
onListChanged();
// the list changed. there is no doubt.
onListChanged();
}
void MojangAccountList::onListChanged()
{
if (m_autosave)
// TODO: Alert the user if this fails.
saveList();
if (m_autosave)
// TODO: Alert the user if this fails.
saveList();
emit listChanged();
emit listChanged();
}
void MojangAccountList::onActiveChanged()
{
if (m_autosave)
saveList();
if (m_autosave)
saveList();
emit activeAccountChanged();
emit activeAccountChanged();
}
int MojangAccountList::count() const
{
return m_accounts.count();
return m_accounts.count();
}
QVariant MojangAccountList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
if (index.row() > count())
return QVariant();
MojangAccountPtr account = at(index.row());
MojangAccountPtr account = at(index.row());
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NameColumn:
return account->username();
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NameColumn:
return account->username();
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
return account->username();
case Qt::ToolTipRole:
return account->username();
case PointerRole:
return qVariantFromValue(account);
case PointerRole:
return qVariantFromValue(account);
case Qt::CheckStateRole:
switch (index.column())
{
case ActiveColumn:
return account == m_activeAccount ? Qt::Checked : Qt::Unchecked;
}
case Qt::CheckStateRole:
switch (index.column())
{
case ActiveColumn:
return account == m_activeAccount ? Qt::Checked : Qt::Unchecked;
}
default:
return QVariant();
}
default:
return QVariant();
}
}
QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return tr("Active?");
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case ActiveColumn:
return tr("Active?");
case NameColumn:
return tr("Name");
case NameColumn:
return tr("Name");
default:
return QVariant();
}
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return tr("The name of the version.");
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return tr("The name of the version.");
default:
return QVariant();
}
default:
return QVariant();
}
default:
return QVariant();
}
default:
return QVariant();
}
}
int MojangAccountList::rowCount(const QModelIndex &) const
{
// Return count
return count();
// Return count
return count();
}
int MojangAccountList::columnCount(const QModelIndex &) const
{
return 2;
return 2;
}
Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return Qt::NoItemFlags;
}
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return Qt::NoItemFlags;
}
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
return false;
}
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
{
MojangAccountPtr account = this->at(index.row());
this->setActiveAccount(account->username());
}
}
if(role == Qt::CheckStateRole)
{
if(value == Qt::Checked)
{
MojangAccountPtr account = this->at(index.row());
this->setActiveAccount(account->username());
}
}
emit dataChanged(index, index);
return true;
emit dataChanged(index, index);
return true;
}
void MojangAccountList::updateListData(QList<MojangAccountPtr> versions)
{
beginResetModel();
m_accounts = versions;
endResetModel();
beginResetModel();
m_accounts = versions;
endResetModel();
}
bool MojangAccountList::loadList(const QString &filePath)
{
QString path = filePath;
if (path.isEmpty())
path = m_listFilePath;
if (path.isEmpty())
{
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
return false;
}
QString path = filePath;
if (path.isEmpty())
path = m_listFilePath;
if (path.isEmpty())
{
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
return false;
}
QFile file(path);
QFile file(path);
// 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))
{
qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
return false;
}
// 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))
{
qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
return false;
}
// Read the file and close it.
QByteArray jsonData = file.readAll();
file.close();
// Read the file and close it.
QByteArray jsonData = file.readAll();
file.close();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
// Fail if the JSON is invalid.
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();
return false;
}
// Fail if the JSON is invalid.
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();
return false;
}
// Make sure the root is an object.
if (!jsonDoc.isObject())
{
qCritical() << "Invalid account list JSON: Root should be an array.";
return false;
}
// Make sure the root is an object.
if (!jsonDoc.isObject())
{
qCritical() << "Invalid account list JSON: Root should be an array.";
return false;
}
QJsonObject root = jsonDoc.object();
QJsonObject root = jsonDoc.object();
// Make sure the format version matches.
if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION)
{
QString newName = "accounts-old.json";
qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to"
<< newName;
// Make sure the format version matches.
if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION)
{
QString newName = "accounts-old.json";
qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to"
<< newName;
// Attempt to rename the old version.
file.rename(newName);
return false;
}
// Attempt to rename the old version.
file.rename(newName);
return false;
}
// Now, load the accounts array.
beginResetModel();
QJsonArray accounts = root.value("accounts").toArray();
for (QJsonValue accountVal : accounts)
{
QJsonObject accountObj = accountVal.toObject();
MojangAccountPtr account = MojangAccount::loadFromJson(accountObj);
if (account.get() != nullptr)
{
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
}
else
{
qWarning() << "Failed to load an account.";
}
}
// Load the active account.
m_activeAccount = findAccount(root.value("activeAccount").toString(""));
endResetModel();
return true;
// Now, load the accounts array.
beginResetModel();
QJsonArray accounts = root.value("accounts").toArray();
for (QJsonValue accountVal : accounts)
{
QJsonObject accountObj = accountVal.toObject();
MojangAccountPtr account = MojangAccount::loadFromJson(accountObj);
if (account.get() != nullptr)
{
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
}
else
{
qWarning() << "Failed to load an account.";
}
}
// Load the active account.
m_activeAccount = findAccount(root.value("activeAccount").toString(""));
endResetModel();
return true;
}
bool MojangAccountList::saveList(const QString &filePath)
{
QString path(filePath);
if (path.isEmpty())
path = m_listFilePath;
if (path.isEmpty())
{
qCritical() << "Can't save Mojang account list. No file path given and no default set.";
return false;
}
QString path(filePath);
if (path.isEmpty())
path = m_listFilePath;
if (path.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(path))
return false;
// make sure the parent folder exists
if(!FS::ensureFilePathExists(path))
return false;
// make sure the file wasn't overwritten with a folder before (fixes a bug)
QFileInfo finfo(path);
if(finfo.isDir())
{
QDir badDir(path);
badDir.removeRecursively();
}
// make sure the file wasn't overwritten with a folder before (fixes a bug)
QFileInfo finfo(path);
if(finfo.isDir())
{
QDir badDir(path);
badDir.removeRecursively();
}
qDebug() << "Writing account list to" << path;
qDebug() << "Writing account list to" << path;
qDebug() << "Building JSON data structure.";
// Build the JSON document to write to the list file.
QJsonObject root;
qDebug() << "Building JSON data structure.";
// Build the JSON document to write to the list file.
QJsonObject root;
root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION);
root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION);
// Build a list of accounts.
qDebug() << "Building account array.";
QJsonArray accounts;
for (MojangAccountPtr account : m_accounts)
{
QJsonObject accountObj = account->saveToJson();
accounts.append(accountObj);
}
// Build a list of accounts.
qDebug() << "Building account array.";
QJsonArray accounts;
for (MojangAccountPtr account : m_accounts)
{
QJsonObject accountObj = account->saveToJson();
accounts.append(accountObj);
}
// Insert the account list into the root object.
root.insert("accounts", accounts);
// Insert the account list into the root object.
root.insert("accounts", accounts);
if(m_activeAccount)
{
// Save the active account.
root.insert("activeAccount", m_activeAccount->username());
}
if(m_activeAccount)
{
// Save the active account.
root.insert("activeAccount", m_activeAccount->username());
}
// Create a JSON document object to convert our JSON to bytes.
QJsonDocument doc(root);
// Create a JSON document object to convert our JSON to bytes.
QJsonDocument doc(root);
// Now that we're done building the JSON object, we can write it to the file.
qDebug() << "Writing account list to file.";
QFile file(path);
// Now that we're done building the JSON object, we can write it to the file.
qDebug() << "Writing account list to file.";
QFile file(path);
// 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))
{
qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
return false;
}
// 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))
{
qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
return false;
}
// Write the JSON to the file.
file.write(doc.toJson());
file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
file.close();
// Write the JSON to the file.
file.write(doc.toJson());
file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
file.close();
qDebug() << "Saved account list to" << path;
qDebug() << "Saved account list to" << path;
return true;
return true;
}
void MojangAccountList::setListFilePath(QString path, bool autosave)
{
m_listFilePath = path;
m_autosave = autosave;
m_listFilePath = path;
m_autosave = autosave;
}
bool MojangAccountList::anyAccountIsValid()
{
for(auto account:m_accounts)
{
if(account->accountStatus() != NotVerified)
return true;
}
return false;
for(auto account:m_accounts)
{
if(account->accountStatus() != NotVerified)
return true;
}
return false;
}

View File

@ -35,167 +35,167 @@
*/
class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum ModelRoles
{
PointerRole = 0x34B1CB48
};
enum ModelRoles
{
PointerRole = 0x34B1CB48
};
enum VListColumns
{
// TODO: Add icon column.
enum VListColumns
{
// TODO: Add icon column.
// First column - Active?
ActiveColumn = 0,
// First column - Active?
ActiveColumn = 0,
// Second column - Name
NameColumn,
};
// Second column - Name
NameColumn,
};
explicit MojangAccountList(QObject *parent = 0);
explicit MojangAccountList(QObject *parent = 0);
//! Gets the account at the given index.
virtual const MojangAccountPtr at(int i) const;
//! Gets the account at the given index.
virtual const MojangAccountPtr at(int i) const;
//! Returns the number of accounts in the list.
virtual int count() const;
//! Returns the number of accounts in the list.
virtual int count() const;
//////// List Model Functions ////////
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
//////// List Model Functions ////////
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
/*!
* Adds a the given Mojang account to the account list.
*/
virtual void addAccount(const MojangAccountPtr account);
/*!
* Adds a the given Mojang account to the account list.
*/
virtual void addAccount(const MojangAccountPtr account);
/*!
* Removes the mojang account with the given username from the account list.
*/
virtual void removeAccount(const QString &username);
/*!
* Removes the mojang account with the given username from the account list.
*/
virtual void removeAccount(const QString &username);
/*!
* Removes the account at the given QModelIndex.
*/
virtual void removeAccount(QModelIndex index);
/*!
* Removes the account at the given QModelIndex.
*/
virtual void removeAccount(QModelIndex index);
/*!
* \brief Finds an account by its username.
* \param The username of the account to find.
* \return A const pointer to the account with the given username. NULL if
* one doesn't exist.
*/
virtual MojangAccountPtr findAccount(const QString &username) const;
/*!
* \brief Finds an account by its username.
* \param The username of the account to find.
* \return A const pointer to the account with the given username. NULL if
* one doesn't exist.
*/
virtual MojangAccountPtr findAccount(const QString &username) const;
/*!
* Sets the default path to save the list file to.
* If autosave is true, this list will automatically save to the given path whenever it changes.
* THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately
* after calling this function to ensure an autosaved change doesn't overwrite the list you intended
* to load.
*/
virtual void setListFilePath(QString path, bool autosave = false);
/*!
* Sets the default path to save the list file to.
* If autosave is true, this list will automatically save to the given path whenever it changes.
* THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately
* after calling this function to ensure an autosaved change doesn't overwrite the list you intended
* to load.
*/
virtual void setListFilePath(QString path, bool autosave = false);
/*!
* \brief Loads the account list from the given file path.
* If the given file is an empty string (default), will load from the default account list file.
* \return True if successful, otherwise false.
*/
virtual bool loadList(const QString &file = "");
/*!
* \brief Loads the account list from the given file path.
* If the given file is an empty string (default), will load from the default account list file.
* \return True if successful, otherwise false.
*/
virtual bool loadList(const QString &file = "");
/*!
* \brief Saves the account list to the given file.
* If the given file is an empty string (default), will save from the default account list file.
* \return True if successful, otherwise false.
*/
virtual bool saveList(const QString &file = "");
/*!
* \brief Saves the account list to the given file.
* If the given file is an empty string (default), will save from the default account list file.
* \return True if successful, otherwise false.
*/
virtual bool saveList(const QString &file = "");
/*!
* \brief Gets a pointer to the account that the user has selected as their "active" account.
* Which account is active can be overridden on a per-instance basis, but this will return the one that
* is set as active globally.
* \return The currently active MojangAccount. If there isn't an active account, returns a null pointer.
*/
virtual MojangAccountPtr activeAccount() const;
/*!
* \brief Gets a pointer to the account that the user has selected as their "active" account.
* Which account is active can be overridden on a per-instance basis, but this will return the one that
* is set as active globally.
* \return The currently active MojangAccount. If there isn't an active account, returns a null pointer.
*/
virtual MojangAccountPtr activeAccount() const;
/*!
* Sets the given account as the current active account.
* If the username given is an empty string, sets the active account to nothing.
*/
virtual void setActiveAccount(const QString &username);
/*!
* Sets the given account as the current active account.
* If the username given is an empty string, sets the active account to nothing.
*/
virtual void setActiveAccount(const QString &username);
/*!
* Returns true if any of the account is at least Validated
*/
bool anyAccountIsValid();
/*!
* Returns true if any of the account is at least Validated
*/
bool anyAccountIsValid();
signals:
/*!
* Signal emitted to indicate that the account list has changed.
* This will also fire if the value of an element in the list changes (will be implemented
* later).
*/
void listChanged();
/*!
* Signal emitted to indicate that the account list has changed.
* This will also fire if the value of an element in the list changes (will be implemented
* later).
*/
void listChanged();
/*!
* Signal emitted to indicate that the active account has changed.
*/
void activeAccountChanged();
/*!
* Signal emitted to indicate that the active account has changed.
*/
void activeAccountChanged();
public
slots:
/**
* This is called when one of the accounts changes and the list needs to be updated
*/
void accountChanged();
/**
* This is called when one of the accounts changes and the list needs to be updated
*/
void accountChanged();
protected:
/*!
* Called whenever the list changes.
* This emits the listChanged() signal and autosaves the list (if autosave is enabled).
*/
void onListChanged();
/*!
* Called whenever the list changes.
* This emits the listChanged() signal and autosaves the list (if autosave is enabled).
*/
void onListChanged();
/*!
* Called whenever the active account changes.
* Emits the activeAccountChanged() signal and autosaves the list if enabled.
*/
void onActiveChanged();
/*!
* Called whenever the active account changes.
* Emits the activeAccountChanged() signal and autosaves the list if enabled.
*/
void onActiveChanged();
QList<MojangAccountPtr> m_accounts;
QList<MojangAccountPtr> m_accounts;
/*!
* Account that is currently active.
*/
MojangAccountPtr m_activeAccount;
/*!
* Account that is currently active.
*/
MojangAccountPtr m_activeAccount;
//! Path to the account list file. Empty string if there isn't one.
QString m_listFilePath;
//! Path to the account list file. Empty string if there isn't one.
QString m_listFilePath;
/*!
* If true, the account list will automatically save to the account list path when it changes.
* Ignored if m_listFilePath is blank.
*/
bool m_autosave = false;
/*!
* If true, the account list will automatically save to the account list path when it changes.
* Ignored if m_listFilePath is blank.
*/
bool m_autosave = false;
protected
slots:
/*!
* Updates this list with the given list of accounts.
* This is done by copying each account in the given list and inserting it
* into this one.
* We need to do this so that we can set the parents of the accounts are set to this
* account list. This can't be done in the load task, because the accounts the load
* task creates are on the load task's thread and Qt won't allow their parents
* to be set to something created on another thread.
* To get around that problem, we invoke this method on the GUI thread, which
* then copies the accounts and sets their parents correctly.
* \param accounts List of accounts whose parents should be set.
*/
virtual void updateListData(QList<MojangAccountPtr> versions);
/*!
* Updates this list with the given list of accounts.
* This is done by copying each account in the given list and inserting it
* into this one.
* We need to do this so that we can set the parents of the accounts are set to this
* account list. This can't be done in the load task, because the accounts the load
* task creates are on the load task's thread and Qt won't allow their parents
* to be set to something created on another thread.
* To get around that problem, we invoke this method on the GUI thread, which
* then copies the accounts and sets their parents correctly.
* \param accounts List of accounts whose parents should be set.
*/
virtual void updateListData(QList<MojangAccountPtr> versions);
};

View File

@ -30,226 +30,226 @@
#include <QDebug>
YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
: Task(parent), m_account(account)
: Task(parent), m_account(account)
{
changeState(STATE_CREATED);
changeState(STATE_CREATED);
}
void YggdrasilTask::executeTask()
{
changeState(STATE_SENDING_REQUEST);
changeState(STATE_SENDING_REQUEST);
// Get the content of the request we're going to send to the server.
QJsonDocument doc(getRequestContent());
// Get the content of the request we're going to send to the server.
QJsonDocument doc(getRequestContent());
QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint());
QNetworkRequest netRequest(reqUrl);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint());
QNetworkRequest netRequest(reqUrl);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray requestData = doc.toJson();
m_netReply = ENV.qnam().post(netRequest, requestData);
connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
timeout_keeper.setSingleShot(true);
timeout_keeper.start(timeout_max);
counter.setSingleShot(false);
counter.start(time_step);
progress(0, timeout_max);
connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout);
connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat);
QByteArray requestData = doc.toJson();
m_netReply = ENV.qnam().post(netRequest, requestData);
connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
timeout_keeper.setSingleShot(true);
timeout_keeper.start(timeout_max);
counter.setSingleShot(false);
counter.start(time_step);
progress(0, timeout_max);
connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout);
connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat);
}
void YggdrasilTask::refreshTimers(qint64, qint64)
{
timeout_keeper.stop();
timeout_keeper.start(timeout_max);
progress(count = 0, timeout_max);
timeout_keeper.stop();
timeout_keeper.start(timeout_max);
progress(count = 0, timeout_max);
}
void YggdrasilTask::heartbeat()
{
count += time_step;
progress(count, timeout_max);
count += time_step;
progress(count, timeout_max);
}
bool YggdrasilTask::abort()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_USER;
m_netReply->abort();
return true;
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_USER;
m_netReply->abort();
return true;
}
void YggdrasilTask::abortByTimeout()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_TIMEOUT;
m_netReply->abort();
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_TIMEOUT;
m_netReply->abort();
}
void YggdrasilTask::sslErrors(QList<QSslError> errors)
{
int i = 1;
for (auto error : errors)
{
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
int i = 1;
for (auto error : errors)
{
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
}
void YggdrasilTask::processReply()
{
changeState(STATE_PROCESSING_RESPONSE);
changeState(STATE_PROCESSING_RESPONSE);
switch (m_netReply->error())
{
case QNetworkReply::NoError:
break;
case QNetworkReply::TimeoutError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
return;
case QNetworkReply::OperationCanceledError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
return;
case QNetworkReply::SslHandshakeFailedError:
changeState(
STATE_FAILED_SOFT,
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows XP and need to <a "
"href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
"your root certificates</a></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 MultiMC log file for details</li>"
"</ul>"));
return;
// used for invalid credentials and similar errors. Fall through.
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
break;
default:
changeState(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(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
return;
case QNetworkReply::OperationCanceledError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
return;
case QNetworkReply::SslHandshakeFailedError:
changeState(
STATE_FAILED_SOFT,
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows XP and need to <a "
"href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
"your root certificates</a></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 MultiMC log file for details</li>"
"</ul>"));
return;
// used for invalid credentials and similar errors. Fall through.
case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
break;
default:
changeState(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.
// Sometimes the auth server will give more information and an error code.
QJsonParseError jsonError;
QByteArray replyData = m_netReply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
// Check the response code.
int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Try to parse the response regardless of the response code.
// Sometimes the auth server will give more information and an error code.
QJsonParseError jsonError;
QByteArray replyData = m_netReply->readAll();
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
// Check the response code.
int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (responseCode == 200)
{
// If the response code was 200, then there shouldn't be an error. Make sure
// anyways.
// Also, sometimes an empty reply indicates success. If there was no data received,
// pass an empty json object to the processResponse function.
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
{
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
return;
}
else
{
changeState(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;
}
if (responseCode == 200)
{
// If the response code was 200, then there shouldn't be an error. Make sure
// anyways.
// Also, sometimes an empty reply indicates success. If there was no data received,
// pass an empty json object to the processResponse function.
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
{
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
return;
}
else
{
changeState(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;
}
// If the response code was not 200, then Yggdrasil may have given us information
// about the error.
// If we can parse the response, then get information from it. Otherwise just say
// there was an unknown error.
if (jsonError.error == QJsonParseError::NoError)
{
// We were able to parse the server's response. Woo!
// Call processError. If a subclass has overridden it then they'll handle their
// stuff there.
qDebug() << "The request failed, but the server gave us an error message. "
"Processing error.";
processError(doc.object());
}
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(STATE_FAILED_SOFT,
tr("An unknown error occurred when trying to communicate with the "
"authentication server: %1").arg(m_netReply->errorString()));
}
// If the response code was not 200, then Yggdrasil may have given us information
// about the error.
// If we can parse the response, then get information from it. Otherwise just say
// there was an unknown error.
if (jsonError.error == QJsonParseError::NoError)
{
// We were able to parse the server's response. Woo!
// Call processError. If a subclass has overridden it then they'll handle their
// stuff there.
qDebug() << "The request failed, but the server gave us an error message. "
"Processing error.";
processError(doc.object());
}
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(STATE_FAILED_SOFT,
tr("An unknown error occurred when trying to communicate with the "
"authentication server: %1").arg(m_netReply->errorString()));
}
}
void YggdrasilTask::processError(QJsonObject responseData)
{
QJsonValue errorVal = responseData.value("error");
QJsonValue errorMessageValue = responseData.value("errorMessage");
QJsonValue causeVal = responseData.value("cause");
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("")});
changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
}
else
{
// Error is not in standard format. Don't set m_error and return unknown error.
changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
}
if (errorVal.isString() && errorMessageValue.isString())
{
m_error = std::shared_ptr<Error>(new Error{
errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
}
else
{
// Error is not in standard format. Don't set m_error and return unknown error.
changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
}
}
QString YggdrasilTask::getStateMessage() const
{
switch (m_state)
{
case STATE_CREATED:
return "Waiting...";
case STATE_SENDING_REQUEST:
return tr("Sending request to auth servers...");
case STATE_PROCESSING_RESPONSE:
return tr("Processing response from servers...");
case STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
case STATE_FAILED_SOFT:
return tr("Failed to contact the authentication server.");
case STATE_FAILED_HARD:
return tr("Failed to authenticate.");
default:
return tr("...");
}
switch (m_state)
{
case STATE_CREATED:
return "Waiting...";
case STATE_SENDING_REQUEST:
return tr("Sending request to auth servers...");
case STATE_PROCESSING_RESPONSE:
return tr("Processing response from servers...");
case STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
case STATE_FAILED_SOFT:
return tr("Failed to contact the authentication server.");
case STATE_FAILED_HARD:
return tr("Failed to authenticate.");
default:
return tr("...");
}
}
void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason)
{
m_state = newState;
setStatus(getStateMessage());
if (newState == STATE_SUCCEEDED)
{
emitSucceeded();
}
else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT)
{
emitFailed(reason);
}
m_state = newState;
setStatus(getStateMessage());
if (newState == STATE_SUCCEEDED)
{
emitSucceeded();
}
else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT)
{
emitFailed(reason);
}
}
YggdrasilTask::State YggdrasilTask::state()
{
return m_state;
return m_state;
}

View File

@ -31,121 +31,121 @@ class QNetworkReply;
*/
class YggdrasilTask : public Task
{
Q_OBJECT
Q_OBJECT
public:
explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
virtual ~YggdrasilTask() {};
explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0);
virtual ~YggdrasilTask() {};
/**
* assign a session to this task. the session will be filled with required infomration
* upon completion
*/
void assignSession(AuthSessionPtr session)
{
m_session = session;
}
/**
* assign a session to this task. the session will be filled with required infomration
* upon completion
*/
void assignSession(AuthSessionPtr session)
{
m_session = session;
}
/// get the assigned session for filling with information.
AuthSessionPtr getAssignedSession()
{
return m_session;
}
/// get the assigned session for filling with information.
AuthSessionPtr getAssignedSession()
{
return m_session;
}
/**
* Class describing a Yggdrasil error response.
*/
struct Error
{
QString m_errorMessageShort;
QString m_errorMessageVerbose;
QString m_cause;
};
/**
* Class describing a Yggdrasil error response.
*/
struct Error
{
QString m_errorMessageShort;
QString m_errorMessageVerbose;
QString m_cause;
};
enum AbortedBy
{
BY_NOTHING,
BY_USER,
BY_TIMEOUT
} m_aborted = BY_NOTHING;
enum AbortedBy
{
BY_NOTHING,
BY_USER,
BY_TIMEOUT
} m_aborted = BY_NOTHING;
/**
* Enum for describing the state of the current task.
* Used by the getStateMessage function to determine what the status message should be.
*/
enum State
{
STATE_CREATED,
STATE_SENDING_REQUEST,
STATE_PROCESSING_RESPONSE,
STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated
STATE_FAILED_HARD, //!< hard failure. auth is invalid
STATE_SUCCEEDED
} m_state = STATE_CREATED;
/**
* Enum for describing the state of the current task.
* Used by the getStateMessage function to determine what the status message should be.
*/
enum State
{
STATE_CREATED,
STATE_SENDING_REQUEST,
STATE_PROCESSING_RESPONSE,
STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated
STATE_FAILED_HARD, //!< hard failure. auth is invalid
STATE_SUCCEEDED
} m_state = STATE_CREATED;
protected:
virtual void executeTask() override;
virtual void executeTask() override;
/**
* Gets the JSON object that will be sent to the authentication server.
* Should be overridden by subclasses.
*/
virtual QJsonObject getRequestContent() const = 0;
/**
* Gets the JSON object that will be sent to the authentication server.
* Should be overridden by subclasses.
*/
virtual QJsonObject getRequestContent() const = 0;
/**
* Gets the endpoint to POST to.
* No leading slash.
*/
virtual QString getEndpoint() const = 0;
/**
* Gets the endpoint to POST to.
* No leading slash.
*/
virtual QString getEndpoint() const = 0;
/**
* Processes the response received from the server.
* If an error occurred, this should emit a failed signal and return false.
* If Yggdrasil gave an error response, it should call setError() first, and then return false.
* Otherwise, it should return true.
* Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
* an empty QJsonObject.
*/
virtual void processResponse(QJsonObject responseData) = 0;
/**
* Processes the response received from the server.
* If an error occurred, this should emit a failed signal and return false.
* If Yggdrasil gave an error response, it should call setError() first, and then return false.
* Otherwise, it should return true.
* Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
* an empty QJsonObject.
*/
virtual void processResponse(QJsonObject responseData) = 0;
/**
* Processes an error response received from the server.
* The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
* \returns a QString error message that will be passed to emitFailed.
*/
virtual void processError(QJsonObject responseData);
/**
* Processes an error response received from the server.
* The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
* \returns a QString error message that will be passed to emitFailed.
*/
virtual void processError(QJsonObject responseData);
/**
* Returns the state message for the given state.
* Used to set the status message for the task.
* Should be overridden by subclasses that want to change messages for a given state.
*/
virtual QString getStateMessage() const;
/**
* Returns the state message for the given state.
* Used to set the status message for the task.
* Should be overridden by subclasses that want to change messages for a given state.
*/
virtual QString getStateMessage() const;
protected
slots:
void processReply();
void refreshTimers(qint64, qint64);
void heartbeat();
void sslErrors(QList<QSslError>);
void processReply();
void refreshTimers(qint64, qint64);
void heartbeat();
void sslErrors(QList<QSslError>);
void changeState(State newState, QString reason=QString());
void changeState(State newState, QString reason=QString());
public
slots:
virtual bool abort() override;
void abortByTimeout();
State state();
virtual bool abort() override;
void abortByTimeout();
State state();
protected:
// FIXME: segfault disaster waiting to happen
MojangAccount *m_account = nullptr;
QNetworkReply *m_netReply = nullptr;
std::shared_ptr<Error> m_error;
QTimer timeout_keeper;
QTimer counter;
int count = 0; // num msec since time reset
// FIXME: segfault disaster waiting to happen
MojangAccount *m_account = nullptr;
QNetworkReply *m_netReply = nullptr;
std::shared_ptr<Error> m_error;
QTimer timeout_keeper;
QTimer counter;
int count = 0; // num msec since time reset
const int timeout_max = 30000;
const int time_step = 50;
const int timeout_max = 30000;
const int time_step = 50;
AuthSessionPtr m_session;
AuthSessionPtr m_session;
};

View File

@ -26,177 +26,177 @@
#include <QUuid>
AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password,
QObject *parent)
: YggdrasilTask(account, parent), m_password(password)
QObject *parent)
: YggdrasilTask(account, parent), m_password(password)
{
}
QJsonObject AuthenticateTask::getRequestContent() const
{
/*
* {
* "agent": { // optional
* "name": "Minecraft", // So far this is the only encountered value
* "version": 1 // This number might be increased
* // by the vanilla client in the future
* },
* "username": "mojang account name", // Can be an email address or player name for
// unmigrated accounts
* "password": "mojang account password",
* "clientToken": "client identifier" // optional
* "requestUser": true/false // request the user structure
* }
*/
QJsonObject req;
/*
* {
* "agent": { // optional
* "name": "Minecraft", // So far this is the only encountered value
* "version": 1 // This number might be increased
* // by the vanilla client in the future
* },
* "username": "mojang account name", // Can be an email address or player name for
// unmigrated accounts
* "password": "mojang account password",
* "clientToken": "client identifier" // optional
* "requestUser": true/false // request the user structure
* }
*/
QJsonObject req;
{
QJsonObject agent;
// C++ makes string literals void* for some stupid reason, so we have to tell it
// QString... Thanks Obama.
agent.insert("name", QString("Minecraft"));
agent.insert("version", 1);
req.insert("agent", agent);
}
{
QJsonObject agent;
// C++ makes string literals void* for some stupid reason, so we have to tell it
// QString... Thanks Obama.
agent.insert("name", QString("Minecraft"));
agent.insert("version", 1);
req.insert("agent", agent);
}
req.insert("username", m_account->username());
req.insert("password", m_password);
req.insert("requestUser", true);
req.insert("username", m_account->username());
req.insert("password", m_password);
req.insert("requestUser", true);
// If we already have a client token, give it to the server.
// Otherwise, let the server give us one.
// If we already have a client token, give it to the server.
// Otherwise, let the server give us one.
if(m_account->m_clientToken.isEmpty())
{
auto uuid = QUuid::createUuid();
auto uuidString = uuid.toString().remove('{').remove('-').remove('}');
m_account->m_clientToken = uuidString;
}
req.insert("clientToken", m_account->m_clientToken);
if(m_account->m_clientToken.isEmpty())
{
auto uuid = QUuid::createUuid();
auto uuidString = uuid.toString().remove('{').remove('-').remove('}');
m_account->m_clientToken = uuidString;
}
req.insert("clientToken", m_account->m_clientToken);
return req;
return req;
}
void AuthenticateTask::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
// qDebug() << responseData;
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
qDebug() << "Getting client token.";
QString clientToken = responseData.value("clientToken").toString("");
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Set the client token.
m_account->m_clientToken = clientToken;
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
// qDebug() << responseData;
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
qDebug() << "Getting client token.";
QString clientToken = responseData.value("clientToken").toString("");
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Set the client token.
m_account->m_clientToken = clientToken;
// Now, we set the access token.
qDebug() << "Getting access token.";
QString accessToken = responseData.value("accessToken").toString("");
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// Set the access token.
m_account->m_accessToken = accessToken;
// Now, we set the access token.
qDebug() << "Getting access token.";
QString accessToken = responseData.value("accessToken").toString("");
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// Set the access token.
m_account->m_accessToken = accessToken;
// Now we load the list of available profiles.
// Mojang hasn't yet implemented the profile system,
// but we might as well support what's there so we
// don't have trouble implementing it later.
qDebug() << "Loading profile list.";
QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
QList<AccountProfile> loadedProfiles;
for (auto iter : availableProfiles)
{
QJsonObject profile = iter.toObject();
// Profiles are easy, we just need their ID and name.
QString id = profile.value("id").toString("");
QString name = profile.value("name").toString("");
bool legacy = profile.value("legacy").toBool(false);
// Now we load the list of available profiles.
// Mojang hasn't yet implemented the profile system,
// but we might as well support what's there so we
// don't have trouble implementing it later.
qDebug() << "Loading profile list.";
QJsonArray availableProfiles = responseData.value("availableProfiles").toArray();
QList<AccountProfile> loadedProfiles;
for (auto iter : availableProfiles)
{
QJsonObject profile = iter.toObject();
// Profiles are easy, we just need their ID and name.
QString id = profile.value("id").toString("");
QString name = profile.value("name").toString("");
bool legacy = profile.value("legacy").toBool(false);
if (id.isEmpty() || name.isEmpty())
{
// This should never happen, but we might as well
// warn about it if it does so we can debug it easily.
// You never know when Mojang might do something truly derpy.
qWarning() << "Found entry in available profiles list with missing ID or name "
"field. Ignoring it.";
}
if (id.isEmpty() || name.isEmpty())
{
// This should never happen, but we might as well
// warn about it if it does so we can debug it easily.
// You never know when Mojang might do something truly derpy.
qWarning() << "Found entry in available profiles list with missing ID or name "
"field. Ignoring it.";
}
// Now, add a new AccountProfile entry to the list.
loadedProfiles.append({id, name, legacy});
}
// Put the list of profiles we loaded into the MojangAccount object.
m_account->m_profiles = loadedProfiles;
// Now, add a new AccountProfile entry to the list.
loadedProfiles.append({id, name, legacy});
}
// Put the list of profiles we loaded into the MojangAccount object.
m_account->m_profiles = loadedProfiles;
// Finally, we set the current profile to the correct value. This is pretty simple.
// We do need to make sure that the current profile that the server gave us
// is actually in the available profiles list.
// If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know).
qDebug() << "Setting current profile.";
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString("");
if (currentProfileId.isEmpty())
{
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium."));
return;
}
if (!m_account->setCurrentProfile(currentProfileId))
{
changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list."));
return;
}
// Finally, we set the current profile to the correct value. This is pretty simple.
// We do need to make sure that the current profile that the server gave us
// is actually in the available profiles list.
// If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know).
qDebug() << "Setting current profile.";
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString("");
if (currentProfileId.isEmpty())
{
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium."));
return;
}
if (!m_account->setCurrentProfile(currentProfileId))
{
changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list."));
return;
}
// this is what the vanilla launcher passes to the userProperties launch param
if (responseData.contains("user"))
{
User u;
auto obj = responseData.value("user").toObject();
u.id = obj.value("id").toString();
auto propArray = obj.value("properties").toArray();
for (auto prop : propArray)
{
auto propTuple = prop.toObject();
auto name = propTuple.value("name").toString();
auto value = propTuple.value("value").toString();
u.properties.insert(name, value);
}
m_account->m_user = u;
}
// this is what the vanilla launcher passes to the userProperties launch param
if (responseData.contains("user"))
{
User u;
auto obj = responseData.value("user").toObject();
u.id = obj.value("id").toString();
auto propArray = obj.value("properties").toArray();
for (auto prop : propArray)
{
auto propTuple = prop.toObject();
auto name = propTuple.value("name").toString();
auto value = propTuple.value("value").toString();
u.properties.insert(name, value);
}
m_account->m_user = u;
}
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading authentication response.";
changeState(STATE_SUCCEEDED);
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading authentication response.";
changeState(STATE_SUCCEEDED);
}
QString AuthenticateTask::getEndpoint() const
{
return "authenticate";
return "authenticate";
}
QString AuthenticateTask::getStateMessage() const
{
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Authenticating: Sending request...");
case STATE_PROCESSING_RESPONSE:
return tr("Authenticating: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Authenticating: Sending request...");
case STATE_PROCESSING_RESPONSE:
return tr("Authenticating: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
}

View File

@ -28,19 +28,19 @@
*/
class AuthenticateTask : public YggdrasilTask
{
Q_OBJECT
Q_OBJECT
public:
AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const override;
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const override;
virtual QString getEndpoint() const override;
virtual void processResponse(QJsonObject responseData) override;
virtual void processResponse(QJsonObject responseData) override;
virtual QString getStateMessage() const override;
virtual QString getStateMessage() const override;
private:
QString m_password;
QString m_password;
};

View File

@ -29,116 +29,116 @@ RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account)
QJsonObject RefreshTask::getRequestContent() const
{
/*
* {
* "clientToken": "client identifier"
* "accessToken": "current access token to be refreshed"
* "selectedProfile": // specifying this causes errors
* {
* "id": "profile ID"
* "name": "profile name"
* }
* "requestUser": true/false // request the user structure
* }
*/
QJsonObject req;
req.insert("clientToken", m_account->m_clientToken);
req.insert("accessToken", m_account->m_accessToken);
/*
{
auto currentProfile = m_account->currentProfile();
QJsonObject profile;
profile.insert("id", currentProfile->id());
profile.insert("name", currentProfile->name());
req.insert("selectedProfile", profile);
}
*/
req.insert("requestUser", true);
/*
* {
* "clientToken": "client identifier"
* "accessToken": "current access token to be refreshed"
* "selectedProfile": // specifying this causes errors
* {
* "id": "profile ID"
* "name": "profile name"
* }
* "requestUser": true/false // request the user structure
* }
*/
QJsonObject req;
req.insert("clientToken", m_account->m_clientToken);
req.insert("accessToken", m_account->m_accessToken);
/*
{
auto currentProfile = m_account->currentProfile();
QJsonObject profile;
profile.insert("id", currentProfile->id());
profile.insert("name", currentProfile->name());
req.insert("selectedProfile", profile);
}
*/
req.insert("requestUser", true);
return req;
return req;
}
void RefreshTask::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
// Read the response data. We need to get the client token, access token, and the selected
// profile.
qDebug() << "Processing authentication response.";
// qDebug() << responseData;
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
QString clientToken = responseData.value("clientToken").toString("");
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// qDebug() << responseData;
// If we already have a client token, make sure the one the server gave us matches our
// existing one.
QString clientToken = responseData.value("clientToken").toString("");
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Now, we set the access token.
qDebug() << "Getting new access token.";
QString accessToken = responseData.value("accessToken").toString("");
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// Now, we set the access token.
qDebug() << "Getting new access token.";
QString accessToken = responseData.value("accessToken").toString("");
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// we validate that the server responded right. (our current profile = returned current
// profile)
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString("");
if (m_account->currentProfile()->id != currentProfileId)
{
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected."));
return;
}
// we validate that the server responded right. (our current profile = returned current
// profile)
QJsonObject currentProfile = responseData.value("selectedProfile").toObject();
QString currentProfileId = currentProfile.value("id").toString("");
if (m_account->currentProfile()->id != currentProfileId)
{
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected."));
return;
}
// this is what the vanilla launcher passes to the userProperties launch param
if (responseData.contains("user"))
{
User u;
auto obj = responseData.value("user").toObject();
u.id = obj.value("id").toString();
auto propArray = obj.value("properties").toArray();
for (auto prop : propArray)
{
auto propTuple = prop.toObject();
auto name = propTuple.value("name").toString();
auto value = propTuple.value("value").toString();
u.properties.insert(name, value);
}
m_account->m_user = u;
}
// this is what the vanilla launcher passes to the userProperties launch param
if (responseData.contains("user"))
{
User u;
auto obj = responseData.value("user").toObject();
u.id = obj.value("id").toString();
auto propArray = obj.value("properties").toArray();
for (auto prop : propArray)
{
auto propTuple = prop.toObject();
auto name = propTuple.value("name").toString();
auto value = propTuple.value("value").toString();
u.properties.insert(name, value);
}
m_account->m_user = u;
}
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading refresh response.";
// Reset the access token.
m_account->m_accessToken = accessToken;
changeState(STATE_SUCCEEDED);
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading refresh response.";
// Reset the access token.
m_account->m_accessToken = accessToken;
changeState(STATE_SUCCEEDED);
}
QString RefreshTask::getEndpoint() const
{
return "refresh";
return "refresh";
}
QString RefreshTask::getStateMessage() const
{
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Refreshing login token...");
case STATE_PROCESSING_RESPONSE:
return tr("Refreshing login token: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Refreshing login token...");
case STATE_PROCESSING_RESPONSE:
return tr("Refreshing login token: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
}

View File

@ -28,17 +28,17 @@
*/
class RefreshTask : public YggdrasilTask
{
Q_OBJECT
Q_OBJECT
public:
RefreshTask(MojangAccount * account);
RefreshTask(MojangAccount * account);
protected:
virtual QJsonObject getRequestContent() const override;
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const override;
virtual QString getEndpoint() const override;
virtual void processResponse(QJsonObject responseData) override;
virtual void processResponse(QJsonObject responseData) override;
virtual QString getStateMessage() const override;
virtual QString getStateMessage() const override;
};

View File

@ -25,37 +25,37 @@
#include <QDebug>
ValidateTask::ValidateTask(MojangAccount * account, QObject *parent)
: YggdrasilTask(account, parent)
: YggdrasilTask(account, parent)
{
}
QJsonObject ValidateTask::getRequestContent() const
{
QJsonObject req;
req.insert("accessToken", m_account->m_accessToken);
return req;
QJsonObject req;
req.insert("accessToken", m_account->m_accessToken);
return req;
}
void ValidateTask::processResponse(QJsonObject responseData)
{
// Assume that if processError wasn't called, then the request was successful.
changeState(YggdrasilTask::STATE_SUCCEEDED);
// Assume that if processError wasn't called, then the request was successful.
changeState(YggdrasilTask::STATE_SUCCEEDED);
}
QString ValidateTask::getEndpoint() const
{
return "validate";
return "validate";
}
QString ValidateTask::getStateMessage() const
{
switch (m_state)
{
case YggdrasilTask::STATE_SENDING_REQUEST:
return tr("Validating access token: Sending request...");
case YggdrasilTask::STATE_PROCESSING_RESPONSE:
return tr("Validating access token: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
switch (m_state)
{
case YggdrasilTask::STATE_SENDING_REQUEST:
return tr("Validating access token: Sending request...");
case YggdrasilTask::STATE_PROCESSING_RESPONSE:
return tr("Validating access token: Processing response...");
default:
return YggdrasilTask::getStateMessage();
}
}

View File

@ -30,18 +30,18 @@
*/
class ValidateTask : public YggdrasilTask
{
Q_OBJECT
Q_OBJECT
public:
ValidateTask(MojangAccount *account, QObject *parent = 0);
ValidateTask(MojangAccount *account, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const override;
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const override;
virtual QString getEndpoint() const override;
virtual void processResponse(QJsonObject responseData) override;
virtual void processResponse(QJsonObject responseData) override;
virtual QString getStateMessage() const override;
virtual QString getStateMessage() const override;
private:
};

View File

@ -25,138 +25,138 @@
ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction()
{
m_entry = entry;
m_target_path = entry->getFullPath();
m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX");
m_status = Job_NotStarted;
m_url_path = relative_path;
m_url = "http://files.minecraftforge.net/maven/" + m_url_path + ".pack.xz";
m_entry = entry;
m_target_path = entry->getFullPath();
m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX");
m_status = Job_NotStarted;
m_url_path = relative_path;
m_url = "http://files.minecraftforge.net/maven/" + m_url_path + ".pack.xz";
}
void ForgeXzDownload::start()
{
if(m_status == Job_Aborted)
{
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
emit aborted(m_index_within_job);
return;
}
m_status = Job_InProgress;
if (!m_entry->isStale())
{
m_status = Job_Finished;
emit succeeded(m_index_within_job);
return;
}
// can we actually create the real, final file?
if (!FS::ensureFilePathExists(m_target_path))
{
m_status = Job_Failed;
emit failed(m_index_within_job);
return;
}
if(m_status == Job_Aborted)
{
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
emit aborted(m_index_within_job);
return;
}
m_status = Job_InProgress;
if (!m_entry->isStale())
{
m_status = Job_Finished;
emit succeeded(m_index_within_job);
return;
}
// can we actually create the real, final file?
if (!FS::ensureFilePathExists(m_target_path))
{
m_status = Job_Failed;
emit failed(m_index_within_job);
return;
}
qDebug() << "Downloading " << m_url.toString();
QNetworkRequest request(m_url);
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
qDebug() << "Downloading " << m_url.toString();
QNetworkRequest request(m_url);
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
QNetworkReply *rep = ENV.qnam().get(request);
QNetworkReply *rep = ENV.qnam().get(request);
m_reply.reset(rep);
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
m_reply.reset(rep);
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
}
void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
m_total_progress = bytesTotal;
m_progress = bytesReceived;
emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
m_total_progress = bytesTotal;
m_progress = bytesReceived;
emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
}
void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error)
{
if(error == QNetworkReply::OperationCanceledError)
{
qCritical() << "Aborted " << m_url.toString();
m_status = Job_Aborted;
}
else
{
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
m_status = Job_Failed;
}
if(error == QNetworkReply::OperationCanceledError)
{
qCritical() << "Aborted " << m_url.toString();
m_status = Job_Aborted;
}
else
{
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
m_status = Job_Failed;
}
}
void ForgeXzDownload::failAndTryNextMirror()
{
m_status = Job_Failed;
emit failed(m_index_within_job);
m_status = Job_Failed;
emit failed(m_index_within_job);
}
void ForgeXzDownload::downloadFinished()
{
// if the download succeeded
if (m_status != Job_Failed && m_status != Job_Aborted)
{
// nothing went wrong...
m_status = Job_Finished;
if (m_pack200_xz_file.isOpen())
{
// we actually downloaded something! process and isntall it
decompressAndInstall();
return;
}
else
{
// something bad happened -- on the local machine!
m_status = Job_Failed;
m_pack200_xz_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
return;
}
}
else if(m_status == Job_Aborted)
{
m_pack200_xz_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
emit aborted(m_index_within_job);
return;
}
// else the download failed
else
{
m_status = Job_Failed;
m_pack200_xz_file.close();
m_pack200_xz_file.remove();
m_reply.reset();
failAndTryNextMirror();
return;
}
// if the download succeeded
if (m_status != Job_Failed && m_status != Job_Aborted)
{
// nothing went wrong...
m_status = Job_Finished;
if (m_pack200_xz_file.isOpen())
{
// we actually downloaded something! process and isntall it
decompressAndInstall();
return;
}
else
{
// something bad happened -- on the local machine!
m_status = Job_Failed;
m_pack200_xz_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
return;
}
}
else if(m_status == Job_Aborted)
{
m_pack200_xz_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
emit aborted(m_index_within_job);
return;
}
// else the download failed
else
{
m_status = Job_Failed;
m_pack200_xz_file.close();
m_pack200_xz_file.remove();
m_reply.reset();
failAndTryNextMirror();
return;
}
}
void ForgeXzDownload::downloadReadyRead()
{
if (!m_pack200_xz_file.isOpen())
{
if (!m_pack200_xz_file.open())
{
/*
* Can't open the file... the job failed
*/
m_reply->abort();
emit failed(m_index_within_job);
return;
}
}
m_pack200_xz_file.write(m_reply->readAll());
if (!m_pack200_xz_file.isOpen())
{
if (!m_pack200_xz_file.open())
{
/*
* Can't open the file... the job failed
*/
m_reply->abort();
emit failed(m_index_within_job);
return;
}
}
m_pack200_xz_file.write(m_reply->readAll());
}
#include "xz.h"
@ -169,225 +169,225 @@ const size_t buffer_size = 8196;
// NOTE: once this gets here, it can't be aborted anymore. we don't care.
void ForgeXzDownload::decompressAndInstall()
{
// rewind the downloaded temp file
m_pack200_xz_file.seek(0);
// de-xz'd file
QTemporaryFile pack200_file("./dl_temp.XXXXXX");
pack200_file.open();
// rewind the downloaded temp file
m_pack200_xz_file.seek(0);
// de-xz'd file
QTemporaryFile pack200_file("./dl_temp.XXXXXX");
pack200_file.open();
bool xz_success = false;
// first, de-xz
{
uint8_t in[buffer_size];
uint8_t out[buffer_size];
struct xz_buf b;
struct xz_dec *s;
enum xz_ret ret;
xz_crc32_init();
xz_crc64_init();
s = xz_dec_init(XZ_DYNALLOC, 1 << 26);
if (s == nullptr)
{
xz_dec_end(s);
failAndTryNextMirror();
return;
}
b.in = in;
b.in_pos = 0;
b.in_size = 0;
b.out = out;
b.out_pos = 0;
b.out_size = buffer_size;
while (!xz_success)
{
if (b.in_pos == b.in_size)
{
b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in));
b.in_pos = 0;
}
bool xz_success = false;
// first, de-xz
{
uint8_t in[buffer_size];
uint8_t out[buffer_size];
struct xz_buf b;
struct xz_dec *s;
enum xz_ret ret;
xz_crc32_init();
xz_crc64_init();
s = xz_dec_init(XZ_DYNALLOC, 1 << 26);
if (s == nullptr)
{
xz_dec_end(s);
failAndTryNextMirror();
return;
}
b.in = in;
b.in_pos = 0;
b.in_size = 0;
b.out = out;
b.out_pos = 0;
b.out_size = buffer_size;
while (!xz_success)
{
if (b.in_pos == b.in_size)
{
b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in));
b.in_pos = 0;
}
ret = xz_dec_run(s, &b);
ret = xz_dec_run(s, &b);
if (b.out_pos == sizeof(out))
{
auto wresult = pack200_file.write((char *)out, b.out_pos);
if (wresult < 0 || size_t(wresult) != b.out_pos)
{
// msg = "Write error\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
}
if (b.out_pos == sizeof(out))
{
auto wresult = pack200_file.write((char *)out, b.out_pos);
if (wresult < 0 || size_t(wresult) != b.out_pos)
{
// msg = "Write error\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
}
b.out_pos = 0;
}
b.out_pos = 0;
}
if (ret == XZ_OK)
continue;
if (ret == XZ_OK)
continue;
if (ret == XZ_UNSUPPORTED_CHECK)
{
// unsupported check. this is OK, but we should log this
continue;
}
if (ret == XZ_UNSUPPORTED_CHECK)
{
// unsupported check. this is OK, but we should log this
continue;
}
auto wresult = pack200_file.write((char *)out, b.out_pos);
if (wresult < 0 || size_t(wresult) != b.out_pos)
{
// write error
pack200_file.close();
xz_dec_end(s);
return;
}
auto wresult = pack200_file.write((char *)out, b.out_pos);
if (wresult < 0 || size_t(wresult) != b.out_pos)
{
// write error
pack200_file.close();
xz_dec_end(s);
return;
}
switch (ret)
{
case XZ_STREAM_END:
xz_dec_end(s);
xz_success = true;
break;
switch (ret)
{
case XZ_STREAM_END:
xz_dec_end(s);
xz_success = true;
break;
case XZ_MEM_ERROR:
qCritical() << "Memory allocation failed\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_MEM_ERROR:
qCritical() << "Memory allocation failed\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_MEMLIMIT_ERROR:
qCritical() << "Memory usage limit reached\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_MEMLIMIT_ERROR:
qCritical() << "Memory usage limit reached\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_FORMAT_ERROR:
qCritical() << "Not a .xz file\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_FORMAT_ERROR:
qCritical() << "Not a .xz file\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_OPTIONS_ERROR:
qCritical() << "Unsupported options in the .xz headers\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_OPTIONS_ERROR:
qCritical() << "Unsupported options in the .xz headers\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_DATA_ERROR:
case XZ_BUF_ERROR:
qCritical() << "File is corrupt\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
case XZ_DATA_ERROR:
case XZ_BUF_ERROR:
qCritical() << "File is corrupt\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
default:
qCritical() << "Bug!\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
}
}
}
m_pack200_xz_file.remove();
default:
qCritical() << "Bug!\n";
xz_dec_end(s);
failAndTryNextMirror();
return;
}
}
}
m_pack200_xz_file.remove();
// revert pack200
pack200_file.seek(0);
int handle_in = pack200_file.handle();
// FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects.
if(handle_in == -1)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
int handle_in_dup = dup (handle_in);
if(handle_in_dup == -1)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
FILE *file_in = fdopen (handle_in_dup, "rb");
if(!file_in)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
QFile qfile_out(m_target_path);
if(!qfile_out.open(QIODevice::WriteOnly))
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out = qfile_out.handle();
if(handle_out == -1)
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out_dup = dup (handle_out);
if(handle_out_dup == -1)
{
qCritical() << "Error reopening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
FILE *file_out = fdopen (handle_out_dup, "wb");
if(!file_out)
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
try
{
// NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above.
unpack_200(file_in, file_out);
}
catch (const std::runtime_error &err)
{
m_status = Job_Failed;
qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what();
QFile f(m_target_path);
if (f.exists())
f.remove();
failAndTryNextMirror();
return;
}
pack200_file.remove();
// revert pack200
pack200_file.seek(0);
int handle_in = pack200_file.handle();
// FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects.
if(handle_in == -1)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
int handle_in_dup = dup (handle_in);
if(handle_in_dup == -1)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
FILE *file_in = fdopen (handle_in_dup, "rb");
if(!file_in)
{
qCritical() << "Error reopening " << pack200_file.fileName();
failAndTryNextMirror();
return;
}
QFile qfile_out(m_target_path);
if(!qfile_out.open(QIODevice::WriteOnly))
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out = qfile_out.handle();
if(handle_out == -1)
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
int handle_out_dup = dup (handle_out);
if(handle_out_dup == -1)
{
qCritical() << "Error reopening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
FILE *file_out = fdopen (handle_out_dup, "wb");
if(!file_out)
{
qCritical() << "Error opening " << qfile_out.fileName();
failAndTryNextMirror();
return;
}
try
{
// NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above.
unpack_200(file_in, file_out);
}
catch (const std::runtime_error &err)
{
m_status = Job_Failed;
qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what();
QFile f(m_target_path);
if (f.exists())
f.remove();
failAndTryNextMirror();
return;
}
pack200_file.remove();
QFile jar_file(m_target_path);
QFile jar_file(m_target_path);
if (!jar_file.open(QIODevice::ReadOnly))
{
jar_file.remove();
failAndTryNextMirror();
return;
}
auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5);
m_entry->setMD5Sum(hash.toHex().constData());
jar_file.close();
if (!jar_file.open(QIODevice::ReadOnly))
{
jar_file.remove();
failAndTryNextMirror();
return;
}
auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5);
m_entry->setMD5Sum(hash.toHex().constData());
jar_file.close();
QFileInfo output_file_info(m_target_path);
m_entry->setETag(m_reply->rawHeader("ETag").constData());
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
m_entry->setStale(false);
ENV.metacache()->updateEntry(m_entry);
QFileInfo output_file_info(m_target_path);
m_entry->setETag(m_reply->rawHeader("ETag").constData());
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
m_entry->setStale(false);
ENV.metacache()->updateEntry(m_entry);
m_reply.reset();
emit succeeded(m_index_within_job);
m_reply.reset();
emit succeeded(m_index_within_job);
}
bool ForgeXzDownload::abort()
{
if(m_reply)
m_reply->abort();
m_status = Job_Aborted;
return true;
if(m_reply)
m_reply->abort();
m_status = Job_Aborted;
return true;
}
bool ForgeXzDownload::canAbort()
{
return true;
return true;
}

View File

@ -24,38 +24,38 @@ typedef std::shared_ptr<class ForgeXzDownload> ForgeXzDownloadPtr;
class ForgeXzDownload : public NetAction
{
Q_OBJECT
Q_OBJECT
public:
MetaEntryPtr m_entry;
/// if saving to file, use the one specified in this string
QString m_target_path;
/// this is the output file, if any
QTemporaryFile m_pack200_xz_file;
/// path relative to the mirror base
QString m_url_path;
MetaEntryPtr m_entry;
/// if saving to file, use the one specified in this string
QString m_target_path;
/// this is the output file, if any
QTemporaryFile m_pack200_xz_file;
/// path relative to the mirror base
QString m_url_path;
public:
explicit ForgeXzDownload(QString relative_path, MetaEntryPtr entry);
static ForgeXzDownloadPtr make(QString relative_path, MetaEntryPtr entry)
{
return ForgeXzDownloadPtr(new ForgeXzDownload(relative_path, entry));
}
virtual ~ForgeXzDownload(){};
bool canAbort() override;
explicit ForgeXzDownload(QString relative_path, MetaEntryPtr entry);
static ForgeXzDownloadPtr make(QString relative_path, MetaEntryPtr entry)
{
return ForgeXzDownloadPtr(new ForgeXzDownload(relative_path, entry));
}
virtual ~ForgeXzDownload(){};
bool canAbort() override;
protected
slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void downloadFinished() override;
void downloadReadyRead() override;
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void downloadFinished() override;
void downloadReadyRead() override;
public
slots:
void start() override;
bool abort() override;
void start() override;
bool abort() override;
private:
void decompressAndInstall();
void failAndTryNextMirror();
void decompressAndInstall();
void failAndTryNextMirror();
};

View File

@ -3,22 +3,22 @@
ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchStep(parent)
{
if(session->status == AuthSession::Status::PlayableOnline)
{
m_account = session->m_accountPtr;
}
if(session->status == AuthSession::Status::PlayableOnline)
{
m_account = session->m_accountPtr;
}
}
void ClaimAccount::executeTask()
{
if(m_account)
{
lock.reset(new UseLock(m_account));
emitSucceeded();
}
if(m_account)
{
lock.reset(new UseLock(m_account));
emitSucceeded();
}
}
void ClaimAccount::finalize()
{
lock.reset();
lock.reset();
}

View File

@ -20,18 +20,18 @@
class ClaimAccount: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session);
virtual ~ClaimAccount() {};
explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session);
virtual ~ClaimAccount() {};
void executeTask() override;
void finalize() override;
bool canAbort() const override
{
return false;
}
void executeTask() override;
void finalize() override;
bool canAbort() const override
{
return false;
}
private:
std::unique_ptr<UseLock> lock;
MojangAccountPtr m_account;
std::unique_ptr<UseLock> lock;
MojangAccountPtr m_account;
};

View File

@ -9,11 +9,11 @@ CreateServerResourcePacksFolder::CreateServerResourcePacksFolder(LaunchTask* par
void CreateServerResourcePacksFolder::executeTask()
{
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->minecraftRoot(), "server-resource-packs")))
{
emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error);
}
emitSucceeded();
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->minecraftRoot(), "server-resource-packs")))
{
emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error);
}
emitSucceeded();
}

View File

@ -22,16 +22,16 @@
// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
class CreateServerResourcePacksFolder: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit CreateServerResourcePacksFolder(LaunchTask *parent);
virtual ~CreateServerResourcePacksFolder() {};
explicit CreateServerResourcePacksFolder(LaunchTask *parent);
virtual ~CreateServerResourcePacksFolder() {};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
};

View File

@ -22,128 +22,128 @@
DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent)
{
connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state);
connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state);
}
void DirectJavaLaunch::executeTask()
{
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
QStringList args = minecraftInstance->javaArguments();
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
QStringList args = minecraftInstance->javaArguments();
args.append("-Djava.library.path=" + minecraftInstance->getNativePath());
args.append("-Djava.library.path=" + minecraftInstance->getNativePath());
auto classPathEntries = minecraftInstance->getClassPath();
args.append("-cp");
QString classpath;
auto classPathEntries = minecraftInstance->getClassPath();
args.append("-cp");
QString classpath;
#ifdef Q_OS_WIN32
classpath = classPathEntries.join(';');
classpath = classPathEntries.join(';');
#else
classpath = classPathEntries.join(':');
classpath = classPathEntries.join(':');
#endif
args.append(classpath);
args.append(minecraftInstance->getMainClass());
args.append(classpath);
args.append(minecraftInstance->getMainClass());
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC);
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC);
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
m_process.setProcessEnvironment(instance->createEnvironment());
m_process.setProcessEnvironment(instance->createEnvironment());
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session);
args.append(mcArgs);
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session);
args.append(mcArgs);
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
{
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
args.prepend(javaPath);
m_process.start(wrapperCommand, wrapperArgs + args);
}
else
{
m_process.start(javaPath, args);
}
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
{
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
args.prepend(javaPath);
m_process.start(wrapperCommand, wrapperArgs + args);
}
else
{
m_process.start(javaPath, args);
}
}
void DirectJavaLaunch::on_state(LoggedProcess::State state)
{
switch(state)
{
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instace can't start
QString reason = tr("Could not launch minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
switch(state)
{
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instace can't start
QString reason = tr("Could not launch minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
{
m_parent->setPid(-1);
emitFailed("Game crashed.");
return;
}
case LoggedProcess::Finished:
{
m_parent->setPid(-1);
// if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
emitFailed("Game crashed.");
return;
}
//FIXME: make this work again
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
emitSucceeded();
break;
}
case LoggedProcess::Running:
emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
break;
default:
break;
}
{
m_parent->setPid(-1);
emitFailed("Game crashed.");
return;
}
case LoggedProcess::Finished:
{
m_parent->setPid(-1);
// if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
emitFailed("Game crashed.");
return;
}
//FIXME: make this work again
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
emitSucceeded();
break;
}
case LoggedProcess::Running:
emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
break;
default:
break;
}
}
void DirectJavaLaunch::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
m_process.setWorkingDirectory(wd);
}
void DirectJavaLaunch::proceed()
{
// nil
// nil
}
bool DirectJavaLaunch::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@ -21,29 +21,29 @@
class DirectJavaLaunch: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit DirectJavaLaunch(LaunchTask *parent);
virtual ~DirectJavaLaunch() {};
explicit DirectJavaLaunch(LaunchTask *parent);
virtual ~DirectJavaLaunch() {};
virtual void executeTask();
virtual bool abort();
virtual void proceed();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
void setAuthSession(AuthSessionPtr session)
{
m_session = session;
}
virtual void executeTask();
virtual bool abort();
virtual void proceed();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
void setAuthSession(AuthSessionPtr session)
{
m_session = session;
}
private slots:
void on_state(LoggedProcess::State state);
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
};

View File

@ -25,76 +25,76 @@
static QString replaceSuffix (QString target, const QString &suffix, const QString &replacement)
{
if (!target.endsWith(suffix))
{
return target;
}
target.resize(target.length() - suffix.length());
return target + replacement;
if (!target.endsWith(suffix))
{
return target;
}
target.resize(target.length() - suffix.length());
return target + replacement;
}
static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack)
{
QuaZip zip(source);
if(!zip.open(QuaZip::mdUnzip))
{
return false;
}
QDir directory(targetFolder);
if (!zip.goToFirstFile())
{
return false;
}
do
{
QString name = zip.getCurrentFileName();
if(applyJnilibHack)
{
name = replaceSuffix(name, ".jnilib", ".dylib");
}
QString absFilePath = directory.absoluteFilePath(name);
if (!JlCompress::extractFile(&zip, "", absFilePath))
{
return false;
}
} while (zip.goToNextFile());
zip.close();
if(zip.getZipError()!=0)
{
return false;
}
return true;
QuaZip zip(source);
if(!zip.open(QuaZip::mdUnzip))
{
return false;
}
QDir directory(targetFolder);
if (!zip.goToFirstFile())
{
return false;
}
do
{
QString name = zip.getCurrentFileName();
if(applyJnilibHack)
{
name = replaceSuffix(name, ".jnilib", ".dylib");
}
QString absFilePath = directory.absoluteFilePath(name);
if (!JlCompress::extractFile(&zip, "", absFilePath))
{
return false;
}
} while (zip.goToNextFile());
zip.close();
if(zip.getZipError()!=0)
{
return false;
}
return true;
}
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())
{
emitSucceeded();
return;
}
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for(const auto &source: toExtract)
{
if(!unzipNatives(source, outputPath, jniHackEnabled))
{
auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath);
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}
emitSucceeded();
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
auto toExtract = minecraftInstance->getNativeJars();
if(toExtract.isEmpty())
{
emitSucceeded();
return;
}
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for(const auto &source: toExtract)
{
if(!unzipNatives(source, outputPath, jniHackEnabled))
{
auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath);
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}
emitSucceeded();
}
void ExtractNatives::finalize()
{
auto instance = m_parent->instance();
QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/");
QDir dir(target_dir);
dir.removeRecursively();
auto instance = m_parent->instance();
QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/");
QDir dir(target_dir);
dir.removeRecursively();
}

View File

@ -22,17 +22,17 @@
// FIXME: temporary wrapper for existing task.
class ExtractNatives: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){};
virtual ~ExtractNatives(){};
explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){};
virtual ~ExtractNatives(){};
void executeTask() override;
bool canAbort() const override
{
return false;
}
void finalize() override;
void executeTask() override;
bool canAbort() const override
{
return false;
}
void finalize() override;
};

View File

@ -24,8 +24,8 @@
LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent)
{
connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state);
connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state);
}
#ifdef Q_OS_WIN
@ -33,187 +33,187 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent)
#include <windows.h>
QString shortPathName(const QString & file)
{
auto input = file.toStdWString();
std::wstring output;
long length = GetShortPathNameW(input.c_str(), NULL, 0);
// NOTE: this resizing might seem weird...
// when GetShortPathNameW fails, it returns length including null character
// when it succeeds, it returns length excluding null character
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
output.resize(length);
GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length);
output.resize(length-1);
QString ret = QString::fromStdWString(output);
return ret;
auto input = file.toStdWString();
std::wstring output;
long length = GetShortPathNameW(input.c_str(), NULL, 0);
// NOTE: this resizing might seem weird...
// when GetShortPathNameW fails, it returns length including null character
// when it succeeds, it returns length excluding null character
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
output.resize(length);
GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length);
output.resize(length-1);
QString ret = QString::fromStdWString(output);
return ret;
}
#endif
// if the string survives roundtrip through local 8bit encoding...
bool fitsInLocal8bit(const QString & string)
{
return string == QString::fromLocal8Bit(string.toLocal8Bit());
return string == QString::fromLocal8Bit(string.toLocal8Bit());
}
void LauncherPartLaunch::executeTask()
{
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
m_launchScript = minecraftInstance->createLaunchScript(m_session);
QStringList args = minecraftInstance->javaArguments();
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC);
m_launchScript = minecraftInstance->createLaunchScript(m_session);
QStringList args = minecraftInstance->javaArguments();
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC);
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
m_process.setProcessEnvironment(instance->createEnvironment());
m_process.setProcessEnvironment(instance->createEnvironment());
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
auto classPath = minecraftInstance->getClassPath();
classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar"));
auto classPath = minecraftInstance->getClassPath();
classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar"));
auto natPath = minecraftInstance->getNativePath();
auto natPath = minecraftInstance->getNativePath();
#ifdef Q_OS_WIN
if (!fitsInLocal8bit(natPath))
{
args << "-Djava.library.path=" + shortPathName(natPath);
}
else
{
args << "-Djava.library.path=" + natPath;
}
if (!fitsInLocal8bit(natPath))
{
args << "-Djava.library.path=" + shortPathName(natPath);
}
else
{
args << "-Djava.library.path=" + natPath;
}
#else
args << "-Djava.library.path=" + natPath;
args << "-Djava.library.path=" + natPath;
#endif
args << "-cp";
args << "-cp";
#ifdef Q_OS_WIN
QStringList processed;
for(auto & item: classPath)
{
if (!fitsInLocal8bit(item))
{
processed << shortPathName(item);
}
else
{
processed << item;
}
}
args << processed.join(';');
QStringList processed;
for(auto & item: classPath)
{
if (!fitsInLocal8bit(item))
{
processed << shortPathName(item);
}
else
{
processed << item;
}
}
args << processed.join(';');
#else
args << classPath.join(':');
args << classPath.join(':');
#endif
args << "org.multimc.EntryPoint";
args << "org.multimc.EntryPoint";
qDebug() << args.join(' ');
qDebug() << args.join(' ');
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
{
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
args.prepend(javaPath);
m_process.start(wrapperCommand, wrapperArgs + args);
}
else
{
m_process.start(javaPath, args);
}
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
{
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
args.prepend(javaPath);
m_process.start(wrapperCommand, wrapperArgs + args);
}
else
{
m_process.start(javaPath, args);
}
}
void LauncherPartLaunch::on_state(LoggedProcess::State state)
{
switch(state)
{
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instace can't start
QString reason = tr("Could not launch minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
switch(state)
{
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instace can't start
QString reason = tr("Could not launch minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
{
m_parent->setPid(-1);
emitFailed("Game crashed.");
return;
}
case LoggedProcess::Finished:
{
m_parent->setPid(-1);
// if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
emitFailed("Game crashed.");
return;
}
//FIXME: make this work again
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
emitSucceeded();
break;
}
case LoggedProcess::Running:
emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
// send the launch script to the launcher part
m_process.write(m_launchScript.toUtf8());
{
m_parent->setPid(-1);
emitFailed("Game crashed.");
return;
}
case LoggedProcess::Finished:
{
m_parent->setPid(-1);
// if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
emitFailed("Game crashed.");
return;
}
//FIXME: make this work again
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
emitSucceeded();
break;
}
case LoggedProcess::Running:
emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
// send the launch script to the launcher part
m_process.write(m_launchScript.toUtf8());
mayProceed = true;
emit readyForLaunch();
break;
default:
break;
}
mayProceed = true;
emit readyForLaunch();
break;
default:
break;
}
}
void LauncherPartLaunch::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
m_process.setWorkingDirectory(wd);
}
void LauncherPartLaunch::proceed()
{
if(mayProceed)
{
QString launchString("launch\n");
m_process.write(launchString.toUtf8());
mayProceed = false;
}
if(mayProceed)
{
QString launchString("launch\n");
m_process.write(launchString.toUtf8());
mayProceed = false;
}
}
bool LauncherPartLaunch::abort()
{
if(mayProceed)
{
mayProceed = false;
QString launchString("abort\n");
m_process.write(launchString.toUtf8());
}
else
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
}
return true;
if(mayProceed)
{
mayProceed = false;
QString launchString("abort\n");
m_process.write(launchString.toUtf8());
}
else
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
}
return true;
}

View File

@ -21,31 +21,31 @@
class LauncherPartLaunch: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit LauncherPartLaunch(LaunchTask *parent);
virtual ~LauncherPartLaunch() {};
explicit LauncherPartLaunch(LaunchTask *parent);
virtual ~LauncherPartLaunch() {};
virtual void executeTask();
virtual bool abort();
virtual void proceed();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
void setAuthSession(AuthSessionPtr session)
{
m_session = session;
}
virtual void executeTask();
virtual bool abort();
virtual void proceed();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
void setAuthSession(AuthSessionPtr session)
{
m_session = session;
}
private slots:
void on_state(LoggedProcess::State state);
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
QString m_launchScript;
bool mayProceed = false;
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
QString m_launchScript;
bool mayProceed = false;
};

View File

@ -23,60 +23,60 @@
void ModMinecraftJar::executeTask()
{
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
if(!m_inst->getJarMods().size())
{
emitSucceeded();
return;
}
// nuke obsolete stripped jar(s) if needed
if(!FS::ensureFolderPathExists(m_inst->binRoot()))
{
emitFailed(tr("Couldn't create the bin folder for Minecraft.jar"));
}
if(!m_inst->getJarMods().size())
{
emitSucceeded();
return;
}
// nuke obsolete stripped jar(s) if needed
if(!FS::ensureFolderPathExists(m_inst->binRoot()))
{
emitFailed(tr("Couldn't create the bin folder for Minecraft.jar"));
}
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
if(!removeJar())
{
emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath));
}
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
if(!removeJar())
{
emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath));
}
// create temporary modded jar, if needed
auto components = m_inst->getComponentList();
auto profile = components->getProfile();
auto jarMods = m_inst->getJarMods();
if(jarMods.size())
{
auto mainJar = profile->getMainJar();
QStringList jars, temp1, temp2, temp3, temp4;
mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
auto sourceJarPath = jars[0];
if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
{
emitFailed(tr("Failed to create the custom Minecraft jar file."));
return;
}
}
emitSucceeded();
// create temporary modded jar, if needed
auto components = m_inst->getComponentList();
auto profile = components->getProfile();
auto jarMods = m_inst->getJarMods();
if(jarMods.size())
{
auto mainJar = profile->getMainJar();
QStringList jars, temp1, temp2, temp3, temp4;
mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
auto sourceJarPath = jars[0];
if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
{
emitFailed(tr("Failed to create the custom Minecraft jar file."));
return;
}
}
emitSucceeded();
}
void ModMinecraftJar::finalize()
{
removeJar();
removeJar();
}
bool ModMinecraftJar::removeJar()
{
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
QFile finalJar(finalJarPath);
if(finalJar.exists())
{
if(!finalJar.remove())
{
return false;
}
}
return true;
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
QFile finalJar(finalJarPath);
if(finalJar.exists())
{
if(!finalJar.remove())
{
return false;
}
}
return true;
}

View File

@ -20,17 +20,17 @@
class ModMinecraftJar: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {};
virtual ~ModMinecraftJar(){};
explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {};
virtual ~ModMinecraftJar(){};
virtual void executeTask() override;
virtual bool canAbort() const override
{
return false;
}
void finalize() override;
virtual void executeTask() override;
virtual bool canAbort() const override
{
return false;
}
void finalize() override;
private:
bool removeJar();
bool removeJar();
};

View File

@ -22,17 +22,17 @@
// FIXME: temporary wrapper for existing task.
class PrintInstanceInfo: public LaunchStep
{
Q_OBJECT
Q_OBJECT
public:
explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {};
virtual ~PrintInstanceInfo(){};
explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {};
virtual ~PrintInstanceInfo(){};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
private:
AuthSessionPtr m_session;
AuthSessionPtr m_session;
};

View File

@ -27,236 +27,236 @@
#include <FileSystem.h>
LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: BaseInstance(globalSettings, settings, rootDir)
: BaseInstance(globalSettings, settings, rootDir)
{
settings->registerSetting("NeedsRebuild", true);
settings->registerSetting("ShouldUpdate", false);
settings->registerSetting("JarVersion", QString());
settings->registerSetting("IntendedJarVersion", QString());
/*
* custom base jar has no default. it is determined in code... see the accessor methods for
*it
*
* for instances that DO NOT have the CustomBaseJar setting (legacy instances),
* [.]minecraft/bin/mcbackup.jar is the default base jar
*/
settings->registerSetting("UseCustomBaseJar", true);
settings->registerSetting("CustomBaseJar", "");
settings->registerSetting("NeedsRebuild", true);
settings->registerSetting("ShouldUpdate", false);
settings->registerSetting("JarVersion", QString());
settings->registerSetting("IntendedJarVersion", QString());
/*
* custom base jar has no default. it is determined in code... see the accessor methods for
*it
*
* for instances that DO NOT have the CustomBaseJar setting (legacy instances),
* [.]minecraft/bin/mcbackup.jar is the default base jar
*/
settings->registerSetting("UseCustomBaseJar", true);
settings->registerSetting("CustomBaseJar", "");
}
QString LegacyInstance::mainJarToPreserve() const
{
bool customJar = m_settings->get("UseCustomBaseJar").toBool();
if(customJar)
{
auto base = baseJar();
if(QFile::exists(base))
{
return base;
}
}
auto runnable = runnableJar();
if(QFile::exists(runnable))
{
return runnable;
}
return QString();
bool customJar = m_settings->get("UseCustomBaseJar").toBool();
if(customJar)
{
auto base = baseJar();
if(QFile::exists(base))
{
return base;
}
}
auto runnable = runnableJar();
if(QFile::exists(runnable))
{
return runnable;
}
return QString();
}
QString LegacyInstance::baseJar() const
{
bool customJar = m_settings->get("UseCustomBaseJar").toBool();
if (customJar)
{
return customBaseJar();
}
else
return defaultBaseJar();
bool customJar = m_settings->get("UseCustomBaseJar").toBool();
if (customJar)
{
return customBaseJar();
}
else
return defaultBaseJar();
}
QString LegacyInstance::customBaseJar() const
{
QString value = m_settings->get("CustomBaseJar").toString();
if (value.isNull() || value.isEmpty())
{
return defaultCustomBaseJar();
}
return value;
QString value = m_settings->get("CustomBaseJar").toString();
if (value.isNull() || value.isEmpty())
{
return defaultCustomBaseJar();
}
return value;
}
bool LegacyInstance::shouldUseCustomBaseJar() const
{
return m_settings->get("UseCustomBaseJar").toBool();
return m_settings->get("UseCustomBaseJar").toBool();
}
shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode)
{
return nullptr;
return nullptr;
}
std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const
{
if (!jar_mod_list)
{
auto list = new LegacyModList(jarModsDir(), modListFile());
jar_mod_list.reset(list);
}
jar_mod_list->update();
return jar_mod_list;
if (!jar_mod_list)
{
auto list = new LegacyModList(jarModsDir(), modListFile());
jar_mod_list.reset(list);
}
jar_mod_list->update();
return jar_mod_list;
}
QList<Mod> LegacyInstance::getJarMods() const
{
return jarModList()->allMods();
return jarModList()->allMods();
}
QString LegacyInstance::minecraftRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
if (mcDir.exists() && !dotMCDir.exists())
return mcDir.filePath();
else
return dotMCDir.filePath();
if (mcDir.exists() && !dotMCDir.exists())
return mcDir.filePath();
else
return dotMCDir.filePath();
}
QString LegacyInstance::binRoot() const
{
return FS::PathCombine(minecraftRoot(), "bin");
return FS::PathCombine(minecraftRoot(), "bin");
}
QString LegacyInstance::jarModsDir() const
{
return FS::PathCombine(instanceRoot(), "instMods");
return FS::PathCombine(instanceRoot(), "instMods");
}
QString LegacyInstance::libDir() const
{
return FS::PathCombine(minecraftRoot(), "lib");
return FS::PathCombine(minecraftRoot(), "lib");
}
QString LegacyInstance::savesDir() const
{
return FS::PathCombine(minecraftRoot(), "saves");
return FS::PathCombine(minecraftRoot(), "saves");
}
QString LegacyInstance::loaderModsDir() const
{
return FS::PathCombine(minecraftRoot(), "mods");
return FS::PathCombine(minecraftRoot(), "mods");
}
QString LegacyInstance::coreModsDir() const
{
return FS::PathCombine(minecraftRoot(), "coremods");
return FS::PathCombine(minecraftRoot(), "coremods");
}
QString LegacyInstance::resourceDir() const
{
return FS::PathCombine(minecraftRoot(), "resources");
return FS::PathCombine(minecraftRoot(), "resources");
}
QString LegacyInstance::texturePacksDir() const
{
return FS::PathCombine(minecraftRoot(), "texturepacks");
return FS::PathCombine(minecraftRoot(), "texturepacks");
}
QString LegacyInstance::runnableJar() const
{
return FS::PathCombine(binRoot(), "minecraft.jar");
return FS::PathCombine(binRoot(), "minecraft.jar");
}
QString LegacyInstance::modListFile() const
{
return FS::PathCombine(instanceRoot(), "modlist");
return FS::PathCombine(instanceRoot(), "modlist");
}
QString LegacyInstance::instanceConfigFolder() const
{
return FS::PathCombine(minecraftRoot(), "config");
return FS::PathCombine(minecraftRoot(), "config");
}
bool LegacyInstance::shouldRebuild() const
{
return m_settings->get("NeedsRebuild").toBool();
return m_settings->get("NeedsRebuild").toBool();
}
QString LegacyInstance::currentVersionId() const
{
return m_settings->get("JarVersion").toString();
return m_settings->get("JarVersion").toString();
}
QString LegacyInstance::intendedVersionId() const
{
return m_settings->get("IntendedJarVersion").toString();
return m_settings->get("IntendedJarVersion").toString();
}
bool LegacyInstance::shouldUpdate() const
{
QVariant var = settings()->get("ShouldUpdate");
if (!var.isValid() || var.toBool() == false)
{
return intendedVersionId() != currentVersionId();
}
return true;
QVariant var = settings()->get("ShouldUpdate");
if (!var.isValid() || var.toBool() == false)
{
return intendedVersionId() != currentVersionId();
}
return true;
}
QString LegacyInstance::defaultBaseJar() const
{
return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
}
QString LegacyInstance::defaultCustomBaseJar() const
{
return FS::PathCombine(binRoot(), "mcbackup.jar");
return FS::PathCombine(binRoot(), "mcbackup.jar");
}
std::shared_ptr<WorldList> LegacyInstance::worldList() const
{
if (!m_world_list)
{
m_world_list.reset(new WorldList(savesDir()));
}
return m_world_list;
if (!m_world_list)
{
m_world_list.reset(new WorldList(savesDir()));
}
return m_world_list;
}
QString LegacyInstance::typeName() const
{
return tr("Legacy");
return tr("Legacy");
}
QString LegacyInstance::getStatusbarDescription()
{
return tr("Instance from previous versions.");
return tr("Instance from previous versions.");
}
QStringList LegacyInstance::verboseDescription(AuthSessionPtr session)
{
QStringList out;
QStringList out;
auto alltraits = traits();
if(alltraits.size())
{
out << "Traits:";
for (auto trait : alltraits)
{
out << " " + trait;
}
out << "";
}
auto alltraits = traits();
if(alltraits.size())
{
out << "Traits:";
for (auto trait : alltraits)
{
out << " " + trait;
}
out << "";
}
QString windowParams;
if (settings()->get("LaunchMaximized").toBool())
{
out << "Window size: max (if available)";
}
else
{
auto width = settings()->get("MinecraftWinWidth").toInt();
auto height = settings()->get("MinecraftWinHeight").toInt();
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
}
out << "";
return out;
QString windowParams;
if (settings()->get("LaunchMaximized").toBool())
{
out << "Window size: max (if available)";
}
else
{
auto width = settings()->get("MinecraftWinWidth").toInt();
auto height = settings()->get("MinecraftWinHeight").toInt();
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
}
out << "";
return out;
}

View File

@ -29,115 +29,115 @@ class Task;
*/
class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance
{
Q_OBJECT
Q_OBJECT
public:
explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
virtual void init() override {}
virtual void saveNow() override {}
virtual void init() override {}
virtual void saveNow() override {}
/// Path to the instance's minecraft.jar
QString runnableJar() const;
/// Path to the instance's minecraft.jar
QString runnableJar() const;
//! Path to the instance's modlist file.
QString modListFile() const;
//! Path to the instance's modlist file.
QString modListFile() const;
////// Directories //////
QString libDir() const;
QString savesDir() const;
QString texturePacksDir() const;
QString jarModsDir() const;
QString loaderModsDir() const;
QString coreModsDir() const;
QString resourceDir() const;
virtual QString instanceConfigFolder() const override;
QString minecraftRoot() const; // Path to the instance's minecraft directory.
QString binRoot() const; // Path to the instance's minecraft bin directory.
////// Directories //////
QString libDir() const;
QString savesDir() const;
QString texturePacksDir() const;
QString jarModsDir() const;
QString loaderModsDir() const;
QString coreModsDir() const;
QString resourceDir() const;
virtual QString instanceConfigFolder() const override;
QString minecraftRoot() const; // Path to the instance's minecraft directory.
QString binRoot() const; // Path to the instance's minecraft bin directory.
/// Get the curent base jar of this instance. By default, it's the
/// versions/$version/$version.jar
QString baseJar() const;
/// Get the curent base jar of this instance. By default, it's the
/// versions/$version/$version.jar
QString baseJar() const;
/// the default base jar of this instance
QString defaultBaseJar() const;
/// the default custom base jar of this instance
QString defaultCustomBaseJar() const;
/// the default base jar of this instance
QString defaultBaseJar() const;
/// the default custom base jar of this instance
QString defaultCustomBaseJar() const;
// the main jar that we actually want to keep when migrating the instance
QString mainJarToPreserve() const;
// the main jar that we actually want to keep when migrating the instance
QString mainJarToPreserve() const;
/*!
* Whether or not custom base jar is used
*/
bool shouldUseCustomBaseJar() const;
/*!
* Whether or not custom base jar is used
*/
bool shouldUseCustomBaseJar() const;
/*!
* The value of the custom base jar
*/
QString customBaseJar() const;
/*!
* The value of the custom base jar
*/
QString customBaseJar() const;
std::shared_ptr<LegacyModList> jarModList() const;
QList<Mod> getJarMods() const;
std::shared_ptr<WorldList> worldList() const;
std::shared_ptr<LegacyModList> jarModList() const;
QList<Mod> getJarMods() const;
std::shared_ptr<WorldList> worldList() const;
/*!
* Whether or not the instance's minecraft.jar needs to be rebuilt.
* If this is true, when the instance launches, its jar mods will be
* re-added to a fresh minecraft.jar file.
*/
bool shouldRebuild() const;
/*!
* Whether or not the instance's minecraft.jar needs to be rebuilt.
* If this is true, when the instance launches, its jar mods will be
* re-added to a fresh minecraft.jar file.
*/
bool shouldRebuild() const;
QString currentVersionId() const;
QString intendedVersionId() const;
QString currentVersionId() const;
QString intendedVersionId() const;
QSet<QString> traits() const override
{
return {"legacy-instance", "texturepacks"};
};
QSet<QString> traits() const override
{
return {"legacy-instance", "texturepacks"};
};
virtual bool shouldUpdate() const;
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
virtual bool shouldUpdate() const;
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
virtual QString typeName() const override;
virtual QString typeName() const override;
bool canLaunch() const override
{
return false;
}
bool canEdit() const override
{
return true;
}
bool canExport() const override
{
return false;
}
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
{
return nullptr;
}
IPathMatcher::Ptr getLogFileMatcher() override
{
return nullptr;
}
QString getLogFileRoot() override
{
return minecraftRoot();
}
bool canLaunch() const override
{
return false;
}
bool canEdit() const override
{
return true;
}
bool canExport() const override
{
return false;
}
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
{
return nullptr;
}
IPathMatcher::Ptr getLogFileMatcher() override
{
return nullptr;
}
QString getLogFileRoot() override
{
return minecraftRoot();
}
QString getStatusbarDescription() override;
QStringList verboseDescription(AuthSessionPtr session) override;
QString getStatusbarDescription() override;
QStringList verboseDescription(AuthSessionPtr session) override;
QProcessEnvironment createEnvironment() override
{
return QProcessEnvironment();
}
QMap<QString, QString> getVariables() const override
{
return {};
}
QProcessEnvironment createEnvironment() override
{
return QProcessEnvironment();
}
QMap<QString, QString> getVariables() const override
{
return {};
}
protected:
mutable std::shared_ptr<LegacyModList> jar_mod_list;
mutable std::shared_ptr<WorldList> m_world_list;
mutable std::shared_ptr<LegacyModList> jar_mod_list;
mutable std::shared_ptr<WorldList> m_world_list;
};

View File

@ -19,153 +19,153 @@
#include <QDebug>
LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
: m_dir(dir), m_list_file(list_file)
: m_dir(dir), m_list_file(list_file)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
}
struct OrderItem
{
QString id;
bool enabled = false;
};
typedef QList<OrderItem> OrderList;
struct OrderItem
{
QString id;
bool enabled = false;
};
typedef QList<OrderItem> OrderList;
static void internalSort(QList<Mod> &what)
{
auto predicate = [](const Mod &left, const Mod &right)
{
if (left.name() == right.name())
{
return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
}
return left.name().localeAwareCompare(right.name()) < 0;
};
std::sort(what.begin(), what.end(), predicate);
auto predicate = [](const Mod &left, const Mod &right)
{
if (left.name() == right.name())
{
return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
}
return left.name().localeAwareCompare(right.name()) < 0;
};
std::sort(what.begin(), what.end(), predicate);
}
static OrderList readListFile(const QString &m_list_file)
{
OrderList itemList;
if (m_list_file.isNull() || m_list_file.isEmpty())
return itemList;
OrderList itemList;
if (m_list_file.isNull() || m_list_file.isEmpty())
return itemList;
QFile textFile(m_list_file);
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
return OrderList();
QFile textFile(m_list_file);
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
return OrderList();
QTextStream textStream;
textStream.setAutoDetectUnicode(true);
textStream.setDevice(&textFile);
while (true)
{
QString line = textStream.readLine();
if (line.isNull() || line.isEmpty())
break;
else
{
OrderItem it;
it.enabled = !line.endsWith(".disabled");
if (!it.enabled)
{
line.chop(9);
}
it.id = line;
itemList.append(it);
}
}
textFile.close();
return itemList;
QTextStream textStream;
textStream.setAutoDetectUnicode(true);
textStream.setDevice(&textFile);
while (true)
{
QString line = textStream.readLine();
if (line.isNull() || line.isEmpty())
break;
else
{
OrderItem it;
it.enabled = !line.endsWith(".disabled");
if (!it.enabled)
{
line.chop(9);
}
it.id = line;
itemList.append(it);
}
}
textFile.close();
return itemList;
}
bool LegacyModList::update()
{
if (!m_dir.exists() || !m_dir.isReadable())
return false;
if (!m_dir.exists() || !m_dir.isReadable())
return false;
QList<Mod> orderedMods;
QList<Mod> newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
bool orderOrStateChanged = false;
QList<Mod> orderedMods;
QList<Mod> newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
bool orderOrStateChanged = false;
// first, process the ordered items (if any)
OrderList listOrder = readListFile(m_list_file);
for (auto item : listOrder)
{
QFileInfo infoEnabled(m_dir.filePath(item.id));
QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
int idxEnabled = folderContents.indexOf(infoEnabled);
int idxDisabled = folderContents.indexOf(infoDisabled);
bool isEnabled;
// if both enabled and disabled versions are present, it's a special case...
if (idxEnabled >= 0 && idxDisabled >= 0)
{
// we only process the one we actually have in the order file.
// and exactly as we have it.
// THIS IS A CORNER CASE
isEnabled = item.enabled;
}
else
{
// only one is present.
// we pick the one that we found.
// we assume the mod was enabled/disabled by external means
isEnabled = idxEnabled >= 0;
}
int idx = isEnabled ? idxEnabled : idxDisabled;
QFileInfo &info = isEnabled ? infoEnabled : infoDisabled;
// if the file from the index file exists
if (idx != -1)
{
// remove from the actual folder contents list
folderContents.takeAt(idx);
// append the new mod
orderedMods.append(Mod(info));
if (isEnabled != item.enabled)
orderOrStateChanged = true;
}
else
{
orderOrStateChanged = true;
}
}
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents)
{
newMods.append(Mod(entry));
}
internalSort(newMods);
orderedMods.append(newMods);
orderOrStateChanged = true;
}
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else
for (int i = 0; i < mods.size(); i++)
{
if (!mods[i].strongCompare(orderedMods[i]))
{
orderOrStateChanged = true;
break;
}
}
}
mods.swap(orderedMods);
if (orderOrStateChanged && !m_list_file.isEmpty())
{
qDebug() << "Mod list " << m_list_file << " changed!";
}
return true;
// first, process the ordered items (if any)
OrderList listOrder = readListFile(m_list_file);
for (auto item : listOrder)
{
QFileInfo infoEnabled(m_dir.filePath(item.id));
QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
int idxEnabled = folderContents.indexOf(infoEnabled);
int idxDisabled = folderContents.indexOf(infoDisabled);
bool isEnabled;
// if both enabled and disabled versions are present, it's a special case...
if (idxEnabled >= 0 && idxDisabled >= 0)
{
// we only process the one we actually have in the order file.
// and exactly as we have it.
// THIS IS A CORNER CASE
isEnabled = item.enabled;
}
else
{
// only one is present.
// we pick the one that we found.
// we assume the mod was enabled/disabled by external means
isEnabled = idxEnabled >= 0;
}
int idx = isEnabled ? idxEnabled : idxDisabled;
QFileInfo &info = isEnabled ? infoEnabled : infoDisabled;
// if the file from the index file exists
if (idx != -1)
{
// remove from the actual folder contents list
folderContents.takeAt(idx);
// append the new mod
orderedMods.append(Mod(info));
if (isEnabled != item.enabled)
orderOrStateChanged = true;
}
else
{
orderOrStateChanged = true;
}
}
// if there are any untracked files...
if (folderContents.size())
{
// the order surely changed!
for (auto entry : folderContents)
{
newMods.append(Mod(entry));
}
internalSort(newMods);
orderedMods.append(newMods);
orderOrStateChanged = true;
}
// otherwise, if we were already tracking some mods
else if (mods.size())
{
// if the number doesn't match, order changed.
if (mods.size() != orderedMods.size())
orderOrStateChanged = true;
// if it does match, compare the mods themselves
else
for (int i = 0; i < mods.size(); i++)
{
if (!mods[i].strongCompare(orderedMods[i]))
{
orderOrStateChanged = true;
break;
}
}
}
mods.swap(orderedMods);
if (orderOrStateChanged && !m_list_file.isEmpty())
{
qDebug() << "Mod list " << m_list_file << " changed!";
}
return true;
}

View File

@ -34,23 +34,23 @@ class MULTIMC_LOGIC_EXPORT LegacyModList
{
public:
LegacyModList(const QString &dir, const QString &list_file = QString());
LegacyModList(const QString &dir, const QString &list_file = QString());
/// Reloads the mod list and returns true if the list changed.
bool update();
/// Reloads the mod list and returns true if the list changed.
bool update();
QDir dir()
{
return m_dir;
}
QDir dir()
{
return m_dir;
}
const QList<Mod> & allMods()
{
return mods;
}
const QList<Mod> & allMods()
{
return mods;
}
protected:
QDir m_dir;
QString m_list_file;
QList<Mod> mods;
QDir m_dir;
QString m_list_file;
QList<Mod> mods;
};

View File

@ -12,128 +12,128 @@
LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance)
{
m_origInstance = origInstance;
m_origInstance = origInstance;
}
void LegacyUpgradeTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(true);
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(true);
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
static QString decideVersion(const QString& currentVersion, const QString& intendedVersion)
{
if(intendedVersion != currentVersion)
{
if(!intendedVersion.isEmpty())
{
return intendedVersion;
}
else if(!currentVersion.isEmpty())
{
return currentVersion;
}
}
else
{
if(!intendedVersion.isEmpty())
{
return intendedVersion;
}
}
return QString();
if(intendedVersion != currentVersion)
{
if(!intendedVersion.isEmpty())
{
return intendedVersion;
}
else if(!currentVersion.isEmpty())
{
return currentVersion;
}
}
else
{
if(!intendedVersion.isEmpty())
{
return intendedVersion;
}
}
return QString();
}
void LegacyUpgradeTask::copyFinished()
{
auto successful = m_copyFuture.result();
if(!successful)
{
emitFailed(tr("Instance folder copy failed."));
return;
}
auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance);
auto successful = m_copyFuture.result();
if(!successful)
{
emitFailed(tr("Instance folder copy failed."));
return;
}
auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
// NOTE: this scope ensures the instance is fully saved before we emitSucceeded
{
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
inst.setName(m_instName);
inst.init();
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
// NOTE: this scope ensures the instance is fully saved before we emitSucceeded
{
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
inst.setName(m_instName);
inst.init();
QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
if(preferredVersionNumber.isNull())
{
// try to decide version based on the jar(s?)
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
if(preferredVersionNumber.isNull())
{
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
if(preferredVersionNumber.isNull())
{
emitFailed(tr("Could not decide Minecraft version."));
return;
}
}
}
auto components = inst.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
if(preferredVersionNumber.isNull())
{
// try to decide version based on the jar(s?)
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
if(preferredVersionNumber.isNull())
{
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
if(preferredVersionNumber.isNull())
{
emitFailed(tr("Could not decide Minecraft version."));
return;
}
}
}
auto components = inst.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
QString jarPath = legacyInst->mainJarToPreserve();
if(!jarPath.isNull())
{
qDebug() << "Preserving base jar! : " << jarPath;
// FIXME: handle case when the jar is unreadable?
// TODO: check the hash, if it's the same as the upstream jar, do not do this
components->installCustomJar(jarPath);
}
QString jarPath = legacyInst->mainJarToPreserve();
if(!jarPath.isNull())
{
qDebug() << "Preserving base jar! : " << jarPath;
// FIXME: handle case when the jar is unreadable?
// TODO: check the hash, if it's the same as the upstream jar, do not do this
components->installCustomJar(jarPath);
}
auto jarMods = legacyInst->getJarMods();
for(auto & jarMod: jarMods)
{
QString modPath = jarMod.filename().absoluteFilePath();
qDebug() << "jarMod: " << modPath;
components->installJarMods({modPath});
}
auto jarMods = legacyInst->getJarMods();
for(auto & jarMod: jarMods)
{
QString modPath = jarMod.filename().absoluteFilePath();
qDebug() << "jarMod: " << modPath;
components->installJarMods({modPath});
}
// remove all the extra garbage we no longer need
auto removeAll = [&](const QString &root, const QStringList &things)
{
for(auto &thing : things)
{
auto removePath = FS::PathCombine(root, thing);
QFileInfo stat(removePath);
if(stat.isDir())
{
FS::deletePath(removePath);
}
else
{
QFile::remove(removePath);
}
}
};
QStringList rootRemovables = {"modlist", "version", "instMods"};
QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
removeAll(inst.instanceRoot(), rootRemovables);
removeAll(inst.minecraftRoot(), mcRemovables);
}
emitSucceeded();
// remove all the extra garbage we no longer need
auto removeAll = [&](const QString &root, const QStringList &things)
{
for(auto &thing : things)
{
auto removePath = FS::PathCombine(root, thing);
QFileInfo stat(removePath);
if(stat.isDir())
{
FS::deletePath(removePath);
}
else
{
QFile::remove(removePath);
}
}
};
QStringList rootRemovables = {"modlist", "version", "instMods"};
QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
removeAll(inst.instanceRoot(), rootRemovables);
removeAll(inst.minecraftRoot(), mcRemovables);
}
emitSucceeded();
}
void LegacyUpgradeTask::copyAborted()
{
emitFailed(tr("Instance folder copy has been aborted."));
return;
emitFailed(tr("Instance folder copy has been aborted."));
return;
}

View File

@ -15,18 +15,18 @@ class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask
{
Q_OBJECT
Q_OBJECT
public:
explicit LegacyUpgradeTask(InstancePtr origInstance);
explicit LegacyUpgradeTask(InstancePtr origInstance);
protected:
//! Entry point for tasks.
virtual void executeTask() override;
void copyFinished();
void copyAborted();
//! Entry point for tasks.
virtual void executeTask() override;
void copyFinished();
void copyAborted();
private: /* data */
InstancePtr m_origInstance;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
InstancePtr m_origInstance;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
};

View File

@ -1,46 +1,46 @@
{
"downloads": {
"classifiers": {
"natives-osx": {
"path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar",
"sha1": "62503ee712766cf77f97252e5902786fd834b8c5",
"size": 418331,
"url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar"
},
"natives-windows-32": {
"path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar",
"sha1": "7c6affe439099806a4f552da14c42f9d643d8b23",
"size": 386792,
"url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"
},
"natives-windows-64": {
"path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar",
"sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9",
"size": 463390,
"url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"
}
}
},
"extract": {
"exclude": [
"META-INF/"
]
},
"name": "tv.twitch:twitch-platform:5.16",
"natives": {
"linux": "natives-linux",
"osx": "natives-osx",
"windows": "natives-windows-${arch}"
},
"rules": [
{
"action": "allow"
},
{
"action": "disallow",
"os": {
"name": "linux"
}
}
]
"downloads": {
"classifiers": {
"natives-osx": {
"path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar",
"sha1": "62503ee712766cf77f97252e5902786fd834b8c5",
"size": 418331,
"url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar"
},
"natives-windows-32": {
"path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar",
"sha1": "7c6affe439099806a4f552da14c42f9d643d8b23",
"size": 386792,
"url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar"
},
"natives-windows-64": {
"path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar",
"sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9",
"size": 463390,
"url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar"
}
}
},
"extract": {
"exclude": [
"META-INF/"
]
},
"name": "tv.twitch:twitch-platform:5.16",
"natives": {
"linux": "natives-linux",
"osx": "natives-osx",
"windows": "natives-windows-${arch}"
},
"rules": [
{
"action": "allow"
},
{
"action": "disallow",
"os": {
"name": "linux"
}
}
]
}

View File

@ -1,52 +1,52 @@
{
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar",
"sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33",
"size": 22,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"
},
"classifiers": {
"natives-linux": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar",
"sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b",
"size": 578680,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar"
},
"natives-osx": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar",
"sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed",
"size": 426822,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"
},
"natives-windows": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar",
"sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0",
"size": 613748,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar"
}
}
},
"extract": {
"exclude": [
"META-INF/"
]
},
"name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
"natives": {
"linux": "natives-linux",
"osx": "natives-osx",
"windows": "natives-windows"
},
"rules": [
{
"action": "allow"
},
{
"action": "disallow",
"os": {
"name": "osx"
}
}
]
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar",
"sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33",
"size": 22,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"
},
"classifiers": {
"natives-linux": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar",
"sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b",
"size": 578680,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar"
},
"natives-osx": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar",
"sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed",
"size": 426822,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"
},
"natives-windows": {
"path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar",
"sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0",
"size": 613748,
"url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar"
}
}
},
"extract": {
"exclude": [
"META-INF/"
]
},
"name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
"natives": {
"linux": "natives-linux",
"osx": "natives-osx",
"windows": "natives-windows"
},
"rules": [
{
"action": "allow"
},
{
"action": "disallow",
"os": {
"name": "osx"
}
}
]
}

View File

@ -1,11 +1,11 @@
{
"downloads": {
"artifact": {
"path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar",
"sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da",
"size": 5618,
"url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"
}
},
"name": "com.paulscode:codecwav:20101023"
"downloads": {
"artifact": {
"path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar",
"sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da",
"size": 5618,
"url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar"
}
},
"name": "com.paulscode:codecwav:20101023"
}

View File

@ -7,7 +7,7 @@
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
{
m_inst = inst;
m_inst = inst;
}
AssetUpdateTask::~AssetUpdateTask()
@ -16,92 +16,92 @@ AssetUpdateTask::~AssetUpdateTask()
void AssetUpdateTask::executeTask()
{
setStatus(tr("Updating assets index..."));
auto components = m_inst->getComponentList();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QUrl indexUrl = assets->url;
QString localPath = assets->id + ".json";
auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name()));
setStatus(tr("Updating assets index..."));
auto components = m_inst->getComponentList();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QUrl indexUrl = assets->url;
QString localPath = assets->id + ".json";
auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name()));
auto metacache = ENV.metacache();
auto entry = metacache->resolveEntry("asset_indexes", localPath);
entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1();
qDebug() << "Asset index SHA1:" << hexSha1;
auto dl = Net::Download::makeCached(indexUrl, entry);
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl);
auto metacache = ENV.metacache();
auto entry = metacache->resolveEntry("asset_indexes", localPath);
entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1();
qDebug() << "Asset index SHA1:" << hexSha1;
auto dl = Net::Download::makeCached(indexUrl, entry);
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl);
downloadJob.reset(job);
downloadJob.reset(job);
connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished);
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished);
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
qDebug() << m_inst->name() << ": Starting asset index download";
downloadJob->start();
qDebug() << m_inst->name() << ": Starting asset index download";
downloadJob->start();
}
bool AssetUpdateTask::canAbort() const
{
return true;
return true;
}
void AssetUpdateTask::assetIndexFinished()
{
AssetsIndex index;
qDebug() << m_inst->name() << ": Finished asset index download";
AssetsIndex index;
qDebug() << m_inst->name() << ": Finished asset index download";
auto components = m_inst->getComponentList();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
auto components = m_inst->getComponentList();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QString asset_fname = "assets/indexes/" + assets->id + ".json";
// FIXME: this looks like a job for a generic validator based on json schema?
if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index))
{
auto metacache = ENV.metacache();
auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json");
metacache->evictEntry(entry);
emitFailed(tr("Failed to read the assets index!"));
}
QString asset_fname = "assets/indexes/" + assets->id + ".json";
// FIXME: this looks like a job for a generic validator based on json schema?
if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index))
{
auto metacache = ENV.metacache();
auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json");
metacache->evictEntry(entry);
emitFailed(tr("Failed to read the assets index!"));
}
auto job = index.getDownloadJob();
if(job)
{
setStatus(tr("Getting the assets files from Mojang..."));
downloadJob = job;
connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
downloadJob->start();
return;
}
emitSucceeded();
auto job = index.getDownloadJob();
if(job)
{
setStatus(tr("Getting the assets files from Mojang..."));
downloadJob = job;
connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
downloadJob->start();
return;
}
emitSucceeded();
}
void AssetUpdateTask::assetIndexFailed(QString reason)
{
qDebug() << m_inst->name() << ": Failed asset index download";
emitFailed(tr("Failed to download the assets index:\n%1").arg(reason));
qDebug() << m_inst->name() << ": Failed asset index download";
emitFailed(tr("Failed to download the assets index:\n%1").arg(reason));
}
void AssetUpdateTask::assetsFailed(QString reason)
{
emitFailed(tr("Failed to download assets:\n%1").arg(reason));
emitFailed(tr("Failed to download assets:\n%1").arg(reason));
}
bool AssetUpdateTask::abort()
{
if(downloadJob)
{
return downloadJob->abort();
}
else
{
qWarning() << "Prematurely aborted FMLLibrariesTask";
}
return true;
if(downloadJob)
{
return downloadJob->abort();
}
else
{
qWarning() << "Prematurely aborted FMLLibrariesTask";
}
return true;
}

View File

@ -5,24 +5,24 @@ class MinecraftInstance;
class AssetUpdateTask : public Task
{
Q_OBJECT
Q_OBJECT
public:
AssetUpdateTask(MinecraftInstance * inst);
virtual ~AssetUpdateTask();
AssetUpdateTask(MinecraftInstance * inst);
virtual ~AssetUpdateTask();
void executeTask() override;
void executeTask() override;
bool canAbort() const override;
bool canAbort() const override;
private slots:
void assetIndexFinished();
void assetIndexFailed(QString reason);
void assetsFailed(QString reason);
void assetIndexFinished();
void assetIndexFailed(QString reason);
void assetsFailed(QString reason);
public slots:
bool abort() override;
bool abort() override;
private:
MinecraftInstance *m_inst;
NetJobPtr downloadJob;
MinecraftInstance *m_inst;
NetJobPtr downloadJob;
};

View File

@ -7,125 +7,125 @@
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
{
m_inst = inst;
m_inst = inst;
}
void FMLLibrariesTask::executeTask()
{
// Get the mod list
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
auto components = inst->getComponentList();
auto profile = components->getProfile();
// Get the mod list
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
auto components = inst->getComponentList();
auto profile = components->getProfile();
if (!profile->hasTrait("legacyFML"))
{
emitSucceeded();
return;
}
if (!profile->hasTrait("legacyFML"))
{
emitSucceeded();
return;
}
QString version = components->getComponentVersion("net.minecraft");
auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
if (!fmlLibsMapping.contains(version))
{
emitSucceeded();
return;
}
QString version = components->getComponentVersion("net.minecraft");
auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
if (!fmlLibsMapping.contains(version))
{
emitSucceeded();
return;
}
auto &libList = fmlLibsMapping[version];
auto &libList = fmlLibsMapping[version];
// determine if we need some libs for FML or forge
setStatus(tr("Checking for FML libraries..."));
if(!components->getComponent("net.minecraftforge"))
{
emitSucceeded();
return;
}
// determine if we need some libs for FML or forge
setStatus(tr("Checking for FML libraries..."));
if(!components->getComponent("net.minecraftforge"))
{
emitSucceeded();
return;
}
// now check the lib folder inside the instance for files.
for (auto &lib : libList)
{
QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename));
if (libInfo.exists())
continue;
fmlLibsToProcess.append(lib);
}
// now check the lib folder inside the instance for files.
for (auto &lib : libList)
{
QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename));
if (libInfo.exists())
continue;
fmlLibsToProcess.append(lib);
}
// if everything is in place, there's nothing to do here...
if (fmlLibsToProcess.isEmpty())
{
emitSucceeded();
return;
}
// if everything is in place, there's nothing to do here...
if (fmlLibsToProcess.isEmpty())
{
emitSucceeded();
return;
}
// download missing libs to our place
setStatus(tr("Dowloading FML libraries..."));
auto dljob = new NetJob("FML libraries");
auto metacache = ENV.metacache();
for (auto &lib : fmlLibsToProcess)
{
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
: URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry));
}
// download missing libs to our place
setStatus(tr("Dowloading FML libraries..."));
auto dljob = new NetJob("FML libraries");
auto metacache = ENV.metacache();
for (auto &lib : fmlLibsToProcess)
{
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
: URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry));
}
connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress);
downloadJob.reset(dljob);
downloadJob->start();
connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress);
downloadJob.reset(dljob);
downloadJob->start();
}
bool FMLLibrariesTask::canAbort() const
{
return true;
return true;
}
void FMLLibrariesTask::fmllibsFinished()
{
downloadJob.reset();
if (!fmlLibsToProcess.isEmpty())
{
setStatus(tr("Copying FML libraries into the instance..."));
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
auto metacache = ENV.metacache();
int index = 0;
for (auto &lib : fmlLibsToProcess)
{
progress(index, fmlLibsToProcess.size());
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
auto path = FS::PathCombine(inst->libDir(), lib.filename);
if (!FS::ensureFilePathExists(path))
{
emitFailed(tr("Failed creating FML library folder inside the instance."));
return;
}
if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename)))
{
emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
return;
}
index++;
}
progress(index, fmlLibsToProcess.size());
}
emitSucceeded();
downloadJob.reset();
if (!fmlLibsToProcess.isEmpty())
{
setStatus(tr("Copying FML libraries into the instance..."));
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
auto metacache = ENV.metacache();
int index = 0;
for (auto &lib : fmlLibsToProcess)
{
progress(index, fmlLibsToProcess.size());
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
auto path = FS::PathCombine(inst->libDir(), lib.filename);
if (!FS::ensureFilePathExists(path))
{
emitFailed(tr("Failed creating FML library folder inside the instance."));
return;
}
if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename)))
{
emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
return;
}
index++;
}
progress(index, fmlLibsToProcess.size());
}
emitSucceeded();
}
void FMLLibrariesTask::fmllibsFailed(QString reason)
{
QStringList failed = downloadJob->getFailedFiles();
QString failed_all = failed.join("\n");
emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason));
QStringList failed = downloadJob->getFailedFiles();
QString failed_all = failed.join("\n");
emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason));
}
bool FMLLibrariesTask::abort()
{
if(downloadJob)
{
return downloadJob->abort();
}
else
{
qWarning() << "Prematurely aborted FMLLibrariesTask";
}
return true;
if(downloadJob)
{
return downloadJob->abort();
}
else
{
qWarning() << "Prematurely aborted FMLLibrariesTask";
}
return true;
}

View File

@ -7,25 +7,25 @@ class MinecraftInstance;
class FMLLibrariesTask : public Task
{
Q_OBJECT
Q_OBJECT
public:
FMLLibrariesTask(MinecraftInstance * inst);
virtual ~FMLLibrariesTask() {};
FMLLibrariesTask(MinecraftInstance * inst);
virtual ~FMLLibrariesTask() {};
void executeTask() override;
void executeTask() override;
bool canAbort() const override;
bool canAbort() const override;
private slots:
void fmllibsFinished();
void fmllibsFailed(QString reason);
void fmllibsFinished();
void fmllibsFailed(QString reason);
public slots:
bool abort() override;
bool abort() override;
private:
MinecraftInstance *m_inst;
NetJobPtr downloadJob;
QList<FMLlib> fmlLibsToProcess;
MinecraftInstance *m_inst;
NetJobPtr downloadJob;
QList<FMLlib> fmlLibsToProcess;
};

View File

@ -3,19 +3,19 @@
#include <QDir>
FoldersTask::FoldersTask(MinecraftInstance * inst)
:Task()
:Task()
{
m_inst = inst;
m_inst = inst;
}
void FoldersTask::executeTask()
{
// Make directories
QDir mcDir(m_inst->minecraftRoot());
if (!mcDir.exists() && !mcDir.mkpath("."))
{
emitFailed(tr("Failed to create folder for minecraft binaries."));
return;
}
emitSucceeded();
// Make directories
QDir mcDir(m_inst->minecraftRoot());
if (!mcDir.exists() && !mcDir.mkpath("."))
{
emitFailed(tr("Failed to create folder for minecraft binaries."));
return;
}
emitSucceeded();
}

View File

@ -5,13 +5,13 @@
class MinecraftInstance;
class FoldersTask : public Task
{
Q_OBJECT
Q_OBJECT
public:
FoldersTask(MinecraftInstance * inst);
virtual ~FoldersTask() {};
FoldersTask(MinecraftInstance * inst);
virtual ~FoldersTask() {};
void executeTask() override;
void executeTask() override;
private:
MinecraftInstance *m_inst;
MinecraftInstance *m_inst;
};

View File

@ -5,86 +5,86 @@
LibrariesTask::LibrariesTask(MinecraftInstance * inst)
{
m_inst = inst;
m_inst = inst;
}
void LibrariesTask::executeTask()
{
setStatus(tr("Getting the library files from Mojang..."));
qDebug() << m_inst->name() << ": downloading libraries";
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
setStatus(tr("Getting the library files from Mojang..."));
qDebug() << m_inst->name() << ": downloading libraries";
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
// Build a list of URLs that will need to be downloaded.
auto components = inst->getComponentList();
auto profile = components->getProfile();
// Build a list of URLs that will need to be downloaded.
auto components = inst->getComponentList();
auto profile = components->getProfile();
auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()));
downloadJob.reset(job);
auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()));
downloadJob.reset(job);
auto metacache = ENV.metacache();
QList<LibraryPtr> brokenLocalLibs;
QStringList failedFiles;
auto createJob = [&](const LibraryPtr & lib)
{
if(!lib)
{
emitFailed(tr("Null jar is specified in the metadata, aborting."));
return;
}
auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles, inst->getLocalLibraryPath());
for(auto dl : dls)
{
downloadJob->addNetAction(dl);
}
};
auto createJobs = [&](const QList<LibraryPtr> & libs)
{
for (auto lib : libs)
{
createJob(lib);
}
};
createJobs(profile->getLibraries());
createJobs(profile->getNativeLibraries());
createJobs(profile->getJarMods());
createJob(profile->getMainJar());
auto metacache = ENV.metacache();
QList<LibraryPtr> brokenLocalLibs;
QStringList failedFiles;
auto createJob = [&](const LibraryPtr & lib)
{
if(!lib)
{
emitFailed(tr("Null jar is specified in the metadata, aborting."));
return;
}
auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles, inst->getLocalLibraryPath());
for(auto dl : dls)
{
downloadJob->addNetAction(dl);
}
};
auto createJobs = [&](const QList<LibraryPtr> & libs)
{
for (auto lib : libs)
{
createJob(lib);
}
};
createJobs(profile->getLibraries());
createJobs(profile->getNativeLibraries());
createJobs(profile->getJarMods());
createJob(profile->getMainJar());
// FIXME: this is never filled!!!!
if (!brokenLocalLibs.empty())
{
downloadJob.reset();
QString failed_all = failedFiles.join("\n");
emitFailed(tr("Some libraries marked as 'local' are missing their jar "
"files:\n%1\n\nYou'll have to correct this problem manually. If this is "
"an externally tracked instance, make sure to run it at least once "
"outside of MultiMC.").arg(failed_all));
return;
}
connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
downloadJob->start();
// FIXME: this is never filled!!!!
if (!brokenLocalLibs.empty())
{
downloadJob.reset();
QString failed_all = failedFiles.join("\n");
emitFailed(tr("Some libraries marked as 'local' are missing their jar "
"files:\n%1\n\nYou'll have to correct this problem manually. If this is "
"an externally tracked instance, make sure to run it at least once "
"outside of MultiMC.").arg(failed_all));
return;
}
connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
downloadJob->start();
}
bool LibrariesTask::canAbort() const
{
return true;
return true;
}
void LibrariesTask::jarlibFailed(QString reason)
{
emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason));
emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason));
}
bool LibrariesTask::abort()
{
if(downloadJob)
{
return downloadJob->abort();
}
else
{
qWarning() << "Prematurely aborted LibrariesTask";
}
return true;
if(downloadJob)
{
return downloadJob->abort();
}
else
{
qWarning() << "Prematurely aborted LibrariesTask";
}
return true;
}

View File

@ -5,22 +5,22 @@ class MinecraftInstance;
class LibrariesTask : public Task
{
Q_OBJECT
Q_OBJECT
public:
LibrariesTask(MinecraftInstance * inst);
virtual ~LibrariesTask() {};
LibrariesTask(MinecraftInstance * inst);
virtual ~LibrariesTask() {};
void executeTask() override;
void executeTask() override;
bool canAbort() const override;
bool canAbort() const override;
private slots:
void jarlibFailed(QString reason);
void jarlibFailed(QString reason);
public slots:
bool abort() override;
bool abort() override;
private:
MinecraftInstance *m_inst;
NetJobPtr downloadJob;
MinecraftInstance *m_inst;
NetJobPtr downloadJob;
};