All of the broken legacy things work.
This commit is contained in:
		| @@ -479,6 +479,8 @@ SET(MULTIMC_SOURCES | |||||||
| 	logic/minecraft/OneSixRule.h | 	logic/minecraft/OneSixRule.h | ||||||
| 	logic/minecraft/OpSys.cpp | 	logic/minecraft/OpSys.cpp | ||||||
| 	logic/minecraft/OpSys.h | 	logic/minecraft/OpSys.h | ||||||
|  | 	logic/minecraft/ParseUtils.cpp | ||||||
|  | 	logic/minecraft/ParseUtils.h | ||||||
| 	logic/minecraft/RawLibrary.cpp | 	logic/minecraft/RawLibrary.cpp | ||||||
| 	logic/minecraft/RawLibrary.h | 	logic/minecraft/RawLibrary.h | ||||||
| 	logic/minecraft/VersionBuilder.cpp | 	logic/minecraft/VersionBuilder.cpp | ||||||
| @@ -489,10 +491,6 @@ SET(MULTIMC_SOURCES | |||||||
| 	logic/minecraft/VersionFinal.h | 	logic/minecraft/VersionFinal.h | ||||||
| 	logic/minecraft/VersionPatch.h | 	logic/minecraft/VersionPatch.h | ||||||
|  |  | ||||||
| 	# Trivial operating system utilities |  | ||||||
| 	logic/minecraft/OpSys.h |  | ||||||
| 	logic/minecraft/OpSys.cpp |  | ||||||
|  |  | ||||||
| 	# Various base classes | 	# Various base classes | ||||||
| 	logic/BaseInstaller.h | 	logic/BaseInstaller.h | ||||||
| 	logic/BaseInstaller.cpp | 	logic/BaseInstaller.cpp | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ import java.awt.BorderLayout; | |||||||
| import java.awt.Graphics; | import java.awt.Graphics; | ||||||
| import java.applet.Applet; | import java.applet.Applet; | ||||||
| import java.applet.AppletStub; | import java.applet.AppletStub; | ||||||
|  | import java.net.MalformedURLException; | ||||||
|  |  | ||||||
| public class Launcher extends Applet implements AppletStub | public class Launcher extends Applet implements AppletStub | ||||||
| { | { | ||||||
| @@ -130,13 +131,23 @@ public class Launcher extends Applet implements AppletStub | |||||||
|  |  | ||||||
| 	@Override | 	@Override | ||||||
| 	public URL getCodeBase() { | 	public URL getCodeBase() { | ||||||
| 		return wrappedApplet.getCodeBase(); | 		try { | ||||||
|  | 			return new URL("http://www.minecraft.net/game/"); | ||||||
|  | 		} catch (MalformedURLException e) { | ||||||
|  | 			e.printStackTrace(); | ||||||
|  | 		} | ||||||
|  | 		return null; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@Override | 	@Override | ||||||
| 	public URL getDocumentBase() | 	public URL getDocumentBase() | ||||||
| 	{ | 	{ | ||||||
| 		return documentBase; | 		try { | ||||||
|  | 			return new URL("http://www.minecraft.net/game/"); | ||||||
|  | 		} catch (MalformedURLException e) { | ||||||
|  | 			e.printStackTrace(); | ||||||
|  | 		} | ||||||
|  | 		return null; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@Override | 	@Override | ||||||
|   | |||||||
| @@ -53,7 +53,7 @@ public class OneSixLauncher implements Launcher | |||||||
| 	{ | 	{ | ||||||
| 		libraries = params.all("cp"); | 		libraries = params.all("cp"); | ||||||
| 		extlibs = params.all("ext"); | 		extlibs = params.all("ext"); | ||||||
| 		mcparams = params.all("param"); | 		mcparams = params.allSafe("param", new ArrayList<String>() ); | ||||||
| 		mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); | 		mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); | ||||||
| 		appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); | 		appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); | ||||||
| 		mods = params.allSafe("mods", new ArrayList<String>()); | 		mods = params.allSafe("mods", new ArrayList<String>()); | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ NewInstanceDialog::NewInstanceDialog(QWidget *parent) | |||||||
| 		taskDlg->exec(loadTask); | 		taskDlg->exec(loadTask); | ||||||
| 	} | 	} | ||||||
| 	*/ | 	*/ | ||||||
| 	setSelectedVersion(MMC->minecraftlist()->getLatestStable()); | 	setSelectedVersion(MMC->minecraftlist()->getLatestStable(), true); | ||||||
| 	InstIconKey = "infinity"; | 	InstIconKey = "infinity"; | ||||||
| 	ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); | 	ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); | ||||||
| } | } | ||||||
| @@ -63,13 +63,17 @@ void NewInstanceDialog::updateDialogState() | |||||||
| 		->setEnabled(!instName().isEmpty() && m_selectedVersion); | 		->setEnabled(!instName().isEmpty() && m_selectedVersion); | ||||||
| } | } | ||||||
|  |  | ||||||
| void NewInstanceDialog::setSelectedVersion(BaseVersionPtr version) | void NewInstanceDialog::setSelectedVersion(BaseVersionPtr version, bool initial) | ||||||
| { | { | ||||||
| 	m_selectedVersion = version; | 	m_selectedVersion = version; | ||||||
|  |  | ||||||
| 	if (m_selectedVersion) | 	if (m_selectedVersion) | ||||||
| 	{ | 	{ | ||||||
| 		ui->versionTextBox->setText(version->name()); | 		ui->versionTextBox->setText(version->name()); | ||||||
|  | 		if(ui->instNameTextBox->text().isEmpty() && !initial) | ||||||
|  | 		{ | ||||||
|  | 			ui->instNameTextBox->setText(version->name()); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ public: | |||||||
|  |  | ||||||
| 	void updateDialogState(); | 	void updateDialogState(); | ||||||
|  |  | ||||||
| 	void setSelectedVersion(BaseVersionPtr version); | 	void setSelectedVersion(BaseVersionPtr version, bool initial = false); | ||||||
|  |  | ||||||
| 	void loadVersionList(); | 	void loadVersionList(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -224,7 +224,10 @@ bool OneSixInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScr | |||||||
| 		} | 		} | ||||||
| 		launchScript += "cp " + versionsPath().absoluteFilePath(minecraftjarpath) + "\n"; | 		launchScript += "cp " + versionsPath().absoluteFilePath(minecraftjarpath) + "\n"; | ||||||
| 	} | 	} | ||||||
| 	launchScript += "mainClass " + version->mainClass + "\n"; | 	if(!version->mainClass.isEmpty()) | ||||||
|  | 	{ | ||||||
|  | 		launchScript += "mainClass " + version->mainClass + "\n"; | ||||||
|  | 	} | ||||||
| 	if(!version->appletClass.isEmpty()) | 	if(!version->appletClass.isEmpty()) | ||||||
| 	{ | 	{ | ||||||
| 		launchScript += "appletClass " + version->appletClass + "\n"; | 		launchScript += "appletClass " + version->appletClass + "\n"; | ||||||
|   | |||||||
| @@ -62,12 +62,14 @@ void OneSixUpdate::executeTask() | |||||||
| 			emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); | 			emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		versionFileStart(); | 		// builtins need no updates, so only update for Mojang | ||||||
| 	} | 		if(targetVersion->m_versionSource == MinecraftVersion::Mojang) | ||||||
| 	else | 		{ | ||||||
| 	{ | 			versionFileStart(); | ||||||
| 		jarlibStart(); | 			return; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  | 	jarlibStart(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void OneSixUpdate::versionFileStart() | void OneSixUpdate::versionFileStart() | ||||||
|   | |||||||
| @@ -1,2 +1,95 @@ | |||||||
| #include "MinecraftVersion.h" | #include "MinecraftVersion.h" | ||||||
|  | #include "VersionFinal.h" | ||||||
|  |  | ||||||
|  | bool MinecraftVersion::usesLegacyLauncher() | ||||||
|  | { | ||||||
|  | 	return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); | ||||||
|  | } | ||||||
|  | QString MinecraftVersion::descriptor() | ||||||
|  | { | ||||||
|  | 	return m_descriptor; | ||||||
|  | } | ||||||
|  | QString MinecraftVersion::name() | ||||||
|  | { | ||||||
|  | 	return m_name; | ||||||
|  | } | ||||||
|  | QString MinecraftVersion::typeString() const | ||||||
|  | { | ||||||
|  | 	if (is_latest && is_snapshot) | ||||||
|  | 	{ | ||||||
|  | 		return QObject::tr("Latest snapshot"); | ||||||
|  | 	} | ||||||
|  | 	else if (is_latest) | ||||||
|  | 	{ | ||||||
|  | 		return QObject::tr("Latest release"); | ||||||
|  | 	} | ||||||
|  | 	else if (is_snapshot) | ||||||
|  | 	{ | ||||||
|  | 		return QObject::tr("Snapshot"); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		return QObject::tr("Regular release"); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | bool MinecraftVersion::hasJarMods() | ||||||
|  | { | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | bool MinecraftVersion::isVanilla() | ||||||
|  | { | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MinecraftVersion::applyTo(VersionFinal *version) | ||||||
|  | { | ||||||
|  | 	// FIXME: make this work. | ||||||
|  | 	if(m_versionSource != Builtin) | ||||||
|  | 	{ | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	if (!m_descriptor.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->id = m_descriptor; | ||||||
|  | 	} | ||||||
|  | 	if (!m_mainClass.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->mainClass = m_mainClass; | ||||||
|  | 	} | ||||||
|  | 	if (!m_appletClass.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->appletClass = m_appletClass; | ||||||
|  | 	} | ||||||
|  | 	if (!m_processArguments.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->vanillaProcessArguments = m_processArguments; | ||||||
|  | 		version->processArguments = m_processArguments; | ||||||
|  | 	} | ||||||
|  | 	if (!m_type.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->type = m_type; | ||||||
|  | 	} | ||||||
|  | 	if (!m_releaseTimeString.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->m_releaseTimeString = m_releaseTimeString; | ||||||
|  | 		version->m_releaseTime = m_releaseTime; | ||||||
|  | 	} | ||||||
|  | 	if (!m_updateTimeString.isNull()) | ||||||
|  | 	{ | ||||||
|  | 		version->m_updateTimeString = m_updateTimeString; | ||||||
|  | 		version->m_updateTime = m_updateTime; | ||||||
|  | 	} | ||||||
|  | 	version->traits.unite(m_traits); | ||||||
|  | } | ||||||
|  | int MinecraftVersion::getOrder() | ||||||
|  | { | ||||||
|  | 	return order; | ||||||
|  | } | ||||||
|  | void MinecraftVersion::setOrder(int order) | ||||||
|  | { | ||||||
|  | 	this->order = order; | ||||||
|  | } | ||||||
|  | QList<JarmodPtr> MinecraftVersion::getJarMods() | ||||||
|  | { | ||||||
|  | 	return QList<JarmodPtr>(); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,16 +15,18 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "logic/BaseVersion.h" |  | ||||||
| #include "VersionPatch.h" |  | ||||||
| #include <QStringList> | #include <QStringList> | ||||||
| #include <QSet> | #include <QSet> | ||||||
|  | #include <QDateTime> | ||||||
|  |  | ||||||
|  | #include "logic/BaseVersion.h" | ||||||
|  | #include "VersionPatch.h" | ||||||
|  | #include "VersionFile.h" | ||||||
|  |  | ||||||
|  | class VersionFinal; | ||||||
|  |  | ||||||
| struct MinecraftVersion : public BaseVersion, public VersionPatch | struct MinecraftVersion : public BaseVersion, public VersionPatch | ||||||
| { | { | ||||||
| 	/// The version's timestamp - this is primarily used for sorting versions in a list. |  | ||||||
| 	qint64 timestamp; |  | ||||||
|  |  | ||||||
| 	/// The URL that this version will be downloaded from. maybe. | 	/// The URL that this version will be downloaded from. maybe. | ||||||
| 	QString download_url; | 	QString download_url; | ||||||
|  |  | ||||||
| @@ -34,16 +36,20 @@ struct MinecraftVersion : public BaseVersion, public VersionPatch | |||||||
| 	/// is this a snapshot? | 	/// is this a snapshot? | ||||||
| 	bool is_snapshot = false; | 	bool is_snapshot = false; | ||||||
|  |  | ||||||
| 	/// is this a built-in version that comes with MultiMC? | 	/// where is this from? | ||||||
| 	bool is_builtin = false; | 	enum VersionSource | ||||||
| 	 | 	{ | ||||||
|  | 		Builtin, | ||||||
|  | 		Mojang | ||||||
|  | 	} m_versionSource = Builtin; | ||||||
|  |  | ||||||
| 	/// the human readable version name | 	/// the human readable version name | ||||||
| 	QString m_name; | 	QString m_name; | ||||||
|  |  | ||||||
| 	/// the version ID. | 	/// the version ID. | ||||||
| 	QString m_descriptor; | 	QString m_descriptor; | ||||||
|  |  | ||||||
| 	/// version traits. generally launcher business... | 	/// version traits. added by MultiMC | ||||||
| 	QSet<QString> m_traits; | 	QSet<QString> m_traits; | ||||||
|  |  | ||||||
| 	/// The main class this version uses (if any, can be empty). | 	/// The main class this version uses (if any, can be empty). | ||||||
| @@ -52,57 +58,47 @@ struct MinecraftVersion : public BaseVersion, public VersionPatch | |||||||
| 	/// The applet class this version uses (if any, can be empty). | 	/// The applet class this version uses (if any, can be empty). | ||||||
| 	QString m_appletClass; | 	QString m_appletClass; | ||||||
|  |  | ||||||
| 	bool usesLegacyLauncher() | 	/// The process arguments used by this version | ||||||
|  | 	QString m_processArguments; | ||||||
|  |  | ||||||
|  | 	/// The type of this release | ||||||
|  | 	QString m_type; | ||||||
|  |  | ||||||
|  | 	/// the time this version was actually released by Mojang, as string and as QDateTime | ||||||
|  | 	QString m_releaseTimeString; | ||||||
|  | 	QDateTime m_releaseTime; | ||||||
|  |  | ||||||
|  | 	/// the time this version was last updated by Mojang, as string and as QDateTime | ||||||
|  | 	QString m_updateTimeString; | ||||||
|  | 	QDateTime m_updateTime; | ||||||
|  |  | ||||||
|  | 	/// order of this file... default = -2 | ||||||
|  | 	int order = -2; | ||||||
|  |  | ||||||
|  | 	bool usesLegacyLauncher(); | ||||||
|  | 	virtual QString descriptor() override; | ||||||
|  | 	virtual QString name() override; | ||||||
|  | 	virtual QString typeString() const override; | ||||||
|  | 	virtual bool hasJarMods() override; | ||||||
|  | 	virtual bool isVanilla() override; | ||||||
|  | 	virtual void applyTo(VersionFinal *version) override; | ||||||
|  | 	virtual int getOrder(); | ||||||
|  | 	virtual void setOrder(int order); | ||||||
|  | 	virtual QList<JarmodPtr> getJarMods() override; | ||||||
|  | 	virtual QString getPatchID() | ||||||
| 	{ | 	{ | ||||||
| 		return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); | 		return "net.minecraft"; | ||||||
| 	} | 	} | ||||||
| 	 | 	virtual QString getPatchVersion() | ||||||
| 	virtual QString descriptor() override |  | ||||||
| 	{ | 	{ | ||||||
| 		return m_descriptor; | 		return m_descriptor; | ||||||
| 	} | 	} | ||||||
|  | 	virtual QString getPatchName() | ||||||
| 	virtual QString name() override |  | ||||||
| 	{ | 	{ | ||||||
| 		return m_name; | 		return "Minecraft"; | ||||||
| 	} | 	} | ||||||
|  | 	virtual QString getPatchFilename() | ||||||
| 	virtual QString typeString() const override |  | ||||||
| 	{ | 	{ | ||||||
| 		if (is_latest && is_snapshot) | 		return QString(); | ||||||
| 		{ | 	}; | ||||||
| 			return QObject::tr("Latest snapshot"); |  | ||||||
| 		} |  | ||||||
| 		else if(is_latest) |  | ||||||
| 		{ |  | ||||||
| 			return QObject::tr("Latest release"); |  | ||||||
| 		} |  | ||||||
| 		else if(is_snapshot) |  | ||||||
| 		{ |  | ||||||
| 			return QObject::tr("Snapshot"); |  | ||||||
| 		} |  | ||||||
| 		else if(is_builtin) |  | ||||||
| 		{ |  | ||||||
| 			return QObject::tr("Museum piece"); |  | ||||||
| 		} |  | ||||||
| 		else |  | ||||||
| 		{ |  | ||||||
| 			return QObject::tr("Regular release"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	virtual bool hasJarMods() override |  | ||||||
| 	{ |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	virtual bool isVanilla() override |  | ||||||
| 	{ |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	virtual void applyTo(VersionFinal *version) |  | ||||||
| 	{ |  | ||||||
| 		// umm... what now? |  | ||||||
| 	} |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -16,7 +16,8 @@ | |||||||
| #include "MinecraftVersionList.h" | #include "MinecraftVersionList.h" | ||||||
| #include "MultiMC.h" | #include "MultiMC.h" | ||||||
| #include "logic/net/URLConstants.h" | #include "logic/net/URLConstants.h" | ||||||
| #include <logic/MMCJson.h> | #include "logic/MMCJson.h" | ||||||
|  | #include "ParseUtils.h" | ||||||
|  |  | ||||||
| #include <QtXml> | #include <QtXml> | ||||||
|  |  | ||||||
| @@ -30,11 +31,6 @@ | |||||||
|  |  | ||||||
| #include <QtNetwork> | #include <QtNetwork> | ||||||
|  |  | ||||||
| inline QDateTime timeFromS3Time(QString str) |  | ||||||
| { |  | ||||||
| 	return QDateTime::fromString(str, Qt::ISODate); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) | MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) | ||||||
| { | { | ||||||
| 	loadBuiltinList(); | 	loadBuiltinList(); | ||||||
| @@ -64,7 +60,7 @@ static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) | |||||||
| { | { | ||||||
| 	auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); | 	auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); | ||||||
| 	auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); | 	auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); | ||||||
| 	return left->timestamp > right->timestamp; | 	return left->m_releaseTime > right->m_releaseTime; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MinecraftVersionList::sortInternal() | void MinecraftVersionList::sortInternal() | ||||||
| @@ -79,55 +75,55 @@ void MinecraftVersionList::loadBuiltinList() | |||||||
| 	QFile filez(versionList.absoluteFilePath()); | 	QFile filez(versionList.absoluteFilePath()); | ||||||
| 	filez.open(QIODevice::ReadOnly); | 	filez.open(QIODevice::ReadOnly); | ||||||
| 	auto data = filez.readAll(); | 	auto data = filez.readAll(); | ||||||
| 	 |  | ||||||
| 	// parse the data as json | 	// parse the data as json | ||||||
| 	QJsonParseError jsonError; | 	QJsonParseError jsonError; | ||||||
| 	QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); | 	QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); | ||||||
| 	QJsonObject root = jsonDoc.object(); | 	QJsonObject root = jsonDoc.object(); | ||||||
| 	 |  | ||||||
| 	// parse all the versions | 	// parse all the versions | ||||||
| 	for (const auto version : MMCJson::ensureArray(root.value("versions"))) | 	for (const auto version : MMCJson::ensureArray(root.value("versions"))) | ||||||
| 	{ | 	{ | ||||||
| 		QJsonObject versionObj = version.toObject(); | 		QJsonObject versionObj = version.toObject(); | ||||||
| 		QString versionID = versionObj.value("id").toString(""); | 		QString versionID = versionObj.value("id").toString(""); | ||||||
| 		QString versionTimeStr = versionObj.value("releaseTime").toString(""); |  | ||||||
| 		QString versionTypeStr = versionObj.value("type").toString(""); | 		QString versionTypeStr = versionObj.value("type").toString(""); | ||||||
| 		QSet<QString> traits; | 		if (versionID.isEmpty() || versionTypeStr.isEmpty()) | ||||||
| 		if (versionObj.contains("+traits")) |  | ||||||
| 		{ | 		{ | ||||||
| 			for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) | 			QLOG_ERROR() << "Parsed version is missing ID or type"; | ||||||
| 			{ |  | ||||||
| 				traits.insert(MMCJson::ensureString(traitVal)); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) |  | ||||||
| 		{ |  | ||||||
| 			// FIXME: log this somewhere |  | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		// Parse the timestamp. |  | ||||||
| 		QDateTime versionTime = timeFromS3Time(versionTimeStr); |  | ||||||
| 		if (!versionTime.isValid()) |  | ||||||
| 		{ |  | ||||||
| 			// FIXME: log this somewhere |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
| 		// Get the download URL. |  | ||||||
| 		QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; |  | ||||||
|  |  | ||||||
| 		// main class and applet class |  | ||||||
| 		QString mainClass = versionObj.value("type").toString(""); |  | ||||||
| 		QString appletClass = versionObj.value("type").toString(""); |  | ||||||
|  |  | ||||||
| 		// Now, we construct the version object and add it to the list. | 		// Now, we construct the version object and add it to the list. | ||||||
| 		std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); | 		std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); | ||||||
| 		mcVersion->m_name = mcVersion->m_descriptor = versionID; | 		mcVersion->m_name = mcVersion->m_descriptor = versionID; | ||||||
| 		mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); |  | ||||||
| 		mcVersion->download_url = dlUrl; | 		// Parse the timestamp. | ||||||
| 		mcVersion->is_builtin = true; | 		try | ||||||
| 		mcVersion->m_appletClass = appletClass; | 		{ | ||||||
| 		mcVersion->m_mainClass = mainClass; | 			parse_timestamp(versionObj.value("releaseTime").toString(""), | ||||||
| 		mcVersion->m_traits = traits; | 							mcVersion->m_releaseTimeString, mcVersion->m_releaseTime); | ||||||
|  | 		} | ||||||
|  | 		catch (MMCError &e) | ||||||
|  | 		{ | ||||||
|  | 			QLOG_ERROR() << "Error while parsing version" << versionID << ":" << e.cause(); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Get the download URL. | ||||||
|  | 		mcVersion->download_url = | ||||||
|  | 			"http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; | ||||||
|  |  | ||||||
|  | 		mcVersion->m_versionSource = MinecraftVersion::Builtin; | ||||||
|  | 		mcVersion->m_appletClass = versionObj.value("appletClass").toString(""); | ||||||
|  | 		mcVersion->m_mainClass = versionObj.value("mainClass").toString(""); | ||||||
|  | 		mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy"); | ||||||
|  | 		if (versionObj.contains("+traits")) | ||||||
|  | 		{ | ||||||
|  | 			for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) | ||||||
|  | 			{ | ||||||
|  | 				mcVersion->m_traits.insert(MMCJson::ensureString(traitVal)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		m_vlist.append(mcVersion); | 		m_vlist.append(mcVersion); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -265,50 +261,14 @@ void MCVListLoadTask::list_downloaded() | |||||||
| 		// Load the version info. | 		// Load the version info. | ||||||
| 		if (!version.isObject()) | 		if (!version.isObject()) | ||||||
| 		{ | 		{ | ||||||
| 			// FIXME: log this somewhere | 			QLOG_ERROR() << "Error while parsing version list : invalid JSON structure"; | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		QJsonObject versionObj = version.toObject(); | 		QJsonObject versionObj = version.toObject(); | ||||||
| 		QString versionID = versionObj.value("id").toString(""); | 		QString versionID = versionObj.value("id").toString(""); | ||||||
| 		QString versionTimeStr = versionObj.value("releaseTime").toString(""); | 		if (versionID.isEmpty()) | ||||||
| 		QString versionTypeStr = versionObj.value("type").toString(""); |  | ||||||
| 		if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) |  | ||||||
| 		{ | 		{ | ||||||
| 			// FIXME: log this somewhere | 			QLOG_ERROR() << "Error while parsing version : version ID is missing"; | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Parse the timestamp. |  | ||||||
| 		QDateTime versionTime = timeFromS3Time(versionTimeStr); |  | ||||||
| 		if (!versionTime.isValid()) |  | ||||||
| 		{ |  | ||||||
| 			// FIXME: log this somewhere |  | ||||||
| 			continue; |  | ||||||
| 		} |  | ||||||
| 		// OneSix or Legacy. use filter to determine type |  | ||||||
| 		if (versionTypeStr == "release") |  | ||||||
| 		{ |  | ||||||
| 			is_latest = (versionID == latestReleaseID); |  | ||||||
| 			is_snapshot = false; |  | ||||||
| 		} |  | ||||||
| 		else if (versionTypeStr == "snapshot") // It's a snapshot... yay |  | ||||||
| 		{ |  | ||||||
| 			is_latest = (versionID == latestSnapshotID); |  | ||||||
| 			is_snapshot = true; |  | ||||||
| 		} |  | ||||||
| 		else if (versionTypeStr == "old_alpha") |  | ||||||
| 		{ |  | ||||||
| 			is_latest = false; |  | ||||||
| 			is_snapshot = false; |  | ||||||
| 		} |  | ||||||
| 		else if (versionTypeStr == "old_beta") |  | ||||||
| 		{ |  | ||||||
| 			is_latest = false; |  | ||||||
| 			is_snapshot = false; |  | ||||||
| 		} |  | ||||||
| 		else |  | ||||||
| 		{ |  | ||||||
| 			// FIXME: log this somewhere |  | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		// Get the download URL. | 		// Get the download URL. | ||||||
| @@ -317,10 +277,61 @@ void MCVListLoadTask::list_downloaded() | |||||||
| 		// Now, we construct the version object and add it to the list. | 		// Now, we construct the version object and add it to the list. | ||||||
| 		std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); | 		std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); | ||||||
| 		mcVersion->m_name = mcVersion->m_descriptor = versionID; | 		mcVersion->m_name = mcVersion->m_descriptor = versionID; | ||||||
| 		mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); |  | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			// Parse the timestamps. | ||||||
|  | 			parse_timestamp(versionObj.value("releaseTime").toString(""), | ||||||
|  | 							mcVersion->m_releaseTimeString, mcVersion->m_releaseTime); | ||||||
|  |  | ||||||
|  | 			parse_timestamp(versionObj.value("time").toString(""), | ||||||
|  | 							mcVersion->m_updateTimeString, mcVersion->m_updateTime); | ||||||
|  | 		} | ||||||
|  | 		catch (MMCError &e) | ||||||
|  | 		{ | ||||||
|  | 			QLOG_ERROR() << "Error while parsing version" << versionID << ":" << e.cause(); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		mcVersion->m_versionSource = MinecraftVersion::Builtin; | ||||||
| 		mcVersion->download_url = dlUrl; | 		mcVersion->download_url = dlUrl; | ||||||
| 		mcVersion->is_latest = is_latest; | 		{ | ||||||
| 		mcVersion->is_snapshot = is_snapshot; | 			QString versionTypeStr = versionObj.value("type").toString(""); | ||||||
|  | 			if (versionTypeStr.isEmpty()) | ||||||
|  | 			{ | ||||||
|  | 				// FIXME: log this somewhere | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			// OneSix or Legacy. use filter to determine type | ||||||
|  | 			if (versionTypeStr == "release") | ||||||
|  | 			{ | ||||||
|  | 				is_latest = (versionID == latestReleaseID); | ||||||
|  | 				is_snapshot = false; | ||||||
|  | 			} | ||||||
|  | 			else if (versionTypeStr == "snapshot") // It's a snapshot... yay | ||||||
|  | 			{ | ||||||
|  | 				is_latest = (versionID == latestSnapshotID); | ||||||
|  | 				is_snapshot = true; | ||||||
|  | 			} | ||||||
|  | 			else if (versionTypeStr == "old_alpha") | ||||||
|  | 			{ | ||||||
|  | 				is_latest = false; | ||||||
|  | 				is_snapshot = false; | ||||||
|  | 			} | ||||||
|  | 			else if (versionTypeStr == "old_beta") | ||||||
|  | 			{ | ||||||
|  | 				is_latest = false; | ||||||
|  | 				is_snapshot = false; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				// FIXME: log this somewhere | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			mcVersion->m_type = versionTypeStr; | ||||||
|  | 			mcVersion->is_latest = is_latest; | ||||||
|  | 			mcVersion->is_snapshot = is_snapshot; | ||||||
|  | 		} | ||||||
| 		tempList.append(mcVersion); | 		tempList.append(mcVersion); | ||||||
| 	} | 	} | ||||||
| 	m_list->updateListData(tempList); | 	m_list->updateListData(tempList); | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								logic/minecraft/ParseUtils.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								logic/minecraft/ParseUtils.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | #include <QDateTime> | ||||||
|  | #include <QString> | ||||||
|  | #include "ParseUtils.h" | ||||||
|  | #include <logic/MMCJson.h> | ||||||
|  |  | ||||||
|  | QDateTime timeFromS3Time(QString str) | ||||||
|  | { | ||||||
|  | 	return QDateTime::fromString(str, Qt::ISODate); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void parse_timestamp (const QString & raw, QString & save_here, QDateTime & parse_here) | ||||||
|  | { | ||||||
|  | 	save_here = raw; | ||||||
|  | 	if (save_here.isEmpty()) | ||||||
|  | 	{ | ||||||
|  | 		throw JSONValidationError("The timestamp is empty!"); | ||||||
|  | 	} | ||||||
|  | 	parse_here = timeFromS3Time(save_here); | ||||||
|  | 	if (!parse_here.isValid()) | ||||||
|  | 	{ | ||||||
|  | 		throw JSONValidationError("The timestamp not a valid timestamp!"); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								logic/minecraft/ParseUtils.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								logic/minecraft/ParseUtils.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <QString> | ||||||
|  | #include <QDateTime> | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * parse the S3 timestamp in 'raw' and fill the forwarded variables. | ||||||
|  |  * return true/false for success/failure | ||||||
|  |  */ | ||||||
|  | void parse_timestamp (const QString &raw, QString &save_here, QDateTime &parse_here); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * take the timestamp used by S3 and turn it into QDateTime | ||||||
|  |  */ | ||||||
|  | QDateTime timeFromS3Time(QString str); | ||||||
							
								
								
									
										58
									
								
								logic/minecraft/VersionBuildError.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								logic/minecraft/VersionBuildError.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | #include "MMCError.h" | ||||||
|  |  | ||||||
|  | class VersionBuildError : public MMCError | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	VersionBuildError(QString cause) : MMCError(cause) {}; | ||||||
|  | 	virtual ~VersionBuildError() noexcept | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * the base version file was meant for a newer version of the vanilla launcher than we support | ||||||
|  |  */ | ||||||
|  | class LauncherVersionError : public VersionBuildError | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	LauncherVersionError(int actual, int supported) | ||||||
|  | 		: VersionBuildError(QObject::tr( | ||||||
|  | 			  "The base version file of this instance was meant for a newer (%1) " | ||||||
|  | 			  "version of the vanilla launcher than this version of MultiMC supports (%2).") | ||||||
|  | 								.arg(actual) | ||||||
|  | 								.arg(supported)) {}; | ||||||
|  | 	virtual ~LauncherVersionError() noexcept | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * some patch was intended for a different version of minecraft | ||||||
|  |  */ | ||||||
|  | class MinecraftVersionMismatch : public VersionBuildError | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) | ||||||
|  | 		: VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " | ||||||
|  | 										"(%2) than that of the instance (%3).") | ||||||
|  | 								.arg(fileId) | ||||||
|  | 								.arg(mcVersion) | ||||||
|  | 								.arg(parentMcVersion)) {}; | ||||||
|  | 	virtual ~MinecraftVersionMismatch() noexcept | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * files required for the version are not (yet?) present | ||||||
|  |  */ | ||||||
|  | class VersionIncomplete : public VersionBuildError | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	VersionIncomplete(QString missingPatch) | ||||||
|  | 		: VersionBuildError(QObject::tr("Version is incomplete: missing %1.") | ||||||
|  | 								.arg(missingPatch)) {}; | ||||||
|  | 	virtual ~VersionIncomplete() noexcept | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| @@ -23,12 +23,17 @@ | |||||||
| #include <QObject> | #include <QObject> | ||||||
| #include <QDir> | #include <QDir> | ||||||
| #include <QDebug> | #include <QDebug> | ||||||
|  | #include <qresource.h> | ||||||
| #include <modutils.h> | #include <modutils.h> | ||||||
|  |  | ||||||
|  | #include "MultiMC.h" | ||||||
| #include "logic/minecraft/VersionBuilder.h" | #include "logic/minecraft/VersionBuilder.h" | ||||||
| #include "logic/minecraft/VersionFinal.h" | #include "logic/minecraft/VersionFinal.h" | ||||||
| #include "logic/minecraft/OneSixRule.h" | #include "logic/minecraft/OneSixRule.h" | ||||||
|  | #include "logic/minecraft/VersionPatch.h" | ||||||
| #include "logic/minecraft/VersionFile.h" | #include "logic/minecraft/VersionFile.h" | ||||||
|  | #include "VersionBuildError.h" | ||||||
|  | #include "MinecraftVersionList.h" | ||||||
|  |  | ||||||
| #include "logic/OneSixInstance.h" | #include "logic/OneSixInstance.h" | ||||||
| #include "logic/MMCJson.h" | #include "logic/MMCJson.h" | ||||||
| @@ -39,16 +44,17 @@ VersionBuilder::VersionBuilder() | |||||||
| { | { | ||||||
| } | } | ||||||
|  |  | ||||||
| void VersionBuilder::build(VersionFinal *version, OneSixInstance *instance, const QStringList &external) | void VersionBuilder::build(VersionFinal *version, OneSixInstance *instance, | ||||||
|  | 						   const QStringList &external) | ||||||
| { | { | ||||||
| 	VersionBuilder builder; | 	VersionBuilder builder; | ||||||
| 	builder.m_version = version; | 	builder.m_version = version; | ||||||
| 	builder.m_instance = instance; | 	builder.m_instance = instance; | ||||||
| 	builder.buildInternal(external); | 	builder.external_patches = external; | ||||||
|  | 	builder.buildInternal(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, | void VersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj) | ||||||
| 													 const QJsonObject &obj) |  | ||||||
| { | { | ||||||
| 	VersionBuilder builder; | 	VersionBuilder builder; | ||||||
| 	builder.m_version = version; | 	builder.m_version = version; | ||||||
| @@ -56,88 +62,143 @@ void VersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, | |||||||
| 	builder.readJsonAndApply(obj); | 	builder.readJsonAndApply(obj); | ||||||
| } | } | ||||||
|  |  | ||||||
| void VersionBuilder::buildInternal(const QStringList &external) | void VersionBuilder::buildFromCustomJson() | ||||||
| { | { | ||||||
| 	m_version->versionFiles.clear(); | 	QLOG_INFO() << "Building version from custom.json within the instance."; | ||||||
|  | 	QLOG_INFO() << "Reading custom.json"; | ||||||
|  | 	auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("custom.json")), false); | ||||||
|  | 	file->name = "custom.json"; | ||||||
|  | 	file->filename = "custom.json"; | ||||||
|  | 	file->fileId = "org.multimc.custom.json"; | ||||||
|  | 	file->order = -1; | ||||||
|  | 	file->version = QString(); | ||||||
|  | 	m_version->VersionPatches.append(file); | ||||||
|  | 	// QObject::tr("The version descriptors of this instance are not compatible with the | ||||||
|  | 	// current version of MultiMC")); | ||||||
|  | 	// QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") | ||||||
|  | 	// some final touches | ||||||
|  | 	m_version->finalize(); | ||||||
|  | 	return; | ||||||
|  | } | ||||||
|  |  | ||||||
| 	QDir root(m_instance->instanceRoot()); | void VersionBuilder::buildFromVersionJson() | ||||||
| 	QDir patches(root.absoluteFilePath("patches/")); | { | ||||||
|  | 	QLOG_INFO() << "Building version from version.json and patches within the instance."; | ||||||
|  | 	QLOG_INFO() << "Reading version.json"; | ||||||
|  | 	auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("version.json")), false); | ||||||
|  | 	file->name = "Minecraft"; | ||||||
|  | 	file->fileId = "org.multimc.version.json"; | ||||||
|  | 	file->order = -1; | ||||||
|  | 	file->version = m_instance->intendedVersionId(); | ||||||
|  | 	file->mcVersion = m_instance->intendedVersionId(); | ||||||
|  | 	m_version->VersionPatches.append(file); | ||||||
|  |  | ||||||
| 	// if we do external files, do just those. | 	// load all patches, put into map for ordering, apply in the right order | ||||||
| 	if (!external.isEmpty()) | 	readInstancePatches(); | ||||||
| 	{ |  | ||||||
| 		int externalOrder = -1; |  | ||||||
| 		for (auto fileName : external) |  | ||||||
| 		{ |  | ||||||
| 			QLOG_INFO() << "Reading" << fileName; |  | ||||||
| 			auto file = |  | ||||||
| 				parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); |  | ||||||
| 			file->name = QFileInfo(fileName).fileName(); |  | ||||||
| 			file->fileId = "org.multimc.external." + file->name; |  | ||||||
| 			file->order = (externalOrder += 1); |  | ||||||
| 			file->version = QString(); |  | ||||||
| 			file->mcVersion = QString(); |  | ||||||
| 			m_version->versionFiles.append(file); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// else, if there's custom json, we just do that. |  | ||||||
| 	else if (QFile::exists(root.absoluteFilePath("custom.json"))) |  | ||||||
| 	{ |  | ||||||
| 		QLOG_INFO() << "Reading custom.json"; |  | ||||||
| 		auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false); |  | ||||||
| 		file->name = "custom.json"; |  | ||||||
| 		file->filename = "custom.json"; |  | ||||||
| 		file->fileId = "org.multimc.custom.json"; |  | ||||||
| 		file->order = -1; |  | ||||||
| 		file->version = QString(); |  | ||||||
| 		m_version->versionFiles.append(file); |  | ||||||
| 		// QObject::tr("The version descriptors of this instance are not compatible with the |  | ||||||
| 		// current version of MultiMC")); |  | ||||||
| 		// QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") |  | ||||||
| 	} |  | ||||||
| 	// version.json -> patches/*.json -> user.json |  | ||||||
| 	else |  | ||||||
| 		do |  | ||||||
| 		{ |  | ||||||
| 			// version.json |  | ||||||
| 			QLOG_INFO() << "Reading version.json"; |  | ||||||
| 			auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false); |  | ||||||
| 			file->name = "Minecraft"; |  | ||||||
| 			file->fileId = "org.multimc.version.json"; |  | ||||||
| 			file->order = -1; |  | ||||||
| 			file->version = m_instance->intendedVersionId(); |  | ||||||
| 			file->mcVersion = m_instance->intendedVersionId(); |  | ||||||
| 			m_version->versionFiles.append(file); |  | ||||||
| 			// QObject::tr("Error while applying %1. Please check MultiMC-0.log for more |  | ||||||
| 			// info.").arg(root.absoluteFilePath("version.json"))); |  | ||||||
|  |  | ||||||
| 			// patches/ |  | ||||||
| 			// load all, put into map for ordering, apply in the right order |  | ||||||
|  |  | ||||||
| 			QMap<int, QPair<QString, VersionFilePtr>> files; |  | ||||||
| 			for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) |  | ||||||
| 			{ |  | ||||||
| 				QLOG_INFO() << "Reading" << info.fileName(); |  | ||||||
| 				auto file = parseJsonFile(info, true); |  | ||||||
| 				if (files.contains(file->order)) |  | ||||||
| 				{ |  | ||||||
| 					throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg( |  | ||||||
| 						file->fileId, files[file->order].second->fileId)); |  | ||||||
| 				} |  | ||||||
| 				files.insert(file->order, qMakePair(info.fileName(), file)); |  | ||||||
| 			} |  | ||||||
| 			for (auto order : files.keys()) |  | ||||||
| 			{ |  | ||||||
| 				auto &filePair = files[order]; |  | ||||||
| 				m_version->versionFiles.append(filePair.second); |  | ||||||
| 			} |  | ||||||
| 		} while (0); |  | ||||||
|  |  | ||||||
| 	// some final touches | 	// some final touches | ||||||
| 	m_version->finalize(); | 	m_version->finalize(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void VersionBuilder::readInstancePatches() | ||||||
|  | { | ||||||
|  | 	QDir patches(instance_root.absoluteFilePath("patches/")); | ||||||
|  | 	QMap<int, QPair<QString, VersionFilePtr>> files; | ||||||
|  | 	for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) | ||||||
|  | 	{ | ||||||
|  | 		QLOG_INFO() << "Reading" << info.fileName(); | ||||||
|  | 		auto file = parseJsonFile(info, true); | ||||||
|  | 		if(file->fileId == "net.minecraft") | ||||||
|  | 			continue; | ||||||
|  | 		if(file->fileId == "org.lwjgl") | ||||||
|  | 			continue; | ||||||
|  | 		if (files.contains(file->order)) | ||||||
|  | 		{ | ||||||
|  | 			throw VersionBuildError(QObject::tr("%1 has the same order as %2") | ||||||
|  | 										.arg(file->fileId, files[file->order].second->fileId)); | ||||||
|  | 		} | ||||||
|  | 		files.insert(file->order, qMakePair(info.fileName(), file)); | ||||||
|  | 	} | ||||||
|  | 	for (auto order : files.keys()) | ||||||
|  | 	{ | ||||||
|  | 		auto &filePair = files[order]; | ||||||
|  | 		m_version->VersionPatches.append(filePair.second); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VersionBuilder::buildFromExternalPatches() | ||||||
|  | { | ||||||
|  | 	QLOG_INFO() << "Building version from external files."; | ||||||
|  | 	int externalOrder = -1; | ||||||
|  | 	for (auto fileName : external_patches) | ||||||
|  | 	{ | ||||||
|  | 		QLOG_INFO() << "Reading" << fileName; | ||||||
|  | 		auto file = parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); | ||||||
|  | 		file->name = QFileInfo(fileName).fileName(); | ||||||
|  | 		file->fileId = "org.multimc.external." + file->name; | ||||||
|  | 		file->order = (externalOrder += 1); | ||||||
|  | 		file->version = QString(); | ||||||
|  | 		file->mcVersion = QString(); | ||||||
|  | 		m_version->VersionPatches.append(file); | ||||||
|  | 	} | ||||||
|  | 	// some final touches | ||||||
|  | 	m_version->finalize(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VersionBuilder::buildFromMultilayer() | ||||||
|  | { | ||||||
|  | 	QLOG_INFO() << "Building version from multilayered sources."; | ||||||
|  | 	// just the builtin stuff for now | ||||||
|  | 	auto minecraftList = MMC->minecraftlist(); | ||||||
|  | 	auto mcversion = minecraftList->findVersion(m_instance->intendedVersionId()); | ||||||
|  | 	auto minecraftPatch = std::dynamic_pointer_cast<VersionPatch>(mcversion); | ||||||
|  | 	if(!minecraftPatch) | ||||||
|  | 	{ | ||||||
|  | 		throw VersionIncomplete("net.minecraft"); | ||||||
|  | 	} | ||||||
|  | 	minecraftPatch->setOrder(-2); | ||||||
|  | 	m_version->VersionPatches.append(minecraftPatch); | ||||||
|  |  | ||||||
|  | 	QResource LWJGL(":/versions/LWJGL/2.9.1.json"); | ||||||
|  | 	auto lwjgl = parseJsonFile(LWJGL.absoluteFilePath(), false, false); | ||||||
|  | 	auto lwjglPatch = std::dynamic_pointer_cast<VersionPatch>(lwjgl); | ||||||
|  | 	if(!lwjglPatch) | ||||||
|  | 	{ | ||||||
|  | 		throw VersionIncomplete("org.lwjgl"); | ||||||
|  | 	} | ||||||
|  | 	lwjglPatch->setOrder(-1); | ||||||
|  | 	m_version->VersionPatches.append(lwjglPatch); | ||||||
|  |  | ||||||
|  | 	// load all patches, put into map for ordering, apply in the right order | ||||||
|  | 	readInstancePatches(); | ||||||
|  |  | ||||||
|  | 	m_version->finalize(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void VersionBuilder::buildInternal() | ||||||
|  | { | ||||||
|  | 	m_version->VersionPatches.clear(); | ||||||
|  | 	instance_root = QDir(m_instance->instanceRoot()); | ||||||
|  | 	// if we do external files, do just those. | ||||||
|  | 	if (!external_patches.isEmpty()) | ||||||
|  | 	{ | ||||||
|  | 		buildFromExternalPatches(); | ||||||
|  | 	} | ||||||
|  | 	// else, if there's custom json, we just do that. | ||||||
|  | 	else if (QFile::exists(instance_root.absoluteFilePath("custom.json"))) | ||||||
|  | 	{ | ||||||
|  | 		buildFromCustomJson(); | ||||||
|  | 	} | ||||||
|  | 	// version.json -> patches/*.json | ||||||
|  | 	else if (QFile::exists(instance_root.absoluteFilePath("version.json"))) | ||||||
|  | 	{ | ||||||
|  | 		buildFromVersionJson(); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		buildFromMultilayer(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| void VersionBuilder::readJsonAndApply(const QJsonObject &obj) | void VersionBuilder::readJsonAndApply(const QJsonObject &obj) | ||||||
| { | { | ||||||
| @@ -147,14 +208,14 @@ void VersionBuilder::readJsonAndApply(const QJsonObject &obj) | |||||||
| 	// QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); | 	// QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); | ||||||
|  |  | ||||||
| 	file->applyTo(m_version); | 	file->applyTo(m_version); | ||||||
| 	m_version->versionFiles.append(file); | 	m_version->VersionPatches.append(file); | ||||||
| 	// QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); | 	// QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); | ||||||
| 	// QObject::tr("The version descriptors of this instance are not compatible with the current | 	// QObject::tr("The version descriptors of this instance are not compatible with the current | ||||||
| 	// version of MultiMC")); | 	// version of MultiMC")); | ||||||
| } | } | ||||||
|  |  | ||||||
| VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, | VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, | ||||||
| 												   const bool requireOrder, bool isFTB) | 											 bool isFTB) | ||||||
| { | { | ||||||
| 	QFile file(fileInfo.absoluteFilePath()); | 	QFile file(fileInfo.absoluteFilePath()); | ||||||
| 	if (!file.open(QFile::ReadOnly)) | 	if (!file.open(QFile::ReadOnly)) | ||||||
| @@ -166,9 +227,10 @@ VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, | |||||||
| 	QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); | 	QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); | ||||||
| 	if (error.error != QJsonParseError::NoError) | 	if (error.error != QJsonParseError::NoError) | ||||||
| 	{ | 	{ | ||||||
| 		throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.") | 		throw JSONValidationError( | ||||||
| 									  .arg(fileInfo.fileName(), error.errorString()) | 			QObject::tr("Unable to process the version file %1: %2 at %3.") | ||||||
| 									  .arg(error.offset)); | 				.arg(fileInfo.fileName(), error.errorString()) | ||||||
|  | 				.arg(error.offset)); | ||||||
| 	} | 	} | ||||||
| 	return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); | 	return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); | ||||||
| 	// QObject::tr("Error while reading %1. Please check MultiMC-0.log for more | 	// QObject::tr("Error while reading %1. Please check MultiMC-0.log for more | ||||||
| @@ -226,7 +288,7 @@ QMap<QString, int> VersionBuilder::readOverrideOrders(OneSixInstance *instance) | |||||||
| } | } | ||||||
|  |  | ||||||
| bool VersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, | bool VersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, | ||||||
| 											   OneSixInstance *instance) | 										 OneSixInstance *instance) | ||||||
| { | { | ||||||
| 	QJsonObject obj; | 	QJsonObject obj; | ||||||
| 	for (auto it = order.cbegin(); it != order.cend(); ++it) | 	for (auto it = order.cbegin(); it != order.cend(); ++it) | ||||||
|   | |||||||
| @@ -37,8 +37,17 @@ public: | |||||||
| private: | private: | ||||||
| 	VersionFinal *m_version; | 	VersionFinal *m_version; | ||||||
| 	OneSixInstance *m_instance; | 	OneSixInstance *m_instance; | ||||||
|  | 	QStringList external_patches; | ||||||
| 	void buildInternal(const QStringList& external); | 	QDir instance_root; | ||||||
|  | 	 | ||||||
|  | 	void buildInternal(); | ||||||
|  | 	void buildFromExternalPatches(); | ||||||
|  | 	void buildFromCustomJson(); | ||||||
|  | 	void buildFromVersionJson(); | ||||||
|  | 	void buildFromMultilayer(); | ||||||
|  | 	 | ||||||
|  | 	void readInstancePatches(); | ||||||
|  | 	 | ||||||
| 	void readJsonAndApply(const QJsonObject &obj); | 	void readJsonAndApply(const QJsonObject &obj); | ||||||
|  |  | ||||||
| 	VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, | 	VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, | ||||||
|   | |||||||
| @@ -8,10 +8,13 @@ | |||||||
| #include "logic/minecraft/OneSixLibrary.h" | #include "logic/minecraft/OneSixLibrary.h" | ||||||
| #include "logic/minecraft/VersionFinal.h" | #include "logic/minecraft/VersionFinal.h" | ||||||
| #include "logic/minecraft/JarMod.h" | #include "logic/minecraft/JarMod.h" | ||||||
|  | #include "ParseUtils.h" | ||||||
|  |  | ||||||
| #include "logic/MMCJson.h" | #include "logic/MMCJson.h" | ||||||
| using namespace MMCJson; | using namespace MMCJson; | ||||||
|  |  | ||||||
|  | #include "VersionBuildError.h" | ||||||
|  |  | ||||||
| #define CURRENT_MINIMUM_LAUNCHER_VERSION 14 | #define CURRENT_MINIMUM_LAUNCHER_VERSION 14 | ||||||
|  |  | ||||||
| int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) | int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) | ||||||
| @@ -23,7 +26,7 @@ int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) | |||||||
| 		if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) | 		if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) | ||||||
| 		{ | 		{ | ||||||
| 			// only one is allowed. | 			// only one is allowed. | ||||||
| 			if(retval != -1) | 			if (retval != -1) | ||||||
| 				return -1; | 				return -1; | ||||||
| 			retval = i; | 			retval = i; | ||||||
| 		} | 		} | ||||||
| @@ -32,7 +35,7 @@ int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) | |||||||
| } | } | ||||||
|  |  | ||||||
| VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, | VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, | ||||||
| 								  const bool requireOrder, const bool isFTB) | 									 const bool requireOrder, const bool isFTB) | ||||||
| { | { | ||||||
| 	VersionFilePtr out(new VersionFile()); | 	VersionFilePtr out(new VersionFile()); | ||||||
| 	if (doc.isEmpty() || doc.isNull()) | 	if (doc.isEmpty() || doc.isNull()) | ||||||
| @@ -41,7 +44,6 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi | |||||||
| 	} | 	} | ||||||
| 	if (!doc.isObject()) | 	if (!doc.isObject()) | ||||||
| 	{ | 	{ | ||||||
| 		throw JSONValidationError("The root of " + filename + " is not an object"); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	QJsonObject root = doc.object(); | 	QJsonObject root = doc.object(); | ||||||
| @@ -65,7 +67,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi | |||||||
| 	out->mcVersion = root.value("mcVersion").toString(); | 	out->mcVersion = root.value("mcVersion").toString(); | ||||||
| 	out->filename = filename; | 	out->filename = filename; | ||||||
|  |  | ||||||
| 	auto readString = [root, filename](const QString & key, QString & variable) | 	auto readString = [root](const QString & key, QString & variable) | ||||||
| 	{ | 	{ | ||||||
| 		if (root.contains(key)) | 		if (root.contains(key)) | ||||||
| 		{ | 		{ | ||||||
| @@ -73,6 +75,16 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi | |||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|  | 	auto readStringRet = [root](const QString & key)->QString | ||||||
|  | 	{ | ||||||
|  | 		if (root.contains(key)) | ||||||
|  | 		{ | ||||||
|  | 			return ensureString(root.value(key)); | ||||||
|  | 		} | ||||||
|  | 		return QString(); | ||||||
|  | 	} | ||||||
|  | 	; | ||||||
|  |  | ||||||
| 	// FIXME: This should be ignored when applying. | 	// FIXME: This should be ignored when applying. | ||||||
| 	if (!isFTB) | 	if (!isFTB) | ||||||
| 	{ | 	{ | ||||||
| @@ -86,8 +98,14 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi | |||||||
| 	readString("+minecraftArguments", out->addMinecraftArguments); | 	readString("+minecraftArguments", out->addMinecraftArguments); | ||||||
| 	readString("-minecraftArguments", out->removeMinecraftArguments); | 	readString("-minecraftArguments", out->removeMinecraftArguments); | ||||||
| 	readString("type", out->type); | 	readString("type", out->type); | ||||||
| 	readString("releaseTime", out->versionReleaseTime); | 	if (out->isVanilla()) | ||||||
| 	readString("time", out->versionFileUpdateTime); | 	{ | ||||||
|  | 		parse_timestamp(readStringRet("releaseTime"), out->m_releaseTimeString, | ||||||
|  | 						out->m_releaseTime); | ||||||
|  | 		parse_timestamp(readStringRet("time"), out->m_updateTimeString, out->m_updateTime); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	readStringRet("time"); | ||||||
| 	readString("assets", out->assets); | 	readString("assets", out->assets); | ||||||
|  |  | ||||||
| 	if (root.contains("minimumLauncherVersion")) | 	if (root.contains("minimumLauncherVersion")) | ||||||
| @@ -284,7 +302,8 @@ void VersionFile::applyTo(VersionFinal *version) | |||||||
| 	{ | 	{ | ||||||
| 		if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) | 		if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) | ||||||
| 		{ | 		{ | ||||||
| 			throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); | 			throw LauncherVersionError(minimumLauncherVersion, | ||||||
|  | 									   CURRENT_MINIMUM_LAUNCHER_VERSION); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -311,23 +330,28 @@ void VersionFile::applyTo(VersionFinal *version) | |||||||
| 	} | 	} | ||||||
| 	if (!processArguments.isNull()) | 	if (!processArguments.isNull()) | ||||||
| 	{ | 	{ | ||||||
| 		if(isVanilla()) | 		if (isVanilla()) | ||||||
| 		{ | 		{ | ||||||
| 			version->vanillaProcessArguments = processArguments; | 			version->vanillaProcessArguments = processArguments; | ||||||
| 		} | 		} | ||||||
| 		version->processArguments = processArguments; | 		version->processArguments = processArguments; | ||||||
| 	} | 	} | ||||||
| 	if (!type.isNull()) | 	if(isVanilla()) | ||||||
| 	{ | 	{ | ||||||
| 		version->type = type; | 		if (!type.isNull()) | ||||||
| 	} | 		{ | ||||||
| 	if (!versionReleaseTime.isNull()) | 			version->type = type; | ||||||
| 	{ | 		} | ||||||
| 		version->versionReleaseTime = versionReleaseTime; | 		if (!m_releaseTimeString.isNull()) | ||||||
| 	} | 		{ | ||||||
| 	if (!versionFileUpdateTime.isNull()) | 			version->m_releaseTimeString = m_releaseTimeString; | ||||||
| 	{ | 			version->m_releaseTime = m_releaseTime; | ||||||
| 		version->time = versionFileUpdateTime; | 		} | ||||||
|  | 		if (!m_updateTimeString.isNull()) | ||||||
|  | 		{ | ||||||
|  | 			version->m_updateTimeString = m_updateTimeString; | ||||||
|  | 			version->m_updateTime = m_updateTime; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	if (!assets.isNull()) | 	if (!assets.isNull()) | ||||||
| 	{ | 	{ | ||||||
| @@ -335,11 +359,12 @@ void VersionFile::applyTo(VersionFinal *version) | |||||||
| 	} | 	} | ||||||
| 	if (minimumLauncherVersion >= 0) | 	if (minimumLauncherVersion >= 0) | ||||||
| 	{ | 	{ | ||||||
| 		version->minimumLauncherVersion = minimumLauncherVersion; | 		if(version->minimumLauncherVersion < minimumLauncherVersion) | ||||||
|  | 			version->minimumLauncherVersion = minimumLauncherVersion; | ||||||
| 	} | 	} | ||||||
| 	if (!overwriteMinecraftArguments.isNull()) | 	if (!overwriteMinecraftArguments.isNull()) | ||||||
| 	{ | 	{ | ||||||
| 		if(isVanilla()) | 		if (isVanilla()) | ||||||
| 		{ | 		{ | ||||||
| 			version->vanillaMinecraftArguments = overwriteMinecraftArguments; | 			version->vanillaMinecraftArguments = overwriteMinecraftArguments; | ||||||
| 		} | 		} | ||||||
| @@ -374,7 +399,7 @@ void VersionFile::applyTo(VersionFinal *version) | |||||||
| 		{ | 		{ | ||||||
| 			libs.append(createLibrary(lib)); | 			libs.append(createLibrary(lib)); | ||||||
| 		} | 		} | ||||||
| 		if(isVanilla()) | 		if (isVanilla()) | ||||||
| 			version->vanillaLibraries = libs; | 			version->vanillaLibraries = libs; | ||||||
| 		version->libraries = libs; | 		version->libraries = libs; | ||||||
| 	} | 	} | ||||||
| @@ -498,7 +523,7 @@ void VersionFile::applyTo(VersionFinal *version) | |||||||
| 		case RawLibrary::Replace: | 		case RawLibrary::Replace: | ||||||
| 		{ | 		{ | ||||||
| 			QString toReplace; | 			QString toReplace; | ||||||
| 			if(lib->insertData.isEmpty()) | 			if (lib->insertData.isEmpty()) | ||||||
| 			{ | 			{ | ||||||
| 				const int startOfVersion = lib->name.lastIndexOf(':') + 1; | 				const int startOfVersion = lib->name.lastIndexOf(':') + 1; | ||||||
| 				toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); | 				toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include <QString> | #include <QString> | ||||||
| #include <QStringList> | #include <QStringList> | ||||||
|  | #include <QDateTime> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "logic/minecraft/OpSys.h" | #include "logic/minecraft/OpSys.h" | ||||||
| #include "logic/minecraft/OneSixRule.h" | #include "logic/minecraft/OneSixRule.h" | ||||||
| @@ -11,46 +12,8 @@ | |||||||
| #include "JarMod.h" | #include "JarMod.h" | ||||||
|  |  | ||||||
| class VersionFinal; | class VersionFinal; | ||||||
|  |  | ||||||
|  |  | ||||||
| class VersionBuildError : public MMCError |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	VersionBuildError(QString cause) : MMCError(cause) {}; |  | ||||||
| 	virtual ~VersionBuildError() noexcept {} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * the base version file was meant for a newer version of the vanilla launcher than we support |  | ||||||
|  */ |  | ||||||
| class LauncherVersionError : public VersionBuildError |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	LauncherVersionError(int actual, int supported) |  | ||||||
| 		: VersionBuildError(QObject::tr( |  | ||||||
| 			  "The base version file of this instance was meant for a newer (%1) " |  | ||||||
| 			  "version of the vanilla launcher than this version of MultiMC supports (%2).") |  | ||||||
| 								.arg(actual) |  | ||||||
| 								.arg(supported)) {}; |  | ||||||
| 	virtual ~LauncherVersionError() noexcept {} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * some patch was intended for a different version of minecraft |  | ||||||
|  */ |  | ||||||
| class MinecraftVersionMismatch : public VersionBuildError |  | ||||||
| { |  | ||||||
| public: |  | ||||||
| 	MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) |  | ||||||
| 		: VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " |  | ||||||
| 										"(%2) than that of the instance (%3).") |  | ||||||
| 								.arg(fileId) |  | ||||||
| 								.arg(mcVersion) |  | ||||||
| 								.arg(parentMcVersion)) {}; |  | ||||||
| 	virtual ~MinecraftVersionMismatch() noexcept {} |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| struct VersionFile; | struct VersionFile; | ||||||
|  |  | ||||||
| typedef std::shared_ptr<VersionFile> VersionFilePtr; | typedef std::shared_ptr<VersionFile> VersionFilePtr; | ||||||
| class VersionFile : public VersionPatch | class VersionFile : public VersionPatch | ||||||
| { | { | ||||||
| @@ -62,7 +25,35 @@ public: /* methods */ | |||||||
| 	virtual void applyTo(VersionFinal *version) override; | 	virtual void applyTo(VersionFinal *version) override; | ||||||
| 	virtual bool isVanilla() override; | 	virtual bool isVanilla() override; | ||||||
| 	virtual bool hasJarMods() override; | 	virtual bool hasJarMods() override; | ||||||
| 	 | 	virtual int getOrder() override | ||||||
|  | 	{ | ||||||
|  | 		return order; | ||||||
|  | 	} | ||||||
|  | 	virtual void setOrder(int order) override | ||||||
|  | 	{ | ||||||
|  | 		this->order = order; | ||||||
|  | 	} | ||||||
|  | 	virtual QList<JarmodPtr> getJarMods() override | ||||||
|  | 	{ | ||||||
|  | 		return jarMods; | ||||||
|  | 	} | ||||||
|  | 	virtual QString getPatchID() override | ||||||
|  | 	{ | ||||||
|  | 		return fileId; | ||||||
|  | 	} | ||||||
|  | 	virtual QString getPatchName() override | ||||||
|  | 	{ | ||||||
|  | 		return name; | ||||||
|  | 	} | ||||||
|  | 	virtual QString getPatchVersion() override | ||||||
|  | 	{ | ||||||
|  | 		return version; | ||||||
|  | 	} | ||||||
|  | 	virtual QString getPatchFilename() override | ||||||
|  | 	{ | ||||||
|  | 		return filename; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| public: /* data */ | public: /* data */ | ||||||
| 	int order = 0; | 	int order = 0; | ||||||
| 	QString name; | 	QString name; | ||||||
| @@ -81,8 +72,16 @@ public: /* data */ | |||||||
| 	QString removeMinecraftArguments; | 	QString removeMinecraftArguments; | ||||||
| 	QString processArguments; | 	QString processArguments; | ||||||
| 	QString type; | 	QString type; | ||||||
| 	QString versionReleaseTime; |  | ||||||
| 	QString versionFileUpdateTime; | 	/// the time this version was actually released by Mojang, as string and as QDateTime | ||||||
|  | 	QString m_releaseTimeString; | ||||||
|  | 	QDateTime m_releaseTime; | ||||||
|  |  | ||||||
|  | 	/// the time this version was last updated by Mojang, as string and as QDateTime | ||||||
|  | 	QString m_updateTimeString; | ||||||
|  | 	QDateTime m_updateTime; | ||||||
|  |  | ||||||
|  | 	/// asset group used by this ... thing. | ||||||
| 	QString assets; | 	QString assets; | ||||||
| 	int minimumLauncherVersion = -1; | 	int minimumLauncherVersion = -1; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,8 +39,10 @@ void VersionFinal::reload(const QStringList &external) | |||||||
| void VersionFinal::clear() | void VersionFinal::clear() | ||||||
| { | { | ||||||
| 	id.clear(); | 	id.clear(); | ||||||
| 	time.clear(); | 	m_updateTimeString.clear(); | ||||||
| 	versionReleaseTime.clear(); | 	m_updateTime = QDateTime(); | ||||||
|  | 	m_releaseTimeString.clear(); | ||||||
|  | 	m_releaseTime = QDateTime(); | ||||||
| 	type.clear(); | 	type.clear(); | ||||||
| 	assets.clear(); | 	assets.clear(); | ||||||
| 	processArguments.clear(); | 	processArguments.clear(); | ||||||
| @@ -56,17 +58,13 @@ void VersionFinal::clear() | |||||||
|  |  | ||||||
| bool VersionFinal::canRemove(const int index) const | bool VersionFinal::canRemove(const int index) const | ||||||
| { | { | ||||||
| 	if (index < versionFiles.size()) | 	return VersionPatches.at(index)->isMoveable(); | ||||||
| 	{ |  | ||||||
| 		return versionFiles.at(index)->fileId != "org.multimc.version.json"; |  | ||||||
| 	} |  | ||||||
| 	return false; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| bool VersionFinal::preremove(VersionFilePtr versionfile) | bool VersionFinal::preremove(VersionPatchPtr patch) | ||||||
| { | { | ||||||
| 	bool ok = true; | 	bool ok = true; | ||||||
| 	for(auto & jarmod: versionfile->jarMods) | 	for(auto & jarmod: patch->getJarMods()) | ||||||
| 	{ | 	{ | ||||||
| 		QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); | 		QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); | ||||||
| 		QFileInfo finfo (fullpath); | 		QFileInfo finfo (fullpath); | ||||||
| @@ -80,14 +78,14 @@ bool VersionFinal::remove(const int index) | |||||||
| { | { | ||||||
| 	if (!canRemove(index)) | 	if (!canRemove(index)) | ||||||
| 		return false; | 		return false; | ||||||
| 	if(!preremove(versionFiles[index])) | 	if(!preremove(VersionPatches[index])) | ||||||
| 	{ | 	{ | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	if(!QFile::remove(versionFiles.at(index)->filename)) | 	if(!QFile::remove(VersionPatches.at(index)->getPatchFilename())) | ||||||
| 		return false; | 		return false; | ||||||
| 	beginResetModel(); | 	beginResetModel(); | ||||||
| 	versionFiles.removeAt(index); | 	VersionPatches.removeAt(index); | ||||||
| 	reapply(true); | 	reapply(true); | ||||||
| 	endResetModel(); | 	endResetModel(); | ||||||
| 	return true; | 	return true; | ||||||
| @@ -96,9 +94,9 @@ bool VersionFinal::remove(const int index) | |||||||
| bool VersionFinal::remove(const QString id) | bool VersionFinal::remove(const QString id) | ||||||
| { | { | ||||||
| 	int i = 0; | 	int i = 0; | ||||||
| 	for (auto file : versionFiles) | 	for (auto patch : VersionPatches) | ||||||
| 	{ | 	{ | ||||||
| 		if (file->fileId == id) | 		if (patch->getPatchID() == id) | ||||||
| 		{ | 		{ | ||||||
| 			return remove(i); | 			return remove(i); | ||||||
| 		} | 		} | ||||||
| @@ -109,18 +107,18 @@ bool VersionFinal::remove(const QString id) | |||||||
|  |  | ||||||
| QString VersionFinal::versionFileId(const int index) const | QString VersionFinal::versionFileId(const int index) const | ||||||
| { | { | ||||||
| 	if (index < 0 || index >= versionFiles.size()) | 	if (index < 0 || index >= VersionPatches.size()) | ||||||
| 	{ | 	{ | ||||||
| 		return QString(); | 		return QString(); | ||||||
| 	} | 	} | ||||||
| 	return versionFiles.at(index)->fileId; | 	return VersionPatches.at(index)->getPatchID(); | ||||||
| } | } | ||||||
|  |  | ||||||
| VersionFilePtr VersionFinal::versionPatch(const QString &id) | VersionPatchPtr VersionFinal::versionPatch(const QString &id) | ||||||
| { | { | ||||||
| 	for (auto file : versionFiles) | 	for (auto file : VersionPatches) | ||||||
| 	{ | 	{ | ||||||
| 		if (file->fileId == id) | 		if (file->getPatchID() == id) | ||||||
| 		{ | 		{ | ||||||
| 			return file; | 			return file; | ||||||
| 		} | 		} | ||||||
| @@ -128,6 +126,14 @@ VersionFilePtr VersionFinal::versionPatch(const QString &id) | |||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | VersionPatchPtr VersionFinal::versionPatch(int index) | ||||||
|  | { | ||||||
|  | 	if(index < 0 || index >= VersionPatches.size()) | ||||||
|  | 		return 0; | ||||||
|  | 	return VersionPatches[index]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| bool VersionFinal::hasJarMods() | bool VersionFinal::hasJarMods() | ||||||
| { | { | ||||||
| 	return !jarMods.isEmpty(); | 	return !jarMods.isEmpty(); | ||||||
| @@ -146,7 +152,7 @@ bool VersionFinal::removeFtbPack() | |||||||
| bool VersionFinal::isVanilla() | bool VersionFinal::isVanilla() | ||||||
| { | { | ||||||
| 	QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); | 	QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); | ||||||
| 	if(versionFiles.size() > 1) | 	if(VersionPatches.size() > 1) | ||||||
| 		return false; | 		return false; | ||||||
| 	if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) | 	if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) | ||||||
| 		return false; | 		return false; | ||||||
| @@ -156,22 +162,22 @@ bool VersionFinal::isVanilla() | |||||||
| bool VersionFinal::revertToVanilla() | bool VersionFinal::revertToVanilla() | ||||||
| { | { | ||||||
| 	beginResetModel(); | 	beginResetModel(); | ||||||
| 	auto it = versionFiles.begin(); | 	auto it = VersionPatches.begin(); | ||||||
| 	while (it != versionFiles.end()) | 	while (it != VersionPatches.end()) | ||||||
| 	{ | 	{ | ||||||
| 		if ((*it)->fileId != "org.multimc.version.json") | 		if ((*it)->isMoveable()) | ||||||
| 		{ | 		{ | ||||||
| 			if(!preremove(*it)) | 			if(!preremove(*it)) | ||||||
| 			{ | 			{ | ||||||
| 				endResetModel(); | 				endResetModel(); | ||||||
| 				return false; | 				return false; | ||||||
| 			} | 			} | ||||||
| 			if(!QFile::remove((*it)->filename)) | 			if(!QFile::remove((*it)->getPatchFilename())) | ||||||
| 			{ | 			{ | ||||||
| 				endResetModel(); | 				endResetModel(); | ||||||
| 				return false; | 				return false; | ||||||
| 			} | 			} | ||||||
| 			it = versionFiles.erase(it); | 			it = VersionPatches.erase(it); | ||||||
| 		} | 		} | ||||||
| 		else | 		else | ||||||
| 			it++; | 			it++; | ||||||
| @@ -233,7 +239,7 @@ QVariant VersionFinal::data(const QModelIndex &index, int role) const | |||||||
| 	int row = index.row(); | 	int row = index.row(); | ||||||
| 	int column = index.column(); | 	int column = index.column(); | ||||||
|  |  | ||||||
| 	if (row < 0 || row >= versionFiles.size()) | 	if (row < 0 || row >= VersionPatches.size()) | ||||||
| 		return QVariant(); | 		return QVariant(); | ||||||
|  |  | ||||||
| 	if (role == Qt::DisplayRole) | 	if (role == Qt::DisplayRole) | ||||||
| @@ -241,9 +247,9 @@ QVariant VersionFinal::data(const QModelIndex &index, int role) const | |||||||
| 		switch (column) | 		switch (column) | ||||||
| 		{ | 		{ | ||||||
| 		case 0: | 		case 0: | ||||||
| 			return versionFiles.at(row)->name; | 			return VersionPatches.at(row)->getPatchName(); | ||||||
| 		case 1: | 		case 1: | ||||||
| 			return versionFiles.at(row)->version; | 			return VersionPatches.at(row)->getPatchVersion(); | ||||||
| 		default: | 		default: | ||||||
| 			return QVariant(); | 			return QVariant(); | ||||||
| 		} | 		} | ||||||
| @@ -278,7 +284,7 @@ Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const | |||||||
|  |  | ||||||
| int VersionFinal::rowCount(const QModelIndex &parent) const | int VersionFinal::rowCount(const QModelIndex &parent) const | ||||||
| { | { | ||||||
| 	return versionFiles.size(); | 	return VersionPatches.size(); | ||||||
| } | } | ||||||
|  |  | ||||||
| int VersionFinal::columnCount(const QModelIndex &parent) const | int VersionFinal::columnCount(const QModelIndex &parent) const | ||||||
| @@ -288,13 +294,12 @@ int VersionFinal::columnCount(const QModelIndex &parent) const | |||||||
|  |  | ||||||
| QMap<QString, int> VersionFinal::getExistingOrder() const | QMap<QString, int> VersionFinal::getExistingOrder() const | ||||||
| { | { | ||||||
|  |  | ||||||
| 	QMap<QString, int> order; | 	QMap<QString, int> order; | ||||||
| 	// default | 	// default | ||||||
| 	{ | 	{ | ||||||
| 		for (auto file : versionFiles) | 		for (auto file : VersionPatches) | ||||||
| 		{ | 		{ | ||||||
| 			order.insert(file->fileId, file->order); | 			order.insert(file->getPatchID(), file->getOrder()); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	// overriden | 	// overriden | ||||||
| @@ -314,39 +319,37 @@ QMap<QString, int> VersionFinal::getExistingOrder() const | |||||||
| void VersionFinal::move(const int index, const MoveDirection direction) | void VersionFinal::move(const int index, const MoveDirection direction) | ||||||
| { | { | ||||||
| 	int theirIndex; | 	int theirIndex; | ||||||
|  | 	int theirIndex_qt; | ||||||
| 	if (direction == MoveUp) | 	if (direction == MoveUp) | ||||||
| 	{ | 	{ | ||||||
| 		theirIndex = index - 1; | 		theirIndex_qt = theirIndex = index - 1; | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		theirIndex = index + 1; | 		theirIndex = index + 1; | ||||||
|  | 		theirIndex_qt = index + 2; | ||||||
| 	} | 	} | ||||||
| 	if (theirIndex < 0 || theirIndex >= versionFiles.size()) | 	auto from = versionPatch(index); | ||||||
| 	{ | 	auto to = versionPatch(theirIndex); | ||||||
| 		return; | 	 | ||||||
| 	} | 	if (!from || !to || !from->isMoveable() || !from->isMoveable()) | ||||||
| 	const QString ourId = versionFileId(index); |  | ||||||
| 	const QString theirId = versionFileId(theirIndex); |  | ||||||
| 	if (ourId.isNull() || ourId.startsWith("org.multimc.") || |  | ||||||
| 			theirId.isNull() || theirId.startsWith("org.multimc.")) |  | ||||||
| 	{ | 	{ | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	if(direction == MoveDown) | 	if(direction == MoveDown) | ||||||
| 	{ | 	{ | ||||||
| 		beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex+1); | 		beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex_qt); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); | 		beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex_qt); | ||||||
| 	} | 	} | ||||||
| 	versionFiles.swap(index, theirIndex); | 	VersionPatches.swap(index, theirIndex); | ||||||
| 	endMoveRows(); | 	endMoveRows(); | ||||||
|  |  | ||||||
| 	auto order = getExistingOrder(); | 	auto order = getExistingOrder(); | ||||||
| 	order[ourId] = theirIndex; | 	order[from->getPatchID()] = theirIndex; | ||||||
| 	order[theirId] = index; | 	order[to->getPatchID()] = index; | ||||||
|  |  | ||||||
| 	if (!VersionBuilder::writeOverrideOrders(order, m_instance)) | 	if (!VersionBuilder::writeOverrideOrders(order, m_instance)) | ||||||
| 	{ | 	{ | ||||||
| @@ -375,14 +378,14 @@ void VersionFinal::reapply(const bool alreadyReseting) | |||||||
| 	auto existingOrders = getExistingOrder(); | 	auto existingOrders = getExistingOrder(); | ||||||
| 	QList<int> orders = existingOrders.values(); | 	QList<int> orders = existingOrders.values(); | ||||||
| 	std::sort(orders.begin(), orders.end()); | 	std::sort(orders.begin(), orders.end()); | ||||||
| 	QList<VersionFilePtr> newVersionFiles; | 	QList<VersionPatchPtr> newVersionFiles; | ||||||
| 	for (auto order : orders) | 	for (auto order : orders) | ||||||
| 	{ | 	{ | ||||||
| 		auto file = versionPatch(existingOrders.key(order)); | 		auto file = versionPatch(existingOrders.key(order)); | ||||||
| 		newVersionFiles.append(file); | 		newVersionFiles.append(file); | ||||||
| 		file->applyTo(this); | 		file->applyTo(this); | ||||||
| 	} | 	} | ||||||
| 	versionFiles.swap(newVersionFiles); | 	VersionPatches.swap(newVersionFiles); | ||||||
| 	finalize(); | 	finalize(); | ||||||
| 	if (!alreadyReseting) | 	if (!alreadyReseting) | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
| @@ -83,16 +83,21 @@ public: | |||||||
| 	static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj); | 	static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj); | ||||||
|  |  | ||||||
| private: | private: | ||||||
| 	bool preremove(VersionFilePtr); | 	bool preremove(VersionPatchPtr patch); | ||||||
| 	 | 	 | ||||||
| 	// data members | 	// data members | ||||||
| public: | public: | ||||||
| 	/// the ID - determines which jar to use! ACTUALLY IMPORTANT! | 	/// the ID - determines which jar to use! ACTUALLY IMPORTANT! | ||||||
| 	QString id; | 	QString id; | ||||||
| 	/// Last updated time - as a string |  | ||||||
| 	QString time; | 	/// the time this version was actually released by Mojang, as string and as QDateTime | ||||||
| 	/// Release time - as a string | 	QString m_releaseTimeString; | ||||||
| 	QString versionReleaseTime; | 	QDateTime m_releaseTime; | ||||||
|  |  | ||||||
|  | 	/// the time this version was last updated by Mojang, as string and as QDateTime | ||||||
|  | 	QString m_updateTimeString; | ||||||
|  | 	QDateTime m_updateTime; | ||||||
|  |  | ||||||
| 	/// Release type - "release" or "snapshot" | 	/// Release type - "release" or "snapshot" | ||||||
| 	QString type; | 	QString type; | ||||||
| 	/// Assets type - "legacy" or a version ID | 	/// Assets type - "legacy" or a version ID | ||||||
| @@ -164,8 +169,9 @@ public: | |||||||
| 	*/ | 	*/ | ||||||
| 	// QList<Rule> rules; | 	// QList<Rule> rules; | ||||||
|  |  | ||||||
| 	QList<VersionFilePtr> versionFiles; | 	QList<VersionPatchPtr> VersionPatches; | ||||||
| 	VersionFilePtr versionPatch(const QString &id); | 	VersionPatchPtr versionPatch(const QString &id); | ||||||
|  | 	VersionPatchPtr versionPatch(int index); | ||||||
|  |  | ||||||
| private: | private: | ||||||
| 	OneSixInstance *m_instance; | 	OneSixInstance *m_instance; | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <QList> | ||||||
|  | #include "JarMod.h" | ||||||
|  |  | ||||||
| class VersionFinal; | class VersionFinal; | ||||||
| class VersionPatch | class VersionPatch | ||||||
| @@ -8,8 +10,22 @@ class VersionPatch | |||||||
| public: | public: | ||||||
| 	virtual ~VersionPatch(){}; | 	virtual ~VersionPatch(){}; | ||||||
| 	virtual void applyTo(VersionFinal *version) = 0; | 	virtual void applyTo(VersionFinal *version) = 0; | ||||||
|  | 	 | ||||||
| 	virtual bool isVanilla() = 0; | 	virtual bool isVanilla() = 0; | ||||||
| 	virtual bool hasJarMods() = 0; | 	virtual bool hasJarMods() = 0; | ||||||
|  | 	virtual QList<JarmodPtr> getJarMods() = 0; | ||||||
|  | 	 | ||||||
|  | 	virtual bool isMoveable() | ||||||
|  | 	{ | ||||||
|  | 		return getOrder() >= 0; | ||||||
|  | 	} | ||||||
|  | 	virtual void setOrder(int order) = 0; | ||||||
|  | 	virtual int getOrder() = 0; | ||||||
|  | 	 | ||||||
|  | 	virtual QString getPatchID() = 0; | ||||||
|  | 	virtual QString getPatchName() = 0; | ||||||
|  | 	virtual QString getPatchVersion() = 0; | ||||||
|  | 	virtual QString getPatchFilename() = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| typedef std::shared_ptr<VersionPatch> VersionPatchPtr; | typedef std::shared_ptr<VersionPatch> VersionPatchPtr; | ||||||
|   | |||||||
| @@ -495,6 +495,7 @@ | |||||||
| 		"releaseTime": "2010-07-13T00:00:00+02:00", | 		"releaseTime": "2010-07-13T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "y", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @@ -502,6 +503,7 @@ | |||||||
| 		"releaseTime": "2010-07-09T00:00:00+02:00", | 		"releaseTime": "2010-07-09T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "ax", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @@ -509,6 +511,8 @@ | |||||||
| 		"releaseTime": "2010-06-16T00:00:00+02:00", | 		"releaseTime": "2010-06-16T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "net.minecraft.client.d", | ||||||
|  | 		"appletClass": "net.minecraft.client.MinecraftApplet", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @@ -516,6 +520,8 @@ | |||||||
| 		"releaseTime": "2009-12-22T00:00:00+02:00", | 		"releaseTime": "2009-12-22T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "com.mojang.minecraft.l", | ||||||
|  | 		"appletClass": "com.mojang.minecraft.MinecraftApplet", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @@ -523,6 +529,8 @@ | |||||||
| 		"releaseTime": "2009-05-22T00:00:00+02:00", | 		"releaseTime": "2009-05-22T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "com.mojang.minecraft.c", | ||||||
|  | 		"appletClass": "com.mojang.minecraft.MinecraftApplet", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @@ -530,6 +538,8 @@ | |||||||
| 		"releaseTime": "2009-05-31T00:00:00+02:00", | 		"releaseTime": "2009-05-31T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "com.mojang.minecraft.Minecraft", | ||||||
|  | 		"appletClass": "com.mojang.minecraft.MinecraftApplet", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @@ -537,6 +547,8 @@ | |||||||
| 		"releaseTime": "2009-05-17T00:00:00+02:00", | 		"releaseTime": "2009-05-17T00:00:00+02:00", | ||||||
| 		"type": "old_alpha", | 		"type": "old_alpha", | ||||||
| 		"processArguments": "legacy", | 		"processArguments": "legacy", | ||||||
|  | 		"mainClass": "com.mojang.minecraft.Minecraft", | ||||||
|  | 		"appletClass": "com.mojang.minecraft.MinecraftApplet", | ||||||
| 		"+traits": ["legacyLaunch"] | 		"+traits": ["legacyLaunch"] | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user