diff options
Diffstat (limited to 'logic/OneSixUpdate.cpp')
-rw-r--r-- | logic/OneSixUpdate.cpp | 357 |
1 files changed, 254 insertions, 103 deletions
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index d083c2ba..abc5f9c1 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -22,28 +22,24 @@ #include <QFileInfo> #include <QTextStream> #include <QDataStream> - -#include "BaseInstance.h" -#include "lists/MinecraftVersionList.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" -#include "net/ForgeMirrors.h" -#include "net/URLConstants.h" -#include "assets/AssetsUtils.h" - -#include "pathutils.h" +#include <pathutils.h> #include <JlCompress.h> -OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) - : Task(parent), m_inst(inst) +#include "logic/BaseInstance.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/OneSixInstance.h" +#include "logic/forge/ForgeMirrors.h" +#include "logic/net/URLConstants.h" +#include "logic/assets/AssetsUtils.h" + +OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } void OneSixUpdate::executeTask() { - QString intendedVersion = m_inst->intendedVersionId(); - // Make directories QDir mcDir(m_inst->minecraftRoot()); if (!mcDir.exists() && !mcDir.mkpath(".")) @@ -52,102 +48,44 @@ void OneSixUpdate::executeTask() return; } - if (m_inst->shouldUpdate()) + // Get a pointer to the version object that corresponds to the instance's version. + targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( + MMC->minecraftlist()->findVersion(m_inst->intendedVersionId())); + if (targetVersion == nullptr) { - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( - MMC->minecraftlist()->findVersion(intendedVersion)); - if (targetVersion == nullptr) - { - // don't do anything if it was invalid - emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); - return; - } - versionFileStart(); + // don't do anything if it was invalid + emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); + return; } - else + if (m_inst->providesVersionFile() || !targetVersion->needsUpdate()) { jarlibStart(); + return; } -} - -void OneSixUpdate::versionFileStart() -{ - if (m_inst->providesVersionFile()) + versionUpdateTask = MMC->minecraftlist()->createUpdateTask(m_inst->intendedVersionId()); + if (!versionUpdateTask) { jarlibStart(); return; } - 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)), + connect(versionUpdateTask.get(), SIGNAL(succeeded()), SLOT(jarlibStart())); + connect(versionUpdateTask.get(), SIGNAL(failed(QString)), SLOT(versionUpdateFailed(QString))); + connect(versionUpdateTask.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - specificVersionDownloadJob->start(); -} - -void OneSixUpdate::versionFileFinished() -{ - NetActionPtr DlJob = specificVersionDownloadJob->first(); - - 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(tr("Can't open %1 for writing.").arg(version1)); - return; - } - auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; - qint64 actual = 0; - if ((actual = vfile1.write(data)) != data.size()) - { - emitFailed(tr("Failed to write into %1. Written %2 out of %3.").arg(version1).arg(actual).arg(data.size())); - return; - } - if (!vfile1.commit()) - { - emitFailed(tr("Can't commit changes to %1").arg(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(); - } - // NOTE: Version is reloaded in jarlibStart - jarlibStart(); + setStatus(tr("Getting the version files from Mojang...")); + versionUpdateTask->start(); } -void OneSixUpdate::versionFileFailed() +void OneSixUpdate::versionUpdateFailed(QString reason) { - emitFailed(tr("Failed to download the version description. Try again.")); + emitFailed(reason); } void OneSixUpdate::assetIndexStart() { setStatus(tr("Updating assets index...")); OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); QString assetName = version->assets; QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; QString localPath = assetName + ".json"; @@ -171,7 +109,7 @@ void OneSixUpdate::assetIndexFinished() AssetsIndex index; OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); QString assetName = version->assets; QString asset_fname = "assets/indexes/" + assetName + ".json"; @@ -235,19 +173,19 @@ void OneSixUpdate::jarlibStart() { inst->reloadVersion(); } - catch(MMCError & e) + catch (MMCError &e) { emitFailed(e.cause()); return; } - catch(...) + catch (...) { emitFailed(tr("Failed to load the version description file for reasons unknown.")); return; } // Build a list of URLs that will need to be downloaded. - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); // minecraft.jar for this version { QString version_id = version->id; @@ -259,7 +197,7 @@ void OneSixUpdate::jarlibStart() auto metacache = MMC->metacache(); auto entry = metacache->resolveEntry("versions", localPath); job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); - + jarHashOnEntry = entry->md5sum; jarlibDownloadJob.reset(job); } @@ -274,7 +212,7 @@ void OneSixUpdate::jarlibStart() { if (lib->hint() == "local") { - if(!lib->filesExist(m_inst->librariesPath())) + if (!lib->filesExist(m_inst->librariesPath())) brokenLocalLibs.append(lib); continue; } @@ -311,16 +249,19 @@ void OneSixUpdate::jarlibStart() f(raw_storage, raw_dl); } } - if(!brokenLocalLibs.empty()) + if (!brokenLocalLibs.empty()) { jarlibDownloadJob.reset(); QStringList failed; - for(auto brokenLib : brokenLocalLibs) + for (auto brokenLib : brokenLocalLibs) { failed.append(brokenLib->files()); } QString failed_all = failed.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)); + 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; } // TODO: think about how to propagate this from the original json file... or IF AT ALL @@ -341,12 +282,222 @@ void OneSixUpdate::jarlibStart() void OneSixUpdate::jarlibFinished() { - assetIndexStart(); + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); + + // create stripped jar, if needed + if (version->hasJarMods()) + { + // FIXME: good candidate for moving elsewhere (jar location resolving/version caching). + QString version_id = version->id; + QString localPath = version_id + "/" + version_id + ".jar"; + QString strippedPath = version_id + "/" + version_id + "-stripped.jar"; + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + auto entryStripped = metacache->resolveEntry("versions", strippedPath); + + QString fullJarPath = entry->getFullPath(); + QString fullStrippedJarPath = entryStripped->getFullPath(); + QFileInfo finfo(fullStrippedJarPath); + if (entry->md5sum != jarHashOnEntry || !finfo.exists()) + { + stripJar(fullJarPath, fullStrippedJarPath); + } + } + if (version->traits.contains("legacyFML")) + { + fmllibsStart(); + } + else + { + assetIndexStart(); + } } void OneSixUpdate::jarlibFailed() { QStringList failed = jarlibDownloadJob->getFailedFiles(); QString failed_all = failed.join("\n"); - emitFailed(tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all)); + emitFailed( + tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all)); +} + +void OneSixUpdate::stripJar(QString origPath, QString newPath) +{ + QFileInfo runnableJar(newPath); + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + { + emitFailed("Failed to delete old minecraft.jar"); + return; + } + + // TaskStep(); // STEP 1 + setStatus(tr("Creating stripped jar: Opening minecraft.jar ...")); + + QuaZip zipOut(runnableJar.filePath()); + if (!zipOut.open(QuaZip::mdCreate)) + { + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to open the minecraft.jar for stripping"); + return; + } + // Modify the jar + setStatus(tr("Creating stripped jar: Adding files...")); + if (!MergeZipFiles(&zipOut, origPath)) + { + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to add " + origPath + " to the jar."); + return; + } +} + +bool OneSixUpdate::MergeZipFiles(QuaZip *into, QString from) +{ + setStatus(tr("Installing mods: Adding ") + from + " ..."); + + QuaZip modZip(from); + modZip.open(QuaZip::mdUnzip); + + QuaZipFile fileInsideMod(&modZip); + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) + { + QString filename = modZip.getCurrentFileName(); + if (filename.contains("META-INF")) + { + QLOG_INFO() << "Skipping META-INF " << filename << " from " << from; + continue; + } + QLOG_INFO() << "Adding file " << filename << " from " << from; + + if (!fileInsideMod.open(QIODevice::ReadOnly)) + { + QLOG_ERROR() << "Failed to open " << filename << " from " << from; + return false; + } + /* + QuaZipFileInfo old_info; + fileInsideMod.getFileInfo(&old_info); + */ + QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); + /* + info_out.externalAttr = old_info.externalAttr; + */ + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) + { + QLOG_ERROR() << "Failed to open " << filename << " in the jar"; + fileInsideMod.close(); + return false; + } + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) + { + zipOutFile.close(); + fileInsideMod.close(); + QLOG_ERROR() << "Failed to copy data of " << filename << " into the jar"; + return false; + } + zipOutFile.close(); + fileInsideMod.close(); + } + return true; +} + +void OneSixUpdate::fmllibsStart() +{ + // Get the mod list + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<InstanceVersion> fullversion = inst->getFullVersion(); + 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 = (fullversion->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(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 = MMC->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, SIGNAL(failed()), SLOT(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 = MMC->metacache(); + int index = 0; + for (auto &lib : fmlLibsToProcess) + { + progress(index, fmlLibsToProcess.size()); + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + auto path = PathCombine(inst->libDir(), lib.filename); + if (!ensureFilePathExists(path)) + { + emitFailed(tr("Failed creating FML library folder inside the instance.")); + return; + } + if (!QFile::copy(entry->getFullPath(), 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() +{ + emitFailed("Game update failed: it was impossible to fetch the required FML libraries."); + return; } |