Merge branch 'develop' into feature_badges

Conflicts:
	logic/OneSixInstance.cpp
This commit is contained in:
Jan Dalheimer 2014-03-10 19:24:29 +01:00
commit fcc5bc2ce0
40 changed files with 2683 additions and 1758 deletions

2
.gitignore vendored
View File

@ -13,6 +13,8 @@ MultiMC5.kdev4
MultiMC.pro.user
CMakeLists.txt.user
CMakeLists.txt.user.*
/.project
/.settings
# Build dirs
build

View File

@ -257,6 +257,7 @@ SET(MULTIMC_SOURCES
MultiMC.h
MultiMC.cpp
MultiMCVersion.h
MMCError.h
# Logging
logger/QsDebugOutput.cpp
@ -353,6 +354,10 @@ logic/ModList.cpp
logic/InstanceLauncher.h
logic/InstanceLauncher.cpp
# JSON parsing helpers
logic/MMCJson.h
logic/MMCJson.cpp
# network stuffs
logic/net/NetAction.h
logic/net/MD5EtagDownload.h
@ -414,31 +419,38 @@ logic/LegacyInstance.cpp
logic/LegacyInstance_p.h
logic/LegacyUpdate.h
logic/LegacyUpdate.cpp
logic/LegacyForge.h
logic/LegacyForge.cpp
# OneSix instances
logic/OneSixUpdate.h
logic/OneSixUpdate.cpp
logic/OneSixVersion.h
logic/OneSixVersion.cpp
logic/OneSixInstance.h
logic/OneSixInstance.cpp
logic/OneSixInstance_p.h
# OneSix version json infrastructure
logic/OneSixVersionBuilder.h
logic/OneSixVersionBuilder.cpp
logic/VersionFile.h
logic/VersionFile.cpp
logic/VersionFinal.h
logic/VersionFinal.cpp
logic/OneSixLibrary.h
logic/OneSixLibrary.cpp
logic/OneSixRule.h
logic/OneSixRule.cpp
logic/OpSys.h
logic/OpSys.cpp
# Mod installers
logic/BaseInstaller.h
logic/BaseInstaller.cpp
logic/ForgeInstaller.h
logic/ForgeInstaller.cpp
logic/LiteLoaderInstaller.h
logic/LiteLoaderInstaller.cpp
logic/OneSixInstance.h
logic/OneSixInstance.cpp
logic/OneSixInstance_p.h
logic/OneSixVersionBuilder.h
logic/OneSixVersionBuilder.cpp
# Nostalgia
logic/NostalgiaInstance.h
@ -758,24 +770,8 @@ INCLUDE(CPack)
include_directories(${PROJECT_BINARY_DIR}/include)
### translation stuff
file (GLOB TRANSLATIONS_FILES translations/*.ts)
option (UPDATE_TRANSLATIONS "Update source translation translations/*.ts files (WARNING: make clean will delete the source .ts files! Danger!)")
IF(UPDATE_TRANSLATIONS)
qt5_create_translation(QM_FILES ${FILES_TO_TRANSLATE} ${TRANSLATIONS_FILES})
ELSE()
qt5_add_translation(QM_FILES ${TRANSLATIONS_FILES})
ENDIF()
add_custom_target (translations DEPENDS ${QM_FILES})
IF(APPLE AND UNIX) ## OSX
install(FILES ${QM_FILES} DESTINATION MultiMC.app/Contents/MacOS/translations)
ELSE()
install(FILES ${QM_FILES} DESTINATION translations)
ENDIF()
# Translations
add_subdirectory(translations)
# Tests
add_subdirectory(tests)

25
MMCError.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <exception>
#include <QString>
#include <logger/QsLog.h>
class MMCError : public std::exception
{
public:
MMCError(QString cause)
{
exceptionCause = cause;
QLOG_ERROR() << "Exception: " + cause;
};
virtual ~MMCError() noexcept {}
virtual const char *what() const noexcept
{
return exceptionCause.toLocal8Bit();
};
virtual QString cause() const
{
return exceptionCause;
}
private:
QString exceptionCause;
};

View File

@ -1,4 +1,3 @@
#include "MultiMC.h"
#include <iostream>
#include <QDir>
@ -43,6 +42,11 @@
#include "logger/QsLog.h"
#include <logger/QsLogDest.h>
#ifdef Q_OS_WIN32
#include <windows.h>
static const int APPDATA_BUFFER_SIZE = 1024;
#endif
using namespace Util::Commandline;
MultiMC::MultiMC(int &argc, char **argv, bool root_override)
@ -340,7 +344,16 @@ void MultiMC::initGlobalSettings()
#ifdef Q_OS_LINUX
QString ftbDefault = QDir::home().absoluteFilePath(".ftblauncher");
#elif defined(Q_OS_WIN32)
QString ftbDefault = PathCombine(QStandardPaths::writableLocation(QStandardPaths::DataLocation), "/ftblauncher");
wchar_t buf[APPDATA_BUFFER_SIZE];
QString ftbDefault;
if(!GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE))
{
QLOG_FATAL() << "Your APPDATA folder is missing! If you are on windows, this means your system is broken. If you aren't on windows, how the **** are you running the windows build????";
}
else
{
ftbDefault = PathCombine(QString::fromWCharArray(buf), "ftblauncher");
}
#elif defined(Q_OS_MAC)
QString ftbDefault =
PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
@ -457,6 +470,7 @@ void MultiMC::initHttpMetaCache()
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("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir(root()).absolutePath());
m_metacache->Load();

View File

@ -28,6 +28,11 @@ void INISettingsObject::setFilePath(const QString &filePath)
m_filePath = filePath;
}
bool INISettingsObject::reload()
{
return m_ini.loadFile(m_filePath) && SettingsObject::reload();
}
void INISettingsObject::changeSetting(const Setting &setting, QVariant value)
{
if (contains(setting.id()))

View File

@ -47,6 +47,8 @@ public:
*/
virtual void setFilePath(const QString &filePath);
bool reload() override;
protected
slots:
virtual void changeSetting(const Setting &setting, QVariant value);

View File

@ -126,6 +126,15 @@ bool SettingsObject::contains(const QString &id)
return m_settings.contains(id);
}
bool SettingsObject::reload()
{
for (auto setting : m_settings.values())
{
setting->set(setting->get());
}
return true;
}
void SettingsObject::connectSignals(const Setting &setting)
{
connect(&setting, SIGNAL(settingChanged(const Setting &, QVariant)),

View File

@ -113,6 +113,12 @@ public:
*/
bool contains(const QString &id);
/*!
* \brief Reloads the settings and emit signals for changed settings
* \return True if reloading was successful
*/
virtual bool reload();
signals:
/*!
* \brief Signal emitted when one of this SettingsObject object's settings changes.

View File

@ -34,7 +34,7 @@
#include "gui/dialogs/ProgressDialog.h"
#include "logic/ModList.h"
#include "logic/OneSixVersion.h"
#include "logic/VersionFinal.h"
#include "logic/EnabledItemFilter.h"
#include "logic/lists/ForgeVersionList.h"
#include "logic/lists/LiteLoaderVersionList.h"
@ -42,8 +42,7 @@
#include "logic/LiteLoaderInstaller.h"
#include "logic/OneSixVersionBuilder.h"
template<typename A, typename B>
QMap<A, B> invert(const QMap<B, A> &in)
template <typename A, typename B> QMap<A, B> invert(const QMap<B, A> &in)
{
QMap<A, B> out;
for (auto it = in.begin(); it != in.end(); ++it)
@ -96,7 +95,8 @@ OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent)
m_resourcepacks->startWatching();
}
connect(m_inst, &OneSixInstance::versionReloaded, this, &OneSixModEditDialog::updateVersionControls);
connect(m_inst, &OneSixInstance::versionReloaded, this,
&OneSixModEditDialog::updateVersionControls);
}
OneSixModEditDialog::~OneSixModEditDialog()
@ -110,7 +110,6 @@ void OneSixModEditDialog::updateVersionControls()
{
ui->forgeBtn->setEnabled(true);
ui->liteloaderBtn->setEnabled(true);
ui->mainClassEdit->setText(m_version->mainClass);
}
void OneSixModEditDialog::disableVersionControls()
@ -119,125 +118,86 @@ void OneSixModEditDialog::disableVersionControls()
ui->liteloaderBtn->setEnabled(false);
ui->reloadLibrariesBtn->setEnabled(false);
ui->removeLibraryBtn->setEnabled(false);
ui->mainClassEdit->setText("");
}
bool OneSixModEditDialog::reloadInstanceVersion()
{
try
{
m_inst->reloadVersion();
return true;
}
catch (MMCError &e)
{
QMessageBox::critical(this, tr("Error"), e.cause());
return false;
}
catch (...)
{
QMessageBox::critical(
this, tr("Error"),
tr("Failed to load the version description file for reasons unknown."));
return false;
}
}
void OneSixModEditDialog::on_reloadLibrariesBtn_clicked()
{
m_inst->reloadVersion(this);
reloadInstanceVersion();
}
void OneSixModEditDialog::on_removeLibraryBtn_clicked()
{
if (ui->libraryTreeView->currentIndex().isValid())
{
// FIXME: use actual model, not reloading.
if (!m_version->remove(ui->libraryTreeView->currentIndex().row()))
{
QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
}
else
{
m_inst->reloadVersion(this);
reloadInstanceVersion();
}
}
}
void OneSixModEditDialog::on_resetLibraryOrderBtn_clicked()
{
QDir(m_inst->instanceRoot()).remove("order.json");
m_inst->reloadVersion(this);
// FIXME: IMPLEMENT LOGIC IN MODEL. SEE LEGACY DIALOG FOR EXAMPLE(S).
}
void OneSixModEditDialog::on_moveLibraryUpBtn_clicked()
{
QMap<QString, int> order = getExistingOrder();
if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty())
{
return;
}
const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row();
const QString ourId = m_version->versionFileId(ourRow);
const int ourOrder = order[ourId];
if (ourId.isNull() || ourId.startsWith("org.multimc."))
{
return;
}
QMap<int, QString> sortedOrder = invert(order);
QList<int> sortedOrders = sortedOrder.keys();
const int ourIndex = sortedOrders.indexOf(ourOrder);
if (ourIndex <= 0)
{
return;
}
const int ourNewOrder = sortedOrders.at(ourIndex - 1);
order[ourId] = ourNewOrder;
order[sortedOrder[sortedOrders[ourIndex - 1]]] = ourOrder;
if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst))
{
QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order"));
}
else
{
m_inst->reloadVersion(this);
ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow - 1), QItemSelectionModel::SelectCurrent);
}
// FIXME: IMPLEMENT LOGIC IN MODEL. SEE LEGACY DIALOG FOR EXAMPLE(S).
}
void OneSixModEditDialog::on_moveLibraryDownBtn_clicked()
{
QMap<QString, int> order = getExistingOrder();
if (order.size() < 2 || ui->libraryTreeView->selectionModel()->selectedIndexes().isEmpty())
{
return;
}
const int ourRow = ui->libraryTreeView->selectionModel()->selectedIndexes().first().row();
const QString ourId = m_version->versionFileId(ourRow);
const int ourOrder = order[ourId];
if (ourId.isNull() || ourId.startsWith("org.multimc."))
{
return;
}
QMap<int, QString> sortedOrder = invert(order);
QList<int> sortedOrders = sortedOrder.keys();
const int ourIndex = sortedOrders.indexOf(ourOrder);
if ((ourIndex + 1) >= sortedOrders.size())
{
return;
}
const int ourNewOrder = sortedOrders.at(ourIndex + 1);
order[ourId] = ourNewOrder;
order[sortedOrder[sortedOrders[ourIndex + 1]]] = ourOrder;
if (!OneSixVersionBuilder::writeOverrideOrders(order, m_inst))
{
QMessageBox::critical(this, tr("Error"), tr("Couldn't save the new order"));
}
else
{
m_inst->reloadVersion(this);
ui->libraryTreeView->selectionModel()->select(m_version->index(ourRow + 1), QItemSelectionModel::SelectCurrent);
}
// FIXME: IMPLEMENT LOGIC IN MODEL. SEE LEGACY DIALOG FOR EXAMPLE(S).
}
void OneSixModEditDialog::on_forgeBtn_clicked()
{
// FIXME: use actual model, not reloading. Move logic to model.
// FIXME: model::isCustom();
if (QDir(m_inst->instanceRoot()).exists("custom.json"))
{
if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes)
if (QMessageBox::question(this, tr("Revert?"),
tr("This action will remove your custom.json. Continue?")) !=
QMessageBox::Yes)
{
return;
}
// FIXME: model::revertToBase();
QDir(m_inst->instanceRoot()).remove("custom.json");
m_inst->reloadVersion(this);
reloadInstanceVersion();
}
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
vselect.setFilter(1, m_inst->currentVersionId());
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") +
m_inst->currentVersionId());
m_inst->currentVersionId());
if (vselect.exec() && vselect.selectedVersion())
{
ForgeVersionPtr forgeVersion =
@ -277,28 +237,32 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
}
}
}
m_inst->reloadVersion(this);
reloadInstanceVersion();
}
void OneSixModEditDialog::on_liteloaderBtn_clicked()
{
// FIXME: model...
if (QDir(m_inst->instanceRoot()).exists("custom.json"))
{
if (QMessageBox::question(this, tr("Revert?"), tr("This action will remove your custom.json. Continue?")) != QMessageBox::Yes)
if (QMessageBox::question(this, tr("Revert?"),
tr("This action will remove your custom.json. Continue?")) !=
QMessageBox::Yes)
{
return;
}
QDir(m_inst->instanceRoot()).remove("custom.json");
m_inst->reloadVersion(this);
reloadInstanceVersion();
}
VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"), this);
VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"),
this);
vselect.setFilter(1, m_inst->currentVersionId());
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") +
m_inst->currentVersionId());
m_inst->currentVersionId());
if (vselect.exec() && vselect.selectedVersion())
{
LiteLoaderVersionPtr liteloaderVersion =
std::dynamic_pointer_cast<LiteLoaderVersion>(vselect.selectedVersion());
std::dynamic_pointer_cast<LiteLoaderVersion>(vselect.selectedVersion());
if (!liteloaderVersion)
return;
LiteLoaderInstaller liteloader(liteloaderVersion);
@ -310,7 +274,7 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked()
}
else
{
m_inst->reloadVersion(this);
reloadInstanceVersion();
}
}
}
@ -347,35 +311,6 @@ bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent)
return QDialog::eventFilter(ui->resPackTreeView, keyEvent);
}
QMap<QString, int> OneSixModEditDialog::getExistingOrder() const
{
QMap<QString, int> order;
// default
{
for (OneSixVersion::VersionFile file : m_version->versionFiles)
{
if (file.id.startsWith("org.multimc."))
{
continue;
}
order.insert(file.id, file.order);
}
}
// overriden
{
QMap<QString, int> overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_inst);
for (auto id : order.keys())
{
if (overridenOrder.contains(id))
{
order[id] = overridenOrder[id];
}
}
}
return order;
}
bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev)
{
if (ev->type() != QEvent::KeyPress)
@ -461,7 +396,8 @@ void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previou
ui->frame->updateWithMod(m);
}
void OneSixModEditDialog::versionCurrent(const QModelIndex &current, const QModelIndex &previous)
void OneSixModEditDialog::versionCurrent(const QModelIndex &current,
const QModelIndex &previous)
{
if (!current.isValid())
{

View File

@ -57,17 +57,17 @@ protected:
bool eventFilter(QObject *obj, QEvent *ev);
bool loaderListFilter(QKeyEvent *ev);
bool resourcePackListFilter(QKeyEvent *ev);
/// FIXME: this shouldn't be necessary!
bool reloadInstanceVersion();
private:
Ui::OneSixModEditDialog *ui;
std::shared_ptr<OneSixVersion> m_version;
std::shared_ptr<VersionFinal> m_version;
std::shared_ptr<ModList> m_mods;
std::shared_ptr<ModList> m_resourcepacks;
EnabledItemFilter *main_model;
OneSixInstance *m_inst;
QMap<QString, int> getExistingOrder() const;
public
slots:
void loaderCurrent(QModelIndex current, QModelIndex previous);

View File

@ -48,24 +48,6 @@
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Main Class:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mainClassEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
@ -108,13 +90,6 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetLibraryOrderBtn">
<property name="text">
<string>Reset order</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
@ -124,6 +99,12 @@
</item>
<item>
<widget class="QPushButton" name="moveLibraryUpBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>This isn't implemented yet.</string>
</property>
<property name="text">
<string>Move up</string>
</property>
@ -131,11 +112,30 @@
</item>
<item>
<widget class="QPushButton" name="moveLibraryDownBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>This isn't implemented yet.</string>
</property>
<property name="text">
<string>Move down</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetLibraryOrderBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>This isn't implemented yet.</string>
</property>
<property name="text">
<string>Reset order</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">

View File

@ -17,7 +17,7 @@
#include <QFile>
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "OneSixInstance.h"

View File

@ -168,6 +168,11 @@ bool BaseInstance::canLaunch() const
return !flags().contains(VersionBrokenFlag);
}
bool BaseInstance::reload()
{
return settings().reload();
}
QString BaseInstance::baseJar() const
{
I_D(BaseInstance);

View File

@ -190,6 +190,8 @@ public:
bool canLaunch() const;
virtual bool reload();
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change

View File

@ -14,7 +14,7 @@
*/
#include "ForgeInstaller.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "net/HttpMetaCache.h"
#include <quazip.h>
@ -33,7 +33,7 @@
ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
{
std::shared_ptr<OneSixVersion> newVersion;
std::shared_ptr<VersionFinal> newVersion;
m_universal_url = universal_url;
QuaZip zip(filename);
@ -66,7 +66,7 @@ ForgeInstaller::ForgeInstaller(QString filename, QString universal_url)
// read the forge version info
{
newVersion = OneSixVersion::fromJson(versionInfoVal.toObject());
newVersion = VersionFinal::fromJson(versionInfoVal.toObject());
if (!newVersion)
return;
}

View File

@ -20,7 +20,7 @@
#include <QString>
#include <memory>
class OneSixVersion;
class VersionFinal;
class ForgeInstaller : public BaseInstaller
{
@ -33,7 +33,7 @@ public:
private:
// the version, read from the installer
std::shared_ptr<OneSixVersion> m_forge_version;
std::shared_ptr<VersionFinal> m_forge_version;
QString internalPath;
QString finalPath;
QString realVersionId;

View File

@ -20,7 +20,7 @@
#include "logger/QsLog.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "OneSixInstance.h"
@ -69,7 +69,7 @@ bool LiteLoaderInstaller::add(OneSixInstance *to)
obj.insert("+libraries", libraries);
obj.insert("name", QString("LiteLoader"));
obj.insert("fileId", id());
obj.insert("version", to->intendedVersionId());
obj.insert("version", m_version->version);
obj.insert("mcVersion", to->intendedVersionId());
QFile file(filename(to->instanceRoot()));

61
logic/MMCJson.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "MMCJson.h"
#include <QString>
#include <math.h>
bool MMCJson::ensureBoolean(const QJsonValue val, const QString what)
{
if (!val.isBool())
throw JSONValidationError(what + " is not boolean");
return val.isBool();
}
QJsonValue MMCJson::ensureExists(QJsonValue val, const QString what)
{
if(val.isNull())
throw JSONValidationError(what + " does not exist");
return val;
}
QJsonArray MMCJson::ensureArray(const QJsonValue val, const QString what)
{
if (!val.isArray())
throw JSONValidationError(what + " is not an array");
return val.toArray();
}
double MMCJson::ensureDouble(const QJsonValue val, const QString what)
{
if (!val.isDouble())
throw JSONValidationError(what + " is not a number");
double ret = val.toDouble();
}
int MMCJson::ensureInteger(const QJsonValue val, const QString what)
{
double ret = ensureDouble(val, what);
if (fmod(ret, 1) != 0)
throw JSONValidationError(what + " is not an integer");
return ret;
}
QJsonObject MMCJson::ensureObject(const QJsonValue val, const QString what)
{
if (!val.isObject())
throw JSONValidationError(what + " is not an object");
return val.toObject();
}
QJsonObject MMCJson::ensureObject(const QJsonDocument val, const QString what)
{
if (!val.isObject())
throw JSONValidationError(what + " is not an object");
return val.object();
}
QString MMCJson::ensureString(const QJsonValue val, const QString what)
{
if (!val.isString())
throw JSONValidationError(what + " is not a string");
return val.toString();
}

46
logic/MMCJson.h Normal file
View File

@ -0,0 +1,46 @@
/**
* Some de-bullshitting for Qt JSON failures.
*
* Simple exception-throwing
*/
#pragma once
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include "MMCError.h"
class JSONValidationError : public MMCError
{
public:
JSONValidationError(QString cause) : MMCError(cause) {};
virtual ~JSONValidationError() noexcept {}
};
namespace MMCJson
{
/// make sure the value exists. throw otherwise.
QJsonValue ensureExists(QJsonValue val, const QString what = "value");
/// make sure the value is converted into an object. throw otherwise.
QJsonObject ensureObject(const QJsonValue val, const QString what = "value");
/// make sure the document is converted into an object. throw otherwise.
QJsonObject ensureObject(const QJsonDocument val, const QString what = "value");
/// make sure the value is converted into an array. throw otherwise.
QJsonArray ensureArray(const QJsonValue val, QString what = "value");
/// make sure the value is converted into a string. throw otherwise.
QString ensureString(const QJsonValue val, QString what = "value");
/// make sure the value is converted into a boolean. throw otherwise.
bool ensureBoolean(const QJsonValue val, QString what = "value");
/// make sure the value is converted into an integer. throw otherwise.
int ensureInteger(const QJsonValue val, QString what = "value");
/// make sure the value is converted into a double precision floating number. throw otherwise.
double ensureDouble(const QJsonValue val, QString what = "value");
}

View File

@ -49,9 +49,11 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
#endif
// export some infos
env.insert("INST_NAME", inst->name());
env.insert("INST_ID", inst->id());
env.insert("INST_DIR", QDir(inst->instanceRoot()).absolutePath());
auto variables = getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
env.insert(it.key(), it.value());
}
this->setProcessEnvironment(env);
m_prepostlaunchprocess.setProcessEnvironment(env);
@ -63,10 +65,10 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
// Log prepost launch command output (can be disabled.)
if (m_instance->settings().get("LogPrePostOutput").toBool())
{
connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError,
this, &MinecraftProcess::on_prepost_stdErr);
connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput,
this, &MinecraftProcess::on_prepost_stdOut);
connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError, this,
&MinecraftProcess::on_prepost_stdErr);
connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this,
&MinecraftProcess::on_prepost_stdOut);
}
}
@ -79,10 +81,10 @@ void MinecraftProcess::setWorkdir(QString path)
QString MinecraftProcess::censorPrivateInfo(QString in)
{
if(!m_session)
if (!m_session)
return in;
if(m_session->session != "-")
if (m_session->session != "-")
in.replace(m_session->session, "<SESSION ID>");
in.replace(m_session->access_token, "<ACCESS TOKEN>");
in.replace(m_session->client_token, "<CLIENT TOKEN>");
@ -113,7 +115,7 @@ MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLeve
level = MessageLevel::Fatal;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
if(line.contains("overwriting existing"))
if (line.contains("overwriting existing"))
level = MessageLevel::Fatal;
return level;
}
@ -139,17 +141,15 @@ MessageLevel::Enum MinecraftProcess::getLevel(const QString &levelName)
return MessageLevel::Message;
}
void MinecraftProcess::logOutput(const QStringList &lines,
MessageLevel::Enum defaultLevel,
void MinecraftProcess::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel,
bool guessLevel, bool censor)
{
for (int i = 0; i < lines.size(); ++i)
logOutput(lines[i], defaultLevel, guessLevel, censor);
}
void MinecraftProcess::logOutput(QString line,
MessageLevel::Enum defaultLevel,
bool guessLevel, bool censor)
void MinecraftProcess::logOutput(QString line, MessageLevel::Enum defaultLevel, bool guessLevel,
bool censor)
{
MessageLevel::Enum level = defaultLevel;
@ -251,33 +251,7 @@ void MinecraftProcess::finish(int code, ExitStatus status)
m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code));
// run post-exit
QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString();
if (!postlaunch_cmd.isEmpty())
{
emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd));
m_prepostlaunchprocess.start(postlaunch_cmd);
m_prepostlaunchprocess.waitForFinished();
// Flush console window
if (!m_err_leftover.isEmpty())
{
logOutput(m_err_leftover, MessageLevel::PrePost);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
logOutput(m_out_leftover, MessageLevel::PrePost);
m_out_leftover.clear();
}
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
emit log(tr("Post-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()),
MessageLevel::Error);
emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
m_prepostlaunchprocess.exitStatus());
}
else
emit log(tr("Post-Launch command ran successfully.\n\n"));
}
postLaunch();
m_instance->cleanupAfterRun();
emit ended(m_instance, code, status);
}
@ -288,14 +262,12 @@ void MinecraftProcess::killMinecraft()
kill();
}
void MinecraftProcess::arm()
bool MinecraftProcess::preLaunch()
{
emit log("MultiMC version: " + MMC->version().toString() + "\n\n");
emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n");
QString prelaunch_cmd = m_instance->settings().get("PreLaunchCommand").toString();
if (!prelaunch_cmd.isEmpty())
{
prelaunch_cmd = substituteVariables(prelaunch_cmd);
// Launch
emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd));
m_prepostlaunchprocess.start(prelaunch_cmd);
@ -315,45 +287,128 @@ void MinecraftProcess::arm()
// Process return values
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
emit log(tr("Pre-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()),
emit log(tr("Pre-Launch command failed with code %1.\n\n")
.arg(m_prepostlaunchprocess.exitCode()),
MessageLevel::Fatal);
m_instance->cleanupAfterRun();
emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
m_prepostlaunchprocess.exitStatus());
return;
return false;
}
else
emit log(tr("Pre-Launch command ran successfully.\n\n"));
return m_instance->reload();
}
return true;
}
bool MinecraftProcess::postLaunch()
{
QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString();
if (!postlaunch_cmd.isEmpty())
{
postlaunch_cmd = substituteVariables(postlaunch_cmd);
emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd));
m_prepostlaunchprocess.start(postlaunch_cmd);
m_prepostlaunchprocess.waitForFinished();
// Flush console window
if (!m_err_leftover.isEmpty())
{
logOutput(m_err_leftover, MessageLevel::PrePost);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
logOutput(m_out_leftover, MessageLevel::PrePost);
m_out_leftover.clear();
}
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
emit log(tr("Post-Launch command failed with code %1.\n\n")
.arg(m_prepostlaunchprocess.exitCode()),
MessageLevel::Error);
emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
m_prepostlaunchprocess.exitStatus());
}
else
emit log(tr("Post-Launch command ran successfully.\n\n"));
return m_instance->reload();
}
return true;
}
QMap<QString, QString> MinecraftProcess::getVariables() const
{
QMap<QString, QString> out;
out.insert("INST_NAME", m_instance->name());
out.insert("INST_ID", m_instance->id());
out.insert("INST_DIR", QDir(m_instance->instanceRoot()).absolutePath());
out.insert("INST_MC_DIR", QDir(m_instance->minecraftRoot()).absolutePath());
out.insert("INST_JAVA", m_instance->settings().get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
return out;
}
QString MinecraftProcess::substituteVariables(const QString &cmd) const
{
QString out = cmd;
auto variables = 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;
}
QStringList MinecraftProcess::javaArguments() const
{
QStringList args;
// custom args go first. we want to override them if we have our own here.
args.append(m_instance->extraArguments());
// OSX dock icon and name
#ifdef OSX
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(m_instance->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(m_instance->settings().get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(m_instance->settings().get("MaxMemAlloc").toInt());
args << QString("-XX:PermSize=%1m").arg(m_instance->settings().get("PermGen").toInt());
args << "-Duser.language=en";
if (!m_nativeFolder.isEmpty())
args << QString("-Djava.library.path=%1").arg(m_nativeFolder);
args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar");
return args;
}
void MinecraftProcess::arm()
{
emit log("MultiMC version: " + MMC->version().toString() + "\n\n");
emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n");
if (!preLaunch())
{
return;
}
m_instance->setLastLaunch();
auto &settings = m_instance->settings();
//////////// java arguments ////////////
QStringList args;
{
// custom args go first. we want to override them if we have our own here.
args.append(m_instance->extraArguments());
// OSX dock icon and name
#ifdef OSX
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(m_instance->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());
args << QString("-XX:PermSize=%1m").arg(settings.get("PermGen").toInt());
if(!m_nativeFolder.isEmpty())
args << QString("-Djava.library.path=%1").arg(m_nativeFolder);
args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar");
}
QStringList args = javaArguments();
QString JavaPath = m_instance->settings().get("JavaPath").toString();
emit log("Java path is:\n" + JavaPath + "\n\n");

View File

@ -131,6 +131,13 @@ protected:
QString launchScript;
QString m_nativeFolder;
bool preLaunch();
bool postLaunch();
QMap<QString, QString> getVariables() const;
QString substituteVariables(const QString &cmd) const;
QStringList javaArguments() const;
protected
slots:
void finish(int, QProcess::ExitStatus status);

View File

@ -1,6 +1,6 @@
#include "OneSixFTBInstance.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "tasks/SequentialTask.h"
#include "ForgeInstaller.h"
@ -10,76 +10,6 @@
#include "MultiMC.h"
#include "pathutils.h"
class OneSixFTBInstanceForge : public Task
{
Q_OBJECT
public:
explicit OneSixFTBInstanceForge(const QString &version, OneSixFTBInstance *inst, QObject *parent = 0) :
Task(parent), instance(inst), version("Forge " + version)
{
}
void executeTask()
{
for (int i = 0; i < MMC->forgelist()->count(); ++i)
{
if (MMC->forgelist()->at(i)->name() == version)
{
forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(MMC->forgelist()->at(i));
break;
}
}
if (!forgeVersion)
{
emitFailed(QString("Couldn't find forge version ") + version );
return;
}
entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename);
if (entry->stale)
{
setStatus(tr("Downloading Forge..."));
fjob = new NetJob("Forge download");
fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry));
connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);});
connect(fjob, &NetJob::succeeded, this, &OneSixFTBInstanceForge::installForge);
connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); });
fjob->start();
}
else
{
installForge();
}
}
private
slots:
void installForge()
{
setStatus(tr("Installing Forge..."));
QString forgePath = entry->getFullPath();
ForgeInstaller forge(forgePath, forgeVersion->universal_url);
if (!instance->reloadVersion())
{
emitFailed(tr("Couldn't load the version config"));
return;
}
auto version = instance->getFullVersion();
if (!forge.add(instance))
{
emitFailed(tr("Couldn't install Forge"));
return;
}
emitSucceeded();
}
private:
OneSixFTBInstance *instance;
QString version;
ForgeVersionPtr forgeVersion;
MetaEntryPtr entry;
NetJob *fjob;
};
OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
OneSixInstance(rootDir, settings, parent)
{
@ -87,7 +17,14 @@ OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *set
void OneSixFTBInstance::init()
{
reloadVersion();
try
{
reloadVersion();
}
catch(MMCError & e)
{
// QLOG_ERROR() << "Caught exception on instance init: " << e.cause();
}
}
void OneSixFTBInstance::copy(const QDir &newDir)

View File

@ -19,7 +19,7 @@
#include "OneSixInstance_p.h"
#include "OneSixUpdate.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "pathutils.h"
#include "logger/QsLog.h"
#include "assets/AssetsUtils.h"
@ -27,6 +27,7 @@
#include "icons/IconList.h"
#include "MinecraftProcess.h"
#include "gui/dialogs/OneSixModEditDialog.h"
#include <MMCError.h>
OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent)
: BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent)
@ -34,15 +35,23 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings,
I_D(OneSixInstance);
d->m_settings->registerSetting("IntendedVersion", "");
d->m_settings->registerSetting("ShouldUpdate", false);
d->version.reset(new OneSixVersion(this, this));
d->vanillaVersion.reset(new OneSixVersion(this, this));
d->version.reset(new VersionFinal(this, this));
d->vanillaVersion.reset(new VersionFinal(this, this));
}
void OneSixInstance::init()
{
// FIXME: why is this decided here? what does this even mean?
if (QDir(instanceRoot()).exists("version.json"))
{
reloadVersion();
try
{
reloadVersion();
}
catch(MMCError & e)
{
// QLOG_ERROR() << "Caught exception on instance init: " << e.cause();
}
}
else
{
@ -79,7 +88,7 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with)
return result;
}
QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version)
{
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes"));
@ -316,25 +325,26 @@ QString OneSixInstance::currentVersionId() const
return intendedVersionId();
}
bool OneSixInstance::reloadVersion(QWidget *widgetParent)
void OneSixInstance::reloadVersion()
{
I_D(OneSixInstance);
bool ret = d->version->reload(widgetParent, false, externalPatches());
if (ret)
{
ret = d->vanillaVersion->reload(widgetParent, true, externalPatches());
}
if (ret)
try
{
d->version->reload(false, externalPatches());
d->vanillaVersion->reload(true, externalPatches());
d->m_flags.remove(VersionBrokenFlag);
emit versionReloaded();
}
else
catch(MMCError & error)
{
d->version->clear();
d->vanillaVersion->clear();
d->m_flags.insert(VersionBrokenFlag);
//TODO: rethrow to show some error message(s)?
emit versionReloaded();
throw;
}
return ret;
}
void OneSixInstance::clearVersion()
@ -345,13 +355,13 @@ void OneSixInstance::clearVersion()
emit versionReloaded();
}
std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() const
std::shared_ptr<VersionFinal> OneSixInstance::getFullVersion() const
{
I_D(const OneSixInstance);
return d->version;
}
std::shared_ptr<OneSixVersion> OneSixInstance::getVanillaVersion() const
std::shared_ptr<VersionFinal> OneSixInstance::getVanillaVersion() const
{
I_D(const OneSixInstance);
return d->vanillaVersion;
@ -413,6 +423,23 @@ bool OneSixInstance::providesVersionFile() const
return false;
}
bool OneSixInstance::reload()
{
if(BaseInstance::reload())
{
try
{
reloadVersion();
return true;
}
catch (...)
{
return false;
}
}
return false;
}
QString OneSixInstance::loaderModsDir() const
{
return PathCombine(minecraftRoot(), "mods");

View File

@ -17,7 +17,7 @@
#include "BaseInstance.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "ModList.h"
class OneSixInstance : public BaseInstance
@ -53,14 +53,18 @@ public:
virtual QDialog *createModEditDialog(QWidget *parent) override;
/// reload the full version json files. return true on success!
bool reloadVersion(QWidget *widgetParent = 0);
/**
* reload the full version json files. return true on success!
*
* throws various exceptions :3
*/
void reloadVersion();
/// clears all version information in preparation for an update
void clearVersion();
/// get the current full version info
std::shared_ptr<OneSixVersion> getFullVersion() const;
std::shared_ptr<VersionFinal> getFullVersion() const;
/// gets the current version info, but only for version.json
std::shared_ptr<OneSixVersion> getVanillaVersion() const;
std::shared_ptr<VersionFinal> getVanillaVersion() const;
/// is the current version original, or custom?
virtual bool versionIsCustom() override;
@ -75,10 +79,12 @@ public:
virtual QStringList externalPatches() const;
virtual bool providesVersionFile() const;
bool reload() override;
signals:
void versionReloaded();
private:
QStringList processMinecraftArgs(AuthSessionPtr account);
QDir reconstructAssets(std::shared_ptr<OneSixVersion> version);
QDir reconstructAssets(std::shared_ptr<VersionFinal> version);
};

View File

@ -16,13 +16,13 @@
#pragma once
#include "BaseInstance_p.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "ModList.h"
struct OneSixInstancePrivate : public BaseInstancePrivate
{
std::shared_ptr<OneSixVersion> version;
std::shared_ptr<OneSixVersion> vanillaVersion;
std::shared_ptr<VersionFinal> version;
std::shared_ptr<VersionFinal> vanillaVersion;
std::shared_ptr<ModList> loader_mod_list;
std::shared_ptr<ModList> resource_pack_list;
};

View File

@ -26,6 +26,9 @@
class Rule;
class OneSixLibrary;
typedef std::shared_ptr<OneSixLibrary> OneSixLibraryPtr;
class OneSixLibrary
{
private:

View File

@ -25,7 +25,7 @@
#include "BaseInstance.h"
#include "lists/MinecraftVersionList.h"
#include "OneSixVersion.h"
#include "VersionFinal.h"
#include "OneSixLibrary.h"
#include "OneSixInstance.h"
#include "net/ForgeMirrors.h"
@ -48,7 +48,7 @@ void OneSixUpdate::executeTask()
QDir mcDir(m_inst->minecraftRoot());
if (!mcDir.exists() && !mcDir.mkpath("."))
{
emitFailed("Failed to create bin folder.");
emitFailed(tr("Failed to create folder for minecraft binaries."));
return;
}
@ -60,7 +60,7 @@ void OneSixUpdate::executeTask()
if (targetVersion == nullptr)
{
// don't do anything if it was invalid
emitFailed("The specified Minecraft version is invalid. Choose a different one.");
emitFailed(tr("The specified Minecraft version is invalid. Choose a different one."));
return;
}
versionFileStart();
@ -108,20 +108,19 @@ void OneSixUpdate::versionFileFinished()
QSaveFile vfile1(version1);
if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly))
{
emitFailed("Can't open " + version1 + " for writing.");
emitFailed(tr("Can't open %1 for writing.").arg(version1));
return;
}
auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data;
qint64 actual = 0;
if ((actual = vfile1.write(data)) != data.size())
{
emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " +
data.size() + '.');
emitFailed(tr("Failed to write into %1. Written %2 out of %3.").arg(version1).arg(actual).arg(data.size()));
return;
}
if (!vfile1.commit())
{
emitFailed("Can't commit changes to " + version1);
emitFailed(tr("Can't commit changes to %1").arg(version1));
return;
}
}
@ -136,21 +135,20 @@ void OneSixUpdate::versionFileFinished()
{
finfo.remove();
}
inst->reloadVersion();
// NOTE: Version is reloaded in jarlibStart
jarlibStart();
}
void OneSixUpdate::versionFileFailed()
{
emitFailed("Failed to download the version description. Try again.");
emitFailed(tr("Failed to download the version description. Try again."));
}
void OneSixUpdate::assetIndexStart()
{
setStatus(tr("Updating assets index..."));
OneSixInstance *inst = (OneSixInstance *)m_inst;
std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
std::shared_ptr<VersionFinal> version = inst->getFullVersion();
QString assetName = version->assets;
QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json";
QString localPath = assetName + ".json";
@ -174,13 +172,13 @@ void OneSixUpdate::assetIndexFinished()
AssetsIndex index;
OneSixInstance *inst = (OneSixInstance *)m_inst;
std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
std::shared_ptr<VersionFinal> version = inst->getFullVersion();
QString assetName = version->assets;
QString asset_fname = "assets/indexes/" + assetName + ".json";
if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index))
{
emitFailed("Failed to read the assets index!");
emitFailed(tr("Failed to read the assets index!"));
}
QList<Md5EtagDownloadPtr> dls;
@ -216,7 +214,7 @@ void OneSixUpdate::assetIndexFinished()
void OneSixUpdate::assetIndexFailed()
{
emitFailed("Failed to download the assets index!");
emitFailed(tr("Failed to download the assets index!"));
}
void OneSixUpdate::assetsFinished()
@ -226,7 +224,7 @@ void OneSixUpdate::assetsFinished()
void OneSixUpdate::assetsFailed()
{
emitFailed("Failed to download assets!");
emitFailed(tr("Failed to download assets!"));
}
void OneSixUpdate::jarlibStart()
@ -234,16 +232,23 @@ void OneSixUpdate::jarlibStart()
setStatus(tr("Getting the library files from Mojang..."));
QLOG_INFO() << m_inst->name() << ": downloading libraries";
OneSixInstance *inst = (OneSixInstance *)m_inst;
bool successful = inst->reloadVersion();
if (!successful)
try
{
emitFailed("Failed to load the version description file. It might be "
"corrupted, missing or simply too new.");
inst->reloadVersion();
}
catch(MMCError & e)
{
emitFailed(e.cause());
return;
}
catch(...)
{
emitFailed(tr("Failed to load the version description file for reasons unknown."));
return;
}
// Build a list of URLs that will need to be downloaded.
std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
std::shared_ptr<VersionFinal> version = inst->getFullVersion();
// minecraft.jar for this version
{
QString version_id = version->id;
@ -326,6 +331,5 @@ void OneSixUpdate::jarlibFailed()
{
QStringList failed = jarlibDownloadJob->getFailedFiles();
QString failed_all = failed.join("\n");
emitFailed("Failed to download the following files:\n" + failed_all +
"\n\nPlease try again.");
emitFailed(tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all));
}

View File

@ -1,221 +0,0 @@
/* Copyright 2013 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 "OneSixVersion.h"
#include <QDebug>
#include <QFile>
#include "OneSixVersionBuilder.h"
OneSixVersion::OneSixVersion(OneSixInstance *instance, QObject *parent)
: QAbstractListModel(parent), m_instance(instance)
{
clear();
}
bool OneSixVersion::reload(QWidget *widgetParent, const bool onlyVanilla, const QStringList &external)
{
beginResetModel();
bool ret = OneSixVersionBuilder::build(this, m_instance, widgetParent, onlyVanilla, external);
endResetModel();
return ret;
}
void OneSixVersion::clear()
{
beginResetModel();
id.clear();
time.clear();
releaseTime.clear();
type.clear();
assets.clear();
processArguments.clear();
minecraftArguments.clear();
minimumLauncherVersion = 0xDEADBEAF;
mainClass.clear();
libraries.clear();
tweakers.clear();
versionFiles.clear();
endResetModel();
}
void OneSixVersion::dump() const
{
qDebug().nospace() << "OneSixVersion("
<< "\n\tid=" << id
<< "\n\ttime=" << time
<< "\n\treleaseTime=" << releaseTime
<< "\n\ttype=" << type
<< "\n\tassets=" << assets
<< "\n\tprocessArguments=" << processArguments
<< "\n\tminecraftArguments=" << minecraftArguments
<< "\n\tminimumLauncherVersion=" << minimumLauncherVersion
<< "\n\tmainClass=" << mainClass
<< "\n\tlibraries=";
for (auto lib : libraries)
{
qDebug().nospace() << "\n\t\t" << lib.get();
}
qDebug().nospace() << "\n)";
}
bool OneSixVersion::canRemove(const int index) const
{
if (index < versionFiles.size())
{
return versionFiles.at(index).id != "org.multimc.version.json";
}
return false;
}
QString OneSixVersion::versionFileId(const int index) const
{
if (index < 0 || index >= versionFiles.size())
{
return QString();
}
return versionFiles.at(index).id;
}
bool OneSixVersion::remove(const int index)
{
if (canRemove(index))
{
return QFile::remove(versionFiles.at(index).filename);
}
return false;
}
QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNormalLibs()
{
QList<std::shared_ptr<OneSixLibrary> > output;
for (auto lib : libraries)
{
if (lib->isActive() && !lib->isNative())
{
output.append(lib);
}
}
return output;
}
QList<std::shared_ptr<OneSixLibrary> > OneSixVersion::getActiveNativeLibs()
{
QList<std::shared_ptr<OneSixLibrary> > output;
for (auto lib : libraries)
{
if (lib->isActive() && lib->isNative())
{
output.append(lib);
}
}
return output;
}
std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(const QJsonObject &obj)
{
std::shared_ptr<OneSixVersion> version(new OneSixVersion(0));
if (OneSixVersionBuilder::read(version.get(), obj))
{
return version;
}
return 0;
}
QVariant OneSixVersion::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
if (row < 0 || row >= versionFiles.size())
return QVariant();
if (role == Qt::DisplayRole)
{
switch (column)
{
case 0:
return versionFiles.at(row).name;
case 1:
return versionFiles.at(row).version;
default:
return QVariant();
}
}
return QVariant();
}
QVariant OneSixVersion::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 OneSixVersion::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
int OneSixVersion::rowCount(const QModelIndex &parent) const
{
return versionFiles.size();
}
int OneSixVersion::columnCount(const QModelIndex &parent) const
{
return 2;
}
QDebug operator<<(QDebug &dbg, const OneSixVersion *version)
{
version->dump();
return dbg.maybeSpace();
}
QDebug operator<<(QDebug &dbg, const OneSixLibrary *library)
{
dbg.nospace() << "OneSixLibrary("
<< "\n\t\t\trawName=" << library->rawName()
<< "\n\t\t\tname=" << library->name()
<< "\n\t\t\tversion=" << library->version()
<< "\n\t\t\ttype=" << library->type()
<< "\n\t\t\tisActive=" << library->isActive()
<< "\n\t\t\tisNative=" << library->isNative()
<< "\n\t\t\tdownloadUrl=" << library->downloadUrl()
<< "\n\t\t\tstoragePath=" << library->storagePath()
<< "\n\t\t\tabsolutePath=" << library->absoluteUrl()
<< "\n\t\t\thint=" << library->hint();
dbg.nospace() << "\n\t\t)";
return dbg.maybeSpace();
}

File diff suppressed because it is too large Load Diff

View File

@ -17,39 +17,32 @@
#include <QString>
#include <QMap>
#include "VersionFile.h"
class OneSixVersion;
class VersionFinal;
class OneSixInstance;
class QWidget;
class QJsonObject;
class QFileInfo;
class VersionFile;
class OneSixVersionBuilder
{
OneSixVersionBuilder();
public:
static bool build(OneSixVersion *version, OneSixInstance *instance, QWidget *widgetParent, const bool onlyVanilla, const QStringList &external);
static bool read(OneSixVersion *version, const QJsonObject &obj);
static void build(VersionFinal *version, OneSixInstance *instance, const bool onlyVanilla,
const QStringList &external);
static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj);
static QMap<QString, int> readOverrideOrders(OneSixInstance *instance);
static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance);
enum ParseFlag
{
NoFlags = 0x0,
IsFTBPackJson = 0x1
};
Q_DECLARE_FLAGS(ParseFlags, ParseFlag)
private:
OneSixVersion *m_version;
VersionFinal *m_version;
OneSixInstance *m_instance;
QWidget *m_widgetParent;
bool build(const bool onlyVanilla, const QStringList &external);
bool read(const QJsonObject &obj);
void buildInternal(const bool onlyVanilla, const QStringList &external);
void readJsonAndApply(const QJsonObject &obj);
void finalizeVersion();
bool read(const QFileInfo &fileInfo, const bool requireOrder, VersionFile *out, const ParseFlags flags = NoFlags);
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder,
bool isFTB = false);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(OneSixVersionBuilder::ParseFlags)

535
logic/VersionFile.cpp Normal file
View File

@ -0,0 +1,535 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <modutils.h>
#include "logger/QsLog.h"
#include "logic/VersionFile.h"
#include "logic/OneSixLibrary.h"
#include "logic/VersionFinal.h"
#include "MMCJson.h"
using namespace MMCJson;
#define CURRENT_MINIMUM_LAUNCHER_VERSION 14
RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename)
{
RawLibraryPtr out(new RawLibrary());
if (!libObj.contains("name"))
{
throw JSONValidationError(filename +
"contains a library that doesn't have a 'name' field");
}
out->name = libObj.value("name").toString();
auto readString = [libObj, filename](const QString & key, QString & variable)
{
if (libObj.contains(key))
{
QJsonValue val = libObj.value(key);
if (!val.isString())
{
QLOG_WARN() << key << "is not a string in" << filename << "(skipping)";
}
else
{
variable = val.toString();
}
}
};
readString("url", out->url);
readString("MMC-hint", out->hint);
readString("MMC-absulute_url", out->absoluteUrl);
readString("MMC-absoluteUrl", out->absoluteUrl);
if (libObj.contains("extract"))
{
out->applyExcludes = true;
auto extractObj = ensureObject(libObj.value("extract"));
for (auto excludeVal : ensureArray(extractObj.value("exclude")))
{
out->excludes.append(ensureString(excludeVal));
}
}
if (libObj.contains("natives"))
{
out->applyNatives = true;
QJsonObject nativesObj = ensureObject(libObj.value("natives"));
for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it)
{
if (!it.value().isString())
{
QLOG_WARN() << filename << "contains an invalid native (skipping)";
}
OpSys opSys = OpSys_fromString(it.key());
if (opSys != Os_Other)
{
out->natives.append(qMakePair(opSys, it.value().toString()));
}
}
}
if (libObj.contains("rules"))
{
out->applyRules = true;
out->rules = rulesFromJsonV4(libObj);
}
return out;
}
VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename,
const bool requireOrder, const bool isFTB)
{
VersionFilePtr out(new VersionFile());
if (doc.isEmpty() || doc.isNull())
{
throw JSONValidationError(filename + " is empty or null");
}
if (!doc.isObject())
{
throw JSONValidationError("The root of " + filename + " is not an object");
}
QJsonObject root = doc.object();
if (requireOrder)
{
if (root.contains("order"))
{
out->order = ensureInteger(root.value("order"));
}
else
{
// FIXME: evaluate if we don't want to throw exceptions here instead
QLOG_ERROR() << filename << "doesn't contain an order field";
}
}
out->name = root.value("name").toString();
out->fileId = root.value("fileId").toString();
out->version = root.value("version").toString();
out->mcVersion = root.value("mcVersion").toString();
out->filename = filename;
auto readString = [root, filename](const QString & key, QString & variable)
{
if (root.contains(key))
{
variable = ensureString(root.value(key));
}
};
// FIXME: This should be ignored when applying.
if (!isFTB)
{
readString("id", out->id);
}
readString("mainClass", out->mainClass);
readString("processArguments", out->processArguments);
readString("minecraftArguments", out->overwriteMinecraftArguments);
readString("+minecraftArguments", out->addMinecraftArguments);
readString("-minecraftArguments", out->removeMinecraftArguments);
readString("type", out->type);
readString("releaseTime", out->releaseTime);
readString("time", out->time);
readString("assets", out->assets);
if (root.contains("minimumLauncherVersion"))
{
out->minimumLauncherVersion = ensureInteger(root.value("minimumLauncherVersion"));
}
if (root.contains("tweakers"))
{
out->shouldOverwriteTweakers = true;
for (auto tweakerVal : ensureArray(root.value("tweakers")))
{
out->overwriteTweakers.append(ensureString(tweakerVal));
}
}
if (root.contains("+tweakers"))
{
for (auto tweakerVal : ensureArray(root.value("+tweakers")))
{
out->addTweakers.append(ensureString(tweakerVal));
}
}
if (root.contains("-tweakers"))
{
for (auto tweakerVal : ensureArray(root.value("-tweakers")))
{
out->removeTweakers.append(ensureString(tweakerVal));
}
}
if (root.contains("libraries"))
{
// FIXME: This should be done when applying.
out->shouldOverwriteLibs = !isFTB;
for (auto libVal : ensureArray(root.value("libraries")))
{
auto libObj = ensureObject(libVal);
auto lib = RawLibrary::fromJson(libObj, filename);
// FIXME: This should be done when applying.
if (isFTB)
{
lib->hint = "local";
lib->insertType = RawLibrary::Prepend;
out->addLibs.prepend(lib);
}
else
{
out->overwriteLibs.append(lib);
}
}
}
if (root.contains("+libraries"))
{
for (auto libVal : ensureArray(root.value("+libraries")))
{
QJsonObject libObj = ensureObject(libVal);
QJsonValue insertVal = ensureExists(libObj.value("insert"));
// parse the library
auto lib = RawLibrary::fromJson(libObj, filename);
// TODO: utility functions for handling this case. templates?
QString insertString;
{
if (insertVal.isString())
{
insertString = insertVal.toString();
}
else if (insertVal.isObject())
{
QJsonObject insertObj = insertVal.toObject();
if (insertObj.isEmpty())
{
throw JSONValidationError("One library has an empty insert object in " +
filename);
}
insertString = insertObj.keys().first();
lib->insertData = insertObj.value(insertString).toString();
}
}
if (insertString == "apply")
{
lib->insertType = RawLibrary::Apply;
}
else if (insertString == "prepend")
{
lib->insertType = RawLibrary::Prepend;
}
else if (insertString == "append")
{
lib->insertType = RawLibrary::Prepend;
}
else if (insertString == "replace")
{
lib->insertType = RawLibrary::Replace;
}
else
{
throw JSONValidationError("A '+' library in " + filename +
" contains an invalid insert type");
}
if (libObj.contains("MMC-depend"))
{
const QString dependString = ensureString(libObj.value("MMC-depend"));
if (dependString == "hard")
{
lib->dependType = RawLibrary::Hard;
}
else if (dependString == "soft")
{
lib->dependType = RawLibrary::Soft;
}
else
{
throw JSONValidationError("A '+' library in " + filename +
" contains an invalid depend type");
}
}
out->addLibs.append(lib);
}
}
if (root.contains("-libraries"))
{
for (auto libVal : ensureArray(root.value("-libraries")))
{
auto libObj = ensureObject(libVal);
out->removeLibs.append(ensureString(libObj.value("name")));
}
}
return out;
}
OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib)
{
std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib->name));
if (!lib->url.isEmpty())
{
out->setBaseUrl(lib->url);
}
out->setHint(lib->hint);
if (!lib->absoluteUrl.isEmpty())
{
out->setAbsoluteUrl(lib->absoluteUrl);
}
out->setAbsoluteUrl(lib->absoluteUrl);
out->extract_excludes = lib->excludes;
for (auto native : lib->natives)
{
out->addNative(native.first, native.second);
}
out->setRules(lib->rules);
out->finalize();
return out;
}
int VersionFile::findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle)
{
for (int i = 0; i < haystack.size(); ++i)
{
if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix)
.indexIn(haystack.at(i)->rawName()) != -1)
{
return i;
}
}
return -1;
}
void VersionFile::applyTo(VersionFinal *version)
{
if (minimumLauncherVersion != -1)
{
if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION)
{
throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION);
}
}
if (!version->id.isNull() && !mcVersion.isNull())
{
if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) ==
-1)
{
throw MinecraftVersionMismatch(fileId, mcVersion, version->id);
}
}
if (!id.isNull())
{
version->id = id;
}
if (!mainClass.isNull())
{
version->mainClass = mainClass;
}
if (!processArguments.isNull())
{
version->processArguments = processArguments;
}
if (!type.isNull())
{
version->type = type;
}
if (!releaseTime.isNull())
{
version->releaseTime = releaseTime;
}
if (!time.isNull())
{
version->time = time;
}
if (!assets.isNull())
{
version->assets = assets;
}
if (minimumLauncherVersion >= 0)
{
version->minimumLauncherVersion = minimumLauncherVersion;
}
if (!overwriteMinecraftArguments.isNull())
{
version->minecraftArguments = overwriteMinecraftArguments;
}
if (!addMinecraftArguments.isNull())
{
version->minecraftArguments += addMinecraftArguments;
}
if (!removeMinecraftArguments.isNull())
{
version->minecraftArguments.remove(removeMinecraftArguments);
}
if (shouldOverwriteTweakers)
{
version->tweakers = overwriteTweakers;
}
for (auto tweaker : addTweakers)
{
version->tweakers += tweaker;
}
for (auto tweaker : removeTweakers)
{
version->tweakers.removeAll(tweaker);
}
if (shouldOverwriteLibs)
{
version->libraries.clear();
for (auto lib : overwriteLibs)
{
version->libraries.append(createLibrary(lib));
}
}
for (auto lib : addLibs)
{
switch (lib->insertType)
{
case RawLibrary::Apply:
{
int index = findLibrary(version->libraries, lib->name);
if (index >= 0)
{
auto library = version->libraries[index];
if (!lib->url.isNull())
{
library->setBaseUrl(lib->url);
}
if (!lib->hint.isNull())
{
library->setHint(lib->hint);
}
if (!lib->absoluteUrl.isNull())
{
library->setAbsoluteUrl(lib->absoluteUrl);
}
if (lib->applyExcludes)
{
library->extract_excludes = lib->excludes;
}
if (lib->applyNatives)
{
library->clearSuffixes();
for (auto native : lib->natives)
{
library->addNative(native.first, native.second);
}
}
if (lib->applyRules)
{
library->setRules(lib->rules);
}
library->finalize();
}
else
{
QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)";
}
break;
}
case RawLibrary::Append:
case RawLibrary::Prepend:
{
const int startOfVersion = lib->name.lastIndexOf(':') + 1;
const int index = findLibrary(
version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*'));
if (index < 0)
{
if (lib->insertType == RawLibrary::Append)
{
version->libraries.append(createLibrary(lib));
}
else
{
version->libraries.prepend(createLibrary(lib));
}
}
else
{
auto otherLib = version->libraries.at(index);
const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX);
const Util::Version otherVersion = otherLib->version();
// if the existing version is a hard dependency we can either use it or
// fail, but we can't change it
if (otherLib->dependType == OneSixLibrary::Hard)
{
// we need a higher version, or we're hard to and the versions aren't
// equal
if (ourVersion > otherVersion ||
(lib->dependType == RawLibrary::Hard && ourVersion != otherVersion))
{
throw VersionBuildError(
QObject::tr(
"Error resolving library dependencies between %1 and %2 in %3.")
.arg(otherLib->rawName(), lib->name, filename));
}
else
{
// the library is already existing, so we don't have to do anything
}
}
else if (otherLib->dependType == OneSixLibrary::Soft)
{
// if we are higher it means we should update
if (ourVersion > otherVersion)
{
auto library = createLibrary(lib);
if (Util::Version(otherLib->minVersion) < ourVersion)
{
library->minVersion = ourVersion.toString();
}
version->libraries.replace(index, library);
}
else
{
// our version is smaller than the existing version, but we require
// it: fail
if (lib->dependType == RawLibrary::Hard)
{
throw VersionBuildError(QObject::tr(
"Error resolving library dependencies between %1 and %2 in %3.")
.arg(otherLib->rawName(), lib->name,
filename));
}
}
}
}
break;
}
case RawLibrary::Replace:
{
int index = findLibrary(version->libraries, lib->insertData);
if (index >= 0)
{
version->libraries.replace(index, createLibrary(lib));
}
else
{
QLOG_WARN() << "Couldn't find" << lib->insertData << "(skipping)";
}
break;
}
}
}
for (auto lib : removeLibs)
{
int index = findLibrary(version->libraries, lib);
if (index >= 0)
{
version->libraries.removeAt(index);
}
else
{
QLOG_WARN() << "Couldn't find" << lib << "(skipping)";
}
}
}

127
logic/VersionFile.h Normal file
View File

@ -0,0 +1,127 @@
#pragma once
#include <QString>
#include <QStringList>
#include <memory>
#include "logic/OpSys.h"
#include "logic/OneSixRule.h"
#include "MMCError.h"
class VersionFinal;
class VersionBuildError : public MMCError
{
public:
VersionBuildError(QString cause) : MMCError(cause) {};
virtual ~VersionBuildError() noexcept {}
};
/**
* the base version file was meant for a newer version of the vanilla launcher than we support
*/
class LauncherVersionError : public VersionBuildError
{
public:
LauncherVersionError(int actual, int supported)
: VersionBuildError(QObject::tr(
"The base version file of this instance was meant for a newer (%1) "
"version of the vanilla launcher than this version of MultiMC supports (%2).")
.arg(actual)
.arg(supported)) {};
virtual ~LauncherVersionError() noexcept {}
};
/**
* some patch was intended for a different version of minecraft
*/
class MinecraftVersionMismatch : public VersionBuildError
{
public:
MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion)
: VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft "
"(%2) than that of the instance (%3).")
.arg(fileId)
.arg(mcVersion)
.arg(parentMcVersion)) {};
virtual ~MinecraftVersionMismatch() noexcept {}
};
struct RawLibrary;
typedef std::shared_ptr<RawLibrary> RawLibraryPtr;
struct RawLibrary
{
QString name;
QString url;
QString hint;
QString absoluteUrl;
bool applyExcludes = false;
QStringList excludes;
bool applyNatives = false;
QList<QPair<OpSys, QString>> natives;
bool applyRules = false;
QList<std::shared_ptr<Rule>> rules;
// user for '+' libraries
enum InsertType
{
Apply,
Append,
Prepend,
Replace
};
InsertType insertType = Append;
QString insertData;
enum DependType
{
Soft,
Hard
};
DependType dependType = Soft;
static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename);
};
struct VersionFile;
typedef std::shared_ptr<VersionFile> VersionFilePtr;
struct VersionFile
{
public: /* methods */
static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename,
const bool requireOrder, const bool isFTB = false);
static OneSixLibraryPtr createLibrary(RawLibraryPtr lib);
int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle);
void applyTo(VersionFinal *version);
public: /* data */
int order = 0;
QString name;
QString fileId;
QString version;
// TODO use the mcVersion to determine if a version file should be removed on update
QString mcVersion;
QString filename;
// TODO requirements
// QMap<QString, QString> requirements;
QString id;
QString mainClass;
QString overwriteMinecraftArguments;
QString addMinecraftArguments;
QString removeMinecraftArguments;
QString processArguments;
QString type;
QString releaseTime;
QString time;
QString assets;
int minimumLauncherVersion = -1;
bool shouldOverwriteTweakers = false;
QStringList overwriteTweakers;
QStringList addTweakers;
QStringList removeTweakers;
bool shouldOverwriteLibs = false;
QList<RawLibraryPtr> overwriteLibs;
QList<RawLibraryPtr> addLibs;
QList<QString> removeLibs;
};

183
logic/VersionFinal.cpp Normal file
View File

@ -0,0 +1,183 @@
/* Copyright 2013 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 "VersionFinal.h"
#include <QDebug>
#include <QFile>
#include "OneSixVersionBuilder.h"
VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent)
: QAbstractListModel(parent), m_instance(instance)
{
clear();
}
bool VersionFinal::reload(const bool onlyVanilla, const QStringList &external)
{
//FIXME: source of epic failure.
beginResetModel();
OneSixVersionBuilder::build(this, m_instance, onlyVanilla, external);
endResetModel();
}
void VersionFinal::clear()
{
beginResetModel();
id.clear();
time.clear();
releaseTime.clear();
type.clear();
assets.clear();
processArguments.clear();
minecraftArguments.clear();
minimumLauncherVersion = 0xDEADBEAF;
mainClass.clear();
libraries.clear();
tweakers.clear();
versionFiles.clear();
endResetModel();
}
bool VersionFinal::canRemove(const int index) const
{
if (index < versionFiles.size())
{
return versionFiles.at(index)->fileId != "org.multimc.version.json";
}
return false;
}
QString VersionFinal::versionFileId(const int index) const
{
if (index < 0 || index >= versionFiles.size())
{
return QString();
}
return versionFiles.at(index)->fileId;
}
bool VersionFinal::remove(const int index)
{
if (canRemove(index))
{
return QFile::remove(versionFiles.at(index)->filename);
}
return false;
}
QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNormalLibs()
{
QList<std::shared_ptr<OneSixLibrary> > output;
for (auto lib : libraries)
{
if (lib->isActive() && !lib->isNative())
{
output.append(lib);
}
}
return output;
}
QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNativeLibs()
{
QList<std::shared_ptr<OneSixLibrary> > output;
for (auto lib : libraries)
{
if (lib->isActive() && lib->isNative())
{
output.append(lib);
}
}
return output;
}
std::shared_ptr<VersionFinal> VersionFinal::fromJson(const QJsonObject &obj)
{
std::shared_ptr<VersionFinal> version(new VersionFinal(0));
try
{
OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj);
}
catch(MMCError & err)
{
return 0;
}
return version;
}
QVariant VersionFinal::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
if (row < 0 || row >= versionFiles.size())
return QVariant();
if (role == Qt::DisplayRole)
{
switch (column)
{
case 0:
return versionFiles.at(row)->name;
case 1:
return versionFiles.at(row)->version;
default:
return QVariant();
}
}
return QVariant();
}
QVariant VersionFinal::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 VersionFinal::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
int VersionFinal::rowCount(const QModelIndex &parent) const
{
return versionFiles.size();
}
int VersionFinal::columnCount(const QModelIndex &parent) const
{
return 2;
}

View File

@ -22,14 +22,15 @@
#include <memory>
#include "OneSixLibrary.h"
#include "VersionFile.h"
class OneSixInstance;
class OneSixVersion : public QAbstractListModel
class VersionFinal : public QAbstractListModel
{
Q_OBJECT
public:
explicit OneSixVersion(OneSixInstance *instance, QObject *parent = 0);
explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
@ -37,7 +38,7 @@ public:
virtual int columnCount(const QModelIndex &parent) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
bool reload(QWidget *widgetParent, const bool onlyVanilla = false, const QStringList &external = QStringList());
bool reload(const bool onlyVanilla = false, const QStringList &external = QStringList());
void clear();
void dump() const;
@ -54,7 +55,7 @@ public:
QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs();
QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs();
static std::shared_ptr<OneSixVersion> fromJson(const QJsonObject &obj);
static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj);
// data members
public:
@ -118,20 +119,8 @@ public:
*/
// QList<Rule> rules;
struct VersionFile
{
QString name;
QString id;
QString version;
QString mcVersion;
QString filename;
int order;
};
QList<VersionFile> versionFiles;
QList<VersionFilePtr> versionFiles;
private:
OneSixInstance *m_instance;
};
QDebug operator<<(QDebug &dbg, const OneSixVersion *version);
QDebug operator<<(QDebug &dbg, const OneSixLibrary *library);

View File

@ -92,7 +92,6 @@ void LiteLoaderVersionList::updateListData(QList<BaseVersionPtr> versions)
LLListLoadTask::LLListLoadTask(LiteLoaderVersionList *vlist)
{
m_list = vlist;
vlistReply = nullptr;
}
LLListLoadTask::~LLListLoadTask()
@ -102,23 +101,49 @@ LLListLoadTask::~LLListLoadTask()
void LLListLoadTask::executeTask()
{
setStatus(tr("Loading LiteLoader version list..."));
auto worker = MMC->qnam();
vlistReply = worker->get(QNetworkRequest(QUrl(URLConstants::LITELOADER_URL)));
connect(vlistReply, SIGNAL(finished()), this, SLOT(listDownloaded()));
auto job = new NetJob("Version index");
// we do not care if the version is stale or not.
auto liteloaderEntry = MMC->metacache()->resolveEntry("liteloader", "versions.json");
// verify by poking the server.
liteloaderEntry->stale = true;
job->addNetAction(listDownload = CacheDownload::make(QUrl(URLConstants::LITELOADER_URL),
liteloaderEntry));
connect(listDownload.get(), SIGNAL(failed(int)), SLOT(listFailed()));
listJob.reset(job);
connect(listJob.get(), SIGNAL(succeeded()), SLOT(listDownloaded()));
connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
listJob->start();
}
void LLListLoadTask::listFailed()
{
emitFailed("Failed to load LiteLoader version list.");
return;
}
void LLListLoadTask::listDownloaded()
{
if (vlistReply->error() != QNetworkReply::NoError)
QByteArray data;
{
vlistReply->deleteLater();
emitFailed("Failed to load LiteLoader version list" + vlistReply->errorString());
return;
auto dlJob = listDownload;
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath();
QFile listFile(filename);
if (!listFile.open(QIODevice::ReadOnly))
{
emitFailed("Failed to open the LiteLoader version list.");
return;
}
data = listFile.readAll();
listFile.close();
dlJob.reset();
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError);
vlistReply->deleteLater();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
@ -140,7 +165,12 @@ void LLListLoadTask::listDownloaded()
emitFailed("Error parsing version list JSON: missing 'versions' object");
return;
}
const QJsonObject versions = root.value("versions").toObject();
auto meta = root.value("meta").toObject();
QString description = meta.value("description").toString(tr("This is a lightweight loader for mods that don't change game mechanics."));
QString defaultUrl = meta.value("url").toString("http://dl.liteloader.com");
QString authors = meta.value("authors").toString("Mumfrey");
auto versions = root.value("versions").toObject();
QList<BaseVersionPtr> tempList;
for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt)
@ -170,6 +200,9 @@ void LLListLoadTask::listDownloaded()
version->md5 = artefact.value("md5").toString();
version->timestamp = artefact.value("timestamp").toDouble();
version->tweakClass = artefact.value("tweakClass").toString();
version->authors = authors;
version->description = description;
version->defaultUrl = defaultUrl;
const QJsonArray libs = artefact.value("libraries").toArray();
for (auto lIt = libs.begin(); lIt != libs.end(); ++lIt)
{

View File

@ -22,6 +22,7 @@
#include "BaseVersionList.h"
#include "logic/tasks/Task.h"
#include "logic/BaseVersion.h"
#include <logic/net/NetJob.h>
class LLListLoadTask;
class QNetworkReply;
@ -46,6 +47,7 @@ public:
return version;
}
// important info
QString version;
QString file;
QString mcVersion;
@ -54,6 +56,11 @@ public:
bool isLatest;
QString tweakClass;
QStringList libraries;
// meta
QString defaultUrl;
QString description;
QString authors;
};
typedef std::shared_ptr<LiteLoaderVersion> LiteLoaderVersionPtr;
@ -96,8 +103,10 @@ public:
protected
slots:
void listDownloaded();
void listFailed();
protected:
QNetworkReply *vlistReply;
NetJobPtr listJob;
CacheDownloadPtr listDownload;
LiteLoaderVersionList *m_list;
};

View File

@ -25,7 +25,7 @@ void PasteUpload::executeTask()
m_reply = std::shared_ptr<QNetworkReply>(rep);
connect(rep, &QNetworkReply::downloadProgress, [&](qint64 value, qint64 max)
{ setProgress(value / max * 100); });
{ setProgress(value / qMax((qint64)1, max) * 100); });
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this,
SLOT(downloadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
@ -52,10 +52,9 @@ void PasteUpload::downloadFinished()
emitFailed(jsonError.errorString());
return;
}
QString error;
if (!parseResult(doc, &error))
if (!parseResult(doc))
{
emitFailed(error);
emitFailed(tr("paste.ee returned an error. Please consult the logs for more information"));
return;
}
}
@ -69,13 +68,13 @@ void PasteUpload::downloadFinished()
emitSucceeded();
}
bool PasteUpload::parseResult(QJsonDocument doc, QString *parseError)
bool PasteUpload::parseResult(QJsonDocument doc)
{
auto object = doc.object();
auto status = object.value("status").toString("error");
if (status == "error")
{
parseError = new QString(object.value("error").toString());
QLOG_ERROR() << "paste.ee reported error:" << QString(object.value("error").toString());
return false;
}
// FIXME: not the place for GUI things.

View File

@ -14,7 +14,7 @@ protected:
virtual void executeTask();
private:
bool parseResult(QJsonDocument doc, QString *parseError);
bool parseResult(QJsonDocument doc);
QString m_text;
QString m_error;
QWidget *m_window;

View File

@ -0,0 +1,20 @@
set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM 1)
### translation stuff
file(GLOB TRANSLATION_FILES ${CMAKE_CURRENT_LIST_DIR}/*.ts)
set(FILES_TO_TRANSLATE_ABSOLUTE)
foreach(file ${FILES_TO_TRANSLATE})
list(APPEND FILES_TO_TRANSLATE_ABSOLUTE "${CMAKE_SOURCE_DIR}/${file}")
endforeach()
qt5_create_translation(TRANSLATION_MESSAGES ${FILES_TO_TRANSLATE_ABSOLUTE} ${TRANSLATION_FILES})
qt5_add_translation(TRANSLATION_QM ${TRANSLATION_FILES})
add_custom_target(translations_update DEPENDS ${TRANSLATION_MESSAGES})
add_custom_target(translations DEPENDS ${TRANSLATION_QM})
IF(APPLE AND UNIX) ## OSX
install(FILES ${TRANSLATION_QM} DESTINATION MultiMC.app/Contents/MacOS/translations)
ELSE()
install(FILES ${TRANSLATION_QM} DESTINATION translations)
ENDIF()

File diff suppressed because it is too large Load Diff