Move all the things (YES. Move them.)

Also, implemented some basic modlist logic, to be wired up.
This commit is contained in:
Petr Mrázek
2013-08-17 13:40:51 +02:00
parent 77e8066542
commit 253067c782
184 changed files with 414 additions and 345 deletions

181
logic/BaseInstance.cpp Normal file
View File

@ -0,0 +1,181 @@
/* 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 "BaseInstance.h"
#include "BaseInstance_p.h"
#include <QFileInfo>
#include "inisettingsobject.h"
#include "setting.h"
#include "overridesetting.h"
#include "pathutils.h"
#include "lists/MinecraftVersionList.h"
BaseInstance::BaseInstance( BaseInstancePrivate* d_in,
const QString& rootDir,
SettingsObject* settings_obj,
QObject* parent
)
:inst_d(d_in), QObject(parent)
{
I_D(BaseInstance);
d->m_settings = settings_obj;
d->m_rootDir = rootDir;
settings().registerSetting(new Setting("name", "Unnamed Instance"));
settings().registerSetting(new Setting("iconKey", "default"));
settings().registerSetting(new Setting("notes", ""));
settings().registerSetting(new Setting("lastLaunchTime", 0));
// Java Settings
settings().registerSetting(new Setting("OverrideJava", false));
settings().registerSetting(new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath")));
settings().registerSetting(new OverrideSetting("JvmArgs", globalSettings->getSetting("JvmArgs")));
// Custom Commands
settings().registerSetting(new Setting("OverrideCommands", false));
settings().registerSetting(new OverrideSetting("PreLaunchCommand", globalSettings->getSetting("PreLaunchCommand")));
settings().registerSetting(new OverrideSetting("PostExitCommand", globalSettings->getSetting("PostExitCommand")));
// Window Size
settings().registerSetting(new Setting("OverrideWindow", false));
settings().registerSetting(new OverrideSetting("LaunchMaximized", globalSettings->getSetting("LaunchMaximized")));
settings().registerSetting(new OverrideSetting("MinecraftWinWidth", globalSettings->getSetting("MinecraftWinWidth")));
settings().registerSetting(new OverrideSetting("MinecraftWinHeight", globalSettings->getSetting("MinecraftWinHeight")));
// Memory
settings().registerSetting(new Setting("OverrideMemory", false));
settings().registerSetting(new OverrideSetting("MinMemAlloc", globalSettings->getSetting("MinMemAlloc")));
settings().registerSetting(new OverrideSetting("MaxMemAlloc", globalSettings->getSetting("MaxMemAlloc")));
// Auto login
settings().registerSetting(new Setting("OverrideLogin", false));
settings().registerSetting(new OverrideSetting("AutoLogin", globalSettings->getSetting("AutoLogin")));
// Console
settings().registerSetting(new Setting("OverrideConsole", false));
settings().registerSetting(new OverrideSetting("ShowConsole", globalSettings->getSetting("ShowConsole")));
settings().registerSetting(new OverrideSetting("AutoCloseConsole", globalSettings->getSetting("AutoCloseConsole")));
}
QString BaseInstance::id() const
{
return QFileInfo(instanceRoot()).fileName();
}
QString BaseInstance::instanceType() const
{
I_D(BaseInstance);
return d->m_settings->get("InstanceType").toString();
}
QString BaseInstance::instanceRoot() const
{
I_D(BaseInstance);
return d->m_rootDir;
}
QString BaseInstance::minecraftRoot() const
{
QFileInfo mcDir(PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(PathCombine(instanceRoot(), ".minecraft"));
if (dotMCDir.exists() && !mcDir.exists())
return dotMCDir.filePath();
else
return mcDir.filePath();
}
InstanceList *BaseInstance::instList() const
{
if (parent()->inherits("InstanceList"))
return (InstanceList *)parent();
else
return NULL;
}
InstVersionList *BaseInstance::versionList() const
{
return &MinecraftVersionList::getMainList();
}
SettingsObject &BaseInstance::settings() const
{
I_D(BaseInstance);
return *d->m_settings;
}
qint64 BaseInstance::lastLaunch() const
{
I_D(BaseInstance);
return d->m_settings->get ( "lastLaunchTime" ).value<qint64>();
}
void BaseInstance::setLastLaunch ( qint64 val )
{
I_D(BaseInstance);
d->m_settings->set ( "lastLaunchTime", val );
emit propertiesChanged ( this );
}
void BaseInstance::setGroup ( QString val )
{
I_D(BaseInstance);
d->m_group = val;
emit propertiesChanged ( this );
}
QString BaseInstance::group() const
{
I_D(BaseInstance);
return d->m_group;
}
void BaseInstance::setNotes ( QString val )
{
I_D(BaseInstance);
d->m_settings->set ( "notes", val );
}
QString BaseInstance::notes() const
{
I_D(BaseInstance);
return d->m_settings->get ( "notes" ).toString();
}
void BaseInstance::setIconKey ( QString val )
{
I_D(BaseInstance);
d->m_settings->set ( "iconKey", val );
emit propertiesChanged ( this );
}
QString BaseInstance::iconKey() const
{
I_D(BaseInstance);
return d->m_settings->get ( "iconKey" ).toString();
}
void BaseInstance::setName ( QString val )
{
I_D(BaseInstance);
d->m_settings->set ( "name", val );
emit propertiesChanged ( this );
}
QString BaseInstance::name() const
{
I_D(BaseInstance);
return d->m_settings->get ( "name" ).toString();
}

145
logic/BaseInstance.h Normal file
View File

@ -0,0 +1,145 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QDateTime>
#include <settingsobject.h>
#include "inifile.h"
#include "lists/InstVersionList.h"
class QDialog;
class BaseUpdate;
class MinecraftProcess;
class OneSixUpdate;
class InstanceList;
class BaseInstancePrivate;
/*!
* \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 BaseInstance : public QObject
{
Q_OBJECT
protected:
/// no-touchy!
BaseInstance(BaseInstancePrivate * d, const QString &rootDir, SettingsObject * settings, QObject *parent = 0);
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {};
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to be unique.
QString id() const;
/// get the type of this instance
QString instanceType() const;
/// Path to the instance's root directory.
QString instanceRoot() const;
/// Path to the instance's minecraft directory.
QString minecraftRoot() const;
QString name() const;
void setName(QString val);
QString iconKey() const;
void setIconKey(QString val);
QString notes() const;
void setNotes(QString val);
QString group() const;
void setGroup(QString val);
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;
//virtual void setCurrentVersionId(QString val) = 0;
/*!
* Whether or not Minecraft should be downloaded when the instance is launched.
*/
virtual bool shouldUpdate() const = 0;
virtual void setShouldUpdate(bool val) = 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());
/*!
* \brief Gets the instance list that this instance is a part of.
* Returns NULL if this instance is not in a list
* (the parent is not an InstanceList).
* \return A pointer to the InstanceList containing this instance.
*/
InstanceList *instList() const;
/*!
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
*/
virtual InstVersionList *versionList() const;
/*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
* \return A pointer to this instance's settings object.
*/
virtual SettingsObject &settings() const;
/// returns a valid update task if update is needed, NULL otherwise
virtual BaseUpdate* doUpdate() = 0;
/// returns a valid minecraft process, ready for launch
virtual MinecraftProcess* prepareForLaunch(QString user, QString session) = 0;
/// do any necessary cleanups after the instance finishes. also runs before 'prepareForLaunch'
virtual void cleanupAfterRun() = 0;
/// create a mod edit dialog for the instance
virtual QSharedPointer<QDialog> createModEditDialog ( QWidget* parent ) = 0;
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
*/
void propertiesChanged(BaseInstance * inst);
protected:
QSharedPointer<BaseInstancePrivate> inst_d;
};
// pointer for lazy people
typedef QSharedPointer<BaseInstance> InstancePtr;

14
logic/BaseInstance_p.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <QString>
#include <settingsobject.h>
class BaseInstance;
#define I_D(Class) Class##Private * const d = (Class##Private * const) inst_d.data()
struct BaseInstancePrivate
{
QString m_rootDir;
QString m_group;
SettingsObject *m_settings;
};

13
logic/BaseUpdate.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "BaseUpdate.h"
BaseUpdate::BaseUpdate ( BaseInstance* inst, QObject* parent ) : Task ( parent )
{
m_inst = inst;
}
void BaseUpdate::updateDownloadProgress(qint64 current, qint64 total)
{
// The progress on the current file is current / total
float currentDLProgress = (float) current / (float) total;
setProgress((int)(currentDLProgress * 100)); // convert to percentage
}

49
logic/BaseUpdate.h Normal file
View File

@ -0,0 +1,49 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QList>
#include <QUrl>
#include "net/DownloadJob.h"
#include "tasks/Task.h"
class MinecraftVersion;
class BaseInstance;
/*!
* The game update task is the task that handles downloading instances' files.
*/
class BaseUpdate : public Task
{
Q_OBJECT
public:
explicit BaseUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask() = 0;
protected slots:
//virtual void error(const QString &msg);
void updateDownloadProgress(qint64 current, qint64 total);
protected:
JobListQueue download_queue;
BaseInstance *m_inst;
};

24
logic/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
project(libMultiMC)
set(CMAKE_AUTOMOC ON)
# Find Qt
find_package(Qt5Core REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Xml REQUIRED)
# Include Qt headers.
include_directories(${Qt5Base_INCLUDE_DIRS})
include_directories(${Qt5Network_INCLUDE_DIRS})
# Include utility library.
include_directories(${CMAKE_SOURCE_DIR}/libutil/include)
# Include settings library.
include_directories(${CMAKE_SOURCE_DIR}/libsettings/include)
SET(LIBINST_HEADERS
)

163
logic/IconListModel.cpp Normal file
View File

@ -0,0 +1,163 @@
#include "IconListModel.h"
#include <pathutils.h>
#include <QMap>
#include <QEventLoop>
#include <QDir>
#define MAX_SIZE 1024
IconList* IconList::m_Instance = 0;
QMutex IconList::mutex;
struct entry
{
QString key;
QString name;
QIcon icon;
bool is_builtin;
};
class Private : public QObject
{
Q_OBJECT
public:
QMap<QString, int> index;
QVector<entry> icons;
Private()
{
}
};
IconList::IconList() : QAbstractListModel(), d(new Private())
{
QDir instance_icons(":/icons/instances/");
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(), true);
}
// FIXME: get from settings
ensurePathExists("icons/");
QDir user_icons("icons/");
file_info_list = user_icons.entryInfoList(QDir::Files, QDir::Name);
for(auto file_info: file_info_list)
{
QString filename = file_info.absoluteFilePath();
QString key = file_info.baseName();
addIcon(key, key, filename);
}
}
IconList::~IconList()
{
delete d;
d = nullptr;
}
QVariant IconList::data ( const QModelIndex& index, int role ) const
{
if(!index.isValid())
return QVariant();
int row = index.row();
if(row < 0 || row >= d->icons.size())
return QVariant();
switch(role)
{
case Qt::DecorationRole:
return d->icons[row].icon;
case Qt::DisplayRole:
return d->icons[row].name;
case Qt::UserRole:
return d->icons[row].key;
default:
return QVariant();
}
}
int IconList::rowCount ( const QModelIndex& parent ) const
{
return d->icons.size();
}
bool IconList::addIcon ( QString key, QString name, QString path, bool is_builtin )
{
auto iter = d->index.find(key);
if(iter != d->index.end())
{
if(d->icons[*iter].is_builtin) return false;
QIcon icon(path);
if(icon.isNull()) return false;
// replace the icon
d->icons[*iter] = {key, name, icon, is_builtin};
return true;
}
else
{
QIcon icon(path);
if(icon.isNull()) return false;
// add a new icon
d->icons.push_back({key, name, icon, is_builtin});
d->index[key] = d->icons.size() - 1;
return true;
}
}
QIcon IconList::getIcon ( QString key )
{
int icon_index = getIconIndex(key);
if(icon_index != -1)
return d->icons[icon_index].icon;
// Fallback for icons that don't exist.
icon_index = getIconIndex("infinity");
if(icon_index != -1)
return d->icons[icon_index].icon;
return QIcon();
}
int IconList::getIconIndex ( QString key )
{
if(key == "default")
key = "infinity";
auto iter = d->index.find(key);
if(iter != d->index.end())
return *iter;
return -1;
}
void IconList::drop()
{
mutex.lock();
delete m_Instance;
m_Instance = 0;
mutex.unlock();
}
IconList* IconList::instance()
{
if ( !m_Instance )
{
mutex.lock();
if ( !m_Instance )
m_Instance = new IconList;
mutex.unlock();
}
return m_Instance;
}
#include "IconListModel.moc"

33
logic/IconListModel.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <QMutex>
#include <QAbstractListModel>
#include <QtGui/QIcon>
class Private;
class IconList : public QAbstractListModel
{
public:
static IconList* instance();
static void drop();
QIcon getIcon ( 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, bool is_builtin = false);
private:
virtual ~IconList();
IconList();
// hide copy constructor
IconList ( const IconList & ) = delete;
// hide assign op
IconList& operator= ( const IconList & ) = delete;
static IconList* m_Instance;
static QMutex mutex;
Private* d;
};

113
logic/InstanceFactory.cpp Normal file
View File

@ -0,0 +1,113 @@
/* 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 "InstanceFactory.h"
#include <QDir>
#include <QFileInfo>
#include "BaseInstance.h"
#include "LegacyInstance.h"
#include "OneSixInstance.h"
#include "NostalgiaInstance.h"
#include "InstanceVersion.h"
#include "MinecraftVersion.h"
#include "inifile.h"
#include <inisettingsobject.h>
#include <setting.h>
#include "pathutils.h"
InstanceFactory InstanceFactory::loader;
InstanceFactory::InstanceFactory() :
QObject(NULL)
{
}
InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst, const QString &instDir)
{
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
m_settings->registerSetting(new Setting("InstanceType", "Legacy"));
QString inst_type = m_settings->get("InstanceType").toString();
//FIXME: replace with a map lookup, where instance classes register their types
if(inst_type == "Legacy")
{
inst = new LegacyInstance(instDir, m_settings, this);
}
else if(inst_type == "OneSix")
{
inst = new OneSixInstance(instDir, m_settings, this);
}
else if(inst_type == "Nostalgia")
{
inst = new NostalgiaInstance(instDir, m_settings, this);
}
else
{
return InstanceFactory::UnknownLoadError;
}
return NoLoadError;
}
InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& inst, InstVersionPtr version, const QString& instDir )
{
QDir rootDir(instDir);
qDebug(instDir.toUtf8());
if (!rootDir.exists() && !rootDir.mkpath("."))
{
return InstanceFactory::CantCreateDir;
}
auto mcVer = version.dynamicCast<MinecraftVersion>();
if(!mcVer)
return InstanceFactory::NoSuchVersion;
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
m_settings->registerSetting(new Setting("InstanceType", "Legacy"));
switch(mcVer->type)
{
case MinecraftVersion::Legacy:
m_settings->set("InstanceType", "Legacy");
inst = new LegacyInstance(instDir, m_settings, this);
inst->setIntendedVersionId(version->descriptor);
break;
case MinecraftVersion::OneSix:
m_settings->set("InstanceType", "OneSix");
inst = new OneSixInstance(instDir, m_settings, this);
inst->setIntendedVersionId(version->descriptor);
break;
case MinecraftVersion::Nostalgia:
m_settings->set("InstanceType", "Nostalgia");
inst = new NostalgiaInstance(instDir, m_settings, this);
inst->setIntendedVersionId(version->descriptor);
break;
default:
{
delete m_settings;
return InstanceFactory::NoSuchVersion;
}
}
//FIXME: really, how do you even know?
return InstanceFactory::NoCreateError;
}

80
logic/InstanceFactory.h Normal file
View File

@ -0,0 +1,80 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QMap>
#include <QList>
#include "InstanceVersion.h"
class InstVersion;
class BaseInstance;
/*!
* The \bInstanceFactory\b is a singleton that manages loading and creating instances.
*/
class InstanceFactory : public QObject
{
Q_OBJECT
public:
/*!
* \brief Gets a reference to the instance loader.
*/
static InstanceFactory &get() { return loader; }
enum InstLoadError
{
NoLoadError = 0,
UnknownLoadError,
NotAnInstance
};
enum InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
/*!
* \brief Creates a stub instance
*
* \param inst Pointer to store the created instance in.
* \param instDir The 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(BaseInstance *&inst, InstVersionPtr version, const QString &instDir);
/*!
* \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(BaseInstance *&inst, const QString &instDir);
private:
InstanceFactory();
static InstanceFactory loader;
};

68
logic/InstanceVersion.h Normal file
View File

@ -0,0 +1,68 @@
/* 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.
*/
#pragma once
#include <QSharedPointer>
/*!
* An abstract base class for versions.
*/
struct InstVersion
{
/*!
* Checks if this version is less (older) than the given version.
* \param other The version to compare this one to.
* \return True if this version is older than the given version.
*/
virtual bool operator<(const InstVersion &rhs) const
{
return timestamp < rhs.timestamp;
}
/*!
* Checks if this version is greater (newer) than the given version.
* \param other The version to compare this one to.
* \return True if this version is newer than the given version.
*/
virtual bool operator>( const InstVersion& rhs ) const
{
return timestamp > rhs.timestamp;
}
/*!
* A string used to identify this version in config files.
* This should be unique within the version list or shenanigans will occur.
*/
QString descriptor;
/*!
* The name of this version as it is displayed to the user.
* For example: "1.5.1"
*/
QString name;
/*!
* Gets the version's timestamp.
* This is primarily used for sorting versions in a list.
*/
qint64 timestamp;
virtual QString typeString() const
{
return "InstVersion";
}
};
typedef QSharedPointer<InstVersion> InstVersionPtr;
Q_DECLARE_METATYPE( InstVersionPtr )

57
logic/LegacyForge.cpp Normal file
View File

@ -0,0 +1,57 @@
//
// Copyright 2012 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 "LegacyForge.h"
MinecraftForge::MinecraftForge ( const QString& file ) : Mod ( file )
{
}
bool MinecraftForge::FixVersionIfNeeded ( QString newVersion )
{/*
wxString reportedVersion = GetModVersion();
if(reportedVersion == "..." || reportedVersion.empty())
{
std::auto_ptr<wxFFileInputStream> in(new wxFFileInputStream("forge.zip"));
wxTempFileOutputStream out("forge.zip");
wxTextOutputStream textout(out);
wxZipInputStream inzip(*in);
wxZipOutputStream outzip(out);
std::auto_ptr<wxZipEntry> entry;
// preserve metadata
outzip.CopyArchiveMetaData(inzip);
// copy all entries
while (entry.reset(inzip.GetNextEntry()), entry.get() != NULL)
if (!outzip.CopyEntry(entry.release(), inzip))
return false;
// release last entry
in.reset();
outzip.PutNextEntry("forgeversion.properties");
wxStringTokenizer tokenizer(newVersion,".");
wxString verFile;
verFile << wxString("forge.major.number=") << tokenizer.GetNextToken() << "\n";
verFile << wxString("forge.minor.number=") << tokenizer.GetNextToken() << "\n";
verFile << wxString("forge.revision.number=") << tokenizer.GetNextToken() << "\n";
verFile << wxString("forge.build.number=") << tokenizer.GetNextToken() << "\n";
auto buf = verFile.ToUTF8();
outzip.Write(buf.data(), buf.length());
// check if we succeeded
return inzip.Eof() && outzip.Close() && out.Commit();
}
*/
return true;
}

25
logic/LegacyForge.h Normal file
View File

@ -0,0 +1,25 @@
//
// Copyright 2012 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 "Mod.h"
class MinecraftForge : public Mod
{
public:
MinecraftForge ( const QString& file );
bool FixVersionIfNeeded(QString newVersion);
};

274
logic/LegacyInstance.cpp Normal file
View File

@ -0,0 +1,274 @@
#include "LegacyInstance.h"
#include "LegacyInstance_p.h"
#include "MinecraftProcess.h"
#include "LegacyUpdate.h"
#include <setting.h>
#include <pathutils.h>
#include <cmdutils.h>
#include "gui/LegacyModEditDialog.h"
#include <QFileInfo>
#include <QDir>
#include <QImage>
#define LAUNCHER_FILE "MultiMCLauncher.jar"
LegacyInstance::LegacyInstance(const QString& rootDir, SettingsObject* settings, QObject* parent)
:BaseInstance( new LegacyInstancePrivate(),rootDir, settings, parent)
{
settings->registerSetting(new Setting("NeedsRebuild", true));
settings->registerSetting(new Setting("ShouldUpdate", false));
settings->registerSetting(new Setting("JarVersion", "Unknown"));
settings->registerSetting(new Setting("LwjglVersion", "2.9.0"));
settings->registerSetting(new Setting("IntendedJarVersion", ""));
}
BaseUpdate* LegacyInstance::doUpdate()
{
return new LegacyUpdate(this, this);
}
MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session)
{
MinecraftProcess * proc = new MinecraftProcess(this);
// FIXME: extract the icon
// QImage(":/icons/instances/" + iconKey()).save(PathCombine(minecraftRoot(), "icon.png"));
// extract the legacy launcher
QFile(":/launcher/launcher.jar").copy(PathCombine(minecraftRoot(), LAUNCHER_FILE));
// set the process arguments
{
QStringList args;
// window size
QString windowSize;
if (settings().get("LaunchMaximized").toBool())
windowSize = "max";
else
windowSize = QString("%1x%2").
arg(settings().get("MinecraftWinWidth").toInt()).
arg(settings().get("MinecraftWinHeight").toInt());
// window title
QString windowTitle;
windowTitle.append("MultiMC: ").append(name());
// Java arguments
args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString()));
#ifdef OSX
// OSX dock icon and name
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(windowTitle);
#endif
QString lwjgl = QDir(globalSettings->get("LWJGLDir").toString() + "/" + lwjglVersion()).absolutePath();
// launcher arguments
args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt());
args << "-jar" << LAUNCHER_FILE;
args << user;
args << session;
args << windowTitle;
args << windowSize;
args << lwjgl;
proc->setMinecraftArguments(args);
}
// set the process work path
proc->setMinecraftWorkdir(minecraftRoot());
return proc;
}
QSharedPointer< ModList > LegacyInstance::coreModList()
{
I_D(LegacyInstance);
if(!d->core_mod_list)
{
d->core_mod_list.reset(new ModList(coreModsDir(), QString()));
}
return d->core_mod_list;
}
QSharedPointer< ModList > LegacyInstance::jarModList()
{
I_D(LegacyInstance);
if(!d->jar_mod_list)
{
auto list = new ModList(instModsDir(), modListFile());
connect(list, SIGNAL(changed()), SLOT(jarModsChanged()));
d->jar_mod_list.reset(list);
}
return d->jar_mod_list;
}
void LegacyInstance::jarModsChanged()
{
setShouldRebuild(true);
}
QSharedPointer< ModList > LegacyInstance::loaderModList()
{
I_D(LegacyInstance);
if(!d->loader_mod_list)
{
d->loader_mod_list.reset(new ModList(mlModsDir(), QString()));
}
return d->loader_mod_list;
}
QSharedPointer< QDialog > LegacyInstance::createModEditDialog ( QWidget* parent )
{
return QSharedPointer<QDialog> (new LegacyModEditDialog(this, parent));
}
void LegacyInstance::cleanupAfterRun()
{
//FIXME: delete the launcher and icons and whatnot.
}
QString LegacyInstance::instModsDir() const
{
return PathCombine(instanceRoot(), "instMods");
}
QString LegacyInstance::binDir() const
{
return PathCombine(minecraftRoot(), "bin");
}
QString LegacyInstance::savesDir() const
{
return PathCombine(minecraftRoot(), "saves");
}
QString LegacyInstance::mlModsDir() const
{
return PathCombine(minecraftRoot(), "mods");
}
QString LegacyInstance::coreModsDir() const
{
return PathCombine(minecraftRoot(), "coremods");
}
QString LegacyInstance::resourceDir() const
{
return PathCombine(minecraftRoot(), "resources");
}
QString LegacyInstance::mcJar() const
{
return PathCombine(binDir(), "minecraft.jar");
}
QString LegacyInstance::mcBackup() const
{
return PathCombine(binDir(), "mcbackup.jar");
}
QString LegacyInstance::modListFile() const
{
return PathCombine(instanceRoot(), "modlist");
}
bool LegacyInstance::shouldUpdateCurrentVersion() const
{
QFileInfo jar(mcJar());
return jar.lastModified().toUTC().toMSecsSinceEpoch() != lastCurrentVersionUpdate();
}
void LegacyInstance::updateCurrentVersion(bool keepCurrent)
{
QFileInfo jar(mcJar());
if(!jar.exists())
{
setLastCurrentVersionUpdate(0);
setCurrentVersionId("Unknown");
return;
}
qint64 time = jar.lastModified().toUTC().toMSecsSinceEpoch();
setLastCurrentVersionUpdate(time);
if (!keepCurrent)
{
// TODO: Implement GetMinecraftJarVersion function.
QString newVersion = "Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath());
setCurrentVersionId(newVersion);
}
}
qint64 LegacyInstance::lastCurrentVersionUpdate() const
{
I_D(LegacyInstance);
return d->m_settings->get ( "lastVersionUpdate" ).value<qint64>();
}
void LegacyInstance::setLastCurrentVersionUpdate ( qint64 val )
{
I_D(LegacyInstance);
d->m_settings->set ( "lastVersionUpdate", val );
}
bool LegacyInstance::shouldRebuild() const
{
I_D(LegacyInstance);
return d->m_settings->get ( "NeedsRebuild" ).toBool();
}
void LegacyInstance::setShouldRebuild ( bool val )
{
I_D(LegacyInstance);
d->m_settings->set ( "NeedsRebuild", val );
}
QString LegacyInstance::currentVersionId() const
{
I_D(LegacyInstance);
return d->m_settings->get ( "JarVersion" ).toString();
}
void LegacyInstance::setCurrentVersionId ( QString val )
{
I_D(LegacyInstance);
d->m_settings->set ( "JarVersion", val );
}
QString LegacyInstance::lwjglVersion() const
{
I_D(LegacyInstance);
return d->m_settings->get ( "LwjglVersion" ).toString();
}
void LegacyInstance::setLWJGLVersion ( QString val )
{
I_D(LegacyInstance);
d->m_settings->set ( "LwjglVersion", val );
}
QString LegacyInstance::intendedVersionId() const
{
I_D(LegacyInstance);
return d->m_settings->get ( "IntendedJarVersion" ).toString();
}
bool LegacyInstance::setIntendedVersionId ( QString version )
{
settings().set("IntendedJarVersion", version);
setShouldUpdate(true);
return true;
}
bool LegacyInstance::shouldUpdate() const
{
I_D(LegacyInstance);
QVariant var = settings().get ( "ShouldUpdate" );
if ( !var.isValid() || var.toBool() == false )
{
return intendedVersionId() != currentVersionId();
}
return true;
}
void LegacyInstance::setShouldUpdate ( bool val )
{
settings().set ( "ShouldUpdate", val );
}

95
logic/LegacyInstance.h Normal file
View File

@ -0,0 +1,95 @@
#pragma once
#include "BaseInstance.h"
class ModList;
class BaseUpdate;
class LegacyInstance : public BaseInstance
{
Q_OBJECT
public:
explicit LegacyInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0);
/// Path to the instance's minecraft.jar
QString mcJar() const;
//! Path to the instance's mcbackup.jar
QString mcBackup() const;
//! Path to the instance's modlist file.
QString modListFile() const;
////// Mod Lists //////
QSharedPointer<ModList> jarModList();
QSharedPointer<ModList> coreModList();
QSharedPointer<ModList> loaderModList();
////// Directories //////
QString savesDir() const;
QString instModsDir() const;
QString binDir() const;
QString mlModsDir() const;
QString coreModsDir() const;
QString resourceDir() const;
/*!
* \brief Checks whether or not the currentVersion of the instance needs to be updated.
* If this returns true, updateCurrentVersion is called. In the
* standard instance, this is determined by checking a timestamp
* stored in the instance config file against the last modified time of Minecraft.jar.
* \return True if updateCurrentVersion() should be called.
*/
bool shouldUpdateCurrentVersion() const;
/*!
* \brief Updates the current version.
* This function should first set the current version timestamp
* (setCurrentVersionTimestamp()) to the current time. Next, if
* keepCurrent is false, this function should check what the
* instance's current version is and call setCurrentVersion() to
* update it. This function will automatically be called when the
* instance is loaded if shouldUpdateCurrentVersion returns true.
* \param keepCurrent If true, only the version timestamp will be updated.
*/
void updateCurrentVersion(bool keepCurrent = false);
/*!
* Gets the last time that the current version was checked.
* This is checked against the last modified time on the jar file to see if
* the current version needs to be checked again.
*/
qint64 lastCurrentVersionUpdate() const;
void setLastCurrentVersionUpdate(qint64 val);
/*!
* Whether or not the instance's minecraft.jar needs to be rebuilt.
* If this is true, when the instance launches, its jar mods will be
* re-added to a fresh minecraft.jar file.
*/
bool shouldRebuild() const;
void setShouldRebuild(bool val);
virtual QString currentVersionId() const;
virtual void setCurrentVersionId(QString val);
//! The version of LWJGL that this instance uses.
QString lwjglVersion() const;
/// st the version of LWJGL libs this instance will use
void setLWJGLVersion(QString val);
virtual QString intendedVersionId() const;
virtual bool setIntendedVersionId ( QString version );
virtual bool shouldUpdate() const;
virtual void setShouldUpdate(bool val);
virtual BaseUpdate* doUpdate();
virtual MinecraftProcess* prepareForLaunch( QString user, QString session );
virtual void cleanupAfterRun();
virtual QSharedPointer< QDialog > createModEditDialog ( QWidget* parent );
protected slots:
virtual void jarModsChanged();
};

15
logic/LegacyInstance_p.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <QString>
#include <settingsobject.h>
#include "BaseInstance_p.h"
#include "ModList.h"
#include <QSharedPointer>
class ModList;
struct LegacyInstancePrivate: public BaseInstancePrivate
{
QSharedPointer<ModList> jar_mod_list;
QSharedPointer<ModList> core_mod_list;
QSharedPointer<ModList> loader_mod_list;
};

381
logic/LegacyUpdate.cpp Normal file
View File

@ -0,0 +1,381 @@
#include "LegacyUpdate.h"
#include "lists/LwjglVersionList.h"
#include "lists/MinecraftVersionList.h"
#include "BaseInstance.h"
#include "LegacyInstance.h"
#include "net/NetWorker.h"
#include <pathutils.h>
#include <quazip.h>
#include <quazipfile.h>
LegacyUpdate::LegacyUpdate ( BaseInstance* inst, QObject* parent ) : BaseUpdate ( inst, parent ) {}
void LegacyUpdate::executeTask()
{
lwjglStart();
}
void LegacyUpdate::lwjglStart()
{
LegacyInstance * inst = (LegacyInstance *) m_inst;
lwjglVersion = inst->lwjglVersion();
lwjglTargetPath = PathCombine("lwjgl", lwjglVersion );
lwjglNativesPath = PathCombine( lwjglTargetPath, "natives/");
// if the 'done' file exists, we don't have to download this again
QFileInfo doneFile(PathCombine(lwjglTargetPath, "done"));
if(doneFile.exists())
{
jarStart();
return;
}
auto &list = LWJGLVersionList::get();
if(!list.isLoaded())
{
emitFailed("Too soon! Let the LWJGL list load :)");
return;
}
setStatus("Downloading new LWJGL.");
auto version = list.getVersion(lwjglVersion);
if(!version)
{
emitFailed("Game update failed: the selected LWJGL version is invalid.");
return;
}
QString url = version->url();
QUrl realUrl(url);
QString hostname = realUrl.host();
auto &worker = NetWorker::spawn();
QNetworkRequest req(realUrl);
req.setRawHeader("Host", hostname.toLatin1());
req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)");
QNetworkReply * rep = worker.get ( req );
m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater);
connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
connect(&worker, SIGNAL(finished(QNetworkReply*)), SLOT(lwjglFinished(QNetworkReply*)));
//connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
}
void LegacyUpdate::lwjglFinished(QNetworkReply* reply)
{
if(m_reply != reply)
{
return;
}
if(reply->error() != QNetworkReply::NoError)
{
emitFailed( "Failed to download: "+
reply->errorString()+
"\nSometimes you have to wait a bit if you download many LWJGL versions in a row. YMMV");
return;
}
auto &worker = NetWorker::spawn();
//Here i check if there is a cookie for me in the reply and extract it
QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader));
if(cookies.count() != 0)
{
//you must tell which cookie goes with which url
worker.cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net"));
}
//here you can check for the 302 or whatever other header i need
QVariant newLoc = reply->header(QNetworkRequest::LocationHeader);
if(newLoc.isValid())
{
auto &worker = NetWorker::spawn();
QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString();
QUrl realUrl(redirectedTo);
QString hostname = realUrl.host();
QNetworkRequest req(redirectedTo);
req.setRawHeader("Host", hostname.toLatin1());
req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)");
QNetworkReply * rep = worker.get(req);
connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater);
return;
}
QFile saveMe("lwjgl.zip");
saveMe.open(QIODevice::WriteOnly);
saveMe.write(m_reply->readAll());
saveMe.close();
setStatus("Installing new LWJGL...");
extractLwjgl();
jarStart();
}
void LegacyUpdate::extractLwjgl()
{
// make sure the directories are there
bool success = ensurePathExists(lwjglNativesPath);
if(!success)
{
emitFailed("Failed to extract the lwjgl libs - error when creating required folders.");
return;
}
QuaZip zip("lwjgl.zip");
if(!zip.open(QuaZip::mdUnzip))
{
emitFailed("Failed to extract the lwjgl libs - not a valid archive.");
return;
}
// and now we are going to access files inside it
QuaZipFile file(&zip);
const QString jarNames[] = { "jinput.jar", "lwjgl_util.jar", "lwjgl.jar" };
for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile())
{
if(!file.open(QIODevice::ReadOnly))
{
zip.close();
emitFailed("Failed to extract the lwjgl libs - error while reading archive.");
return;
}
QuaZipFileInfo info;
QString name = file.getActualFileName();
if(name.endsWith('/'))
{
file.close();
continue;
}
QString destFileName;
// Look for the jars
for (int i = 0; i < 3; i++)
{
if (name.endsWith(jarNames[i]))
{
destFileName = PathCombine(lwjglTargetPath, jarNames[i]);
}
}
// Not found? look for the natives
if(destFileName.isEmpty())
{
#ifdef Q_OS_WIN32
QString nativesDir = "windows";
#elif Q_OS_MAC
QString nativesDir = "macosx";
#else
QString nativesDir = "linux";
#endif
if (name.contains(nativesDir))
{
int lastSlash = name.lastIndexOf('/');
int lastBackSlash = name.lastIndexOf('/');
if(lastSlash != -1)
name = name.mid(lastSlash+1);
else if(lastBackSlash != -1)
name = name.mid(lastBackSlash+1);
destFileName = PathCombine(lwjglNativesPath, name);
}
}
// Now if destFileName is still empty, go to the next file.
if (!destFileName.isEmpty())
{
setStatus("Installing new LWJGL - Extracting " + name);
QFile output(destFileName);
output.open(QIODevice::WriteOnly);
output.write(file.readAll()); // FIXME: wste of memory!?
output.close();
}
file.close(); // do not forget to close!
}
zip.close();
m_reply.clear();
QFile doneFile(PathCombine(lwjglTargetPath, "done"));
doneFile.open(QIODevice::WriteOnly);
doneFile.write("done.");
doneFile.close();
}
void LegacyUpdate::lwjglFailed()
{
emitFailed("Bad stuff happened while trying to get the lwjgl libs...");
}
void LegacyUpdate::jarStart()
{
setStatus("Checking ...");
LegacyInstance * inst = (LegacyInstance *) m_inst;
QString current_version_id = inst->currentVersionId();
QString intended_version_id = inst->intendedVersionId();
bool shouldUpdate = inst->shouldUpdate();
if(!shouldUpdate)
{
emitSucceeded();
return;
}
// nuke the backup file, we are replacing the base jar anyway
QFile mc_backup(inst->mcBackup());
if (mc_backup.exists())
{
mc_backup.remove();
}
// Get a pointer to the version object that corresponds to the instance's version.
auto targetVersion = MinecraftVersionList::getMainList().findVersion(intended_version_id);
if(!targetVersion)
{
emitFailed("Not a valid version:" + intended_version_id);
return;
}
// Make directories
QDir binDir(inst->binDir());
if (!binDir.exists() && !binDir.mkpath("."))
{
emitFailed("Failed to create bin folder.");
return;
}
// Build a list of URLs that will need to be downloaded.
setStatus("Downloading new minecraft.jar");
// This will be either 'minecraft' or the version number, depending on where
// we're downloading from.
QString jarFilename = "minecraft";
QString download_path = PathCombine(inst->minecraftRoot(), "bin/minecraft.jar");
QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
urlstr += targetVersion->descriptor + "/" + targetVersion->descriptor + ".jar";
auto dljob = DownloadJob::create(QUrl(urlstr), download_path);
legacyDownloadJob.reset(new JobList());
legacyDownloadJob->add(dljob);
connect(legacyDownloadJob.data(), SIGNAL(finished()), SLOT(jarFinished()));
connect(legacyDownloadJob.data(), SIGNAL(failed()), SLOT(jarFailed()));
connect(legacyDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
download_queue.enqueue(legacyDownloadJob);
}
void LegacyUpdate::jarFinished()
{
// process the jar
emitSucceeded();
}
void LegacyUpdate::jarFailed()
{
// bad, bad
emitFailed("Failed to download the minecraft jar. Try again later.");
}
void LegacyUpdate::ModTheJar()
{
/*
LegacyInstance * inst = (LegacyInstance *) m_inst;
// Get the mod list
auto modList = inst->getJarModList();
QFileInfo mcBin(inst->binDir());
QFileInfo mcJar(inst->mcJar());
QFileInfo mcBackup(inst->mcBackup());
// Nothing to do if there are no jar mods to install, no backup and just the mc jar
if(mcJar.isFile() && !mcBackup.exists() && modList->empty())
{
inst->setShouldRebuild(false);
emitSucceeded();
return;
}
setStatus("Installing mods - backing up minecraft.jar...");
if (!mcBackup.exists() && !QFile::copy(mcJar.absoluteFilePath(), mcBackup.absoluteFilePath()) )
{
emitFailed("Failed to back up minecraft.jar");
return;
}
if (mcJar.isFile() && !QFile::remove(mcJar.absoluteFilePath()))
{
emitFailed("Failed to delete old minecraft.jar");
return;
}
setStatus("Installing mods - Opening minecraft.jar");
wxFFileOutputStream jarStream(mcJar.absoluteFilePath());
wxZipOutputStream zipOut(jarStream);
// Files already added to the jar.
// These files will be skipped.
QSet<QString> addedFiles;
// Modify the jar
setStatus("Installing mods - Adding mod files...");
for (ModList::const_reverse_iterator iter = modList->rbegin(); iter != modList->rend(); iter++)
{
wxFileName modFileName = iter->GetFileName();
setStatus("Installing mods - Adding " + modFileName.GetFullName());
if (iter->GetModType() == Mod::ModType::MOD_ZIPFILE)
{
wxFFileInputStream modStream(modFileName.GetFullPath());
wxZipInputStream zipStream(modStream);
std::unique_ptr<wxZipEntry> entry;
while (entry.reset(zipStream.GetNextEntry()), entry.get() != NULL)
{
if (entry->IsDir())
continue;
wxString name = entry->GetName();
if (addedFiles.count(name) == 0)
{
if (!zipOut.CopyEntry(entry.release(), zipStream))
break;
addedFiles.insert(name);
}
}
}
else
{
wxFileName destFileName = modFileName;
destFileName.MakeRelativeTo(m_inst->GetInstModsDir().GetFullPath());
wxString destFile = destFileName.GetFullPath();
if (addedFiles.count(destFile) == 0)
{
wxFFileInputStream input(modFileName.GetFullPath());
zipOut.PutNextEntry(destFile);
zipOut.Write(input);
addedFiles.insert(destFile);
}
}
}
{
wxFFileInputStream inStream(mcBackup.GetFullPath());
wxZipInputStream zipIn(inStream);
std::auto_ptr<wxZipEntry> entry;
while (entry.reset(zipIn.GetNextEntry()), entry.get() != NULL)
{
wxString name = entry->GetName();
if (!name.Matches("META-INF*") &&
addedFiles.count(name) == 0)
{
if (!zipOut.CopyEntry(entry.release(), zipIn))
break;
addedFiles.insert(name);
}
}
}
// Recompress the jar
TaskStep(); // STEP 3
SetStatus(_("Installing mods - Recompressing jar..."));
inst->SetNeedsRebuild(false);
inst->UpdateVersion(true);
return (ExitCode)1;
*/
}

67
logic/LegacyUpdate.h Normal file
View File

@ -0,0 +1,67 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QList>
#include <QUrl>
#include "net/DownloadJob.h"
#include "tasks/Task.h"
#include "BaseUpdate.h"
class MinecraftVersion;
class BaseInstance;
class LegacyUpdate : public BaseUpdate
{
Q_OBJECT
public:
explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask();
private slots:
void lwjglStart();
void lwjglFinished( QNetworkReply* );
void lwjglFailed();
void jarStart();
void jarFinished();
void jarFailed();
void extractLwjgl();
void ModTheJar();
private:
QSharedPointer<QNetworkReply> m_reply;
// target version, determined during this task
// MinecraftVersion *targetVersion;
QString lwjglURL;
QString lwjglVersion;
QString lwjglTargetPath;
QString lwjglNativesPath;
private:
JobListPtr legacyDownloadJob;
JobListQueue download_queue;
// target version, determined during this task
QSharedPointer<MinecraftVersion> targetVersion;
};

169
logic/MinecraftProcess.cpp Normal file
View File

@ -0,0 +1,169 @@
/* Copyright 2013 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 "MinecraftProcess.h"
#include <QDataStream>
#include <QFile>
#include <QDir>
//#include <QImage>
#include <QProcessEnvironment>
#include "BaseInstance.h"
#include "osutils.h"
#include "pathutils.h"
#include "cmdutils.h"
#define IBUS "@im=ibus"
// constructor
MinecraftProcess::MinecraftProcess( BaseInstance* inst ) :
m_instance(inst)
{
connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus)));
// prepare the process environment
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
#ifdef LINUX
// Strip IBus
if (env.value("XMODIFIERS").contains(IBUS))
env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, ""));
#endif
// export some infos
env.insert("INST_NAME", inst->name());
env.insert("INST_ID", inst->id());
env.insert("INST_DIR", QDir(inst->instanceRoot()).absolutePath());
this->setProcessEnvironment(env);
m_prepostlaunchprocess.setProcessEnvironment(env);
// std channels
connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr()));
connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut()));
}
void MinecraftProcess::setMinecraftArguments ( QStringList args )
{
m_args = args;
}
void MinecraftProcess::setMinecraftWorkdir ( QString path )
{
QDir mcDir(path);
this->setWorkingDirectory(mcDir.absolutePath());
m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath());
}
// console window
void MinecraftProcess::on_stdErr()
{
QByteArray data = readAllStandardError();
QString str = m_err_leftover + QString::fromLocal8Bit(data);
m_err_leftover.clear();
QStringList lines = str.split("\n");
bool complete = str.endsWith("\n");
for(int i = 0; i < lines.size() - 1; i++)
{
QString & line = lines[i];
MessageLevel::Enum level = MessageLevel::Error;
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("[WARNING]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
emit log(lines[i].toLocal8Bit(), level);
}
if(!complete)
m_err_leftover = lines.last();
}
void MinecraftProcess::on_stdOut()
{
QByteArray data = readAllStandardOutput();
QString str = m_out_leftover + QString::fromLocal8Bit(data);
m_out_leftover.clear();
QStringList lines = str.split("\n");
bool complete = str.endsWith("\n");
for(int i = 0; i < lines.size() - 1; i++)
{
QString & line = lines[i];
emit log(lines[i].toLocal8Bit(), MessageLevel::Message);
}
if(!complete)
m_out_leftover = lines.last();
}
// exit handler
void MinecraftProcess::finish(int code, ExitStatus status)
{
if (status != NormalExit)
{
//TODO: error handling
}
emit log("Minecraft exited.");
m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code));
// run post-exit
if (!m_instance->settings().get("PostExitCommand").toString().isEmpty())
{
m_prepostlaunchprocess.start(m_instance->settings().get("PostExitCommand").toString());
m_prepostlaunchprocess.waitForFinished();
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
//TODO: error handling
}
}
m_instance->cleanupAfterRun();
emit ended();
}
void MinecraftProcess::launch()
{
if (!m_instance->settings().get("PreLaunchCommand").toString().isEmpty())
{
m_prepostlaunchprocess.start(m_instance->settings().get("PreLaunchCommand").toString());
m_prepostlaunchprocess.waitForFinished();
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
//TODO: error handling
return;
}
}
m_instance->setLastLaunch();
emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory()));
QString JavaPath = m_instance->settings().get("JavaPath").toString();
emit log(QString("Java path: '%1'").arg(JavaPath));
emit log(QString("Arguments: '%1'").arg(m_args.join("' '")));
start(JavaPath, m_args);
if (!waitForStarted())
{
emit log("Could not launch minecraft!");
return;
//TODO: error handling
}
}

86
logic/MinecraftProcess.h Normal file
View File

@ -0,0 +1,86 @@
/* Copyright 2013 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"
/**
* @brief the MessageLevel Enum
* defines what level a message is
*/
namespace MessageLevel {
enum Enum {
MultiMC, /**< MultiMC Messages */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal /**< Fatal Errors */
};
}
/**
* @file data/minecraftprocess.h
* @brief The MinecraftProcess class
*/
class MinecraftProcess : public QProcess
{
Q_OBJECT
public:
/**
* @brief MinecraftProcess constructor
* @param inst the Instance pointer to launch
*/
MinecraftProcess(BaseInstance *inst);
/**
* @brief launch minecraft
*/
void launch();
void setMinecraftWorkdir(QString path);
void setMinecraftArguments(QStringList args);
signals:
/**
* @brief emitted when mc has finished and the PostLaunchCommand was run
*/
void ended();
/**
* @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);
protected:
BaseInstance *m_instance;
QStringList m_args;
QString m_err_leftover;
QString m_out_leftover;
QProcess m_prepostlaunchprocess;
protected slots:
void finish(int, QProcess::ExitStatus status);
void on_stdErr();
void on_stdOut();
};

76
logic/MinecraftVersion.h Normal file
View File

@ -0,0 +1,76 @@
/* Copyright 2013 Andrew Okin
*
* 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 "InstanceVersion.h"
#include <QStringList>
struct MinecraftVersion : public InstVersion
{
// From InstVersion:
/*
QString m_descriptor;
QString m_name;
qint64 m_timestamp;
*/
/// The URL that this version will be downloaded from. maybe.
QString download_url;
/// This version's type. Used internally to identify what kind of version this is.
enum VersionType
{
OneSix,
Legacy,
Nostalgia
} type;
/// is this the latest version?
bool is_latest = false;
/// is this a snapshot?
bool is_snapshot = false;
virtual QString typeString() const
{
QStringList pre_final;
if(is_latest == true)
{
pre_final.append("Latest");
}
switch (type)
{
case OneSix:
pre_final.append("OneSix");
break;
case Legacy:
pre_final.append("Legacy");
break;
case Nostalgia:
pre_final.append("Nostalgia");
break;
default:
pre_final.append(QString("Type(%1)").arg(type));
break;
}
if(is_snapshot == true)
{
pre_final.append("Snapshot");
}
return pre_final.join(' ');
}
};

264
logic/Mod.cpp Normal file
View File

@ -0,0 +1,264 @@
//
// Copyright 2012 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 "Mod.h"
#include <pathutils.h>
#include <QDir>
Mod::Mod( const QFileInfo& file )
{
repath(file);
}
void Mod::repath ( const QFileInfo& file )
{
m_file = file;
m_name = file.baseName();
m_id = file.fileName();
m_type = Mod::MOD_UNKNOWN;
if (m_file.isDir())
m_type = MOD_FOLDER;
else if (m_file.isFile())
{
QString ext = m_file.suffix().toLower();
if (ext == "zip" || ext == "jar")
m_type = MOD_ZIPFILE;
else
m_type = MOD_SINGLEFILE;
}
/*
switch (modType)
{
case MOD_ZIPFILE:
{
wxFFileInputStream fileIn(modFile.GetFullPath());
wxZipInputStream zipIn(fileIn);
std::auto_ptr<wxZipEntry> entry;
bool is_forge = false;
while(true)
{
entry.reset(zipIn.GetNextEntry());
if (entry.get() == nullptr)
break;
if(entry->GetInternalName().EndsWith("mcmod.info"))
break;
if(entry->GetInternalName().EndsWith("forgeversion.properties"))
{
is_forge = true;
break;
}
}
if (entry.get() != nullptr)
{
// Read the info file into text
wxString infoFileData;
wxStringOutputStream stringOut(&infoFileData);
zipIn.Read(stringOut);
if(!is_forge)
ReadModInfoData(infoFileData);
else
ReadForgeInfoData(infoFileData);
}
}
break;
case MOD_FOLDER:
{
wxString infoFile = Path::Combine(modFile, "mcmod.info");
if (!wxFileExists(infoFile))
{
infoFile = wxEmptyString;
wxDir modDir(modFile.GetFullPath());
if (!modDir.IsOpened())
{
wxLogError(_("Can't fine mod info file. Failed to open mod folder."));
break;
}
wxString currentFile;
if (modDir.GetFirst(&currentFile))
{
do
{
if (currentFile.EndsWith("mcmod.info"))
{
infoFile = Path::Combine(modFile.GetFullPath(), currentFile);
break;
}
} while (modDir.GetNext(&currentFile));
}
}
if (infoFile != wxEmptyString && wxFileExists(infoFile))
{
wxString infoStr;
wxFFileInputStream fileIn(infoFile);
wxStringOutputStream strOut(&infoStr);
fileIn.Read(strOut);
ReadModInfoData(infoStr);
}
}
break;
}
*/
}
/*
void ReadModInfoData(QString info)
{
using namespace boost::property_tree;
// Read the data
ptree ptRoot;
std::stringstream stringIn(cStr(info));
try
{
read_json(stringIn, ptRoot);
ptree pt = ptRoot.get_child(ptRoot.count("modlist") == 1 ? "modlist" : "").begin()->second;
modID = wxStr(pt.get<std::string>("modid"));
modName = wxStr(pt.get<std::string>("name"));
modVersion = wxStr(pt.get<std::string>("version"));
}
catch (json_parser_error e)
{
// Silently fail...
}
catch (ptree_error e)
{
// Silently fail...
}
}
*/
// FIXME: abstraction violated.
/*
void Mod::ReadForgeInfoData(QString infoFileData)
{
using namespace boost::property_tree;
// Read the data
ptree ptRoot;
modName = "Minecraft Forge";
modID = "Forge";
std::stringstream stringIn(cStr(infoFileData));
try
{
read_ini(stringIn, ptRoot);
wxString major, minor, revision, build;
// BUG: boost property tree is bad. won't let us get a key with dots in it
// Likely cause = treating the dots as path separators.
for (auto iter = ptRoot.begin(); iter != ptRoot.end(); iter++)
{
auto &item = *iter;
std::string key = item.first;
std::string value = item.second.get_value<std::string>();
if(key == "forge.major.number")
major = value;
if(key == "forge.minor.number")
minor = value;
if(key == "forge.revision.number")
revision = value;
if(key == "forge.build.number")
build = value;
}
modVersion.Empty();
modVersion << major << "." << minor << "." << revision << "." << build;
}
catch (json_parser_error e)
{
std::cerr << e.what();
}
catch (ptree_error e)
{
std::cerr << e.what();
}
}
*/
bool Mod::replace ( Mod& with )
{
if(!destroy())
return false;
bool success = false;
auto t = with.type();
if(t == MOD_ZIPFILE || t == MOD_SINGLEFILE)
{
success = QFile::copy(with.m_file.filePath(), m_file.path());
}
if(t == MOD_FOLDER)
{
success = copyPath(with.m_file.filePath(), m_file.path());
}
if(success)
{
m_id = with.m_id;
m_mcversion = with.m_mcversion;
m_type = with.m_type;
m_name = with.m_name;
m_version = with.m_version;
}
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)
{
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:
return m_version;
case MOD_FOLDER:
return "Folder";
case MOD_SINGLEFILE:
return "File";
}
}

71
logic/Mod.h Normal file
View File

@ -0,0 +1,71 @@
//
// Copyright 2012 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(const QFileInfo &file);
QFileInfo filename() const { return m_file; }
QString id() const { return m_id; }
ModType type() const { return m_type; }
QString mcversion() const { return m_mcversion; };
bool valid() {return m_type != MOD_UNKNOWN;}
QString name() const {return m_name; };
QString version() const;
// 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);
bool operator ==(const Mod &other) const
{
return filename() == other.filename();
}
protected:
//FIXME: what do do with those? HMM...
/*
void ReadModInfoData(QString info);
void ReadForgeInfoData(QString infoFileData);
*/
QFileInfo m_file;
QString m_id;
QString m_name;
QString m_version;
QString m_mcversion;
ModType m_type;
};

472
logic/ModList.cpp Normal file
View File

@ -0,0 +1,472 @@
//
// 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 "ModList.h"
#include "LegacyInstance.h"
#include <pathutils.h>
ModList::ModList ( const QString& dir, const QString& list_file )
: QAbstractListModel(), m_dir(dir), m_list_file(list_file)
{
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
m_dir.setSorting(QDir::Name);
update();
}
bool ModList::update()
{
if (!isValid())
return false;
bool initial = mods.empty();
bool listChanged = false;
auto list = m_dir.entryInfoList();
for(auto entry: list)
{
Mod mod(entry);
if (initial || !mods.contains(mod))
{
mods.push_back(mod);
listChanged = true;
}
}
return listChanged;
}
bool ModList::isValid()
{
return m_dir.exists() && m_dir.isReadable();
}
bool ModList::installMod ( const QFileInfo& filename, size_t index )
{
if(!filename.exists() || !filename.isReadable())
{
return false;
}
Mod m(filename);
if(!m.valid())
return false;
// if it's already there, replace the original mod (in place)
int idx = mods.indexOf(m);
if(idx != -1)
{
if(mods[idx].replace(m))
{
emit changed();
return true;
}
return false;
}
auto type = m.type();
if(type == Mod::MOD_UNKNOWN)
return false;
if(type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE)
{
QString newpath = PathCombine(m_dir.path(), filename.fileName());
if(!QFile::copy(filename.filePath(), newpath))
return false;
m.repath(newpath);
mods.append(m);
emit changed();
return true;
}
else if(type == Mod::MOD_FOLDER)
{
QString newpath = PathCombine(m_dir.path(), filename.fileName());
if(!copyPath(filename.filePath(), newpath))
return false;
m.repath(newpath);
mods.append(m);
emit changed();
return true;
}
return false;
}
bool ModList::deleteMod ( size_t index )
{
if(index >= mods.size())
return false;
Mod & m = mods[index];
if(m.destroy())
{
mods.erase(mods.begin() + index);
emit changed();
return true;
}
return false;
}
bool ModList::moveMod ( size_t from, size_t to )
{
return false;
}
int ModList::columnCount ( const QModelIndex& parent ) const
{
return 2;
}
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();
if(role != Qt::DisplayRole)
return QVariant();
switch(column)
{
case 0:
return mods[row].name();
case 1:
return mods[row].version();
case 2:
return mods[row].mcversion();
default:
return QVariant();
}
}
QVariant ModList::headerData ( int section, Qt::Orientation orientation, int role ) const
{
if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
return QVariant();
switch (section)
{
case 0:
return QString("Mod Name");
case 1:
return QString("Mod Version");
case 2:
return QString("MC Version");
}
}
/*
ModList::ModList(const QString &dir)
: modsFolder(dir)
{
}
bool ModList::update(bool quickLoad)
{
bool listChanged = false;
// Check for mods in the list whose files do not exist and remove them from the list.
// If doing a quickLoad, erase the whole list.
for (size_t i = 0; i < size(); i++)
{
if (quickLoad || !at(i).GetFileName().FileExists())
{
erase(begin() + i);
i--;
listChanged = true;
}
}
// Add any mods in the mods folder that aren't already in the list.
if (LoadModListFromDir(QString(), quickLoad))
listChanged = true;
return listChanged;
}
bool ModList::LoadModListFromDir(const QString& loadFrom, bool quickLoad)
{
QString dir(loadFrom.isEmpty() ? modsFolder : loadFrom);
QDir modDir(dir);
if (!modDir.exists())
return false;
bool listChanged = false;
auto list = modDir.entryInfoList(QDir::Readable|QDir::NoDotAndDotDot, QDir::Name);
for(auto currentFile: list)
{
if (currentFile.isFile())
{
if (quickLoad || FindByFilename(currentFile.absoluteFilePath()) == nullptr)
{
Mod mod(currentFile.absoluteFilePath());
push_back(mod);
listChanged = true;
}
}
else if (currentFile.isDir())
{
if (LoadModListFromDir(currentFile.absoluteFilePath()))
listChanged = true;
}
}
return listChanged;
}
Mod *ModList::FindByFilename(const QString& filename)
{
// Search the list for a mod with the given filename.
for (auto iter = begin(); iter != end(); ++iter)
{
if (iter->GetFileName() == QFileInfo(filename))
return &(*iter);
}
// If nothing is found, return nullptr.
return nullptr;
}
int ModList::FindIndexByFilename(const QString& filename)
{
// Search the list for a mod with the given filename.
int i = 0;
for (auto iter = begin(); iter != end(); ++iter, i++)
{
if (iter->GetFileName() == QFileInfo(filename))
return i;
}
// If nothing is found, return nullptr.
return -1;
}
Mod* ModList::FindByID(const QString& modID, const QString& modVersion)
{
// Search the list for a mod that matches
for (auto iter = begin(); iter != end(); ++iter)
{
QString ID = iter->GetModID();
QString version = iter->GetModVersion();
if ( ID == modID && version == modVersion)
return &(*iter);
}
// If nothing is found, return nullptr.
return nullptr;
}
void ModList::LoadFromFile(const QString& file)
{
if (!wxFileExists(file))
return;
wxFFileInputStream inputStream(file);
wxArrayString modListFile = ReadAllLines(inputStream);
for (wxArrayString::iterator iter = modListFile.begin(); iter != modListFile.end(); iter++)
{
// Normalize the path to the instMods dir.
wxFileName modFile(*iter);
modFile.Normalize(wxPATH_NORM_ALL, modsFolder);
modFile.MakeRelativeTo();
// if the file is gone, do not load it
if(!modFile.Exists())
{
continue;
}
if (FindByFilename(modFile.GetFullPath()) == nullptr)
{
push_back(Mod(modFile));
}
}
}
void ModList::SaveToFile(const QString& file)
{
QString text;
for (iterator iter = begin(); iter != end(); ++iter)
{
wxFileName modFile = iter->GetFileName();
modFile.MakeRelativeTo(modsFolder);
text.append(modFile.GetFullPath());
text.append("\n");
}
wxTempFileOutputStream out(file);
WriteAllText(out, text);
out.Commit();
}
bool ModList::InsertMod(size_t index, const QString &filename, const QString& saveToFile)
{
QFileInfo source(filename);
QFileInfo dest(PathCombine(modsFolder, source.fileName()));
if (source != dest)
{
QFile::copy(source.absoluteFilePath(), dest.absoluteFilePath());
}
int oldIndex = FindIndexByFilename(dest.absoluteFilePath());
if (oldIndex != -1)
{
erase(begin() + oldIndex);
}
if (index >= size())
push_back(Mod(dest));
else
insert(begin() + index, Mod(dest));
if (!saveToFile.isEmpty())
SaveToFile(saveToFile);
return true;
}
bool ModList::DeleteMod(size_t index, const QString& saveToFile)
{
Mod *mod = &at(index);
if(mod->GetModType() == Mod::MOD_FOLDER)
{
QDir dir(mod->GetFileName().absoluteFilePath());
if(dir.removeRecursively())
{
erase(begin() + index);
if (!saveToFile.isEmpty())
SaveToFile(saveToFile);
return true;
}
else
{
// wxLogError(_("Failed to delete mod."));
}
}
else if (QFile(mod->GetFileName().absoluteFilePath()).remove())
{
erase(begin() + index);
if (!saveToFile.isEmpty())
SaveToFile(saveToFile);
return true;
}
else
{
// wxLogError(_("Failed to delete mod."));
}
return false;
}
bool JarModList::InsertMod(size_t index, const QString &filename, const QString& saveToFile)
{
QString saveFile = saveToFile;
if (saveToFile.isEmpty())
saveFile = m_inst->GetModListFile().GetFullPath();
if (ModList::InsertMod(index, filename, saveFile))
{
m_inst->setLWJGLVersion(true);
return true;
}
return false;
}
bool JarModList::DeleteMod(size_t index, const QString& saveToFile)
{
QString saveFile = saveToFile;
if (saveToFile.IsEmpty())
saveFile = m_inst->GetModListFile().GetFullPath();
if (ModList::DeleteMod(index, saveFile))
{
m_inst->SetNeedsRebuild();
return true;
}
return false;
}
bool JarModList::UpdateModList(bool quickLoad)
{
if (ModList::UpdateModList(quickLoad))
{
m_inst->SetNeedsRebuild();
return true;
}
return false;
}
bool FolderModList::LoadModListFromDir(const QString& loadFrom, bool quickLoad)
{
QString dir(loadFrom.IsEmpty() ? modsFolder : loadFrom);
if (!wxDirExists(dir))
return false;
bool listChanged = false;
wxDir modDir(dir);
if (!modDir.IsOpened())
{
wxLogError(_("Failed to open directory: ") + dir);
return false;
}
QString currentFile;
if (modDir.GetFirst(&currentFile))
{
do
{
wxFileName modFile(Path::Combine(dir, currentFile));
if (wxFileExists(modFile.GetFullPath()) || wxDirExists(modFile.GetFullPath()))
{
if (quickLoad || FindByFilename(modFile.GetFullPath()) == nullptr)
{
Mod mod(modFile.GetFullPath());
push_back(mod);
listChanged = true;
}
}
} while (modDir.GetNext(&currentFile));
}
return listChanged;
}
bool ModNameSort (const Mod & i,const Mod & j)
{
if(i.GetModType() == j.GetModType())
return (i.GetName().toLower() < j.GetName().toLower());
return (i.GetModType() < j.GetModType());
}
bool FolderModList::UpdateModList ( bool quickLoad )
{
bool changed = ModList::UpdateModList(quickLoad);
std::sort(begin(),end(),ModNameSort);
return changed;
}
*/

67
logic/ModList.h Normal file
View File

@ -0,0 +1,67 @@
//
// 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
//
#pragma once
class LegacyInstance;
class BaseInstance;
#include <QList>
#include <QString>
#include <QDir>
#include <QAbstractListModel>
#include "Mod.h"
/**
* A legacy mod list.
* Backed by a folder.
*/
class ModList : public QAbstractListModel
{
Q_OBJECT
public:
ModList(const QString& dir, const QString& list_file);
virtual QVariant data ( const QModelIndex& index, int role = Qt::DisplayRole ) const;
virtual int rowCount ( const QModelIndex& parent = QModelIndex() ) const
{
return size();
};
virtual QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;
virtual int columnCount ( const QModelIndex& parent ) const;
size_t size() const { return mods.size(); };
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 QFileInfo& filename, size_t index = 0);
/// Deletes the mod at the given index.
virtual bool deleteMod(size_t index);
/**
* 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 moveMod(size_t from, size_t to);
virtual bool isValid();
signals:
void changed();
protected:
QDir m_dir;
QString m_list_file;
QList<Mod> mods;
};

View File

@ -0,0 +1,12 @@
#include "NostalgiaInstance.h"
NostalgiaInstance::NostalgiaInstance ( const QString& rootDir, SettingsObject* settings, QObject* parent )
: OneSixInstance ( rootDir, settings, parent )
{
}
/*
ADD MORE
IF REQUIRED
*/

10
logic/NostalgiaInstance.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include "OneSixInstance.h"
class NostalgiaInstance : public OneSixInstance
{
Q_OBJECT
public:
explicit NostalgiaInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0);
};

162
logic/OneSixAssets.cpp Normal file
View File

@ -0,0 +1,162 @@
#include <QString>
#include <QDebug>
#include <QtXml/QtXml>
#include "OneSixAssets.h"
#include "net/DownloadJob.h"
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
{
QDomNodeList elementList = parent.elementsByTagName(tagname);
if (elementList.count())
return elementList.at(0).toElement();
else
return QDomElement();
}
class ThreadedDeleter : public QThread
{
Q_OBJECT
public:
void run()
{
QDirIterator iter(m_base, QDirIterator::Subdirectories);
QStringList nuke_list;
int base_length = m_base.length();
while (iter.hasNext())
{
QString filename = iter.next();
QFileInfo current(filename);
// we keep the dirs... whatever
if(current.isDir())
continue;
QString trimmedf = filename;
trimmedf.remove(0, base_length + 1);
if(m_whitelist.contains(trimmedf))
{
// qDebug() << trimmedf << " gets to live";
}
else
{
// DO NOT TOLERATE JUNK
// qDebug() << trimmedf << " dies";
QFile f (filename);
f.remove();
}
}
};
QString m_base;
QStringList m_whitelist;
};
class NukeAndPaveJob: public Job
{
Q_OBJECT
public:
explicit NukeAndPaveJob(QString base, QStringList whitelist)
:Job()
{
QDir dir(base);
deleterThread.m_base = dir.absolutePath();
deleterThread.m_whitelist = whitelist;
};
public slots:
virtual void start()
{
connect(&deleterThread, SIGNAL(finished()), SLOT(threadFinished()));
deleterThread.start();
};
void threadFinished()
{
emit finish();
}
private:
ThreadedDeleter deleterThread;
};
void OneSixAssets::fetchFinished()
{
QString prefix ( "http://s3.amazonaws.com/Minecraft.Resources/" );
QString fprefix ( "assets/" );
QStringList nuke_whitelist;
JobPtr firstJob = index_job->getFirstJob();
auto DlJob = firstJob.dynamicCast<DownloadJob>();
QByteArray ba = DlJob->m_data;
QString xmlErrorMsg;
QDomDocument doc;
if ( !doc.setContent ( ba, false, &xmlErrorMsg ) )
{
qDebug() << "Failed to process s3.amazonaws.com/Minecraft.Resources. XML error:" <<
xmlErrorMsg << ba;
}
//QRegExp etag_match(".*([a-f0-9]{32}).*");
QDomNodeList contents = doc.elementsByTagName ( "Contents" );
JobList *job = new JobList();
connect ( job, SIGNAL ( finished() ), SIGNAL(finished()) );
connect ( job, SIGNAL ( failed() ), SIGNAL(failed()) );
for ( int i = 0; i < contents.length(); i++ )
{
QDomElement element = contents.at ( i ).toElement();
if ( element.isNull() )
continue;
QDomElement keyElement = getDomElementByTagName ( element, "Key" );
QDomElement lastmodElement = getDomElementByTagName ( element, "LastModified" );
QDomElement etagElement = getDomElementByTagName ( element, "ETag" );
QDomElement sizeElement = getDomElementByTagName ( element, "Size" );
if ( keyElement.isNull() || lastmodElement.isNull() || etagElement.isNull() || sizeElement.isNull() )
continue;
QString keyStr = keyElement.text();
QString lastModStr = lastmodElement.text();
QString etagStr = etagElement.text();
QString sizeStr = sizeElement.text();
//Filter folder keys
if ( sizeStr == "0" )
continue;
QString filename = fprefix + keyStr;
QFile check_file ( filename );
QString client_etag = "nonsense";
// if there already is a file and md5 checking is in effect and it can be opened
if ( check_file.exists() && check_file.open ( QIODevice::ReadOnly ) )
{
// check the md5 against the expected one
client_etag = QCryptographicHash::hash ( check_file.readAll(), QCryptographicHash::Md5 ).toHex().constData();
check_file.close();
}
QString trimmedEtag = etagStr.remove ( '"' );
nuke_whitelist.append ( keyStr );
if(trimmedEtag != client_etag)
job->add ( DownloadJob::create ( QUrl ( prefix + keyStr ), filename ) );
}
job->add ( JobPtr ( new NukeAndPaveJob ( fprefix, nuke_whitelist ) ) );
files_job.reset ( job );
dl.enqueue ( files_job );
}
void OneSixAssets::fetchStarted()
{
qDebug() << "Started downloading!";
}
void OneSixAssets::start()
{
JobList *job = new JobList();
job->add ( DownloadJob::create ( QUrl ( "http://s3.amazonaws.com/Minecraft.Resources/" ) ) );
connect ( job, SIGNAL ( finished() ), SLOT ( fetchFinished() ) );
connect ( job, SIGNAL ( started() ), SLOT ( fetchStarted() ) );
index_job.reset ( job );
dl.enqueue ( index_job );
}
#include "OneSixAssets.moc"

22
logic/OneSixAssets.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "net/DownloadJob.h"
class Private;
class OneSixAssets : public QObject
{
Q_OBJECT
signals:
void failed();
void finished();
public slots:
void fetchFinished();
void fetchStarted();
public:
void start();
private:
JobListQueue dl;
JobListPtr index_job;
JobListPtr files_job;
};

218
logic/OneSixInstance.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "OneSixInstance.h"
#include "OneSixInstance_p.h"
#include "OneSixUpdate.h"
#include "MinecraftProcess.h"
#include "VersionFactory.h"
#include <setting.h>
#include <pathutils.h>
#include <cmdutils.h>
#include <JlCompress.h>
OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting_obj, QObject* parent )
: BaseInstance ( new OneSixInstancePrivate(), rootDir, setting_obj, parent )
{
I_D(OneSixInstance);
d->m_settings->registerSetting(new Setting("IntendedVersion", ""));
d->m_settings->registerSetting(new Setting("ShouldUpdate", false));
reloadFullVersion();
}
BaseUpdate* OneSixInstance::doUpdate()
{
return new OneSixUpdate(this);
}
QString replaceTokensIn(QString text, QMap<QString, QString> with)
{
QString result;
QRegExp token_regexp("\\$\\{(.+)\\}");
token_regexp.setMinimal(true);
QStringList list;
int tail = 0;
int head = 0;
while ((head = token_regexp.indexIn(text, head)) != -1)
{
result.append(text.mid(tail, head-tail));
QString key = token_regexp.cap(1);
auto iter = with.find(key);
if(iter != with.end())
{
result.append(*iter);
}
head += token_regexp.matchedLength();
tail = head;
}
result.append(text.mid(tail));
return result;
}
QStringList OneSixInstance::processMinecraftArgs( QString user, QString session )
{
I_D(OneSixInstance);
auto version = d->version;
QString args_pattern = version->minecraftArguments;
QMap<QString, QString> token_mapping;
token_mapping["auth_username"] = user;
token_mapping["auth_session"] = session;
//FIXME: user and player name are DIFFERENT!
token_mapping["auth_player_name"] = user;
//FIXME: WTF is this. I just plugged in a random UUID here.
token_mapping["auth_uuid"] = "7d4bacf0-fd62-11e2-b778-0800200c9a66"; // obviously fake.
// this is for offline:
/*
map["auth_player_name"] = "Player";
map["auth_player_name"] = "00000000-0000-0000-0000-000000000000";
*/
token_mapping["profile_name"] = name();
token_mapping["version_name"] = version->id;
QString absRootDir = QDir(minecraftRoot()).absolutePath();
token_mapping["game_directory"] = absRootDir;
QString absAssetsDir = QDir("assets/").absolutePath();
token_mapping["game_assets"] = absAssetsDir;
QStringList parts = args_pattern.split(' ',QString::SkipEmptyParts);
for (int i = 0; i < parts.length(); i++)
{
parts[i] = replaceTokensIn(parts[i], token_mapping);
}
return parts;
}
MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString session )
{
I_D(OneSixInstance);
cleanupAfterRun();
auto version = d->version;
if(!version)
return nullptr;
auto libs_to_extract = version->getActiveNativeLibs();
QString natives_dir_raw = PathCombine(instanceRoot(), "natives/");
bool success = ensurePathExists(natives_dir_raw);
if(!success)
{
// FIXME: handle errors
return nullptr;
}
for(auto lib: libs_to_extract)
{
QString path = "libraries/" + lib->storagePath();
qDebug() << "Will extract " << path.toLocal8Bit();
if(JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes).isEmpty())
{
return nullptr;
}
}
QStringList args;
args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString()));
args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt());
QDir natives_dir(natives_dir_raw);
args << QString("-Djava.library.path=%1").arg( natives_dir.absolutePath() );
QString classPath;
{
auto libs = version->getActiveNormalLibs();
for (auto lib: libs)
{
QFileInfo fi(QString("libraries/") + lib->storagePath());
classPath.append(fi.absoluteFilePath());
//FIXME: make separator tweakable
classPath.append(':');
}
QString targetstr = "versions/" + version->id + "/" + version->id + ".jar";
QFileInfo fi(targetstr);
classPath.append(fi.absoluteFilePath());
}
if(classPath.size())
{
args << "-cp";
args << classPath;
}
args << version->mainClass;
args.append(processMinecraftArgs(user, session));
// create the process and set its parameters
MinecraftProcess * proc = new MinecraftProcess(this);
proc->setMinecraftArguments(args);
proc->setMinecraftWorkdir(minecraftRoot());
return proc;
}
void OneSixInstance::cleanupAfterRun()
{
QString target_dir = PathCombine(instanceRoot(), "natives/");
QDir dir(target_dir);
dir.removeRecursively();
}
QSharedPointer< QDialog > OneSixInstance::createModEditDialog ( QWidget* parent )
{
return QSharedPointer< QDialog >();
}
bool OneSixInstance::setIntendedVersionId ( QString version )
{
settings().set("IntendedVersion", version);
setShouldUpdate(true);
return true;
}
QString OneSixInstance::intendedVersionId() const
{
return settings().get("IntendedVersion").toString();
}
void OneSixInstance::setShouldUpdate ( bool val )
{
settings().set ( "ShouldUpdate", val );
}
bool OneSixInstance::shouldUpdate() const
{
I_D(OneSixInstance);
QVariant var = settings().get ( "ShouldUpdate" );
if ( !var.isValid() || var.toBool() == false )
{
return intendedVersionId() != currentVersionId();
}
return true;
}
QString OneSixInstance::currentVersionId() const
{
return intendedVersionId();
}
bool OneSixInstance::reloadFullVersion()
{
I_D(OneSixInstance);
QString verpath = PathCombine(instanceRoot(), "version.json");
QFile versionfile(verpath);
if(versionfile.exists() && versionfile.open(QIODevice::ReadOnly))
{
FullVersionFactory fvf;
auto version = fvf.parse(versionfile.readAll());
versionfile.close();
if(version)
{
d->version = version;
return true;
}
};
return false;
}
QSharedPointer< FullVersion > OneSixInstance::getFullVersion()
{
I_D(OneSixInstance);
return d->version;
}

34
logic/OneSixInstance.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include "BaseInstance.h"
#include <QStringList>
class FullVersion;
class BaseUpdate;
class OneSixInstance : public BaseInstance
{
Q_OBJECT
public:
explicit OneSixInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0);
virtual BaseUpdate* doUpdate();
virtual MinecraftProcess* prepareForLaunch ( QString user, QString session );
virtual void cleanupAfterRun();
virtual QString intendedVersionId() const;
virtual bool setIntendedVersionId ( QString version );
virtual QString currentVersionId() const;
// virtual void setCurrentVersionId ( QString val ) {};
virtual bool shouldUpdate() const;
virtual void setShouldUpdate(bool val);
virtual QSharedPointer< QDialog > createModEditDialog ( QWidget* parent );
/// reload the full version json file. return true on success!
bool reloadFullVersion();
/// get the current full version info
QSharedPointer<FullVersion> getFullVersion();
private:
QStringList processMinecraftArgs( QString user, QString session );
};

9
logic/OneSixInstance_p.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#include "BaseInstance_p.h"
#include "OneSixVersion.h"
struct OneSixInstancePrivate: public BaseInstancePrivate
{
QSharedPointer<FullVersion> version;
};

169
logic/OneSixUpdate.cpp Normal file
View File

@ -0,0 +1,169 @@
/* 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 "OneSixUpdate.h"
#include <QtNetwork>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDataStream>
#include <QDebug>
#include "BaseInstance.h"
#include "lists/MinecraftVersionList.h"
#include "VersionFactory.h"
#include "OneSixVersion.h"
#include "OneSixInstance.h"
#include "pathutils.h"
OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent):BaseUpdate(inst, parent){}
void OneSixUpdate::executeTask()
{
QString intendedVersion = m_inst->intendedVersionId();
// Make directories
QDir mcDir(m_inst->minecraftRoot());
if (!mcDir.exists() && !mcDir.mkpath("."))
{
emitFailed("Failed to create bin folder.");
return;
}
// Get a pointer to the version object that corresponds to the instance's version.
targetVersion = MinecraftVersionList::getMainList().findVersion(intendedVersion).dynamicCast<MinecraftVersion>();
if(targetVersion == nullptr)
{
// don't do anything if it was invalid
emitSucceeded();
return;
}
if(m_inst->shouldUpdate())
{
versionFileStart();
}
else
{
jarlibStart();
}
}
void OneSixUpdate::versionFileStart()
{
setStatus("Getting the version files from Mojang.");
QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
urlstr += targetVersion->descriptor + "/" + targetVersion->descriptor + ".json";
auto dljob = DownloadJob::create(QUrl(urlstr));
specificVersionDownloadJob.reset(new JobList());
specificVersionDownloadJob->add(dljob);
connect(specificVersionDownloadJob.data(), SIGNAL(finished()), SLOT(versionFileFinished()));
connect(specificVersionDownloadJob.data(), SIGNAL(failed()), SLOT(versionFileFailed()));
connect(specificVersionDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
download_queue.enqueue(specificVersionDownloadJob);
}
void OneSixUpdate::versionFileFinished()
{
JobPtr firstJob = specificVersionDownloadJob->getFirstJob();
auto DlJob = firstJob.dynamicCast<DownloadJob>();
QString version_id = targetVersion->descriptor;
QString inst_dir = m_inst->instanceRoot();
// save the version file in $instanceId/version.json
{
QString version1 = PathCombine(inst_dir, "/version.json");
ensurePathExists(version1);
// FIXME: detect errors here, download to a temp file, swap
QFile vfile1 (version1);
vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly );
vfile1.write(DlJob->m_data);
vfile1.close();
}
// the version is downloaded safely. update is 'done' at this point
m_inst->setShouldUpdate(false);
// save the version file in versions/$version/$version.json
/*
//QString version2 = QString("versions/") + version_id + "/" + version_id + ".json";
//ensurePathExists(version2);
//QFile vfile2 (version2);
//vfile2.open(QIODevice::Truncate | QIODevice::WriteOnly );
//vfile2.write(DlJob->m_data);
//vfile2.close();
*/
jarlibStart();
}
void OneSixUpdate::versionFileFailed()
{
emitFailed("Failed to download the version description. Try again.");
}
void OneSixUpdate::jarlibStart()
{
OneSixInstance * inst = (OneSixInstance *) m_inst;
bool successful = inst->reloadFullVersion();
if(!successful)
{
emitFailed("Failed to load the version description file (version.json). It might be corrupted, missing or simply too new.");
return;
}
QSharedPointer<FullVersion> version = inst->getFullVersion();
// download the right jar, save it in versions/$version/$version.jar
QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/");
urlstr += version->id + "/" + version->id + ".jar";
QString targetstr ("versions/");
targetstr += version->id + "/" + version->id + ".jar";
auto dljob = DownloadJob::create(QUrl(urlstr), targetstr);
jarlibDownloadJob.reset(new JobList());
jarlibDownloadJob->add(dljob);
auto libs = version->getActiveNativeLibs();
libs.append(version->getActiveNormalLibs());
for(auto lib: libs)
{
QString download_path = lib->downloadPath();
QString storage_path = "libraries/" + lib->storagePath();
jarlibDownloadJob->add(DownloadJob::create(download_path, storage_path));
}
connect(jarlibDownloadJob.data(), SIGNAL(finished()), SLOT(jarlibFinished()));
connect(jarlibDownloadJob.data(), SIGNAL(failed()), SLOT(jarlibFailed()));
connect(jarlibDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64)));
download_queue.enqueue(jarlibDownloadJob);
}
void OneSixUpdate::jarlibFinished()
{
emitSucceeded();
}
void OneSixUpdate::jarlibFailed()
{
emitFailed("Failed to download the binary garbage. Try again. Maybe. IF YOU DARE");
}

54
logic/OneSixUpdate.h Normal file
View File

@ -0,0 +1,54 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QList>
#include <QUrl>
#include "net/DownloadJob.h"
#include "tasks/Task.h"
#include "BaseUpdate.h"
class MinecraftVersion;
class BaseInstance;
class OneSixUpdate : public BaseUpdate
{
Q_OBJECT
public:
explicit OneSixUpdate(BaseInstance *inst, QObject *parent = 0);
virtual void executeTask();
private slots:
void versionFileStart();
void versionFileFinished();
void versionFileFailed();
void jarlibStart();
void jarlibFinished();
void jarlibFailed();
private:
JobListPtr specificVersionDownloadJob;
JobListPtr jarlibDownloadJob;
JobListQueue download_queue;
// target version, determined during this task
QSharedPointer<MinecraftVersion> targetVersion;
};

132
logic/OneSixVersion.cpp Normal file
View File

@ -0,0 +1,132 @@
#include "OneSixVersion.h"
RuleAction RuleAction_fromString(QString name)
{
if(name == "allow")
return Allow;
if(name == "disallow")
return Disallow;
return Defer;
}
OpSys OpSys_fromString(QString name)
{
if(name == "linux")
return Os_Linux;
if(name == "windows")
return Os_Windows;
if(name == "osx")
return Os_OSX;
return Os_Other;
}
void Library::finalize()
{
QStringList parts = m_name.split ( ':' );
QString relative = parts[0];
relative.replace ( '.','/' );
relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2];
if ( !m_is_native )
relative += ".jar";
else
{
if ( m_native_suffixes.contains ( currentSystem ) )
{
relative += "-" + m_native_suffixes[currentSystem] + ".jar";
}
else
{
// really, bad.
relative += ".jar";
}
}
m_storage_path = relative;
m_download_path = m_base_url + relative;
if ( m_rules.empty() )
{
m_is_active = true;
}
else
{
RuleAction result = Disallow;
for ( auto rule: m_rules )
{
RuleAction temp = rule->apply ( this );
if ( temp != Defer )
result = temp;
}
m_is_active = ( result == Allow );
}
if ( m_is_native )
{
m_is_active = m_is_active && m_native_suffixes.contains ( currentSystem );
}
}
void Library::setName ( QString name )
{
m_name = name;
}
void Library::setBaseUrl ( QString base_url )
{
m_base_url = base_url;
}
void Library::setIsNative()
{
m_is_native = true;
}
void Library::addNative ( OpSys os, QString suffix )
{
m_is_native = true;
m_native_suffixes[os] = suffix;
}
void Library::setRules ( QList< QSharedPointer< Rule > > rules )
{
m_rules = rules;
}
bool Library::isActive()
{
return m_is_active;
}
bool Library::isNative()
{
return m_is_native;
}
QString Library::downloadPath()
{
return m_download_path;
}
QString Library::storagePath()
{
return m_storage_path;
}
QList<QSharedPointer<Library> > FullVersion::getActiveNormalLibs()
{
QList<QSharedPointer<Library> > output;
for ( auto lib: libraries )
{
if (lib->isActive() && !lib->isNative())
{
output.append(lib);
}
}
return output;
}
QList<QSharedPointer<Library> > FullVersion::getActiveNativeLibs()
{
QList<QSharedPointer<Library> > output;
for ( auto lib: libraries )
{
if (lib->isActive() && lib->isNative())
{
output.append(lib);
}
}
return output;
}

212
logic/OneSixVersion.h Normal file
View File

@ -0,0 +1,212 @@
#pragma once
#include <QtCore>
class Library;
enum OpSys
{
Os_Windows,
Os_Linux,
Os_OSX,
Os_Other
};
OpSys OpSys_fromString(QString);
#ifdef Q_OS_WIN32
#define currentSystem Os_Windows
#elif Q_OS_MAC
#define currentSystem Os_OSX
#else
#define currentSystem Os_Linux
#endif
enum RuleAction
{
Allow,
Disallow,
Defer
};
RuleAction RuleAction_fromString(QString);
class Rule
{
protected:
RuleAction m_result;
virtual bool applies(Library * parent) = 0;
public:
Rule(RuleAction result)
:m_result(result) {}
virtual ~Rule(){};
RuleAction apply(Library * parent)
{
if(applies(parent))
return m_result;
else
return Defer;
};
};
class OsRule : public Rule
{
private:
// the OS
OpSys m_system;
// the OS version regexp
QString m_version_regexp;
protected:
virtual bool applies ( Library* )
{
return (m_system == currentSystem);
}
OsRule(RuleAction result, OpSys system, QString version_regexp)
: Rule(result), m_system(system), m_version_regexp(version_regexp) {}
public:
static QSharedPointer<OsRule> create(RuleAction result, OpSys system, QString version_regexp)
{
return QSharedPointer<OsRule> (new OsRule(result, system, version_regexp));
}
};
class ImplicitRule : public Rule
{
protected:
virtual bool applies ( Library* )
{
return true;
}
ImplicitRule(RuleAction result)
: Rule(result) {}
public:
static QSharedPointer<ImplicitRule> create(RuleAction result)
{
return QSharedPointer<ImplicitRule> (new ImplicitRule(result));
}
};
class Library
{
private:
// basic values used internally (so far)
QString m_name;
QString m_base_url;
QList<QSharedPointer<Rule> > m_rules;
// derived values used for real things
/// where to store the lib locally
QString m_storage_path;
/// where to download the lib from
QString m_download_path;
/// is this lib actually active on the current OS?
bool m_is_active;
/// is the library a native?
bool m_is_native;
/// native suffixes per OS
QMap<OpSys, QString> m_native_suffixes;
public:
QStringList extract_excludes;
public:
/// Constructor
Library(QString name)
{
m_is_native = false;
m_is_native = false;
m_name = name;
m_base_url = "https://s3.amazonaws.com/Minecraft.Download/libraries/";
}
/**
* finalize the library, processing the input values into derived values and state
*
* This SHALL be called after all the values are parsed or after any further change.
*/
void finalize();
/// Set the library composite name
void setName(QString name);
/// Set the url base for downloads
void setBaseUrl(QString base_url);
/// Call this to mark the library as 'native' (it's a zip archive with DLLs)
void setIsNative();
/// Attach a name suffix to the specified OS native
void addNative(OpSys os, QString suffix);
/// Set the load rules
void setRules(QList<QSharedPointer<Rule> > rules);
/// Returns true if the library should be loaded (or extracted, in case of natives)
bool isActive();
/// Returns true if the library is native
bool isNative();
/// Get the URL to download the library from
QString downloadPath();
/// Get the relative path where the library should be saved
QString storagePath();
};
class FullVersion
{
public:
/// the ID - determines which jar to use! ACTUALLY IMPORTANT!
QString id;
/// Last updated time - as a string
QString time;
/// Release time - as a string
QString releaseTime;
/// Release type - "release" or "snapshot"
QString type;
/**
* DEPRECATED: Old versions of the new vanilla launcher used this
* ex: "username_session_version"
*/
QString processArguments;
/**
* 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 minecraftArguments;
/**
* the minimum launcher version required by this version ... current is 4 (at point of writing)
*/
int minimumLauncherVersion;
/**
* The main class to load first
*/
QString mainClass;
/// the list of libs - both active and inactive, native and java
QList<QSharedPointer<Library> > libraries;
/*
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;
public:
FullVersion()
{
minimumLauncherVersion = 0xDEADBEEF;
}
QList<QSharedPointer<Library> > getActiveNormalLibs();
QList<QSharedPointer<Library> > getActiveNativeLibs();
};

195
logic/VersionFactory.cpp Normal file
View File

@ -0,0 +1,195 @@
#include "VersionFactory.h"
#include "OneSixVersion.h"
// Library rules (if any)
QList<QSharedPointer<Rule> > FullVersionFactory::parse4rules(QJsonObject & baseObj)
{
QList<QSharedPointer<Rule> > rules;
auto rulesVal = baseObj.value("rules");
if(rulesVal.isArray())
{
QJsonArray ruleList = rulesVal.toArray();
for(auto ruleVal : ruleList)
{
QSharedPointer<Rule> rule;
if(!ruleVal.isObject())
continue;
auto ruleObj = ruleVal.toObject();
auto actionVal = ruleObj.value("action");
if(!actionVal.isString())
continue;
auto action = RuleAction_fromString(actionVal.toString());
if(action == Defer)
continue;
auto osVal = ruleObj.value("os");
if(!osVal.isObject())
{
// add a new implicit action rule
rules.append(ImplicitRule::create(action));
}
else
{
auto osObj = osVal.toObject();
auto osNameVal = osObj.value("name");
if(!osNameVal.isString())
continue;
OpSys requiredOs = OpSys_fromString(osNameVal.toString());
QString versionRegex = osObj.value("version").toString();
// add a new OS rule
rules.append(OsRule::create(action, requiredOs, versionRegex));
}
}
}
return rules;
}
QSharedPointer<FullVersion> FullVersionFactory::parse4(QJsonObject root, QSharedPointer<FullVersion> fullVersion)
{
fullVersion->id = root.value("id").toString();
fullVersion->mainClass = root.value("mainClass").toString();
auto procArgsValue = root.value("processArguments");
if(procArgsValue.isString())
{
fullVersion->processArguments = procArgsValue.toString();
QString toCompare = fullVersion->processArguments.toLower();
if(toCompare == "legacy")
{
fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}";
}
else if(toCompare == "username_session")
{
fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
}
else if(toCompare == "username_session_version")
{
fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
}
}
auto minecraftArgsValue = root.value("minecraftArguments");
if(minecraftArgsValue.isString())
{
fullVersion->minecraftArguments = minecraftArgsValue.toString();
}
auto minecraftTypeValue = root.value("type");
if(minecraftTypeValue.isString())
{
fullVersion->type = minecraftTypeValue.toString();
}
fullVersion->releaseTime = root.value("releaseTime").toString();
fullVersion->time = root.value("time").toString();
// Iterate through the list, if it's a list.
auto librariesValue = root.value("libraries");
if(!librariesValue.isArray())
return fullVersion;
QJsonArray libList = root.value("libraries").toArray();
for (auto libVal : libList)
{
if (!libVal.isObject())
{
continue;
}
QJsonObject libObj = libVal.toObject();
// Library name
auto nameVal = libObj.value("name");
if(!nameVal.isString())
continue;
QSharedPointer<Library> library(new Library(nameVal.toString()));
auto urlVal = libObj.value("url");
if(urlVal.isString())
{
library->setBaseUrl(urlVal.toString());
}
// Extract excludes (if any)
auto extractVal = libObj.value("extract");
if(extractVal.isObject())
{
QStringList excludes;
auto extractObj = extractVal.toObject();
auto excludesVal = extractObj.value("exclude");
if(!excludesVal.isArray())
goto SKIP_EXTRACTS;
auto excludesList = excludesVal.toArray();
for(auto excludeVal : excludesList)
{
if(excludeVal.isString())
excludes.append(excludeVal.toString());
}
library->extract_excludes = excludes;
}
SKIP_EXTRACTS:
auto nativesVal = libObj.value("natives");
if(nativesVal.isObject())
{
library->setIsNative();
auto nativesObj = nativesVal.toObject();
auto iter = nativesObj.begin();
while(iter != nativesObj.end())
{
auto osType = OpSys_fromString(iter.key());
if(osType == Os_Other)
continue;
if(!iter.value().isString())
continue;
library->addNative(osType, iter.value().toString());
iter++;
}
}
library->setRules(parse4rules(libObj));
library->finalize();
fullVersion->libraries.append(library);
}
return fullVersion;
}
QSharedPointer<FullVersion> FullVersionFactory::parse(QByteArray data)
{
QSharedPointer<FullVersion> readVersion(new FullVersion());
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
error_string = QString( "Error reading version file :") + " " + jsonError.errorString();
m_error = FullVersionFactory::ParseError;
return QSharedPointer<FullVersion>();
}
if(!jsonDoc.isObject())
{
error_string = "Error reading version file.";
m_error = FullVersionFactory::ParseError;
return QSharedPointer<FullVersion>();
}
QJsonObject root = jsonDoc.object();
int launcher_ver = readVersion->minimumLauncherVersion = root.value("minimumLauncherVersion").toDouble();
// ADD MORE HERE :D
if(launcher_ver > 0 && launcher_ver <= 7)
return parse4(root, readVersion);
else
{
error_string = "Version file was for an unrecognized launcher version. RIP";
m_error = FullVersionFactory::UnsupportedVersion;
return QSharedPointer<FullVersion>();
}
}
FullVersionFactory::FullVersionFactory()
{
m_error = FullVersionFactory::AllOK;
}

24
logic/VersionFactory.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <QtCore>
struct FullVersion;
class Rule;
class FullVersionFactory
{
public:
enum Error
{
AllOK, // all parsed OK
ParseError, // the file was corrupted somehow
UnsupportedVersion // the file was meant for a launcher version we don't support (yet)
} m_error;
QString error_string;
public:
FullVersionFactory();
QSharedPointer<FullVersion> parse(QByteArray data);
private:
QSharedPointer<FullVersion> parse4(QJsonObject root, QSharedPointer<FullVersion> product);
QList<QSharedPointer<Rule> > parse4rules(QJsonObject & baseObj);
};

View File

@ -0,0 +1,129 @@
/* 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 "logic/lists/InstVersionList.h"
#include "logic/InstanceVersion.h"
InstVersionList::InstVersionList(QObject *parent) :
QAbstractListModel(parent)
{
}
InstVersionPtr InstVersionList::findVersion( const QString& descriptor )
{
for (int i = 0; i < count(); i++)
{
if (at(i)->descriptor == descriptor)
return at(i);
}
return InstVersionPtr();
}
InstVersionPtr InstVersionList::getLatestStable() const
{
if (count() <= 0)
return InstVersionPtr();
else
return at(0);
}
QVariant InstVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
InstVersionPtr version = at(index.row());
switch (role)
{
case Qt::DisplayRole:
switch (index.column())
{
case NameColumn:
return version->name;
case TypeColumn:
return version->typeString();
case TimeColumn:
return version->timestamp;
default:
return QVariant();
}
case Qt::ToolTipRole:
return version->descriptor;
case VersionPointerRole:
return qVariantFromValue(version);
default:
return QVariant();
}
}
QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
case NameColumn:
return "Name";
case TypeColumn:
return "Type";
case TimeColumn:
return "Time";
default:
return QVariant();
}
case Qt::ToolTipRole:
switch (section)
{
case NameColumn:
return "The name of the version.";
case TypeColumn:
return "The version's type.";
default:
return QVariant();
}
default:
return QVariant();
}
}
int InstVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
return count();
}
int InstVersionList::columnCount(const QModelIndex &parent) const
{
return 2;
}

View File

@ -0,0 +1,121 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QVariant>
#include <QAbstractListModel>
#include <QSharedPointer>
#include "logic/InstanceVersion.h"
class Task;
/*!
* \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 InstVersionList : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles
{
VersionPointerRole = 0x34B1CB48
};
enum VListColumns
{
// First column - Name
NameColumn = 0,
// Second column - Type
TypeColumn,
// Third column - Timestamp
TimeColumn
};
explicit InstVersionList(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 InstVersionPtr 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 QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) 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 InstVersionPtr findVersion(const QString &descriptor);
/*!
* \brief Gets the latest stable version of this instance type.
* This is the version that will be selected by default.
* By default, this is simply the first version in the list.
*/
virtual InstVersionPtr getLatestStable() const;
/*!
* Sorts the version list.
*/
virtual void sort() = 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<InstVersionPtr > versions) = 0;
};

View File

@ -0,0 +1,232 @@
/* 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 <QDir>
#include <QFile>
#include <QDirIterator>
#include <QThread>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include "logic/lists/InstanceList.h"
#include "logic/BaseInstance.h"
#include "logic/InstanceFactory.h"
#include "pathutils.h"
const static int GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(const QString &instDir, QObject *parent) :
QObject(parent), m_instDir("instances")
{
}
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;
QFile groupFile(groupFileName);
// if you can't open the file, fail
if (!groupFile.open(QIODevice::ReadOnly))
{
// An error occurred. Ignore it.
qDebug("Failed to read instance group file.");
return;
}
QTextStream in(&groupFile);
QString jsonStr = in.readAll();
groupFile.close();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qWarning(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;
}
// 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);
m_instances.clear();
QDir dir(m_instDir);
QDirIterator iter(dir);
while (iter.hasNext())
{
QString subDir = iter.next();
if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
continue;
BaseInstance *instPtr = NULL;
auto &loader = InstanceFactory::get();
auto error = loader.loadInstance(instPtr, subDir);
switch(error)
{
case InstanceFactory::NoLoadError:
break;
case InstanceFactory::NotAnInstance:
break;
}
if (error != InstanceFactory::NoLoadError &&
error != InstanceFactory::NotAnInstance)
{
QString errorMsg = QString("Failed to load instance %1: ").
arg(QFileInfo(subDir).baseName()).toUtf8();
switch (error)
{
default:
errorMsg += QString("Unknown instance loader error %1").
arg(error);
break;
}
qDebug(errorMsg.toUtf8());
}
else if (!instPtr)
{
qDebug(QString("Error loading instance %1. Instance loader returned null.").
arg(QFileInfo(subDir).baseName()).toUtf8());
}
else
{
QSharedPointer<BaseInstance> inst(instPtr);
auto iter = groupMap.find(inst->id());
if(iter != groupMap.end())
{
inst->setGroup((*iter));
}
qDebug(QString("Loaded instance %1").arg(inst->name()).toUtf8());
inst->setParent(this);
m_instances.append(inst);
connect(instPtr, SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*)));
}
}
emit invalidated();
return NoError;
}
/// Clear all instances. Triggers notifications.
void InstanceList::clear()
{
m_instances.clear();
emit invalidated();
};
/// Add an instance. Triggers notifications, returns the new index
int InstanceList::add(InstancePtr t)
{
m_instances.append(t);
emit instanceAdded(count() - 1);
return count() - 1;
}
InstancePtr InstanceList::getInstanceById(QString instId)
{
QListIterator<InstancePtr> iter(m_instances);
InstancePtr inst;
while(iter.hasNext())
{
inst = iter.next();
if (inst->id() == instId)
break;
}
if (inst->id() != instId)
return InstancePtr();
else
return iter.peekPrevious();
}
void InstanceList::propertiesChanged(BaseInstance * inst)
{
for(int i = 0; i < m_instances.count(); i++)
{
if(inst == m_instances[i].data())
{
emit instanceChanged(i);
break;
}
}
}

View File

@ -0,0 +1,91 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QSharedPointer>
#include "logic/BaseInstance.h"
class BaseInstance;
class InstanceList : public QObject
{
Q_OBJECT
private:
/*!
* \brief Get the instance groups
*/
void loadGroupList(QMap<QString, QString> & groupList);
public:
explicit InstanceList(const QString &instDir, QObject *parent = 0);
/*!
* \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
};
QString instDir() const { return m_instDir; }
/*!
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
/*!
* \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);
signals:
void instanceAdded(int index);
void instanceChanged(int index);
void invalidated();
private slots:
void propertiesChanged(BaseInstance * inst);
protected:
QString m_instDir;
QList< InstancePtr > m_instances;
};

View File

@ -0,0 +1,206 @@
/* 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 "LwjglVersionList.h"
#include "logic/net/NetWorker.h"
#include <QtNetwork>
#include <QtXml>
#include <QRegExp>
#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss"
LWJGLVersionList mainVersionList;
LWJGLVersionList &LWJGLVersionList::get()
{
return mainVersionList;
}
LWJGLVersionList::LWJGLVersionList(QObject *parent) :
QAbstractListModel(parent)
{
setLoading(false);
}
QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() > count())
return QVariant();
const PtrLWJGLVersion version = at(index.row());
switch (role)
{
case Qt::DisplayRole:
return version->name();
case Qt::ToolTipRole:
return version->url();
default:
return QVariant();
}
}
QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
return "Version";
case Qt::ToolTipRole:
return "LWJGL version name.";
default:
return QVariant();
}
}
int LWJGLVersionList::columnCount(const QModelIndex &parent) const
{
return 1;
}
bool LWJGLVersionList::isLoading() const
{
return m_loading;
}
void LWJGLVersionList::loadList()
{
Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)");
setLoading(true);
auto & worker = NetWorker::spawn();
reply = worker.get(QNetworkRequest(QUrl(RSS_URL)));
connect(reply, SIGNAL(finished()), SLOT(netRequestComplete()));
}
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
{
QDomNodeList elementList = parent.elementsByTagName(tagname);
if (elementList.count())
return elementList.at(0).toElement();
else
return QDomElement();
}
void LWJGLVersionList::netRequestComplete()
{
if (reply->error() == QNetworkReply::NoError)
{
QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip");
Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list",
"LWJGL regex is invalid");
QDomDocument doc;
QString xmlErrorMsg;
int errorLine;
if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine))
{
failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine));
setLoading(false);
return;
}
QDomNodeList items = doc.elementsByTagName("item");
QList<PtrLWJGLVersion> tempList;
for (int i = 0; i < items.length(); i++)
{
Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list",
"XML element isn't an element... wat?");
QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link");
if (linkElement.isNull())
{
qWarning() << "Link element" << i << "in RSS feed doesn't exist! Skipping.";
continue;
}
QString link = linkElement.text();
// Make sure it's a download link.
if (link.endsWith("/download") && link.contains(lwjglRegex))
{
QString name = link.mid(lwjglRegex.indexIn(link) + 6);
// Subtract 4 here to remove the .zip file extension.
name = name.left(lwjglRegex.matchedLength() - 10);
QUrl url(link);
if (!url.isValid())
{
qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping.";
continue;
}
tempList.append(LWJGLVersion::Create(name, link));
}
}
beginResetModel();
m_vlist.swap(tempList);
endResetModel();
qDebug("Loaded LWJGL list.");
finished();
}
else
{
failed("Failed to load LWJGL list. Network error: " + reply->errorString());
}
setLoading(false);
reply->deleteLater();
}
const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName)
{
for (int i = 0; i < count(); i++)
{
QString name = at(i)->name();
if ( name == versionName)
return at(i);
}
return PtrLWJGLVersion();
}
void LWJGLVersionList::failed(QString msg)
{
qWarning() << msg;
emit loadListFailed(msg);
}
void LWJGLVersionList::finished()
{
emit loadListFinished();
}
void LWJGLVersionList::setLoading(bool loading)
{
m_loading = loading;
emit loadingStateUpdated(m_loading);
}

View File

@ -0,0 +1,117 @@
/* 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.
*/
#pragma once
#include <QObject>
#include <QAbstractListModel>
#include <QSharedPointer>
#include <QUrl>
#include <QNetworkReply>
class LWJGLVersion;
typedef QSharedPointer<LWJGLVersion> PtrLWJGLVersion;
class LWJGLVersion : public QObject
{
Q_OBJECT
LWJGLVersion(const QString &name, const QString &url, QObject *parent = 0) :
QObject(parent), m_name(name), m_url(url) { }
public:
static PtrLWJGLVersion Create(const QString &name, const QString &url, QObject *parent = 0)
{
return PtrLWJGLVersion(new LWJGLVersion(name, url, parent));
};
QString name() const { return m_name; }
QString url() const { return m_url; }
protected:
QString m_name;
QString m_url;
};
class LWJGLVersionList : public QAbstractListModel
{
Q_OBJECT
public:
explicit LWJGLVersionList(QObject *parent = 0);
static LWJGLVersionList &get();
bool isLoaded() { return m_vlist.length() > 0; }
const PtrLWJGLVersion getVersion(const QString &versionName);
PtrLWJGLVersion at(int index) { return m_vlist[index]; }
const PtrLWJGLVersion at(int index) const { return m_vlist[index]; }
int count() const { return m_vlist.length(); }
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
virtual int rowCount(const QModelIndex &parent) const { return count(); }
virtual int columnCount(const QModelIndex &parent) const;
virtual bool isLoading() const;
virtual bool errored() const { return m_errored; }
virtual QString lastErrorMsg() const { return m_lastErrorMsg; }
public slots:
/*!
* Loads the version list.
* This is done asynchronously. On success, the loadListFinished() signal will
* be emitted. The list model will be reset as well, resulting in the modelReset()
* signal being emitted. Note that the model will be reset before loadListFinished() is emitted.
* If loading the list failed, the loadListFailed(QString msg),
* signal will be emitted.
*/
virtual void loadList();
signals:
/*!
* Emitted when the list either starts or finishes loading.
* \param loading Whether or not the list is loading.
*/
void loadingStateUpdated(bool loading);
void loadListFinished();
void loadListFailed(QString msg);
private:
QList<PtrLWJGLVersion> m_vlist;
QNetworkReply *m_netReply;
QNetworkReply *reply;
bool m_loading;
bool m_errored;
QString m_lastErrorMsg;
void failed(QString msg);
void finished();
void setLoading(bool loading);
private slots:
virtual void netRequestComplete();
};

View File

@ -0,0 +1,300 @@
/* Copyright 2013 Andrew Okin
*
* 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 "MinecraftVersionList.h"
#include <logic/net/NetWorker.h>
#include <QDebug>
#include <QtXml>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonParseError>
#include <QtAlgorithms>
#include <QtNetwork>
#define MCVLIST_URLBASE "http://s3.amazonaws.com/Minecraft.Download/versions/"
#define ASSETS_URLBASE "http://assets.minecraft.net/"
#define MCN_URLBASE "http://sonicrules.org/mcnweb.py"
MinecraftVersionList mcVList;
MinecraftVersionList::MinecraftVersionList(QObject *parent) :
InstVersionList(parent)
{
}
Task *MinecraftVersionList::getLoadTask()
{
return new MCVListLoadTask(this);
}
bool MinecraftVersionList::isLoaded()
{
return m_loaded;
}
const InstVersionPtr MinecraftVersionList::at(int i) const
{
return m_vlist.at(i);
}
int MinecraftVersionList::count() const
{
return m_vlist.count();
}
bool cmpVersions(InstVersionPtr first, InstVersionPtr second)
{
const InstVersion & left = *first;
const InstVersion & right = *second;
return left > right;
}
void MinecraftVersionList::sort()
{
beginResetModel();
qSort(m_vlist.begin(), m_vlist.end(), cmpVersions);
endResetModel();
}
InstVersionPtr MinecraftVersionList::getLatestStable() const
{
for (int i = 0; i < m_vlist.length(); i++)
{
auto ver = m_vlist.at(i).dynamicCast<MinecraftVersion>();
if (ver->is_latest && !ver->is_snapshot)
{
return m_vlist.at(i);
}
}
return InstVersionPtr();
}
MinecraftVersionList &MinecraftVersionList::getMainList()
{
return mcVList;
}
void MinecraftVersionList::updateListData(QList<InstVersionPtr > versions)
{
beginResetModel();
m_vlist = versions;
m_loaded = true;
endResetModel();
// NOW SORT!!
sort();
}
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
{
QDomNodeList elementList = parent.elementsByTagName(tagname);
if (elementList.count())
return elementList.at(0).toElement();
else
return QDomElement();
}
inline QDateTime timeFromS3Time(QString str)
{
return QDateTime::fromString(str, Qt::ISODate);
}
MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist)
{
m_list = vlist;
m_currentStable = NULL;
vlistReply = nullptr;
legacyWhitelist.insert("1.5.2");
legacyWhitelist.insert("1.5.1");
legacyWhitelist.insert("1.5");
legacyWhitelist.insert("1.4.7");
legacyWhitelist.insert("1.4.6");
legacyWhitelist.insert("1.4.5");
legacyWhitelist.insert("1.4.4");
legacyWhitelist.insert("1.4.2");
legacyWhitelist.insert("1.3.2");
legacyWhitelist.insert("1.3.1");
legacyWhitelist.insert("1.2.5");
legacyWhitelist.insert("1.2.4");
legacyWhitelist.insert("1.2.3");
legacyWhitelist.insert("1.2.2");
legacyWhitelist.insert("1.2.1");
legacyWhitelist.insert("1.1");
legacyWhitelist.insert("1.0.1");
legacyWhitelist.insert("1.0");
}
MCVListLoadTask::~MCVListLoadTask()
{
}
void MCVListLoadTask::executeTask()
{
setStatus("Loading instance version list...");
auto & worker = NetWorker::spawn();
vlistReply = worker.get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + "versions.json")));
connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded()));
}
void MCVListLoadTask::list_downloaded()
{
if(vlistReply->error() != QNetworkReply::QNetworkReply::NoError)
{
vlistReply->deleteLater();
emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString());
return;
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError);
vlistReply->deleteLater();
if (jsonError.error != QJsonParseError::NoError)
{
emitFailed("Error parsing version list JSON:" + jsonError.errorString());
return;
}
if(!jsonDoc.isObject())
{
emitFailed("Error parsing version list JSON: jsonDoc is not an object");
return;
}
QJsonObject root = jsonDoc.object();
// Get the ID of the latest release and the latest snapshot.
if(!root.value("latest").isObject())
{
emitFailed("Error parsing version list JSON: version list is missing 'latest' object");
return;
}
QJsonObject latest = root.value("latest").toObject();
QString latestReleaseID = latest.value("release").toString("");
QString latestSnapshotID = latest.value("snapshot").toString("");
if(latestReleaseID.isEmpty())
{
emitFailed("Error parsing version list JSON: latest release field is missing");
return;
}
if(latestSnapshotID.isEmpty())
{
emitFailed("Error parsing version list JSON: latest snapshot field is missing");
return;
}
// Now, get the array of versions.
if(!root.value("versions").isArray())
{
emitFailed("Error parsing version list JSON: version list object is missing 'versions' array");
return;
}
QJsonArray versions = root.value("versions").toArray();
QList<InstVersionPtr > tempList;
for (int i = 0; i < versions.count(); i++)
{
bool is_snapshot = false;
bool is_latest = false;
// Load the version info.
if(!versions[i].isObject())
{
//FIXME: log this somewhere
continue;
}
QJsonObject version = versions[i].toObject();
QString versionID = version.value("id").toString("");
QString versionTimeStr = version.value("releaseTime").toString("");
QString versionTypeStr = version.value("type").toString("");
if(versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty())
{
//FIXME: log this somewhere
continue;
}
// Parse the timestamp.
QDateTime versionTime = timeFromS3Time(versionTimeStr);
if(!versionTime.isValid())
{
//FIXME: log this somewhere
continue;
}
// Parse the type.
MinecraftVersion::VersionType versionType;
// OneSix or Legacy. use filter to determine type
if (versionTypeStr == "release")
{
versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix;
is_latest = (versionID == latestReleaseID);
is_snapshot = false;
}
else if(versionTypeStr == "snapshot") // It's a snapshot... yay
{
versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix;
is_latest = (versionID == latestSnapshotID);
is_snapshot = true;
}
else if(versionTypeStr == "old_alpha")
{
versionType = MinecraftVersion::Nostalgia;
is_latest = false;
is_snapshot = false;
}
else if(versionTypeStr == "old_beta")
{
versionType = MinecraftVersion::Legacy;
is_latest = false;
is_snapshot = false;
}
else
{
//FIXME: log this somewhere
continue;
}
// Get the download URL.
QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/";
// Now, we construct the version object and add it to the list.
QSharedPointer<MinecraftVersion> mcVersion(new MinecraftVersion());
mcVersion->name = mcVersion->descriptor = versionID;
mcVersion->timestamp = versionTime.toMSecsSinceEpoch();
mcVersion->download_url = dlUrl;
mcVersion->is_latest = is_latest;
mcVersion->is_snapshot = is_snapshot;
mcVersion->type = versionType;
tempList.append(mcVersion);
}
m_list->updateListData(tempList);
emitSucceeded();
return;
}
// FIXME: we should have a local cache of the version list and a local cache of version data
bool MCVListLoadTask::loadFromVList()
{
}

View File

@ -0,0 +1,83 @@
/* Copyright 2013 Andrew Okin
*
* 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 <QSharedPointer>
#include "InstVersionList.h"
#include "logic/tasks/Task.h"
#include "logic/MinecraftVersion.h"
class MCVListLoadTask;
class QNetworkReply;
class MinecraftVersionList : public InstVersionList
{
Q_OBJECT
public:
friend class MCVListLoadTask;
explicit MinecraftVersionList(QObject *parent = 0);
virtual Task *getLoadTask();
virtual bool isLoaded();
virtual const InstVersionPtr at(int i) const;
virtual int count() const;
virtual void sort();
virtual InstVersionPtr getLatestStable() const;
/*!
* Gets the main version list instance.
*/
static MinecraftVersionList &getMainList();
protected:
QList<InstVersionPtr > m_vlist;
bool m_loaded;
protected slots:
virtual void updateListData(QList<InstVersionPtr > versions);
};
class MCVListLoadTask : public Task
{
Q_OBJECT
public:
explicit MCVListLoadTask(MinecraftVersionList *vlist);
~MCVListLoadTask();
virtual void executeTask();
protected slots:
void list_downloaded();
protected:
//! Loads versions from Mojang's official version list.
bool loadFromVList();
QNetworkReply *vlistReply;
MinecraftVersionList *m_list;
MinecraftVersion *m_currentStable;
QSet<QString> legacyWhitelist;
};

138
logic/net/DownloadJob.cpp Normal file
View File

@ -0,0 +1,138 @@
#include "DownloadJob.h"
#include "pathutils.h"
#include "NetWorker.h"
DownloadJob::DownloadJob (QUrl url,
QString target_path,
QString expected_md5 )
:Job()
{
m_url = url;
m_target_path = target_path;
m_expected_md5 = expected_md5;
m_check_md5 = m_expected_md5.size();
m_save_to_file = m_target_path.size();
m_status = Job_NotStarted;
m_opened_for_saving = false;
}
JobPtr DownloadJob::create (QUrl url,
QString target_path,
QString expected_md5 )
{
return JobPtr ( new DownloadJob ( url, target_path, expected_md5 ) );
}
void DownloadJob::start()
{
if ( m_save_to_file )
{
QString filename = m_target_path;
m_output_file.setFileName ( filename );
// if there already is a file and md5 checking is in effect and it can be opened
if ( m_output_file.exists() && m_output_file.open ( QIODevice::ReadOnly ) )
{
// check the md5 against the expected one
QString hash = QCryptographicHash::hash ( m_output_file.readAll(), QCryptographicHash::Md5 ).toHex().constData();
m_output_file.close();
// skip this file if they match
if ( m_check_md5 && hash == m_expected_md5 )
{
qDebug() << "Skipping " << m_url.toString() << ": md5 match.";
emit finish();
return;
}
else
{
m_expected_md5 = hash;
}
}
if(!ensurePathExists(filename))
{
emit fail();
return;
}
}
qDebug() << "Downloading " << m_url.toString();
QNetworkRequest request ( m_url );
request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1());
auto &worker = NetWorker::spawn();
QNetworkReply * rep = worker.get ( request );
m_reply = QSharedPointer<QNetworkReply> ( rep, &QObject::deleteLater );
connect ( rep, SIGNAL ( downloadProgress ( qint64,qint64 ) ), SLOT ( downloadProgress ( qint64,qint64 ) ) );
connect ( rep, SIGNAL ( finished() ), SLOT ( downloadFinished() ) );
connect ( rep, SIGNAL ( error ( QNetworkReply::NetworkError ) ), SLOT ( downloadError ( QNetworkReply::NetworkError ) ) );
connect ( rep, SIGNAL ( readyRead() ), SLOT ( downloadReadyRead() ) );
}
void DownloadJob::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal )
{
emit progress ( bytesReceived, bytesTotal );
}
void DownloadJob::downloadError ( QNetworkReply::NetworkError error )
{
// error happened during download.
// TODO: log the reason why
m_status = Job_Failed;
}
void DownloadJob::downloadFinished()
{
// if the download succeeded
if ( m_status != Job_Failed )
{
// nothing went wrong...
m_status = Job_Finished;
// save the data to the downloadable if we aren't saving to file
if ( !m_save_to_file )
{
m_data = m_reply->readAll();
}
else
{
m_output_file.close();
}
//TODO: check md5 here!
m_reply.clear();
emit finish();
return;
}
// else the download failed
else
{
if ( m_save_to_file )
{
m_output_file.close();
m_output_file.remove();
}
m_reply.clear();
emit fail();
return;
}
}
void DownloadJob::downloadReadyRead()
{
if( m_save_to_file )
{
if(!m_opened_for_saving)
{
if ( !m_output_file.open ( QIODevice::WriteOnly ) )
{
/*
* Can't open the file... the job failed
*/
m_reply->abort();
emit fail();
return;
}
m_opened_for_saving = true;
}
m_output_file.write ( m_reply->readAll() );
}
}

51
logic/net/DownloadJob.h Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include "JobQueue.h"
#include <QtNetwork>
/**
* A single file for the downloader/cache to process.
*/
class LIBUTIL_EXPORT DownloadJob : public Job
{
Q_OBJECT
public:
DownloadJob(QUrl url,
QString rel_target_path = QString(),
QString expected_md5 = QString()
);
static JobPtr create(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString());
public slots:
virtual void start();
private slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);;
void downloadError(QNetworkReply::NetworkError error);
void downloadFinished();
void downloadReadyRead();
public:
/// the network reply
QSharedPointer<QNetworkReply> m_reply;
/// source URL
QUrl m_url;
/// if true, check the md5sum against a provided md5sum
/// also, if a file exists, perform an md5sum first and don't download only if they don't match
bool m_check_md5;
/// the expected md5 checksum
QString m_expected_md5;
/// save to file?
bool m_save_to_file;
/// is the saving file already open?
bool m_opened_for_saving;
/// if saving to file, use the one specified in this string
QString m_target_path;
/// this is the output file, if any
QFile m_output_file;
/// if not saving to file, downloaded data is placed here
QByteArray m_data;
/// The file's status
JobStatus m_status;
};

180
logic/net/JobQueue.h Normal file
View File

@ -0,0 +1,180 @@
#pragma once
#include <QtCore>
#include "libutil_config.h"
enum JobStatus
{
Job_NotStarted,
Job_InProgress,
Job_Finished,
Job_Failed
};
class JobList;
class LIBUTIL_EXPORT Job : public QObject
{
Q_OBJECT
protected:
explicit Job(): QObject(0){};
public:
virtual ~Job() {};
signals:
void finish();
void fail();
void progress(qint64 current, qint64 total);
public slots:
virtual void start() = 0;
};
typedef QSharedPointer<Job> JobPtr;
/**
* A list of jobs, to be processed one by one.
*/
class LIBUTIL_EXPORT JobList : public QObject
{
friend class JobListQueue;
Q_OBJECT
public:
JobList() : QObject(0)
{
m_status = Job_NotStarted;
current_job_idx = 0;
}
JobStatus getStatus()
{
return m_status;
}
void add(JobPtr dlable)
{
if(m_status == Job_NotStarted)
m_jobs.append(dlable);
//else there's a bug. TODO: catch the bugs
}
JobPtr getFirstJob()
{
if(m_jobs.size())
return m_jobs[0];
else
return JobPtr();
}
void start()
{
current_job_idx = 0;
auto job = m_jobs[current_job_idx];
connect(job.data(), SIGNAL(progress(qint64,qint64)), SLOT(currentJobProgress(qint64,qint64)));
connect(job.data(), SIGNAL(finish()), SLOT(currentJobFinished()));
connect(job.data(), SIGNAL(fail()), SLOT(currentJobFailed()));
job->start();
emit started();
}
private slots:
void currentJobFinished()
{
if(current_job_idx == m_jobs.size() - 1)
{
m_status = Job_Finished;
emit finished();
}
else
{
current_job_idx++;
auto job = m_jobs[current_job_idx];
connect(job.data(), SIGNAL(progress(qint64,qint64)), SLOT(currentJobProgress(qint64,qint64)));
connect(job.data(), SIGNAL(finish()), SLOT(currentJobFinished()));
connect(job.data(), SIGNAL(fail()), SLOT(currentJobFailed()));
job->start();
}
}
void currentJobFailed()
{
m_status = Job_Failed;
emit failed();
}
void currentJobProgress(qint64 current, qint64 total)
{
if(!total)
return;
int total_jobs = m_jobs.size();
if(!total_jobs)
return;
float job_chunk = 1000.0 / float(total_jobs);
float cur = current;
float tot = total;
float last_chunk = (cur / tot) * job_chunk;
float list_total = job_chunk * current_job_idx + last_chunk;
emit progress(qint64(list_total), 1000LL);
}
private:
QVector<JobPtr> m_jobs;
/// The overall status of this job list
JobStatus m_status;
int current_job_idx;
signals:
void progress(qint64 current, qint64 total);
void started();
void finished();
void failed();
};
typedef QSharedPointer<JobList> JobListPtr;
/**
* A queue of job lists! The job lists fail or finish as units.
*/
class LIBUTIL_EXPORT JobListQueue : public QObject
{
Q_OBJECT
public:
JobListQueue(QObject *p = 0):
QObject(p),
currentIndex(0),
is_running(false){}
void enqueue(JobListPtr job)
{
jobs.enqueue(job);
// finish or fail, we should catch that and start the next one
connect(job.data(),SIGNAL(finished()), SLOT(startNextJob()));
connect(job.data(),SIGNAL(failed()), SLOT(startNextJob()));
if(!is_running)
{
QTimer::singleShot(0, this, SLOT(startNextJob()));
}
}
private slots:
void startNextJob()
{
if (jobs.isEmpty())
{
currentJobList.clear();
currentIndex = 0;
is_running = false;
emit finishedAllJobs();
return;
}
currentJobList = jobs.dequeue();
is_running = true;
currentIndex = 0;
currentJobList->start();
}
signals:
void finishedAllJobs();
private:
JobListPtr currentJobList;
QQueue<JobListPtr> jobs;
unsigned currentIndex;
bool is_running;
};

12
logic/net/NetWorker.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "NetWorker.h"
#include <QThreadStorage>
NetWorker& NetWorker::spawn()
{
static QThreadStorage<NetWorker *> storage;
if (!storage.hasLocalData())
{
storage.setLocalData(new NetWorker());
}
return *storage.localData();
}

20
logic/net/NetWorker.h Normal file
View File

@ -0,0 +1,20 @@
/*
_.ooo-._
.OOOP _ '.
dOOOO (_) \
OOOOOb |
OOOOOOb. |
OOOOOOOOb |
YOO(_)OOO /
'OOOOOY _.'
'""""''
*/
#pragma once
#include <QNetworkAccessManager>
class NetWorker : public QNetworkAccessManager
{
Q_OBJECT
public:
static NetWorker &spawn();
};

111
logic/tasks/LoginTask.cpp Normal file
View File

@ -0,0 +1,111 @@
/* 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 "LoginTask.h"
#include "logic/net/NetWorker.h"
#include <QStringList>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QUrlQuery>
LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : Task(parent), uInfo(uInfo){}
void LoginTask::executeTask()
{
setStatus("Logging in...");
auto & worker = NetWorker::spawn();
connect(&worker, SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*)));
QUrl loginURL("https://login.minecraft.net/");
QNetworkRequest netRequest(loginURL);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery params;
params.addQueryItem("user", uInfo.username);
params.addQueryItem("password", uInfo.password);
params.addQueryItem("version", "13");
netReply = worker.post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8());
}
void LoginTask::processNetReply(QNetworkReply *reply)
{
if(netReply != reply)
return;
// Check for errors.
switch (reply->error())
{
case QNetworkReply::NoError:
{
// Check the response code.
int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (responseCode == 200)
{
QString responseStr(reply->readAll());
QStringList strings = responseStr.split(":");
if (strings.count() >= 4)
{
bool parseSuccess;
qint64 latestVersion = strings[0].toLongLong(&parseSuccess);
if (parseSuccess)
{
// strings[1] is the download ticket. It isn't used anymore.
QString username = strings[2];
QString sessionID = strings[3];
result = {username, sessionID, latestVersion};
emitSucceeded();
}
else
{
emitFailed("Failed to parse Minecraft version string.");
}
}
else
{
if (responseStr.toLower() == "bad login")
emitFailed("Invalid username or password.");
else if (responseStr.toLower() == "old version")
emitFailed("Launcher outdated, please update.");
else
emitFailed("Login failed: " + responseStr);
}
}
else if (responseCode == 503)
{
emitFailed("The login servers are currently unavailable. Check http://help.mojang.com/ for more info.");
}
else
{
emitFailed(QString("Login failed: Unknown HTTP error %1 occurred.").arg(QString::number(responseCode)));
}
break;
}
case QNetworkReply::OperationCanceledError:
emitFailed("Login canceled.");
break;
default:
emitFailed("Login failed: " + reply->errorString());
break;
}
}

58
logic/tasks/LoginTask.h Normal file
View File

@ -0,0 +1,58 @@
/* 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.
*/
#ifndef LOGINTASK_H
#define LOGINTASK_H
#include "Task.h"
#include <QSharedPointer>
struct UserInfo
{
QString username;
QString password;
};
struct LoginResponse
{
QString username;
QString sessionID;
qint64 latestVersion;
};
class QNetworkReply;
class LoginTask : public Task
{
Q_OBJECT
public:
explicit LoginTask(const UserInfo& uInfo, QObject *parent = 0);
LoginResponse getResult()
{
return result;
};
protected slots:
void processNetReply(QNetworkReply* reply);
protected:
void executeTask();
LoginResponse result;
QNetworkReply* netReply;
UserInfo uInfo;
};
#endif // LOGINTASK_H

85
logic/tasks/Task.cpp Normal file
View File

@ -0,0 +1,85 @@
/* 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 "Task.h"
Task::Task(QObject *parent) :
QObject(parent)
{
}
QString Task::getStatus() const
{
return status;
}
void Task::setStatus(const QString &status)
{
this->status = status;
emitStatusChange(status);
}
int Task::getProgress() const
{
return progress;
}
void Task::setProgress(int progress)
{
this->progress = progress;
emitProgressChange(progress);
}
void Task::startTask()
{
emitStarted();
executeTask();
}
void Task::emitStarted()
{
running = true;
emit started();
}
void Task::emitFailed(QString reason)
{
running = false;
emit failed(reason);
}
void Task::emitSucceeded()
{
running = false;
emit succeeded();
}
bool Task::isRunning() const
{
return running;
}
void Task::emitStatusChange(const QString &status)
{
emit statusChanged(status);
}
void Task::emitProgressChange(int progress)
{
emit progressChanged(progress);
}

65
logic/tasks/Task.h Normal file
View File

@ -0,0 +1,65 @@
/* 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.
*/
#ifndef TASK_H
#define TASK_H
#include <QObject>
#include <QString>
class Task : public QObject
{
Q_OBJECT
public:
explicit Task(QObject *parent = 0);
QString getStatus() const;
int getProgress() const;
bool isRunning() const;
public slots:
void startTask();
protected slots:
void setStatus(const QString& status);
void setProgress(int progress);
signals:
void started();
void failed(QString reason);
void succeeded();
void statusChanged(Task* task, const QString& status);
void progressChanged(Task* task, int progress);
void statusChanged(const QString& status);
void progressChanged(int progress);
protected:
virtual void executeTask() = 0;
virtual void emitStarted();
virtual void emitFailed(QString reason);
virtual void emitSucceeded();
virtual void emitStatusChange(const QString &status);
virtual void emitProgressChange(int progress);
QString status;
int progress;
bool running = false;
};
#endif // TASK_H