NOISSUE split GUI stuff from logic library

This commit is contained in:
Petr Mrázek
2016-04-10 04:29:29 +02:00
parent fcd4a482f7
commit 47e37635f5
286 changed files with 126 additions and 132 deletions

View File

@ -0,0 +1,28 @@
project(MultiMC_logic)
set(GUI_SOURCES
DesktopServices.h
DesktopServices.cpp
# Icons
icons/MMCIcon.h
icons/MMCIcon.cpp
icons/IconList.h
icons/IconList.cpp
SkinUtils.cpp
SkinUtils.h
)
################################ COMPILE ################################
add_library(MultiMC_gui SHARED ${GUI_SOURCES})
set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_gui)
# Link
target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
qt5_use_modules(MultiMC_gui Gui)
# Mark and export headers
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")

View File

@ -0,0 +1,149 @@
#include "DesktopServices.h"
#include <QDir>
#include <QDesktopServices>
#include <QProcess>
#include <QDebug>
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
*/
#if defined(Q_OS_LINUX)
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
template <typename T>
bool IndirectOpen(T callable, qint64 *pid_forked = nullptr)
{
auto pid = fork();
if(pid_forked)
{
if(pid > 0)
*pid_forked = pid;
else
*pid_forked = 0;
}
if(pid == -1)
{
qWarning() << "IndirectOpen failed to fork: " << errno;
return false;
}
// child - do the stuff
if(pid == 0)
{
// unset all this garbage so it doesn't get passed to the child process
qunsetenv("LD_PRELOAD");
qunsetenv("LD_LIBRARY_PATH");
qunsetenv("LD_DEBUG");
qunsetenv("QT_PLUGIN_PATH");
qunsetenv("QT_FONTPATH");
// open the URL
auto status = callable();
// detach from the parent process group.
setsid();
// die. now. do not clean up anything, it would just hang forever.
_exit(status ? 0 : 1);
}
else
{
//parent - assume it worked.
int status;
while (waitpid(pid, &status, 0))
{
if(WIFEXITED(status))
{
return WEXITSTATUS(status) == 0;
}
if(WIFSIGNALED(status))
{
return false;
}
}
return true;
}
}
#endif
namespace DesktopServices {
bool openDirectory(const QString &path, bool ensureExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (!dir.exists())
{
parentPath.mkpath(dir.absolutePath());
}
auto f = [&]()
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
};
#if defined(Q_OS_LINUX)
return IndirectOpen(f);
#else
return f();
#endif
}
bool openFile(const QString &path)
{
qDebug() << "Opening file" << path;
auto f = [&]()
{
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
};
#if defined(Q_OS_LINUX)
return IndirectOpen(f);
#else
return f();
#endif
}
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
{
qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]()
{
return QProcess::startDetached(application, QStringList() << path, workingDirectory);
}, pid);
#else
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
#endif
}
bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid)
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]()
{
return QProcess::startDetached(application, args, workingDirectory);
}, pid);
#else
return QProcess::startDetached(application, args, workingDirectory, pid);
#endif
}
bool openUrl(const QUrl &url)
{
qDebug() << "Opening URL" << url.toString();
auto f = [&]()
{
return QDesktopServices::openUrl(url);
};
#if defined(Q_OS_LINUX)
return IndirectOpen(f);
#else
return f();
#endif
}
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <QUrl>
#include <QString>
#include "multimc_gui_export.h"
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices
{
/**
* Open a file in whatever application is applicable
*/
MULTIMC_GUI_EXPORT bool openFile(const QString &path);
/**
* Open a file in the specified application
*/
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Run an application
*/
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Open a directory
*/
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
/**
* Open the URL, most likely in a browser. Maybe.
*/
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
};

View File

@ -0,0 +1,47 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SkinUtils.h"
#include "net/HttpMetaCache.h"
#include "Env.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
namespace SkinUtils
{
/*
* Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise
*/
QPixmap getFaceFromCache(QString username, int height, int width)
{
QFile fskin(ENV.metacache()
->resolveEntry("skins", username + ".png")
->getFullPath());
if (fskin.exists())
{
QPixmap skin(fskin.fileName());
if(!skin.isNull())
{
return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
}
}
return QPixmap();
}
}

25
libraries/gui/SkinUtils.h Normal file
View File

@ -0,0 +1,25 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QPixmap>
#include "multimc_gui_export.h"
namespace SkinUtils
{
QPixmap MULTIMC_GUI_EXPORT getFaceFromCache(QString id, int height = 64, int width = 64);
}

View File

@ -0,0 +1,381 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "IconList.h"
#include <FileSystem.h>
#include <QMap>
#include <QEventLoop>
#include <QMimeData>
#include <QUrl>
#include <QFileSystemWatcher>
#include <QSet>
#include <QDebug>
#define MAX_SIZE 1024
IconList::IconList(QString builtinPath, QString path, QObject *parent) : QAbstractListModel(parent)
{
// add builtin icons
QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
QString key = file_info.baseName();
addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin);
}
m_watcher.reset(new QFileSystemWatcher());
is_watching = false;
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
SLOT(directoryChanged(QString)));
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
directoryChanged(path);
}
void IconList::directoryChanged(const QString &path)
{
QDir new_dir (path);
if(m_dir.absolutePath() != new_dir.absolutePath())
{
m_dir.setPath(path);
m_dir.refresh();
if(is_watching)
stopWatching();
startWatching();
}
if(!m_dir.exists())
if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
for (auto it = new_list.begin(); it != new_list.end(); it++)
{
QString &foo = (*it);
foo = m_dir.filePath(foo);
}
auto new_set = new_list.toSet();
QList<QString> current_list;
for (auto &it : icons)
{
if (!it.has(MMCIcon::FileBased))
continue;
current_list.push_back(it.m_images[MMCIcon::FileBased].filename);
}
QSet<QString> current_set = current_list.toSet();
QSet<QString> to_remove = current_set;
to_remove -= new_set;
QSet<QString> to_add = new_set;
to_add -= current_set;
for (auto remove : to_remove)
{
qDebug() << "Removing " << remove;
QFileInfo rmfile(remove);
QString key = rmfile.baseName();
int idx = getIconIndex(key);
if (idx == -1)
continue;
icons[idx].remove(MMCIcon::FileBased);
if (icons[idx].type() == MMCIcon::ToBeDeleted)
{
beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx);
reindex();
endRemoveRows();
}
else
{
dataChanged(index(idx), index(idx));
}
m_watcher->removePath(remove);
emit iconUpdated(key);
}
for (auto add : to_add)
{
qDebug() << "Adding " << add;
QFileInfo addfile(add);
QString key = addfile.baseName();
if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased))
{
m_watcher->addPath(add);
emit iconUpdated(key);
}
}
}
void IconList::fileChanged(const QString &path)
{
qDebug() << "Checking " << path;
QFileInfo checkfile(path);
if (!checkfile.exists())
return;
QString key = checkfile.baseName();
int idx = getIconIndex(key);
if (idx == -1)
return;
QIcon icon(path);
if (!icon.availableSizes().size())
return;
icons[idx].m_images[MMCIcon::FileBased].icon = icon;
dataChanged(index(idx), index(idx));
emit iconUpdated(key);
}
void IconList::SettingChanged(const Setting &setting, QVariant value)
{
if(setting.id() != "IconsDir")
return;
directoryChanged(value.toString());
}
void IconList::startWatching()
{
auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path);
if (is_watching)
{
qDebug() << "Started watching " << abs_path;
}
else
{
qDebug() << "Failed to start watching " << abs_path;
}
}
void IconList::stopWatching()
{
m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories());
is_watching = false;
}
QStringList IconList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
}
Qt::DropActions IconList::supportedDropActions() const
{
return Qt::CopyAction;
}
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
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())
{
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls)
{
// only local files may be dropped...
if (!url.isLocalFile())
continue;
iconFiles += url.toLocalFile();
}
installIcons(iconFiles);
return true;
}
return false;
}
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
QVariant IconList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
if (row < 0 || row >= icons.size())
return QVariant();
switch (role)
{
case Qt::DecorationRole:
return icons[row].icon();
case Qt::DisplayRole:
return icons[row].name();
case Qt::UserRole:
return icons[row].m_key;
default:
return QVariant();
}
}
int IconList::rowCount(const QModelIndex &parent) const
{
return icons.size();
}
void IconList::installIcons(QStringList iconFiles)
{
for (QString file : iconFiles)
{
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
continue;
if (!QFile::copy(file, target))
continue;
}
}
bool IconList::iconFileExists(QString key)
{
auto iconEntry = icon(key);
if(!iconEntry)
{
return false;
}
return iconEntry->has(MMCIcon::FileBased);
}
const MMCIcon *IconList::icon(QString key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return nullptr;
return &icons[iconIdx];
}
bool IconList::deleteIcon(QString key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return false;
auto &iconEntry = icons[iconIdx];
if (iconEntry.has(MMCIcon::FileBased))
{
return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename);
}
return false;
}
bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type)
{
// replace the icon even? is the input valid?
QIcon icon(path);
if (!icon.availableSizes().size())
return false;
auto iter = name_index.find(key);
if (iter != name_index.end())
{
auto &oldOne = icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
}
void IconList::reindex()
{
name_index.clear();
int i = 0;
for (auto &iter : icons)
{
name_index[iter.m_key] = i;
i++;
}
}
QIcon IconList::getIcon(QString key)
{
int icon_index = getIconIndex(key);
if (icon_index != -1)
return icons[icon_index].icon();
// Fallback for icons that don't exist.
icon_index = getIconIndex("infinity");
if (icon_index != -1)
return icons[icon_index].icon();
return QIcon();
}
QIcon IconList::getBigIcon(QString key)
{
int icon_index = getIconIndex(key);
if (icon_index == -1)
key = "infinity";
// Fallback for icons that don't exist.
icon_index = getIconIndex(key);
if (icon_index == -1)
return QIcon();
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
return QIcon(bigone);
}
int IconList::getIconIndex(QString key)
{
if (key == "default")
key = "infinity";
auto iter = name_index.find(key);
if (iter != name_index.end())
return *iter;
return -1;
}
//#include "IconList.moc"

View File

@ -0,0 +1,85 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QMutex>
#include <QAbstractListModel>
#include <QFile>
#include <QDir>
#include <QtGui/QIcon>
#include <memory>
#include "MMCIcon.h"
#include "settings/Setting.h"
#include "Env.h" // there is a global icon list inside Env.
#include "multimc_logic_export.h"
class QFileSystemWatcher;
class MULTIMC_LOGIC_EXPORT IconList : public QAbstractListModel
{
Q_OBJECT
public:
explicit IconList(QString builtinPath, QString path, QObject *parent = 0);
virtual ~IconList() {};
QIcon getIcon(QString key);
QIcon getBigIcon(QString key);
int getIconIndex(QString key);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
bool addIcon(QString key, QString name, QString path, MMCIcon::Type type);
bool deleteIcon(QString key);
bool iconFileExists(QString key);
virtual QStringList mimeTypes() const;
virtual Qt::DropActions supportedDropActions() const;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent);
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
void installIcons(QStringList iconFiles);
const MMCIcon * icon(QString key);
void startWatching();
void stopWatching();
signals:
void iconUpdated(QString key);
private:
// hide copy constructor
IconList(const IconList &) = delete;
// hide assign op
IconList &operator=(const IconList &) = delete;
void reindex();
public slots:
void directoryChanged(const QString &path);
protected slots:
void fileChanged(const QString &path);
void SettingChanged(const Setting & setting, QVariant value);
private:
std::shared_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QMap<QString, int> name_index;
QVector<MMCIcon> icons;
QDir m_dir;
};

View File

@ -0,0 +1,89 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MMCIcon.h"
#include <QFileInfo>
MMCIcon::Type operator--(MMCIcon::Type &t, int)
{
MMCIcon::Type temp = t;
switch (t)
{
case MMCIcon::Type::Builtin:
t = MMCIcon::Type::ToBeDeleted;
break;
case MMCIcon::Type::Transient:
t = MMCIcon::Type::Builtin;
break;
case MMCIcon::Type::FileBased:
t = MMCIcon::Type::Transient;
break;
default:
{
}
}
return temp;
}
MMCIcon::Type MMCIcon::type() const
{
return m_current_type;
}
QString MMCIcon::name() const
{
if (m_name.size())
return m_name;
return m_key;
}
bool MMCIcon::has(MMCIcon::Type _type) const
{
return m_images[_type].present();
}
QIcon MMCIcon::icon() const
{
if (m_current_type == Type::ToBeDeleted)
return QIcon();
return m_images[m_current_type].icon;
}
void MMCIcon::remove(Type rm_type)
{
m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != Type::ToBeDeleted; iter--)
{
if (m_images[iter].present())
{
m_current_type = iter;
return;
}
}
m_current_type = Type::ToBeDeleted;
}
void MMCIcon::replace(MMCIcon::Type new_type, QIcon icon, QString path)
{
QFileInfo foo(path);
if (new_type > m_current_type || m_current_type == MMCIcon::ToBeDeleted)
{
m_current_type = new_type;
}
m_images[new_type].icon = icon;
m_images[new_type].changed = foo.lastModified();
m_images[new_type].filename = path;
}

View File

@ -0,0 +1,55 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
#include <QDateTime>
#include <QIcon>
#include "multimc_gui_export.h"
struct MULTIMC_GUI_EXPORT MMCImage
{
QIcon icon;
QString filename;
QDateTime changed;
bool present() const
{
return !icon.isNull();
}
};
struct MULTIMC_GUI_EXPORT MMCIcon
{
enum Type : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
QString m_key;
QString m_name;
MMCImage m_images[ICONS_TOTAL];
Type m_current_type = ToBeDeleted;
Type type() const;
QString name() const;
bool has(Type _type) const;
QIcon icon() const;
void remove(Type rm_type);
void replace(Type new_type, QIcon icon, QString path = QString());
};

View File

@ -0,0 +1,133 @@
/* Copyright 2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AbstractCommonModel.h"
BaseAbstractCommonModel::BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent)
: QAbstractListModel(parent), m_orientation(orientation)
{
}
int BaseAbstractCommonModel::rowCount(const QModelIndex &parent) const
{
return m_orientation == Qt::Horizontal ? entryCount() : size();
}
int BaseAbstractCommonModel::columnCount(const QModelIndex &parent) const
{
return m_orientation == Qt::Horizontal ? size() : entryCount();
}
QVariant BaseAbstractCommonModel::data(const QModelIndex &index, int role) const
{
if (!hasIndex(index.row(), index.column(), index.parent()))
{
return QVariant();
}
const int i = m_orientation == Qt::Horizontal ? index.column() : index.row();
const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
return formatData(i, role, get(i, entry, role));
}
QVariant BaseAbstractCommonModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != m_orientation && role == Qt::DisplayRole)
{
return entryTitle(section);
}
else
{
return QVariant();
}
}
bool BaseAbstractCommonModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
const int i = m_orientation == Qt::Horizontal ? index.column() : index.row();
const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
const bool result = set(i, entry, role, sanetizeData(i, role, value));
if (result)
{
emit dataChanged(index, index, QVector<int>() << role);
}
return result;
}
Qt::ItemFlags BaseAbstractCommonModel::flags(const QModelIndex &index) const
{
if (!hasIndex(index.row(), index.column(), index.parent()))
{
return Qt::NoItemFlags;
}
const int entry = m_orientation == Qt::Horizontal ? index.row() : index.column();
if (canSet(entry))
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
else
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
void BaseAbstractCommonModel::notifyAboutToAddObject(const int at)
{
if (m_orientation == Qt::Horizontal)
{
beginInsertColumns(QModelIndex(), at, at);
}
else
{
beginInsertRows(QModelIndex(), at, at);
}
}
void BaseAbstractCommonModel::notifyObjectAdded()
{
if (m_orientation == Qt::Horizontal)
{
endInsertColumns();
}
else
{
endInsertRows();
}
}
void BaseAbstractCommonModel::notifyAboutToRemoveObject(const int at)
{
if (m_orientation == Qt::Horizontal)
{
beginRemoveColumns(QModelIndex(), at, at);
}
else
{
beginRemoveRows(QModelIndex(), at, at);
}
}
void BaseAbstractCommonModel::notifyObjectRemoved()
{
if (m_orientation == Qt::Horizontal)
{
endRemoveColumns();
}
else
{
endRemoveRows();
}
}
void BaseAbstractCommonModel::notifyBeginReset()
{
beginResetModel();
}
void BaseAbstractCommonModel::notifyEndReset()
{
endResetModel();
}

View File

@ -0,0 +1,462 @@
/* Copyright 2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QAbstractListModel>
#include <type_traits>
#include <functional>
#include <memory>
class BaseAbstractCommonModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit BaseAbstractCommonModel(const Qt::Orientation orientation, QObject *parent = nullptr);
// begin QAbstractItemModel interface
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
// end QAbstractItemModel interface
virtual int size() const = 0;
virtual int entryCount() const = 0;
virtual QVariant formatData(const int index, int role, const QVariant &data) const { return data; }
virtual QVariant sanetizeData(const int index, int role, const QVariant &data) const { return data; }
protected:
virtual QVariant get(const int index, const int entry, const int role) const = 0;
virtual bool set(const int index, const int entry, const int role, const QVariant &value) = 0;
virtual bool canSet(const int entry) const = 0;
virtual QString entryTitle(const int entry) const = 0;
void notifyAboutToAddObject(const int at);
void notifyObjectAdded();
void notifyAboutToRemoveObject(const int at);
void notifyObjectRemoved();
void notifyBeginReset();
void notifyEndReset();
const Qt::Orientation m_orientation;
};
template<typename Object>
class AbstractCommonModel : public BaseAbstractCommonModel
{
public:
explicit AbstractCommonModel(const Qt::Orientation orientation)
: BaseAbstractCommonModel(orientation) {}
virtual ~AbstractCommonModel() {}
int size() const override { return m_objects.size(); }
int entryCount() const override { return m_entries.size(); }
void append(const Object &object)
{
notifyAboutToAddObject(size());
m_objects.append(object);
notifyObjectAdded();
}
void prepend(const Object &object)
{
notifyAboutToAddObject(0);
m_objects.prepend(object);
notifyObjectAdded();
}
void insert(const Object &object, const int index)
{
if (index >= size())
{
prepend(object);
}
else if (index <= 0)
{
append(object);
}
else
{
notifyAboutToAddObject(index);
m_objects.insert(index, object);
notifyObjectAdded();
}
}
void remove(const int index)
{
notifyAboutToRemoveObject(index);
m_objects.removeAt(index);
notifyObjectRemoved();
}
Object get(const int index) const
{
return m_objects.at(index);
}
private:
friend class CommonModel;
QVariant get(const int index, const int entry, const int role) const override
{
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
{
return QVariant();
}
return m_entries[entry].second.value(role)->get(m_objects.at(index));
}
bool set(const int index, const int entry, const int role, const QVariant &value) override
{
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
{
return false;
}
IEntry *e = m_entries[entry].second.value(role);
if (!e->canSet())
{
return false;
}
e->set(m_objects[index], value);
return true;
}
bool canSet(const int entry) const override
{
if (m_entries.size() < entry || !m_entries[entry].second.contains(Qt::EditRole))
{
return false;
}
IEntry *e = m_entries[entry].second.value(Qt::EditRole);
return e->canSet();
}
QString entryTitle(const int entry) const override
{
return m_entries.at(entry).first;
}
private:
struct IEntry
{
virtual ~IEntry() {}
virtual void set(Object &object, const QVariant &value) = 0;
virtual QVariant get(const Object &object) const = 0;
virtual bool canSet() const = 0;
};
template<typename T>
struct VariableEntry : public IEntry
{
typedef T (Object::*Member);
explicit VariableEntry(Member member)
: m_member(member) {}
void set(Object &object, const QVariant &value) override
{
object.*m_member = value.value<T>();
}
QVariant get(const Object &object) const override
{
return QVariant::fromValue<T>(object.*m_member);
}
bool canSet() const override { return true; }
private:
Member m_member;
};
template<typename T>
struct FunctionEntry : public IEntry
{
typedef T (Object::*Getter)() const;
typedef void (Object::*Setter)(T);
explicit FunctionEntry(Getter getter, Setter setter)
: m_getter(m_getter), m_setter(m_setter) {}
void set(Object &object, const QVariant &value) override
{
object.*m_setter(value.value<T>());
}
QVariant get(const Object &object) const override
{
return QVariant::fromValue<T>(object.*m_getter());
}
bool canSet() const override { return !!m_setter; }
private:
Getter m_getter;
Setter m_setter;
};
QList<Object> m_objects;
QVector<QPair<QString, QMap<int, IEntry *>>> m_entries;
void addEntryInternal(IEntry *e, const int entry, const int role)
{
if (m_entries.size() <= entry)
{
m_entries.resize(entry + 1);
}
m_entries[entry].second.insert(role, e);
}
protected:
template<typename Getter, typename Setter>
typename std::enable_if<std::is_member_function_pointer<Getter>::value && std::is_member_function_pointer<Getter>::value, void>::type
addEntry(Getter getter, Setter setter, const int entry, const int role)
{
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, setter), entry, role);
}
template<typename Getter>
typename std::enable_if<std::is_member_function_pointer<Getter>::value, void>::type
addEntry(Getter getter, const int entry, const int role)
{
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, nullptr), entry, role);
}
template<typename T>
typename std::enable_if<!std::is_member_function_pointer<T (Object::*)>::value, void>::type
addEntry(T (Object::*member), const int entry, const int role)
{
addEntryInternal(new VariableEntry<T>(member), entry, role);
}
void setEntryTitle(const int entry, const QString &title)
{
m_entries[entry].first = title;
}
};
template<typename Object>
class AbstractCommonModel<Object *> : public BaseAbstractCommonModel
{
public:
explicit AbstractCommonModel(const Qt::Orientation orientation)
: BaseAbstractCommonModel(orientation) {}
virtual ~AbstractCommonModel()
{
qDeleteAll(m_objects);
}
int size() const override { return m_objects.size(); }
int entryCount() const override { return m_entries.size(); }
void append(Object *object)
{
notifyAboutToAddObject(size());
m_objects.append(object);
notifyObjectAdded();
}
void prepend(Object *object)
{
notifyAboutToAddObject(0);
m_objects.prepend(object);
notifyObjectAdded();
}
void insert(Object *object, const int index)
{
if (index >= size())
{
prepend(object);
}
else if (index <= 0)
{
append(object);
}
else
{
notifyAboutToAddObject(index);
m_objects.insert(index, object);
notifyObjectAdded();
}
}
void remove(const int index)
{
notifyAboutToRemoveObject(index);
m_objects.removeAt(index);
notifyObjectRemoved();
}
Object *get(const int index) const
{
return m_objects.at(index);
}
int find(Object * const obj) const
{
return m_objects.indexOf(obj);
}
QList<Object *> getAll() const
{
return m_objects;
}
private:
friend class CommonModel;
QVariant get(const int index, const int entry, const int role) const override
{
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
{
return QVariant();
}
return m_entries[entry].second.value(role)->get(m_objects.at(index));
}
bool set(const int index, const int entry, const int role, const QVariant &value) override
{
if (m_entries.size() < entry || !m_entries[entry].second.contains(role))
{
return false;
}
IEntry *e = m_entries[entry].second.value(role);
if (!e->canSet())
{
return false;
}
e->set(m_objects[index], value);
return true;
}
bool canSet(const int entry) const override
{
if (m_entries.size() < entry || !m_entries[entry].second.contains(Qt::EditRole))
{
return false;
}
IEntry *e = m_entries[entry].second.value(Qt::EditRole);
return e->canSet();
}
QString entryTitle(const int entry) const override
{
return m_entries.at(entry).first;
}
private:
struct IEntry
{
virtual ~IEntry() {}
virtual void set(Object *object, const QVariant &value) = 0;
virtual QVariant get(Object *object) const = 0;
virtual bool canSet() const = 0;
};
template<typename T>
struct VariableEntry : public IEntry
{
typedef T (Object::*Member);
explicit VariableEntry(Member member)
: m_member(member) {}
void set(Object *object, const QVariant &value) override
{
object->*m_member = value.value<T>();
}
QVariant get(Object *object) const override
{
return QVariant::fromValue<T>(object->*m_member);
}
bool canSet() const override { return true; }
private:
Member m_member;
};
template<typename T>
struct FunctionEntry : public IEntry
{
typedef T (Object::*Getter)() const;
typedef void (Object::*Setter)(T);
explicit FunctionEntry(Getter getter, Setter setter)
: m_getter(getter), m_setter(setter) {}
void set(Object *object, const QVariant &value) override
{
(object->*m_setter)(value.value<T>());
}
QVariant get(Object *object) const override
{
return QVariant::fromValue<T>((object->*m_getter)());
}
bool canSet() const override { return !!m_setter; }
private:
Getter m_getter;
Setter m_setter;
};
template<typename T>
struct LambdaEntry : public IEntry
{
using Getter = std::function<T(Object *)>;
explicit LambdaEntry(Getter getter)
: m_getter(getter) {}
void set(Object *object, const QVariant &value) override {}
QVariant get(Object *object) const override
{
return QVariant::fromValue<T>(m_getter(object));
}
bool canSet() const override { return false; }
private:
Getter m_getter;
};
QList<Object *> m_objects;
QVector<QPair<QString, QMap<int, IEntry *>>> m_entries;
void addEntryInternal(IEntry *e, const int entry, const int role)
{
if (m_entries.size() <= entry)
{
m_entries.resize(entry + 1);
}
m_entries[entry].second.insert(role, e);
}
protected:
template<typename Getter, typename Setter>
typename std::enable_if<std::is_member_function_pointer<Getter>::value && std::is_member_function_pointer<Getter>::value, void>::type
addEntry(const int entry, const int role, Getter getter, Setter setter)
{
addEntryInternal(new FunctionEntry<typename std::result_of<Getter>::type>(getter, setter), entry, role);
}
template<typename T>
typename std::enable_if<std::is_member_function_pointer<typename FunctionEntry<T>::Getter>::value, void>::type
addEntry(const int entry, const int role, typename FunctionEntry<T>::Getter getter)
{
addEntryInternal(new FunctionEntry<T>(getter, nullptr), entry, role);
}
template<typename T>
typename std::enable_if<!std::is_member_function_pointer<T (Object::*)>::value, void>::type
addEntry(const int entry, const int role, T (Object::*member))
{
addEntryInternal(new VariableEntry<T>(member), entry, role);
}
template<typename T>
void addEntry(const int entry, const int role, typename LambdaEntry<T>::Getter lambda)
{
addEntryInternal(new LambdaEntry<T>(lambda), entry, role);
}
void setEntryTitle(const int entry, const QString &title)
{
m_entries[entry].first = title;
}
void setAll(const QList<Object *> objects)
{
notifyBeginReset();
qDeleteAll(m_objects);
m_objects = objects;
notifyEndReset();
}
};

View File

@ -0,0 +1,103 @@
/* Copyright 2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BaseConfigObject.h"
#include <QTimer>
#include <QFile>
#include <QCoreApplication>
#include <QDebug>
#include "Exception.h"
#include "FileSystem.h"
BaseConfigObject::BaseConfigObject(const QString &filename)
: m_filename(filename)
{
m_saveTimer = new QTimer;
m_saveTimer->setSingleShot(true);
// cppcheck-suppress pureVirtualCall
QObject::connect(m_saveTimer, &QTimer::timeout, [this](){saveNow();});
setSaveTimeout(250);
m_initialReadTimer = new QTimer;
m_initialReadTimer->setSingleShot(true);
QObject::connect(m_initialReadTimer, &QTimer::timeout, [this]()
{
loadNow();
m_initialReadTimer->deleteLater();
m_initialReadTimer = 0;
});
m_initialReadTimer->start(0);
// cppcheck-suppress pureVirtualCall
m_appQuitConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this](){saveNow();});
}
BaseConfigObject::~BaseConfigObject()
{
delete m_saveTimer;
if (m_initialReadTimer)
{
delete m_initialReadTimer;
}
QObject::disconnect(m_appQuitConnection);
}
void BaseConfigObject::setSaveTimeout(int msec)
{
m_saveTimer->setInterval(msec);
}
void BaseConfigObject::scheduleSave()
{
m_saveTimer->stop();
m_saveTimer->start();
}
void BaseConfigObject::saveNow()
{
if (m_saveTimer->isActive())
{
m_saveTimer->stop();
}
if (m_disableSaving)
{
return;
}
try
{
FS::write(m_filename, doSave());
}
catch (Exception & e)
{
qCritical() << e.cause();
}
}
void BaseConfigObject::loadNow()
{
if (m_saveTimer->isActive())
{
saveNow();
}
try
{
doLoad(FS::read(m_filename));
}
catch (Exception & e)
{
qWarning() << "Error loading" << m_filename << ":" << e.cause();
}
}

View File

@ -0,0 +1,50 @@
/* Copyright 2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
class QTimer;
class BaseConfigObject
{
public:
void setSaveTimeout(int msec);
protected:
explicit BaseConfigObject(const QString &filename);
virtual ~BaseConfigObject();
// cppcheck-suppress pureVirtualCall
virtual QByteArray doSave() const = 0;
virtual void doLoad(const QByteArray &data) = 0;
void setSavingDisabled(bool savingDisabled) { m_disableSaving = savingDisabled; }
QString fileName() const { return m_filename; }
public:
void scheduleSave();
void saveNow();
void loadNow();
private:
QTimer *m_saveTimer;
QTimer *m_initialReadTimer;
QString m_filename;
QMetaObject::Connection m_appQuitConnection;
bool m_disableSaving = false;
};

View File

@ -0,0 +1,61 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QFile>
#include "BaseInstaller.h"
#include "minecraft/onesix/OneSixInstance.h"
BaseInstaller::BaseInstaller()
{
}
bool BaseInstaller::isApplied(OneSixInstance *on)
{
return QFile::exists(filename(on->instanceRoot()));
}
bool BaseInstaller::add(OneSixInstance *to)
{
if (!patchesDir(to->instanceRoot()).exists())
{
QDir(to->instanceRoot()).mkdir("patches");
}
if (isApplied(to))
{
if (!remove(to))
{
return false;
}
}
return true;
}
bool BaseInstaller::remove(OneSixInstance *from)
{
return QFile::remove(filename(from->instanceRoot()));
}
QString BaseInstaller::filename(const QString &root) const
{
return patchesDir(root).absoluteFilePath(id() + ".json");
}
QDir BaseInstaller::patchesDir(const QString &root) const
{
return QDir(root + "/patches/");
}

View File

@ -0,0 +1,46 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <memory>
#include "multimc_logic_export.h"
class OneSixInstance;
class QDir;
class QString;
class QObject;
class Task;
class BaseVersion;
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
class MULTIMC_LOGIC_EXPORT BaseInstaller
{
public:
BaseInstaller();
virtual ~BaseInstaller(){};
bool isApplied(OneSixInstance *on);
virtual bool add(OneSixInstance *to);
virtual bool remove(OneSixInstance *from);
virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
protected:
virtual QString id() const = 0;
QString filename(const QString &root) const;
QDir patchesDir(const QString &root) const;
};

View File

@ -0,0 +1,270 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BaseInstance.h"
#include <QFileInfo>
#include <QDir>
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
#include "settings/OverrideSetting.h"
#include "minecraft/MinecraftVersionList.h"
#include "FileSystem.h"
#include "Commandline.h"
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: QObject()
{
m_settings = settings;
m_rootDir = rootDir;
m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default");
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
// Console
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
}
QString BaseInstance::getPreLaunchCommand()
{
return settings()->get("PreLaunchCommand").toString();
}
QString BaseInstance::getWrapperCommand()
{
return settings()->get("WrapperCommand").toString();
}
QString BaseInstance::getPostExitCommand()
{
return settings()->get("PostExitCommand").toString();
}
void BaseInstance::iconUpdated(QString key)
{
if(iconKey() == key)
{
emit propertiesChanged(this);
}
}
void BaseInstance::nuke()
{
FS::deletePath(instanceRoot());
emit nuked(this);
}
QString BaseInstance::id() const
{
return QFileInfo(instanceRoot()).fileName();
}
bool BaseInstance::isRunning() const
{
return m_isRunning;
}
void BaseInstance::setRunning(bool running)
{
if(running && !m_isRunning)
{
m_timeStarted = QDateTime::currentDateTime();
}
else if(!running && m_isRunning)
{
qint64 current = settings()->get("totalTimePlayed").toLongLong();
QDateTime timeEnded = QDateTime::currentDateTime();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
emit propertiesChanged(this);
}
m_isRunning = running;
}
int64_t BaseInstance::totalTimePlayed() const
{
qint64 current = settings()->get("totalTimePlayed").toLongLong();
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
return current + m_timeStarted.secsTo(timeNow);
}
return current;
}
void BaseInstance::resetTimePlayed()
{
settings()->reset("totalTimePlayed");
}
QString BaseInstance::instanceType() const
{
return m_settings->get("InstanceType").toString();
}
QString BaseInstance::instanceRoot() const
{
return m_rootDir;
}
InstancePtr BaseInstance::getSharedPtr()
{
return shared_from_this();
}
SettingsObjectPtr BaseInstance::settings() const
{
return m_settings;
}
BaseInstance::InstanceFlags BaseInstance::flags() const
{
return m_flags;
}
void BaseInstance::setFlags(const InstanceFlags &flags)
{
if (flags != m_flags)
{
m_flags = flags;
emit flagsChanged();
emit propertiesChanged(this);
}
}
void BaseInstance::setFlag(const BaseInstance::InstanceFlag flag)
{
// nothing to set?
if(flag & m_flags)
return;
m_flags |= flag;
emit flagsChanged();
emit propertiesChanged(this);
}
void BaseInstance::unsetFlag(const BaseInstance::InstanceFlag flag)
{
// nothing to unset?
if(!(flag & m_flags))
return;
m_flags &= ~flag;
emit flagsChanged();
emit propertiesChanged(this);
}
bool BaseInstance::canLaunch() const
{
return !(flags() & VersionBrokenFlag);
}
bool BaseInstance::reload()
{
return m_settings->reload();
}
qint64 BaseInstance::lastLaunch() const
{
return m_settings->get("lastLaunchTime").value<qint64>();
}
void BaseInstance::setLastLaunch(qint64 val)
{
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("lastLaunchTime", val);
emit propertiesChanged(this);
}
void BaseInstance::setGroupInitial(QString val)
{
if(m_group == val)
{
return;
}
m_group = val;
emit propertiesChanged(this);
}
void BaseInstance::setGroupPost(QString val)
{
if(m_group == val)
{
return;
}
setGroupInitial(val);
emit groupChanged();
}
QString BaseInstance::group() const
{
return m_group;
}
void BaseInstance::setNotes(QString val)
{
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("notes", val);
}
QString BaseInstance::notes() const
{
return m_settings->get("notes").toString();
}
void BaseInstance::setIconKey(QString val)
{
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("iconKey", val);
emit propertiesChanged(this);
}
QString BaseInstance::iconKey() const
{
return m_settings->get("iconKey").toString();
}
void BaseInstance::setName(QString val)
{
//FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("name", val);
emit propertiesChanged(this);
}
QString BaseInstance::name() const
{
return m_settings->get("name").toString();
}
QString BaseInstance::windowTitle() const
{
return "MultiMC: " + name();
}
QStringList BaseInstance::extraArguments() const
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}

View File

@ -0,0 +1,243 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QDateTime>
#include <QSet>
#include <QProcess>
#include "settings/SettingsObject.h"
#include "settings/INIFile.h"
#include "BaseVersionList.h"
#include "minecraft/auth/MojangAccount.h"
#include "launch/MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "multimc_logic_export.h"
class QDir;
class Task;
class LaunchTask;
class BaseInstance;
// pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr;
/*!
* \brief Base class for instances.
* This class implements many functions that are common between instances and
* provides a standard interface for all instances.
*
* To create a new instance type, create a new class inheriting from this class
* and implement the pure virtual functions.
*/
class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
{
Q_OBJECT
protected:
/// no-touchy!
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {};
virtual void copy(const QDir &newDir) {}
virtual void init() = 0;
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
/// responsible of cleaning up the husk
void nuke();
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
/// be unique.
virtual QString id() const;
void setRunning(bool running);
bool isRunning() const;
int64_t totalTimePlayed() const;
void resetTimePlayed();
/// get the type of this instance
QString instanceType() const;
/// Path to the instance's root directory.
QString instanceRoot() const;
QString name() const;
void setName(QString val);
/// Value used for instance window titles
QString windowTitle() const;
QString iconKey() const;
void setIconKey(QString val);
QString notes() const;
void setNotes(QString val);
QString group() const;
void setGroupInitial(QString val);
void setGroupPost(QString val);
QString getPreLaunchCommand();
QString getPostExitCommand();
QString getWrapperCommand();
/// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{
return level;
};
virtual QStringList extraArguments() const;
virtual QString intendedVersionId() const = 0;
virtual bool setIntendedVersionId(QString version) = 0;
/*!
* The instance's current version.
* This value represents the instance's current version. If this value is
* different from the intendedVersion, the instance should be updated.
* \warning Don't change this value unless you know what you're doing.
*/
virtual QString currentVersionId() const = 0;
/*!
* Whether or not 'the game' should be downloaded when the instance is launched.
*/
virtual bool shouldUpdate() const = 0;
virtual void setShouldUpdate(bool val) = 0;
/// Traits. Normally inside the version, depends on instance implementation.
virtual QSet <QString> traits() = 0;
/**
* Gets the time that the instance was last launched.
* Stored in milliseconds since epoch.
*/
qint64 lastLaunch() const;
/// Sets the last launched time to 'val' milliseconds since epoch
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
InstancePtr getSharedPtr();
/*!
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
*/
virtual std::shared_ptr<BaseVersionList> versionList() const = 0;
/*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
* \return A pointer to this instance's settings object.
*/
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
virtual std::shared_ptr<Task> createUpdateTask() = 0;
/// returns a valid launcher (task container)
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
/*!
* Returns a task that should be done right before launch
* This task should do any extra preparations needed
*/
virtual std::shared_ptr<Task> createJarModdingTask() = 0;
/*!
* Create envrironment variables for running the instance
*/
virtual QProcessEnvironment createEnvironment() = 0;
/*!
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
*/
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
/*!
* Returns the root folder to use for looking up log files
*/
virtual QString getLogFileRoot() = 0;
/*!
* does any necessary cleanups after the instance finishes. also runs before\
* TODO: turn into a task that can run asynchronously
*/
virtual void cleanupAfterRun() = 0;
virtual QString getStatusbarDescription() = 0;
/// FIXME: this really should be elsewhere...
virtual QString instanceConfigFolder() const = 0;
/// get variables this instance exports
virtual QMap<QString, QString> getVariables() const = 0;
virtual QString typeName() const = 0;
enum InstanceFlag
{
VersionBrokenFlag = 0x01,
UpdateAvailable = 0x02
};
Q_DECLARE_FLAGS(InstanceFlags, InstanceFlag)
InstanceFlags flags() const;
void setFlags(const InstanceFlags &flags);
void setFlag(const InstanceFlag flag);
void unsetFlag(const InstanceFlag flag);
bool canLaunch() const;
virtual bool canExport() const = 0;
virtual bool reload();
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
*/
void propertiesChanged(BaseInstance *inst);
/*!
* \brief Signal emitted when groups are affected in any way
*/
void groupChanged();
/*!
* \brief The instance just got nuked. Hurray!
*/
void nuked(BaseInstance *inst);
void flagsChanged();
protected slots:
void iconUpdated(QString key);
protected:
QString m_rootDir;
QString m_group;
SettingsObjectPtr m_settings;
InstanceFlags m_flags;
bool m_isRunning = false;
QDateTime m_timeStarted;
};
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)

View File

@ -0,0 +1,59 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <memory>
#include <QString>
#include <QMetaType>
/*!
* An abstract base class for versions.
*/
class BaseVersion
{
public:
virtual ~BaseVersion() {}
/*!
* A string used to identify this version in config files.
* This should be unique within the version list or shenanigans will occur.
*/
virtual QString descriptor() = 0;
/*!
* The name of this version as it is displayed to the user.
* For example: "1.5.1"
*/
virtual QString name() = 0;
/*!
* This should return a string that describes
* the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion &a)
{
return name() < a.name();
};
virtual bool operator>(BaseVersion &a)
{
return name() > a.name();
};
};
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
Q_DECLARE_METATYPE(BaseVersionPtr)

View File

@ -0,0 +1,104 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BaseVersionList.h"
#include "BaseVersion.h"
BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
{
}
BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
{
for (int i = 0; i < count(); i++)
{
if (at(i)->descriptor() == descriptor)
return at(i);
}
return BaseVersionPtr();
}
BaseVersionPtr BaseVersionList::getLatestStable() const
{
if (count() <= 0)
return BaseVersionPtr();
else
return at(0);
}
BaseVersionPtr BaseVersionList::getRecommended() const
{
return getLatestStable();
}
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
BaseVersionPtr version = at(index.row());
switch (role)
{
case VersionPointerRole:
return qVariantFromValue(version);
case VersionRole:
return version->name();
case VersionIdRole:
return version->descriptor();
case TypeRole:
return version->typeString();
default:
return QVariant();
}
}
BaseVersionList::RoleList BaseVersionList::providesRoles() const
{
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
}
int BaseVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
return count();
}
int BaseVersionList::columnCount(const QModelIndex &parent) const
{
return 1;
}
QHash<int, QByteArray> BaseVersionList::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles.insert(VersionRole, "version");
roles.insert(VersionIdRole, "versionId");
roles.insert(ParentGameVersionRole, "parentGameVersion");
roles.insert(RecommendedRole, "recommended");
roles.insert(LatestRole, "latest");
roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path");
roles.insert(ArchitectureRole, "architecture");
return roles;
}

View File

@ -0,0 +1,126 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include "BaseVersion.h"
#include "tasks/Task.h"
#include "multimc_logic_export.h"
/*!
* \brief Class that each instance type's version list derives from.
* Version lists are the lists that keep track of the available game versions
* for that instance. This list will not be loaded on startup. It will be loaded
* when the list's load function is called. Before using the version list, you
* should check to see if it has been loaded yet and if not, load the list.
*
* Note that this class also inherits from QAbstractListModel. Methods from that
* class determine how this version list shows up in a list view. Said methods
* all have a default implementation, but they can be overridden by plugins to
* change the behavior of the list.
*/
class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles
{
VersionPointerRole = Qt::UserRole,
VersionRole,
VersionIdRole,
ParentGameVersionRole,
RecommendedRole,
LatestRole,
TypeRole,
BranchRole,
PathRole,
ArchitectureRole,
SortRole
};
typedef QList<int> RoleList;
explicit BaseVersionList(QObject *parent = 0);
/*!
* \brief Gets a task that will reload the version list.
* Simply execute the task to load the list.
* The task returned by this function should reset the model when it's done.
* \return A pointer to a task that reloads the version list.
*/
virtual Task *getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be
//loaded.
virtual bool isLoaded() = 0;
//! Gets the version at the given index.
virtual const BaseVersionPtr at(int i) const = 0;
//! Returns the number of versions in the list.
virtual int count() const = 0;
//////// List Model Functions ////////
virtual QVariant data(const QModelIndex &index, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list?
virtual RoleList providesRoles() const;
/*!
* \brief Finds a version by its descriptor.
* \param The descriptor of the version to find.
* \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist.
*/
virtual BaseVersionPtr findVersion(const QString &descriptor);
/*!
* \brief Gets the latest stable version from this list
*/
virtual BaseVersionPtr getLatestStable() const;
/*!
* \brief Gets the recommended version from this list
* If the list doesn't support recommended versions, this works exactly as getLatestStable
*/
virtual BaseVersionPtr getRecommended() const;
/*!
* Sorts the version list.
*/
virtual void sortVersions() = 0;
protected
slots:
/*!
* Updates this list with the given list of versions.
* This is done by copying each version in the given list and inserting it
* into this one.
* We need to do this so that we can set the parents of the versions are set to this
* version list. This can't be done in the load task, because the versions 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 versions and sets their parents correctly.
* \param versions List of versions whose parents should be set.
*/
virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
};

View File

@ -0,0 +1,344 @@
project(MultiMC_logic)
set(LOGIC_SOURCES
# LOGIC - Base classes and infrastructure
BaseInstaller.h
BaseInstaller.cpp
BaseVersionList.h
BaseVersionList.cpp
InstanceList.h
InstanceList.cpp
BaseVersion.h
BaseInstance.h
BaseInstance.cpp
NullInstance.h
MMCZip.h
MMCZip.cpp
MMCStrings.h
MMCStrings.cpp
BaseConfigObject.h
BaseConfigObject.cpp
AbstractCommonModel.h
AbstractCommonModel.cpp
TypeMagic.h
# Prefix tree where node names are strings between separators
SeparatorPrefixTree.h
# WARNING: globals live here
Env.h
Env.cpp
# JSON parsing helpers
Json.h
Json.cpp
FileSystem.h
FileSystem.cpp
Exception.h
# RW lock protected map
RWStorage.h
# A variable that has an implicit default value and keeps track of changes
DefaultVariable.h
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
QObjectPtr.h
# Resources
resources/Resource.cpp
resources/Resource.h
resources/ResourceHandler.cpp
resources/ResourceHandler.h
resources/ResourceObserver.cpp
resources/ResourceObserver.h
resources/ResourceProxyModel.h
resources/ResourceProxyModel.cpp
# Path matchers
pathmatcher/FSTreeMatcher.h
pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h
# Compression support
GZip.h
GZip.cpp
# Command line parameter parsing
Commandline.h
Commandline.cpp
# Version number string support
Version.h
Version.cpp
# network stuffs
net/NetAction.h
net/MD5EtagDownload.h
net/MD5EtagDownload.cpp
net/ByteArrayDownload.h
net/ByteArrayDownload.cpp
net/CacheDownload.h
net/CacheDownload.cpp
net/NetJob.h
net/NetJob.cpp
net/HttpMetaCache.h
net/HttpMetaCache.cpp
net/PasteUpload.h
net/PasteUpload.cpp
net/URLConstants.h
net/URLConstants.cpp
# Yggdrasil login stuff
minecraft/auth/AuthSession.h
minecraft/auth/AuthSession.cpp
minecraft/auth/MojangAccountList.h
minecraft/auth/MojangAccountList.cpp
minecraft/auth/MojangAccount.h
minecraft/auth/MojangAccount.cpp
minecraft/auth/YggdrasilTask.h
minecraft/auth/YggdrasilTask.cpp
minecraft/auth/flows/AuthenticateTask.h
minecraft/auth/flows/AuthenticateTask.cpp
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp
# Game launch logic
launch/steps/CheckJava.cpp
launch/steps/CheckJava.h
launch/steps/LaunchMinecraft.cpp
launch/steps/LaunchMinecraft.h
launch/steps/ModMinecraftJar.cpp
launch/steps/ModMinecraftJar.h
launch/steps/PostLaunchCommand.cpp
launch/steps/PostLaunchCommand.h
launch/steps/PreLaunchCommand.cpp
launch/steps/PreLaunchCommand.h
launch/steps/TextPrint.cpp
launch/steps/TextPrint.h
launch/steps/Update.cpp
launch/steps/Update.h
launch/LaunchStep.cpp
launch/LaunchStep.h
launch/LaunchTask.cpp
launch/LaunchTask.h
launch/LoggedProcess.cpp
launch/LoggedProcess.h
launch/MessageLevel.cpp
launch/MessageLevel.h
# Update system
updater/GoUpdate.h
updater/GoUpdate.cpp
updater/UpdateChecker.h
updater/UpdateChecker.cpp
updater/DownloadTask.h
updater/DownloadTask.cpp
# Notifications - short warning messages
notifications/NotificationChecker.h
notifications/NotificationChecker.cpp
# News System
news/NewsChecker.h
news/NewsChecker.cpp
news/NewsEntry.h
news/NewsEntry.cpp
# Status system
status/StatusChecker.h
status/StatusChecker.cpp
# Minecraft support
minecraft/onesix/OneSixUpdate.h
minecraft/onesix/OneSixUpdate.cpp
minecraft/onesix/OneSixInstance.h
minecraft/onesix/OneSixInstance.cpp
minecraft/onesix/OneSixProfileStrategy.cpp
minecraft/onesix/OneSixProfileStrategy.h
minecraft/onesix/OneSixVersionFormat.cpp
minecraft/onesix/OneSixVersionFormat.h
minecraft/legacy/LegacyUpdate.h
minecraft/legacy/LegacyUpdate.cpp
minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LwjglVersionList.h
minecraft/legacy/LwjglVersionList.cpp
minecraft/GradleSpecifier.h
minecraft/MinecraftProfile.cpp
minecraft/MinecraftProfile.h
minecraft/MojangVersionFormat.cpp
minecraft/MojangVersionFormat.h
minecraft/JarMod.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h
minecraft/MinecraftVersion.cpp
minecraft/MinecraftVersion.h
minecraft/MinecraftVersionList.cpp
minecraft/MinecraftVersionList.h
minecraft/Rule.cpp
minecraft/Rule.h
minecraft/OpSys.cpp
minecraft/OpSys.h
minecraft/ParseUtils.cpp
minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp
minecraft/ProfileUtils.h
minecraft/ProfileStrategy.h
minecraft/Library.cpp
minecraft/Library.h
minecraft/MojangDownloadInfo.h
minecraft/VersionBuildError.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
minecraft/ProfilePatch.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
minecraft/Mod.h
minecraft/Mod.cpp
minecraft/ModList.h
minecraft/ModList.cpp
minecraft/World.h
minecraft/World.cpp
minecraft/WorldList.h
minecraft/WorldList.cpp
# FTB
minecraft/ftb/OneSixFTBInstance.h
minecraft/ftb/OneSixFTBInstance.cpp
minecraft/ftb/LegacyFTBInstance.h
minecraft/ftb/LegacyFTBInstance.cpp
minecraft/ftb/FTBProfileStrategy.h
minecraft/ftb/FTBProfileStrategy.cpp
minecraft/ftb/FTBPlugin.h
minecraft/ftb/FTBPlugin.cpp
# A Recursive file system watcher
RecursiveFileSystemWatcher.h
RecursiveFileSystemWatcher.cpp
# the screenshots feature
screenshots/Screenshot.h
screenshots/ImgurUpload.h
screenshots/ImgurUpload.cpp
screenshots/ImgurAlbumCreation.h
screenshots/ImgurAlbumCreation.cpp
# Tasks
tasks/Task.h
tasks/Task.cpp
tasks/ThreadTask.h
tasks/ThreadTask.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
# Settings
settings/INIFile.cpp
settings/INIFile.h
settings/INISettingsObject.cpp
settings/INISettingsObject.h
settings/OverrideSetting.cpp
settings/OverrideSetting.h
settings/PassthroughSetting.cpp
settings/PassthroughSetting.h
settings/Setting.cpp
settings/Setting.h
settings/SettingsObject.cpp
settings/SettingsObject.h
# Java related code
java/JavaChecker.h
java/JavaChecker.cpp
java/JavaCheckerJob.h
java/JavaCheckerJob.cpp
java/JavaInstall.h
java/JavaInstall.cpp
java/JavaInstallList.h
java/JavaInstallList.cpp
java/JavaUtils.h
java/JavaUtils.cpp
java/JavaVersion.h
java/JavaVersion.cpp
# Assets
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
# Forge and all things forge related
minecraft/forge/ForgeVersion.h
minecraft/forge/ForgeVersion.cpp
minecraft/forge/ForgeVersionList.h
minecraft/forge/ForgeVersionList.cpp
minecraft/forge/ForgeXzDownload.h
minecraft/forge/ForgeXzDownload.cpp
minecraft/forge/LegacyForge.h
minecraft/forge/LegacyForge.cpp
minecraft/forge/ForgeInstaller.h
minecraft/forge/ForgeInstaller.cpp
# Liteloader and related things
minecraft/liteloader/LiteLoaderInstaller.h
minecraft/liteloader/LiteLoaderInstaller.cpp
minecraft/liteloader/LiteLoaderVersionList.h
minecraft/liteloader/LiteLoaderVersionList.cpp
# Translations
trans/TranslationDownloader.h
trans/TranslationDownloader.cpp
# Tools
tools/BaseExternalTool.cpp
tools/BaseExternalTool.h
tools/BaseProfiler.cpp
tools/BaseProfiler.h
tools/JProfiler.cpp
tools/JProfiler.h
tools/JVisualVM.cpp
tools/JVisualVM.h
tools/MCEditTool.cpp
tools/MCEditTool.h
# Wonko
wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp
wonko/tasks/BaseWonkoEntityRemoteLoadTask.h
wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp
wonko/tasks/BaseWonkoEntityLocalLoadTask.h
wonko/format/WonkoFormatV1.cpp
wonko/format/WonkoFormatV1.h
wonko/format/WonkoFormat.cpp
wonko/format/WonkoFormat.h
wonko/BaseWonkoEntity.cpp
wonko/BaseWonkoEntity.h
wonko/WonkoVersionList.cpp
wonko/WonkoVersionList.h
wonko/WonkoVersion.cpp
wonko/WonkoVersion.h
wonko/WonkoIndex.cpp
wonko/WonkoIndex.h
wonko/WonkoUtil.cpp
wonko/WonkoUtil.h
wonko/WonkoReference.cpp
wonko/WonkoReference.h
)
################################ COMPILE ################################
# we need zlib
find_package(ZLIB REQUIRED)
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_logic)
# Link
target_link_libraries(MultiMC_logic xz-embedded unpack200 ${QUAZIP_LIBRARIES} nbt++ ${ZLIB_LIBRARIES})
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
add_dependencies(MultiMC_logic QuaZIP)
# Mark and export headers
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")

View File

@ -0,0 +1,483 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Commandline.h"
/**
* @file libutil/src/cmdutils.cpp
*/
namespace Commandline
{
// commandline splitter
QStringList splitArgs(QString args)
{
QStringList argv;
QString current;
bool escape = false;
QChar inquotes;
for (int i = 0; i < args.length(); i++)
{
QChar cchar = args.at(i);
// \ escaped
if (escape)
{
current += cchar;
escape = false;
// in "quotes"
}
else if (!inquotes.isNull())
{
if (cchar == 0x5C)
escape = true;
else if (cchar == inquotes)
inquotes = 0;
else
current += cchar;
// otherwise
}
else
{
if (cchar == 0x20)
{
if (!current.isEmpty())
{
argv << current;
current.clear();
}
}
else if (cchar == 0x22 || cchar == 0x27)
inquotes = cchar;
else
current += cchar;
}
}
if (!current.isEmpty())
argv << current;
return argv;
}
Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle)
{
m_flagStyle = flagStyle;
m_argStyle = argStyle;
}
// styles setter/getter
void Parser::setArgumentStyle(ArgumentStyle::Enum style)
{
m_argStyle = style;
}
ArgumentStyle::Enum Parser::argumentStyle()
{
return m_argStyle;
}
void Parser::setFlagStyle(FlagStyle::Enum style)
{
m_flagStyle = style;
}
FlagStyle::Enum Parser::flagStyle()
{
return m_flagStyle;
}
// setup methods
void Parser::addSwitch(QString name, bool def)
{
if (m_params.contains(name))
throw "Name not unique";
OptionDef *param = new OptionDef;
param->type = otSwitch;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
m_options[name] = param;
m_params[name] = (CommonDef *)param;
m_optionList.append(param);
}
void Parser::addOption(QString name, QVariant def)
{
if (m_params.contains(name))
throw "Name not unique";
OptionDef *param = new OptionDef;
param->type = otOption;
param->name = name;
param->metavar = QString("<%1>").arg(name);
param->def = def;
m_options[name] = param;
m_params[name] = (CommonDef *)param;
m_optionList.append(param);
}
void Parser::addArgument(QString name, bool required, QVariant def)
{
if (m_params.contains(name))
throw "Name not unique";
PositionalDef *param = new PositionalDef;
param->name = name;
param->def = def;
param->required = required;
param->metavar = name;
m_positionals.append(param);
m_params[name] = (CommonDef *)param;
}
void Parser::addDocumentation(QString name, QString doc, QString metavar)
{
if (!m_params.contains(name))
throw "Name does not exist";
CommonDef *param = m_params[name];
param->doc = doc;
if (!metavar.isNull())
param->metavar = metavar;
}
void Parser::addShortOpt(QString name, QChar flag)
{
if (!m_params.contains(name))
throw "Name does not exist";
if (!m_options.contains(name))
throw "Name is not an Option or Swtich";
OptionDef *param = m_options[name];
m_flags[flag] = param;
param->flag = flag;
}
// help methods
QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
{
QStringList help;
help << compileUsage(progName, useFlags) << "\r\n";
// positionals
if (!m_positionals.isEmpty())
{
help << "\r\n";
help << "Positional arguments:\r\n";
QListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext())
{
PositionalDef *param = it2.next();
help << " " << param->metavar;
help << " " << QString(helpIndent - param->metavar.length() - 1, ' ');
help << param->doc << "\r\n";
}
}
// Options
if (!m_optionList.isEmpty())
{
help << "\r\n";
QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix);
help << "Options & Switches:\r\n";
QListIterator<OptionDef *> it(m_optionList);
while (it.hasNext())
{
OptionDef *option = it.next();
help << " ";
int nameLength = optPrefix.length() + option->name.length();
if (!option->flag.isNull())
{
nameLength += 3 + flagPrefix.length();
help << flagPrefix << option->flag << ", ";
}
help << optPrefix << option->name;
if (option->type == otOption)
{
QString arg = QString("%1%2").arg(
((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
nameLength += arg.length();
help << arg;
}
help << " " << QString(helpIndent - nameLength - 1, ' ');
help << option->doc << "\r\n";
}
}
return help.join("");
}
QString Parser::compileUsage(QString progName, bool useFlags)
{
QStringList usage;
usage << "Usage: " << progName;
QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix);
// options
QListIterator<OptionDef *> it(m_optionList);
while (it.hasNext())
{
OptionDef *option = it.next();
usage << " [";
if (!option->flag.isNull() && useFlags)
usage << flagPrefix << option->flag;
else
usage << optPrefix << option->name;
if (option->type == otOption)
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
usage << "]";
}
// arguments
QListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext())
{
PositionalDef *param = it2.next();
usage << " " << (param->required ? "<" : "[");
usage << param->metavar;
usage << (param->required ? ">" : "]");
}
return usage.join("");
}
// parsing
QHash<QString, QVariant> Parser::parse(QStringList argv)
{
QHash<QString, QVariant> map;
QStringListIterator it(argv);
QString programName = it.next();
QString optionPrefix;
QString flagPrefix;
QListIterator<PositionalDef *> positionals(m_positionals);
QStringList expecting;
getPrefix(optionPrefix, flagPrefix);
while (it.hasNext())
{
QString arg = it.next();
if (!expecting.isEmpty())
// we were expecting an argument
{
QString name = expecting.first();
/*
if (map.contains(name))
throw ParsingError(
QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
*/
map[name] = QVariant(arg);
expecting.removeFirst();
continue;
}
if (arg.startsWith(optionPrefix))
// we have an option
{
// qDebug("Found option %s", qPrintable(arg));
QString name = arg.mid(optionPrefix.length());
QString equals;
if ((m_argStyle == ArgumentStyle::Equals ||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
name.contains("="))
{
int i = name.indexOf("=");
equals = name.mid(i + 1);
name = name.left(i);
}
if (m_options.contains(name))
{
/*
if (map.contains(name))
throw ParsingError(QString("Option %2%1 was given multiple times")
.arg(name, optionPrefix));
*/
OptionDef *option = m_options[name];
if (option->type == otSwitch)
map[name] = true;
else // if (option->type == otOption)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(name);
else if (!equals.isNull())
map[name] = equals;
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(name);
else
throw ParsingError(QString("Option %2%1 reqires an argument.")
.arg(name, optionPrefix));
}
continue;
}
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
}
if (arg.startsWith(flagPrefix))
// we have (a) flag(s)
{
// qDebug("Found flags %s", qPrintable(arg));
QString flags = arg.mid(flagPrefix.length());
QString equals;
if ((m_argStyle == ArgumentStyle::Equals ||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
flags.contains("="))
{
int i = flags.indexOf("=");
equals = flags.mid(i + 1);
flags = flags.left(i);
}
for (int i = 0; i < flags.length(); i++)
{
QChar flag = flags.at(i);
if (!m_flags.contains(flag))
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
OptionDef *option = m_flags[flag];
/*
if (map.contains(option->name))
throw ParsingError(QString("Option %2%1 was given multiple times")
.arg(option->name, optionPrefix));
*/
if (option->type == otSwitch)
map[option->name] = true;
else // if (option->type == otOption)
{
if (m_argStyle == ArgumentStyle::Space)
expecting.append(option->name);
else if (!equals.isNull())
if (i == flags.length() - 1)
map[option->name] = equals;
else
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option "
"%1 not last flag in %4%3")
.arg(option->name, flag, flags, flagPrefix));
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(option->name);
else
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)")
.arg(option->name, flag, flagPrefix));
}
}
continue;
}
// must be a positional argument
if (!positionals.hasNext())
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
PositionalDef *param = positionals.next();
map[param->name] = arg;
}
// check if we're missing something
if (!expecting.isEmpty())
throw ParsingError(QString("Was still expecting arguments for %2%1").arg(
expecting.join(QString(", ") + optionPrefix), optionPrefix));
while (positionals.hasNext())
{
PositionalDef *param = positionals.next();
if (param->required)
throw ParsingError(
QString("Missing required positional argument '%1'").arg(param->name));
else
map[param->name] = param->def;
}
// fill out gaps
QListIterator<OptionDef *> iter(m_optionList);
while (iter.hasNext())
{
OptionDef *option = iter.next();
if (!map.contains(option->name))
map[option->name] = option->def;
}
return map;
}
// clear defs
void Parser::clear()
{
m_flags.clear();
m_params.clear();
m_options.clear();
QMutableListIterator<OptionDef *> it(m_optionList);
while (it.hasNext())
{
OptionDef *option = it.next();
it.remove();
delete option;
}
QMutableListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext())
{
PositionalDef *arg = it2.next();
it2.remove();
delete arg;
}
}
// Destructor
Parser::~Parser()
{
clear();
}
// getPrefix
void Parser::getPrefix(QString &opt, QString &flag)
{
if (m_flagStyle == FlagStyle::Windows)
opt = flag = "/";
else if (m_flagStyle == FlagStyle::Unix)
opt = flag = "-";
// else if (m_flagStyle == FlagStyle::GNU)
else
{
opt = "--";
flag = "-";
}
}
// ParsingError
ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString())
{
}
}

View File

@ -0,0 +1,252 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <exception>
#include <stdexcept>
#include <QString>
#include <QVariant>
#include <QHash>
#include <QStringList>
#include "multimc_logic_export.h"
/**
* @file libutil/include/cmdutils.h
* @brief commandline parsing and processing utilities
*/
namespace Commandline
{
/**
* @brief split a string into argv items like a shell would do
* @param args the argument string
* @return a QStringList containing all arguments
*/
MULTIMC_LOGIC_EXPORT QStringList splitArgs(QString args);
/**
* @brief The FlagStyle enum
* Specifies how flags are decorated
*/
namespace FlagStyle
{
enum Enum
{
GNU, /**< --option and -o (GNU Style) */
Unix, /**< -option and -o (Unix Style) */
Windows, /**< /option and /o (Windows Style) */
#ifdef Q_OS_WIN32
Default = Windows
#else
Default = GNU
#endif
};
}
/**
* @brief The ArgumentStyle enum
*/
namespace ArgumentStyle
{
enum Enum
{
Space, /**< --option=value */
Equals, /**< --option value */
SpaceAndEquals, /**< --option[= ]value */
#ifdef Q_OS_WIN32
Default = Equals
#else
Default = SpaceAndEquals
#endif
};
}
/**
* @brief The ParsingError class
*/
class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
{
public:
ParsingError(const QString &what);
};
/**
* @brief The Parser class
*/
class MULTIMC_LOGIC_EXPORT Parser
{
public:
/**
* @brief Parser constructor
* @param flagStyle the FlagStyle to use in this Parser
* @param argStyle the ArgumentStyle to use in this Parser
*/
Parser(FlagStyle::Enum flagStyle = FlagStyle::Default,
ArgumentStyle::Enum argStyle = ArgumentStyle::Default);
/**
* @brief set the flag style
* @param style
*/
void setFlagStyle(FlagStyle::Enum style);
/**
* @brief get the flag style
* @return
*/
FlagStyle::Enum flagStyle();
/**
* @brief set the argument style
* @param style
*/
void setArgumentStyle(ArgumentStyle::Enum style);
/**
* @brief get the argument style
* @return
*/
ArgumentStyle::Enum argumentStyle();
/**
* @brief define a boolean switch
* @param name the parameter name
* @param def the default value
*/
void addSwitch(QString name, bool def = false);
/**
* @brief define an option that takes an additional argument
* @param name the parameter name
* @param def the default value
*/
void addOption(QString name, QVariant def = QVariant());
/**
* @brief define a positional argument
* @param name the parameter name
* @param required wether this argument is required
* @param def the default value
*/
void addArgument(QString name, bool required = true, QVariant def = QVariant());
/**
* @brief adds a flag to an existing parameter
* @param name the (existing) parameter name
* @param flag the flag character
* @see addSwitch addArgument addOption
* Note: any one parameter can only have one flag
*/
void addShortOpt(QString name, QChar flag);
/**
* @brief adds documentation to a Parameter
* @param name the parameter name
* @param metavar a string to be displayed as placeholder for the value
* @param doc a QString containing the documentation
* Note: on positional arguments, metavar replaces the name as displayed.
* on options , metavar replaces the value placeholder
*/
void addDocumentation(QString name, QString doc, QString metavar = QString());
/**
* @brief generate a help message
* @param progName the program name to use in the help message
* @param helpIndent how much the parameter documentation should be indented
* @param flagsInUsage whether we should use flags instead of options in the usage
* @return a help message
*/
QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true);
/**
* @brief generate a short usage message
* @param progName the program name to use in the usage message
* @param useFlags whether we should use flags instead of options
* @return a usage message
*/
QString compileUsage(QString progName, bool useFlags = true);
/**
* @brief parse
* @param argv a QStringList containing the program ARGV
* @return a QHash mapping argument names to their values
*/
QHash<QString, QVariant> parse(QStringList argv);
/**
* @brief clear all definitions
*/
void clear();
~Parser();
private:
FlagStyle::Enum m_flagStyle;
ArgumentStyle::Enum m_argStyle;
enum OptionType
{
otSwitch,
otOption
};
// Important: the common part MUST BE COMMON ON ALL THREE structs
struct CommonDef
{
QString name;
QString doc;
QString metavar;
QVariant def;
};
struct OptionDef
{
// common
QString name;
QString doc;
QString metavar;
QVariant def;
// option
OptionType type;
QChar flag;
};
struct PositionalDef
{
// common
QString name;
QString doc;
QString metavar;
QVariant def;
// positional
bool required;
};
QHash<QString, OptionDef *> m_options;
QHash<QChar, OptionDef *> m_flags;
QHash<QString, CommonDef *> m_params;
QList<PositionalDef *> m_positionals;
QList<OptionDef *> m_optionList;
void getPrefix(QString &opt, QString &flag);
};
}

View File

@ -0,0 +1,35 @@
#pragma once
template <typename T>
class DefaultVariable
{
public:
DefaultVariable(const T & value)
{
defaultValue = value;
}
DefaultVariable<T> & operator =(const T & value)
{
currentValue = value;
is_default = currentValue == defaultValue;
is_explicit = true;
return *this;
}
operator const T &() const
{
return is_default ? defaultValue : currentValue;
}
bool isDefault() const
{
return is_default;
}
bool isExplicit() const
{
return is_explicit;
}
private:
T currentValue;
T defaultValue;
bool is_default = true;
bool is_explicit = false;
};

222
libraries/logic/Env.cpp Normal file
View File

@ -0,0 +1,222 @@
#include "Env.h"
#include "net/HttpMetaCache.h"
#include "BaseVersion.h"
#include "BaseVersionList.h"
#include <QDir>
#include <QNetworkProxy>
#include <QNetworkAccessManager>
#include <QDebug>
#include "tasks/Task.h"
#include "wonko/WonkoIndex.h"
#include <QDebug>
/*
* The *NEW* global rat nest of an object. Handle with care.
*/
Env::Env()
{
m_qnam = std::make_shared<QNetworkAccessManager>();
}
void Env::destroy()
{
m_metacache.reset();
m_qnam.reset();
m_versionLists.clear();
}
Env& Env::Env::getInstance()
{
static Env instance;
return instance;
}
std::shared_ptr< HttpMetaCache > Env::metacache()
{
Q_ASSERT(m_metacache != nullptr);
return m_metacache;
}
std::shared_ptr< QNetworkAccessManager > Env::qnam()
{
return m_qnam;
}
/*
class NullVersion : public BaseVersion
{
Q_OBJECT
public:
virtual QString name()
{
return "null";
}
virtual QString descriptor()
{
return "null";
}
virtual QString typeString() const
{
return "Null";
}
};
class NullTask: public Task
{
Q_OBJECT
public:
virtual void executeTask()
{
emitFailed(tr("Nothing to do."));
}
};
class NullVersionList: public BaseVersionList
{
Q_OBJECT
public:
virtual const BaseVersionPtr at(int i) const
{
return std::make_shared<NullVersion>();
}
virtual int count() const
{
return 0;
};
virtual Task* getLoadTask()
{
return new NullTask;
}
virtual bool isLoaded()
{
return false;
}
virtual void sort()
{
}
virtual void updateListData(QList< BaseVersionPtr >)
{
}
};
*/
BaseVersionPtr Env::getVersion(QString component, QString version)
{
auto list = getVersionList(component);
if(!list)
{
return nullptr;
}
return list->findVersion(version);
}
std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
{
auto iter = m_versionLists.find(component);
if(iter != m_versionLists.end())
{
return *iter;
}
//return std::make_shared<NullVersionList>();
return nullptr;
}
void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
{
m_versionLists[name] = vlist;
}
std::shared_ptr<WonkoIndex> Env::wonkoIndex()
{
if (!m_wonkoIndex)
{
m_wonkoIndex = std::make_shared<WonkoIndex>();
}
return m_wonkoIndex;
}
void Env::initHttpMetaCache()
{
m_metacache.reset(new HttpMetaCache("metacache"));
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath());
m_metacache->Load();
}
void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password)
{
// Set the application proxy settings.
if (proxyTypeStr == "SOCKS5")
{
QNetworkProxy::setApplicationProxy(
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password));
}
else if (proxyTypeStr == "HTTP")
{
QNetworkProxy::setApplicationProxy(
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password));
}
else if (proxyTypeStr == "None")
{
// If we have no proxy set, set no proxy and return.
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy));
}
else
{
// If we have "Default" selected, set Qt to use the system proxy settings.
QNetworkProxyFactory::setUseSystemConfiguration(true);
}
qDebug() << "Detecting proxy settings...";
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
if (m_qnam.get())
m_qnam->setProxy(proxy);
QString proxyDesc;
if (proxy.type() == QNetworkProxy::NoProxy)
{
qDebug() << "Using no proxy is an option!";
return;
}
switch (proxy.type())
{
case QNetworkProxy::DefaultProxy:
proxyDesc = "Default proxy: ";
break;
case QNetworkProxy::Socks5Proxy:
proxyDesc = "Socks5 proxy: ";
break;
case QNetworkProxy::HttpProxy:
proxyDesc = "HTTP proxy: ";
break;
case QNetworkProxy::HttpCachingProxy:
proxyDesc = "HTTP caching: ";
break;
case QNetworkProxy::FtpCachingProxy:
proxyDesc = "FTP caching: ";
break;
default:
proxyDesc = "DERP proxy: ";
break;
}
proxyDesc += QString("%3@%1:%2 pass %4")
.arg(proxy.hostName())
.arg(proxy.port())
.arg(proxy.user())
.arg(proxy.password());
qDebug() << proxyDesc;
}
#include "Env.moc"

60
libraries/logic/Env.h Normal file
View File

@ -0,0 +1,60 @@
#pragma once
#include <memory>
#include <QString>
#include <QMap>
#include "multimc_logic_export.h"
class QNetworkAccessManager;
class HttpMetaCache;
class BaseVersionList;
class BaseVersion;
class WonkoIndex;
#if defined(ENV)
#undef ENV
#endif
#define ENV (Env::getInstance())
class MULTIMC_LOGIC_EXPORT Env
{
friend class MultiMC;
private:
Env();
public:
static Env& getInstance();
// call when Qt stuff is being torn down
void destroy();
std::shared_ptr<QNetworkAccessManager> qnam();
std::shared_ptr<HttpMetaCache> metacache();
/// init the cache. FIXME: possible future hook point
void initHttpMetaCache();
/// Updates the application proxy settings from the settings object.
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
/// get a version list by name
std::shared_ptr<BaseVersionList> getVersionList(QString component);
/// get a version by list name and version name
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
std::shared_ptr<WonkoIndex> wonkoIndex();
QString wonkoRootUrl() const { return m_wonkoRootUrl; }
void setWonkoRootUrl(const QString &url) { m_wonkoRootUrl = url; }
protected:
std::shared_ptr<QNetworkAccessManager> m_qnam;
std::shared_ptr<HttpMetaCache> m_metacache;
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
std::shared_ptr<WonkoIndex> m_wonkoIndex;
QString m_wonkoRootUrl;
};

View File

@ -0,0 +1,34 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QString>
#include <QDebug>
#include <exception>
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT Exception : public std::exception
{
public:
Exception(const QString &message) : std::exception(), m_message(message)
{
qCritical() << "Exception:" << message;
}
Exception(const Exception &other)
: std::exception(), m_message(other.cause())
{
}
virtual ~Exception() noexcept {}
const char *what() const noexcept
{
return m_message.toLatin1().constData();
}
QString cause() const
{
return m_message;
}
private:
QString m_message;
};

View File

@ -0,0 +1,436 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "FileSystem.h"
#include <QDir>
#include <QSaveFile>
#include <QFileInfo>
#include <QDebug>
#include <QUrl>
#include <QStandardPaths>
namespace FS {
void ensureExists(const QDir &dir)
{
if (!QDir().mkpath(dir.absolutePath()))
{
throw FileSystemException("Unable to create directory " + dir.dirName() + " (" +
dir.absolutePath() + ")");
}
}
void write(const QString &filename, const QByteArray &data)
{
ensureExists(QFileInfo(filename).dir());
QSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly))
{
throw FileSystemException("Couldn't open " + filename + " for writing: " +
file.errorString());
}
if (data.size() != file.write(data))
{
throw FileSystemException("Error writing data to " + filename + ": " +
file.errorString());
}
if (!file.commit())
{
throw FileSystemException("Error while committing data to " + filename + ": " +
file.errorString());
}
}
QByteArray read(const QString &filename)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly))
{
throw FileSystemException("Unable to open " + filename + " for reading: " +
file.errorString());
}
const qint64 size = file.size();
QByteArray data(int(size), 0);
const qint64 ret = file.read(data.data(), size);
if (ret == -1 || ret != size)
{
throw FileSystemException("Error reading data from " + filename + ": " +
file.errorString());
}
return data;
}
bool ensureFilePathExists(QString filenamepath)
{
QFileInfo a(filenamepath);
QDir dir;
QString ensuredPath = a.path();
bool success = dir.mkpath(ensuredPath);
return success;
}
bool ensureFolderPathExists(QString foldernamepath)
{
QFileInfo a(foldernamepath);
QDir dir;
QString ensuredPath = a.filePath();
bool success = dir.mkpath(ensuredPath);
return success;
}
bool copy::operator()(const QString &offset)
{
//NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32
m_followSymlinks = true;
#endif
auto src = PathCombine(m_src.absolutePath(), offset);
auto dst = PathCombine(m_dst.absolutePath(), offset);
QFileInfo currentSrc(src);
if (!currentSrc.exists())
return false;
if(!m_followSymlinks && currentSrc.isSymLink())
{
qDebug() << "creating symlink" << src << " - " << dst;
if (!ensureFilePathExists(dst))
{
qWarning() << "Cannot create path!";
return false;
}
return QFile::link(currentSrc.symLinkTarget(), dst);
}
else if(currentSrc.isFile())
{
qDebug() << "copying file" << src << " - " << dst;
if (!ensureFilePathExists(dst))
{
qWarning() << "Cannot create path!";
return false;
}
return QFile::copy(src, dst);
}
else if(currentSrc.isDir())
{
qDebug() << "recursing" << offset;
if (!ensureFolderPathExists(dst))
{
qWarning() << "Cannot create path!";
return false;
}
QDir currentDir(src);
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
{
auto inner_offset = PathCombine(offset, f);
// ignore and skip stuff that matches the blacklist.
if(m_blacklist && m_blacklist->matches(inner_offset))
{
continue;
}
if(!operator()(inner_offset))
{
return false;
}
}
}
else
{
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
return false;
}
return true;
}
#if defined Q_OS_WIN32
#include <windows.h>
#include <string>
#endif
bool deletePath(QString path)
{
bool OK = true;
QDir dir(path);
if (!dir.exists())
{
return OK;
}
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
QDir::AllDirs | QDir::Files,
QDir::DirsFirst);
for(auto & info: allEntries)
{
#if defined Q_OS_WIN32
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
auto wString = nativePath.toStdWString();
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
// Windows: check for junctions, reparse points and other nasty things of that sort
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
{
if (info.isFile())
{
OK &= QFile::remove(info.absoluteFilePath());
}
else if (info.isDir())
{
OK &= dir.rmdir(info.absoluteFilePath());
}
}
#else
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
if(info.isSymLink())
{
OK &= QFile::remove(info.absoluteFilePath());
}
#endif
else if (info.isDir())
{
OK &= deletePath(info.absoluteFilePath());
}
else if (info.isFile())
{
OK &= QFile::remove(info.absoluteFilePath());
}
else
{
OK = false;
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
}
}
OK &= dir.rmdir(dir.absolutePath());
return OK;
}
QString PathCombine(QString path1, QString path2)
{
if(!path1.size())
return path2;
if(!path2.size())
return path1;
return QDir::cleanPath(path1 + QDir::separator() + path2);
}
QString PathCombine(QString path1, QString path2, QString path3)
{
return PathCombine(PathCombine(path1, path2), path3);
}
QString AbsolutePath(QString path)
{
return QFileInfo(path).absolutePath();
}
QString ResolveExecutable(QString path)
{
if (path.isEmpty())
{
return QString();
}
if(!path.contains('/'))
{
path = QStandardPaths::findExecutable(path);
}
QFileInfo pathInfo(path);
if(!pathInfo.exists() || !pathInfo.isExecutable())
{
return QString();
}
return pathInfo.absoluteFilePath();
}
/**
* Normalize path
*
* Any paths inside the current directory will be normalized to relative paths (to current)
* Other paths will be made absolute
*/
QString NormalizePath(QString path)
{
QDir a = QDir::currentPath();
QString currentAbsolute = a.absolutePath();
QDir b(path);
QString newAbsolute = b.absolutePath();
if (newAbsolute.startsWith(currentAbsolute))
{
return a.relativeFilePath(newAbsolute);
}
else
{
return newAbsolute;
}
}
QString badFilenameChars = "\"\\/?<>:*|!";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
for (int i = 0; i < string.length(); i++)
{
if (badFilenameChars.contains(string[i]))
{
string[i] = replaceWith;
}
}
return string;
}
QString DirNameFromString(QString string, QString inDir)
{
int num = 0;
QString baseName = RemoveInvalidFilenameChars(string, '-');
QString dirName;
do
{
if(num == 0)
{
dirName = baseName;
}
else
{
dirName = baseName + QString::number(num);;
}
// If it's over 9000
if (num > 9000)
return "";
num++;
} while (QFileInfo(PathCombine(inDir, dirName)).exists());
return dirName;
}
// Does the directory path contain any '!'? If yes, return true, otherwise false.
// (This is a problem for Java)
bool checkProblemticPathJava(QDir folder)
{
QString pathfoldername = folder.absolutePath();
return pathfoldername.contains("!", Qt::CaseInsensitive);
}
#include <QStandardPaths>
#include <QFile>
#include <QTextStream>
// Win32 crap
#if defined Q_OS_WIN
#include <windows.h>
#include <winnls.h>
#include <shobjidl.h>
#include <objbase.h>
#include <objidl.h>
#include <shlguid.h>
#include <shlobj.h>
bool called_coinit = false;
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
{
HRESULT hres;
if (!called_coinit)
{
hres = CoInitialize(NULL);
called_coinit = true;
if (!SUCCEEDED(hres))
{
qWarning("Failed to initialize COM. Error 0x%08X", hres);
return hres;
}
}
IShellLink *link;
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
(LPVOID *)&link);
if (SUCCEEDED(hres))
{
IPersistFile *persistFile;
link->SetPath(targetPath);
link->SetArguments(args);
hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
if (SUCCEEDED(hres))
{
WCHAR wstr[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
hres = persistFile->Save(wstr, TRUE);
persistFile->Release();
}
link->Release();
}
return hres;
}
#endif
QString getDesktopDir()
{
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
}
// Cross-platform Shortcut creation
bool createShortCut(QString location, QString dest, QStringList args, QString name,
QString icon)
{
#if defined Q_OS_LINUX
location = PathCombine(location, name + ".desktop");
QFile f(location);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
QString argstring;
if (!args.empty())
argstring = " '" + args.join("' '") + "'";
stream << "[Desktop Entry]"
<< "\n";
stream << "Type=Application"
<< "\n";
stream << "TryExec=" << dest.toLocal8Bit() << "\n";
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
stream << "Icon=" << icon.toLocal8Bit() << "\n";
stream.flush();
f.close();
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
QFileDevice::ExeOther);
return true;
#elif defined Q_OS_WIN
// TODO: Fix
// QFile file(PathCombine(location, name + ".lnk"));
// WCHAR *file_w;
// WCHAR *dest_w;
// WCHAR *args_w;
// file.fileName().toWCharArray(file_w);
// dest.toWCharArray(dest_w);
// QString argStr;
// for (int i = 0; i < args.count(); i++)
// {
// argStr.append(args[i]);
// argStr.append(" ");
// }
// argStr.toWCharArray(args_w);
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
return false;
#else
qWarning("Desktop Shortcuts not supported on your platform!");
return false;
#endif
}
}

View File

@ -0,0 +1,123 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include "Exception.h"
#include "pathmatcher/IPathMatcher.h"
#include "multimc_logic_export.h"
#include <QDir>
#include <QFlags>
namespace FS
{
class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
{
public:
FileSystemException(const QString &message) : Exception(message) {}
};
/**
* write data to a file safely
*/
MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data);
/**
* read data from a file safely\
*/
MULTIMC_LOGIC_EXPORT QByteArray read(const QString &filename);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a file name and is ignored!
*/
MULTIMC_LOGIC_EXPORT bool ensureFilePathExists(QString filenamepath);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
class MULTIMC_LOGIC_EXPORT copy
{
public:
copy(const copy&) = delete;
copy(const QString & src, const QString & dst)
{
m_src = src;
m_dst = dst;
}
copy & followSymlinks(const bool follow)
{
m_followSymlinks = follow;
return *this;
}
copy & blacklist(const IPathMatcher * filter)
{
m_blacklist = filter;
return *this;
}
bool operator()()
{
return operator()(QString());
}
private:
bool operator()(const QString &offset);
private:
bool m_followSymlinks = true;
const IPathMatcher * m_blacklist = nullptr;
QDir m_src;
QDir m_dst;
};
/**
* Delete a folder recursively
*/
MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
/**
* Resolve an executable
*
* Will resolve:
* single executable (by name)
* relative path
* absolute path
*
* @return absolute path to executable or null string
*/
MULTIMC_LOGIC_EXPORT QString ResolveExecutable(QString path);
/**
* Normalize path
*
* Any paths inside the current directory will be normalized to relative paths (to current)
* Other paths will be made absolute
*
* Returns false if the path logic somehow filed (and normalizedPath in invalid)
*/
MULTIMC_LOGIC_EXPORT QString NormalizePath(QString path);
MULTIMC_LOGIC_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
MULTIMC_LOGIC_EXPORT QString DirNameFromString(QString string, QString inDir = ".");
/// Checks if the a given Path contains "!"
MULTIMC_LOGIC_EXPORT bool checkProblemticPathJava(QDir folder);
// Get the Directory representing the User's Desktop
MULTIMC_LOGIC_EXPORT QString getDesktopDir();
// Create a shortcut at *location*, pointing to *dest* called with the arguments *args*
// call it *name* and assign it the icon *icon*
// return true if operation succeeded
MULTIMC_LOGIC_EXPORT bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
}

115
libraries/logic/GZip.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "GZip.h"
#include <zlib.h>
#include <QByteArray>
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
{
if (compressedBytes.size() == 0)
{
uncompressedBytes = compressedBytes;
return true;
}
unsigned uncompLength = compressedBytes.size();
uncompressedBytes.clear();
uncompressedBytes.resize(uncompLength);
z_stream strm;
memset(&strm, 0, sizeof(strm));
strm.next_in = (Bytef *)compressedBytes.data();
strm.avail_in = compressedBytes.size();
bool done = false;
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
{
return false;
}
int err = Z_OK;
while (!done)
{
// If our output buffer is too small
if (strm.total_out >= uncompLength)
{
uncompressedBytes.resize(uncompLength * 2);
uncompLength *= 2;
}
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
strm.avail_out = uncompLength - strm.total_out;
// Inflate another chunk.
err = inflate(&strm, Z_SYNC_FLUSH);
if (err == Z_STREAM_END)
done = true;
else if (err != Z_OK)
{
break;
}
}
if (inflateEnd(&strm) != Z_OK || !done)
{
return false;
}
uncompressedBytes.resize(strm.total_out);
return true;
}
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
{
if (uncompressedBytes.size() == 0)
{
compressedBytes = uncompressedBytes;
return true;
}
unsigned compLength = std::min(uncompressedBytes.size(), 16);
compressedBytes.clear();
compressedBytes.resize(compLength);
z_stream zs;
memset(&zs, 0, sizeof(zs));
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
{
return false;
}
zs.next_in = (Bytef*)uncompressedBytes.data();
zs.avail_in = uncompressedBytes.size();
int ret;
compressedBytes.resize(uncompressedBytes.size());
unsigned offset = 0;
unsigned temp = 0;
do
{
auto remaining = compressedBytes.size() - offset;
if(remaining < 1)
{
compressedBytes.resize(compressedBytes.size() * 2);
}
zs.next_out = (Bytef *) (compressedBytes.data() + offset);
temp = zs.avail_out = compressedBytes.size() - offset;
ret = deflate(&zs, Z_FINISH);
offset += temp - zs.avail_out;
} while (ret == Z_OK);
compressedBytes.resize(offset);
if (deflateEnd(&zs) != Z_OK)
{
return false;
}
if (ret != Z_STREAM_END)
{
return false;
}
return true;
}

12
libraries/logic/GZip.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <QByteArray>
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT GZip
{
public:
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
};

View File

@ -0,0 +1,580 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QDir>
#include <QSet>
#include <QFile>
#include <QDirIterator>
#include <QThread>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QXmlStreamReader>
#include <QRegularExpression>
#include <QDebug>
#include "InstanceList.h"
#include "BaseInstance.h"
//FIXME: this really doesn't belong *here*
#include "minecraft/onesix/OneSixInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "minecraft/ftb/FTBPlugin.h"
#include "minecraft/MinecraftVersion.h"
#include "settings/INISettingsObject.h"
#include "NullInstance.h"
#include "FileSystem.h"
#include "pathmatcher/RegexpMatcher.h"
const static int GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
: QAbstractListModel(parent), m_instDir(instDir)
{
m_globalSettings = globalSettings;
if (!QDir::current().exists(m_instDir))
{
QDir::current().mkpath(m_instDir);
}
}
InstanceList::~InstanceList()
{
}
int InstanceList::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_instances.count();
}
QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(parent);
if (row < 0 || row >= m_instances.size())
return QModelIndex();
return createIndex(row, column, (void *)m_instances.at(row).get());
}
QVariant InstanceList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
switch (role)
{
case InstancePointerRole:
{
QVariant v = qVariantFromValue((void *)pdata);
return v;
}
case InstanceIDRole:
{
return pdata->id();
}
case Qt::DisplayRole:
{
return pdata->name();
}
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
}
case Qt::DecorationRole:
{
return pdata->iconKey();
}
// HACK: see GroupView.h in gui!
case GroupRole:
{
return pdata->group();
}
default:
break;
}
return QVariant();
}
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
{
Qt::ItemFlags f;
if (index.isValid())
{
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
return f;
}
void InstanceList::groupChanged()
{
// save the groups. save all of them.
saveGroupList();
}
QStringList InstanceList::getGroups()
{
return m_groups.toList();
}
void InstanceList::suspendGroupSaving()
{
suspendedGroupSave = true;
}
void InstanceList::resumeGroupSaving()
{
if(suspendedGroupSave)
{
suspendedGroupSave = false;
if(queuedGroupSave)
{
saveGroupList();
}
}
}
void InstanceList::deleteGroup(const QString& name)
{
for(auto & instance: m_instances)
{
auto instGroupName = instance->group();
if(instGroupName == name)
{
instance->setGroupPost(QString());
}
}
}
void InstanceList::saveGroupList()
{
if(suspendedGroupSave)
{
queuedGroupSave = true;
return;
}
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> groupMap;
for (auto instance : m_instances)
{
QString id = instance->id();
QString group = instance->group();
if (group.isEmpty())
continue;
// keep a list/set of groups for choosing
m_groups.insert(group);
if (!groupMap.count(group))
{
QSet<QString> set;
set.insert(id);
groupMap[group] = set;
}
else
{
QSet<QString> &set = groupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
{
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(QString("false")));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try
{
FS::write(groupFileName, doc.toJson());
}
catch(FS::FileSystemException & e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
{
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
return;
QByteArray jsonData;
try
{
jsonData = FS::read(groupFileName);
}
catch (FS::FileSystemException & e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
qWarning() << QString("Group '%1' in the group list should "
"be an object.")
.arg(groupName)
.toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
qWarning() << QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.")
.arg(groupName)
.toUtf8();
continue;
}
// keep a list/set of groups for choosing
m_groups.insert(groupName);
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
iter2++)
{
groupMap[(*iter2).toString()] = groupName;
}
}
}
InstanceList::InstListError InstanceList::loadList()
{
// load the instance groups
QMap<QString, QString> groupMap;
loadGroupList(groupMap);
QList<InstancePtr> tempList;
{
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
QDirIterator::FollowSymlinks);
while (iter.hasNext())
{
QString subDir = iter.next();
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
continue;
qDebug() << "Loading MultiMC instance from " << subDir;
InstancePtr instPtr;
auto error = loadInstance(instPtr, subDir);
if(!continueProcessInstance(instPtr, error, subDir, groupMap))
continue;
tempList.append(instPtr);
}
}
// FIXME: generalize
FTBPlugin::loadInstances(m_globalSettings, groupMap, tempList);
beginResetModel();
m_instances.clear();
for(auto inst: tempList)
{
inst->setParent(this);
connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
SLOT(propertiesChanged(BaseInstance *)));
connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this,
SLOT(instanceNuked(BaseInstance *)));
m_instances.append(inst);
}
endResetModel();
emit dataIsInvalid();
return NoError;
}
/// Clear all instances. Triggers notifications.
void InstanceList::clear()
{
beginResetModel();
saveGroupList();
m_instances.clear();
endResetModel();
emit dataIsInvalid();
}
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
{
m_instDir = value.toString();
loadList();
}
/// Add an instance. Triggers notifications, returns the new index
int InstanceList::add(InstancePtr t)
{
beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size());
m_instances.append(t);
t->setParent(this);
connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
SLOT(propertiesChanged(BaseInstance *)));
connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *)));
endInsertRows();
return count() - 1;
}
InstancePtr InstanceList::getInstanceById(QString instId) const
{
if(instId.isEmpty())
return InstancePtr();
for(auto & inst: m_instances)
{
if (inst->id() == instId)
{
return inst;
}
}
return InstancePtr();
}
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
{
return index(getInstIndex(getInstanceById(id).get()));
}
int InstanceList::getInstIndex(BaseInstance *inst) const
{
int count = m_instances.count();
for (int i = 0; i < count; i++)
{
if (inst == m_instances[i].get())
{
return i;
}
}
return -1;
}
bool InstanceList::continueProcessInstance(InstancePtr instPtr, const int error,
const QDir &dir, QMap<QString, QString> &groupMap)
{
if (error != InstanceList::NoLoadError && error != InstanceList::NotAnInstance)
{
QString errorMsg = QString("Failed to load instance %1: ")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
switch (error)
{
default:
errorMsg += QString("Unknown instance loader error %1").arg(error);
break;
}
qCritical() << errorMsg.toUtf8();
return false;
}
else if (!instPtr)
{
qCritical() << QString("Error loading instance %1. Instance loader returned null.")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
return false;
}
else
{
auto iter = groupMap.find(instPtr->id());
if (iter != groupMap.end())
{
instPtr->setGroupInitial((*iter));
}
qDebug() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath();
return true;
}
}
InstanceList::InstLoadError
InstanceList::loadInstance(InstancePtr &inst, const QString &instDir)
{
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instDir, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
QString inst_type = instanceSettings->get("InstanceType").toString();
// FIXME: replace with a map lookup, where instance classes register their types
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir));
}
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instDir));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instDir));
}
inst->init();
return NoLoadError;
}
InstanceList::InstCreateError
InstanceList::createInstance(InstancePtr &inst, BaseVersionPtr version, const QString &instDir)
{
QDir rootDir(instDir);
qDebug() << instDir.toUtf8();
if (!rootDir.exists() && !rootDir.mkpath("."))
{
qCritical() << "Can't create instance folder" << instDir;
return InstanceList::CantCreateDir;
}
if (!version)
{
qCritical() << "Can't create instance for non-existing MC version";
return InstanceList::NoSuchVersion;
}
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instDir, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(version);
if(minecraftVersion)
{
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(version);
instanceSettings->set("InstanceType", "OneSix");
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir));
inst->setIntendedVersionId(version->descriptor());
inst->init();
return InstanceList::NoCreateError;
}
return InstanceList::NoSuchVersion;
}
InstanceList::InstCreateError
InstanceList::copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance, const QString &instDir, bool copySaves)
{
QDir rootDir(instDir);
std::unique_ptr<IPathMatcher> matcher;
if(!copySaves)
{
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
matcherReal->caseSensitive(false);
matcher.reset(matcherReal);
}
qDebug() << instDir.toUtf8();
FS::copy folderCopy(oldInstance->instanceRoot(), instDir);
folderCopy.followSymlinks(false).blacklist(matcher.get());
if (!folderCopy())
{
FS::deletePath(instDir);
return InstanceList::CantCreateDir;
}
INISettingsObject settings_obj(FS::PathCombine(instDir, "instance.cfg"));
settings_obj.registerSetting("InstanceType", "Legacy");
QString inst_type = settings_obj.get("InstanceType").toString();
oldInstance->copy(instDir);
auto error = loadInstance(newInstance, instDir);
switch (error)
{
case NoLoadError:
return NoCreateError;
case NotAnInstance:
rootDir.removeRecursively();
return CantCreateDir;
default:
case UnknownLoadError:
rootDir.removeRecursively();
return UnknownCreateError;
}
}
void InstanceList::instanceNuked(BaseInstance *inst)
{
int i = getInstIndex(inst);
if (i != -1)
{
beginRemoveRows(QModelIndex(), i, i);
m_instances.removeAt(i);
endRemoveRows();
}
}
void InstanceList::propertiesChanged(BaseInstance *inst)
{
int i = getInstIndex(inst);
if (i != -1)
{
emit dataChanged(index(i), index(i));
}
}

View File

@ -0,0 +1,187 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QAbstractListModel>
#include <QSet>
#include "BaseInstance.h"
#include "multimc_logic_export.h"
class BaseInstance;
class QDir;
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{
Q_OBJECT
private:
void loadGroupList(QMap<QString, QString> &groupList);
void suspendGroupSaving();
void resumeGroupSaving();
public slots:
void saveGroupList();
public:
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
virtual ~InstanceList();
public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
enum AdditionalRoles
{
GroupRole = Qt::UserRole,
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
};
/*!
* \brief Error codes returned by functions in the InstanceList class.
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError
{
NoError = 0,
UnknownError
};
enum InstLoadError
{
NoLoadError = 0,
UnknownLoadError,
NotAnInstance
};
enum InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
QString instDir() const
{
return m_instDir;
}
/*!
* \brief Get the instance at index
*/
InstancePtr at(int i) const
{
return m_instances.at(i);
}
;
/*!
* \brief Get the count of loaded instances
*/
int count() const
{
return m_instances.count();
}
;
/// Clear all instances. Triggers notifications.
void clear();
/// Add an instance. Triggers notifications, returns the new index
int add(InstancePtr t);
/// Get an instance by ID
InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
// FIXME: instead of iterating through all instances and forming a set, keep the set around
QStringList getGroups();
void deleteGroup(const QString & name);
/*!
* \brief Creates a stub instance
*
* \param inst Pointer to store the created instance in.
* \param version Game version to use for the instance
* \param instDir The new instance's directory.
* \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created.
*/
InstCreateError createInstance(InstancePtr &inst, BaseVersionPtr version,
const QString &instDir);
/*!
* \brief Creates a copy of an existing instance with a new name
*
* \param newInstance Pointer to store the created instance in.
* \param oldInstance The instance to copy
* \param instDir The new instance's directory.
* \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created.
*/
InstCreateError copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance,
const QString &instDir, bool copySaves);
/*!
* \brief Loads an instance from the given directory.
* Checks the instance's INI file to figure out what the instance's type is first.
* \param inst Pointer to store the loaded instance in.
* \param instDir The instance's directory.
* \return An InstLoadError error code.
* - NotAnInstance if the given instance directory isn't a valid instance.
*/
InstLoadError loadInstance(InstancePtr &inst, const QString &instDir);
signals:
void dataIsInvalid();
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
/*!
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
private slots:
void propertiesChanged(BaseInstance *inst);
void instanceNuked(BaseInstance *inst);
void groupChanged();
private:
int getInstIndex(BaseInstance *inst) const;
public:
static bool continueProcessInstance(InstancePtr instPtr, const int error, const QDir &dir, QMap<QString, QString> &groupMap);
protected:
QString m_instDir;
QList<InstancePtr> m_instances;
QSet<QString> m_groups;
SettingsObjectPtr m_globalSettings;
bool suspendedGroupSave = false;
bool queuedGroupSave = false;
};

272
libraries/logic/Json.cpp Normal file
View File

@ -0,0 +1,272 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#include "Json.h"
#include <QFile>
#include "FileSystem.h"
#include <math.h>
namespace Json
{
void write(const QJsonDocument &doc, const QString &filename)
{
FS::write(filename, doc.toJson());
}
void write(const QJsonObject &object, const QString &filename)
{
write(QJsonDocument(object), filename);
}
void write(const QJsonArray &array, const QString &filename)
{
write(QJsonDocument(array), filename);
}
QByteArray toBinary(const QJsonObject &obj)
{
return QJsonDocument(obj).toBinaryData();
}
QByteArray toBinary(const QJsonArray &array)
{
return QJsonDocument(array).toBinaryData();
}
QByteArray toText(const QJsonObject &obj)
{
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
QByteArray toText(const QJsonArray &array)
{
return QJsonDocument(array).toJson(QJsonDocument::Compact);
}
static bool isBinaryJson(const QByteArray &data)
{
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
}
QJsonDocument requireDocument(const QByteArray &data, const QString &what)
{
if (isBinaryJson(data))
{
QJsonDocument doc = QJsonDocument::fromBinaryData(data);
if (doc.isNull())
{
throw JsonException(what + ": Invalid JSON (binary JSON detected)");
}
return doc;
}
else
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError)
{
throw JsonException(what + ": Error parsing JSON: " + error.errorString());
}
return doc;
}
}
QJsonDocument requireDocument(const QString &filename, const QString &what)
{
return requireDocument(FS::read(filename), what);
}
QJsonObject requireObject(const QJsonDocument &doc, const QString &what)
{
if (!doc.isObject())
{
throw JsonException(what + " is not an object");
}
return doc.object();
}
QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
{
if (!doc.isArray())
{
throw JsonException(what + " is not an array");
}
return doc.array();
}
void writeString(QJsonObject &to, const QString &key, const QString &value)
{
if (!value.isEmpty())
{
to.insert(key, value);
}
}
void writeStringList(QJsonObject &to, const QString &key, const QStringList &values)
{
if (!values.isEmpty())
{
QJsonArray array;
for(auto value: values)
{
array.append(value);
}
to.insert(key, array);
}
}
template<>
QJsonValue toJson<QUrl>(const QUrl &url)
{
return QJsonValue(url.toString(QUrl::FullyEncoded));
}
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data)
{
return QJsonValue(QString::fromLatin1(data.toHex()));
}
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
{
return QJsonValue(datetime.toString(Qt::ISODate));
}
template<>
QJsonValue toJson<QDir>(const QDir &dir)
{
return QDir::current().relativeFilePath(dir.absolutePath());
}
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid)
{
return uuid.toString();
}
template<>
QJsonValue toJson<QVariant>(const QVariant &variant)
{
return QJsonValue::fromVariant(variant);
}
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what)
{
const QString string = ensureIsType<QString>(value, what);
// ensure that the string can be safely cast to Latin1
if (string != QString::fromLatin1(string.toLatin1()))
{
throw JsonException(what + " is not encodable as Latin1");
}
return QByteArray::fromHex(string.toLatin1());
}
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
{
if (!value.isArray())
{
throw JsonException(what + " is not an array");
}
return value.toArray();
}
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what)
{
if (!value.isString())
{
throw JsonException(what + " is not a string");
}
return value.toString();
}
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
{
if (!value.isBool())
{
throw JsonException(what + " is not a bool");
}
return value.toBool();
}
template<> double requireIsType<double>(const QJsonValue &value, const QString &what)
{
if (!value.isDouble())
{
throw JsonException(what + " is not a double");
}
return value.toDouble();
}
template<> int requireIsType<int>(const QJsonValue &value, const QString &what)
{
const double doubl = requireIsType<double>(value, what);
if (fmod(doubl, 1) != 0)
{
throw JsonException(what + " is not an integer");
}
return int(doubl);
}
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
if (!datetime.isValid())
{
throw JsonException(what + " is not a ISO formatted date/time value");
}
return datetime;
}
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what)
{
const QString string = ensureIsType<QString>(value, what);
if (string.isEmpty())
{
return QUrl();
}
const QUrl url = QUrl(string, QUrl::StrictMode);
if (!url.isValid())
{
throw JsonException(what + " is not a correctly formatted URL");
}
return url;
}
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
// FIXME: does not handle invalid characters!
return QDir::current().absoluteFilePath(string);
}
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what)
{
const QString string = requireIsType<QString>(value, what);
const QUuid uuid = QUuid(string);
if (uuid.toString() != string) // converts back => valid
{
throw JsonException(what + " is not a valid UUID");
}
return uuid;
}
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
{
if (!value.isObject())
{
throw JsonException(what + " is not an object");
}
return value.toObject();
}
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what)
{
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value.toVariant();
}
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
{
if (value.isNull() || value.isUndefined())
{
throw JsonException(what + " is null or undefined");
}
return value;
}
}

249
libraries/logic/Json.h Normal file
View File

@ -0,0 +1,249 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QUrl>
#include <QDir>
#include <QUuid>
#include <QVariant>
#include <memory>
#include "Exception.h"
namespace Json
{
class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
{
public:
JsonException(const QString &message) : Exception(message) {}
};
/// @throw FileSystemException
void write(const QJsonDocument &doc, const QString &filename);
/// @throw FileSystemException
void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
void write(const QJsonArray &array, const QString &filename);
QByteArray toBinary(const QJsonObject &obj);
QByteArray toBinary(const QJsonArray &array);
QByteArray toText(const QJsonObject &obj);
QByteArray toText(const QJsonArray &array);
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
/////////////////// WRITING ////////////////////
void writeString(QJsonObject & to, const QString &key, const QString &value);
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
template<typename T>
QJsonValue toJson(const T &t)
{
return QJsonValue(t);
}
template<>
QJsonValue toJson<QUrl>(const QUrl &url);
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data);
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
template<>
QJsonValue toJson<QDir>(const QDir &dir);
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid);
template<>
QJsonValue toJson<QVariant>(const QVariant &variant);
template<typename T>
QJsonArray toJsonArray(const QList<T> &container)
{
QJsonArray array;
for (const T item : container)
{
array.append(toJson<T>(item));
}
return array;
}
////////////////// READING ////////////////////
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonValue &value, const QString &what = "Value");
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT double requireIsType<double>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT bool requireIsType<bool>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT int requireIsType<int>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QString requireIsType<QString>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QDir requireIsType<QDir>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what);
// the following functions are higher level functions, that make use of the above functions for
// type conversion
template <typename T>
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
{
if (value.isUndefined() || value.isNull())
{
return default_;
}
try
{
return requireIsType<T>(value, what);
}
catch (JsonException &)
{
return default_;
}
}
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return requireIsType<T>(parent.value(key), localWhat);
}
template <typename T>
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
return default_;
}
return ensureIsType<T>(parent.value(key), default_, localWhat);
}
template <typename T>
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
{
const QJsonArray array = requireArray(doc);
QVector<T> out;
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, "Document"));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
{
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
QVector<T> out;
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, what));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
{
if (value.isUndefined())
{
return default_;
}
return ensureIsArrayOf<T>(value, what);
}
/// @throw JsonException
template <typename T>
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return ensureIsArrayOf<T>(parent.value(key), localWhat);
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
return default_;
}
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
}
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
{ \
return requireIsType<TYPE>(value, what); \
} \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
{ \
return ensureIsType<TYPE>(value, default_, what); \
} \
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
{ \
return requireIsType<TYPE>(parent, key, what); \
} \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
{ \
return ensureIsType<TYPE>(parent, key, default_, what); \
}
JSON_HELPERFUNCTIONS(Array, QJsonArray)
JSON_HELPERFUNCTIONS(Object, QJsonObject)
JSON_HELPERFUNCTIONS(JsonValue, QJsonValue)
JSON_HELPERFUNCTIONS(String, QString)
JSON_HELPERFUNCTIONS(Boolean, bool)
JSON_HELPERFUNCTIONS(Double, double)
JSON_HELPERFUNCTIONS(Integer, int)
JSON_HELPERFUNCTIONS(DateTime, QDateTime)
JSON_HELPERFUNCTIONS(Url, QUrl)
JSON_HELPERFUNCTIONS(ByteArray, QByteArray)
JSON_HELPERFUNCTIONS(Dir, QDir)
JSON_HELPERFUNCTIONS(Uuid, QUuid)
JSON_HELPERFUNCTIONS(Variant, QVariant)
#undef JSON_HELPERFUNCTIONS
}
using JSONValidationError = Json::JsonException;

View File

@ -0,0 +1,76 @@
#include "MMCStrings.h"
/// TAKEN FROM Qt, because it doesn't expose it intelligently
static inline QChar getNextChar(const QString &s, int location)
{
return (location < s.length()) ? s.at(location) : QChar();
}
/// TAKEN FROM Qt, because it doesn't expose it intelligently
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
{
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
{
// skip spaces, tabs and 0's
QChar c1 = getNextChar(s1, l1);
while (c1.isSpace())
c1 = getNextChar(s1, ++l1);
QChar c2 = getNextChar(s2, l2);
while (c2.isSpace())
c2 = getNextChar(s2, ++l2);
if (c1.isDigit() && c2.isDigit())
{
while (c1.digitValue() == 0)
c1 = getNextChar(s1, ++l1);
while (c2.digitValue() == 0)
c2 = getNextChar(s2, ++l2);
int lookAheadLocation1 = l1;
int lookAheadLocation2 = l2;
int currentReturnValue = 0;
// find the last digit, setting currentReturnValue as we go if it isn't equal
for (QChar lookAhead1 = c1, lookAhead2 = c2;
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
{
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
if (!is1ADigit && !is2ADigit)
break;
if (!is1ADigit)
return -1;
if (!is2ADigit)
return 1;
if (currentReturnValue == 0)
{
if (lookAhead1 < lookAhead2)
{
currentReturnValue = -1;
}
else if (lookAhead1 > lookAhead2)
{
currentReturnValue = 1;
}
}
}
if (currentReturnValue != 0)
return currentReturnValue;
}
if (cs == Qt::CaseInsensitive)
{
if (!c1.isLower())
c1 = c1.toLower();
if (!c2.isLower())
c2 = c2.toLower();
}
int r = QString::localeAwareCompare(c1, c2);
if (r < 0)
return -1;
if (r > 0)
return 1;
}
// The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs);
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <QString>
#include "multimc_logic_export.h"
namespace Strings
{
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
}

491
libraries/logic/MMCZip.cpp Normal file
View File

@ -0,0 +1,491 @@
/*
Copyright (C) 2010 Roberto Pompermaier
Copyright (C) 2005-2014 Sergey A. Tachenov
Parts of this file were part of QuaZIP.
QuaZIP is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 of the License, or
(at your option) any later version.
QuaZIP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with QuaZIP. If not, see <http://www.gnu.org/licenses/>.
See COPYING file for the full LGPL text.
Original ZIP package is copyrighted by Gilles Vollant and contributors,
see quazip/(un)MMCZip.h files for details. Basically it's the zlib license.
*/
#include <quazip.h>
#include <JlCompress.h>
#include <quazipdir.h>
#include "MMCZip.h"
#include "FileSystem.h"
#include <QDebug>
bool copyData(QIODevice &inFile, QIODevice &outFile)
{
while (!inFile.atEnd())
{
char buf[4096];
qint64 readLen = inFile.read(buf, 4096);
if (readLen <= 0)
return false;
if (outFile.write(buf, readLen) != readLen)
return false;
}
return true;
}
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
{
return JlCompress::extractDir(fileCompressed, dir);
}
bool compressFile(QuaZip *zip, QString fileName, QString fileDest)
{
if (!zip)
{
return false;
}
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend &&
zip->getMode() != QuaZip::mdAdd)
{
return false;
}
QFile inFile;
inFile.setFileName(fileName);
if (!inFile.open(QIODevice::ReadOnly))
{
return false;
}
QuaZipFile outFile(zip);
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName())))
{
return false;
}
if (!copyData(inFile, outFile) || outFile.getZipError() != UNZ_OK)
{
return false;
}
outFile.close();
if (outFile.getZipError() != UNZ_OK)
{
return false;
}
inFile.close();
return true;
}
bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QString>& added, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
{
if (!zip) return false;
if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd)
{
return false;
}
QDir directory(dir);
if (!directory.exists())
{
return false;
}
QDir origDirectory(origDir);
if (dir != origDir)
{
QString internalDirName = origDirectory.relativeFilePath(dir);
if(!blacklist || !blacklist->covers(internalDirName))
{
QuaZipFile dirZipFile(zip);
auto dirPrefix = FS::PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0))
{
return false;
}
dirZipFile.close();
}
}
QFileInfoList files = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
for (auto file: files)
{
if(!file.isDir())
{
continue;
}
if(!compressSubDir(zip,file.absoluteFilePath(),origDir, added, prefix, blacklist))
{
return false;
}
}
files = directory.entryInfoList(QDir::Files);
for (auto file: files)
{
if(!file.isFile())
{
continue;
}
if(file.absoluteFilePath()==zip->getZipName())
{
continue;
}
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
if(blacklist && blacklist->covers(filename))
{
continue;
}
if(prefix.size())
{
filename = FS::PathCombine(prefix, filename);
}
added.insert(filename);
if (!compressFile(zip,file.absoluteFilePath(),filename))
{
return false;
}
}
return true;
}
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
std::function<bool(QString)> filter)
{
QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip);
QuaZipFile fileInsideMod(&modZip);
QuaZipFile zipOutFile(into);
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
{
QString filename = modZip.getCurrentFileName();
if (!filter(filename))
{
qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered";
continue;
}
if (contained.contains(filename))
{
qDebug() << "Skipping already contained file " << filename << " from "
<< from.fileName();
continue;
}
contained.insert(filename);
if (!fileInsideMod.open(QIODevice::ReadOnly))
{
qCritical() << "Failed to open " << filename << " from " << from.fileName();
return false;
}
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
{
qCritical() << "Failed to open " << filename << " in the jar";
fileInsideMod.close();
return false;
}
if (!copyData(fileInsideMod, zipOutFile))
{
zipOutFile.close();
fileInsideMod.close();
qCritical() << "Failed to copy data of " << filename << " into the jar";
return false;
}
zipOutFile.close();
fileInsideMod.close();
}
return true;
}
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
{
QuaZip zipOut(targetJarPath);
if (!zipOut.open(QuaZip::mdCreate))
{
QFile::remove(targetJarPath);
qCritical() << "Failed to open the minecraft.jar for modding";
return false;
}
// Files already added to the jar.
// These files will be skipped.
QSet<QString> addedFiles;
// Modify the jar
QListIterator<Mod> i(mods);
i.toBack();
while (i.hasPrevious())
{
const Mod &mod = i.previous();
// do not merge disabled mods.
if (!mod.enabled())
continue;
if (mod.type() == Mod::MOD_ZIPFILE)
{
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
return false;
}
}
else if (mod.type() == Mod::MOD_SINGLEFILE)
{
auto filename = mod.filename();
if (!compressFile(&zipOut, filename.absoluteFilePath(),
filename.fileName()))
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
return false;
}
addedFiles.insert(filename.fileName());
}
else if (mod.type() == Mod::MOD_FOLDER)
{
auto filename = mod.filename();
QString what_to_zip = filename.absoluteFilePath();
QDir dir(what_to_zip);
dir.cdUp();
QString parent_dir = dir.absolutePath();
if (!compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
return false;
}
qDebug() << "Adding folder " << filename.fileName() << " from "
<< filename.absoluteFilePath();
}
}
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
{
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to insert minecraft.jar contents.";
return false;
}
// Recompress the jar
zipOut.close();
if (zipOut.getZipError() != 0)
{
QFile::remove(targetJarPath);
qCritical() << "Failed to finalize minecraft.jar!";
return false;
}
return true;
}
bool MMCZip::noFilter(QString)
{
return true;
}
bool MMCZip::metaInfFilter(QString key)
{
if(key.contains("META-INF"))
{
return false;
}
return true;
}
bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
{
QuaZip zip(zipFile);
QDir().mkpath(QFileInfo(zipFile).absolutePath());
if(!zip.open(QuaZip::mdCreate))
{
QFile::remove(zipFile);
return false;
}
QSet<QString> added;
if (!compressSubDir(&zip, dir, dir, added, prefix, blacklist))
{
QFile::remove(zipFile);
return false;
}
zip.close();
if(zip.getZipError()!=0)
{
QFile::remove(zipFile);
return false;
}
return true;
}
QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
{
QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
return root;
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
QString result = findFileInZip(zip, what, root + fileName);
if(!result.isEmpty())
{
return result;
}
}
return QString();
}
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
{
QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
{
result.append(root);
return true;
}
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
findFilesInZip(zip, what, result, root + fileName);
}
return !result.isEmpty();
}
bool removeFile(QStringList listFile)
{
bool ret = true;
for (int i = 0; i < listFile.count(); i++)
{
ret &= QFile::remove(listFile.at(i));
}
return ret;
}
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
{
if(!zip)
return false;
if (zip->getMode() != QuaZip::mdUnzip)
return false;
if (!fileName.isEmpty())
zip->setCurrentFile(fileName);
QuaZipFile inFile(zip);
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
return false;
// Controllo esistenza cartella file risultato
QDir curDir;
if (fileDest.endsWith('/'))
{
if (!curDir.mkpath(fileDest))
{
return false;
}
}
else
{
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
{
return false;
}
}
QuaZipFileInfo64 info;
if (!zip->getCurrentFileInfo(&info))
return false;
QFile::Permissions srcPerm = info.getPermissions();
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
{
if (srcPerm != 0)
{
QFile(fileDest).setPermissions(srcPerm);
}
return true;
}
QFile outFile;
outFile.setFileName(fileDest);
if (!outFile.open(QIODevice::WriteOnly))
return false;
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
{
outFile.close();
removeFile(QStringList(fileDest));
return false;
}
outFile.close();
inFile.close();
if (inFile.getZipError() != UNZ_OK)
{
removeFile(QStringList(fileDest));
return false;
}
if (srcPerm != 0)
{
outFile.setPermissions(srcPerm);
}
return true;
}
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{
QDir directory(target);
QStringList extracted;
if (!zip->goToFirstFile())
{
return QStringList();
}
do
{
QString name = zip->getCurrentFileName();
if(!name.startsWith(subdir))
{
continue;
}
name.remove(0, subdir.size());
QString absFilePath = directory.absoluteFilePath(name);
if(name.isEmpty())
{
absFilePath += "/";
}
if (!extractFile(zip, "", absFilePath))
{
removeFile(extracted);
return QStringList();
}
extracted.append(absFilePath);
} while (zip->goToNextFile());
return extracted;
}

88
libraries/logic/MMCZip.h Normal file
View File

@ -0,0 +1,88 @@
#pragma once
#include <QString>
#include <QFileInfo>
#include <QSet>
#include "minecraft/Mod.h"
#include "SeparatorPrefixTree.h"
#include <functional>
#include "multimc_logic_export.h"
class QuaZip;
namespace MMCZip
{
/**
* Compress a subdirectory.
* \param parentZip Opened zip containing the parent directory.
* \param dir The full path to the directory to pack.
* \param parentDir The full path to the directory corresponding to the root of the ZIP.
* \param recursive Whether to pack sub-directories as well or only files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added,
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/**
* Compress a whole directory.
* \param fileCompressed The name of the archive.
* \param dir The directory to compress.
* \param recursive Whether to pack the subdirectories as well, or just regular files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressDir(QString zipFile, QString dir, QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/// filter function for @mergeZipFiles - passthrough
bool MULTIMC_LOGIC_EXPORT noFilter(QString key);
/// filter function for @mergeZipFiles - ignores METAINF
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key);
/**
* Merge two zip files, using a filter function
*/
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter);
/**
* take a source jar, add mods to it, resulting in target jar
*/
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if
* left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString());
/**
* Find a single file in archive by file name (not path)
*
* \return the path prefix where the file is
*/
QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
/**
* Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a single file to a destination
*
* \return true if it succeeds
*/
bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest);
/**
* Extract a subdirectory from an archive
*/
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
}

View File

@ -0,0 +1,90 @@
#pragma once
#include "BaseInstance.h"
class NullInstance: public BaseInstance
{
public:
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
:BaseInstance(globalSettings, settings, rootDir)
{
setFlag(BaseInstance::VersionBrokenFlag);
}
virtual ~NullInstance() {};
virtual bool setIntendedVersionId(QString) override
{
return false;
}
virtual void cleanupAfterRun() override
{
}
virtual QString currentVersionId() const override
{
return "Null";
};
virtual QString intendedVersionId() const override
{
return "Null";
};
virtual void init() override
{
};
virtual QString getStatusbarDescription() override
{
return tr("Unknown instance type");
};
virtual bool shouldUpdate() const override
{
return false;
};
virtual QSet< QString > traits() override
{
return {};
};
virtual QString instanceConfigFolder() const override
{
return instanceRoot();
};
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
{
return nullptr;
}
virtual std::shared_ptr< Task > createUpdateTask() override
{
return nullptr;
}
virtual std::shared_ptr<Task> createJarModdingTask() override
{
return nullptr;
}
virtual void setShouldUpdate(bool) override
{
};
virtual std::shared_ptr< BaseVersionList > versionList() const override
{
return nullptr;
};
virtual QProcessEnvironment createEnvironment() override
{
return QProcessEnvironment();
}
virtual QMap<QString, QString> getVariables() const override
{
return QMap<QString, QString>();
}
virtual IPathMatcher::Ptr getLogFileMatcher() override
{
return nullptr;
}
virtual QString getLogFileRoot() override
{
return instanceRoot();
}
virtual QString typeName() const override
{
return "Null";
}
bool canExport() const override
{
return false;
}
};

View File

@ -0,0 +1,78 @@
#pragma once
#include <memory>
#include <QObject>
namespace details
{
struct DeleteQObjectLater
{
void operator()(QObject *obj) const
{
obj->deleteLater();
}
};
}
/**
* A unique pointer class with unique pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
/**
* A shared pointer class with shared pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
template <typename T>
class shared_qobject_ptr
{
public:
shared_qobject_ptr(){}
shared_qobject_ptr(T * wrap)
{
reset(wrap);
}
shared_qobject_ptr(const shared_qobject_ptr<T>& other)
{
m_ptr = other.m_ptr;
}
template<typename Derived>
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
{
m_ptr = other.unwrap();
}
public:
void reset(T * wrap)
{
using namespace std::placeholders;
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
}
void reset()
{
m_ptr.reset();
}
T * get() const
{
return m_ptr.get();
}
T * operator->() const
{
return m_ptr.get();
}
T & operator*() const
{
return *m_ptr.get();
}
operator bool() const
{
return m_ptr.get() != nullptr;
}
const std::shared_ptr <T> unwrap() const
{
return m_ptr;
}
private:
std::shared_ptr <T> m_ptr;
};

View File

@ -0,0 +1,60 @@
#pragma once
template <typename K, typename V>
class RWStorage
{
public:
void add(K key, V value)
{
QWriteLocker l(&lock);
cache[key] = value;
stale_entries.remove(key);
}
V get(K key)
{
QReadLocker l(&lock);
if(cache.contains(key))
{
return cache[key];
}
else return V();
}
bool get(K key, V& value)
{
QReadLocker l(&lock);
if(cache.contains(key))
{
value = cache[key];
return true;
}
else return false;
}
bool has(K key)
{
QReadLocker l(&lock);
return cache.contains(key);
}
bool stale(K key)
{
QReadLocker l(&lock);
if(!cache.contains(key))
return true;
return stale_entries.contains(key);
}
void setStale(K key)
{
QReadLocker l(&lock);
if(cache.contains(key))
{
stale_entries.insert(key);
}
}
void clear()
{
QWriteLocker l(&lock);
cache.clear();
}
private:
QReadWriteLock lock;
QMap<K, V> cache;
QSet<K> stale_entries;
};

View File

@ -0,0 +1,111 @@
#include "RecursiveFileSystemWatcher.h"
#include <QRegularExpression>
#include <QDebug>
RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent)
: QObject(parent), m_watcher(new QFileSystemWatcher(this))
{
connect(m_watcher, &QFileSystemWatcher::fileChanged, this,
&RecursiveFileSystemWatcher::fileChange);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this,
&RecursiveFileSystemWatcher::directoryChange);
}
void RecursiveFileSystemWatcher::setRootDir(const QDir &root)
{
bool wasEnabled = m_isEnabled;
disable();
m_root = root;
setFiles(scanRecursive(m_root));
if (wasEnabled)
{
enable();
}
}
void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles)
{
bool wasEnabled = m_isEnabled;
disable();
m_watchFiles = watchFiles;
if (wasEnabled)
{
enable();
}
}
void RecursiveFileSystemWatcher::enable()
{
if (m_isEnabled)
{
return;
}
Q_ASSERT(m_root != QDir::root());
addFilesToWatcherRecursive(m_root);
m_isEnabled = true;
}
void RecursiveFileSystemWatcher::disable()
{
if (!m_isEnabled)
{
return;
}
m_isEnabled = false;
m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories());
}
void RecursiveFileSystemWatcher::setFiles(const QStringList &files)
{
if (files != m_files)
{
m_files = files;
emit filesChanged();
}
}
void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir)
{
m_watcher->addPath(dir.absolutePath());
for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
{
addFilesToWatcherRecursive(dir.absoluteFilePath(directory));
}
if (m_watchFiles)
{
for (const QFileInfo &info : dir.entryInfoList(QDir::Files))
{
m_watcher->addPath(info.absoluteFilePath());
}
}
}
QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory)
{
QStringList ret;
if(!m_matcher)
{
return {};
}
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden))
{
ret.append(scanRecursive(directory.absoluteFilePath(dir)));
}
for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden))
{
auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file));
if (m_matcher->matches(relPath))
{
ret.append(relPath);
}
}
return ret;
}
void RecursiveFileSystemWatcher::fileChange(const QString &path)
{
emit fileChanged(path);
}
void RecursiveFileSystemWatcher::directoryChange(const QString &path)
{
setFiles(scanRecursive(m_root));
}

View File

@ -0,0 +1,63 @@
#pragma once
#include <QFileSystemWatcher>
#include <QDir>
#include "pathmatcher/IPathMatcher.h"
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject
{
Q_OBJECT
public:
RecursiveFileSystemWatcher(QObject *parent);
void setRootDir(const QDir &root);
QDir rootDir() const
{
return m_root;
}
// WARNING: setting this to true may be bad for performance
void setWatchFiles(const bool watchFiles);
bool watchFiles() const
{
return m_watchFiles;
}
void setMatcher(IPathMatcher::Ptr matcher)
{
m_matcher = matcher;
}
QStringList files() const
{
return m_files;
}
signals:
void filesChanged();
void fileChanged(const QString &path);
public slots:
void enable();
void disable();
private:
QDir m_root;
bool m_watchFiles = false;
bool m_isEnabled = false;
IPathMatcher::Ptr m_matcher;
QFileSystemWatcher *m_watcher;
QStringList m_files;
void setFiles(const QStringList &files);
void addFilesToWatcherRecursive(const QDir &dir);
QStringList scanRecursive(const QDir &dir);
private slots:
void fileChange(const QString &path);
void directoryChange(const QString &path);
};

View File

@ -0,0 +1,298 @@
#pragma once
#include <QString>
#include <QMap>
#include <QStringList>
template <char Tseparator>
class SeparatorPrefixTree
{
public:
SeparatorPrefixTree(QStringList paths)
{
insert(paths);
}
SeparatorPrefixTree(bool contained = false)
{
m_contained = contained;
}
void insert(QStringList paths)
{
for(auto &path: paths)
{
insert(path);
}
}
/// insert an exact path into the tree
SeparatorPrefixTree & insert(QString path)
{
auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1)
{
children[path] = SeparatorPrefixTree(true);
return children[path];
}
else
{
auto prefix = path.left(sepIndex);
if(!children.contains(prefix))
{
children[prefix] = SeparatorPrefixTree(false);
}
return children[prefix].insert(path.mid(sepIndex + 1));
}
}
/// is the path fully contained in the tree?
bool contains(QString path) const
{
auto node = find(path);
return node != nullptr;
}
/// does the tree cover a path? That means the prefix of the path is contained in the tree
bool covers(QString path) const
{
// if we found some valid node, it's good enough. the tree covers the path
if(m_contained)
{
return true;
}
auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1)
{
auto found = children.find(path);
if(found == children.end())
{
return false;
}
return (*found).covers(QString());
}
else
{
auto prefix = path.left(sepIndex);
auto found = children.find(prefix);
if(found == children.end())
{
return false;
}
return (*found).covers(path.mid(sepIndex + 1));
}
}
/// return the contained path that covers the path specified
QString cover(QString path) const
{
// if we found some valid node, it's good enough. the tree covers the path
if(m_contained)
{
return QString("");
}
auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1)
{
auto found = children.find(path);
if(found == children.end())
{
return QString();
}
auto nested = (*found).cover(QString());
if(nested.isNull())
{
return nested;
}
if(nested.isEmpty())
return path;
return path + Tseparator + nested;
}
else
{
auto prefix = path.left(sepIndex);
auto found = children.find(prefix);
if(found == children.end())
{
return QString();
}
auto nested = (*found).cover(path.mid(sepIndex + 1));
if(nested.isNull())
{
return nested;
}
if(nested.isEmpty())
return prefix;
return prefix + Tseparator + nested;
}
}
/// Does the path-specified node exist in the tree? It does not have to be contained.
bool exists(QString path) const
{
auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1)
{
auto found = children.find(path);
if(found == children.end())
{
return false;
}
return true;
}
else
{
auto prefix = path.left(sepIndex);
auto found = children.find(prefix);
if(found == children.end())
{
return false;
}
return (*found).exists(path.mid(sepIndex + 1));
}
}
/// find a node in the tree by name
const SeparatorPrefixTree * find(QString path) const
{
auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1)
{
auto found = children.find(path);
if(found == children.end())
{
return nullptr;
}
return &(*found);
}
else
{
auto prefix = path.left(sepIndex);
auto found = children.find(prefix);
if(found == children.end())
{
return nullptr;
}
return (*found).find(path.mid(sepIndex + 1));
}
}
/// is this a leaf node?
bool leaf() const
{
return children.isEmpty();
}
/// is this node actually contained in the tree, or is it purely structural?
bool contained() const
{
return m_contained;
}
/// Remove a path from the tree
bool remove(QString path)
{
return removeInternal(path) != Failed;
}
/// Clear all children of this node tree node
void clear()
{
children.clear();
}
QStringList toStringList() const
{
QStringList collected;
// collecting these is more expensive.
auto iter = children.begin();
while(iter != children.end())
{
QStringList list = iter.value().toStringList();
for(int i = 0; i < list.size(); i++)
{
list[i] = iter.key() + Tseparator + list[i];
}
collected.append(list);
if((*iter).m_contained)
{
collected.append(iter.key());
}
iter++;
}
return collected;
}
private:
enum Removal
{
Failed,
Succeeded,
HasChildren
};
Removal removeInternal(QString path = QString())
{
if(path.isEmpty())
{
if(!m_contained)
{
// remove all children - we are removing a prefix
clear();
return Succeeded;
}
m_contained = false;
if(children.size())
{
return HasChildren;
}
return Succeeded;
}
Removal remStatus = Failed;
QString childToRemove;
auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1)
{
childToRemove = path;
auto found = children.find(childToRemove);
if(found == children.end())
{
return Failed;
}
remStatus = (*found).removeInternal();
}
else
{
childToRemove = path.left(sepIndex);
auto found = children.find(childToRemove);
if(found == children.end())
{
return Failed;
}
remStatus = (*found).removeInternal(path.mid(sepIndex + 1));
}
switch (remStatus)
{
case Failed:
case HasChildren:
{
return remStatus;
}
case Succeeded:
{
children.remove(childToRemove);
if(m_contained)
{
return HasChildren;
}
if(children.size())
{
return HasChildren;
}
return Succeeded;
}
}
return Failed;
}
private:
QMap<QString,SeparatorPrefixTree<Tseparator>> children;
bool m_contained = false;
};

View File

@ -0,0 +1,37 @@
#pragma once
namespace TypeMagic
{
/** "Cleans" the given type T by stripping references (&) and cv-qualifiers (const, volatile) from it
* const int => int
* QString & => QString
* const unsigned long long & => unsigned long long
*
* Usage:
* using Cleaned = Detail::CleanType<const int>;
* static_assert(std::is_same<Cleaned, int>, "Cleaned == int");
*/
// the order of remove_cv and remove_reference matters!
template <typename T>
using CleanType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
/// For functors (structs with operator()), including lambdas, which in **most** cases are functors
/// "Calls" Function<Ret(*)(Arg)> or Function<Ret(C::*)(Arg)>
template <typename T> struct Function : public Function<decltype(&T::operator())> {};
/// For function pointers (&function), including static members (&Class::member)
template <typename Ret, typename Arg> struct Function<Ret(*)(Arg)> : public Function<Ret(Arg)> {};
/// Default specialization used by others.
template <typename Ret, typename Arg> struct Function<Ret(Arg)>
{
using ReturnType = Ret;
using Argument = Arg;
};
/// For member functions. Also used by the lambda overload if the lambda captures [this]
template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg)> : public Function<Ret(Arg)> {};
template <class C, typename Ret, typename Arg> struct Function<Ret(C::*)(Arg) const> : public Function<Ret(Arg)> {};
/// Overload for references
template <typename F> struct Function<F&> : public Function<F> {};
/// Overload for rvalues
template <typename F> struct Function<F&&> : public Function<F> {};
// for more info: https://functionalcpp.wordpress.com/2013/08/05/function-traits/
}

140
libraries/logic/Version.cpp Normal file
View File

@ -0,0 +1,140 @@
#include "Version.h"
#include <QStringList>
#include <QUrl>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
Version::Version(const QString &str) : m_string(str)
{
parse();
}
bool Version::operator<(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
{
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2)
{
return sec1 < sec2;
}
}
return false;
}
bool Version::operator<=(const Version &other) const
{
return *this < other || *this == other;
}
bool Version::operator>(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
{
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2)
{
return sec1 > sec2;
}
}
return false;
}
bool Version::operator>=(const Version &other) const
{
return *this > other || *this == other;
}
bool Version::operator==(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
{
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2)
{
return false;
}
}
return true;
}
bool Version::operator!=(const Version &other) const
{
return !operator==(other);
}
void Version::parse()
{
m_sections.clear();
QStringList parts = m_string.split('.');
for (const auto part : parts)
{
m_sections.append(Section(part));
}
}
bool versionIsInInterval(const QString &version, const QString &interval)
{
return versionIsInInterval(Version(version), interval);
}
bool versionIsInInterval(const Version &version, const QString &interval)
{
if (interval.isEmpty() || version.toString() == interval)
{
return true;
}
// Interval notation is used
QRegularExpression exp(
"(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)]),?");
QRegularExpressionMatch match = exp.match(interval);
if (match.hasMatch())
{
const QChar start = match.captured("start").at(0);
const QChar end = match.captured("end").at(0);
const QString bottom = match.captured("bottom");
const QString top = match.captured("top");
// check if in range (bottom)
if (!bottom.isEmpty())
{
const auto bottomVersion = Version(bottom);
if ((start == '[') && !(version >= bottomVersion))
{
return false;
}
else if ((start == '(') && !(version > bottomVersion))
{
return false;
}
}
// check if in range (top)
if (!top.isEmpty())
{
const auto topVersion = Version(top);
if ((end == ']') && !(version <= topVersion))
{
return false;
}
else if ((end == ')') && !(version < topVersion))
{
return false;
}
}
return true;
}
return false;
}

110
libraries/logic/Version.h Normal file
View File

@ -0,0 +1,110 @@
#pragma once
#include <QString>
#include <QList>
#include "multimc_logic_export.h"
class QUrl;
struct MULTIMC_LOGIC_EXPORT Version
{
Version(const QString &str);
Version() {}
bool operator<(const Version &other) const;
bool operator<=(const Version &other) const;
bool operator>(const Version &other) const;
bool operator>=(const Version &other) const;
bool operator==(const Version &other) const;
bool operator!=(const Version &other) const;
QString toString() const
{
return m_string;
}
private:
QString m_string;
struct Section
{
explicit Section(const QString &fullString)
{
m_fullString = fullString;
int cutoff = m_fullString.size();
for(int i = 0; i < m_fullString.size(); i++)
{
if(!m_fullString[i].isDigit())
{
cutoff = i;
break;
}
}
auto numPart = m_fullString.leftRef(cutoff);
if(numPart.size())
{
numValid = true;
m_numPart = numPart.toInt();
}
auto stringPart = m_fullString.midRef(cutoff);
if(stringPart.size())
{
m_stringPart = stringPart.toString();
}
}
explicit Section() {}
bool numValid = false;
int m_numPart = 0;
QString m_stringPart;
QString m_fullString;
inline bool operator!=(const Section &other) const
{
if(numValid && other.numValid)
{
return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart;
}
else
{
return m_fullString != other.m_fullString;
}
}
inline bool operator<(const Section &other) const
{
if(numValid && other.numValid)
{
if(m_numPart < other.m_numPart)
return true;
if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart)
return true;
return false;
}
else
{
return m_fullString < other.m_fullString;
}
}
inline bool operator>(const Section &other) const
{
if(numValid && other.numValid)
{
if(m_numPart > other.m_numPart)
return true;
if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart)
return true;
return false;
}
else
{
return m_fullString > other.m_fullString;
}
}
};
QList<Section> m_sections;
void parse();
};
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);

View File

@ -0,0 +1,159 @@
#include "JavaChecker.h"
#include <FileSystem.h>
#include <Commandline.h>
#include <QFile>
#include <QProcess>
#include <QMap>
#include <QCoreApplication>
#include <QDebug>
JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
{
}
void JavaChecker::performCheck()
{
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
QStringList args;
process.reset(new QProcess());
if(m_args.size())
{
auto extraArgs = Commandline::splitArgs(m_args);
args.append(extraArgs);
}
if(m_minMem != 0)
{
args << QString("-Xms%1m").arg(m_minMem);
}
if(m_maxMem != 0)
{
args << QString("-Xmx%1m").arg(m_maxMem);
}
if(m_permGen != 64)
{
args << QString("-XX:PermSize=%1m").arg(m_permGen);
}
args.append({"-jar", checkerJar});
process->setArguments(args);
process->setProgram(m_path);
process->setProcessChannelMode(QProcess::SeparateChannels);
qDebug() << "Running java checker: " + m_path + args.join(" ");;
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
killTimer.setSingleShot(true);
killTimer.start(15000);
process->start();
}
void JavaChecker::stdoutReady()
{
QByteArray data = process->readAllStandardOutput();
QString added = QString::fromLocal8Bit(data);
added.remove('\r');
m_stdout += added;
}
void JavaChecker::stderrReady()
{
QByteArray data = process->readAllStandardError();
QString added = QString::fromLocal8Bit(data);
added.remove('\r');
m_stderr += added;
}
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
{
killTimer.stop();
QProcessPtr _process;
_process.swap(process);
JavaCheckResult result;
{
result.path = m_path;
result.id = m_id;
}
result.errorLog = m_stderr;
qDebug() << "STDOUT" << m_stdout;
qWarning() << "STDERR" << m_stderr;
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;
if (status == QProcess::CrashExit || exitcode == 1)
{
qDebug() << "Java checker failed!";
emit checkFinished(result);
return;
}
bool success = true;
QMap<QString, QString> results;
QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts);
for(QString line : lines)
{
line = line.trimmed();
auto parts = line.split('=', QString::SkipEmptyParts);
if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty())
{
success = false;
}
else
{
results.insert(parts[0], parts[1]);
}
}
if(!results.contains("os.arch") || !results.contains("java.version") || !success)
{
qDebug() << "Java checker failed - couldn't extract required information.";
emit checkFinished(result);
return;
}
auto os_arch = results["os.arch"];
auto java_version = results["java.version"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
result.valid = true;
result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
result.javaVersion = java_version;
qDebug() << "Java checker succeeded.";
emit checkFinished(result);
}
void JavaChecker::error(QProcess::ProcessError err)
{
if(err == QProcess::FailedToStart)
{
killTimer.stop();
qDebug() << "Java checker has failed to start.";
JavaCheckResult result;
{
result.path = m_path;
result.id = m_id;
}
emit checkFinished(result);
return;
}
}
void JavaChecker::timeout()
{
// NO MERCY. NO ABUSE.
if(process)
{
qDebug() << "Java checker has been killed by timeout.";
process->kill();
}
}

View File

@ -0,0 +1,54 @@
#pragma once
#include <QProcess>
#include <QTimer>
#include <memory>
#include "multimc_logic_export.h"
#include "JavaVersion.h"
class JavaChecker;
struct MULTIMC_LOGIC_EXPORT JavaCheckResult
{
QString path;
QString mojangPlatform;
QString realPlatform;
JavaVersion javaVersion;
QString errorLog;
bool valid = false;
bool is_64bit = false;
int id;
};
typedef std::shared_ptr<QProcess> QProcessPtr;
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
{
Q_OBJECT
public:
explicit JavaChecker(QObject *parent = 0);
void performCheck();
QString m_path;
QString m_args;
int m_id = 0;
int m_minMem = 0;
int m_maxMem = 0;
int m_permGen = 64;
signals:
void checkFinished(JavaCheckResult result);
private:
QProcessPtr process;
QTimer killTimer;
QString m_stdout;
QString m_stderr;
public
slots:
void timeout();
void finished(int exitcode, QProcess::ExitStatus);
void error(QProcess::ProcessError);
void stdoutReady();
void stderrReady();
};

View File

@ -0,0 +1,45 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "JavaCheckerJob.h"
#include <QDebug>
void JavaCheckerJob::partFinished(JavaCheckResult result)
{
num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
<< javacheckers.size();
emit progress(num_finished, javacheckers.size());
javaresults.replace(result.id, result);
if (num_finished == javacheckers.size())
{
emit finished(javaresults);
}
}
void JavaCheckerJob::executeTask()
{
qDebug() << m_job_name.toLocal8Bit() << " started.";
m_running = true;
for (auto iter : javacheckers)
{
javaresults.append(JavaCheckResult());
connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
iter->performCheck();
}
}

View File

@ -0,0 +1,84 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QtNetwork>
#include "JavaChecker.h"
#include "tasks/Task.h"
class JavaCheckerJob;
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
class JavaCheckerJob : public Task
{
Q_OBJECT
public:
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
bool addJavaCheckerAction(JavaCheckerPtr base)
{
javacheckers.append(base);
total_progress++;
// if this is already running, the action needs to be started right away!
if (isRunning())
{
setProgress(current_progress, total_progress);
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
base->performCheck();
}
return true;
}
JavaCheckerPtr operator[](int index)
{
return javacheckers[index];
}
;
JavaCheckerPtr first()
{
if (javacheckers.size())
return javacheckers[0];
return JavaCheckerPtr();
}
int size() const
{
return javacheckers.size();
}
virtual bool isRunning() const override
{
return m_running;
}
signals:
void started();
void finished(QList<JavaCheckResult>);
private slots:
void partFinished(JavaCheckResult result);
protected:
virtual void executeTask() override;
private:
QString m_job_name;
QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults;
qint64 current_progress = 0;
qint64 total_progress = 0;
int num_finished = 0;
bool m_running = false;
};

View File

@ -0,0 +1,28 @@
#include "JavaInstall.h"
#include <MMCStrings.h>
bool JavaInstall::operator<(const JavaInstall &rhs)
{
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
if(archCompare != 0)
return archCompare < 0;
if(id < rhs.id)
{
return true;
}
if(id > rhs.id)
{
return false;
}
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
}
bool JavaInstall::operator==(const JavaInstall &rhs)
{
return arch == rhs.arch && id == rhs.id && path == rhs.path;
}
bool JavaInstall::operator>(const JavaInstall &rhs)
{
return (!operator<(rhs)) && (!operator==(rhs));
}

View File

@ -0,0 +1,38 @@
#pragma once
#include "BaseVersion.h"
#include "JavaVersion.h"
struct JavaInstall : public BaseVersion
{
JavaInstall(){}
JavaInstall(QString id, QString arch, QString path)
: id(id), arch(arch), path(path)
{
}
virtual QString descriptor()
{
return id.toString();
}
virtual QString name()
{
return id.toString();
}
virtual QString typeString() const
{
return arch;
}
bool operator<(const JavaInstall & rhs);
bool operator==(const JavaInstall & rhs);
bool operator>(const JavaInstall & rhs);
JavaVersion id;
QString arch;
QString path;
bool recommended = false;
};
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;

View File

@ -0,0 +1,186 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QtNetwork>
#include <QtXml>
#include <QRegExp>
#include <QDebug>
#include "java/JavaInstallList.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaUtils.h"
#include "MMCStrings.h"
#include "minecraft/VersionFilterData.h"
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
{
}
Task *JavaInstallList::getLoadTask()
{
return new JavaListLoadTask(this);
}
const BaseVersionPtr JavaInstallList::at(int i) const
{
return m_vlist.at(i);
}
bool JavaInstallList::isLoaded()
{
return m_loaded;
}
int JavaInstallList::count() const
{
return m_vlist.count();
}
QVariant JavaInstallList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
switch (role)
{
case VersionPointerRole:
return qVariantFromValue(m_vlist[index.row()]);
case VersionIdRole:
return version->descriptor();
case VersionRole:
return version->id.toString();
case RecommendedRole:
return version->recommended;
case PathRole:
return version->path;
case ArchitectureRole:
return version->arch;
default:
return QVariant();
}
}
BaseVersionList::RoleList JavaInstallList::providesRoles() const
{
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
}
void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
{
beginResetModel();
m_vlist = versions;
m_loaded = true;
sortVersions();
if(m_vlist.size())
{
auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]);
best->recommended = true;
}
endResetModel();
}
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
{
auto rleft = std::dynamic_pointer_cast<JavaInstall>(left);
auto rright = std::dynamic_pointer_cast<JavaInstall>(right);
return (*rleft) > (*rright);
}
void JavaInstallList::sortVersions()
{
beginResetModel();
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
endResetModel();
}
JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task()
{
m_list = vlist;
m_currentRecommended = NULL;
}
JavaListLoadTask::~JavaListLoadTask()
{
}
void JavaListLoadTask::executeTask()
{
setStatus(tr("Detecting Java installations..."));
JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths();
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: ";
int id = 0;
for(QString candidate : candidate_paths)
{
qDebug() << " " << candidate;
auto candidate_checker = new JavaChecker();
candidate_checker->m_path = candidate;
candidate_checker->m_id = id;
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
id++;
}
m_job->start();
}
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
{
QList<JavaInstallPtr> candidates;
qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results)
{
if(result.valid)
{
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion;
javaVersion->arch = result.mojangPlatform;
javaVersion->path = result.path;
candidates.append(javaVersion);
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;
}
}
QList<BaseVersionPtr> javas_bvp;
for (auto java : candidates)
{
//qDebug() << java->id << java->arch << " at " << java->path;
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
if (bp_java)
{
javas_bvp.append(java);
}
}
m_list->updateListData(javas_bvp);
emitSucceeded();
}

View File

@ -0,0 +1,71 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QAbstractListModel>
#include "BaseVersionList.h"
#include "tasks/Task.h"
#include "JavaCheckerJob.h"
#include "JavaInstall.h"
#include "multimc_logic_export.h"
class JavaListLoadTask;
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
{
Q_OBJECT
public:
explicit JavaInstallList(QObject *parent = 0);
virtual Task *getLoadTask() override;
virtual bool isLoaded() override;
virtual const BaseVersionPtr at(int i) const override;
virtual int count() const override;
virtual void sortVersions() override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual RoleList providesRoles() const override;
public slots:
virtual void updateListData(QList<BaseVersionPtr> versions) override;
protected:
QList<BaseVersionPtr> m_vlist;
bool m_loaded = false;
};
class JavaListLoadTask : public Task
{
Q_OBJECT
public:
explicit JavaListLoadTask(JavaInstallList *vlist);
~JavaListLoadTask();
virtual void executeTask();
public slots:
void javaCheckerFinished(QList<JavaCheckResult> results);
protected:
std::shared_ptr<JavaCheckerJob> m_job;
JavaInstallList *m_list;
JavaInstall *m_currentRecommended;
};

View File

@ -0,0 +1,219 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QStringList>
#include <QString>
#include <QDir>
#include <QStringList>
#include <settings/Setting.h>
#include <QDebug>
#include "java/JavaUtils.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaInstallList.h"
#include "FileSystem.h"
JavaUtils::JavaUtils()
{
}
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
{
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = id;
javaVersion->arch = arch;
javaVersion->path = path;
return javaVersion;
}
JavaInstallPtr JavaUtils::GetDefaultJava()
{
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = "java";
javaVersion->arch = "unknown";
javaVersion->path = "java";
return javaVersion;
}
#if defined(Q_OS_WIN32)
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
{
QList<JavaInstallPtr> javas;
QString archType = "unknown";
if (keyType == KEY_WOW64_64KEY)
archType = "64";
else if (keyType == KEY_WOW64_32KEY)
archType = "32";
HKEY jreKey;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0,
KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS)
{
// Read the current type version from the registry.
// This will be used to find any key that contains the JavaHome value.
char *value = new char[0];
DWORD valueSz = 0;
if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
ERROR_MORE_DATA)
{
value = new char[valueSz];
RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
}
QString recommended = value;
TCHAR subKeyName[255];
DWORD subKeyNameSize, numSubKeys, retCode;
// Get the number of subkeys
RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
NULL, NULL);
// Iterate until RegEnumKeyEx fails
if (numSubKeys > 0)
{
for (int i = 0; i < numSubKeys; i++)
{
subKeyNameSize = 255;
retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
NULL);
if (retCode == ERROR_SUCCESS)
{
// Now open the registry key for the version that we just got.
QString newKeyName = keyName + "\\" + subKeyName;
HKEY newKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
{
// Read the JavaHome value to find where Java is installed.
value = new char[0];
valueSz = 0;
if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
&valueSz) == ERROR_MORE_DATA)
{
value = new char[valueSz];
RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
&valueSz);
// Now, we construct the version object and add it to the list.
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = subKeyName;
javaVersion->arch = archType;
javaVersion->path =
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
javas.append(javaVersion);
}
RegCloseKey(newKey);
}
}
}
}
RegCloseKey(jreKey);
}
return javas;
}
QList<QString> JavaUtils::FindJavaPaths()
{
QList<JavaInstallPtr> java_candidates;
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
java_candidates.append(JRE64s);
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK64s);
java_candidates.append(JRE32s);
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK32s);
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
QList<QString> candidates;
for(JavaInstallPtr java_candidate : java_candidates)
{
if(!candidates.contains(java_candidate->path))
{
candidates.append(java_candidate->path);
}
}
return candidates;
}
#elif defined(Q_OS_MAC)
QList<QString> JavaUtils::FindJavaPaths()
{
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java");
javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString &java, libraryJVMJavas) {
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
}
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString &java, systemLibraryJVMJavas) {
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
}
return javas;
}
#elif defined(Q_OS_LINUX)
QList<QString> JavaUtils::FindJavaPaths()
{
qDebug() << "Linux Java detection incomplete - defaulting to \"java\"";
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
javas.append("/opt/java/bin/java");
javas.append("/usr/bin/java");
return javas;
}
#else
QList<QString> JavaUtils::FindJavaPaths()
{
qDebug() << "Unknown operating system build - defaulting to \"java\"";
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
return javas;
}
#endif

View File

@ -0,0 +1,43 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QStringList>
#include "JavaCheckerJob.h"
#include "JavaChecker.h"
#include "JavaInstallList.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
{
Q_OBJECT
public:
JavaUtils();
JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
QList<QString> FindJavaPaths();
JavaInstallPtr GetDefaultJava();
#ifdef Q_OS_WIN
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
#endif
};

View File

@ -0,0 +1,112 @@
#include "JavaVersion.h"
#include <MMCStrings.h>
#include <QRegularExpression>
#include <QString>
JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
{
string = javaVersionString;
auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int
{
auto str = match.captured(what);
if(str.isEmpty())
{
return 0;
}
return str.toInt();
};
QRegularExpression pattern;
if(javaVersionString.startsWith("1."))
{
pattern = QRegularExpression ("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?");
}
else
{
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
}
auto match = pattern.match(string);
parseable = match.hasMatch();
major = getCapturedInteger(match, "major");
minor = getCapturedInteger(match, "minor");
security = getCapturedInteger(match, "security");
prerelease = match.captured("prerelease");
return *this;
}
JavaVersion::JavaVersion(const QString &rhs)
{
operator=(rhs);
}
QString JavaVersion::toString()
{
return string;
}
bool JavaVersion::requiresPermGen()
{
if(parseable)
{
return major < 8;
}
return true;
}
bool JavaVersion::operator<(const JavaVersion &rhs)
{
if(parseable && rhs.parseable)
{
if(major < rhs.major)
return true;
if(major > rhs.major)
return false;
if(minor < rhs.minor)
return true;
if(minor > rhs.minor)
return false;
if(security < rhs.security)
return true;
if(security > rhs.security)
return false;
// everything else being equal, consider prerelease status
bool thisPre = !prerelease.isEmpty();
bool rhsPre = !rhs.prerelease.isEmpty();
if(thisPre && !rhsPre)
{
// this is a prerelease and the other one isn't -> lesser
return true;
}
else if(!thisPre && rhsPre)
{
// this isn't a prerelease and the other one is -> greater
return false;
}
else if(thisPre && rhsPre)
{
// both are prereleases - use natural compare...
return Strings::naturalCompare(prerelease, rhs.prerelease, Qt::CaseSensitive) < 0;
}
// neither is prerelease, so they are the same -> this cannot be less than rhs
return false;
}
else return Strings::naturalCompare(string, rhs.string, Qt::CaseSensitive) < 0;
}
bool JavaVersion::operator==(const JavaVersion &rhs)
{
if(parseable && rhs.parseable)
{
return major == rhs.major && minor == rhs.minor && security == rhs.security && prerelease == rhs.prerelease;
}
return string == rhs.string;
}
bool JavaVersion::operator>(const JavaVersion &rhs)
{
return (!operator<(rhs)) && (!operator==(rhs));
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "multimc_logic_export.h"
#include <QString>
class MULTIMC_LOGIC_EXPORT JavaVersion
{
friend class JavaVersionTest;
public:
JavaVersion() {};
JavaVersion(const QString & rhs);
JavaVersion & operator=(const QString & rhs);
bool operator<(const JavaVersion & rhs);
bool operator==(const JavaVersion & rhs);
bool operator>(const JavaVersion & rhs);
bool requiresPermGen();
QString toString();
private:
QString string;
int major = 0;
int minor = 0;
int security = 0;
bool parseable = false;
QString prerelease;
};

View File

@ -0,0 +1,27 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LaunchStep.h"
#include "LaunchTask.h"
void LaunchStep::bind(LaunchTask *parent)
{
m_parent = parent;
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished);
connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested);
}

View File

@ -0,0 +1,48 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "tasks/Task.h"
#include "MessageLevel.h"
#include <QStringList>
class LaunchTask;
class LaunchStep: public Task
{
Q_OBJECT
public: /* methods */
explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent)
{
bind(parent);
};
virtual ~LaunchStep() {};
protected: /* methods */
virtual void bind(LaunchTask *parent);
signals:
void logLines(QStringList lines, MessageLevel::Enum level);
void logLine(QString line, MessageLevel::Enum level);
void readyForLaunch();
void progressReportingRequest();
public slots:
virtual void proceed() {};
protected: /* data */
LaunchTask *m_parent;
};

View File

@ -0,0 +1,228 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "launch/LaunchTask.h"
#include "MessageLevel.h"
#include "MMCStrings.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include <QDebug>
#include <QDir>
#include <QEventLoop>
#include <QRegularExpression>
#include <QCoreApplication>
#include <QStandardPaths>
#include <assert.h>
void LaunchTask::init()
{
m_instance->setRunning(true);
}
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
{
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
proc->init();
return proc;
}
LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
{
}
void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
{
m_steps.append(step);
}
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
{
m_steps.prepend(step);
}
void LaunchTask::executeTask()
{
if(!m_steps.size())
{
state = LaunchTask::Finished;
emitSucceeded();
}
state = LaunchTask::Running;
onStepFinished();
}
void LaunchTask::onReadyForLaunch()
{
state = LaunchTask::Waiting;
emit readyForLaunch();
}
void LaunchTask::onStepFinished()
{
// initial -> just start the first step
if(currentStep == -1)
{
currentStep ++;
m_steps[currentStep]->start();
return;
}
auto step = m_steps[currentStep];
if(step->successful())
{
// end?
if(currentStep == m_steps.size() - 1)
{
emitSucceeded();
}
else
{
currentStep ++;
step = m_steps[currentStep];
step->start();
}
}
else
{
emitFailed(step->failReason());
}
}
void LaunchTask::onProgressReportingRequested()
{
state = LaunchTask::Waiting;
emit requestProgress(m_steps[currentStep].get());
}
void LaunchTask::setCensorFilter(QMap<QString, QString> filter)
{
m_censorFilter = filter;
}
QString LaunchTask::censorPrivateInfo(QString in)
{
auto iter = m_censorFilter.begin();
while (iter != m_censorFilter.end())
{
in.replace(iter.key(), iter.value());
iter++;
}
return in;
}
void LaunchTask::proceed()
{
if(state != LaunchTask::Waiting)
{
return;
}
m_steps[currentStep]->proceed();
}
bool LaunchTask::abort()
{
switch(state)
{
case LaunchTask::Aborted:
case LaunchTask::Failed:
case LaunchTask::Finished:
return true;
case LaunchTask::NotStarted:
{
state = LaunchTask::Aborted;
emitFailed("Aborted");
return true;
}
case LaunchTask::Running:
case LaunchTask::Waiting:
{
auto step = m_steps[currentStep];
if(!step->canAbort())
{
return false;
}
if(step->abort())
{
state = LaunchTask::Aborted;
return true;
}
}
default:
break;
}
return false;
}
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
{
for (auto & line: lines)
{
onLogLine(line, defaultLevel);
}
}
void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
{
// if the launcher part set a log level, use it
auto innerLevel = MessageLevel::fromLine(line);
if(innerLevel != MessageLevel::Unknown)
{
level = innerLevel;
}
// If the level is still undetermined, guess level
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown)
{
level = m_instance->guessLevel(line, level);
}
// censor private user info
line = censorPrivateInfo(line);
emit log(line, level);
}
void LaunchTask::emitSucceeded()
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
Task::emitSucceeded();
}
void LaunchTask::emitFailed(QString reason)
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
Task::emitFailed(reason);
}
QString LaunchTask::substituteVariables(const QString &cmd) const
{
QString out = cmd;
auto variables = m_instance->getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
out.replace("$" + it.key(), it.value());
}
auto env = QProcessEnvironment::systemEnvironment();
for (auto var : env.keys())
{
out.replace("$" + var, env.value(var));
}
return out;
}

View File

@ -0,0 +1,122 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QProcess>
#include "BaseInstance.h"
#include "MessageLevel.h"
#include "LoggedProcess.h"
#include "LaunchStep.h"
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT LaunchTask: public Task
{
Q_OBJECT
protected:
explicit LaunchTask(InstancePtr instance);
void init();
public:
enum State
{
NotStarted,
Running,
Waiting,
Failed,
Aborted,
Finished
};
public: /* methods */
static std::shared_ptr<LaunchTask> create(InstancePtr inst);
virtual ~LaunchTask() {};
void appendStep(std::shared_ptr<LaunchStep> step);
void prependStep(std::shared_ptr<LaunchStep> step);
void setCensorFilter(QMap<QString, QString> filter);
InstancePtr instance()
{
return m_instance;
}
void setPid(qint64 pid)
{
m_pid = pid;
}
qint64 pid()
{
return m_pid;
}
/**
* @brief prepare the process for launch (for multi-stage launch)
*/
virtual void executeTask() override;
/**
* @brief launch the armed instance
*/
void proceed();
/**
* @brief abort launch
*/
virtual bool abort() override;
public:
QString substituteVariables(const QString &cmd) const;
QString censorPrivateInfo(QString in);
protected: /* methods */
virtual void emitFailed(QString reason) override;
virtual void emitSucceeded() override;
signals:
/**
* @brief emitted when the launch preparations are done
*/
void readyForLaunch();
void requestProgress(Task *task);
void requestLogging();
/**
* @brief emitted when we want to log something
* @param text the text to log
* @param level the level to log at
*/
void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC);
public slots:
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onReadyForLaunch();
void onStepFinished();
void onProgressReportingRequested();
protected: /* data */
InstancePtr m_instance;
QList <std::shared_ptr<LaunchStep>> m_steps;
QMap<QString, QString> m_censorFilter;
int currentStep = -1;
State state = NotStarted;
qint64 m_pid = -1;
};

View File

@ -0,0 +1,163 @@
#include "LoggedProcess.h"
#include "MessageLevel.h"
#include <QDebug>
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
}
QStringList reprocess(const QByteArray & data, QString & leftover)
{
QString str = leftover + QString::fromLocal8Bit(data);
str.remove('\r');
QStringList lines = str.split("\n");
leftover = lines.takeLast();
return lines;
}
void LoggedProcess::on_stdErr()
{
auto lines = reprocess(readAllStandardError(), m_err_leftover);
emit log(lines, MessageLevel::StdErr);
}
void LoggedProcess::on_stdOut()
{
auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
emit log(lines, MessageLevel::StdOut);
}
void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
{
// save the exit code
m_exit_code = exit_code;
// Flush console window
if (!m_err_leftover.isEmpty())
{
emit log({m_err_leftover}, MessageLevel::StdErr);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
emit log({m_err_leftover}, MessageLevel::StdOut);
m_out_leftover.clear();
}
// based on state, send signals
if (!m_is_aborting)
{
if (status == QProcess::NormalExit)
{
//: Message displayed on instance exit
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC);
changeState(LoggedProcess::Finished);
}
else
{
//: Message displayed on instance crashed
if(exit_code == -1)
emit log({tr("Process crashed.")}, MessageLevel::MultiMC);
else
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC);
changeState(LoggedProcess::Crashed);
}
}
else
{
//: Message displayed after the instance exits due to kill request
emit log({tr("Process was killed by user.")}, MessageLevel::Error);
changeState(LoggedProcess::Aborted);
}
}
void LoggedProcess::on_error(QProcess::ProcessError error)
{
switch(error)
{
case QProcess::FailedToStart:
{
emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
changeState(LoggedProcess::FailedToStart);
break;
}
// we'll just ignore those... never needed them
case QProcess::Crashed:
case QProcess::ReadError:
case QProcess::Timedout:
case QProcess::UnknownError:
case QProcess::WriteError:
break;
}
}
void LoggedProcess::kill()
{
m_is_aborting = true;
QProcess::kill();
}
int LoggedProcess::exitCode() const
{
return m_exit_code;
}
void LoggedProcess::changeState(LoggedProcess::State state)
{
if(state == m_state)
return;
m_state = state;
emit stateChanged(m_state);
}
LoggedProcess::State LoggedProcess::state() const
{
return m_state;
}
void LoggedProcess::on_stateChange(QProcess::ProcessState state)
{
switch(state)
{
case QProcess::NotRunning:
break; // let's not - there are too many that handle this already.
case QProcess::Starting:
{
if(m_state != LoggedProcess::NotRunning)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
}
changeState(LoggedProcess::Starting);
return;
}
case QProcess::Running:
{
if(m_state != LoggedProcess::Starting)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
}
changeState(LoggedProcess::Running);
return;
}
}
}
#if defined Q_OS_WIN32
#include <windows.h>
#endif
qint64 LoggedProcess::processId() const
{
#ifdef Q_OS_WIN
return pid() ? pid()->dwProcessId : 0;
#else
return pid();
#endif
}

View File

@ -0,0 +1,76 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QProcess>
#include "MessageLevel.h"
/*
* This is a basic process.
* It has line-based logging support and hides some of the nasty bits.
*/
class LoggedProcess : public QProcess
{
Q_OBJECT
public:
enum State
{
NotRunning,
Starting,
FailedToStart,
Running,
Finished,
Crashed,
Aborted
};
public:
explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess() {};
State state() const;
int exitCode() const;
qint64 processId() const;
signals:
void log(QStringList lines, MessageLevel::Enum level);
void stateChanged(LoggedProcess::State state);
public slots:
/**
* @brief kill the process - equivalent to kill -9
*/
void kill();
private slots:
void on_stdErr();
void on_stdOut();
void on_exit(int exit_code, QProcess::ExitStatus status);
void on_error(QProcess::ProcessError error);
void on_stateChange(QProcess::ProcessState);
private:
void changeState(LoggedProcess::State state);
private:
QString m_err_leftover;
QString m_out_leftover;
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;
bool m_is_aborting = false;
};

View File

@ -0,0 +1,36 @@
#include "MessageLevel.h"
MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
{
if (levelName == "MultiMC")
return MessageLevel::MultiMC;
else if (levelName == "Debug")
return MessageLevel::Debug;
else if (levelName == "Info")
return MessageLevel::Info;
else if (levelName == "Message")
return MessageLevel::Message;
else if (levelName == "Warning")
return MessageLevel::Warning;
else if (levelName == "Error")
return MessageLevel::Error;
else if (levelName == "Fatal")
return MessageLevel::Fatal;
// Skip PrePost, it's not exposed to !![]!
// Also skip StdErr and StdOut
else
return MessageLevel::Unknown;
}
MessageLevel::Enum MessageLevel::fromLine(QString &line)
{
// Level prefix
int endmark = line.indexOf("]!");
if (line.startsWith("!![") && endmark != -1)
{
auto level = MessageLevel::getLevel(line.left(endmark).mid(3));
line = line.mid(endmark + 2);
return level;
}
return MessageLevel::Unknown;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <QString>
/**
* @brief the MessageLevel Enum
* defines what level a log message is
*/
namespace MessageLevel
{
enum Enum
{
Unknown, /**< No idea what this is or where it came from */
StdOut, /**< Undetermined stderr messages */
StdErr, /**< Undetermined stdout messages */
MultiMC, /**< MultiMC Messages */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal, /**< Fatal Errors */
};
MessageLevel::Enum getLevel(const QString &levelName);
/* Get message level from a line. Line is modified if it was successful. */
MessageLevel::Enum fromLine(QString &line);
}

View File

@ -0,0 +1,92 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "CheckJava.h"
#include <launch/LaunchTask.h>
#include <FileSystem.h>
#include <QStandardPaths>
#include <QFileInfo>
void CheckJava::executeTask()
{
auto instance = m_parent->instance();
auto settings = instance->settings();
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty())
{
if (perInstance)
{
emit logLine(
tr("The java binary \"%1\" couldn't be found. Please fix the java path "
"override in the instance's settings or disable it.").arg(m_javaPath),
MessageLevel::Warning);
}
else
{
emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
"the settings.").arg(m_javaPath),
MessageLevel::Warning);
}
emitFailed(tr("Java path is not valid."));
return;
}
else
{
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC);
}
QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
m_javaUnixTime = javaUnixTime;
// if they are not the same, check!
if (javaUnixTime != storedUnixTime)
{
m_JavaChecker = std::make_shared<JavaChecker>();
QString errorLog;
QString version;
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this,
&CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
return;
}
emitSucceeded();
}
void CheckJava::checkJavaFinished(JavaCheckResult result)
{
if (!result.valid)
{
// Error message displayed if java can't start
emit logLine(tr("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
emitFailed(tr("Could not start java!"));
}
else
{
auto instance = m_parent->instance();
emit logLine(tr("Java version is %1!\n").arg(result.javaVersion.toString()),
MessageLevel::MultiMC);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
}
}

View File

@ -0,0 +1,41 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <java/JavaChecker.h>
class CheckJava: public LaunchStep
{
Q_OBJECT
public:
explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){};
virtual ~CheckJava() {};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
private slots:
void checkJavaFinished(JavaCheckResult result);
private:
QString m_javaPath;
qlonglong m_javaUnixTime;
JavaCheckerPtr m_JavaChecker;
};

View File

@ -0,0 +1,154 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LaunchMinecraft.h"
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <FileSystem.h>
#include <QStandardPaths>
LaunchMinecraft::LaunchMinecraft(LaunchTask *parent) : LaunchStep(parent)
{
connect(&m_process, &LoggedProcess::log, this, &LaunchMinecraft::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &LaunchMinecraft::on_state);
}
void LaunchMinecraft::executeTask()
{
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();
// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' is created.
if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->minecraftRoot(), "server-resource-packs")))
{
emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error);
}
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());
m_process.setProcessEnvironment(instance->createEnvironment());
QString wrapperCommand = instance->getWrapperCommand();
if(!wrapperCommand.isEmpty())
{
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" + wrapperCommand + "\n\n", MessageLevel::MultiMC);
args.prepend(javaPath);
m_process.start(wrapperCommand, args);
}
else
{
m_process.start(javaPath, args);
}
}
void LaunchMinecraft::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:
{
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;
}
}
void LaunchMinecraft::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
void LaunchMinecraft::proceed()
{
if(mayProceed)
{
QString launchString("launch\n");
m_process.write(launchString.toUtf8());
mayProceed = false;
}
}
bool LaunchMinecraft::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;
}

View File

@ -0,0 +1,48 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <minecraft/auth/AuthSession.h>
class LaunchMinecraft: public LaunchStep
{
Q_OBJECT
public:
explicit LaunchMinecraft(LaunchTask *parent);
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);
private:
LoggedProcess m_process;
QString m_command;
QString m_launchScript;
AuthSessionPtr m_session;
bool mayProceed = false;
};

View File

@ -0,0 +1,44 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModMinecraftJar.h"
#include <launch/LaunchTask.h>
#include <QStandardPaths>
void ModMinecraftJar::executeTask()
{
m_jarModTask = m_parent->instance()->createJarModdingTask();
if(m_jarModTask)
{
connect(m_jarModTask.get(), SIGNAL(finished()), this, SLOT(jarModdingFinished()));
m_jarModTask->start();
return;
}
emitSucceeded();
}
void ModMinecraftJar::jarModdingFinished()
{
if(m_jarModTask->successful())
{
emitSucceeded();
}
else
{
QString reason = tr("jar modding failed because: %1.\n\n").arg(m_jarModTask->failReason());
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}

View File

@ -0,0 +1,39 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <memory>
// FIXME: temporary wrapper for existing task.
class ModMinecraftJar: public LaunchStep
{
Q_OBJECT
public:
explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {};
virtual ~ModMinecraftJar(){};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
private slots:
void jarModdingFinished();
private:
std::shared_ptr<Task> m_jarModTask;
};

View File

@ -0,0 +1,84 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PostLaunchCommand.h"
#include <launch/LaunchTask.h>
PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
{
auto instance = m_parent->instance();
m_command = instance->getPostExitCommand();
m_process.setProcessEnvironment(instance->createEnvironment());
connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state);
}
void PostLaunchCommand::executeTask()
{
QString postlaunch_cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC);
m_process.start(postlaunch_cmd);
}
void PostLaunchCommand::on_state(LoggedProcess::State state)
{
auto getError = [&]()
{
return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode());
};
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
return;
}
case LoggedProcess::Finished:
{
if(m_process.exitCode() != 0)
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
}
else
{
emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC);
emitSucceeded();
}
}
default:
break;
}
}
void PostLaunchCommand::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
bool PostLaunchCommand::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@ -0,0 +1,39 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
class PostLaunchCommand: public LaunchStep
{
Q_OBJECT
public:
explicit PostLaunchCommand(LaunchTask *parent);
virtual void executeTask();
virtual bool abort();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
private slots:
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
};

View File

@ -0,0 +1,85 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PreLaunchCommand.h"
#include <launch/LaunchTask.h>
PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
{
auto instance = m_parent->instance();
m_command = instance->getPreLaunchCommand();
m_process.setProcessEnvironment(instance->createEnvironment());
connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state);
}
void PreLaunchCommand::executeTask()
{
//FIXME: where to put this?
QString prelaunch_cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC);
m_process.start(prelaunch_cmd);
}
void PreLaunchCommand::on_state(LoggedProcess::State state)
{
auto getError = [&]()
{
return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode());
};
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
return;
}
case LoggedProcess::Finished:
{
if(m_process.exitCode() != 0)
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
}
else
{
emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC);
emitSucceeded();
}
}
default:
break;
}
}
void PreLaunchCommand::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
bool PreLaunchCommand::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@ -0,0 +1,39 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
class PreLaunchCommand: public LaunchStep
{
Q_OBJECT
public:
explicit PreLaunchCommand(LaunchTask *parent);
virtual void executeTask();
virtual bool abort();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
private slots:
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
};

View File

@ -0,0 +1,29 @@
#include "TextPrint.h"
TextPrint::TextPrint(LaunchTask * parent, const QStringList &lines, MessageLevel::Enum level) : LaunchStep(parent)
{
m_lines = lines;
m_level = level;
}
TextPrint::TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level) : LaunchStep(parent)
{
m_lines.append(line);
m_level = level;
}
void TextPrint::executeTask()
{
emit logLines(m_lines, m_level);
emitSucceeded();
}
bool TextPrint::canAbort() const
{
return true;
}
bool TextPrint::abort()
{
emitFailed("Aborted.");
return true;
}

View File

@ -0,0 +1,43 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <java/JavaChecker.h>
#include "multimc_logic_export.h"
/*
* FIXME: maybe do not export
*/
class MULTIMC_LOGIC_EXPORT TextPrint: public LaunchStep
{
Q_OBJECT
public:
explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level);
explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level);
virtual ~TextPrint(){};
virtual void executeTask();
virtual bool canAbort() const;
virtual bool abort();
private:
QStringList m_lines;
MessageLevel::Enum m_level;
};

View File

@ -0,0 +1,50 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Update.h"
#include <launch/LaunchTask.h>
void Update::executeTask()
{
m_updateTask = m_parent->instance()->createUpdateTask();
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress);
connect(m_updateTask.get(), &Task::status, this, &Task::setStatus);
emit progressReportingRequest();
return;
}
emitSucceeded();
}
void Update::proceed()
{
m_updateTask->start();
}
void Update::updateFinished()
{
if(m_updateTask->successful())
{
emitSucceeded();
}
else
{
QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}

View File

@ -0,0 +1,41 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <java/JavaChecker.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
class Update: public LaunchStep
{
Q_OBJECT
public:
explicit Update(LaunchTask *parent):LaunchStep(parent) {};
virtual ~Update() {};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
virtual void proceed();
private slots:
void updateFinished();
private:
std::shared_ptr<Task> m_updateTask;
};

View File

@ -0,0 +1,230 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QCryptographicHash>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QVariant>
#include <QDebug>
#include "AssetsUtils.h"
#include "FileSystem.h"
#include "net/MD5EtagDownload.h"
namespace AssetsUtils
{
/*
* Returns true on success, with index populated
* index is undefined otherwise
*/
bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
{
/*
{
"objects": {
"icons/icon_16x16.png": {
"hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a",
"size": 3665
},
...
}
}
}
*/
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;
// Read the file and close it.
QByteArray jsonData = file.readAll();
file.close();
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;
}
// 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();
QJsonValue isVirtual = root.value("virtual");
if (!isVirtual.isUndefined())
{
index->isVirtual = isVirtual.toBool(false);
}
QJsonValue objects = root.value("objects");
QVariantMap map = objects.toVariant().toMap();
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
// qDebug() << iter.key();
QVariant variant = iter.value();
QVariantMap nested_objects = variant.toMap();
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();
if (key == "hash")
{
object.hash = value.toString();
}
else if (key == "size")
{
object.size = value.toDouble();
}
}
index->objects.insert(iter.key(), object);
}
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"));
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;
}
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index;
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
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);
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());
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
}
return virtualRoot;
}
}
NetActionPtr AssetObject::getDownloadAction()
{
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size))
{
auto objectDL = MD5EtagDownload::make(getUrl(), objectFile.filePath());
objectDL->m_total_progress = size;
return objectDL;
}
return nullptr;
}
QString AssetObject::getLocalPath()
{
return "assets/objects/" + getRelPath();
}
QUrl AssetObject::getUrl()
{
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
}
QString AssetObject::getRelPath()
{
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;
}

View File

@ -0,0 +1,48 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
#include <QMap>
#include "net/NetAction.h"
#include "net/NetJob.h"
struct AssetObject
{
QString getRelPath();
QUrl getUrl();
QString getLocalPath();
NetActionPtr getDownloadAction();
QString hash;
qint64 size;
};
struct AssetsIndex
{
NetJobPtr getDownloadJob();
QString id;
QMap<QString, AssetObject> objects;
bool isVirtual = false;
};
namespace AssetsUtils
{
bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
QDir reconstructAssets(QString assetsId);
}

View File

@ -0,0 +1,129 @@
#pragma once
#include <QString>
#include <QStringList>
#include "DefaultVariable.h"
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 toPath() const
{
if(!m_valid)
return "INVALID";
QString path = m_groupId;
path.replace('.', '/');
path += '/' + m_artifactId + '/' + m_version + '/' + m_artifactId + '-' + m_version;
if(!m_classifier.isEmpty())
{
path += "-" + m_classifier;
}
path += "." + m_extension;
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;
};

View File

@ -0,0 +1,12 @@
#pragma once
#include <QString>
#include <QJsonObject>
#include <memory>
class Jarmod;
typedef std::shared_ptr<Jarmod> JarmodPtr;
class Jarmod
{
public: /* data */
QString name;
QString originalName;
};

View File

@ -0,0 +1,239 @@
#include "Library.h"
#include <net/CacheDownload.h>
#include <minecraft/forge/ForgeXzDownload.h>
#include <Env.h>
#include <FileSystem.h>
void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, QStringList& native64) const
{
auto actualPath = [&](QString relPath)
{
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
return out.absoluteFilePath();
};
if(m_mojangDownloads)
{
if(m_mojangDownloads->artifact)
{
auto artifact = m_mojangDownloads->artifact;
jar += actualPath(artifact->path);
}
if(!isNative())
return;
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)
native32 += actualPath(nat32info->path);
auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
if(nat64info)
native64 += actualPath(nat64info->path);
}
else
{
native += actualPath(m_mojangDownloads->getDownloadInfo(nativeClassifier)->path);
}
}
}
else
{
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<NetActionPtr> Library::getDownloads(OpSys system, HttpMetaCache * cache, QStringList &failedFiles) const
{
QList<NetActionPtr> out;
bool isLocal = (hint() == "local");
bool isForge = (hint() == "forge-pack-xz");
auto add_download = [&](QString storage, QString dl)
{
auto entry = cache->resolveEntry("libraries", storage);
if (!entry->isStale())
return true;
if(isLocal)
{
QFileInfo fileinfo(entry->getFullPath());
if(!fileinfo.exists())
{
failedFiles.append(entry->getFullPath());
return false;
}
return true;
}
if (isForge)
{
out.append(ForgeXzDownload::make(storage, entry));
}
else
{
out.append(CacheDownload::make(dl, entry));
}
return true;
};
if(m_mojangDownloads)
{
if(m_mojangDownloads->artifact)
{
auto artifact = m_mojangDownloads->artifact;
add_download(artifact->path, artifact->url);
}
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)
add_download(nat32info->path, nat32info->url);
auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
if(nat64info)
add_download(nat64info->path, nat64info->url);
}
else
{
auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
if(info)
{
add_download(info->path, info->url);
}
}
}
}
else
{
QString raw_storage = storageSuffix(system);
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.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"));
cooked_storage = raw_storage;
cooked_dl = raw_dl;
add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"));
}
else
{
add_download(raw_storage, raw_dl);
}
}
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;
}
void Library::setStoragePrefix(QString prefix)
{
m_storagePrefix = prefix;
}
QString Library::defaultStoragePrefix()
{
return "libraries/";
}
QString Library::storagePrefix() const
{
if(m_storagePrefix.isEmpty())
{
return defaultStoragePrefix();
}
return m_storagePrefix;
}
QString Library::storageSuffix(OpSys system) const
{
// non-native? use only the gradle specifier
if (!isNative())
{
return m_name.toPath();
}
// 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();
}

View File

@ -0,0 +1,184 @@
#pragma once
#include <QString>
#include <net/NetAction.h>
#include <QPair>
#include <QList>
#include <QStringList>
#include <QMap>
#include <QDir>
#include <QUrl>
#include <memory>
#include "Rule.h"
#include "minecraft/OpSys.h"
#include "GradleSpecifier.h"
#include "net/URLConstants.h"
#include "MojangDownloadInfo.h"
#include "multimc_logic_export.h"
class Library;
typedef std::shared_ptr<Library> LibraryPtr;
class MULTIMC_LOGIC_EXPORT Library
{
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;
return newlib;
}
public: /* methods */
/// Returns the raw name field
const GradleSpecifier & rawName() const
{
return m_name;
}
void setRawName(const GradleSpecifier & spec)
{
m_name = spec;
}
void setClassifier(const QString & spec)
{
m_name.setClassifier(spec);
}
/// 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 version
QString version() const
{
return m_name.version();
}
/// Returns true if the library is native
bool isNative() const
{
return m_nativeClassifiers.size() != 0;
}
void setStoragePrefix(QString prefix = QString());
/// Set the url base for downloads
void setRepositoryURL(const QString &base_url)
{
m_repositoryURL = base_url;
}
void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, QStringList & native32, QStringList & native64) const;
void setAbsoluteUrl(const QString &absolute_url)
{
m_absoluteURL = absolute_url;
}
void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info)
{
m_mojangDownloads = info;
}
void setHint(const QString &hint)
{
m_hint = hint;
}
/// 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;
// Get a list of downloads for this library
QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache, QStringList &failedFiles) const;
private: /* methods */
/// 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 relative path where the library should be saved
QString storageSuffix(OpSys system) const;
QString hint() const
{
return m_hint;
}
protected: /* data */
/// 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: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined
QString m_absoluteURL;
/**
* 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;
/// 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;
/// native suffixes per OS
QMap<OpSys, QString> m_nativeClassifiers;
/// 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;
/// MOJANG: container with Mojang style download info
MojangLibraryDownloadInfo::Ptr m_mojangDownloads;
};

View File

@ -0,0 +1,369 @@
#include "MinecraftInstance.h"
#include <settings/Setting.h>
#include "settings/SettingsObject.h"
#include "Env.h"
#include "minecraft/MinecraftVersionList.h"
#include <MMCStrings.h>
#include <pathmatcher/RegexpMatcher.h>
#include <pathmatcher/MultiMatcher.h>
#include <FileSystem.h>
#include <java/JavaVersion.h>
#define IBUS "@im=ibus"
// all of this because keeping things compatible with deprecated old settings
// if either of the settings {a, b} is true, this also resolves to true
class OrSetting : public Setting
{
Q_OBJECT
public:
OrSetting(QString id, std::shared_ptr<Setting> a, std::shared_ptr<Setting> b)
:Setting({id}, false), m_a(a), m_b(b)
{
}
virtual QVariant get() const
{
bool a = m_a->get().toBool();
bool b = m_b->get().toBool();
return a || b;
}
virtual void reset() {}
virtual void set(QVariant value) {}
private:
std::shared_ptr<Setting> m_a;
std::shared_ptr<Setting> m_b;
};
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: BaseInstance(globalSettings, settings, rootDir)
{
// Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false);
// combinations
auto javaOrLocation = std::make_shared<OrSetting>("JavaOrLocationOverride", javaOverride, locationOverride);
auto javaOrArgs = std::make_shared<OrSetting>("JavaOrArgsOverride", javaOverride, argsOverride);
m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation);
m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs);
// special!
m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation);
m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation);
// Window Size
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting);
m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting);
m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting);
// Memory
auto memorySetting = m_settings->registerSetting("OverrideMemory", false);
m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting);
m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting);
}
QString MinecraftInstance::minecraftRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
if (dotMCDir.exists() && !mcDir.exists())
return dotMCDir.filePath();
else
return mcDir.filePath();
}
std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const
{
return ENV.getVersionList("net.minecraft");
}
QStringList MinecraftInstance::javaArguments() const
{
QStringList args;
// custom args go first. we want to override them if we have our own here.
args.append(extraArguments());
// OSX dock icon and name
#ifdef Q_OS_MAC
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(windowTitle());
#endif
// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
#ifdef Q_OS_WIN32
args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
"minecraft.exe.heapdump");
#endif
args << QString("-Xms%1m").arg(settings()->get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt());
// No PermGen in newer java.
JavaVersion javaVersion(settings()->get("JavaVersion").toString());
if(javaVersion.requiresPermGen())
{
auto permgen = settings()->get("PermGen").toInt();
if (permgen != 64)
{
args << QString("-XX:PermSize=%1m").arg(permgen);
}
}
args << "-Duser.language=en";
args << "-jar" << FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "NewLaunch.jar");
return args;
}
QMap<QString, QString> MinecraftInstance::getVariables() const
{
QMap<QString, QString> out;
out.insert("INST_NAME", name());
out.insert("INST_ID", id());
out.insert("INST_DIR", QDir(instanceRoot()).absolutePath());
out.insert("INST_MC_DIR", QDir(minecraftRoot()).absolutePath());
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
return out;
}
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
{
QDir mmcBin(QCoreApplication::applicationDirPath());
auto items = LD_LIBRARY_PATH.split(':');
QStringList final;
for(auto & item: items)
{
QDir test(item);
if(test == mmcBin)
{
qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
continue;
}
final.append(item);
}
return final.join(':');
}
QProcessEnvironment MinecraftInstance::createEnvironment()
{
// prepare the process environment
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
QProcessEnvironment env;
QStringList ignored =
{
"JAVA_ARGS",
"CLASSPATH",
"CONFIGPATH",
"JAVA_HOME",
"JRE_HOME",
"_JAVA_OPTIONS",
"JAVA_OPTIONS",
"JAVA_TOOL_OPTIONS"
};
for(auto key: rawenv.keys())
{
auto value = rawenv.value(key);
// filter out dangerous java crap
if(ignored.contains(key))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// filter MultiMC-related things
if(key.startsWith("QT_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
#ifdef Q_OS_LINUX
// Do not pass LD_* variables to java. They were intended for MultiMC
if(key.startsWith("LD_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// Strip IBus
// IBus is a Linux IME framework. For some reason, it breaks MC?
if (key == "XMODIFIERS" && value.contains(IBUS))
{
QString save = value;
value.replace(IBUS, "");
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
}
if(key == "GAME_PRELOAD")
{
env.insert("LD_PRELOAD", value);
continue;
}
if(key == "GAME_LIBRARY_PATH")
{
env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
continue;
}
#endif
qDebug() << "Env: " << key << value;
env.insert(key, value);
}
#ifdef Q_OS_LINUX
// HACK: Workaround for QTBUG42500
if(!env.contains("LD_LIBRARY_PATH"))
{
env.insert("LD_LIBRARY_PATH", "");
}
#endif
// export some infos
auto variables = getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
env.insert(it.key(), it.value());
}
return env;
}
QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session)
{
if(!session)
{
return QMap<QString, QString>();
}
auto & sessionRef = *session.get();
QMap<QString, QString> filter;
auto addToFilter = [&filter](QString key, QString value)
{
if(key.trimmed().size())
{
filter[key] = value;
}
};
if (sessionRef.session != "-")
{
addToFilter(sessionRef.session, tr("<SESSION ID>"));
}
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
addToFilter(sessionRef.player_name, tr("<PROFILE NAME>"));
auto i = sessionRef.u.properties.begin();
while (i != sessionRef.u.properties.end())
{
addToFilter(i.value(), "<" + i.key().toUpper() + ">");
++i;
}
return filter;
}
MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level)
{
QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
auto match = re.match(line);
if(match.hasMatch())
{
// New style logs from log4j
QString timestamp = match.captured("timestamp");
QString levelStr = match.captured("level");
if(levelStr == "INFO")
level = MessageLevel::Message;
if(levelStr == "WARN")
level = MessageLevel::Warning;
if(levelStr == "ERROR")
level = MessageLevel::Error;
if(levelStr == "FATAL")
level = MessageLevel::Fatal;
if(levelStr == "TRACE" || levelStr == "DEBUG")
level = MessageLevel::Debug;
}
else
{
// Old style forge logs
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") ||
line.contains("[FINER]") || line.contains("[FINEST]"))
level = MessageLevel::Message;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
}
if (line.contains("overwriting existing"))
return MessageLevel::Fatal;
//NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*";
if (line.contains("Exception in thread")
|| line.contains(QRegularExpression("\\s+at " + javaSymbol))
|| line.contains(QRegularExpression("Caused by: " + javaSymbol))
|| line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)"))
|| line.contains(QRegularExpression("... \\d+ more$"))
)
return MessageLevel::Error;
return level;
}
IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher()
{
auto combined = std::make_shared<MultiMatcher>();
combined->add(std::make_shared<RegexpMatcher>(".*\\.log(\\.[0-9]*)?(\\.gz)?$"));
combined->add(std::make_shared<RegexpMatcher>("crash-.*\\.txt"));
combined->add(std::make_shared<RegexpMatcher>("IDMap dump.*\\.txt$"));
combined->add(std::make_shared<RegexpMatcher>("ModLoader\\.txt(\\..*)?$"));
return combined;
}
QString MinecraftInstance::getLogFileRoot()
{
return minecraftRoot();
}
QString MinecraftInstance::prettifyTimeDuration(int64_t duration)
{
int seconds = (int) (duration % 60);
duration /= 60;
int minutes = (int) (duration % 60);
duration /= 60;
int hours = (int) (duration % 24);
int days = (int) (duration / 24);
if((hours == 0)&&(days == 0))
{
return tr("%1m %2s").arg(minutes).arg(seconds);
}
if (days == 0)
{
return tr("%1h %2m").arg(hours).arg(minutes);
}
return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes);
}
QString MinecraftInstance::getStatusbarDescription()
{
QStringList traits;
if (flags() & VersionBrokenFlag)
{
traits.append(tr("broken"));
}
QString description;
description.append(tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(typeName()));
if(totalTimePlayed() > 0)
{
description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
}
/*
if(traits.size())
{
description.append(QString(" (%1)").arg(traits.join(", ")));
}
*/
return description;
}
#include "MinecraftInstance.moc"

View File

@ -0,0 +1,69 @@
#pragma once
#include "BaseInstance.h"
#include "minecraft/Mod.h"
#include <QProcess>
#include "multimc_logic_export.h"
class ModList;
class WorldList;
class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
{
public:
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
virtual ~MinecraftInstance() {};
/// Path to the instance's minecraft directory.
QString minecraftRoot() const;
////// Mod Lists //////
virtual std::shared_ptr<ModList> resourcePackList() const
{
return nullptr;
}
virtual std::shared_ptr<ModList> texturePackList() const
{
return nullptr;
}
virtual std::shared_ptr<WorldList> worldList() const
{
return nullptr;
}
/// get all jar mods applicable to this instance's jar
virtual QList<Mod> getJarMods() const
{
return QList<Mod>();
}
/// get the launch script to be used with this
virtual QString createLaunchScript(AuthSessionPtr session) = 0;
//FIXME: nuke?
virtual std::shared_ptr<BaseVersionList> versionList() const override;
/// get arguments passed to java
QStringList javaArguments() const;
/// get variables for launch command variable substitution/environment
virtual QMap<QString, QString> getVariables() const override;
/// create an environment for launching processes
virtual QProcessEnvironment createEnvironment() override;
/// guess log level from a line of minecraft log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
virtual IPathMatcher::Ptr getLogFileMatcher() override;
virtual QString getLogFileRoot() override;
virtual QString getStatusbarDescription() override;
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
private:
QString prettifyTimeDuration(int64_t duration);
};
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;

View File

@ -0,0 +1,610 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QFile>
#include <QCryptographicHash>
#include <Version.h>
#include <QDir>
#include <QJsonDocument>
#include <QJsonArray>
#include <QDebug>
#include "minecraft/MinecraftProfile.h"
#include "ProfileUtils.h"
#include "ProfileStrategy.h"
#include "Exception.h"
MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy)
: QAbstractListModel()
{
setStrategy(strategy);
clear();
}
void MinecraftProfile::setStrategy(ProfileStrategy* strategy)
{
Q_ASSERT(strategy != nullptr);
if(m_strategy != nullptr)
{
delete m_strategy;
m_strategy = nullptr;
}
m_strategy = strategy;
m_strategy->profile = this;
}
ProfileStrategy* MinecraftProfile::strategy()
{
return m_strategy;
}
void MinecraftProfile::reload()
{
beginResetModel();
m_strategy->load();
reapplyPatches();
endResetModel();
}
void MinecraftProfile::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();
mojangDownloads.clear();
m_problemSeverity = ProblemSeverity::PROBLEM_NONE;
}
void MinecraftProfile::clearPatches()
{
beginResetModel();
m_patches.clear();
endResetModel();
}
void MinecraftProfile::appendPatch(ProfilePatchPtr patch)
{
int index = m_patches.size();
beginInsertRows(QModelIndex(), index, index);
m_patches.append(patch);
endInsertRows();
}
bool MinecraftProfile::remove(const int index)
{
auto patch = versionPatch(index);
if (!patch->isRemovable())
{
qDebug() << "Patch" << patch->getID() << "is non-removable";
return false;
}
if(!m_strategy->removePatch(patch))
{
qCritical() << "Patch" << patch->getID() << "could not be removed";
return false;
}
beginRemoveRows(QModelIndex(), index, index);
m_patches.removeAt(index);
endRemoveRows();
reapplyPatches();
saveCurrentOrder();
return true;
}
bool MinecraftProfile::remove(const QString id)
{
int i = 0;
for (auto patch : m_patches)
{
if (patch->getID() == id)
{
return remove(i);
}
i++;
}
return false;
}
bool MinecraftProfile::customize(int index)
{
auto patch = versionPatch(index);
if (!patch->isCustomizable())
{
qDebug() << "Patch" << patch->getID() << "is not customizable";
return false;
}
if(!m_strategy->customizePatch(patch))
{
qCritical() << "Patch" << patch->getID() << "could not be customized";
return false;
}
reapplyPatches();
saveCurrentOrder();
// FIXME: maybe later in unstable
// emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
return true;
}
bool MinecraftProfile::revertToBase(int index)
{
auto patch = versionPatch(index);
if (!patch->isRevertible())
{
qDebug() << "Patch" << patch->getID() << "is not revertible";
return false;
}
if(!m_strategy->revertPatch(patch))
{
qCritical() << "Patch" << patch->getID() << "could not be reverted";
return false;
}
reapplyPatches();
saveCurrentOrder();
// FIXME: maybe later in unstable
// emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
return true;
}
ProfilePatchPtr MinecraftProfile::versionPatch(const QString &id)
{
for (auto file : m_patches)
{
if (file->getID() == id)
{
return file;
}
}
return nullptr;
}
ProfilePatchPtr MinecraftProfile::versionPatch(int index)
{
if(index < 0 || index >= m_patches.size())
return nullptr;
return m_patches[index];
}
bool MinecraftProfile::isVanilla()
{
for(auto patchptr: m_patches)
{
if(patchptr->isCustom())
return false;
}
return true;
}
bool MinecraftProfile::revertToVanilla()
{
// remove patches, if present
auto VersionPatchesCopy = m_patches;
for(auto & it: VersionPatchesCopy)
{
if (!it->isCustom())
{
continue;
}
if(it->isRevertible() || it->isRemovable())
{
if(!remove(it->getID()))
{
qWarning() << "Couldn't remove" << it->getID() << "from profile!";
reapplyPatches();
saveCurrentOrder();
return false;
}
}
}
reapplyPatches();
saveCurrentOrder();
return true;
}
QVariant MinecraftProfile::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
if (row < 0 || row >= m_patches.size())
return QVariant();
auto patch = m_patches.at(row);
if (role == Qt::DisplayRole)
{
switch (column)
{
case 0:
return m_patches.at(row)->getName();
case 1:
{
if(patch->isCustom())
{
return QString("%1 (Custom)").arg(patch->getVersion());
}
else
{
return patch->getVersion();
}
}
default:
return QVariant();
}
}
if(role == Qt::DecorationRole)
{
switch(column)
{
case 0:
{
auto severity = patch->getProblemSeverity();
switch (severity)
{
case PROBLEM_WARNING:
return "warning";
case PROBLEM_ERROR:
return "error";
default:
return QVariant();
}
}
default:
{
return QVariant();
}
}
}
return QVariant();
}
QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal)
{
if (role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return tr("Name");
case 1:
return tr("Version");
default:
return QVariant();
}
}
}
return QVariant();
}
Qt::ItemFlags MinecraftProfile::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
int MinecraftProfile::rowCount(const QModelIndex &parent) const
{
return m_patches.size();
}
int MinecraftProfile::columnCount(const QModelIndex &parent) const
{
return 2;
}
void MinecraftProfile::saveCurrentOrder() const
{
ProfileUtils::PatchOrder order;
for(auto item: m_patches)
{
if(!item->isMoveable())
continue;
order.append(item->getID());
}
m_strategy->saveOrder(order);
}
void MinecraftProfile::move(const int index, const MoveDirection direction)
{
int theirIndex;
if (direction == MoveUp)
{
theirIndex = index - 1;
}
else
{
theirIndex = index + 1;
}
if (index < 0 || index >= m_patches.size())
return;
if (theirIndex >= rowCount())
theirIndex = rowCount() - 1;
if (theirIndex == -1)
theirIndex = rowCount() - 1;
if (index == theirIndex)
return;
int togap = theirIndex > index ? theirIndex + 1 : theirIndex;
auto from = versionPatch(index);
auto to = versionPatch(theirIndex);
if (!from || !to || !to->isMoveable() || !from->isMoveable())
{
return;
}
beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
m_patches.swap(index, theirIndex);
endMoveRows();
reapplyPatches();
saveCurrentOrder();
}
void MinecraftProfile::resetOrder()
{
m_strategy->resetOrder();
reload();
}
bool MinecraftProfile::reapplyPatches()
{
try
{
clear();
for(auto file: m_patches)
{
file->applyTo(this);
}
}
catch (Exception & error)
{
clear();
qWarning() << "Couldn't apply profile patches because: " << error.cause();
return false;
}
return true;
}
static void applyString(const QString & from, QString & to)
{
if(from.isEmpty())
return;
to = from;
}
void MinecraftProfile::applyMinecraftVersion(const QString& id)
{
applyString(id, this->m_minecraftVersion);
}
void MinecraftProfile::applyAppletClass(const QString& appletClass)
{
applyString(appletClass, this->m_appletClass);
}
void MinecraftProfile::applyMainClass(const QString& mainClass)
{
applyString(mainClass, this->m_mainClass);
}
void MinecraftProfile::applyMinecraftArguments(const QString& minecraftArguments)
{
applyString(minecraftArguments, this->m_minecraftArguments);
}
void MinecraftProfile::applyMinecraftVersionType(const QString& type)
{
applyString(type, this->m_minecraftVersionType);
}
void MinecraftProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets)
{
if(assets)
{
m_minecraftAssets = assets;
}
}
void MinecraftProfile::applyMojangDownload(const QString &key, MojangDownloadInfo::Ptr download)
{
if(download)
{
mojangDownloads[key] = download;
}
else
{
mojangDownloads.remove(key);
}
}
void MinecraftProfile::applyTraits(const QSet<QString>& traits)
{
this->m_traits.unite(traits);
}
void MinecraftProfile::applyTweakers(const QStringList& tweakers)
{
// FIXME: check for dupes?
// FIXME: does order matter?
for (auto tweaker : tweakers)
{
this->m_tweakers += tweaker;
}
}
void MinecraftProfile::applyJarMods(const QList<JarmodPtr>& 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;
}
void MinecraftProfile::applyLibrary(LibraryPtr library)
{
if(!library->isActive())
{
return;
}
// find the library by name.
const int index = findLibraryByName(m_libraries, library->rawName());
// library not found? just add it.
if (index < 0)
{
m_libraries.append(Library::limitedCopy(library));
return;
}
auto existingLibrary = m_libraries.at(index);
// if we are higher it means we should update
if (Version(library->version()) > Version(existingLibrary->version()))
{
auto libraryCopy = Library::limitedCopy(library);
m_libraries.replace(index, libraryCopy);
}
}
void MinecraftProfile::applyProblemSeverity(ProblemSeverity severity)
{
if (m_problemSeverity < severity)
{
m_problemSeverity = severity;
}
}
QString MinecraftProfile::getMinecraftVersion() const
{
return m_minecraftVersion;
}
QString MinecraftProfile::getAppletClass() const
{
return m_appletClass;
}
QString MinecraftProfile::getMainClass() const
{
return m_mainClass;
}
const QSet<QString> &MinecraftProfile::getTraits() const
{
return m_traits;
}
const QStringList & MinecraftProfile::getTweakers() const
{
return m_tweakers;
}
bool MinecraftProfile::hasTrait(const QString& trait) const
{
return m_traits.contains(trait);
}
ProblemSeverity MinecraftProfile::getProblemSeverity() const
{
return m_problemSeverity;
}
QString MinecraftProfile::getMinecraftVersionType() const
{
return m_minecraftVersionType;
}
std::shared_ptr<MojangAssetIndexInfo> MinecraftProfile::getMinecraftAssets() const
{
if(!m_minecraftAssets)
{
return std::make_shared<MojangAssetIndexInfo>("legacy");
}
return m_minecraftAssets;
}
QString MinecraftProfile::getMinecraftArguments() const
{
return m_minecraftArguments;
}
const QList<JarmodPtr> & MinecraftProfile::getJarMods() const
{
return m_jarMods;
}
const QList<LibraryPtr> & MinecraftProfile::getLibraries() const
{
return m_libraries;
}
QString MinecraftProfile::getMainJarUrl() const
{
auto iter = mojangDownloads.find("client");
if(iter != mojangDownloads.end())
{
// current
return iter.value()->url;
}
else
{
// legacy fallback
return URLConstants::getLegacyJarUrl(getMinecraftVersion());
}
}
void MinecraftProfile::installJarMods(QStringList selectedFiles)
{
m_strategy->installJarMods(selectedFiles);
}
/*
* TODO: get rid of this. Get rid of all order numbers.
*/
int MinecraftProfile::getFreeOrderNumber()
{
int largest = 100;
// yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next.
for(auto thing: m_patches)
{
int order = thing->getOrder();
if(order > largest)
largest = order;
}
return largest + 1;
}

View File

@ -0,0 +1,200 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QAbstractListModel>
#include <QString>
#include <QList>
#include <memory>
#include "Library.h"
#include "VersionFile.h"
#include "JarMod.h"
#include "MojangDownloadInfo.h"
#include "multimc_logic_export.h"
class ProfileStrategy;
class OneSixInstance;
class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel
{
Q_OBJECT
public:
explicit MinecraftProfile(ProfileStrategy *strategy);
void setStrategy(ProfileStrategy * strategy);
ProfileStrategy *strategy();
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const 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;
/// is this version unchanged by the user?
bool isVanilla();
/// remove any customizations on top of whatever 'vanilla' means
bool revertToVanilla();
/// install more jar mods
void installJarMods(QStringList selectedFiles);
/// DEPRECATED, remove ASAP
int getFreeOrderNumber();
enum MoveDirection { MoveUp, MoveDown };
/// move patch file # up or down the list
void move(const int index, const MoveDirection direction);
/// remove patch file # - including files/records
bool remove(const int index);
/// remove patch file by id - including files/records
bool remove(const QString id);
bool customize(int index);
bool revertToBase(int index);
void resetOrder();
/// reload all profile patches from storage, clear the profile and apply the patches
void reload();
/// clear the profile
void clear();
/// apply the patches. Catches all the errors and returns true/false for success/failure
bool reapplyPatches();
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<JarmodPtr> &jarMods);
void applyLibrary(LibraryPtr library);
void applyProblemSeverity(ProblemSeverity severity);
void applyMojangDownload(const QString & key, MojangDownloadInfo::Ptr download);
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<JarmodPtr> & getJarMods() const;
const QList<LibraryPtr> & getLibraries() const;
QString getMainJarUrl() const;
bool hasTrait(const QString & trait) const;
ProblemSeverity getProblemSeverity() const;
public:
/// get the profile patch by id
ProfilePatchPtr versionPatch(const QString &id);
/// get the profile patch by index
ProfilePatchPtr versionPatch(int index);
/// save the current patch order
void saveCurrentOrder() const;
/// Remove all the patches
void clearPatches();
/// Add the patch object to the internal list of patches
void appendPatch(ProfilePatchPtr patch);
private: /* data */
/// the version of Minecraft - jar to use
QString m_minecraftVersion;
/// Release type - "release" or "snapshot"
QString m_minecraftVersionType;
/// Assets type - "legacy" or a version ID
MojangAssetIndexInfo::Ptr m_minecraftAssets;
// Mojang: list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
/**
* 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;
/// The main class to load first
QString m_mainClass;
/// The applet class, for some very old minecraft releases
QString m_appletClass;
/// the list of libraries
QList<LibraryPtr> m_libraries;
/// 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<JarmodPtr> m_jarMods;
ProblemSeverity m_problemSeverity = PROBLEM_NONE;
/*
FIXME: add support for those rules here? Looks like a pile of quick hacks to me though.
"rules": [
{
"action": "allow"
},
{
"action": "disallow",
"os": {
"name": "osx",
"version": "^10\\.5\\.\\d$"
}
}
],
"incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX
10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!"
}
*/
// QList<Rule> rules;
/// list of attached profile patches
QList<ProfilePatchPtr> m_patches;
/// strategy used for profile operations
ProfileStrategy *m_strategy = nullptr;
};

View File

@ -0,0 +1,215 @@
#include "MinecraftVersion.h"
#include "MinecraftProfile.h"
#include "VersionBuildError.h"
#include "ProfileUtils.h"
#include "settings/SettingsObject.h"
#include "minecraft/VersionFilterData.h"
bool MinecraftVersion::usesLegacyLauncher()
{
return getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate;
}
QString MinecraftVersion::descriptor()
{
return m_version;
}
QString MinecraftVersion::name()
{
return m_version;
}
QString MinecraftVersion::typeString() const
{
if(m_type == "snapshot")
{
return QObject::tr("Snapshot");
}
else if (m_type == "release")
{
return QObject::tr("Regular release");
}
else if (m_type == "old_alpha")
{
return QObject::tr("Alpha");
}
else if (m_type == "old_beta")
{
return QObject::tr("Beta");
}
else
{
return QString();
}
}
VersionSource MinecraftVersion::getVersionSource()
{
return m_versionSource;
}
bool MinecraftVersion::hasJarMods()
{
return false;
}
bool MinecraftVersion::isMinecraftVersion()
{
return true;
}
void MinecraftVersion::applyFileTo(MinecraftProfile *profile)
{
if(m_versionSource == Local && getVersionFile())
{
getVersionFile()->applyTo(profile);
}
else
{
throw VersionIncomplete(QObject::tr("Can't apply incomplete/builtin Minecraft version %1").arg(m_version));
}
}
QString MinecraftVersion::getUrl() const
{
// legacy fallback
if(m_versionFileURL.isEmpty())
{
return QString("http://") + URLConstants::AWS_DOWNLOAD_VERSIONS + m_version + "/" + m_version + ".json";
}
// current
return m_versionFileURL;
}
VersionFilePtr MinecraftVersion::getVersionFile()
{
QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_version));
m_problems.clear();
if(!versionFile.exists())
{
if(m_loadedVersionFile)
{
m_loadedVersionFile.reset();
}
addProblem(PROBLEM_WARNING, QObject::tr("The patch file doesn't exist locally. It's possible it just needs to be downloaded."));
}
else
{
try
{
if(versionFile.lastModified() != m_loadedVersionFileTimestamp)
{
auto loadedVersionFile = ProfileUtils::parseBinaryJsonFile(versionFile);
loadedVersionFile->name = "Minecraft";
loadedVersionFile->setCustomizable(true);
m_loadedVersionFileTimestamp = versionFile.lastModified();
m_loadedVersionFile = loadedVersionFile;
}
}
catch(Exception e)
{
m_loadedVersionFile.reset();
addProblem(PROBLEM_ERROR, QObject::tr("The patch file couldn't be read:\n%1").arg(e.cause()));
}
}
return m_loadedVersionFile;
}
bool MinecraftVersion::isCustomizable()
{
switch(m_versionSource)
{
case Local:
case Remote:
// locally cached file, or a remote file that we can acquire can be customized
return true;
default:
// Everything else is undefined and therefore not customizable.
return false;
}
return false;
}
const QList<PatchProblem> &MinecraftVersion::getProblems()
{
if(getVersionFile())
{
return getVersionFile()->getProblems();
}
return ProfilePatch::getProblems();
}
ProblemSeverity MinecraftVersion::getProblemSeverity()
{
if(getVersionFile())
{
return getVersionFile()->getProblemSeverity();
}
return ProfilePatch::getProblemSeverity();
}
void MinecraftVersion::applyTo(MinecraftProfile *profile)
{
// do we have this one cached?
if (m_versionSource == Local)
{
applyFileTo(profile);
return;
}
throw VersionIncomplete(QObject::tr("Minecraft version %1 could not be applied: version files are missing.").arg(m_version));
}
int MinecraftVersion::getOrder()
{
return order;
}
void MinecraftVersion::setOrder(int order)
{
this->order = order;
}
QList<JarmodPtr> MinecraftVersion::getJarMods()
{
return QList<JarmodPtr>();
}
QString MinecraftVersion::getName()
{
return "Minecraft";
}
QString MinecraftVersion::getVersion()
{
return m_version;
}
QString MinecraftVersion::getID()
{
return "net.minecraft";
}
QString MinecraftVersion::getFilename()
{
return QString();
}
QDateTime MinecraftVersion::getReleaseDateTime()
{
return m_releaseTime;
}
bool MinecraftVersion::needsUpdate()
{
return m_versionSource == Remote || hasUpdate();
}
bool MinecraftVersion::hasUpdate()
{
return m_versionSource == Remote || (m_versionSource == Local && upstreamUpdate);
}
bool MinecraftVersion::isCustom()
{
// if we add any other source types, this will evaluate to false for them.
return m_versionSource != Local && m_versionSource != Remote;
}

View File

@ -0,0 +1,119 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QStringList>
#include <QSet>
#include <QDateTime>
#include "BaseVersion.h"
#include "ProfilePatch.h"
#include "VersionFile.h"
#include "multimc_logic_export.h"
class MinecraftProfile;
class MinecraftVersion;
typedef std::shared_ptr<MinecraftVersion> MinecraftVersionPtr;
class MULTIMC_LOGIC_EXPORT MinecraftVersion : public BaseVersion, public ProfilePatch
{
friend class MinecraftVersionList;
public: /* methods */
// FIXME: nuke this.
bool usesLegacyLauncher();
virtual QString descriptor() override;
virtual QString name() override;
virtual QString typeString() const override;
virtual bool hasJarMods() override;
virtual bool isMinecraftVersion() override;
virtual void applyTo(MinecraftProfile *profile) override;
virtual int getOrder() override;
virtual void setOrder(int order) override;
virtual QList<JarmodPtr> getJarMods() override;
virtual QString getID() override;
virtual QString getVersion() override;
virtual QString getName() override;
virtual QString getFilename() override;
QDateTime getReleaseDateTime() override;
VersionSource getVersionSource() override;
bool needsUpdate();
bool hasUpdate();
virtual bool isCustom() override;
virtual bool isMoveable() override
{
return false;
}
virtual bool isCustomizable() override;
virtual bool isRemovable() override
{
return false;
}
virtual bool isRevertible() override
{
return false;
}
virtual bool isEditable() override
{
return false;
}
virtual bool isVersionChangeable() override
{
return true;
}
virtual VersionFilePtr getVersionFile() override;
// virtual QJsonDocument toJson(bool saveOrder) override;
QString getUrl() const;
virtual const QList<PatchProblem> &getProblems() override;
virtual ProblemSeverity getProblemSeverity() override;
private: /* methods */
void applyFileTo(MinecraftProfile *profile);
protected: /* data */
VersionSource m_versionSource = Remote;
/// The URL that this version will be downloaded from.
QString m_versionFileURL;
/// the human readable version name
QString m_version;
/// The type of this release
QString m_type;
/// the time this version was actually released by Mojang
QDateTime m_releaseTime;
/// the time this version was last updated by Mojang
QDateTime m_updateTime;
/// order of this file... default = -2
int order = -2;
/// an update available from Mojang
MinecraftVersionPtr upstreamUpdate;
QDateTime m_loadedVersionFileTimestamp;
mutable VersionFilePtr m_loadedVersionFile;
};

View File

@ -0,0 +1,591 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QtXml>
#include "Json.h"
#include <QtAlgorithms>
#include <QtNetwork>
#include "Env.h"
#include "Exception.h"
#include "MinecraftVersionList.h"
#include "net/URLConstants.h"
#include "ParseUtils.h"
#include "ProfileUtils.h"
#include "VersionFilterData.h"
#include "onesix/OneSixVersionFormat.h"
#include "MojangVersionFormat.h"
#include <FileSystem.h>
static const char * localVersionCache = "versions/versions.dat";
class MCVListLoadTask : public Task
{
Q_OBJECT
public:
explicit MCVListLoadTask(MinecraftVersionList *vlist);
virtual ~MCVListLoadTask() override{};
virtual void executeTask() override;
protected
slots:
void list_downloaded();
protected:
QNetworkReply *vlistReply;
MinecraftVersionList *m_list;
MinecraftVersion *m_currentStable;
};
class MCVListVersionUpdateTask : public Task
{
Q_OBJECT
public:
explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, std::shared_ptr<MinecraftVersion> updatedVersion);
virtual ~MCVListVersionUpdateTask() override{};
virtual void executeTask() override;
protected
slots:
void json_downloaded();
protected:
NetJobPtr specificVersionDownloadJob;
std::shared_ptr<MinecraftVersion> updatedVersion;
MinecraftVersionList *m_list;
};
class ListLoadError : public Exception
{
public:
ListLoadError(QString cause) : Exception(cause) {};
virtual ~ListLoadError() noexcept
{
}
};
MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent)
{
loadCachedList();
}
Task *MinecraftVersionList::getLoadTask()
{
return new MCVListLoadTask(this);
}
bool MinecraftVersionList::isLoaded()
{
return m_loaded;
}
const BaseVersionPtr MinecraftVersionList::at(int i) const
{
return m_vlist.at(i);
}
int MinecraftVersionList::count() const
{
return m_vlist.count();
}
static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second)
{
auto left = std::dynamic_pointer_cast<MinecraftVersion>(first);
auto right = std::dynamic_pointer_cast<MinecraftVersion>(second);
return left->getReleaseDateTime() > right->getReleaseDateTime();
}
void MinecraftVersionList::sortInternal()
{
qSort(m_vlist.begin(), m_vlist.end(), cmpVersions);
}
void MinecraftVersionList::loadCachedList()
{
QFile localIndex(localVersionCache);
if (!localIndex.exists())
{
return;
}
if (!localIndex.open(QIODevice::ReadOnly))
{
// FIXME: this is actually a very bad thing! How do we deal with this?
qCritical() << "The minecraft version cache can't be read.";
return;
}
auto data = localIndex.readAll();
try
{
localIndex.close();
QJsonDocument jsonDoc = QJsonDocument::fromBinaryData(data);
if (jsonDoc.isNull())
{
throw ListLoadError(tr("Error reading the version list."));
}
loadList(jsonDoc, Local);
}
catch (Exception &e)
{
// the cache has gone bad for some reason... flush it.
qCritical() << "The minecraft version cache is corrupted. Flushing cache.";
localIndex.remove();
return;
}
m_hasLocalIndex = true;
}
void MinecraftVersionList::loadList(QJsonDocument jsonDoc, VersionSource source)
{
qDebug() << "Loading" << ((source == Remote) ? "remote" : "local") << "version list.";
if (!jsonDoc.isObject())
{
throw ListLoadError(tr("Error parsing version list JSON: jsonDoc is not an object"));
}
QJsonObject root = jsonDoc.object();
try
{
QJsonObject latest = Json::requireObject(root.value("latest"));
m_latestReleaseID = Json::requireString(latest.value("release"));
m_latestSnapshotID = Json::requireString(latest.value("snapshot"));
}
catch (Exception &err)
{
qCritical()
<< tr("Error parsing version list JSON: couldn't determine latest versions");
}
// Now, get the array of versions.
if (!root.value("versions").isArray())
{
throw ListLoadError(tr("Error parsing version list JSON: version list object is "
"missing 'versions' array"));
}
QJsonArray versions = root.value("versions").toArray();
QList<BaseVersionPtr> tempList;
for (auto version : versions)
{
// Load the version info.
if (!version.isObject())
{
qCritical() << "Error while parsing version list : invalid JSON structure";
continue;
}
QJsonObject versionObj = version.toObject();
QString versionID = versionObj.value("id").toString("");
if (versionID.isEmpty())
{
qCritical() << "Error while parsing version : version ID is missing";
continue;
}
if (g_VersionFilterData.legacyBlacklist.contains(versionID))
{
qWarning() << "Blacklisted legacy version ignored: " << versionID;
continue;
}
// Now, we construct the version object and add it to the list.
std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion());
mcVersion->m_version = versionID;
mcVersion->m_releaseTime = timeFromS3Time(versionObj.value("releaseTime").toString(""));
mcVersion->m_updateTime = timeFromS3Time(versionObj.value("time").toString(""));
// depends on where we load the version from -- network request or local file?
mcVersion->m_versionSource = source;
mcVersion->m_versionFileURL = versionObj.value("url").toString("");
QString versionTypeStr = versionObj.value("type").toString("");
if (versionTypeStr.isEmpty())
{
qCritical() << "Ignoring" << versionID
<< "because it doesn't have the version type set.";
continue;
}
// OneSix or Legacy. use filter to determine type
if (versionTypeStr == "release")
{
}
else if (versionTypeStr == "snapshot") // It's a snapshot... yay
{
}
else if (versionTypeStr == "old_alpha")
{
}
else if (versionTypeStr == "old_beta")
{
}
else
{
qCritical() << "Ignoring" << versionID
<< "because it has an invalid version type.";
continue;
}
mcVersion->m_type = versionTypeStr;
qDebug() << "Loaded version" << versionID << "from"
<< ((source == Remote) ? "remote" : "local") << "version list.";
tempList.append(mcVersion);
}
updateListData(tempList);
if(source == Remote)
{
m_loaded = true;
}
}
void MinecraftVersionList::sortVersions()
{
beginResetModel();
sortInternal();
endResetModel();
}
QVariant MinecraftVersionList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
auto version = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist[index.row()]);
switch (role)
{
case VersionPointerRole:
return qVariantFromValue(m_vlist[index.row()]);
case VersionRole:
return version->name();
case VersionIdRole:
return version->descriptor();
case RecommendedRole:
return version->descriptor() == m_latestReleaseID;
case LatestRole:
{
if(version->descriptor() != m_latestSnapshotID)
return false;
MinecraftVersionPtr latestRelease = std::dynamic_pointer_cast<MinecraftVersion>(getLatestStable());
/*
if(latestRelease && latestRelease->m_releaseTime > version->m_releaseTime)
{
return false;
}
*/
return true;
}
case TypeRole:
return version->typeString();
default:
return QVariant();
}
}
BaseVersionList::RoleList MinecraftVersionList::providesRoles() const
{
return {VersionPointerRole, VersionRole, VersionIdRole, RecommendedRole, LatestRole, TypeRole};
}
BaseVersionPtr MinecraftVersionList::getLatestStable() const
{
if(m_lookup.contains(m_latestReleaseID))
return m_lookup[m_latestReleaseID];
return BaseVersionPtr();
}
BaseVersionPtr MinecraftVersionList::getRecommended() const
{
return getLatestStable();
}
void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions)
{
beginResetModel();
for (auto version : versions)
{
auto descr = version->descriptor();
if (!m_lookup.contains(descr))
{
m_lookup[version->descriptor()] = version;
m_vlist.append(version);
continue;
}
auto orig = std::dynamic_pointer_cast<MinecraftVersion>(m_lookup[descr]);
auto added = std::dynamic_pointer_cast<MinecraftVersion>(version);
// updateListData is called after Mojang list loads. those can be local or remote
// remote comes always after local
// any other options are ignored
if (orig->m_versionSource != Local || added->m_versionSource != Remote)
{
continue;
}
// alright, it's an update. put it inside the original, for further processing.
orig->upstreamUpdate = added;
}
sortInternal();
endResetModel();
}
MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist)
{
m_list = vlist;
m_currentStable = NULL;
vlistReply = nullptr;
}
void MCVListLoadTask::executeTask()
{
setStatus(tr("Loading instance version list..."));
auto worker = ENV.qnam();
vlistReply = worker->get(QNetworkRequest(QUrl("https://launchermeta.mojang.com/mc/game/version_manifest.json")));
connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded()));
}
void MCVListLoadTask::list_downloaded()
{
if (vlistReply->error() != QNetworkReply::NoError)
{
vlistReply->deleteLater();
emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString());
return;
}
auto data = vlistReply->readAll();
vlistReply->deleteLater();
try
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
throw ListLoadError(
tr("Error parsing version list JSON: %1").arg(jsonError.errorString()));
}
m_list->loadList(jsonDoc, Remote);
}
catch (Exception &e)
{
emitFailed(e.cause());
return;
}
emitSucceeded();
return;
}
MCVListVersionUpdateTask::MCVListVersionUpdateTask(MinecraftVersionList *vlist, std::shared_ptr<MinecraftVersion> updatedVersion)
: Task()
{
m_list = vlist;
this->updatedVersion = updatedVersion;
}
void MCVListVersionUpdateTask::executeTask()
{
auto job = new NetJob("Version index");
job->addNetAction(ByteArrayDownload::make(QUrl(updatedVersion->getUrl())));
specificVersionDownloadJob.reset(job);
connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(json_downloaded()));
connect(specificVersionDownloadJob.get(), SIGNAL(failed(QString)), SIGNAL(failed(QString)));
connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
specificVersionDownloadJob->start();
}
void MCVListVersionUpdateTask::json_downloaded()
{
NetActionPtr DlJob = specificVersionDownloadJob->first();
auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data;
specificVersionDownloadJob.reset();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
emitFailed(tr("The download version file is not valid."));
return;
}
VersionFilePtr file;
try
{
file = MojangVersionFormat::versionFileFromJson(jsonDoc, "net.minecraft.json");
}
catch (Exception &e)
{
emitFailed(tr("Couldn't process version file: %1").arg(e.cause()));
return;
}
// Strip LWJGL from the version file. We use our own.
ProfileUtils::removeLwjglFromPatch(file);
file->fileId = "net.minecraft";
// now dump the file to disk
auto doc = OneSixVersionFormat::versionFileToJson(file, false);
auto newdata = doc.toBinaryData();
auto id = updatedVersion->descriptor();
QString targetPath = "versions/" + id + "/" + id + ".dat";
FS::ensureFilePathExists(targetPath);
QSaveFile vfile1(targetPath);
if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly))
{
emitFailed(tr("Can't open %1 for writing.").arg(targetPath));
return;
}
qint64 actual = 0;
if ((actual = vfile1.write(newdata)) != newdata.size())
{
emitFailed(tr("Failed to write into %1. Written %2 out of %3.")
.arg(targetPath)
.arg(actual)
.arg(newdata.size()));
return;
}
if (!vfile1.commit())
{
emitFailed(tr("Can't commit changes to %1").arg(targetPath));
return;
}
m_list->finalizeUpdate(id);
emitSucceeded();
}
std::shared_ptr<Task> MinecraftVersionList::createUpdateTask(QString version)
{
auto iter = m_lookup.find(version);
if(iter == m_lookup.end())
return nullptr;
auto mcversion = std::dynamic_pointer_cast<MinecraftVersion>(*iter);
if(!mcversion)
{
return nullptr;
}
return std::shared_ptr<Task>(new MCVListVersionUpdateTask(this, mcversion));
}
void MinecraftVersionList::saveCachedList()
{
// FIXME: throw.
if (!FS::ensureFilePathExists(localVersionCache))
return;
QSaveFile tfile(localVersionCache);
if (!tfile.open(QIODevice::WriteOnly | QIODevice::Truncate))
return;
QJsonObject toplevel;
QJsonArray entriesArr;
for (auto version : m_vlist)
{
auto mcversion = std::dynamic_pointer_cast<MinecraftVersion>(version);
// do not save the remote versions.
if (mcversion->m_versionSource != Local)
continue;
QJsonObject entryObj;
entryObj.insert("id", mcversion->descriptor());
entryObj.insert("version", mcversion->descriptor());
entryObj.insert("time", timeToS3Time(mcversion->m_updateTime));
entryObj.insert("releaseTime", timeToS3Time(mcversion->m_releaseTime));
entryObj.insert("url", mcversion->m_versionFileURL);
entryObj.insert("type", mcversion->m_type);
entriesArr.append(entryObj);
}
toplevel.insert("versions", entriesArr);
{
bool someLatest = false;
QJsonObject latestObj;
if(!m_latestReleaseID.isNull())
{
latestObj.insert("release", m_latestReleaseID);
someLatest = true;
}
if(!m_latestSnapshotID.isNull())
{
latestObj.insert("snapshot", m_latestSnapshotID);
someLatest = true;
}
if(someLatest)
{
toplevel.insert("latest", latestObj);
}
}
QJsonDocument doc(toplevel);
QByteArray jsonData = doc.toBinaryData();
qint64 result = tfile.write(jsonData);
if (result == -1)
return;
if (result != jsonData.size())
return;
tfile.commit();
}
void MinecraftVersionList::finalizeUpdate(QString version)
{
int idx = -1;
for (int i = 0; i < m_vlist.size(); i++)
{
if (version == m_vlist[i]->descriptor())
{
idx = i;
break;
}
}
if (idx == -1)
{
return;
}
auto updatedVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist[idx]);
// if we have an update for the version, replace it, make the update local
if (updatedVersion->upstreamUpdate)
{
auto updatedWith = updatedVersion->upstreamUpdate;
updatedWith->m_versionSource = Local;
m_vlist[idx] = updatedWith;
m_lookup[version] = updatedWith;
}
else
{
// otherwise, just set the version as local;
updatedVersion->m_versionSource = Local;
}
dataChanged(index(idx), index(idx));
saveCachedList();
}
#include "MinecraftVersionList.moc"

View File

@ -0,0 +1,72 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QList>
#include <QSet>
#include "BaseVersionList.h"
#include "tasks/Task.h"
#include "minecraft/MinecraftVersion.h"
#include <net/NetJob.h>
#include "multimc_logic_export.h"
class MCVListLoadTask;
class MCVListVersionUpdateTask;
class MULTIMC_LOGIC_EXPORT MinecraftVersionList : public BaseVersionList
{
Q_OBJECT
private:
void sortInternal();
void loadList(QJsonDocument jsonDoc, VersionSource source);
void loadCachedList();
void saveCachedList();
void finalizeUpdate(QString version);
public:
friend class MCVListLoadTask;
friend class MCVListVersionUpdateTask;
explicit MinecraftVersionList(QObject *parent = 0);
std::shared_ptr<Task> createUpdateTask(QString version);
virtual Task *getLoadTask() override;
virtual bool isLoaded() override;
virtual const BaseVersionPtr at(int i) const override;
virtual int count() const override;
virtual void sortVersions() override;
virtual QVariant data(const QModelIndex & index, int role) const override;
virtual RoleList providesRoles() const override;
virtual BaseVersionPtr getLatestStable() const override;
virtual BaseVersionPtr getRecommended() const override;
protected:
QList<BaseVersionPtr> m_vlist;
QMap<QString, BaseVersionPtr> m_lookup;
bool m_loaded = false;
bool m_hasLocalIndex = false;
QString m_latestReleaseID = "INVALID";
QString m_latestSnapshotID = "INVALID";
protected
slots:
virtual void updateListData(QList<BaseVersionPtr> versions) override;
};

View File

@ -0,0 +1,377 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QDir>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <quazip.h>
#include <quazipfile.h>
#include "Mod.h"
#include "settings/INIFile.h"
#include <FileSystem.h>
#include <QDebug>
Mod::Mod(const QFileInfo &file)
{
repath(file);
}
void Mod::repath(const QFileInfo &file)
{
m_file = file;
QString name_base = file.fileName();
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_type == MOD_ZIPFILE)
{
QuaZip zip(m_file.filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
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;
}
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;
QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
}
ReadLiteModInfo(file.readAll());
file.close();
}
zip.close();
}
}
// NEW format
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
// OLD format:
// 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();
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;
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;
}
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();
}
bool Mod::replace(Mod &with)
{
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;
}
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;
}
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";
}
}
bool Mod::enable(bool value)
{
if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
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;
}
bool Mod::operator==(const Mod &other) const
{
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();
}

View File

@ -0,0 +1,134 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QFileInfo>
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
};
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
{
if(m_name.trimmed().isEmpty())
{
return m_mmc_id;
}
return m_name;
}
QString version() const;
QString homeurl() const
{
return m_homeurl;
}
QString description() const
{
return m_description;
}
QString authors() const
{
return m_authors;
}
QString credits() const
{
return m_credits;
}
bool enabled() const
{
return m_enabled;
}
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);
// 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);
protected:
// FIXME: what do do with those? HMM...
/*
void ReadModInfoData(QString info);
void ReadForgeInfoData(QString infoFileData);
*/
QFileInfo m_file;
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;
};

View File

@ -0,0 +1,616 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ModList.h"
#include <FileSystem.h>
#include <QMimeData>
#include <QUrl>
#include <QUuid>
#include <QString>
#include <QFileSystemWatcher>
#include <QDebug>
ModList::ModList(const QString &dir, const QString &list_file)
: QAbstractListModel(), 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);
m_list_id = QUuid::createUuid().toString();
m_watcher = new QFileSystemWatcher(this);
is_watching = false;
connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
SLOT(directoryChanged(QString)));
}
void ModList::startWatching()
{
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 ModList::stopWatching()
{
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();
}
}
void ModList::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);
}
bool ModList::update()
{
if (!isValid())
return 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();
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;
}
}
}
beginResetModel();
mods.swap(orderedMods);
endResetModel();
if (orderOrStateChanged && !m_list_file.isEmpty())
{
qDebug() << "Mod list " << m_list_file << " changed!";
saveListFile();
emit changed();
}
return true;
}
void ModList::directoryChanged(QString path)
{
update();
}
ModList::OrderList ModList::readListFile()
{
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();
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 ModList::saveListFile()
{
if (m_list_file.isNull() || m_list_file.isEmpty())
return false;
QFile textFile(m_list_file);
if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
return false;
QTextStream textStream;
textStream.setGenerateByteOrderMark(true);
textStream.setCodec("UTF-8");
textStream.setDevice(&textFile);
for (auto mod : mods)
{
textStream << mod.mmc_id();
if (!mod.enabled())
textStream << ".disabled";
textStream << endl;
}
textFile.close();
return false;
}
bool ModList::isValid()
{
return m_dir.exists() && m_dir.isReadable();
}
bool ModList::installMod(const QString &filename, int index)
{
// 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();
if (!fileinfo.exists() || !fileinfo.isReadable() || index < 0)
{
return false;
}
Mod m(fileinfo);
if (!m.valid())
return false;
// if it's already there, replace the original mod (in place)
int idx = mods.indexOf(m);
if (idx != -1)
{
int idx2 = mods.indexOf(m, idx + 1);
if (idx2 != -1)
return false;
if (mods[idx].replace(m))
{
auto left = this->index(index);
auto right = this->index(index, columnCount(QModelIndex()) - 1);
emit dataChanged(left, right);
saveListFile();
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;
m.repath(newpath);
beginInsertRows(QModelIndex(), index, index);
mods.insert(index, m);
endInsertRows();
saveListFile();
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);
beginInsertRows(QModelIndex(), index, index);
mods.insert(index, m);
endInsertRows();
saveListFile();
update();
return true;
}
return false;
}
bool ModList::deleteMod(int index)
{
if (index >= mods.size() || index < 0)
return false;
Mod &m = mods[index];
if (m.destroy())
{
beginRemoveRows(QModelIndex(), index, index);
mods.removeAt(index);
endRemoveRows();
saveListFile();
emit changed();
return true;
}
return false;
}
bool ModList::deleteMods(int first, int last)
{
for (int i = first; i <= last; i++)
{
Mod &m = mods[i];
m.destroy();
}
beginRemoveRows(QModelIndex(), first, last);
mods.erase(mods.begin() + first, mods.begin() + last + 1);
endRemoveRows();
saveListFile();
emit changed();
return true;
}
bool ModList::moveModTo(int from, int to)
{
if (from < 0 || from >= mods.size())
return false;
if (to >= rowCount())
to = rowCount() - 1;
if (to == -1)
to = rowCount() - 1;
if (from == to)
return false;
int togap = to > from ? to + 1 : to;
beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap);
mods.move(from, to);
endMoveRows();
saveListFile();
emit changed();
return true;
}
bool ModList::moveModUp(int from)
{
if (from > 0)
return moveModTo(from, from - 1);
return false;
}
bool ModList::moveModsUp(int first, int last)
{
if (first == 0)
return false;
beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
mods.move(first - 1, last);
endMoveRows();
saveListFile();
emit changed();
return true;
}
bool ModList::moveModDown(int from)
{
if (from < 0)
return false;
if (from < mods.size() - 1)
return moveModTo(from, from + 1);
return false;
}
bool ModList::moveModsDown(int first, int last)
{
if (last == mods.size() - 1)
return false;
beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2);
mods.move(last + 1, first);
endMoveRows();
saveListFile();
emit changed();
return true;
}
int ModList::columnCount(const QModelIndex &parent) const
{
return 3;
}
QVariant ModList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
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();
default:
return QVariant();
}
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();
}
}
bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
{
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;
}
QVariant ModList::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");
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.");
default:
return QVariant();
}
default:
return QVariant();
}
return QVariant();
}
Qt::ItemFlags ModList::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;
}
QStringList ModList::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
types << "text/plain";
return types;
}
Qt::DropActions ModList::supportedDropActions() const
{
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions ModList::supportedDragActions() const
{
// move to other mod lists or VOID
return Qt::MoveAction;
}
QMimeData *ModList::mimeData(const QModelIndexList &indexes) const
{
QMimeData *data = new QMimeData();
if (indexes.size() == 0)
return data;
auto idx = indexes[0];
int row = idx.row();
if (row < 0 || row >= mods.size())
return data;
QStringList params;
params << m_list_id << QString::number(row);
data->setText(params.join('|'));
return data;
}
bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
// check if the action is supported
if (!data || !(action & supportedDropActions()))
return false;
if (parent.isValid())
{
row = parent.row();
column = parent.column();
}
if (row > rowCount())
row = rowCount();
if (row == -1)
row = rowCount();
if (column == -1)
column = 0;
qDebug() << "Drop row: " << row << " column: " << column;
// 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();
installMod(filename, row);
// if there is no ordering, re-sort the list
if (m_list_file.isEmpty())
{
beginResetModel();
internalSort(mods);
endResetModel();
}
}
if (was_watching)
startWatching();
return true;
}
else if (data->hasText())
{
QString sourcestr = data->text();
auto list = sourcestr.split('|');
if (list.size() != 2)
return false;
QString remoteId = list[0];
int remoteIndex = list[1].toInt();
qDebug() << "move: " << sourcestr;
// no moving of things between two lists
if (remoteId != m_list_id)
return false;
// no point moving to the same place...
if (row == remoteIndex)
return false;
// otherwise, move the mod :D
moveModTo(remoteIndex, row);
return true;
}
return false;
}

View File

@ -0,0 +1,160 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QList>
#include <QString>
#include <QDir>
#include <QAbstractListModel>
#include "minecraft/Mod.h"
#include "multimc_logic_export.h"
class LegacyInstance;
class BaseInstance;
class QFileSystemWatcher;
/**
* A legacy mod list.
* Backed by a folder.
*/
class MULTIMC_LOGIC_EXPORT ModList : public QAbstractListModel
{
Q_OBJECT
public:
enum Columns
{
ActiveColumn = 0,
NameColumn,
VersionColumn
};
ModList(const QString &dir, const QString &list_file = QString());
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
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 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();
/**
* Adds the given mod to the list at the given index - if the list supports custom ordering
*/
virtual bool installMod(const QString & filename, int index = 0);
/// Deletes the mod at the given index.
virtual bool deleteMod(int index);
/// Deletes all the selected mods
virtual bool deleteMods(int first, int last);
/**
* move the mod at index to the position N
* 0 is the beginning of the list, length() is the end of the list.
*/
virtual bool moveModTo(int from, int to);
/**
* move the mod at index one position upwards
*/
virtual bool moveModUp(int from);
virtual bool moveModsUp(int first, int last);
/**
* move the mod at index one position downwards
*/
virtual bool moveModDown(int from);
virtual bool moveModsDown(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;
/// what drop actions do we support?
virtual Qt::DropActions supportedDropActions() const;
void startWatching();
void stopWatching();
virtual bool isValid();
QDir dir()
{
return m_dir;
}
const QList<Mod> & allMods()
{
return mods;
}
private:
void internalSort(QList<Mod> & what);
struct OrderItem
{
QString id;
bool enabled = false;
};
typedef QList<OrderItem> OrderList;
OrderList readListFile();
bool saveListFile();
private
slots:
void directoryChanged(QString path);
signals:
void changed();
protected:
QFileSystemWatcher *m_watcher;
bool is_watching;
QDir m_dir;
QString m_list_file;
QString m_list_id;
QList<Mod> mods;
};

Some files were not shown because too many files have changed in this diff Show More