NOISSUE tabs -> spaces
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
@ -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;
|
||||
};
|
||||
|
@ -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
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
@ -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;
|
||||
};
|
||||
|
@ -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"};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
100
api/logic/minecraft/testdata/lib-native.json
vendored
100
api/logic/minecraft/testdata/lib-native.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
18
api/logic/minecraft/testdata/lib-simple.json
vendored
18
api/logic/minecraft/testdata/lib-simple.json
vendored
@ -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"
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
Reference in New Issue
Block a user