NOISSUE Remove Legacy support
This commit is contained in:
		| @@ -236,14 +236,6 @@ set(MINECRAFT_SOURCES | ||||
| 	minecraft/launch/LauncherPartLaunch.h | ||||
| 	minecraft/launch/PrintInstanceInfo.cpp | ||||
| 	minecraft/launch/PrintInstanceInfo.h | ||||
| 	minecraft/legacy/LegacyModList.h | ||||
| 	minecraft/legacy/LegacyModList.cpp | ||||
| 	minecraft/legacy/LegacyUpdate.h | ||||
| 	minecraft/legacy/LegacyUpdate.cpp | ||||
| 	minecraft/legacy/LegacyInstance.h | ||||
| 	minecraft/legacy/LegacyInstance.cpp | ||||
| 	minecraft/legacy/LwjglVersionList.h | ||||
| 	minecraft/legacy/LwjglVersionList.cpp | ||||
| 	minecraft/GradleSpecifier.h | ||||
| 	minecraft/MinecraftProfile.cpp | ||||
| 	minecraft/MinecraftProfile.h | ||||
|   | ||||
| @@ -10,7 +10,6 @@ | ||||
| #include "tasks/Task.h" | ||||
| #include "meta/Index.h" | ||||
| #include "FileSystem.h" | ||||
| #include "minecraft/legacy/LwjglVersionList.h" | ||||
| #include <QDebug> | ||||
|  | ||||
|  | ||||
| @@ -182,13 +181,4 @@ void Env::setJarsPath(const QString& path) | ||||
| 	d->m_jarsPath = path; | ||||
| } | ||||
|  | ||||
| LWJGLVersionList *Env::getLegacyLWJGL() | ||||
| { | ||||
| 	if(!d->m_lwjgllist) | ||||
| 	{ | ||||
| 		d->m_lwjgllist.reset(new LWJGLVersionList()); | ||||
| 	} | ||||
| 	return d->m_lwjgllist.get(); | ||||
| } | ||||
|  | ||||
| #include "Env.moc" | ||||
|   | ||||
| @@ -53,8 +53,6 @@ public: | ||||
|  | ||||
| 	shared_qobject_ptr<Meta::Index> metadataIndex(); | ||||
|  | ||||
| 	LWJGLVersionList *getLegacyLWJGL(); | ||||
|  | ||||
| 	QString getJarsPath(); | ||||
| 	void setJarsPath(const QString & path); | ||||
| protected: | ||||
|   | ||||
| @@ -2,7 +2,6 @@ | ||||
| #include "settings/INISettingsObject.h" | ||||
| #include "FileSystem.h" | ||||
| #include "minecraft/onesix/OneSixInstance.h" | ||||
| #include "minecraft/legacy/LegacyInstance.h" | ||||
| #include "NullInstance.h" | ||||
|  | ||||
| #include <QDir> | ||||
| @@ -91,10 +90,6 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) | ||||
| 	{ | ||||
| 		inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot)); | ||||
| 	} | ||||
| 	else if (inst_type == "Legacy") | ||||
| 	{ | ||||
| 		inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot)); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|  | ||||
| #include "multimc_logic_export.h" | ||||
|  | ||||
| class LegacyInstance; | ||||
| class BaseInstance; | ||||
| class QFileSystemWatcher; | ||||
|  | ||||
|   | ||||
| @@ -1,521 +0,0 @@ | ||||
| /* Copyright 2013-2017 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include <QFileInfo> | ||||
| #include <minecraft/launch/LauncherPartLaunch.h> | ||||
| #include <QDir> | ||||
| #include <settings/Setting.h> | ||||
|  | ||||
| #include "LegacyInstance.h" | ||||
|  | ||||
| #include "minecraft/legacy/LegacyUpdate.h" | ||||
| #include "minecraft/legacy/LegacyModList.h" | ||||
| #include "minecraft/ModList.h" | ||||
| #include "minecraft/WorldList.h" | ||||
| #include <MMCZip.h> | ||||
| #include <FileSystem.h> | ||||
|  | ||||
| LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) | ||||
| 	: MinecraftInstance(globalSettings, settings, rootDir) | ||||
| { | ||||
| 	m_lwjglFolderSetting = globalSettings->getSetting("LWJGLDir"); | ||||
| 	settings->registerSetting("NeedsRebuild", true); | ||||
| 	settings->registerSetting("ShouldUpdate", false); | ||||
| 	settings->registerSetting("JarVersion", "Unknown"); | ||||
| 	settings->registerSetting("LwjglVersion", "2.9.0"); | ||||
| 	settings->registerSetting("IntendedJarVersion", ""); | ||||
| 	/* | ||||
| 	 * custom base jar has no default. it is determined in code... see the accessor methods for | ||||
| 	 *it | ||||
| 	 * | ||||
| 	 * for instances that DO NOT have the CustomBaseJar setting (legacy instances), | ||||
| 	 * [.]minecraft/bin/mcbackup.jar is the default base jar | ||||
| 	 */ | ||||
| 	settings->registerSetting("UseCustomBaseJar", true); | ||||
| 	settings->registerSetting("CustomBaseJar", ""); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::baseJar() const | ||||
| { | ||||
| 	bool customJar = m_settings->get("UseCustomBaseJar").toBool(); | ||||
| 	if (customJar) | ||||
| 	{ | ||||
| 		return customBaseJar(); | ||||
| 	} | ||||
| 	else | ||||
| 		return defaultBaseJar(); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::customBaseJar() const | ||||
| { | ||||
| 	QString value = m_settings->get("CustomBaseJar").toString(); | ||||
| 	if (value.isNull() || value.isEmpty()) | ||||
| 	{ | ||||
| 		return defaultCustomBaseJar(); | ||||
| 	} | ||||
| 	return value; | ||||
| } | ||||
|  | ||||
| void LegacyInstance::setCustomBaseJar(QString val) | ||||
| { | ||||
| 	if (val.isNull() || val.isEmpty() || val == defaultCustomBaseJar()) | ||||
| 		m_settings->reset("CustomBaseJar"); | ||||
| 	else | ||||
| 		m_settings->set("CustomBaseJar", val); | ||||
| } | ||||
|  | ||||
| void LegacyInstance::setShouldUseCustomBaseJar(bool val) | ||||
| { | ||||
| 	m_settings->set("UseCustomBaseJar", val); | ||||
| } | ||||
|  | ||||
| bool LegacyInstance::shouldUseCustomBaseJar() const | ||||
| { | ||||
| 	return m_settings->get("UseCustomBaseJar").toBool(); | ||||
| } | ||||
|  | ||||
|  | ||||
| shared_qobject_ptr<Task> LegacyInstance::createUpdateTask() | ||||
| { | ||||
| 	// make sure the jar mods list is initialized by asking for it. | ||||
| 	auto list = jarModList(); | ||||
| 	// create an update task | ||||
| 	return shared_qobject_ptr<Task>(new LegacyUpdate(this, this)); | ||||
| } | ||||
|  | ||||
| class LegacyJarModTask : public Task | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit LegacyJarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst) | ||||
| 	{ | ||||
| 	} | ||||
| 	virtual void executeTask() | ||||
| 	{ | ||||
| 		if (!m_inst->shouldRebuild()) | ||||
| 		{ | ||||
| 			emitSucceeded(); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// Get the mod list | ||||
| 		auto modList = m_inst->getJarMods(); | ||||
|  | ||||
| 		QFileInfo runnableJar(m_inst->runnableJar()); | ||||
| 		QFileInfo baseJar(m_inst->baseJar()); | ||||
| 		bool base_is_custom = m_inst->shouldUseCustomBaseJar(); | ||||
|  | ||||
| 		// Nothing to do if there are no jar mods to install, no backup and just the mc jar | ||||
| 		if (base_is_custom) | ||||
| 		{ | ||||
| 			// yes, this can happen if the instance only has the runnable jar and not the base jar | ||||
| 			// it *could* be assumed that such an instance is vanilla, but that wouldn't be safe | ||||
| 			// because that's not something mmc4 guarantees | ||||
| 			if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) | ||||
| 			{ | ||||
| 				m_inst->setShouldRebuild(false); | ||||
| 				emitSucceeded(); | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			setStatus(tr("Installing mods: Backing up minecraft.jar ...")); | ||||
| 			if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) | ||||
| 			{ | ||||
| 				emitFailed("It seems both the active and base jar are gone. A fresh base jar will " | ||||
| 						"be used on next run."); | ||||
| 				m_inst->setShouldRebuild(true); | ||||
| 				m_inst->setShouldUpdate(true); | ||||
| 				m_inst->setShouldUseCustomBaseJar(false); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (!baseJar.exists()) | ||||
| 		{ | ||||
| 			emitFailed("The base jar " + baseJar.filePath() + " does not exist"); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) | ||||
| 		{ | ||||
| 			emitFailed("Failed to delete old minecraft.jar"); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		setStatus(tr("Installing mods: Opening minecraft.jar ...")); | ||||
|  | ||||
| 		QString outputJarPath = runnableJar.filePath(); | ||||
| 		QString inputJarPath = baseJar.filePath(); | ||||
|  | ||||
| 		if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) | ||||
| 		{ | ||||
| 			emitFailed(tr("Failed to create the custom Minecraft jar file.")); | ||||
| 			return; | ||||
| 		} | ||||
| 		m_inst->setShouldRebuild(false); | ||||
| 		// inst->UpdateVersion(true); | ||||
| 		emitSucceeded(); | ||||
| 		return; | ||||
|  | ||||
| 	} | ||||
| 	std::shared_ptr<LegacyInstance> m_inst; | ||||
| }; | ||||
|  | ||||
| std::shared_ptr<Task> LegacyInstance::createJarModdingTask() | ||||
| { | ||||
| 	return std::make_shared<LegacyJarModTask>(std::dynamic_pointer_cast<LegacyInstance>(shared_from_this())); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::createLaunchScript(AuthSessionPtr session) | ||||
| { | ||||
| 	QString launchScript; | ||||
|  | ||||
| 	// window size | ||||
| 	QString windowParams; | ||||
| 	if (settings()->get("LaunchMaximized").toBool()) | ||||
| 	{ | ||||
| 		windowParams = "max"; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		windowParams = QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt()); | ||||
| 	} | ||||
|  | ||||
| 	QString lwjgl = QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath(); | ||||
| 	launchScript += "userName " + session->player_name + "\n"; | ||||
| 	launchScript += "sessionId " + session->session + "\n"; | ||||
| 	launchScript += "windowTitle " + windowTitle() + "\n"; | ||||
| 	launchScript += "windowParams " + windowParams + "\n"; | ||||
| 	launchScript += "cp bin/minecraft.jar\n"; | ||||
| 	launchScript += "cp " + lwjgl + "/lwjgl.jar\n"; | ||||
| 	launchScript += "cp " + lwjgl + "/lwjgl_util.jar\n"; | ||||
| 	launchScript += "cp " + lwjgl + "/jinput.jar\n"; | ||||
| 	launchScript += "natives " + lwjgl + "/natives\n"; | ||||
| 	launchScript += "traits legacyLaunch\n"; | ||||
| 	launchScript += "launcher onesix\n"; | ||||
| 	return launchScript; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<LaunchStep> LegacyInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session) | ||||
| { | ||||
| 	auto step = std::make_shared<LauncherPartLaunch>(parent); | ||||
| 	step->setWorkingDirectory(minecraftRoot()); | ||||
| 	step->setAuthSession(session); | ||||
| 	return step; | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::launchMethod() | ||||
| { | ||||
| 	return "Legacy"; | ||||
| } | ||||
|  | ||||
| QStringList LegacyInstance::validLaunchMethods() | ||||
| { | ||||
| 	return {"Legacy"}; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> LegacyInstance::coreModList() const | ||||
| { | ||||
| 	if (!core_mod_list) | ||||
| 	{ | ||||
| 		core_mod_list.reset(new ModList(coreModsDir())); | ||||
| 	} | ||||
| 	core_mod_list->update(); | ||||
| 	return core_mod_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const | ||||
| { | ||||
| 	if (!jar_mod_list) | ||||
| 	{ | ||||
| 		auto list = new LegacyModList(jarModsDir(), modListFile()); | ||||
| 		connect(list, SIGNAL(changed()), SLOT(jarModsChanged())); | ||||
| 		jar_mod_list.reset(list); | ||||
| 	} | ||||
| 	jar_mod_list->update(); | ||||
| 	return jar_mod_list; | ||||
| } | ||||
|  | ||||
| QList<Mod> LegacyInstance::getJarMods() const | ||||
| { | ||||
| 	return jarModList()->allMods(); | ||||
| } | ||||
|  | ||||
| void LegacyInstance::jarModsChanged() | ||||
| { | ||||
| 	qDebug() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt."; | ||||
| 	setShouldRebuild(true); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> LegacyInstance::loaderModList() const | ||||
| { | ||||
| 	if (!loader_mod_list) | ||||
| 	{ | ||||
| 		loader_mod_list.reset(new ModList(loaderModsDir())); | ||||
| 	} | ||||
| 	loader_mod_list->update(); | ||||
| 	return loader_mod_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> LegacyInstance::texturePackList() const | ||||
| { | ||||
| 	if (!texture_pack_list) | ||||
| 	{ | ||||
| 		texture_pack_list.reset(new ModList(texturePacksDir())); | ||||
| 	} | ||||
| 	texture_pack_list->update(); | ||||
| 	return texture_pack_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<WorldList> LegacyInstance::worldList() const | ||||
| { | ||||
| 	if (!m_world_list) | ||||
| 	{ | ||||
| 		m_world_list.reset(new WorldList(savesDir())); | ||||
| 	} | ||||
| 	return m_world_list; | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::jarModsDir() const | ||||
| { | ||||
| 	return FS::PathCombine(instanceRoot(), "instMods"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::libDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "lib"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::savesDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "saves"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::loaderModsDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "mods"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::coreModsDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "coremods"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::resourceDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "resources"); | ||||
| } | ||||
| QString LegacyInstance::texturePacksDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "texturepacks"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::runnableJar() const | ||||
| { | ||||
| 	return FS::PathCombine(binRoot(), "minecraft.jar"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::modListFile() const | ||||
| { | ||||
| 	return FS::PathCombine(instanceRoot(), "modlist"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::instanceConfigFolder() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "config"); | ||||
| } | ||||
|  | ||||
| bool LegacyInstance::shouldRebuild() const | ||||
| { | ||||
| 	return m_settings->get("NeedsRebuild").toBool(); | ||||
| } | ||||
|  | ||||
| void LegacyInstance::setShouldRebuild(bool val) | ||||
| { | ||||
| 	m_settings->set("NeedsRebuild", val); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::currentVersionId() const | ||||
| { | ||||
| 	return m_settings->get("JarVersion").toString(); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::lwjglVersion() const | ||||
| { | ||||
| 	return m_settings->get("LwjglVersion").toString(); | ||||
| } | ||||
|  | ||||
| void LegacyInstance::setLWJGLVersion(QString val) | ||||
| { | ||||
| 	m_settings->set("LwjglVersion", val); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::intendedVersionId() const | ||||
| { | ||||
| 	return m_settings->get("IntendedJarVersion").toString(); | ||||
| } | ||||
|  | ||||
| bool LegacyInstance::setIntendedVersionId(QString version) | ||||
| { | ||||
| 	settings()->set("IntendedJarVersion", version); | ||||
| 	setShouldUpdate(true); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool LegacyInstance::shouldUpdate() const | ||||
| { | ||||
| 	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); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::defaultBaseJar() const | ||||
| { | ||||
| 	return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::defaultCustomBaseJar() const | ||||
| { | ||||
| 	return FS::PathCombine(binRoot(), "mcbackup.jar"); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::lwjglFolder() const | ||||
| { | ||||
| 	return m_lwjglFolderSetting->get().toString(); | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::typeName() const | ||||
| { | ||||
| 	return tr("Legacy"); | ||||
| } | ||||
|  | ||||
| QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) | ||||
| { | ||||
| 	QStringList out; | ||||
|  | ||||
| 	auto alltraits = traits(); | ||||
| 	if(alltraits.size()) | ||||
| 	{ | ||||
| 		out << "Traits:"; | ||||
| 		for (auto trait : alltraits) | ||||
| 		{ | ||||
| 			out << "  " + trait; | ||||
| 		} | ||||
| 		out << ""; | ||||
| 	} | ||||
|  | ||||
| 	if(loaderModList()->size()) | ||||
| 	{ | ||||
| 		out << "Mods:"; | ||||
| 		for(auto & mod: loaderModList()->allMods()) | ||||
| 		{ | ||||
| 			if(!mod.enabled()) | ||||
| 				continue; | ||||
| 			if(mod.type() == Mod::MOD_FOLDER) | ||||
| 				continue; | ||||
| 			// TODO: proper implementation would need to descend into folders. | ||||
|  | ||||
| 			out << "  " + mod.filename().completeBaseName(); | ||||
| 		} | ||||
| 		out << ""; | ||||
| 	} | ||||
|  | ||||
| 	if(coreModList()->size()) | ||||
| 	{ | ||||
| 		out << "Core Mods:"; | ||||
| 		for(auto & coremod: coreModList()->allMods()) | ||||
| 		{ | ||||
| 			if(!coremod.enabled()) | ||||
| 				continue; | ||||
| 			if(coremod.type() == Mod::MOD_FOLDER) | ||||
| 				continue; | ||||
| 			// TODO: proper implementation would need to descend into folders. | ||||
|  | ||||
| 			out << "  " + coremod.filename().completeBaseName(); | ||||
| 		} | ||||
| 		out << ""; | ||||
| 	} | ||||
|  | ||||
| 	if(jarModList()->size()) | ||||
| 	{ | ||||
| 		out << "Jar Mods:"; | ||||
| 		for(auto & jarmod: jarModList()->allMods()) | ||||
| 		{ | ||||
| 			out << "  " + jarmod.name() + " (" + jarmod.filename().filePath() + ")"; | ||||
| 		} | ||||
| 		out << ""; | ||||
| 	} | ||||
|  | ||||
| 	QString windowParams; | ||||
| 	if (settings()->get("LaunchMaximized").toBool()) | ||||
| 	{ | ||||
| 		out << "Window size: max (if available)"; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		auto width = settings()->get("MinecraftWinWidth").toInt(); | ||||
| 		auto height = settings()->get("MinecraftWinHeight").toInt(); | ||||
| 		out << "Window size: " + QString::number(width) + " x " + QString::number(height); | ||||
| 	} | ||||
| 	out << ""; | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QStringList LegacyInstance::getClassPath() const | ||||
| { | ||||
| 	QString launchScript; | ||||
| 	QString lwjgl = getNativePath(); | ||||
| 	QStringList out = | ||||
| 	{ | ||||
| 		"bin/minecraft.jar", | ||||
| 		lwjgl + "/lwjgl.jar", | ||||
| 		lwjgl + "/lwjgl_util.jar", | ||||
| 		lwjgl + "/jinput.jar" | ||||
| 	}; | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::getMainClass() const | ||||
| { | ||||
| 	return "net.minecraft.client.Minecraft"; | ||||
| } | ||||
|  | ||||
| QString LegacyInstance::getNativePath() const | ||||
| { | ||||
| 	return QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath(); | ||||
| } | ||||
|  | ||||
| QStringList LegacyInstance::getNativeJars() const | ||||
| { | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| QStringList LegacyInstance::processMinecraftArgs(AuthSessionPtr account) const | ||||
| { | ||||
| 	QStringList out; | ||||
| 	out.append(account->player_name); | ||||
| 	out.append(account->session); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| #include "LegacyInstance.moc" | ||||
| @@ -1,155 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 "minecraft/MinecraftInstance.h" | ||||
|  | ||||
| #include "multimc_logic_export.h" | ||||
|  | ||||
| class ModList; | ||||
| class LegacyModList; | ||||
| class Task; | ||||
|  | ||||
| class MULTIMC_LOGIC_EXPORT LegacyInstance : public MinecraftInstance | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
|  | ||||
| 	explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); | ||||
|  | ||||
| 	virtual void init() override {}; | ||||
|  | ||||
| 	/// Path to the instance's minecraft.jar | ||||
| 	QString runnableJar() const; | ||||
|  | ||||
| 	//! Path to the instance's modlist file. | ||||
| 	QString modListFile() const; | ||||
|  | ||||
| 	/* | ||||
| 	////// Edit Instance Dialog stuff ////// | ||||
| 	virtual QList<BasePage *> getPages(); | ||||
| 	virtual QString dialogTitle(); | ||||
| 	*/ | ||||
|  | ||||
| 	//////  Mod Lists  ////// | ||||
| 	std::shared_ptr<LegacyModList> jarModList() const ; | ||||
| 	virtual QList< Mod > getJarMods() const override; | ||||
| 	std::shared_ptr<ModList> coreModList() const; | ||||
| 	std::shared_ptr<ModList> loaderModList() const; | ||||
| 	std::shared_ptr<ModList> texturePackList() const override; | ||||
| 	std::shared_ptr<WorldList> worldList() const override; | ||||
|  | ||||
| 	////// Directories ////// | ||||
| 	QString libDir() const; | ||||
| 	QString savesDir() const; | ||||
| 	QString texturePacksDir() const; | ||||
| 	QString jarModsDir() const; | ||||
| 	QString loaderModsDir() const; | ||||
| 	QString coreModsDir() const; | ||||
| 	QString resourceDir() const; | ||||
| 	virtual QString instanceConfigFolder() const override; | ||||
|  | ||||
| 		/// Get the curent base jar of this instance. By default, it's the | ||||
| 	/// versions/$version/$version.jar | ||||
| 	QString baseJar() const; | ||||
|  | ||||
| 	/// the default base jar of this instance | ||||
| 	QString defaultBaseJar() const; | ||||
| 	/// the default custom base jar of this instance | ||||
| 	QString defaultCustomBaseJar() const; | ||||
|  | ||||
| 	/*! | ||||
| 	 * Whether or not custom base jar is used | ||||
| 	 */ | ||||
| 	bool shouldUseCustomBaseJar() const; | ||||
| 	void setShouldUseCustomBaseJar(bool val); | ||||
|  | ||||
| 	/*! | ||||
| 	 * The value of the custom base jar | ||||
| 	 */ | ||||
| 	QString customBaseJar() const; | ||||
| 	void setCustomBaseJar(QString 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 override; | ||||
|  | ||||
| 	//! The version of LWJGL that this instance uses. | ||||
| 	QString lwjglVersion() const; | ||||
|  | ||||
| 	//! Where the lwjgl versions foor this instance can be found... HACK HACK HACK | ||||
| 	QString lwjglFolder() const; | ||||
|  | ||||
| 	/// st the version of LWJGL libs this instance will use | ||||
| 	void setLWJGLVersion(QString val); | ||||
|  | ||||
| 	virtual QString intendedVersionId() const override; | ||||
| 	virtual bool setIntendedVersionId(QString version) override; | ||||
|  | ||||
| 	virtual QSet<QString> traits() override | ||||
| 	{ | ||||
| 		return {"legacy-instance", "texturepacks"}; | ||||
| 	}; | ||||
|  | ||||
| 	virtual bool shouldUpdate() const override; | ||||
| 	virtual void setShouldUpdate(bool val) override; | ||||
| 	virtual shared_qobject_ptr<Task> createUpdateTask() override; | ||||
| 	virtual std::shared_ptr<Task> createJarModdingTask() override; | ||||
| 	virtual QString createLaunchScript(AuthSessionPtr session) override; | ||||
|  | ||||
| 	virtual QString typeName() const override; | ||||
|  | ||||
| 	bool canExport() const override | ||||
| 	{ | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	QStringList getClassPath() const override; | ||||
| 	QString getMainClass() const override; | ||||
|  | ||||
| 	QStringList getNativeJars() const override; | ||||
| 	QString getNativePath() const override; | ||||
|  | ||||
| 	QString getLocalLibraryPath() const override | ||||
| 	{ | ||||
| 		return QString(); | ||||
| 	} | ||||
|  | ||||
| 	QStringList processMinecraftArgs(AuthSessionPtr account) const override; | ||||
| 	QStringList verboseDescription(AuthSessionPtr session) override; | ||||
|  | ||||
| protected: | ||||
| 	std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override; | ||||
| 	QStringList validLaunchMethods() override; | ||||
| 	QString launchMethod() override; | ||||
|  | ||||
| protected: | ||||
| 	mutable std::shared_ptr<LegacyModList> jar_mod_list; | ||||
| 	mutable std::shared_ptr<ModList> core_mod_list; | ||||
| 	mutable std::shared_ptr<ModList> loader_mod_list; | ||||
| 	mutable std::shared_ptr<ModList> texture_pack_list; | ||||
| 	mutable std::shared_ptr<WorldList> m_world_list; | ||||
| 	std::shared_ptr<Setting> m_lwjglFolderSetting; | ||||
| protected | ||||
| slots: | ||||
| 	virtual void jarModsChanged(); | ||||
| }; | ||||
| @@ -1,616 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 "LegacyModList.h" | ||||
| #include <FileSystem.h> | ||||
| #include <QMimeData> | ||||
| #include <QUrl> | ||||
| #include <QUuid> | ||||
| #include <QString> | ||||
| #include <QFileSystemWatcher> | ||||
| #include <QDebug> | ||||
|  | ||||
| LegacyModList::LegacyModList(const QString &dir, const QString &list_file) | ||||
| 	: QAbstractListModel(), m_dir(dir), m_list_file(list_file) | ||||
| { | ||||
| 	FS::ensureFolderPathExists(m_dir.absolutePath()); | ||||
| 	m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | | ||||
| 					QDir::NoSymLinks); | ||||
| 	m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); | ||||
| 	m_list_id = QUuid::createUuid().toString(); | ||||
| 	m_watcher = new QFileSystemWatcher(this); | ||||
| 	is_watching = false; | ||||
| 	connect(m_watcher, SIGNAL(directoryChanged(QString)), this, | ||||
| 			SLOT(directoryChanged(QString))); | ||||
| } | ||||
|  | ||||
| void LegacyModList::startWatching() | ||||
| { | ||||
| 	update(); | ||||
| 	is_watching = m_watcher->addPath(m_dir.absolutePath()); | ||||
| 	if (is_watching) | ||||
| 	{ | ||||
| 		qDebug() << "Started watching " << m_dir.absolutePath(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		qDebug() << "Failed to start watching " << m_dir.absolutePath(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void LegacyModList::stopWatching() | ||||
| { | ||||
| 	is_watching = !m_watcher->removePath(m_dir.absolutePath()); | ||||
| 	if (!is_watching) | ||||
| 	{ | ||||
| 		qDebug() << "Stopped watching " << m_dir.absolutePath(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		qDebug() << "Failed to stop watching " << m_dir.absolutePath(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void LegacyModList::internalSort(QList<Mod> &what) | ||||
| { | ||||
| 	auto predicate = [](const Mod &left, const Mod &right) | ||||
| 	{ | ||||
| 		if (left.name() == right.name()) | ||||
| 		{ | ||||
| 			return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0; | ||||
| 		} | ||||
| 		return left.name().localeAwareCompare(right.name()) < 0; | ||||
| 	}; | ||||
| 	std::sort(what.begin(), what.end(), predicate); | ||||
| } | ||||
|  | ||||
| bool LegacyModList::update() | ||||
| { | ||||
| 	if (!isValid()) | ||||
| 		return false; | ||||
|  | ||||
| 	QList<Mod> orderedMods; | ||||
| 	QList<Mod> newMods; | ||||
| 	m_dir.refresh(); | ||||
| 	auto folderContents = m_dir.entryInfoList(); | ||||
| 	bool orderOrStateChanged = false; | ||||
|  | ||||
| 	// first, process the ordered items (if any) | ||||
| 	OrderList listOrder = readListFile(); | ||||
| 	for (auto item : listOrder) | ||||
| 	{ | ||||
| 		QFileInfo infoEnabled(m_dir.filePath(item.id)); | ||||
| 		QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); | ||||
| 		int idxEnabled = folderContents.indexOf(infoEnabled); | ||||
| 		int idxDisabled = folderContents.indexOf(infoDisabled); | ||||
| 		bool isEnabled; | ||||
| 		// if both enabled and disabled versions are present, it's a special case... | ||||
| 		if (idxEnabled >= 0 && idxDisabled >= 0) | ||||
| 		{ | ||||
| 			// we only process the one we actually have in the order file. | ||||
| 			// and exactly as we have it. | ||||
| 			// THIS IS A CORNER CASE | ||||
| 			isEnabled = item.enabled; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// only one is present. | ||||
| 			// we pick the one that we found. | ||||
| 			// we assume the mod was enabled/disabled by external means | ||||
| 			isEnabled = idxEnabled >= 0; | ||||
| 		} | ||||
| 		int idx = isEnabled ? idxEnabled : idxDisabled; | ||||
| 		QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; | ||||
| 		// if the file from the index file exists | ||||
| 		if (idx != -1) | ||||
| 		{ | ||||
| 			// remove from the actual folder contents list | ||||
| 			folderContents.takeAt(idx); | ||||
| 			// append the new mod | ||||
| 			orderedMods.append(Mod(info)); | ||||
| 			if (isEnabled != item.enabled) | ||||
| 				orderOrStateChanged = true; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			orderOrStateChanged = true; | ||||
| 		} | ||||
| 	} | ||||
| 	// if there are any untracked files... | ||||
| 	if (folderContents.size()) | ||||
| 	{ | ||||
| 		// the order surely changed! | ||||
| 		for (auto entry : folderContents) | ||||
| 		{ | ||||
| 			newMods.append(Mod(entry)); | ||||
| 		} | ||||
| 		internalSort(newMods); | ||||
| 		orderedMods.append(newMods); | ||||
| 		orderOrStateChanged = true; | ||||
| 	} | ||||
| 	// otherwise, if we were already tracking some mods | ||||
| 	else if (mods.size()) | ||||
| 	{ | ||||
| 		// if the number doesn't match, order changed. | ||||
| 		if (mods.size() != orderedMods.size()) | ||||
| 			orderOrStateChanged = true; | ||||
| 		// if it does match, compare the mods themselves | ||||
| 		else | ||||
| 			for (int i = 0; i < mods.size(); i++) | ||||
| 			{ | ||||
| 				if (!mods[i].strongCompare(orderedMods[i])) | ||||
| 				{ | ||||
| 					orderOrStateChanged = true; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 	} | ||||
| 	beginResetModel(); | ||||
| 	mods.swap(orderedMods); | ||||
| 	endResetModel(); | ||||
| 	if (orderOrStateChanged && !m_list_file.isEmpty()) | ||||
| 	{ | ||||
| 		qDebug() << "Mod list " << m_list_file << " changed!"; | ||||
| 		saveListFile(); | ||||
| 		emit changed(); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void LegacyModList::directoryChanged(QString path) | ||||
| { | ||||
| 	update(); | ||||
| } | ||||
|  | ||||
| LegacyModList::OrderList LegacyModList::readListFile() | ||||
| { | ||||
| 	OrderList itemList; | ||||
| 	if (m_list_file.isNull() || m_list_file.isEmpty()) | ||||
| 		return itemList; | ||||
|  | ||||
| 	QFile textFile(m_list_file); | ||||
| 	if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) | ||||
| 		return OrderList(); | ||||
|  | ||||
| 	QTextStream textStream; | ||||
| 	textStream.setAutoDetectUnicode(true); | ||||
| 	textStream.setDevice(&textFile); | ||||
| 	while (true) | ||||
| 	{ | ||||
| 		QString line = textStream.readLine(); | ||||
| 		if (line.isNull() || line.isEmpty()) | ||||
| 			break; | ||||
| 		else | ||||
| 		{ | ||||
| 			OrderItem it; | ||||
| 			it.enabled = !line.endsWith(".disabled"); | ||||
| 			if (!it.enabled) | ||||
| 			{ | ||||
| 				line.chop(9); | ||||
| 			} | ||||
| 			it.id = line; | ||||
| 			itemList.append(it); | ||||
| 		} | ||||
| 	} | ||||
| 	textFile.close(); | ||||
| 	return itemList; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::saveListFile() | ||||
| { | ||||
| 	if (m_list_file.isNull() || m_list_file.isEmpty()) | ||||
| 		return false; | ||||
| 	QFile textFile(m_list_file); | ||||
| 	if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) | ||||
| 		return false; | ||||
| 	QTextStream textStream; | ||||
| 	textStream.setGenerateByteOrderMark(true); | ||||
| 	textStream.setCodec("UTF-8"); | ||||
| 	textStream.setDevice(&textFile); | ||||
| 	for (auto mod : mods) | ||||
| 	{ | ||||
| 		textStream << mod.mmc_id(); | ||||
| 		if (!mod.enabled()) | ||||
| 			textStream << ".disabled"; | ||||
| 		textStream << endl; | ||||
| 	} | ||||
| 	textFile.close(); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::isValid() | ||||
| { | ||||
| 	return m_dir.exists() && m_dir.isReadable(); | ||||
| } | ||||
|  | ||||
| bool LegacyModList::installMod(const QString &filename, int index) | ||||
| { | ||||
| 	// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName | ||||
| 	QFileInfo fileinfo(FS::NormalizePath(filename)); | ||||
|  | ||||
| 	qDebug() << "installing: " << fileinfo.absoluteFilePath(); | ||||
|  | ||||
| 	if (!fileinfo.exists() || !fileinfo.isReadable() || index < 0) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	Mod m(fileinfo); | ||||
| 	if (!m.valid()) | ||||
| 		return false; | ||||
|  | ||||
| 	// if it's already there, replace the original mod (in place) | ||||
| 	int idx = mods.indexOf(m); | ||||
| 	if (idx != -1) | ||||
| 	{ | ||||
| 		int idx2 = mods.indexOf(m, idx + 1); | ||||
| 		if (idx2 != -1) | ||||
| 			return false; | ||||
| 		if (mods[idx].replace(m)) | ||||
| 		{ | ||||
|  | ||||
| 			auto left = this->index(index); | ||||
| 			auto right = this->index(index, columnCount(QModelIndex()) - 1); | ||||
| 			emit dataChanged(left, right); | ||||
| 			saveListFile(); | ||||
| 			update(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	auto type = m.type(); | ||||
| 	if (type == Mod::MOD_UNKNOWN) | ||||
| 		return false; | ||||
| 	if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) | ||||
| 	{ | ||||
| 		QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName()); | ||||
| 		if (!QFile::copy(fileinfo.filePath(), newpath)) | ||||
| 			return false; | ||||
| 		m.repath(newpath); | ||||
| 		beginInsertRows(QModelIndex(), index, index); | ||||
| 		mods.insert(index, m); | ||||
| 		endInsertRows(); | ||||
| 		saveListFile(); | ||||
| 		update(); | ||||
| 		return true; | ||||
| 	} | ||||
| 	else if (type == Mod::MOD_FOLDER) | ||||
| 	{ | ||||
|  | ||||
| 		QString from = fileinfo.filePath(); | ||||
| 		QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName()); | ||||
| 		if (!FS::copy(from, to)()) | ||||
| 			return false; | ||||
| 		m.repath(to); | ||||
| 		beginInsertRows(QModelIndex(), index, index); | ||||
| 		mods.insert(index, m); | ||||
| 		endInsertRows(); | ||||
| 		saveListFile(); | ||||
| 		update(); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::deleteMod(int index) | ||||
| { | ||||
| 	if (index >= mods.size() || index < 0) | ||||
| 		return false; | ||||
| 	Mod &m = mods[index]; | ||||
| 	if (m.destroy()) | ||||
| 	{ | ||||
| 		beginRemoveRows(QModelIndex(), index, index); | ||||
| 		mods.removeAt(index); | ||||
| 		endRemoveRows(); | ||||
| 		saveListFile(); | ||||
| 		emit changed(); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::deleteMods(int first, int last) | ||||
| { | ||||
| 	for (int i = first; i <= last; i++) | ||||
| 	{ | ||||
| 		Mod &m = mods[i]; | ||||
| 		m.destroy(); | ||||
| 	} | ||||
| 	beginRemoveRows(QModelIndex(), first, last); | ||||
| 	mods.erase(mods.begin() + first, mods.begin() + last + 1); | ||||
| 	endRemoveRows(); | ||||
| 	saveListFile(); | ||||
| 	emit changed(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::moveModTo(int from, int to) | ||||
| { | ||||
| 	if (from < 0 || from >= mods.size()) | ||||
| 		return false; | ||||
| 	if (to >= rowCount()) | ||||
| 		to = rowCount() - 1; | ||||
| 	if (to == -1) | ||||
| 		to = rowCount() - 1; | ||||
| 	if (from == to) | ||||
| 		return false; | ||||
| 	int togap = to > from ? to + 1 : to; | ||||
| 	beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap); | ||||
| 	mods.move(from, to); | ||||
| 	endMoveRows(); | ||||
| 	saveListFile(); | ||||
| 	emit changed(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::moveModUp(int from) | ||||
| { | ||||
| 	if (from > 0) | ||||
| 		return moveModTo(from, from - 1); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::moveModsUp(int first, int last) | ||||
| { | ||||
| 	if (first == 0) | ||||
| 		return false; | ||||
|  | ||||
| 	beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1); | ||||
| 	mods.move(first - 1, last); | ||||
| 	endMoveRows(); | ||||
| 	saveListFile(); | ||||
| 	emit changed(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::moveModDown(int from) | ||||
| { | ||||
| 	if (from < 0) | ||||
| 		return false; | ||||
| 	if (from < mods.size() - 1) | ||||
| 		return moveModTo(from, from + 1); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::moveModsDown(int first, int last) | ||||
| { | ||||
| 	if (last == mods.size() - 1) | ||||
| 		return false; | ||||
|  | ||||
| 	beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2); | ||||
| 	mods.move(last + 1, first); | ||||
| 	endMoveRows(); | ||||
| 	saveListFile(); | ||||
| 	emit changed(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| int LegacyModList::columnCount(const QModelIndex &parent) const | ||||
| { | ||||
| 	return 3; | ||||
| } | ||||
|  | ||||
| QVariant LegacyModList::data(const QModelIndex &index, int role) const | ||||
| { | ||||
| 	if (!index.isValid()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	int row = index.row(); | ||||
| 	int column = index.column(); | ||||
|  | ||||
| 	if (row < 0 || row >= mods.size()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	switch (role) | ||||
| 	{ | ||||
| 	case Qt::DisplayRole: | ||||
| 		switch (column) | ||||
| 		{ | ||||
| 		case NameColumn: | ||||
| 			return mods[row].name(); | ||||
| 		case VersionColumn: | ||||
| 			return mods[row].version(); | ||||
|  | ||||
| 		default: | ||||
| 			return QVariant(); | ||||
| 		} | ||||
|  | ||||
| 	case Qt::ToolTipRole: | ||||
| 		return mods[row].mmc_id(); | ||||
|  | ||||
| 	case Qt::CheckStateRole: | ||||
| 		switch (column) | ||||
| 		{ | ||||
| 		case ActiveColumn: | ||||
| 			return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; | ||||
| 		default: | ||||
| 			return QVariant(); | ||||
| 		} | ||||
| 	default: | ||||
| 		return QVariant(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool LegacyModList::setData(const QModelIndex &index, const QVariant &value, int role) | ||||
| { | ||||
| 	if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (role == Qt::CheckStateRole) | ||||
| 	{ | ||||
| 		auto &mod = mods[index.row()]; | ||||
| 		if (mod.enable(!mod.enabled())) | ||||
| 		{ | ||||
| 			emit dataChanged(index, index); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| QVariant LegacyModList::headerData(int section, Qt::Orientation orientation, int role) const | ||||
| { | ||||
| 	switch (role) | ||||
| 	{ | ||||
| 	case Qt::DisplayRole: | ||||
| 		switch (section) | ||||
| 		{ | ||||
| 		case ActiveColumn: | ||||
| 			return QString(); | ||||
| 		case NameColumn: | ||||
| 			return tr("Name"); | ||||
| 		case VersionColumn: | ||||
| 			return tr("Version"); | ||||
| 		default: | ||||
| 			return QVariant(); | ||||
| 		} | ||||
|  | ||||
| 	case Qt::ToolTipRole: | ||||
| 		switch (section) | ||||
| 		{ | ||||
| 		case ActiveColumn: | ||||
| 			return tr("Is the mod enabled?"); | ||||
| 		case NameColumn: | ||||
| 			return tr("The name of the mod."); | ||||
| 		case VersionColumn: | ||||
| 			return tr("The version of the mod."); | ||||
| 		default: | ||||
| 			return QVariant(); | ||||
| 		} | ||||
| 	default: | ||||
| 		return QVariant(); | ||||
| 	} | ||||
| 	return QVariant(); | ||||
| } | ||||
|  | ||||
| Qt::ItemFlags LegacyModList::flags(const QModelIndex &index) const | ||||
| { | ||||
| 	Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); | ||||
| 	if (index.isValid()) | ||||
| 		return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | | ||||
| 			   defaultFlags; | ||||
| 	else | ||||
| 		return Qt::ItemIsDropEnabled | defaultFlags; | ||||
| } | ||||
|  | ||||
| QStringList LegacyModList::mimeTypes() const | ||||
| { | ||||
| 	QStringList types; | ||||
| 	types << "text/uri-list"; | ||||
| 	types << "text/plain"; | ||||
| 	return types; | ||||
| } | ||||
|  | ||||
| Qt::DropActions LegacyModList::supportedDropActions() const | ||||
| { | ||||
| 	// copy from outside, move from within and other mod lists | ||||
| 	return Qt::CopyAction | Qt::MoveAction; | ||||
| } | ||||
|  | ||||
| Qt::DropActions LegacyModList::supportedDragActions() const | ||||
| { | ||||
| 	// move to other mod lists or VOID | ||||
| 	return Qt::MoveAction; | ||||
| } | ||||
|  | ||||
| QMimeData *LegacyModList::mimeData(const QModelIndexList &indexes) const | ||||
| { | ||||
| 	QMimeData *data = new QMimeData(); | ||||
|  | ||||
| 	if (indexes.size() == 0) | ||||
| 		return data; | ||||
|  | ||||
| 	auto idx = indexes[0]; | ||||
| 	int row = idx.row(); | ||||
| 	if (row < 0 || row >= mods.size()) | ||||
| 		return data; | ||||
|  | ||||
| 	QStringList params; | ||||
| 	params << m_list_id << QString::number(row); | ||||
| 	data->setText(params.join('|')); | ||||
| 	return data; | ||||
| } | ||||
|  | ||||
| bool LegacyModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, | ||||
| 						   const QModelIndex &parent) | ||||
| { | ||||
| 	if (action == Qt::IgnoreAction) | ||||
| 		return true; | ||||
| 	// check if the action is supported | ||||
| 	if (!data || !(action & supportedDropActions())) | ||||
| 		return false; | ||||
| 	if (parent.isValid()) | ||||
| 	{ | ||||
| 		row = parent.row(); | ||||
| 		column = parent.column(); | ||||
| 	} | ||||
|  | ||||
| 	if (row > rowCount()) | ||||
| 		row = rowCount(); | ||||
| 	if (row == -1) | ||||
| 		row = rowCount(); | ||||
| 	if (column == -1) | ||||
| 		column = 0; | ||||
| 	qDebug() << "Drop row: " << row << " column: " << column; | ||||
|  | ||||
| 	// files dropped from outside? | ||||
| 	if (data->hasUrls()) | ||||
| 	{ | ||||
| 		bool was_watching = is_watching; | ||||
| 		if (was_watching) | ||||
| 			stopWatching(); | ||||
| 		auto urls = data->urls(); | ||||
| 		for (auto url : urls) | ||||
| 		{ | ||||
| 			// only local files may be dropped... | ||||
| 			if (!url.isLocalFile()) | ||||
| 				continue; | ||||
| 			QString filename = url.toLocalFile(); | ||||
| 			installMod(filename, row); | ||||
| 			// if there is no ordering, re-sort the list | ||||
| 			if (m_list_file.isEmpty()) | ||||
| 			{ | ||||
| 				beginResetModel(); | ||||
| 				internalSort(mods); | ||||
| 				endResetModel(); | ||||
| 			} | ||||
| 		} | ||||
| 		if (was_watching) | ||||
| 			startWatching(); | ||||
| 		return true; | ||||
| 	} | ||||
| 	else if (data->hasText()) | ||||
| 	{ | ||||
| 		QString sourcestr = data->text(); | ||||
| 		auto list = sourcestr.split('|'); | ||||
| 		if (list.size() != 2) | ||||
| 			return false; | ||||
| 		QString remoteId = list[0]; | ||||
| 		int remoteIndex = list[1].toInt(); | ||||
| 		qDebug() << "move: " << sourcestr; | ||||
| 		// no moving of things between two lists | ||||
| 		if (remoteId != m_list_id) | ||||
| 			return false; | ||||
| 		// no point moving to the same place... | ||||
| 		if (row == remoteIndex) | ||||
| 			return false; | ||||
| 		// otherwise, move the mod :D | ||||
| 		moveModTo(remoteIndex, row); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| @@ -1,160 +0,0 @@ | ||||
| /* Copyright 2013-2017 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QList> | ||||
| #include <QString> | ||||
| #include <QDir> | ||||
| #include <QAbstractListModel> | ||||
|  | ||||
| #include "minecraft/Mod.h" | ||||
|  | ||||
| #include "multimc_logic_export.h" | ||||
|  | ||||
| class LegacyInstance; | ||||
| class BaseInstance; | ||||
| class QFileSystemWatcher; | ||||
|  | ||||
| /** | ||||
|  * A legacy mod list. | ||||
|  * Backed by a folder. | ||||
|  */ | ||||
| class MULTIMC_LOGIC_EXPORT LegacyModList : public QAbstractListModel | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	enum Columns | ||||
| 	{ | ||||
| 		ActiveColumn = 0, | ||||
| 		NameColumn, | ||||
| 		VersionColumn | ||||
| 	}; | ||||
| 	LegacyModList(const QString &dir, const QString &list_file = QString()); | ||||
|  | ||||
| 	virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; | ||||
| 	virtual bool setData(const QModelIndex &index, const QVariant &value, | ||||
| 						 int role = Qt::EditRole); | ||||
|  | ||||
| 	virtual int rowCount(const QModelIndex &parent = QModelIndex()) const | ||||
| 	{ | ||||
| 		return size(); | ||||
| 	} | ||||
| 	; | ||||
| 	virtual QVariant headerData(int section, Qt::Orientation orientation, | ||||
| 								int role = Qt::DisplayRole) const; | ||||
| 	virtual int columnCount(const QModelIndex &parent) const; | ||||
|  | ||||
| 	size_t size() const | ||||
| 	{ | ||||
| 		return mods.size(); | ||||
| 	} | ||||
| 	; | ||||
| 	bool empty() const | ||||
| 	{ | ||||
| 		return size() == 0; | ||||
| 	} | ||||
| 	Mod &operator[](size_t index) | ||||
| 	{ | ||||
| 		return mods[index]; | ||||
| 	} | ||||
|  | ||||
| 	/// Reloads the mod list and returns true if the list changed. | ||||
| 	virtual bool update(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Adds the given mod to the list at the given index - if the list supports custom ordering | ||||
| 	 */ | ||||
| 	virtual bool installMod(const QString & filename, int index = 0); | ||||
|  | ||||
| 	/// Deletes the mod at the given index. | ||||
| 	virtual bool deleteMod(int index); | ||||
|  | ||||
| 	/// Deletes all the selected mods | ||||
| 	virtual bool deleteMods(int first, int last); | ||||
|  | ||||
| 	/** | ||||
| 	 * move the mod at index to the position N | ||||
| 	 * 0 is the beginning of the list, length() is the end of the list. | ||||
| 	 */ | ||||
| 	virtual bool moveModTo(int from, int to); | ||||
|  | ||||
| 	/** | ||||
| 	 * move the mod at index one position upwards | ||||
| 	 */ | ||||
| 	virtual bool moveModUp(int from); | ||||
| 	virtual bool moveModsUp(int first, int last); | ||||
|  | ||||
| 	/** | ||||
| 	 * move the mod at index one position downwards | ||||
| 	 */ | ||||
| 	virtual bool moveModDown(int from); | ||||
| 	virtual bool moveModsDown(int first, int last); | ||||
|  | ||||
| 	/// flags, mostly to support drag&drop | ||||
| 	virtual Qt::ItemFlags flags(const QModelIndex &index) const; | ||||
| 	/// get data for drag action | ||||
| 	virtual QMimeData *mimeData(const QModelIndexList &indexes) const; | ||||
| 	/// get the supported mime types | ||||
| 	virtual QStringList mimeTypes() const; | ||||
| 	/// process data from drop action | ||||
| 	virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, | ||||
| 							  const QModelIndex &parent); | ||||
| 	/// what drag actions do we support? | ||||
| 	virtual Qt::DropActions supportedDragActions() const; | ||||
|  | ||||
| 	/// what drop actions do we support? | ||||
| 	virtual Qt::DropActions supportedDropActions() const; | ||||
|  | ||||
| 	void startWatching(); | ||||
| 	void stopWatching(); | ||||
|  | ||||
| 	virtual bool isValid(); | ||||
|  | ||||
| 	QDir dir() | ||||
| 	{ | ||||
| 		return m_dir; | ||||
| 	} | ||||
|  | ||||
| 	const QList<Mod> & allMods() | ||||
| 	{ | ||||
| 		return mods; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	void internalSort(QList<Mod> & what); | ||||
| 	struct OrderItem | ||||
| 	{ | ||||
| 		QString id; | ||||
| 		bool enabled = false; | ||||
| 	}; | ||||
| 	typedef QList<OrderItem> OrderList; | ||||
| 	OrderList readListFile(); | ||||
| 	bool saveListFile(); | ||||
| private | ||||
| slots: | ||||
| 	void directoryChanged(QString path); | ||||
|  | ||||
| signals: | ||||
| 	void changed(); | ||||
|  | ||||
| protected: | ||||
| 	QFileSystemWatcher *m_watcher; | ||||
| 	bool is_watching; | ||||
| 	QDir m_dir; | ||||
| 	QString m_list_file; | ||||
| 	QString m_list_id; | ||||
| 	QList<Mod> mods; | ||||
| }; | ||||
| @@ -1,399 +0,0 @@ | ||||
| /* Copyright 2013-2017 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include <QStringList> | ||||
| #include <quazip.h> | ||||
| #include <quazipfile.h> | ||||
| #include <QDebug> | ||||
|  | ||||
| #include "Env.h" | ||||
| #include "BaseInstance.h" | ||||
| #include "net/URLConstants.h" | ||||
| #include "MMCZip.h" | ||||
|  | ||||
| #include "LegacyUpdate.h" | ||||
| #include "LegacyModList.h" | ||||
|  | ||||
| #include "LwjglVersionList.h" | ||||
| #include "LegacyInstance.h" | ||||
| #include <FileSystem.h> | ||||
|  | ||||
| LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst) | ||||
| { | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::executeTask() | ||||
| { | ||||
| 	fmllibsStart(); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::fmllibsStart() | ||||
| { | ||||
| 	// Get the mod list | ||||
| 	LegacyInstance *inst = (LegacyInstance *)m_inst; | ||||
| 	auto modList = inst->jarModList(); | ||||
|  | ||||
| 	bool forge_present = false; | ||||
|  | ||||
| 	QString version = inst->intendedVersionId(); | ||||
| 	auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; | ||||
| 	if (!fmlLibsMapping.contains(version)) | ||||
| 	{ | ||||
| 		lwjglStart(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto &libList = fmlLibsMapping[version]; | ||||
|  | ||||
| 	// determine if we need some libs for FML or forge | ||||
| 	setStatus(tr("Checking for FML libraries...")); | ||||
| 	for (unsigned i = 0; i < modList->size(); i++) | ||||
| 	{ | ||||
| 		auto &mod = modList->operator[](i); | ||||
|  | ||||
| 		// do not use disabled mods. | ||||
| 		if (!mod.enabled()) | ||||
| 			continue; | ||||
|  | ||||
| 		if (mod.type() != Mod::MOD_ZIPFILE) | ||||
| 			continue; | ||||
|  | ||||
| 		if (mod.mmc_id().contains("forge", Qt::CaseInsensitive)) | ||||
| 		{ | ||||
| 			forge_present = true; | ||||
| 			break; | ||||
| 		} | ||||
| 		if (mod.mmc_id().contains("fml", Qt::CaseInsensitive)) | ||||
| 		{ | ||||
| 			forge_present = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	// we don't... | ||||
| 	if (!forge_present) | ||||
| 	{ | ||||
| 		lwjglStart(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// now check the lib folder inside the instance for files. | ||||
| 	for (auto &lib : libList) | ||||
| 	{ | ||||
| 		QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); | ||||
| 		if (libInfo.exists()) | ||||
| 			continue; | ||||
| 		fmlLibsToProcess.append(lib); | ||||
| 	} | ||||
|  | ||||
| 	// if everything is in place, there's nothing to do here... | ||||
| 	if (fmlLibsToProcess.isEmpty()) | ||||
| 	{ | ||||
| 		lwjglStart(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// download missing libs to our place | ||||
| 	setStatus(tr("Dowloading FML libraries...")); | ||||
| 	auto dljob = new NetJob("FML libraries"); | ||||
| 	auto metacache = ENV.metacache(); | ||||
| 	for (auto &lib : fmlLibsToProcess) | ||||
| 	{ | ||||
| 		auto entry = metacache->resolveEntry("fmllibs", lib.filename); | ||||
| 		QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename | ||||
| 									 : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; | ||||
| 		dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); | ||||
| 	} | ||||
|  | ||||
| 	connect(dljob, &NetJob::succeeded, this, &LegacyUpdate::fmllibsFinished); | ||||
| 	connect(dljob, &NetJob::failed, this, &LegacyUpdate::fmllibsFailed); | ||||
| 	connect(dljob, &NetJob::progress, this, &LegacyUpdate::progress); | ||||
| 	legacyDownloadJob.reset(dljob); | ||||
| 	legacyDownloadJob->start(); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::fmllibsFinished() | ||||
| { | ||||
| 	legacyDownloadJob.reset(); | ||||
| 	if(!fmlLibsToProcess.isEmpty()) | ||||
| 	{ | ||||
| 		setStatus(tr("Copying FML libraries into the instance...")); | ||||
| 		LegacyInstance *inst = (LegacyInstance *)m_inst; | ||||
| 		auto metacache = ENV.metacache(); | ||||
| 		int index = 0; | ||||
| 		for (auto &lib : fmlLibsToProcess) | ||||
| 		{ | ||||
| 			progress(index, fmlLibsToProcess.size()); | ||||
| 			auto entry = metacache->resolveEntry("fmllibs", lib.filename); | ||||
| 			auto path = FS::PathCombine(inst->libDir(), lib.filename); | ||||
| 			if(!FS::ensureFilePathExists(path)) | ||||
| 			{ | ||||
| 				emitFailed(tr("Failed creating FML library folder inside the instance.")); | ||||
| 				return; | ||||
| 			} | ||||
| 			if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) | ||||
| 			{ | ||||
| 				emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); | ||||
| 				return; | ||||
| 			} | ||||
| 			index++; | ||||
| 		} | ||||
| 		progress(index, fmlLibsToProcess.size()); | ||||
| 	} | ||||
| 	lwjglStart(); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::fmllibsFailed(QString reason) | ||||
| { | ||||
| 	emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries. Reason: %1").arg(reason)); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::lwjglStart() | ||||
| { | ||||
| 	LegacyInstance *inst = (LegacyInstance *)m_inst; | ||||
|  | ||||
| 	auto list = ENV.getLegacyLWJGL(); | ||||
| 	if (!list->isLoaded()) | ||||
| 	{ | ||||
| 		setStatus(tr("Checking the LWJGL version list...")); | ||||
| 		list->loadList(); | ||||
| 		auto task = list->getLoadTask(); | ||||
| 		connect(task.get(), &Task::succeeded, this, &LegacyUpdate::lwjglStart); | ||||
| 		connect(task.get(), &Task::failed, this, [&](const QString & error) | ||||
| 		{ | ||||
| 			emitFailed(tr("Failed to refresh LWJGL list: %1.").arg(error)); | ||||
| 		}); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	lwjglVersion = inst->lwjglVersion(); | ||||
| 	lwjglTargetPath = FS::PathCombine(inst->lwjglFolder(), lwjglVersion); | ||||
| 	lwjglNativesPath = FS::PathCombine(lwjglTargetPath, "natives"); | ||||
|  | ||||
| 	// if the 'done' file exists, we don't have to download this again | ||||
| 	QFileInfo doneFile(FS::PathCombine(lwjglTargetPath, "done")); | ||||
| 	if (doneFile.exists()) | ||||
| 	{ | ||||
| 		jarStart(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	setStatus(tr("Downloading new LWJGL...")); | ||||
| 	auto version = std::dynamic_pointer_cast<LWJGLVersion>(list->findVersion(lwjglVersion)); | ||||
| 	if (!version) | ||||
| 	{ | ||||
| 		emitFailed(QString("Game update failed: the selected LWJGL version is invalid: %1").arg(lwjglVersion)); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	QString url = version->url(); | ||||
| 	QUrl realUrl(url); | ||||
| 	QString hostname = realUrl.host(); | ||||
| 	auto worker = &ENV.qnam(); | ||||
| 	QNetworkRequest req(realUrl); | ||||
| 	req.setRawHeader("Host", hostname.toLatin1()); | ||||
| 	req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); | ||||
| 	QNetworkReply *rep = worker->get(req); | ||||
|  | ||||
| 	m_reply = std::shared_ptr<QNetworkReply>(rep); | ||||
| 	connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress); | ||||
| 	connect(worker, &QNetworkAccessManager::finished, this, &LegacyUpdate::lwjglFinished); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::lwjglFinished(QNetworkReply *reply) | ||||
| { | ||||
| 	if (m_reply.get() != 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 = &ENV.qnam(); | ||||
| 	// 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()) | ||||
| 	{ | ||||
| 		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, "MultiMC/5.0 (Cached)"); | ||||
| 		QNetworkReply *rep = worker->get(req); | ||||
| 		connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress); | ||||
| 		m_reply = std::shared_ptr<QNetworkReply>(rep); | ||||
| 		return; | ||||
| 	} | ||||
| 	QFile saveMe("lwjgl.zip"); | ||||
| 	saveMe.open(QIODevice::WriteOnly); | ||||
| 	saveMe.write(m_reply->readAll()); | ||||
| 	saveMe.close(); | ||||
| 	setStatus(tr("Installing new LWJGL...")); | ||||
| 	extractLwjgl(); | ||||
| 	jarStart(); | ||||
| } | ||||
| void LegacyUpdate::extractLwjgl() | ||||
| { | ||||
| 	// make sure the directories are there | ||||
|  | ||||
| 	bool success = FS::ensureFolderPathExists(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 = FS::PathCombine(lwjglTargetPath, jarNames[i]); | ||||
| 			} | ||||
| 		} | ||||
| 		// Not found? look for the natives | ||||
| 		if (destFileName.isEmpty()) | ||||
| 		{ | ||||
| #ifdef Q_OS_WIN32 | ||||
| 			QString nativesDir = "windows"; | ||||
| #else | ||||
| #ifdef Q_OS_MAC | ||||
| 			QString nativesDir = "macosx"; | ||||
| #else | ||||
| 			QString nativesDir = "linux"; | ||||
| #endif | ||||
| #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 = FS::PathCombine(lwjglNativesPath, name); | ||||
| 			} | ||||
| 		} | ||||
| 		// Now if destFileName is still empty, go to the next file. | ||||
| 		if (!destFileName.isEmpty()) | ||||
| 		{ | ||||
| 			setStatus(tr("Installing new LWJGL - extracting ") + name + "..."); | ||||
| 			QFile output(destFileName); | ||||
| 			output.open(QIODevice::WriteOnly); | ||||
| 			output.write(file.readAll()); | ||||
| 			output.close(); | ||||
| 		} | ||||
| 		file.close(); // do not forget to close! | ||||
| 	} | ||||
| 	zip.close(); | ||||
| 	m_reply.reset(); | ||||
| 	QFile doneFile(FS::PathCombine(lwjglTargetPath, "done")); | ||||
| 	doneFile.open(QIODevice::WriteOnly); | ||||
| 	doneFile.write("done."); | ||||
| 	doneFile.close(); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::lwjglFailed(QString reason) | ||||
| { | ||||
| 	emitFailed(tr("Bad stuff happened while trying to get the lwjgl libs: %1").arg(reason)); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::jarStart() | ||||
| { | ||||
| 	LegacyInstance *inst = (LegacyInstance *)m_inst; | ||||
| 	if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) | ||||
| 	{ | ||||
| 		emitSucceeded(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	setStatus(tr("Checking for jar updates...")); | ||||
| 	// Make directories | ||||
| 	QDir binDir(inst->binRoot()); | ||||
| 	if (!binDir.exists() && !binDir.mkpath(".")) | ||||
| 	{ | ||||
| 		emitFailed("Failed to create bin folder."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Build a list of URLs that will need to be downloaded. | ||||
| 	setStatus(tr("Downloading new minecraft.jar ...")); | ||||
|  | ||||
| 	QString version_id = inst->intendedVersionId(); | ||||
|  | ||||
| 	auto dljob = new NetJob("Minecraft.jar for version " + version_id); | ||||
|  | ||||
| 	auto metacache = ENV.metacache(); | ||||
| 	auto entry = metacache->resolveEntry("versions", URLConstants::getJarPath(version_id)); | ||||
| 	dljob->addNetAction(Net::Download::makeCached(QUrl(URLConstants::getLegacyJarUrl(version_id)), entry)); | ||||
| 	connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished())); | ||||
| 	connect(dljob, SIGNAL(failed(QString)), SLOT(jarFailed(QString))); | ||||
| 	connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); | ||||
| 	legacyDownloadJob.reset(dljob); | ||||
| 	legacyDownloadJob->start(); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::jarFinished() | ||||
| { | ||||
| 	// process the jar | ||||
| 	emitSucceeded(); | ||||
| } | ||||
|  | ||||
| void LegacyUpdate::jarFailed(QString reason) | ||||
| { | ||||
| 	// bad, bad | ||||
| 	emitFailed(tr("Failed to download the minecraft jar: %1.").arg(reason)); | ||||
| } | ||||
| @@ -1,70 +0,0 @@ | ||||
| /* Copyright 2013-2017 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/NetJob.h" | ||||
| #include "tasks/Task.h" | ||||
| #include "minecraft/VersionFilterData.h" | ||||
|  | ||||
| class MinecraftVersion; | ||||
| class BaseInstance; | ||||
| class QuaZip; | ||||
| class Mod; | ||||
|  | ||||
| class LegacyUpdate : public Task | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0); | ||||
| 	virtual void executeTask(); | ||||
|  | ||||
| private | ||||
| slots: | ||||
| 	void lwjglStart(); | ||||
| 	void lwjglFinished(QNetworkReply *); | ||||
| 	void lwjglFailed(QString reason); | ||||
|  | ||||
| 	void jarStart(); | ||||
| 	void jarFinished(); | ||||
| 	void jarFailed(QString reason); | ||||
|  | ||||
| 	void fmllibsStart(); | ||||
| 	void fmllibsFinished(); | ||||
| 	void fmllibsFailed(QString reason); | ||||
|  | ||||
| 	void extractLwjgl(); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	std::shared_ptr<QNetworkReply> m_reply; | ||||
|  | ||||
| 	// target version, determined during this task | ||||
| 	// MinecraftVersion *targetVersion; | ||||
| 	QString lwjglURL; | ||||
| 	QString lwjglVersion; | ||||
|  | ||||
| 	QString lwjglTargetPath; | ||||
| 	QString lwjglNativesPath; | ||||
|  | ||||
| private: | ||||
| 	NetJobPtr legacyDownloadJob; | ||||
| 	BaseInstance *m_inst = nullptr; | ||||
| 	QList<FMLlib> fmlLibsToProcess; | ||||
| }; | ||||
| @@ -1,169 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 "Env.h" | ||||
|  | ||||
| #include <QtNetwork> | ||||
| #include <QtXml> | ||||
| #include <QRegExp> | ||||
|  | ||||
| #include <QDebug> | ||||
|  | ||||
| #define RSS_URL "https://sourceforge.net/projects/java-game-lib/rss" | ||||
|  | ||||
| LWJGLVersionList::LWJGLVersionList(QObject *parent) : BaseVersionList(parent) | ||||
| { | ||||
| } | ||||
|  | ||||
| QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const | ||||
| { | ||||
| 	if (!index.isValid()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	if (index.row() > count()) | ||||
| 		return QVariant(); | ||||
|  | ||||
| 	const PtrLWJGLVersion version = m_vlist.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 tr("Version"); | ||||
|  | ||||
| 	case Qt::ToolTipRole: | ||||
| 		return tr("LWJGL version name."); | ||||
|  | ||||
| 	default: | ||||
| 		return QVariant(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int LWJGLVersionList::columnCount(const QModelIndex &parent) const | ||||
| { | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| void LWJGLVersionList::loadList() | ||||
| { | ||||
| 	if(m_loading) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	m_loading = true; | ||||
|  | ||||
| 	qDebug() << "Downloading LWJGL RSS..."; | ||||
| 	m_rssDLJob.reset(new NetJob("LWJGL RSS")); | ||||
| 	m_rssDL = Net::Download::makeByteArray(QUrl(RSS_URL), &m_rssData); | ||||
| 	m_rssDLJob->addNetAction(m_rssDL); | ||||
| 	connect(m_rssDLJob.get(), &NetJob::failed, this, &LWJGLVersionList::rssFailed); | ||||
| 	connect(m_rssDLJob.get(), &NetJob::succeeded, this, &LWJGLVersionList::rssSucceeded); | ||||
| 	m_rssDLJob->start(); | ||||
| } | ||||
|  | ||||
| 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::rssFailed(const QString& reason) | ||||
| { | ||||
| 	m_rssDLJob.reset(); | ||||
| 	m_loading = false; | ||||
| 	qWarning() << "Failed to load LWJGL list. Network error: " + reason; | ||||
| } | ||||
|  | ||||
| void LWJGLVersionList::rssSucceeded() | ||||
| { | ||||
| 	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(m_rssData, false, &xmlErrorMsg, &errorLine)) | ||||
| 	{ | ||||
| 		qWarning() << "Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine); | ||||
| 		m_rssDLJob.reset(); | ||||
| 		m_rssData.clear(); | ||||
| 		m_loading = false; | ||||
| 		return; | ||||
| 	} | ||||
| 	m_rssData.clear(); | ||||
|  | ||||
| 	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()) | ||||
| 		{ | ||||
| 			qDebug() << "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; | ||||
| 			} | ||||
| 			qDebug() << "Discovered LWGL version" << name << "at" << link; | ||||
| 			tempList.append(std::make_shared<LWJGLVersion>(name, link)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	beginResetModel(); | ||||
| 	m_vlist.swap(tempList); | ||||
| 	endResetModel(); | ||||
|  | ||||
| 	qDebug() << "Loaded LWJGL list."; | ||||
| 	m_rssDLJob.reset(); | ||||
| 	m_loading = false; | ||||
| } | ||||
| @@ -1,116 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 <QUrl> | ||||
| #include <QNetworkReply> | ||||
| #include <memory> | ||||
|  | ||||
| #include "BaseVersion.h" | ||||
| #include "BaseVersionList.h" | ||||
|  | ||||
| #include "multimc_logic_export.h" | ||||
| #include <net/NetJob.h> | ||||
|  | ||||
| class LWJGLVersion; | ||||
| typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion; | ||||
|  | ||||
| class MULTIMC_LOGIC_EXPORT LWJGLVersion : public BaseVersion | ||||
| { | ||||
| public: | ||||
| 	LWJGLVersion(const QString &name, const QString &url) | ||||
| 		: m_name(name), m_url(url) | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	virtual QString descriptor() | ||||
| 	{ | ||||
| 		return m_name; | ||||
| 	} | ||||
|  | ||||
| 	virtual QString name() | ||||
| 	{ | ||||
| 		return m_name; | ||||
| 	} | ||||
|  | ||||
| 	virtual QString typeString() const | ||||
| 	{ | ||||
| 		return QObject::tr("Upstream"); | ||||
| 	} | ||||
|  | ||||
| 	QString url() const | ||||
| 	{ | ||||
| 		return m_url; | ||||
| 	} | ||||
|  | ||||
| protected: | ||||
| 	QString m_name; | ||||
| 	QString m_url; | ||||
| }; | ||||
|  | ||||
| class MULTIMC_LOGIC_EXPORT LWJGLVersionList : public BaseVersionList | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit LWJGLVersionList(QObject *parent = 0); | ||||
|  | ||||
| 	bool isLoaded() override | ||||
| 	{ | ||||
| 		return m_vlist.length() > 0; | ||||
| 	} | ||||
| 	virtual const BaseVersionPtr at(int i) const override | ||||
| 	{ | ||||
| 		return m_vlist[i]; | ||||
| 	} | ||||
|  | ||||
| 	virtual shared_qobject_ptr<Task> getLoadTask() override | ||||
| 	{ | ||||
| 		return m_rssDLJob; | ||||
| 	} | ||||
|  | ||||
| 	virtual void sortVersions() override {}; | ||||
|  | ||||
| 	virtual void updateListData(QList< BaseVersionPtr > versions) override {}; | ||||
|  | ||||
| 	int count() const override | ||||
| 	{ | ||||
| 		return m_vlist.length(); | ||||
| 	} | ||||
|  | ||||
| 	virtual QVariant data(const QModelIndex &index, int role) const override; | ||||
| 	virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; | ||||
| 	virtual int rowCount(const QModelIndex &parent) const override | ||||
| 	{ | ||||
| 		return count(); | ||||
| 	} | ||||
| 	virtual int columnCount(const QModelIndex &parent) const override; | ||||
|  | ||||
| public slots: | ||||
| 	virtual void loadList(); | ||||
|  | ||||
| private slots: | ||||
| 	void rssFailed(const QString & reason); | ||||
| 	void rssSucceeded(); | ||||
|  | ||||
| private: | ||||
| 	QList<PtrLWJGLVersion> m_vlist; | ||||
| 	Net::Download::Ptr m_rssDL; | ||||
| 	NetJobPtr m_rssDLJob; | ||||
| 	QByteArray m_rssData; | ||||
| 	bool m_loading = false; | ||||
| }; | ||||
| @@ -131,10 +131,6 @@ SET(MULTIMC_SOURCES | ||||
| 	pages/ScreenshotsPage.h | ||||
| 	pages/OtherLogsPage.cpp | ||||
| 	pages/OtherLogsPage.h | ||||
| 	pages/LegacyJarModPage.cpp | ||||
| 	pages/LegacyJarModPage.h | ||||
| 	pages/LegacyUpgradePage.cpp | ||||
| 	pages/LegacyUpgradePage.h | ||||
| 	pages/WorldListPage.cpp | ||||
| 	pages/WorldListPage.h | ||||
|  | ||||
| @@ -241,8 +237,6 @@ SET(MULTIMC_UIS | ||||
| 	pages/NotesPage.ui | ||||
| 	pages/ScreenshotsPage.ui | ||||
| 	pages/OtherLogsPage.ui | ||||
| 	pages/LegacyJarModPage.ui | ||||
| 	pages/LegacyUpgradePage.ui | ||||
| 	pages/WorldListPage.ui | ||||
|  | ||||
| 	# Global settings pages | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #pragma once | ||||
| #include "minecraft/onesix/OneSixInstance.h" | ||||
| #include "minecraft/legacy/LegacyInstance.h" | ||||
| #include <FileSystem.h> | ||||
| #include "pages/BasePage.h" | ||||
| #include "pages/LogPage.h" | ||||
| @@ -13,7 +12,6 @@ | ||||
| #include "pages/InstanceSettingsPage.h" | ||||
| #include "pages/OtherLogsPage.h" | ||||
| #include "pages/BasePageProvider.h" | ||||
| #include "pages/LegacyJarModPage.h" | ||||
| #include "pages/WorldListPage.h" | ||||
|  | ||||
|  | ||||
| @@ -46,22 +44,6 @@ public: | ||||
| 			values.append(new ScreenshotsPage(FS::PathCombine(onesix->minecraftRoot(), "screenshots"))); | ||||
| 			values.append(new InstanceSettingsPage(onesix.get())); | ||||
| 		} | ||||
| 		std::shared_ptr<LegacyInstance> legacy = std::dynamic_pointer_cast<LegacyInstance>(inst); | ||||
| 		if(legacy) | ||||
| 		{ | ||||
| 			// FIXME: actually implement the legacy instance upgrade, then enable this. | ||||
| 			//values.append(new LegacyUpgradePage(this)); | ||||
| 			values.append(new LegacyJarModPage(legacy.get())); | ||||
| 			auto modsPage = new ModFolderPage(legacy.get(), legacy->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); | ||||
| 			modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); | ||||
| 			values.append(modsPage); | ||||
| 			values.append(new ModFolderPage(legacy.get(), legacy->coreModList(), "coremods", "coremods", tr("Core mods"), "Loader-mods")); | ||||
| 			values.append(new TexturePackPage(legacy.get())); | ||||
| 			values.append(new NotesPage(legacy.get())); | ||||
| 			values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); | ||||
| 			values.append(new ScreenshotsPage(FS::PathCombine(legacy->minecraftRoot(), "screenshots"))); | ||||
| 			values.append(new InstanceSettingsPage(legacy.get())); | ||||
| 		} | ||||
| 		auto logMatcher = inst->getLogFileMatcher(); | ||||
| 		if(logMatcher) | ||||
| 		{ | ||||
|   | ||||
| @@ -54,7 +54,6 @@ | ||||
| #include <java/JavaUtils.h> | ||||
| #include <java/JavaInstallList.h> | ||||
| #include <launch/LaunchTask.h> | ||||
| #include <minecraft/legacy/LwjglVersionList.h> | ||||
| #include <minecraft/auth/MojangAccountList.h> | ||||
| #include <SkinUtils.h> | ||||
| #include <net/URLConstants.h> | ||||
|   | ||||
| @@ -1,162 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 "LegacyJarModPage.h" | ||||
| #include "ui_LegacyJarModPage.h" | ||||
|  | ||||
| #include <QKeyEvent> | ||||
| #include <QKeyEvent> | ||||
|  | ||||
| #include "dialogs/VersionSelectDialog.h" | ||||
| #include "dialogs/ProgressDialog.h" | ||||
| #include "dialogs/ModEditDialogCommon.h" | ||||
| #include "minecraft/legacy/LegacyModList.h" | ||||
| #include "minecraft/legacy/LegacyInstance.h" | ||||
| #include "Env.h" | ||||
| #include <DesktopServices.h> | ||||
| #include "MultiMC.h" | ||||
| #include <GuiUtil.h> | ||||
|  | ||||
| LegacyJarModPage::LegacyJarModPage(LegacyInstance *inst, QWidget *parent) | ||||
| 	: QWidget(parent), ui(new Ui::LegacyJarModPage), m_inst(inst) | ||||
| { | ||||
| 	ui->setupUi(this); | ||||
| 	ui->tabWidget->tabBar()->hide(); | ||||
|  | ||||
| 	m_jarmods = m_inst->jarModList(); | ||||
| 	ui->jarModsTreeView->setModel(m_jarmods.get()); | ||||
| 	ui->jarModsTreeView->setDragDropMode(QAbstractItemView::DragDrop); | ||||
| 	ui->jarModsTreeView->setSelectionMode(QAbstractItemView::SingleSelection); | ||||
| 	ui->jarModsTreeView->installEventFilter(this); | ||||
| 	m_jarmods->startWatching(); | ||||
| 	auto smodel = ui->jarModsTreeView->selectionModel(); | ||||
| 	connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), | ||||
| 			SLOT(jarCurrent(QModelIndex, QModelIndex))); | ||||
| } | ||||
|  | ||||
| LegacyJarModPage::~LegacyJarModPage() | ||||
| { | ||||
| 	m_jarmods->stopWatching(); | ||||
| 	delete ui; | ||||
| } | ||||
|  | ||||
| bool LegacyJarModPage::shouldDisplay() const | ||||
| { | ||||
| 	return !m_inst->isRunning(); | ||||
| } | ||||
|  | ||||
| bool LegacyJarModPage::eventFilter(QObject *obj, QEvent *ev) | ||||
| { | ||||
| 	if (ev->type() != QEvent::KeyPress || obj != ui->jarModsTreeView) | ||||
| 	{ | ||||
| 		return QWidget::eventFilter(obj, ev); | ||||
| 	} | ||||
|  | ||||
| 	QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); | ||||
| 	switch (keyEvent->key()) | ||||
| 	{ | ||||
| 	case Qt::Key_Up: | ||||
| 	{ | ||||
| 		if (keyEvent->modifiers() & Qt::ControlModifier) | ||||
| 		{ | ||||
| 			on_moveJarUpBtn_clicked(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		break; | ||||
| 	} | ||||
| 	case Qt::Key_Down: | ||||
| 	{ | ||||
| 		if (keyEvent->modifiers() & Qt::ControlModifier) | ||||
| 		{ | ||||
| 			on_moveJarDownBtn_clicked(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		break; | ||||
| 	} | ||||
| 	case Qt::Key_Delete: | ||||
| 		on_rmJarBtn_clicked(); | ||||
| 		return true; | ||||
| 	case Qt::Key_Plus: | ||||
| 		on_addJarBtn_clicked(); | ||||
| 		return true; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 	return QWidget::eventFilter(obj, ev); | ||||
| } | ||||
|  | ||||
| void LegacyJarModPage::on_addJarBtn_clicked() | ||||
| { | ||||
| 	auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); | ||||
| 	if(!list.empty()) | ||||
| 	{ | ||||
| 		m_jarmods->stopWatching(); | ||||
| 		for (auto filename : list) | ||||
| 		{ | ||||
| 			m_jarmods->installMod(filename); | ||||
| 		} | ||||
| 		m_jarmods->startWatching(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void LegacyJarModPage::on_moveJarDownBtn_clicked() | ||||
| { | ||||
| 	int first, last; | ||||
| 	auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); | ||||
|  | ||||
| 	if (!lastfirst(list, first, last)) | ||||
| 		return; | ||||
|  | ||||
| 	m_jarmods->moveModsDown(first, last); | ||||
| } | ||||
|  | ||||
| void LegacyJarModPage::on_moveJarUpBtn_clicked() | ||||
| { | ||||
| 	int first, last; | ||||
| 	auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); | ||||
|  | ||||
| 	if (!lastfirst(list, first, last)) | ||||
| 		return; | ||||
| 	m_jarmods->moveModsUp(first, last); | ||||
| } | ||||
|  | ||||
| void LegacyJarModPage::on_rmJarBtn_clicked() | ||||
| { | ||||
| 	int first, last; | ||||
| 	auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); | ||||
|  | ||||
| 	if (!lastfirst(list, first, last)) | ||||
| 		return; | ||||
| 	m_jarmods->stopWatching(); | ||||
| 	m_jarmods->deleteMods(first, last); | ||||
| 	m_jarmods->startWatching(); | ||||
| } | ||||
|  | ||||
| void LegacyJarModPage::on_viewJarBtn_clicked() | ||||
| { | ||||
| 	DesktopServices::openDirectory(m_inst->jarModsDir(), true); | ||||
| } | ||||
|  | ||||
| void LegacyJarModPage::jarCurrent(QModelIndex current, QModelIndex previous) | ||||
| { | ||||
| 	if (!current.isValid()) | ||||
| 	{ | ||||
| 		ui->jarMIFrame->clear(); | ||||
| 		return; | ||||
| 	} | ||||
| 	int row = current.row(); | ||||
| 	Mod &m = m_jarmods->operator[](row); | ||||
| 	ui->jarMIFrame->updateWithMod(m); | ||||
| } | ||||
| @@ -1,76 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 <QWidget> | ||||
|  | ||||
| #include "net/NetJob.h" | ||||
| #include "BasePage.h" | ||||
| #include <MultiMC.h> | ||||
|  | ||||
| class LegacyModList; | ||||
| class LegacyInstance; | ||||
| namespace Ui | ||||
| { | ||||
| class LegacyJarModPage; | ||||
| } | ||||
|  | ||||
| class LegacyJarModPage : public QWidget, public BasePage | ||||
| { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
| 	explicit LegacyJarModPage(LegacyInstance *inst, QWidget *parent = 0); | ||||
| 	virtual ~LegacyJarModPage(); | ||||
|  | ||||
| 	virtual QString displayName() const override | ||||
| 	{ | ||||
| 		return tr("Jar Mods"); | ||||
| 	} | ||||
| 	virtual QIcon icon() const override | ||||
| 	{ | ||||
| 		return MMC->getThemedIcon("jarmods"); | ||||
| 	} | ||||
| 	virtual QString id() const override | ||||
| 	{ | ||||
| 		return "jarmods"; | ||||
| 	} | ||||
| 	virtual QString helpPage() const override | ||||
| 	{ | ||||
| 		return "Legacy-jar-mods"; | ||||
| 	} | ||||
| 	virtual bool shouldDisplay() const override; | ||||
|  | ||||
| private | ||||
| slots: | ||||
|  | ||||
| 	void on_addJarBtn_clicked(); | ||||
| 	void on_rmJarBtn_clicked(); | ||||
| 	void on_moveJarUpBtn_clicked(); | ||||
| 	void on_moveJarDownBtn_clicked(); | ||||
| 	void on_viewJarBtn_clicked(); | ||||
|  | ||||
| 	void jarCurrent(QModelIndex current, QModelIndex previous); | ||||
|  | ||||
| protected: | ||||
| 	virtual bool eventFilter(QObject *obj, QEvent *ev) override; | ||||
|  | ||||
| private: | ||||
| 	Ui::LegacyJarModPage *ui; | ||||
| 	std::shared_ptr<LegacyModList> m_jarmods; | ||||
| 	LegacyInstance *m_inst; | ||||
| 	NetJobPtr forgeJob; | ||||
| }; | ||||
| @@ -1,162 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>LegacyJarModPage</class> | ||||
|  <widget class="QWidget" name="LegacyJarModPage"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>659</width> | ||||
|     <height>593</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|    <property name="leftMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="topMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="rightMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="bottomMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <item> | ||||
|     <widget class="QTabWidget" name="tabWidget"> | ||||
|      <property name="currentIndex"> | ||||
|       <number>0</number> | ||||
|      </property> | ||||
|      <widget class="QWidget" name="tab"> | ||||
|       <attribute name="title"> | ||||
|        <string notr="true">Tab 1</string> | ||||
|       </attribute> | ||||
|       <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|        <item> | ||||
|         <layout class="QHBoxLayout" name="horizontalLayout"> | ||||
|          <item> | ||||
|           <widget class="ModListView" name="jarModsTreeView"> | ||||
|            <property name="verticalScrollBarPolicy"> | ||||
|             <enum>Qt::ScrollBarAlwaysOn</enum> | ||||
|            </property> | ||||
|            <property name="horizontalScrollBarPolicy"> | ||||
|             <enum>Qt::ScrollBarAlwaysOff</enum> | ||||
|            </property> | ||||
|           </widget> | ||||
|          </item> | ||||
|          <item> | ||||
|           <layout class="QVBoxLayout" name="jarModsButtonBox"> | ||||
|            <item> | ||||
|             <widget class="QLabel" name="label"> | ||||
|              <property name="text"> | ||||
|               <string>Selection</string> | ||||
|              </property> | ||||
|              <property name="alignment"> | ||||
|               <set>Qt::AlignCenter</set> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="QPushButton" name="rmJarBtn"> | ||||
|              <property name="text"> | ||||
|               <string>&Remove</string> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="QPushButton" name="moveJarUpBtn"> | ||||
|              <property name="text"> | ||||
|               <string>Move &Up</string> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="QPushButton" name="moveJarDownBtn"> | ||||
|              <property name="text"> | ||||
|               <string>Move &Down</string> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="LineSeparator" name="separator" native="true"/> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="QLabel" name="label_2"> | ||||
|              <property name="text"> | ||||
|               <string>Install</string> | ||||
|              </property> | ||||
|              <property name="alignment"> | ||||
|               <set>Qt::AlignCenter</set> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="QPushButton" name="addJarBtn"> | ||||
|              <property name="text"> | ||||
|               <string>&Add jar mod</string> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|            <item> | ||||
|             <spacer name="verticalSpacer"> | ||||
|              <property name="orientation"> | ||||
|               <enum>Qt::Vertical</enum> | ||||
|              </property> | ||||
|              <property name="sizeHint" stdset="0"> | ||||
|               <size> | ||||
|                <width>20</width> | ||||
|                <height>40</height> | ||||
|               </size> | ||||
|              </property> | ||||
|             </spacer> | ||||
|            </item> | ||||
|            <item> | ||||
|             <widget class="QPushButton" name="viewJarBtn"> | ||||
|              <property name="text"> | ||||
|               <string>&View Folder</string> | ||||
|              </property> | ||||
|             </widget> | ||||
|            </item> | ||||
|           </layout> | ||||
|          </item> | ||||
|         </layout> | ||||
|        </item> | ||||
|       </layout> | ||||
|      </widget> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="MCModInfoFrame" name="jarMIFrame"> | ||||
|      <property name="sizePolicy"> | ||||
|       <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> | ||||
|        <horstretch>0</horstretch> | ||||
|        <verstretch>0</verstretch> | ||||
|       </sizepolicy> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <customwidgets> | ||||
|   <customwidget> | ||||
|    <class>ModListView</class> | ||||
|    <extends>QTreeView</extends> | ||||
|    <header>widgets/ModListView.h</header> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|    <class>MCModInfoFrame</class> | ||||
|    <extends>QFrame</extends> | ||||
|    <header>widgets/MCModInfoFrame.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|   <customwidget> | ||||
|    <class>LineSeparator</class> | ||||
|    <extends>QWidget</extends> | ||||
|    <header>widgets/LineSeparator.h</header> | ||||
|    <container>1</container> | ||||
|   </customwidget> | ||||
|  </customwidgets> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
| @@ -1,25 +0,0 @@ | ||||
| #include "LegacyUpgradePage.h" | ||||
| #include "ui_LegacyUpgradePage.h" | ||||
|  | ||||
| #include "minecraft/legacy/LegacyInstance.h" | ||||
|  | ||||
| LegacyUpgradePage::LegacyUpgradePage(LegacyInstance *inst, QWidget *parent) | ||||
| 	: QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst) | ||||
| { | ||||
| 	ui->setupUi(this); | ||||
| } | ||||
|  | ||||
| LegacyUpgradePage::~LegacyUpgradePage() | ||||
| { | ||||
| 	delete ui; | ||||
| } | ||||
|  | ||||
| void LegacyUpgradePage::on_upgradeButton_clicked() | ||||
| { | ||||
| 	// now what? | ||||
| } | ||||
|  | ||||
| bool LegacyUpgradePage::shouldDisplay() const | ||||
| { | ||||
| 	return !m_inst->isRunning(); | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| /* Copyright 2013-2017 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 <QWidget> | ||||
|  | ||||
| #include "minecraft/legacy/LegacyInstance.h" | ||||
| #include "pages/BasePage.h" | ||||
| #include <MultiMC.h> | ||||
|  | ||||
| namespace Ui | ||||
| { | ||||
| class LegacyUpgradePage; | ||||
| } | ||||
|  | ||||
| class LegacyUpgradePage : public QWidget, public BasePage | ||||
| { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
| 	explicit LegacyUpgradePage(LegacyInstance *inst, QWidget *parent = 0); | ||||
| 	virtual ~LegacyUpgradePage(); | ||||
| 	virtual QString displayName() const override | ||||
| 	{ | ||||
| 		return tr("Upgrade"); | ||||
| 	} | ||||
| 	virtual QIcon icon() const override | ||||
| 	{ | ||||
| 		return MMC->getThemedIcon("checkupdate"); | ||||
| 	} | ||||
| 	virtual QString id() const override | ||||
| 	{ | ||||
| 		return "upgrade"; | ||||
| 	} | ||||
| 	virtual QString helpPage() const override | ||||
| 	{ | ||||
| 		return "Legacy-upgrade"; | ||||
| 	} | ||||
| 	virtual bool shouldDisplay() const override; | ||||
| private | ||||
| slots: | ||||
| 	void on_upgradeButton_clicked(); | ||||
|  | ||||
| private: | ||||
| 	Ui::LegacyUpgradePage *ui; | ||||
| 	LegacyInstance *m_inst; | ||||
| }; | ||||
| @@ -1,51 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>LegacyUpgradePage</class> | ||||
|  <widget class="QWidget" name="LegacyUpgradePage"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>546</width> | ||||
|     <height>405</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||
|    <property name="leftMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="topMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="rightMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <property name="bottomMargin"> | ||||
|     <number>0</number> | ||||
|    </property> | ||||
|    <item> | ||||
|     <widget class="QTextBrowser" name="textBrowser"> | ||||
|      <property name="html"> | ||||
|       <string> | ||||
| <h1>New format is available</h1> | ||||
| <p>MultiMC now supports old Minecraft versions in the new (OneSix) instance format. The old format won't be getting any new features and only the most critical bugfixes. As a consequence, you should upgrade this instance.</p> | ||||
| <p>The upgrade will create a new instance with the same contents as the current one, in the new format. The original instance will remain untouched, in case anything goes wrong in the process.</p> | ||||
| <p>Please report any issues on our <a href="https://github.com/MultiMC/MultiMC5/issues"><img src=":/icons/multimc/22x22/bug.png" /> github issues page</a>.</p></string> | ||||
|      </property> | ||||
|      <property name="openExternalLinks"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QCommandLinkButton" name="upgradeButton"> | ||||
|      <property name="text"> | ||||
|       <string>Start the upgrade! (Not Yet Implemented, Coming Soon™)</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
		Reference in New Issue
	
	Block a user