NOISSUE reorganize and document libraries
This commit is contained in:
		
							
								
								
									
										597
									
								
								api/logic/minecraft/onesix/OneSixInstance.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										597
									
								
								api/logic/minecraft/onesix/OneSixInstance.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,597 @@ | ||||
| /* Copyright 2013-2015 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include <QDebug> | ||||
| #include <Env.h> | ||||
|  | ||||
| #include "OneSixInstance.h" | ||||
| #include "OneSixUpdate.h" | ||||
| #include "OneSixProfileStrategy.h" | ||||
|  | ||||
| #include "minecraft/MinecraftProfile.h" | ||||
| #include "minecraft/VersionBuildError.h" | ||||
| #include "launch/LaunchTask.h" | ||||
| #include "launch/steps/PreLaunchCommand.h" | ||||
| #include "launch/steps/Update.h" | ||||
| #include "launch/steps/LaunchMinecraft.h" | ||||
| #include "launch/steps/PostLaunchCommand.h" | ||||
| #include "launch/steps/TextPrint.h" | ||||
| #include "launch/steps/ModMinecraftJar.h" | ||||
| #include "launch/steps/CheckJava.h" | ||||
| #include "MMCZip.h" | ||||
|  | ||||
| #include "minecraft/AssetsUtils.h" | ||||
| #include "minecraft/WorldList.h" | ||||
| #include <FileSystem.h> | ||||
|  | ||||
| OneSixInstance::OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) | ||||
| 	: MinecraftInstance(globalSettings, settings, rootDir) | ||||
| { | ||||
| 	m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); | ||||
| } | ||||
|  | ||||
| void OneSixInstance::init() | ||||
| { | ||||
| 	createProfile(); | ||||
| } | ||||
|  | ||||
| void OneSixInstance::createProfile() | ||||
| { | ||||
| 	m_profile.reset(new MinecraftProfile(new OneSixProfileStrategy(this))); | ||||
| } | ||||
|  | ||||
| QSet<QString> OneSixInstance::traits() | ||||
| { | ||||
| 	auto version = getMinecraftProfile(); | ||||
| 	if (!version) | ||||
| 	{ | ||||
| 		return {"version-incomplete"}; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return version->getTraits(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Task> OneSixInstance::createUpdateTask() | ||||
| { | ||||
| 	return std::shared_ptr<Task>(new OneSixUpdate(this)); | ||||
| } | ||||
|  | ||||
| QString replaceTokensIn(QString text, QMap<QString, QString> with) | ||||
| { | ||||
| 	QString result; | ||||
| 	QRegExp token_regexp("\\$\\{(.+)\\}"); | ||||
| 	token_regexp.setMinimal(true); | ||||
| 	QStringList list; | ||||
| 	int tail = 0; | ||||
| 	int head = 0; | ||||
| 	while ((head = token_regexp.indexIn(text, head)) != -1) | ||||
| 	{ | ||||
| 		result.append(text.mid(tail, head - tail)); | ||||
| 		QString key = token_regexp.cap(1); | ||||
| 		auto iter = with.find(key); | ||||
| 		if (iter != with.end()) | ||||
| 		{ | ||||
| 			result.append(*iter); | ||||
| 		} | ||||
| 		head += token_regexp.matchedLength(); | ||||
| 		tail = head; | ||||
| 	} | ||||
| 	result.append(text.mid(tail)); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) | ||||
| { | ||||
| 	QString args_pattern = m_profile->getMinecraftArguments(); | ||||
| 	for (auto tweaker : m_profile->getTweakers()) | ||||
| 	{ | ||||
| 		args_pattern += " --tweakClass " + tweaker; | ||||
| 	} | ||||
|  | ||||
| 	QMap<QString, QString> token_mapping; | ||||
| 	// yggdrasil! | ||||
| 	token_mapping["auth_username"] = session->username; | ||||
| 	token_mapping["auth_session"] = session->session; | ||||
| 	token_mapping["auth_access_token"] = session->access_token; | ||||
| 	token_mapping["auth_player_name"] = session->player_name; | ||||
| 	token_mapping["auth_uuid"] = session->uuid; | ||||
|  | ||||
| 	// blatant self-promotion. | ||||
| 	token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; | ||||
| 	if(m_profile->isVanilla()) | ||||
| 	{ | ||||
| 		token_mapping["version_type"] = m_profile->getMinecraftVersionType(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		token_mapping["version_type"] = "custom"; | ||||
| 	} | ||||
|  | ||||
| 	QString absRootDir = QDir(minecraftRoot()).absolutePath(); | ||||
| 	token_mapping["game_directory"] = absRootDir; | ||||
| 	QString absAssetsDir = QDir("assets/").absolutePath(); | ||||
| 	auto assets = m_profile->getMinecraftAssets(); | ||||
| 	token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); | ||||
|  | ||||
| 	token_mapping["user_properties"] = session->serializeUserProperties(); | ||||
| 	token_mapping["user_type"] = session->user_type; | ||||
|  | ||||
| 	// 1.7.3+ assets tokens | ||||
| 	token_mapping["assets_root"] = absAssetsDir; | ||||
| 	token_mapping["assets_index_name"] = assets->id; | ||||
|  | ||||
| 	QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); | ||||
| 	for (int i = 0; i < parts.length(); i++) | ||||
| 	{ | ||||
| 		parts[i] = replaceTokensIn(parts[i], token_mapping); | ||||
| 	} | ||||
| 	return parts; | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::createLaunchScript(AuthSessionPtr session) | ||||
| { | ||||
| 	QString launchScript; | ||||
|  | ||||
| 	if (!m_profile) | ||||
| 		return nullptr; | ||||
|  | ||||
| 	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. | ||||
|  | ||||
| 		launchScript += "mod " + mod.filename().completeBaseName()  + "\n";; | ||||
| 	} | ||||
|  | ||||
| 	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. | ||||
|  | ||||
| 		launchScript += "coremod " + coremod.filename().completeBaseName()  + "\n";; | ||||
| 	} | ||||
|  | ||||
| 	for(auto & jarmod: m_profile->getJarMods()) | ||||
| 	{ | ||||
| 		launchScript += "jarmod " + jarmod->originalName + " (" + jarmod->name + ")\n"; | ||||
| 	} | ||||
|  | ||||
| 	auto mainClass = m_profile->getMainClass(); | ||||
| 	if (!mainClass.isEmpty()) | ||||
| 	{ | ||||
| 		launchScript += "mainClass " + mainClass + "\n"; | ||||
| 	} | ||||
| 	auto appletClass = m_profile->getAppletClass(); | ||||
| 	if (!appletClass.isEmpty()) | ||||
| 	{ | ||||
| 		launchScript += "appletClass " + appletClass + "\n"; | ||||
| 	} | ||||
|  | ||||
| 	// generic minecraft params | ||||
| 	for (auto param : processMinecraftArgs(session)) | ||||
| 	{ | ||||
| 		launchScript += "param " + param + "\n"; | ||||
| 	} | ||||
|  | ||||
| 	// window size, title and state, legacy | ||||
| 	{ | ||||
| 		QString windowParams; | ||||
| 		if (settings()->get("LaunchMaximized").toBool()) | ||||
| 			windowParams = "max"; | ||||
| 		else | ||||
| 			windowParams = QString("%1x%2") | ||||
| 							   .arg(settings()->get("MinecraftWinWidth").toInt()) | ||||
| 							   .arg(settings()->get("MinecraftWinHeight").toInt()); | ||||
| 		launchScript += "windowTitle " + windowTitle() + "\n"; | ||||
| 		launchScript += "windowParams " + windowParams + "\n"; | ||||
| 	} | ||||
|  | ||||
| 	// legacy auth | ||||
| 	{ | ||||
| 		launchScript += "userName " + session->player_name + "\n"; | ||||
| 		launchScript += "sessionId " + session->session + "\n"; | ||||
| 	} | ||||
|  | ||||
| 	// libraries and class path. | ||||
| 	{ | ||||
| 		auto libs = m_profile->getLibraries(); | ||||
|  | ||||
| 		QStringList jar, native, native32, native64; | ||||
| 		for (auto lib : libs) | ||||
| 		{ | ||||
| 			lib->getApplicableFiles(currentSystem, jar, native, native32, native64); | ||||
| 		} | ||||
| 		for(auto file: jar) | ||||
| 		{ | ||||
| 			launchScript += "cp " + file + "\n"; | ||||
| 		} | ||||
| 		for(auto file: native) | ||||
| 		{ | ||||
| 			launchScript += "ext " + file + "\n"; | ||||
| 		} | ||||
| 		for(auto file: native32) | ||||
| 		{ | ||||
| 			launchScript += "ext32 " + file + "\n"; | ||||
| 		} | ||||
| 		for(auto file: native64) | ||||
| 		{ | ||||
| 			launchScript += "ext64 " + file + "\n"; | ||||
| 		} | ||||
| 		QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); | ||||
| 		launchScript += "natives " + natives_dir.absolutePath() + "\n"; | ||||
| 		auto jarMods = getJarMods(); | ||||
| 		if (!jarMods.isEmpty()) | ||||
| 		{ | ||||
| 			launchScript += "cp " + QDir(instanceRoot()).absoluteFilePath("minecraft.jar") + "\n"; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			QString relpath = m_profile->getMinecraftVersion() + "/" + m_profile->getMinecraftVersion() + ".jar"; | ||||
| 			launchScript += "cp " + versionsPath().absoluteFilePath(relpath) + "\n"; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// traits. including legacyLaunch and others ;) | ||||
| 	for (auto trait : m_profile->getTraits()) | ||||
| 	{ | ||||
| 		launchScript += "traits " + trait + "\n"; | ||||
| 	} | ||||
| 	launchScript += "launcher onesix\n"; | ||||
| 	return launchScript; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<LaunchTask> OneSixInstance::createLaunchTask(AuthSessionPtr session) | ||||
| { | ||||
| 	auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); | ||||
| 	auto pptr = process.get(); | ||||
|  | ||||
| 	// print a header | ||||
| 	{ | ||||
| 		process->appendStep(std::make_shared<TextPrint>(pptr, "Minecraft folder is:\n" + minecraftRoot() + "\n\n", MessageLevel::MultiMC)); | ||||
| 	} | ||||
| 	{ | ||||
| 		auto step = std::make_shared<CheckJava>(pptr); | ||||
| 		process->appendStep(step); | ||||
| 	} | ||||
| 	// run pre-launch command if that's needed | ||||
| 	if(getPreLaunchCommand().size()) | ||||
| 	{ | ||||
| 		auto step = std::make_shared<PreLaunchCommand>(pptr); | ||||
| 		step->setWorkingDirectory(minecraftRoot()); | ||||
| 		process->appendStep(step); | ||||
| 	} | ||||
| 	// if we aren't in offline mode,. | ||||
| 	if(session->status != AuthSession::PlayableOffline) | ||||
| 	{ | ||||
| 		process->appendStep(std::make_shared<Update>(pptr)); | ||||
| 	} | ||||
| 	// if there are any jar mods | ||||
| 	if(getJarMods().size()) | ||||
| 	{ | ||||
| 		auto step = std::make_shared<ModMinecraftJar>(pptr); | ||||
| 		process->appendStep(step); | ||||
| 	} | ||||
| 	// actually launch the game | ||||
| 	{ | ||||
| 		auto step = std::make_shared<LaunchMinecraft>(pptr); | ||||
| 		step->setWorkingDirectory(minecraftRoot()); | ||||
| 		step->setAuthSession(session); | ||||
| 		process->appendStep(step); | ||||
| 	} | ||||
| 	// run post-exit command if that's needed | ||||
| 	if(getPostExitCommand().size()) | ||||
| 	{ | ||||
| 		auto step = std::make_shared<PostLaunchCommand>(pptr); | ||||
| 		step->setWorkingDirectory(minecraftRoot()); | ||||
| 		process->appendStep(step); | ||||
| 	} | ||||
| 	if (session) | ||||
| 	{ | ||||
| 		process->setCensorFilter(createCensorFilterFromSession(session)); | ||||
| 	} | ||||
| 	return process; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<Task> OneSixInstance::createJarModdingTask() | ||||
| { | ||||
| 	class JarModTask : public Task | ||||
| 	{ | ||||
| 	public: | ||||
| 		explicit JarModTask(std::shared_ptr<OneSixInstance> inst) : Task(nullptr), m_inst(inst) | ||||
| 		{ | ||||
| 		} | ||||
| 		virtual void executeTask() | ||||
| 		{ | ||||
| 			auto profile = m_inst->getMinecraftProfile(); | ||||
| 			// nuke obsolete stripped jar(s) if needed | ||||
| 			QString version_id = profile->getMinecraftVersion(); | ||||
| 			QString strippedPath = version_id + "/" + version_id + "-stripped.jar"; | ||||
| 			QFile strippedJar(strippedPath); | ||||
| 			if(strippedJar.exists()) | ||||
| 			{ | ||||
| 				strippedJar.remove(); | ||||
| 			} | ||||
| 			auto tempJarPath = QDir(m_inst->instanceRoot()).absoluteFilePath("temp.jar"); | ||||
| 			QFile tempJar(tempJarPath); | ||||
| 			if(tempJar.exists()) | ||||
| 			{ | ||||
| 				tempJar.remove(); | ||||
| 			} | ||||
| 			auto finalJarPath = QDir(m_inst->instanceRoot()).absoluteFilePath("minecraft.jar"); | ||||
| 			QFile finalJar(finalJarPath); | ||||
| 			if(finalJar.exists()) | ||||
| 			{ | ||||
| 				if(!finalJar.remove()) | ||||
| 				{ | ||||
| 					emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// create temporary modded jar, if needed | ||||
| 			auto jarMods = m_inst->getJarMods(); | ||||
| 			if(jarMods.size()) | ||||
| 			{ | ||||
| 				auto sourceJarPath = m_inst->versionsPath().absoluteFilePath(version_id + "/" + version_id + ".jar"); | ||||
| 				QString localPath = version_id + "/" + version_id + ".jar"; | ||||
| 				auto metacache = ENV.metacache(); | ||||
| 				auto entry = metacache->resolveEntry("versions", localPath); | ||||
| 				QString fullJarPath = entry->getFullPath(); | ||||
| 				if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) | ||||
| 				{ | ||||
| 					emitFailed(tr("Failed to create the custom Minecraft jar file.")); | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 			emitSucceeded(); | ||||
| 		} | ||||
| 		std::shared_ptr<OneSixInstance> m_inst; | ||||
| 	}; | ||||
| 	return std::make_shared<JarModTask>(std::dynamic_pointer_cast<OneSixInstance>(shared_from_this())); | ||||
| } | ||||
|  | ||||
| void OneSixInstance::cleanupAfterRun() | ||||
| { | ||||
| 	QString target_dir = FS::PathCombine(instanceRoot(), "natives/"); | ||||
| 	QDir dir(target_dir); | ||||
| 	dir.removeRecursively(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> OneSixInstance::loaderModList() const | ||||
| { | ||||
| 	if (!m_loader_mod_list) | ||||
| 	{ | ||||
| 		m_loader_mod_list.reset(new ModList(loaderModsDir())); | ||||
| 	} | ||||
| 	m_loader_mod_list->update(); | ||||
| 	return m_loader_mod_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> OneSixInstance::coreModList() const | ||||
| { | ||||
| 	if (!m_core_mod_list) | ||||
| 	{ | ||||
| 		m_core_mod_list.reset(new ModList(coreModsDir())); | ||||
| 	} | ||||
| 	m_core_mod_list->update(); | ||||
| 	return m_core_mod_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> OneSixInstance::resourcePackList() const | ||||
| { | ||||
| 	if (!m_resource_pack_list) | ||||
| 	{ | ||||
| 		m_resource_pack_list.reset(new ModList(resourcePacksDir())); | ||||
| 	} | ||||
| 	m_resource_pack_list->update(); | ||||
| 	return m_resource_pack_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ModList> OneSixInstance::texturePackList() const | ||||
| { | ||||
| 	if (!m_texture_pack_list) | ||||
| 	{ | ||||
| 		m_texture_pack_list.reset(new ModList(texturePacksDir())); | ||||
| 	} | ||||
| 	m_texture_pack_list->update(); | ||||
| 	return m_texture_pack_list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<WorldList> OneSixInstance::worldList() const | ||||
| { | ||||
| 	if (!m_world_list) | ||||
| 	{ | ||||
| 		m_world_list.reset(new WorldList(worldDir())); | ||||
| 	} | ||||
| 	return m_world_list; | ||||
| } | ||||
|  | ||||
| bool OneSixInstance::setIntendedVersionId(QString version) | ||||
| { | ||||
| 	settings()->set("IntendedVersion", version); | ||||
| 	if(getMinecraftProfile()) | ||||
| 	{ | ||||
| 		clearProfile(); | ||||
| 	} | ||||
| 	emit propertiesChanged(this); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| QList< Mod > OneSixInstance::getJarMods() const | ||||
| { | ||||
| 	QList<Mod> mods; | ||||
| 	for (auto jarmod : m_profile->getJarMods()) | ||||
| 	{ | ||||
| 		QString filePath = jarmodsPath().absoluteFilePath(jarmod->name); | ||||
| 		mods.push_back(Mod(QFileInfo(filePath))); | ||||
| 	} | ||||
| 	return mods; | ||||
| } | ||||
|  | ||||
|  | ||||
| QString OneSixInstance::intendedVersionId() const | ||||
| { | ||||
| 	return settings()->get("IntendedVersion").toString(); | ||||
| } | ||||
|  | ||||
| void OneSixInstance::setShouldUpdate(bool) | ||||
| { | ||||
| } | ||||
|  | ||||
| bool OneSixInstance::shouldUpdate() const | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::currentVersionId() const | ||||
| { | ||||
| 	return intendedVersionId(); | ||||
| } | ||||
|  | ||||
| void OneSixInstance::reloadProfile() | ||||
| { | ||||
| 	m_profile->reload(); | ||||
| 	auto severity = m_profile->getProblemSeverity(); | ||||
| 	if(severity == ProblemSeverity::PROBLEM_ERROR) | ||||
| 	{ | ||||
| 		setFlag(VersionBrokenFlag); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		unsetFlag(VersionBrokenFlag); | ||||
| 	} | ||||
| 	emit versionReloaded(); | ||||
| } | ||||
|  | ||||
| void OneSixInstance::clearProfile() | ||||
| { | ||||
| 	m_profile->clear(); | ||||
| 	emit versionReloaded(); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<MinecraftProfile> OneSixInstance::getMinecraftProfile() const | ||||
| { | ||||
| 	return m_profile; | ||||
| } | ||||
|  | ||||
| QDir OneSixInstance::librariesPath() const | ||||
| { | ||||
| 	return QDir::current().absoluteFilePath("libraries"); | ||||
| } | ||||
|  | ||||
| QDir OneSixInstance::jarmodsPath() const | ||||
| { | ||||
| 	return QDir(jarModsDir()); | ||||
| } | ||||
|  | ||||
| QDir OneSixInstance::versionsPath() const | ||||
| { | ||||
| 	return QDir::current().absoluteFilePath("versions"); | ||||
| } | ||||
|  | ||||
| bool OneSixInstance::providesVersionFile() const | ||||
| { | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool OneSixInstance::reload() | ||||
| { | ||||
| 	if (BaseInstance::reload()) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			reloadProfile(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		catch (...) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::loaderModsDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "mods"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::coreModsDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "coremods"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::resourcePacksDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "resourcepacks"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::texturePacksDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "texturepacks"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::instanceConfigFolder() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "config"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::jarModsDir() const | ||||
| { | ||||
| 	return FS::PathCombine(instanceRoot(), "jarmods"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::libDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "lib"); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::worldDir() const | ||||
| { | ||||
| 	return FS::PathCombine(minecraftRoot(), "saves"); | ||||
| } | ||||
|  | ||||
| QStringList OneSixInstance::extraArguments() const | ||||
| { | ||||
| 	auto list = BaseInstance::extraArguments(); | ||||
| 	auto version = getMinecraftProfile(); | ||||
| 	if (!version) | ||||
| 		return list; | ||||
| 	auto jarMods = getJarMods(); | ||||
| 	if (!jarMods.isEmpty()) | ||||
| 	{ | ||||
| 		list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", | ||||
| 					 "-Dfml.ignorePatchDiscrepancies=true"}); | ||||
| 	} | ||||
| 	return list; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr() | ||||
| { | ||||
| 	return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr()); | ||||
| } | ||||
|  | ||||
| QString OneSixInstance::typeName() const | ||||
| { | ||||
| 	return tr("OneSix"); | ||||
| } | ||||
							
								
								
									
										117
									
								
								api/logic/minecraft/onesix/OneSixInstance.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								api/logic/minecraft/onesix/OneSixInstance.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| /* Copyright 2013-2015 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "minecraft/MinecraftInstance.h" | ||||
|  | ||||
| #include "minecraft/MinecraftProfile.h" | ||||
| #include "minecraft/ModList.h" | ||||
|  | ||||
| #include "multimc_logic_export.h" | ||||
|  | ||||
| class MULTIMC_LOGIC_EXPORT OneSixInstance : public MinecraftInstance | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); | ||||
| 	virtual ~OneSixInstance(){}; | ||||
|  | ||||
| 	virtual void init() override; | ||||
|  | ||||
| 	//////  Mod Lists  ////// | ||||
| 	std::shared_ptr<ModList> loaderModList() const; | ||||
| 	std::shared_ptr<ModList> coreModList() const; | ||||
| 	std::shared_ptr<ModList> resourcePackList() const override; | ||||
| 	std::shared_ptr<ModList> texturePackList() const override; | ||||
| 	std::shared_ptr<WorldList> worldList() const override; | ||||
| 	virtual QList<Mod> getJarMods() const override; | ||||
| 	virtual void createProfile(); | ||||
|  | ||||
| 	virtual QSet<QString> traits() override; | ||||
|  | ||||
| 	////// Directories and files ////// | ||||
| 	QString jarModsDir() const; | ||||
| 	QString resourcePacksDir() const; | ||||
| 	QString texturePacksDir() const; | ||||
| 	QString loaderModsDir() const; | ||||
| 	QString coreModsDir() const; | ||||
| 	QString libDir() const; | ||||
| 	QString worldDir() const; | ||||
| 	virtual QString instanceConfigFolder() const override; | ||||
|  | ||||
| 	virtual std::shared_ptr<Task> createUpdateTask() override; | ||||
| 	virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override; | ||||
| 	virtual std::shared_ptr<Task> createJarModdingTask() override; | ||||
|  | ||||
| 	virtual QString createLaunchScript(AuthSessionPtr session) override; | ||||
|  | ||||
| 	virtual void cleanupAfterRun() override; | ||||
|  | ||||
| 	virtual QString intendedVersionId() const override; | ||||
| 	virtual bool setIntendedVersionId(QString version) override; | ||||
|  | ||||
| 	virtual QString currentVersionId() const override; | ||||
|  | ||||
| 	virtual bool shouldUpdate() const override; | ||||
| 	virtual void setShouldUpdate(bool val) override; | ||||
|  | ||||
| 	/** | ||||
| 	 * reload the profile, including version json files. | ||||
| 	 * | ||||
| 	 * throws various exceptions :3 | ||||
| 	 */ | ||||
| 	void reloadProfile(); | ||||
|  | ||||
| 	/// clears all version information in preparation for an update | ||||
| 	void clearProfile(); | ||||
|  | ||||
| 	/// get the current full version info | ||||
| 	std::shared_ptr<MinecraftProfile> getMinecraftProfile() const; | ||||
|  | ||||
| 	virtual QDir jarmodsPath() const; | ||||
| 	virtual QDir librariesPath() const; | ||||
| 	virtual QDir versionsPath() const; | ||||
| 	virtual bool providesVersionFile() const; | ||||
|  | ||||
| 	bool reload() override; | ||||
|  | ||||
| 	virtual QStringList extraArguments() const override; | ||||
|  | ||||
| 	std::shared_ptr<OneSixInstance> getSharedPtr(); | ||||
|  | ||||
| 	virtual QString typeName() const override; | ||||
|  | ||||
| 	bool canExport() const override | ||||
| 	{ | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| signals: | ||||
| 	void versionReloaded(); | ||||
|  | ||||
| private: | ||||
| 	QStringList processMinecraftArgs(AuthSessionPtr account); | ||||
|  | ||||
| protected: | ||||
| 	std::shared_ptr<MinecraftProfile> m_profile; | ||||
| 	mutable std::shared_ptr<ModList> m_loader_mod_list; | ||||
| 	mutable std::shared_ptr<ModList> m_core_mod_list; | ||||
| 	mutable std::shared_ptr<ModList> m_resource_pack_list; | ||||
| 	mutable std::shared_ptr<ModList> m_texture_pack_list; | ||||
| 	mutable std::shared_ptr<WorldList> m_world_list; | ||||
| }; | ||||
|  | ||||
| Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>) | ||||
							
								
								
									
										418
									
								
								api/logic/minecraft/onesix/OneSixProfileStrategy.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										418
									
								
								api/logic/minecraft/onesix/OneSixProfileStrategy.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,418 @@ | ||||
| #include "OneSixProfileStrategy.h" | ||||
| #include "OneSixInstance.h" | ||||
| #include "OneSixVersionFormat.h" | ||||
|  | ||||
| #include "minecraft/VersionBuildError.h" | ||||
| #include "minecraft/MinecraftVersionList.h" | ||||
| #include "Env.h" | ||||
| #include <FileSystem.h> | ||||
|  | ||||
| #include <QDir> | ||||
| #include <QUuid> | ||||
| #include <QJsonDocument> | ||||
| #include <QJsonArray> | ||||
|  | ||||
| OneSixProfileStrategy::OneSixProfileStrategy(OneSixInstance* instance) | ||||
| { | ||||
| 	m_instance = instance; | ||||
| } | ||||
|  | ||||
| void OneSixProfileStrategy::upgradeDeprecatedFiles() | ||||
| { | ||||
| 	auto versionJsonPath = FS::PathCombine(m_instance->instanceRoot(), "version.json"); | ||||
| 	auto customJsonPath = FS::PathCombine(m_instance->instanceRoot(), "custom.json"); | ||||
| 	auto mcJson = FS::PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json"); | ||||
|  | ||||
| 	QString sourceFile; | ||||
| 	QString renameFile; | ||||
|  | ||||
| 	// convert old crap. | ||||
| 	if(QFile::exists(customJsonPath)) | ||||
| 	{ | ||||
| 		sourceFile = customJsonPath; | ||||
| 		renameFile = versionJsonPath; | ||||
| 	} | ||||
| 	else if(QFile::exists(versionJsonPath)) | ||||
| 	{ | ||||
| 		sourceFile = versionJsonPath; | ||||
| 	} | ||||
| 	if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) | ||||
| 	{ | ||||
| 		if(!FS::ensureFilePathExists(mcJson)) | ||||
| 		{ | ||||
| 			qWarning() << "Couldn't create patches folder for" << m_instance->name(); | ||||
| 			return; | ||||
| 		} | ||||
| 		if(!renameFile.isEmpty() && QFile::exists(renameFile)) | ||||
| 		{ | ||||
| 			if(!QFile::rename(renameFile, renameFile + ".old")) | ||||
| 			{ | ||||
| 				qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << m_instance->name(); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 		auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); | ||||
| 		ProfileUtils::removeLwjglFromPatch(file); | ||||
| 		file->fileId = "net.minecraft"; | ||||
| 		file->version = file->minecraftVersion; | ||||
| 		file->name = "Minecraft"; | ||||
| 		auto data = OneSixVersionFormat::versionFileToJson(file, false).toJson(); | ||||
| 		QSaveFile newPatchFile(mcJson); | ||||
| 		if(!newPatchFile.open(QIODevice::WriteOnly)) | ||||
| 		{ | ||||
| 			newPatchFile.cancelWriting(); | ||||
| 			qWarning() << "Couldn't open main patch for writing in" << m_instance->name(); | ||||
| 			return; | ||||
| 		} | ||||
| 		newPatchFile.write(data); | ||||
| 		if(!newPatchFile.commit()) | ||||
| 		{ | ||||
| 			qWarning() << "Couldn't save main patch in" << m_instance->name(); | ||||
| 			return; | ||||
| 		} | ||||
| 		if(!QFile::rename(sourceFile, sourceFile + ".old")) | ||||
| 		{ | ||||
| 			qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << m_instance->name(); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OneSixProfileStrategy::loadDefaultBuiltinPatches() | ||||
| { | ||||
| 	{ | ||||
| 		auto mcJson = FS::PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json"); | ||||
| 		// load up the base minecraft patch | ||||
| 		ProfilePatchPtr minecraftPatch; | ||||
| 		if(QFile::exists(mcJson)) | ||||
| 		{ | ||||
| 			auto file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false); | ||||
| 			if(file->version.isEmpty()) | ||||
| 			{ | ||||
| 				file->version = m_instance->intendedVersionId(); | ||||
| 			} | ||||
| 			file->setVanilla(false); | ||||
| 			file->setRevertible(true); | ||||
| 			minecraftPatch = std::dynamic_pointer_cast<ProfilePatch>(file); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			auto mcversion = ENV.getVersion("net.minecraft", m_instance->intendedVersionId()); | ||||
| 			minecraftPatch = std::dynamic_pointer_cast<ProfilePatch>(mcversion); | ||||
| 		} | ||||
| 		if (!minecraftPatch) | ||||
| 		{ | ||||
| 			throw VersionIncomplete("net.minecraft"); | ||||
| 		} | ||||
| 		minecraftPatch->setOrder(-2); | ||||
| 		profile->appendPatch(minecraftPatch); | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		auto lwjglJson = FS::PathCombine(m_instance->instanceRoot(), "patches" , "org.lwjgl.json"); | ||||
| 		ProfilePatchPtr lwjglPatch; | ||||
| 		if(QFile::exists(lwjglJson)) | ||||
| 		{ | ||||
| 			auto file = ProfileUtils::parseJsonFile(QFileInfo(lwjglJson), false); | ||||
| 			file->setVanilla(false); | ||||
| 			file->setRevertible(true); | ||||
| 			lwjglPatch = std::dynamic_pointer_cast<ProfilePatch>(file); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// NOTE: this is obviously fake, is fixed in unstable. | ||||
| 			QResource LWJGL(":/versions/LWJGL/2.9.1.json"); | ||||
| 			auto lwjgl = ProfileUtils::parseJsonFile(LWJGL.absoluteFilePath(), false); | ||||
| 			lwjgl->setVanilla(true); | ||||
| 			lwjgl->setCustomizable(true); | ||||
| 			lwjglPatch = std::dynamic_pointer_cast<ProfilePatch>(lwjgl); | ||||
| 		} | ||||
| 		if (!lwjglPatch) | ||||
| 		{ | ||||
| 			throw VersionIncomplete("org.lwjgl"); | ||||
| 		} | ||||
| 		lwjglPatch->setOrder(-1); | ||||
| 		profile->appendPatch(lwjglPatch); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OneSixProfileStrategy::loadUserPatches() | ||||
| { | ||||
| 	// load all patches, put into map for ordering, apply in the right order | ||||
| 	ProfileUtils::PatchOrder userOrder; | ||||
| 	ProfileUtils::readOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), userOrder); | ||||
| 	QDir patches(FS::PathCombine(m_instance->instanceRoot(),"patches")); | ||||
| 	QSet<QString> seen_extra; | ||||
|  | ||||
| 	// first, load things by sort order. | ||||
| 	for (auto id : userOrder) | ||||
| 	{ | ||||
| 		// ignore builtins | ||||
| 		if (id == "net.minecraft") | ||||
| 			continue; | ||||
| 		if (id == "org.lwjgl") | ||||
| 			continue; | ||||
| 		// parse the file | ||||
| 		QString filename = patches.absoluteFilePath(id + ".json"); | ||||
| 		QFileInfo finfo(filename); | ||||
| 		if(!finfo.exists()) | ||||
| 		{ | ||||
| 			qDebug() << "Patch file " << filename << " was deleted by external means..."; | ||||
| 			continue; | ||||
| 		} | ||||
| 		qDebug() << "Reading" << filename << "by user order"; | ||||
| 		VersionFilePtr file = ProfileUtils::parseJsonFile(finfo, false); | ||||
| 		// sanity check. prevent tampering with files. | ||||
| 		if (file->fileId != id) | ||||
| 		{ | ||||
| 			file->addProblem(PROBLEM_WARNING, QObject::tr("load id %1 does not match internal id %2").arg(id, file->fileId)); | ||||
| 			seen_extra.insert(file->fileId); | ||||
| 		} | ||||
| 		file->setRemovable(true); | ||||
| 		file->setMovable(true); | ||||
| 		profile->appendPatch(file); | ||||
| 	} | ||||
| 	// now load the rest by internal preference. | ||||
| 	QMultiMap<int, VersionFilePtr> files; | ||||
| 	for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) | ||||
| 	{ | ||||
| 		// parse the file | ||||
| 		qDebug() << "Reading" << info.fileName(); | ||||
| 		auto file = ProfileUtils::parseJsonFile(info, true); | ||||
| 		// ignore builtins | ||||
| 		if (file->fileId == "net.minecraft") | ||||
| 			continue; | ||||
| 		if (file->fileId == "org.lwjgl") | ||||
| 			continue; | ||||
| 		// do not load versions with broken IDs twice | ||||
| 		if(seen_extra.contains(file->fileId)) | ||||
| 			continue; | ||||
| 		// do not load what we already loaded in the first pass | ||||
| 		if (userOrder.contains(file->fileId)) | ||||
| 			continue; | ||||
| 		file->setRemovable(true); | ||||
| 		file->setMovable(true); | ||||
| 		files.insert(file->order, file); | ||||
| 	} | ||||
| 	QSet<int> seen; | ||||
| 	for (auto order : files.keys()) | ||||
| 	{ | ||||
| 		if(seen.contains(order)) | ||||
| 			continue; | ||||
| 		seen.insert(order); | ||||
| 		const auto &values = files.values(order); | ||||
| 		if(values.size() == 1) | ||||
| 		{ | ||||
| 			profile->appendPatch(values[0]); | ||||
| 			continue; | ||||
| 		} | ||||
| 		for(auto &file: values) | ||||
| 		{ | ||||
| 			QStringList list; | ||||
| 			for(auto &file2: values) | ||||
| 			{ | ||||
| 				if(file != file2) | ||||
| 					list.append(file2->name); | ||||
| 			} | ||||
| 			file->addProblem(PROBLEM_WARNING, QObject::tr("%1 has the same order as the following components:\n%2").arg(file->name, list.join(", "))); | ||||
| 			profile->appendPatch(file); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| void OneSixProfileStrategy::load() | ||||
| { | ||||
| 	profile->clearPatches(); | ||||
|  | ||||
| 	upgradeDeprecatedFiles(); | ||||
| 	loadDefaultBuiltinPatches(); | ||||
| 	loadUserPatches(); | ||||
| } | ||||
|  | ||||
| bool OneSixProfileStrategy::saveOrder(ProfileUtils::PatchOrder order) | ||||
| { | ||||
| 	return ProfileUtils::writeOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), order); | ||||
| } | ||||
|  | ||||
| bool OneSixProfileStrategy::resetOrder() | ||||
| { | ||||
| 	return QDir(m_instance->instanceRoot()).remove("order.json"); | ||||
| } | ||||
|  | ||||
| bool OneSixProfileStrategy::removePatch(ProfilePatchPtr patch) | ||||
| { | ||||
| 	bool ok = true; | ||||
| 	// first, remove the patch file. this ensures it's not used anymore | ||||
| 	auto fileName = patch->getFilename(); | ||||
| 	if(fileName.size()) | ||||
| 	{ | ||||
| 		QFile patchFile(fileName); | ||||
| 		if(patchFile.exists() && !patchFile.remove()) | ||||
| 		{ | ||||
| 			qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	auto preRemoveJarMod = [&](JarmodPtr jarMod) -> bool | ||||
| 	{ | ||||
| 		QString fullpath = FS::PathCombine(m_instance->jarModsDir(), jarMod->name); | ||||
| 		QFileInfo finfo (fullpath); | ||||
| 		if(finfo.exists()) | ||||
| 		{ | ||||
| 			QFile jarModFile(fullpath); | ||||
| 			if(!jarModFile.remove()) | ||||
| 			{ | ||||
| 				qCritical() << "File" << fullpath << "could not be removed because:" << jarModFile.errorString(); | ||||
| 				return false; | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
| 		return true; | ||||
| 	}; | ||||
|  | ||||
| 	for(auto &jarmod: patch->getJarMods()) | ||||
| 	{ | ||||
| 		ok &= preRemoveJarMod(jarmod); | ||||
| 	} | ||||
| 	return ok; | ||||
| } | ||||
|  | ||||
| bool OneSixProfileStrategy::customizePatch(ProfilePatchPtr patch) | ||||
| { | ||||
| 	if(patch->isCustom()) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	auto filename = FS::PathCombine(m_instance->instanceRoot(), "patches" , patch->getID() + ".json"); | ||||
| 	if(!FS::ensureFilePathExists(filename)) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	try | ||||
| 	{ | ||||
| 		QSaveFile jsonFile(filename); | ||||
| 		if(!jsonFile.open(QIODevice::WriteOnly)) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 		auto vfile = patch->getVersionFile(); | ||||
| 		if(!vfile) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 		auto document = OneSixVersionFormat::versionFileToJson(vfile, true); | ||||
| 		jsonFile.write(document.toJson()); | ||||
| 		if(!jsonFile.commit()) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 		load(); | ||||
| 	} | ||||
| 	catch (VersionIncomplete &error) | ||||
| 	{ | ||||
| 		qDebug() << "Version was incomplete:" << error.cause(); | ||||
| 	} | ||||
| 	catch (Exception &error) | ||||
| 	{ | ||||
| 		qWarning() << "Version could not be loaded:" << error.cause(); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool OneSixProfileStrategy::revertPatch(ProfilePatchPtr patch) | ||||
| { | ||||
| 	if(!patch->isCustom()) | ||||
| 	{ | ||||
| 		// already not custom | ||||
| 		return true; | ||||
| 	} | ||||
| 	auto filename = patch->getFilename(); | ||||
| 	if(!QFile::exists(filename)) | ||||
| 	{ | ||||
| 		// already gone / not custom | ||||
| 		return true; | ||||
| 	} | ||||
| 	// just kill the file and reload | ||||
| 	bool result = QFile::remove(filename); | ||||
| 	try | ||||
| 	{ | ||||
| 		load(); | ||||
| 	} | ||||
| 	catch (VersionIncomplete &error) | ||||
| 	{ | ||||
| 		qDebug() << "Version was incomplete:" << error.cause(); | ||||
| 	} | ||||
| 	catch (Exception &error) | ||||
| 	{ | ||||
| 		qWarning() << "Version could not be loaded:" << error.cause(); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| bool OneSixProfileStrategy::installJarMods(QStringList filepaths) | ||||
| { | ||||
| 	QString patchDir = FS::PathCombine(m_instance->instanceRoot(), "patches"); | ||||
| 	if(!FS::ensureFolderPathExists(patchDir)) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!FS::ensureFolderPathExists(m_instance->jarModsDir())) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	for(auto filepath:filepaths) | ||||
| 	{ | ||||
| 		QFileInfo sourceInfo(filepath); | ||||
| 		auto uuid = QUuid::createUuid(); | ||||
| 		QString id = uuid.toString().remove('{').remove('}'); | ||||
| 		QString target_filename = id + ".jar"; | ||||
| 		QString target_id = "org.multimc.jarmod." + id; | ||||
| 		QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; | ||||
| 		QString finalPath = FS::PathCombine(m_instance->jarModsDir(), target_filename); | ||||
|  | ||||
| 		QFileInfo targetInfo(finalPath); | ||||
| 		if(targetInfo.exists()) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
|  | ||||
| 		auto f = std::make_shared<VersionFile>(); | ||||
| 		auto jarMod = std::make_shared<Jarmod>(); | ||||
| 		jarMod->name = target_filename; | ||||
| 		jarMod->originalName = sourceInfo.completeBaseName(); | ||||
| 		f->jarMods.append(jarMod); | ||||
| 		f->name = target_name; | ||||
| 		f->fileId = target_id; | ||||
| 		f->order = profile->getFreeOrderNumber(); | ||||
| 		QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); | ||||
| 		f->filename = patchFileName; | ||||
| 		f->setMovable(true); | ||||
| 		f->setRemovable(true); | ||||
|  | ||||
| 		QFile file(patchFileName); | ||||
| 		if (!file.open(QFile::WriteOnly)) | ||||
| 		{ | ||||
| 			qCritical() << "Error opening" << file.fileName() | ||||
| 						<< "for reading:" << file.errorString(); | ||||
| 			return false; | ||||
| 		} | ||||
| 		file.write(OneSixVersionFormat::versionFileToJson(f, true).toJson()); | ||||
| 		file.close(); | ||||
| 		profile->appendPatch(f); | ||||
| 	} | ||||
| 	profile->saveCurrentOrder(); | ||||
| 	profile->reapplyPatches(); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
							
								
								
									
										26
									
								
								api/logic/minecraft/onesix/OneSixProfileStrategy.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								api/logic/minecraft/onesix/OneSixProfileStrategy.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #pragma once | ||||
| #include "minecraft/ProfileStrategy.h" | ||||
|  | ||||
| class OneSixInstance; | ||||
|  | ||||
| class OneSixProfileStrategy : public ProfileStrategy | ||||
| { | ||||
| public: | ||||
| 	OneSixProfileStrategy(OneSixInstance * instance); | ||||
| 	virtual ~OneSixProfileStrategy() {}; | ||||
| 	virtual void load() override; | ||||
| 	virtual bool resetOrder() override; | ||||
| 	virtual bool saveOrder(ProfileUtils::PatchOrder order) override; | ||||
| 	virtual bool installJarMods(QStringList filepaths) override; | ||||
| 	virtual bool removePatch(ProfilePatchPtr patch) override; | ||||
| 	virtual bool customizePatch(ProfilePatchPtr patch) override; | ||||
| 	virtual bool revertPatch(ProfilePatchPtr patch) override; | ||||
|  | ||||
| protected: | ||||
| 	virtual void loadDefaultBuiltinPatches(); | ||||
| 	virtual void loadUserPatches(); | ||||
| 	void upgradeDeprecatedFiles(); | ||||
|  | ||||
| protected: | ||||
| 	OneSixInstance *m_instance; | ||||
| }; | ||||
							
								
								
									
										342
									
								
								api/logic/minecraft/onesix/OneSixUpdate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								api/logic/minecraft/onesix/OneSixUpdate.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | ||||
| /* Copyright 2013-2015 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include "Env.h" | ||||
| #include <minecraft/forge/ForgeXzDownload.h> | ||||
| #include "OneSixUpdate.h" | ||||
| #include "OneSixInstance.h" | ||||
|  | ||||
| #include <QtNetwork> | ||||
|  | ||||
| #include <QFile> | ||||
| #include <QFileInfo> | ||||
| #include <QTextStream> | ||||
| #include <QDataStream> | ||||
| #include <JlCompress.h> | ||||
|  | ||||
| #include "BaseInstance.h" | ||||
| #include "minecraft/MinecraftVersionList.h" | ||||
| #include "minecraft/MinecraftProfile.h" | ||||
| #include "minecraft/Library.h" | ||||
| #include "net/URLConstants.h" | ||||
| #include "minecraft/AssetsUtils.h" | ||||
| #include "Exception.h" | ||||
| #include "MMCZip.h" | ||||
| #include <FileSystem.h> | ||||
|  | ||||
| OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) | ||||
| { | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::executeTask() | ||||
| { | ||||
| 	// Make directories | ||||
| 	QDir mcDir(m_inst->minecraftRoot()); | ||||
| 	if (!mcDir.exists() && !mcDir.mkpath(".")) | ||||
| 	{ | ||||
| 		emitFailed(tr("Failed to create folder for minecraft binaries.")); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Get a pointer to the version object that corresponds to the instance's version. | ||||
| 	targetVersion = std::dynamic_pointer_cast<MinecraftVersion>(ENV.getVersion("net.minecraft", m_inst->intendedVersionId())); | ||||
| 	if (targetVersion == nullptr) | ||||
| 	{ | ||||
| 		// don't do anything if it was invalid | ||||
| 		emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); | ||||
| 		return; | ||||
| 	} | ||||
| 	if (m_inst->providesVersionFile() || !targetVersion->needsUpdate()) | ||||
| 	{ | ||||
| 		qDebug() << "Instance either provides a version file or doesn't need an update."; | ||||
| 		jarlibStart(); | ||||
| 		return; | ||||
| 	} | ||||
| 	versionUpdateTask = std::dynamic_pointer_cast<MinecraftVersionList>(ENV.getVersionList("net.minecraft"))->createUpdateTask(m_inst->intendedVersionId()); | ||||
| 	if (!versionUpdateTask) | ||||
| 	{ | ||||
| 		qDebug() << "Didn't spawn an update task."; | ||||
| 		jarlibStart(); | ||||
| 		return; | ||||
| 	} | ||||
| 	connect(versionUpdateTask.get(), SIGNAL(succeeded()), SLOT(jarlibStart())); | ||||
| 	connect(versionUpdateTask.get(), &NetJob::failed, this, &OneSixUpdate::versionUpdateFailed); | ||||
| 	connect(versionUpdateTask.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); | ||||
| 	setStatus(tr("Getting the version files from Mojang...")); | ||||
| 	versionUpdateTask->start(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::versionUpdateFailed(QString reason) | ||||
| { | ||||
| 	emitFailed(reason); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::assetIndexStart() | ||||
| { | ||||
| 	setStatus(tr("Updating assets index...")); | ||||
| 	OneSixInstance *inst = (OneSixInstance *)m_inst; | ||||
| 	auto profile = inst->getMinecraftProfile(); | ||||
| 	auto assets = profile->getMinecraftAssets(); | ||||
| 	QUrl indexUrl = assets->url; | ||||
| 	QString localPath = assets->id + ".json"; | ||||
| 	auto job = new NetJob(tr("Asset index for %1").arg(inst->name())); | ||||
|  | ||||
| 	auto metacache = ENV.metacache(); | ||||
| 	auto entry = metacache->resolveEntry("asset_indexes", localPath); | ||||
| 	entry->setStale(true); | ||||
| 	job->addNetAction(CacheDownload::make(indexUrl, entry)); | ||||
| 	jarlibDownloadJob.reset(job); | ||||
|  | ||||
| 	connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); | ||||
| 	connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::assetIndexFailed); | ||||
| 	connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); | ||||
|  | ||||
| 	qDebug() << m_inst->name() << ": Starting asset index download"; | ||||
| 	jarlibDownloadJob->start(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::assetIndexFinished() | ||||
| { | ||||
| 	AssetsIndex index; | ||||
| 	qDebug() << m_inst->name() << ": Finished asset index download"; | ||||
|  | ||||
| 	OneSixInstance *inst = (OneSixInstance *)m_inst; | ||||
| 	auto profile = inst->getMinecraftProfile(); | ||||
| 	auto assets = profile->getMinecraftAssets(); | ||||
|  | ||||
| 	QString asset_fname = "assets/indexes/" + assets->id + ".json"; | ||||
| 	// FIXME: this looks like a job for a generic validator based on json schema? | ||||
| 	if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index)) | ||||
| 	{ | ||||
| 		auto metacache = ENV.metacache(); | ||||
| 		auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); | ||||
| 		metacache->evictEntry(entry); | ||||
| 		emitFailed(tr("Failed to read the assets index!")); | ||||
| 	} | ||||
|  | ||||
| 	auto job = index.getDownloadJob(); | ||||
| 	if(job) | ||||
| 	{ | ||||
| 		setStatus(tr("Getting the assets files from Mojang...")); | ||||
| 		jarlibDownloadJob = job; | ||||
| 		connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); | ||||
| 		connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::assetsFailed); | ||||
| 		connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); | ||||
| 		jarlibDownloadJob->start(); | ||||
| 		return; | ||||
| 	} | ||||
| 	assetsFinished(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::assetIndexFailed(QString reason) | ||||
| { | ||||
| 	qDebug() << m_inst->name() << ": Failed asset index download"; | ||||
| 	emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::assetsFinished() | ||||
| { | ||||
| 	emitSucceeded(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::assetsFailed(QString reason) | ||||
| { | ||||
| 	emitFailed(tr("Failed to download assets:\n%1").arg(reason)); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::jarlibStart() | ||||
| { | ||||
| 	setStatus(tr("Getting the library files from Mojang...")); | ||||
| 	qDebug() << m_inst->name() << ": downloading libraries"; | ||||
| 	OneSixInstance *inst = (OneSixInstance *)m_inst; | ||||
| 	inst->reloadProfile(); | ||||
| 	if(inst->flags() & BaseInstance::VersionBrokenFlag) | ||||
| 	{ | ||||
| 		emitFailed(tr("Failed to load the version description files - check the instance for errors.")); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Build a list of URLs that will need to be downloaded. | ||||
| 	std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); | ||||
| 	// minecraft.jar for this version | ||||
| 	{ | ||||
| 		QString version_id = profile->getMinecraftVersion(); | ||||
| 		QString localPath = version_id + "/" + version_id + ".jar"; | ||||
| 		QString urlstr = profile->getMainJarUrl(); | ||||
|  | ||||
| 		auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); | ||||
|  | ||||
| 		auto metacache = ENV.metacache(); | ||||
| 		auto entry = metacache->resolveEntry("versions", localPath); | ||||
| 		job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); | ||||
| 		jarlibDownloadJob.reset(job); | ||||
| 	} | ||||
|  | ||||
| 	auto libs = profile->getLibraries(); | ||||
|  | ||||
| 	auto metacache = ENV.metacache(); | ||||
| 	QList<LibraryPtr> brokenLocalLibs; | ||||
|  | ||||
| 	QStringList failedFiles; | ||||
| 	for (auto lib : libs) | ||||
| 	{ | ||||
| 		auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles); | ||||
| 		for(auto dl : dls) | ||||
| 		{ | ||||
| 			jarlibDownloadJob->addNetAction(dl); | ||||
| 		} | ||||
| 	} | ||||
| 	if (!brokenLocalLibs.empty()) | ||||
| 	{ | ||||
| 		jarlibDownloadJob.reset(); | ||||
|  | ||||
| 		QString failed_all = failedFiles.join("\n"); | ||||
| 		emitFailed(tr("Some libraries marked as 'local' are missing their jar " | ||||
| 					  "files:\n%1\n\nYou'll have to correct this problem manually. If this is " | ||||
| 					  "an externally tracked instance, make sure to run it at least once " | ||||
| 					  "outside of MultiMC.").arg(failed_all)); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); | ||||
| 	connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::jarlibFailed); | ||||
| 	connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), | ||||
| 			SIGNAL(progress(qint64, qint64))); | ||||
|  | ||||
| 	jarlibDownloadJob->start(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::jarlibFinished() | ||||
| { | ||||
| 	OneSixInstance *inst = (OneSixInstance *)m_inst; | ||||
| 	std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); | ||||
|  | ||||
| 	if (profile->hasTrait("legacyFML")) | ||||
| 	{ | ||||
| 		fmllibsStart(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		assetIndexStart(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::jarlibFailed(QString reason) | ||||
| { | ||||
| 	QStringList failed = jarlibDownloadJob->getFailedFiles(); | ||||
| 	QString failed_all = failed.join("\n"); | ||||
| 	emitFailed( | ||||
| 		tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::fmllibsStart() | ||||
| { | ||||
| 	// Get the mod list | ||||
| 	OneSixInstance *inst = (OneSixInstance *)m_inst; | ||||
| 	std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); | ||||
| 	bool forge_present = false; | ||||
|  | ||||
| 	QString version = inst->intendedVersionId(); | ||||
| 	auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; | ||||
| 	if (!fmlLibsMapping.contains(version)) | ||||
| 	{ | ||||
| 		assetIndexStart(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto &libList = fmlLibsMapping[version]; | ||||
|  | ||||
| 	// determine if we need some libs for FML or forge | ||||
| 	setStatus(tr("Checking for FML libraries...")); | ||||
| 	forge_present = (profile->versionPatch("net.minecraftforge") != nullptr); | ||||
| 	// we don't... | ||||
| 	if (!forge_present) | ||||
| 	{ | ||||
| 		assetIndexStart(); | ||||
| 		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()) | ||||
| 	{ | ||||
| 		assetIndexStart(); | ||||
| 		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(CacheDownload::make(QUrl(urlString), entry)); | ||||
| 	} | ||||
|  | ||||
| 	connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished())); | ||||
| 	connect(dljob, &NetJob::failed, this, &OneSixUpdate::fmllibsFailed); | ||||
| 	connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); | ||||
| 	legacyDownloadJob.reset(dljob); | ||||
| 	legacyDownloadJob->start(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::fmllibsFinished() | ||||
| { | ||||
| 	legacyDownloadJob.reset(); | ||||
| 	if (!fmlLibsToProcess.isEmpty()) | ||||
| 	{ | ||||
| 		setStatus(tr("Copying FML libraries into the instance...")); | ||||
| 		OneSixInstance *inst = (OneSixInstance *)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()); | ||||
| 	} | ||||
| 	assetIndexStart(); | ||||
| } | ||||
|  | ||||
| void OneSixUpdate::fmllibsFailed(QString reason) | ||||
| { | ||||
| 	emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries.\nReason:\n%1").arg(reason)); | ||||
| 	return; | ||||
| } | ||||
|  | ||||
							
								
								
									
										67
									
								
								api/logic/minecraft/onesix/OneSixUpdate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								api/logic/minecraft/onesix/OneSixUpdate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| /* Copyright 2013-2015 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QObject> | ||||
| #include <QList> | ||||
| #include <QUrl> | ||||
|  | ||||
| #include "net/NetJob.h" | ||||
| #include "tasks/Task.h" | ||||
| #include "minecraft/VersionFilterData.h" | ||||
| #include <quazip.h> | ||||
|  | ||||
| class MinecraftVersion; | ||||
| class OneSixInstance; | ||||
|  | ||||
| class OneSixUpdate : public Task | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0); | ||||
| 	virtual void executeTask(); | ||||
|  | ||||
| private | ||||
| slots: | ||||
| 	void versionUpdateFailed(QString reason); | ||||
|  | ||||
| 	void jarlibStart(); | ||||
| 	void jarlibFinished(); | ||||
| 	void jarlibFailed(QString reason); | ||||
|  | ||||
| 	void fmllibsStart(); | ||||
| 	void fmllibsFinished(); | ||||
| 	void fmllibsFailed(QString reason); | ||||
|  | ||||
| 	void assetIndexStart(); | ||||
| 	void assetIndexFinished(); | ||||
| 	void assetIndexFailed(QString reason); | ||||
|  | ||||
| 	void assetsFinished(); | ||||
| 	void assetsFailed(QString reason); | ||||
|  | ||||
| private: | ||||
| 	NetJobPtr jarlibDownloadJob; | ||||
| 	NetJobPtr legacyDownloadJob; | ||||
|  | ||||
| 	/// target version, determined during this task | ||||
| 	std::shared_ptr<MinecraftVersion> targetVersion; | ||||
| 	/// the task that is spawned for version updates | ||||
| 	std::shared_ptr<Task> versionUpdateTask; | ||||
|  | ||||
| 	OneSixInstance *m_inst = nullptr; | ||||
| 	QList<FMLlib> fmlLibsToProcess; | ||||
| }; | ||||
							
								
								
									
										225
									
								
								api/logic/minecraft/onesix/OneSixVersionFormat.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								api/logic/minecraft/onesix/OneSixVersionFormat.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | ||||
| #include "OneSixVersionFormat.h" | ||||
| #include <Json.h> | ||||
| #include "minecraft/ParseUtils.h" | ||||
| #include <minecraft/MinecraftVersion.h> | ||||
| #include <minecraft/VersionBuildError.h> | ||||
| #include <minecraft/MojangVersionFormat.h> | ||||
|  | ||||
| using namespace Json; | ||||
|  | ||||
| static void readString(const QJsonObject &root, const QString &key, QString &variable) | ||||
| { | ||||
| 	if (root.contains(key)) | ||||
| 	{ | ||||
| 		variable = requireString(root.value(key)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| LibraryPtr OneSixVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) | ||||
| { | ||||
| 	LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename); | ||||
| 	readString(libObj, "MMC-hint", out->m_hint); | ||||
| 	readString(libObj, "MMC-absulute_url", out->m_absoluteURL); | ||||
| 	readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QJsonObject OneSixVersionFormat::libraryToJson(Library *library) | ||||
| { | ||||
| 	QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); | ||||
| 	if (library->m_absoluteURL.size()) | ||||
| 		libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); | ||||
| 	if (library->m_hint.size()) | ||||
| 		libRoot.insert("MMC-hint", library->m_hint); | ||||
| 	return libRoot; | ||||
| } | ||||
|  | ||||
| VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder) | ||||
| { | ||||
| 	VersionFilePtr out(new VersionFile()); | ||||
| 	if (doc.isEmpty() || doc.isNull()) | ||||
| 	{ | ||||
| 		throw JSONValidationError(filename + " is empty or null"); | ||||
| 	} | ||||
| 	if (!doc.isObject()) | ||||
| 	{ | ||||
| 		throw JSONValidationError(filename + " is not an object"); | ||||
| 	} | ||||
|  | ||||
| 	QJsonObject root = doc.object(); | ||||
|  | ||||
| 	if (requireOrder) | ||||
| 	{ | ||||
| 		if (root.contains("order")) | ||||
| 		{ | ||||
| 			out->order = requireInteger(root.value("order")); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// FIXME: evaluate if we don't want to throw exceptions here instead | ||||
| 			qCritical() << filename << "doesn't contain an order field"; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out->name = root.value("name").toString(); | ||||
| 	out->fileId = root.value("fileId").toString(); | ||||
| 	out->version = root.value("version").toString(); | ||||
| 	out->dependsOnMinecraftVersion = root.value("mcVersion").toString(); | ||||
| 	out->filename = filename; | ||||
|  | ||||
| 	MojangVersionFormat::readVersionProperties(root, out.get()); | ||||
|  | ||||
| 	// added for legacy Minecraft window embedding, TODO: remove | ||||
| 	readString(root, "appletClass", out->appletClass); | ||||
|  | ||||
| 	if (root.contains("+tweakers")) | ||||
| 	{ | ||||
| 		for (auto tweakerVal : requireArray(root.value("+tweakers"))) | ||||
| 		{ | ||||
| 			out->addTweakers.append(requireString(tweakerVal)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (root.contains("+traits")) | ||||
| 	{ | ||||
| 		for (auto tweakerVal : requireArray(root.value("+traits"))) | ||||
| 		{ | ||||
| 			out->traits.insert(requireString(tweakerVal)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (root.contains("+jarMods")) | ||||
| 	{ | ||||
| 		for (auto libVal : requireArray(root.value("+jarMods"))) | ||||
| 		{ | ||||
| 			QJsonObject libObj = requireObject(libVal); | ||||
| 			// parse the jarmod | ||||
| 			auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename, out->name); | ||||
| 			if(lib->originalName.isEmpty()) | ||||
| 			{ | ||||
| 				auto fixed = out->name; | ||||
| 				fixed.remove(" (jar mod)"); | ||||
| 				lib->originalName = out->name; | ||||
| 			} | ||||
| 			// and add to jar mods | ||||
| 			out->jarMods.append(lib); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	auto readLibs = [&](const char * which) | ||||
| 	{ | ||||
| 		for (auto libVal : requireArray(root.value(which))) | ||||
| 		{ | ||||
| 			QJsonObject libObj = requireObject(libVal); | ||||
| 			// parse the library | ||||
| 			auto lib = libraryFromJson(libObj, filename); | ||||
| 			out->libraries.append(lib); | ||||
| 		} | ||||
| 	}; | ||||
| 	bool hasPlusLibs = root.contains("+libraries"); | ||||
| 	bool hasLibs = root.contains("libraries"); | ||||
| 	if (hasPlusLibs && hasLibs) | ||||
| 	{ | ||||
| 		out->addProblem(PROBLEM_WARNING, QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); | ||||
| 		readLibs("libraries"); | ||||
| 		readLibs("+libraries"); | ||||
| 	} | ||||
| 	else if (hasLibs) | ||||
| 	{ | ||||
| 		readLibs("libraries"); | ||||
| 	} | ||||
| 	else if(hasPlusLibs) | ||||
| 	{ | ||||
| 		readLibs("+libraries"); | ||||
| 	} | ||||
|  | ||||
| 	/* removed features that shouldn't be used */ | ||||
| 	if (root.contains("tweakers")) | ||||
| 	{ | ||||
| 		out->addProblem(PROBLEM_ERROR, QObject::tr("Version file contains unsupported element 'tweakers'")); | ||||
| 	} | ||||
| 	if (root.contains("-libraries")) | ||||
| 	{ | ||||
| 		out->addProblem(PROBLEM_ERROR, QObject::tr("Version file contains unsupported element '-libraries'")); | ||||
| 	} | ||||
| 	if (root.contains("-tweakers")) | ||||
| 	{ | ||||
| 		out->addProblem(PROBLEM_ERROR, QObject::tr("Version file contains unsupported element '-tweakers'")); | ||||
| 	} | ||||
| 	if (root.contains("-minecraftArguments")) | ||||
| 	{ | ||||
| 		out->addProblem(PROBLEM_ERROR, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); | ||||
| 	} | ||||
| 	if (root.contains("+minecraftArguments")) | ||||
| 	{ | ||||
| 		out->addProblem(PROBLEM_ERROR, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch, bool saveOrder) | ||||
| { | ||||
| 	QJsonObject root; | ||||
| 	if (saveOrder) | ||||
| 	{ | ||||
| 		root.insert("order", patch->order); | ||||
| 	} | ||||
| 	writeString(root, "name", patch->name); | ||||
| 	writeString(root, "fileId", patch->fileId); | ||||
| 	writeString(root, "version", patch->version); | ||||
| 	writeString(root, "mcVersion", patch->dependsOnMinecraftVersion); | ||||
|  | ||||
| 	MojangVersionFormat::writeVersionProperties(patch.get(), root); | ||||
|  | ||||
| 	writeString(root, "appletClass", patch->appletClass); | ||||
| 	writeStringList(root, "+tweakers", patch->addTweakers); | ||||
| 	writeStringList(root, "+traits", patch->traits.toList()); | ||||
| 	if (!patch->libraries.isEmpty()) | ||||
| 	{ | ||||
| 		QJsonArray array; | ||||
| 		for (auto value: patch->libraries) | ||||
| 		{ | ||||
| 			array.append(OneSixVersionFormat::libraryToJson(value.get())); | ||||
| 		} | ||||
| 		root.insert("+libraries", array); | ||||
| 	} | ||||
| 	if (!patch->jarMods.isEmpty()) | ||||
| 	{ | ||||
| 		QJsonArray array; | ||||
| 		for (auto value: patch->jarMods) | ||||
| 		{ | ||||
| 			array.append(OneSixVersionFormat::jarModtoJson(value.get())); | ||||
| 		} | ||||
| 		root.insert("+jarMods", array); | ||||
| 	} | ||||
| 	// write the contents to a json document. | ||||
| 	{ | ||||
| 		QJsonDocument out; | ||||
| 		out.setObject(root); | ||||
| 		return out; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| JarmodPtr OneSixVersionFormat::jarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName) | ||||
| { | ||||
| 	JarmodPtr out(new Jarmod()); | ||||
| 	if (!libObj.contains("name")) | ||||
| 	{ | ||||
| 		throw JSONValidationError(filename + | ||||
| 								  "contains a jarmod that doesn't have a 'name' field"); | ||||
| 	} | ||||
| 	out->name = libObj.value("name").toString(); | ||||
| 	out->originalName = libObj.value("originalName").toString(); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QJsonObject OneSixVersionFormat::jarModtoJson(Jarmod *jarmod) | ||||
| { | ||||
| 	QJsonObject out; | ||||
| 	writeString(out, "name", jarmod->name); | ||||
| 	if(!jarmod->originalName.isEmpty()) | ||||
| 	{ | ||||
| 		writeString(out, "originalName", jarmod->originalName); | ||||
| 	} | ||||
| 	return out; | ||||
| } | ||||
							
								
								
									
										22
									
								
								api/logic/minecraft/onesix/OneSixVersionFormat.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								api/logic/minecraft/onesix/OneSixVersionFormat.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <minecraft/VersionFile.h> | ||||
| #include <minecraft/MinecraftProfile.h> | ||||
| #include <minecraft/Library.h> | ||||
| #include <QJsonDocument> | ||||
|  | ||||
| class OneSixVersionFormat | ||||
| { | ||||
| public: | ||||
| 	// version files / profile patches | ||||
| 	static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); | ||||
| 	static QJsonDocument versionFileToJson(const VersionFilePtr &patch, bool saveOrder); | ||||
|  | ||||
| 	// libraries | ||||
| 	static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); | ||||
| 	static QJsonObject libraryToJson(Library *library); | ||||
|  | ||||
| 	// jar mods | ||||
| 	static JarmodPtr jarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName); | ||||
| 	static QJsonObject jarModtoJson(Jarmod * jarmod); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user