From a1a06cc89f7f1d904a1b71d330d6129b866ff29b Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Wed, 22 Jan 2014 07:33:32 +0100 Subject: Derpstances. Everything renamed. Launching does not yet work. --- logic/BaseInstance.h | 2 +- logic/DerpFTBInstance.cpp | 123 +++++++++++ logic/DerpFTBInstance.h | 22 ++ logic/DerpInstance.cpp | 373 +++++++++++++++++++++++++++++++ logic/DerpInstance.h | 73 ++++++ logic/DerpInstance_p.h | 27 +++ logic/DerpLibrary.cpp | 268 ++++++++++++++++++++++ logic/DerpLibrary.h | 132 +++++++++++ logic/DerpRule.cpp | 89 ++++++++ logic/DerpRule.h | 98 +++++++++ logic/DerpUpdate.cpp | 377 +++++++++++++++++++++++++++++++ logic/DerpUpdate.h | 63 ++++++ logic/DerpVersion.cpp | 164 ++++++++++++++ logic/DerpVersion.h | 110 +++++++++ logic/DerpVersionBuilder.cpp | 279 +++++++++++++++++++++++ logic/DerpVersionBuilder.h | 43 ++++ logic/ForgeInstaller.cpp | 22 +- logic/ForgeInstaller.h | 6 +- logic/InstanceFactory.cpp | 34 +-- logic/LegacyInstance.h | 2 +- logic/LiteLoaderInstaller.cpp | 25 +-- logic/LiteLoaderInstaller.h | 8 +- logic/MinecraftVersion.h | 6 +- logic/NostalgiaInstance.cpp | 2 +- logic/NostalgiaInstance.h | 4 +- logic/OneSixFTBInstance.cpp | 125 ----------- logic/OneSixFTBInstance.h | 22 -- logic/OneSixInstance.cpp | 416 ----------------------------------- logic/OneSixInstance.h | 78 ------- logic/OneSixInstance_p.h | 30 --- logic/OneSixLibrary.cpp | 268 ---------------------- logic/OneSixLibrary.h | 132 ----------- logic/OneSixRule.cpp | 89 -------- logic/OneSixRule.h | 98 --------- logic/OneSixUpdate.cpp | 377 ------------------------------- logic/OneSixUpdate.h | 63 ------ logic/OneSixVersion.cpp | 50 ++--- logic/OneSixVersion.h | 14 +- logic/lists/MinecraftVersionList.cpp | 6 +- 39 files changed, 2334 insertions(+), 1786 deletions(-) create mode 100644 logic/DerpFTBInstance.cpp create mode 100644 logic/DerpFTBInstance.h create mode 100644 logic/DerpInstance.cpp create mode 100644 logic/DerpInstance.h create mode 100644 logic/DerpInstance_p.h create mode 100644 logic/DerpLibrary.cpp create mode 100644 logic/DerpLibrary.h create mode 100644 logic/DerpRule.cpp create mode 100644 logic/DerpRule.h create mode 100644 logic/DerpUpdate.cpp create mode 100644 logic/DerpUpdate.h create mode 100644 logic/DerpVersion.cpp create mode 100644 logic/DerpVersion.h create mode 100644 logic/DerpVersionBuilder.cpp create mode 100644 logic/DerpVersionBuilder.h delete mode 100644 logic/OneSixFTBInstance.cpp delete mode 100644 logic/OneSixFTBInstance.h delete mode 100644 logic/OneSixInstance.cpp delete mode 100644 logic/OneSixInstance.h delete mode 100644 logic/OneSixInstance_p.h delete mode 100644 logic/OneSixLibrary.cpp delete mode 100644 logic/OneSixLibrary.h delete mode 100644 logic/OneSixRule.cpp delete mode 100644 logic/OneSixRule.h delete mode 100644 logic/OneSixUpdate.cpp delete mode 100644 logic/OneSixUpdate.h (limited to 'logic') diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index a861e9b2..79640c84 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -27,7 +27,7 @@ class QDialog; class Task; class MinecraftProcess; -class OneSixUpdate; +class DerpUpdate; class InstanceList; class BaseInstancePrivate; diff --git a/logic/DerpFTBInstance.cpp b/logic/DerpFTBInstance.cpp new file mode 100644 index 00000000..f9aeeca0 --- /dev/null +++ b/logic/DerpFTBInstance.cpp @@ -0,0 +1,123 @@ +#include "DerpFTBInstance.h" + +#include "DerpVersion.h" +#include "DerpLibrary.h" +#include "tasks/SequentialTask.h" +#include "ForgeInstaller.h" +#include "lists/ForgeVersionList.h" +#include "MultiMC.h" + +class DerpFTBInstanceForge : public Task +{ + Q_OBJECT +public: + explicit DerpFTBInstanceForge(const QString &version, DerpFTBInstance *inst, QObject *parent = 0) : + Task(parent), instance(inst), version("Forge " + version) + { + } + + void executeTask() + { + for (int i = 0; i < MMC->forgelist()->count(); ++i) + { + if (MMC->forgelist()->at(i)->name() == version) + { + forgeVersion = std::dynamic_pointer_cast(MMC->forgelist()->at(i)); + break; + } + } + if (!forgeVersion) + { + emitFailed(QString("Couldn't find forge version ") + version ); + return; + } + entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); + if (entry->stale) + { + setStatus(tr("Downloading Forge...")); + fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); + connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);}); + connect(fjob, &NetJob::succeeded, this, &DerpFTBInstanceForge::installForge); + connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); }); + fjob->start(); + } + else + { + installForge(); + } + } + +private +slots: + void installForge() + { + setStatus(tr("Installing Forge...")); + QString forgePath = entry->getFullPath(); + ForgeInstaller forge(forgePath, forgeVersion->universal_url); + if (!instance->reloadFullVersion()) + { + emitFailed(tr("Couldn't load the version config")); + return; + } + auto version = instance->getFullVersion(); + if (!forge.apply(version)) + { + emitFailed(tr("Couldn't install Forge")); + return; + } + emitSucceeded(); + } + +private: + DerpFTBInstance *instance; + QString version; + ForgeVersionPtr forgeVersion; + MetaEntryPtr entry; + NetJob *fjob; +}; + +DerpFTBInstance::DerpFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) : + DerpInstance(rootDir, settings, parent) +{ + QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json")); + if (f.open(QFile::ReadOnly)) + { + QString data = QString::fromUtf8(f.readAll()); + QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data); + m_forge.reset(new DerpLibrary(match.captured())); + m_forge->finalize(); + } +} + +QString DerpFTBInstance::id() const +{ + return "FTB/" + BaseInstance::id(); +} + +QString DerpFTBInstance::getStatusbarDescription() +{ + return "Derp FTB: " + intendedVersionId(); +} +bool DerpFTBInstance::menuActionEnabled(QString action_name) const +{ + return false; +} + +std::shared_ptr DerpFTBInstance::doUpdate(bool only_prepare) +{ + std::shared_ptr task; + task.reset(new SequentialTask(this)); + if (!MMC->forgelist()->isLoaded()) + { + task->addTask(std::shared_ptr(MMC->forgelist()->getLoadTask())); + } + task->addTask(DerpInstance::doUpdate(only_prepare)); + task->addTask(std::shared_ptr(new DerpFTBInstanceForge(m_forge->version(), this, this))); + //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again. + //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly. + task->addTask(DerpInstance::doUpdate(only_prepare)); + return task; +} + +#include "DerpFTBInstance.moc" diff --git a/logic/DerpFTBInstance.h b/logic/DerpFTBInstance.h new file mode 100644 index 00000000..c16998bf --- /dev/null +++ b/logic/DerpFTBInstance.h @@ -0,0 +1,22 @@ +#pragma once + +#include "DerpInstance.h" + +class DerpLibrary; + +class DerpFTBInstance : public DerpInstance +{ + Q_OBJECT +public: + explicit DerpFTBInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + virtual QString getStatusbarDescription(); + virtual bool menuActionEnabled(QString action_name) const; + + virtual std::shared_ptr doUpdate(bool only_prepare) override; + + virtual QString id() const; + +private: + std::shared_ptr m_forge; +}; diff --git a/logic/DerpInstance.cpp b/logic/DerpInstance.cpp new file mode 100644 index 00000000..31ed7c95 --- /dev/null +++ b/logic/DerpInstance.cpp @@ -0,0 +1,373 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DerpInstance.h" + +#include + +#include "DerpInstance_p.h" +#include "DerpUpdate.h" +#include "DerpVersion.h" +#include "pathutils.h" +#include "logger/QsLog.h" +#include "assets/AssetsUtils.h" +#include "MultiMC.h" +#include "icons/IconList.h" +#include "MinecraftProcess.h" +#include "gui/dialogs/DerpModEditDialog.h" + +DerpInstance::DerpInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) + : BaseInstance(new DerpInstancePrivate(), rootDir, settings, parent) +{ + I_D(DerpInstance); + d->m_settings->registerSetting("IntendedVersion", ""); + d->m_settings->registerSetting("ShouldUpdate", false); + d->version.reset(new DerpVersion(this, this)); + reloadFullVersion(); +} + + +std::shared_ptr DerpInstance::doUpdate(bool only_prepare) +{ + return std::shared_ptr(new DerpUpdate(this, only_prepare)); +} + +QString replaceTokensIn(QString text, QMap 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; +} + +QDir DerpInstance::reconstructAssets(std::shared_ptr version) +{ + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = PathCombine(indexDir.path(), version->assets + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(PathCombine(virtualDir.path(), version->assets)); + + if (!indexFile.exists()) + { + QLOG_ERROR() << "No assets index file" << indexPath << "; can't reconstruct assets"; + return virtualRoot; + } + + QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() + << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index); + + if (loadAssetsIndex && index.isVirtual) + { + QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path(); + + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = PathCombine(virtualRoot.path(), map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = + PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); + QFile original(original_path); + if(!original.exists()) + continue; + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + // QLOG_DEBUG() << target_dir; + if (!target_dir.exists()) + QDir("").mkpath(target_dir.path()); + + bool couldCopy = original.copy(target_path); + QLOG_DEBUG() << " Copying" << original_path << "to" << target_path + << QString::number(couldCopy); // << original.errorString(); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + } + + return virtualRoot; +} + +QStringList DerpInstance::processMinecraftArgs(MojangAccountPtr account) +{ + I_D(DerpInstance); + auto version = d->version; + QString args_pattern = version->minecraftArguments; + + QMap token_mapping; + // yggdrasil! + token_mapping["auth_username"] = account->username(); + token_mapping["auth_session"] = account->sessionId(); + token_mapping["auth_access_token"] = account->accessToken(); + token_mapping["auth_player_name"] = account->currentProfile()->name; + token_mapping["auth_uuid"] = account->currentProfile()->id; + + // this is for offline?: + /* + map["auth_player_name"] = "Player"; + map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; + */ + + // these do nothing and are stupid. + token_mapping["profile_name"] = name(); + token_mapping["version_name"] = version->id; + + QString absRootDir = QDir(minecraftRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + token_mapping["game_assets"] = reconstructAssets(d->version).absolutePath(); + + auto user = account->user(); + QJsonObject userAttrs; + for (auto key : user.properties.keys()) + { + auto array = QJsonArray::fromStringList(user.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + + token_mapping["user_properties"] = value.toJson(QJsonDocument::Compact); + token_mapping["user_type"] = account->currentProfile()->legacy ? "legacy" : "mojang"; + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = version->assets; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) + { + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; +} + +MinecraftProcess *DerpInstance::prepareForLaunch(MojangAccountPtr account) +{ + I_D(DerpInstance); + + QIcon icon = MMC->icons()->getIcon(iconKey()); + auto pixmap = icon.pixmap(128, 128); + pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); + + auto version = d->version; + if (!version) + return nullptr; + QString launchScript; + { + auto libs = version->getActiveNormalLibs(); + for (auto lib : libs) + { + QFileInfo fi(QString("libraries/") + lib->storagePath()); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; + } + QString targetstr = "versions/" + version->id + "/" + version->id + ".jar"; + QFileInfo fi(targetstr); + launchScript += "cp " + fi.absoluteFilePath() + "\n"; + } + launchScript += "mainClass " + version->mainClass + "\n"; + + for (auto param : processMinecraftArgs(account)) + { + launchScript += "param " + param + "\n"; + } + + // Set the width and height for 1.6 instances + bool maximize = settings().get("LaunchMaximized").toBool(); + if (maximize) + { + // this is probably a BAD idea + // launchScript += "param --fullscreen\n"; + } + else + { + launchScript += + "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; + launchScript += + "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; + } + QDir natives_dir(PathCombine(instanceRoot(), "natives/")); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "natives " + natives_dir.absolutePath() + "\n"; + launchScript += "launch onesix"; + + qDebug() << launchScript; + + // create the process and set its parameters + MinecraftProcess *proc = new MinecraftProcess(this); + proc->setWorkdir(minecraftRoot()); + proc->setLaunchScript(launchScript); + // proc->setNativeFolder(natives_dir.absolutePath()); + return proc; +} + +void DerpInstance::cleanupAfterRun() +{ + QString target_dir = PathCombine(instanceRoot(), "natives/"); + QDir dir(target_dir); + dir.removeRecursively(); +} + +std::shared_ptr DerpInstance::loaderModList() +{ + I_D(DerpInstance); + if (!d->loader_mod_list) + { + d->loader_mod_list.reset(new ModList(loaderModsDir())); + } + d->loader_mod_list->update(); + return d->loader_mod_list; +} + +std::shared_ptr DerpInstance::resourcePackList() +{ + I_D(DerpInstance); + if (!d->resource_pack_list) + { + d->resource_pack_list.reset(new ModList(resourcePacksDir())); + } + d->resource_pack_list->update(); + return d->resource_pack_list; +} + +QDialog *DerpInstance::createModEditDialog(QWidget *parent) +{ + return new DerpModEditDialog(this, parent); +} + +bool DerpInstance::setIntendedVersionId(QString version) +{ + settings().set("IntendedVersion", version); + setShouldUpdate(true); + auto pathOrig = PathCombine(instanceRoot(), "version.json"); + QFile::remove(pathOrig); + reloadFullVersion(); + return true; +} + +QString DerpInstance::intendedVersionId() const +{ + return settings().get("IntendedVersion").toString(); +} + +void DerpInstance::setShouldUpdate(bool val) +{ + settings().set("ShouldUpdate", val); +} + +bool DerpInstance::shouldUpdate() const +{ + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) + { + return intendedVersionId() != currentVersionId(); + } + return true; +} + +bool DerpInstance::versionIsCustom() +{ + QDir patches(PathCombine(instanceRoot(), "patches/")); + return QFile::exists(PathCombine(instanceRoot(), "custom.json")) + || (patches.exists() && patches.count() >= 0); +} + +QString DerpInstance::currentVersionId() const +{ + return intendedVersionId(); +} + +bool DerpInstance::reloadFullVersion(QWidget *widgetParent) +{ + I_D(DerpInstance); + + bool ret = d->version->reload(widgetParent); + emit versionReloaded(); + return ret; +} + +std::shared_ptr DerpInstance::getFullVersion() +{ + I_D(DerpInstance); + return d->version; +} + +QString DerpInstance::defaultBaseJar() const +{ + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; +} + +QString DerpInstance::defaultCustomBaseJar() const +{ + return PathCombine(instanceRoot(), "custom.jar"); +} + +bool DerpInstance::menuActionEnabled(QString action_name) const +{ + if (action_name == "actionChangeInstLWJGLVersion") + return false; + return true; +} + +QString DerpInstance::getStatusbarDescription() +{ + QString descr = "Derp : " + intendedVersionId(); + if (versionIsCustom()) + { + descr + " (custom)"; + } + return descr; +} + +QString DerpInstance::loaderModsDir() const +{ + return PathCombine(minecraftRoot(), "mods"); +} + +QString DerpInstance::resourcePacksDir() const +{ + return PathCombine(minecraftRoot(), "resourcepacks"); +} + +QString DerpInstance::instanceConfigFolder() const +{ + return PathCombine(minecraftRoot(), "config"); +} diff --git a/logic/DerpInstance.h b/logic/DerpInstance.h new file mode 100644 index 00000000..37d3df52 --- /dev/null +++ b/logic/DerpInstance.h @@ -0,0 +1,73 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance.h" + +#include "DerpVersion.h" +#include "ModList.h" + +class DerpInstance : public BaseInstance +{ + Q_OBJECT +public: + explicit DerpInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + + ////// Mod Lists ////// + std::shared_ptr loaderModList(); + std::shared_ptr resourcePackList(); + + ////// Directories ////// + QString resourcePacksDir() const; + QString loaderModsDir() const; + virtual QString instanceConfigFolder() const override; + + virtual std::shared_ptr doUpdate(bool only_prepare) override; + virtual MinecraftProcess *prepareForLaunch(MojangAccountPtr account) 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; + + virtual QDialog *createModEditDialog(QWidget *parent) override; + + /// reload the full version json files. return true on success! + bool reloadFullVersion(QWidget *widgetParent = 0); + /// get the current full version info + std::shared_ptr getFullVersion(); + /// is the current version original, or custom? + virtual bool versionIsCustom() override; + + virtual QString defaultBaseJar() const override; + virtual QString defaultCustomBaseJar() const override; + + virtual bool menuActionEnabled(QString action_name) const override; + virtual QString getStatusbarDescription() override; + +signals: + void versionReloaded(); + +private: + QStringList processMinecraftArgs(MojangAccountPtr account); + QDir reconstructAssets(std::shared_ptr version); +}; diff --git a/logic/DerpInstance_p.h b/logic/DerpInstance_p.h new file mode 100644 index 00000000..41f7b62d --- /dev/null +++ b/logic/DerpInstance_p.h @@ -0,0 +1,27 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "BaseInstance_p.h" +#include "DerpVersion.h" +#include "ModList.h" + +struct DerpInstancePrivate : public BaseInstancePrivate +{ + std::shared_ptr version; + std::shared_ptr loader_mod_list; + std::shared_ptr resource_pack_list; +}; diff --git a/logic/DerpLibrary.cpp b/logic/DerpLibrary.cpp new file mode 100644 index 00000000..ba4d516b --- /dev/null +++ b/logic/DerpLibrary.cpp @@ -0,0 +1,268 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "DerpLibrary.h" +#include "DerpRule.h" +#include "OpSys.h" +#include "logic/net/URLConstants.h" +#include +#include +#include "logger/QsLog.h" + +void DerpLibrary::finalize() +{ + QStringList parts = m_name.split(':'); + QString relative = parts[0]; + relative.replace('.', '/'); + relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; + + if (!m_is_native) + relative += ".jar"; + else + { + if (m_native_suffixes.contains(currentSystem)) + { + relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + } + else + { + // really, bad. + relative += ".jar"; + } + } + + m_decentname = parts[1]; + m_decentversion = parts[2]; + m_storage_path = relative; + m_download_url = m_base_url + relative; + + if (m_rules.empty()) + { + m_is_active = true; + } + else + { + RuleAction result = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + result = temp; + } + m_is_active = (result == Allow); + } + if (m_is_native) + { + m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); + m_decenttype = "Native"; + } + else + { + m_decenttype = "Java"; + } +} + +void DerpLibrary::setName(const QString &name) +{ + m_name = name; +} +void DerpLibrary::setBaseUrl(const QString &base_url) +{ + m_base_url = base_url; +} +void DerpLibrary::setIsNative() +{ + m_is_native = true; +} +void DerpLibrary::addNative(OpSys os, const QString &suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void DerpLibrary::setRules(QList> rules) +{ + m_rules = rules; +} +bool DerpLibrary::isActive() const +{ + return m_is_active; +} +bool DerpLibrary::isNative() const +{ + return m_is_native; +} +QString DerpLibrary::downloadUrl() const +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString DerpLibrary::storagePath() const +{ + return m_storage_path; +} + +void DerpLibrary::setAbsoluteUrl(const QString &absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString DerpLibrary::absoluteUrl() const +{ + return m_absolute_url; +} + +void DerpLibrary::setHint(const QString &hint) +{ + m_hint = hint; +} + +QString DerpLibrary::hint() const +{ + return m_hint; +} + +bool DerpLibrary::filesExist() +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QFileInfo info32(PathCombine("libraries", cooked_storage)); + if (!info32.exists()) + { + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + QFileInfo info64(PathCombine("libraries", cooked_storage)); + if (!info64.exists()) + { + return false; + } + } + else + { + QFileInfo info(PathCombine("libraries", storage)); + if (!info.exists()) + { + return false; + } + } + return true; +} + +bool DerpLibrary::extractTo(QString target_dir) +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QString origin = PathCombine("libraries", cooked_storage); + QString target_dir_cooked = PathCombine(target_dir, "32"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + origin = PathCombine("libraries", cooked_storage); + target_dir_cooked = PathCombine(target_dir, "64"); + if(!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + } + else + { + if(!ensureFolderPathExists(target_dir)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir; + return false; + } + QString path = PathCombine("libraries", storage); + if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + path; + return false; + } + } + return true; +} + +QJsonObject DerpLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE) + libRoot.insert("url", m_base_url); + if (isNative() && m_native_suffixes.size()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + } + if (isNative() && extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} diff --git a/logic/DerpLibrary.h b/logic/DerpLibrary.h new file mode 100644 index 00000000..d1cee843 --- /dev/null +++ b/logic/DerpLibrary.h @@ -0,0 +1,132 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "logic/net/URLConstants.h" +#include "OpSys.h" + +class Rule; + +class DerpLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + QList> m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// download hint - how to actually get the library + QString m_hint; + + // derived values used for real things + /// a decent name fit for display + QString m_decentname; + /// a decent version fit for display + QString m_decentversion; + /// a decent type fit for display + QString m_decenttype; + /// where to store the lib locally + QString m_storage_path; + /// where to download the lib from + QString m_download_url; + /// is this lib actually active on the current OS? + bool m_is_active = false; + /// is the library a native? + bool m_is_native = false; + /// native suffixes per OS + QMap m_native_suffixes; + +public: + QStringList extract_excludes; + +public: + /// Constructor + DerpLibrary(const QString &name) + { + m_name = name; + } + + /// Returns the raw name field + QString rawName() const + { + return m_name; + } + + QJsonObject toJson(); + + /** + * finalize the library, processing the input values into derived values and state + * + * This SHALL be called after all the values are parsed or after any further change. + */ + void finalize(); + + /// Set the library composite name + void setName(const QString &name); + /// get a decent-looking name + QString name() const + { + return m_decentname; + } + /// get a decent-looking version + QString version() const + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() const + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(const QString &base_url); + + /// Call this to mark the library as 'native' (it's a zip archive with DLLs) + void setIsNative(); + /// Attach a name suffix to the specified OS native + void addNative(OpSys os, const QString &suffix); + /// Set the load rules + void setRules(QList> rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + /// Returns true if the library is native + bool isNative() const; + /// Get the URL to download the library from + QString downloadUrl() const; + /// Get the relative path where the library should be saved + QString storagePath() const; + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(const QString &absolute_url); + QString absoluteUrl() const; + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(const QString &hint); + QString hint() const; + + bool extractTo(QString target_dir); + bool filesExist(); +}; diff --git a/logic/DerpRule.cpp b/logic/DerpRule.cpp new file mode 100644 index 00000000..d4cf1ba3 --- /dev/null +++ b/logic/DerpRule.cpp @@ -0,0 +1,89 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "DerpRule.h" + +QList> rulesFromJsonV4(QJsonObject &objectWithRules) +{ + QList> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; + + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } + + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } + return rules; +} + +QJsonObject ImplicitRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; +} + +QJsonObject OsRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("version", m_version_regexp); + } + ruleObj.insert("os", osObj); + return ruleObj; +} + +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +} diff --git a/logic/DerpRule.h b/logic/DerpRule.h new file mode 100644 index 00000000..7895ea98 --- /dev/null +++ b/logic/DerpRule.h @@ -0,0 +1,98 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "logic/DerpLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList> rulesFromJsonV4(QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(DerpLibrary *parent) = 0; + +public: + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(DerpLibrary *parent) + { + if (applies(parent)) + return m_result; + else + return Defer; + } + ; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; + +protected: + virtual bool applies(DerpLibrary *) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result, OpSys system, + QString version_regexp) + { + return std::shared_ptr(new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies(DerpLibrary *) + { + return true; + } + ImplicitRule(RuleAction result) : Rule(result) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result) + { + return std::shared_ptr(new ImplicitRule(result)); + } +}; diff --git a/logic/DerpUpdate.cpp b/logic/DerpUpdate.cpp new file mode 100644 index 00000000..e1600d28 --- /dev/null +++ b/logic/DerpUpdate.cpp @@ -0,0 +1,377 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiMC.h" +#include "DerpUpdate.h" + +#include + +#include +#include +#include +#include + +#include "BaseInstance.h" +#include "lists/MinecraftVersionList.h" +#include "DerpVersion.h" +#include "DerpLibrary.h" +#include "DerpInstance.h" +#include "net/ForgeMirrors.h" +#include "net/URLConstants.h" +#include "assets/AssetsUtils.h" + +#include "pathutils.h" +#include + +DerpUpdate::DerpUpdate(BaseInstance *inst, bool only_prepare, QObject *parent) + : Task(parent), m_inst(inst), m_only_prepare(only_prepare) +{ +} + +void DerpUpdate::executeTask() +{ + QString intendedVersion = m_inst->intendedVersionId(); + + // Make directories + QDir mcDir(m_inst->minecraftRoot()); + if (!mcDir.exists() && !mcDir.mkpath(".")) + { + emitFailed("Failed to create bin folder."); + return; + } + + if (m_only_prepare) + { + prepareForLaunch(); + return; + } + + if (m_inst->shouldUpdate()) + { + // Get a pointer to the version object that corresponds to the instance's version. + targetVersion = std::dynamic_pointer_cast( + MMC->minecraftlist()->findVersion(intendedVersion)); + if (targetVersion == nullptr) + { + // don't do anything if it was invalid + emitFailed("The specified Minecraft version is invalid. Choose a different one."); + return; + } + versionFileStart(); + } + else + { + jarlibStart(); + } +} + +void DerpUpdate::versionFileStart() +{ + QLOG_INFO() << m_inst->name() << ": getting version file."; + setStatus(tr("Getting the version files from Mojang...")); + + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; + auto job = new NetJob("Version index"); + job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + specificVersionDownloadJob->start(); +} + +void DerpUpdate::versionFileFinished() +{ + NetActionPtr DlJob = specificVersionDownloadJob->first(); + DerpInstance *inst = (DerpInstance *)m_inst; + + QString version_id = targetVersion->descriptor(); + QString inst_dir = m_inst->instanceRoot(); + // save the version file in $instanceId/version.json + { + QString version1 = PathCombine(inst_dir, "/version.json"); + ensureFilePathExists(version1); + // FIXME: detect errors here, download to a temp file, swap + QSaveFile vfile1(version1); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed("Can't open " + version1 + " for writing."); + return; + } + auto data = std::dynamic_pointer_cast(DlJob)->m_data; + qint64 actual = 0; + if ((actual = vfile1.write(data)) != data.size()) + { + emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + + data.size() + '.'); + return; + } + if (!vfile1.commit()) + { + emitFailed("Can't commit changes to " + version1); + return; + } + } + + // the version is downloaded safely. update is 'done' at this point + m_inst->setShouldUpdate(false); + + // delete any custom version inside the instance (it's no longer relevant, we did an update) + QString custom = PathCombine(inst_dir, "/custom.json"); + QFile finfo(custom); + if (finfo.exists()) + { + finfo.remove(); + } + inst->reloadFullVersion(); + + jarlibStart(); +} + +void DerpUpdate::versionFileFailed() +{ + emitFailed("Failed to download the version description. Try again."); +} + +void DerpUpdate::assetIndexStart() +{ + setStatus(tr("Updating assets index...")); + DerpInstance *inst = (DerpInstance *)m_inst; + std::shared_ptr version = inst->getFullVersion(); + QString assetName = version->assets; + QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; + QString localPath = assetName + ".json"; + auto job = new NetJob("Asset index for " + inst->name()); + + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("asset_indexes", localPath); + job->addNetAction(CacheDownload::make(indexUrl, entry)); + jarlibDownloadJob.reset(job); + + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetIndexFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + + jarlibDownloadJob->start(); +} + +void DerpUpdate::assetIndexFinished() +{ + AssetsIndex index; + + DerpInstance *inst = (DerpInstance *)m_inst; + std::shared_ptr version = inst->getFullVersion(); + QString assetName = version->assets; + + QString asset_fname = "assets/indexes/" + assetName + ".json"; + if (!AssetsUtils::loadAssetsIndexJson(asset_fname, &index)) + { + emitFailed("Failed to read the assets index!"); + } + + QList dls; + for (auto object : index.objects.values()) + { + QString objectName = object.hash.left(2) + "/" + object.hash; + QFileInfo objectFile("assets/objects/" + objectName); + if ((!objectFile.isFile()) || (objectFile.size() != object.size)) + { + auto objectDL = MD5EtagDownload::make( + QUrl("http://" + URLConstants::RESOURCE_BASE + objectName), + objectFile.filePath()); + objectDL->m_total_progress = object.size; + dls.append(objectDL); + } + } + if (dls.size()) + { + setStatus(tr("Getting the assets files from Mojang...")); + auto job = new NetJob("Assets for " + inst->name()); + for (auto dl : dls) + job->addNetAction(dl); + jarlibDownloadJob.reset(job); + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(assetsFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + jarlibDownloadJob->start(); + return; + } + assetsFinished(); +} + +void DerpUpdate::assetIndexFailed() +{ + emitFailed("Failed to download the assets index!"); +} + +void DerpUpdate::assetsFinished() +{ + prepareForLaunch(); +} + +void DerpUpdate::assetsFailed() +{ + emitFailed("Failed to download assets!"); +} + +void DerpUpdate::jarlibStart() +{ + setStatus(tr("Getting the library files from Mojang...")); + QLOG_INFO() << m_inst->name() << ": downloading libraries"; + DerpInstance *inst = (DerpInstance *)m_inst; + bool successful = inst->reloadFullVersion(); + if (!successful) + { + emitFailed("Failed to load the version description file. It might be " + "corrupted, missing or simply too new."); + return; + } + + // Build a list of URLs that will need to be downloaded. + std::shared_ptr version = inst->getFullVersion(); + // minecraft.jar for this version + { + QString version_id = version->id; + QString localPath = version_id + "/" + version_id + ".jar"; + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + localPath; + + auto job = new NetJob("Libraries for instance " + inst->name()); + + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); + + jarlibDownloadJob.reset(job); + } + + auto libs = version->getActiveNativeLibs(); + libs.append(version->getActiveNormalLibs()); + + auto metacache = MMC->metacache(); + QList ForgeLibs; + for (auto lib : libs) + { + if (lib->hint() == "local") + continue; + + QString raw_storage = lib->storagePath(); + QString raw_dl = lib->downloadUrl(); + + auto f = [&](QString storage, QString dl) + { + auto entry = metacache->resolveEntry("libraries", storage); + if (entry->stale) + { + if (lib->hint() == "forge-pack-xz") + { + ForgeLibs.append(ForgeXzDownload::make(storage, entry)); + } + else + { + jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry)); + } + } + }; + if (raw_storage.contains("${arch}")) + { + QString cooked_storage = raw_storage; + QString cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); + } + else + { + f(raw_storage, raw_dl); + } + } + // TODO: think about how to propagate this from the original json file... or IF AT ALL + QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list"; + if (!ForgeLibs.empty()) + { + jarlibDownloadJob->addNetAction( + ForgeMirrors::make(ForgeLibs, jarlibDownloadJob, forgeMirrorList)); + } + + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + + jarlibDownloadJob->start(); +} + +void DerpUpdate::jarlibFinished() +{ + assetIndexStart(); +} + +void DerpUpdate::jarlibFailed() +{ + QStringList failed = jarlibDownloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed("Failed to download the following files:\n" + failed_all + + "\n\nPlease try again."); +} + +void DerpUpdate::prepareForLaunch() +{ + setStatus(tr("Preparing for launch...")); + QLOG_INFO() << m_inst->name() << ": preparing for launch"; + auto derp_inst = (DerpInstance *)m_inst; + + // delete any leftovers, if they are present. + derp_inst->cleanupAfterRun(); + + QString natives_dir_raw = PathCombine(derp_inst->instanceRoot(), "natives/"); + auto version = derp_inst->getFullVersion(); + if (!version) + { + emitFailed("The version information for this instance is not complete. Try re-creating " + "it or changing the version."); + return; + } + /* + * emitFailed("Could not create the native library folder:\n" + natives_dir_raw + + "\nMake sure MultiMC has appropriate permissions and there is enough + space " + "on the storage device."); + */ + for (auto lib : version->getActiveNativeLibs()) + { + if (!lib->filesExist()) + { + emitFailed("Native library is missing some files:\n" + lib->storagePath() + + "\n\nRun the instance at least once in online mode to get all the " + "required files."); + return; + } + if (!lib->extractTo(natives_dir_raw)) + { + emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " + + natives_dir_raw + + "\n\nMake sure MultiMC has appropriate permissions and there is enough " + "space on the storage device."); + return; + } + } + + emitSucceeded(); +} diff --git a/logic/DerpUpdate.h b/logic/DerpUpdate.h new file mode 100644 index 00000000..475f6c35 --- /dev/null +++ b/logic/DerpUpdate.h @@ -0,0 +1,63 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "logic/net/NetJob.h" +#include "logic/tasks/Task.h" + +class MinecraftVersion; +class BaseInstance; + +class DerpUpdate : public Task +{ + Q_OBJECT +public: + explicit DerpUpdate(BaseInstance *inst, bool prepare_for_launch, QObject *parent = 0); + virtual void executeTask(); + +private +slots: + void versionFileStart(); + void versionFileFinished(); + void versionFileFailed(); + + void jarlibStart(); + void jarlibFinished(); + void jarlibFailed(); + + void assetIndexStart(); + void assetIndexFinished(); + void assetIndexFailed(); + + void assetsFinished(); + void assetsFailed(); + + // extract the appropriate libraries + void prepareForLaunch(); + +private: + NetJobPtr specificVersionDownloadJob; + NetJobPtr jarlibDownloadJob; + + // target version, determined during this task + std::shared_ptr targetVersion; + BaseInstance *m_inst = nullptr; + bool m_only_prepare = false; +}; diff --git a/logic/DerpVersion.cpp b/logic/DerpVersion.cpp new file mode 100644 index 00000000..f13ac620 --- /dev/null +++ b/logic/DerpVersion.cpp @@ -0,0 +1,164 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DerpVersion.h" + +#include + +#include "DerpVersionBuilder.h" + +DerpVersion::DerpVersion(DerpInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ +} + +bool DerpVersion::reload(QWidget *widgetParent) +{ + return DerpVersionBuilder::build(this, m_instance, widgetParent); +} + +QList > DerpVersion::getActiveNormalLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +QList > DerpVersion::getActiveNativeLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +QVariant DerpVersion::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= libraries.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return libraries[row]->name(); + case 1: + return libraries[row]->type(); + case 2: + return libraries[row]->version(); + default: + return QVariant(); + } + } + return QVariant(); +} + +Qt::ItemFlags DerpVersion::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + int row = index.row(); + if (libraries[row]->isActive()) + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } + else + { + return Qt::ItemNeverHasChildren; + } + // return QAbstractListModel::flags(index); +} + +QVariant DerpVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + switch (section) + { + case 0: + return QString("Name"); + case 1: + return QString("Type"); + case 2: + return QString("Version"); + default: + return QString(); + } +} + +int DerpVersion::rowCount(const QModelIndex &parent) const +{ + return libraries.size(); +} + +int DerpVersion::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QDebug operator<<(QDebug &dbg, const DerpVersion *version) +{ + dbg.nospace() << "DerpVersion(" + << "\n\tid=" << version->id + << "\n\ttime=" << version->time + << "\n\treleaseTime=" << version->releaseTime + << "\n\ttype=" << version->type + << "\n\tassets=" << version->assets + << "\n\tprocessArguments=" << version->processArguments + << "\n\tminecraftArguments=" << version->minecraftArguments + << "\n\tminimumLauncherVersion=" << version->minimumLauncherVersion + << "\n\tmainClass=" << version->mainClass + << "\n\tlibraries="; + for (auto lib : version->libraries) + { + dbg.nospace() << "\n\t\t" << lib.get(); + } + dbg.nospace() << "\n)"; + return dbg.maybeSpace(); +} +QDebug operator<<(QDebug &dbg, const DerpLibrary *library) +{ + dbg.nospace() << "DerpLibrary(" + << "\n\t\t\trawName=" << library->rawName() + << "\n\t\t\tname=" << library->name() + << "\n\t\t\tversion=" << library->version() + << "\n\t\t\ttype=" << library->type() + << "\n\t\t\tisActive=" << library->isActive() + << "\n\t\t\tisNative=" << library->isNative() + << "\n\t\t\tdownloadUrl=" << library->downloadUrl() + << "\n\t\t\tstoragePath=" << library->storagePath() + << "\n\t\t\tabsolutePath=" << library->absoluteUrl() + << "\n\t\t\thint=" << library->hint(); + dbg.nospace() << "\n\t\t)"; + return dbg.maybeSpace(); +} diff --git a/logic/DerpVersion.h b/logic/DerpVersion.h new file mode 100644 index 00000000..cadfa850 --- /dev/null +++ b/logic/DerpVersion.h @@ -0,0 +1,110 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "DerpLibrary.h" + +class DerpInstance; + +class DerpVersion : public QAbstractListModel +{ + Q_OBJECT +public: + explicit DerpVersion(DerpInstance *instance, QObject *parent = 0); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + bool reload(QWidget *widgetParent); + +public: + QList> getActiveNormalLibs(); + QList> getActiveNativeLibs(); + + // data members +public: + /// the ID - determines which jar to use! ACTUALLY IMPORTANT! + QString id; + /// Last updated time - as a string + QString time; + /// Release time - as a string + QString releaseTime; + /// Release type - "release" or "snapshot" + QString type; + /// Assets type - "legacy" or a version ID + QString assets; + /** + * DEPRECATED: Old versions of the new vanilla launcher used this + * ex: "username_session_version" + */ + QString processArguments; + /** + * arguments that should be used for launching minecraft + * + * ex: "--username ${auth_player_name} --session ${auth_session} + * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" + */ + QString minecraftArguments; + /** + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) + */ + int minimumLauncherVersion = 0xDEADBEEF; + /** + * The main class to load first + */ + QString mainClass; + + /// the list of libs - both active and inactive, native and java + QList> libraries; + + /* + FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. + + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ], + "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX + 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" + } + */ + // QList rules; + +private: + DerpInstance *m_instance; +}; + +QDebug operator<<(QDebug &dbg, const DerpVersion *version); +QDebug operator<<(QDebug &dbg, const DerpLibrary *library); diff --git a/logic/DerpVersionBuilder.cpp b/logic/DerpVersionBuilder.cpp new file mode 100644 index 00000000..d8091f32 --- /dev/null +++ b/logic/DerpVersionBuilder.cpp @@ -0,0 +1,279 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DerpVersionBuilder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DerpVersion.h" +#include "DerpInstance.h" +#include "DerpRule.h" + +DerpVersionBuilder::DerpVersionBuilder() +{ + +} + +bool DerpVersionBuilder::build(DerpVersion *version, DerpInstance *instance, QWidget *widgetParent) +{ + DerpVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.m_widgetParent = widgetParent; + return builder.build(); +} + +bool DerpVersionBuilder::build() +{ + clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + // version.json -> patches/*.json -> custom.json + + // version.json + { + QJsonObject obj; + if (!read(QFileInfo(root.absoluteFilePath("version.json")), &obj)) + { + return false; + } + if (!apply(obj)) + { + return false; + } + } + + // patches/ + { + // load all, put into map for ordering, apply in the right order + + QMap objects; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + QJsonObject obj; + if (!read(info, &obj)) + { + return false; + } + if (!obj.contains("order") || !obj.value("order").isDouble()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("Missing or invalid 'order' in %1").arg(info.absoluteFilePath())); + return false; + } + objects.insert(obj.value("order").toDouble(), obj); + } + for (auto object : objects.values()) + { + if (!apply(object)) + { + return false; + } + } + } + + // custom.json + { + if (QFile::exists(root.absoluteFilePath("custom.json"))) + { + QJsonObject obj; + if (!read(QFileInfo(root.absoluteFilePath("custom.json")), &obj)) + { + return false; + } + if (!apply(obj)) + { + return false; + } + } + } + + return true; +} + +void DerpVersionBuilder::clear() +{ + m_version->id.clear(); + m_version->time.clear(); + m_version->releaseTime.clear(); + m_version->type.clear(); + m_version->assets.clear(); + m_version->processArguments.clear(); + m_version->minecraftArguments.clear(); + m_version->minimumLauncherVersion = 0xDEADBEAF; + m_version->mainClass.clear(); + m_version->libraries.clear(); +} + +void applyString(const QJsonObject &obj, const QString &key, QString &out) +{ + if (obj.contains(key) && obj.value(key).isString()) + { + out = obj.value(key).toString(); + } +} +void applyString(const QJsonObject &obj, const QString &key, std::shared_ptr lib, void(DerpLibrary::*func)(const QString &val)) +{ + if (obj.contains(key) && obj.value(key).isString()) + { + (lib.get()->*func)(obj.value(key).toString()); + } +} +bool DerpVersionBuilder::apply(const QJsonObject &object) +{ + applyString(object, "id", m_version->id); + applyString(object, "mainClass", m_version->mainClass); + applyString(object, "processArguments", m_version->processArguments); + { + const QString toCompare = m_version->processArguments.toLower(); + if (toCompare == "legacy") + { + m_version->minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + m_version->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; + } + } + applyString(object, "minecraftArguments", m_version->minecraftArguments); + applyString(object, "type", m_version->type); + applyString(object, "releaseTime", m_version->releaseTime); + applyString(object, "time", m_version->time); + applyString(object, "assets", m_version->assets); + { + if (m_version->assets.isEmpty()) + { + m_version->assets = "legacy"; + } + } + if (object.contains("minimumLauncherVersion")) + { + auto minLauncherVersionVal = object.value("minimumLauncherVersion"); + if (minLauncherVersionVal.isDouble()) + { + m_version->minimumLauncherVersion = minLauncherVersionVal.toDouble(); + } + } + + // libraries + { + auto librariesValue = object.value("libraries"); + if (!librariesValue.isArray()) + { + QMessageBox::critical(m_widgetParent, QObject::tr("Error"), QObject::tr("One json files contains a libraries field, but it's not an array")); + return false; + } + for (auto libVal : librariesValue.toArray()) + { + if (!libVal.isObject()) + { + continue; + } + + QJsonObject libObj = libVal.toObject(); + + // Library name + auto nameVal = libObj.value("name"); + if (!nameVal.isString()) + { + continue; + } + std::shared_ptr library(new DerpLibrary(nameVal.toString())); + + applyString(libObj, "url", library, &DerpLibrary::setBaseUrl); + applyString(libObj, "MMC-hint", library, &DerpLibrary::setHint); + applyString(libObj, "MMC-absulute_url", library, &DerpLibrary::setAbsoluteUrl); + applyString(libObj, "MMC-absoluteUrl", library, &De