diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp index 1b44ec7a..8304fdd4 100644 --- a/api/logic/InstanceImportTask.cpp +++ b/api/logic/InstanceImportTask.cpp @@ -10,6 +10,8 @@ #include "settings/INISettingsObject.h" #include "icons/IIconList.h" #include + +// FIXME: this does not belong here, it's Minecraft/Curse specific #include "minecraft/curse/FileResolvingTask.h" #include "minecraft/curse/PackManifest.h" #include "Json.h" @@ -56,11 +58,13 @@ void InstanceImportTask::executeTask() void InstanceImportTask::downloadSucceeded() { extractAndTweak(); + m_filesNetJob.reset(); } void InstanceImportTask::downloadFailed(QString reason) { emitFailed(reason); + m_filesNetJob.reset(); } void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) @@ -93,7 +97,7 @@ void InstanceImportTask::extractAndTweak() setStatus(tr("Extracting modpack")); m_stagingPath = m_target->getStagedInstancePath(); QDir extractDir(m_stagingPath); - qDebug() << "Attempting to create instance from" << m_archivePath; + qInfo() << "Attempting to create instance from" << m_archivePath; m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath()); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); @@ -110,18 +114,53 @@ void InstanceImportTask::extractFinished() return; } QDir extractDir(m_stagingPath); + + qInfo() << "Fixing permissions for extracted pack files..."; + QDirIterator it(extractDir, QDirIterator::Subdirectories); + while (it.hasNext()) + { + auto filepath = it.next(); + QFileInfo file(filepath); + auto permissions = QFile::permissions(filepath); + auto origPermissions = permissions; + if(file.isDir()) + { + // Folder +rwx for current user + permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; + } + else + { + // File +rw for current user + permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; + } + if(origPermissions != permissions) + { + if(!QFile::setPermissions(filepath, permissions)) + { + qWarning() << "Could not fix" << filepath; + } + else + { + qDebug() << "Fixed" << filepath; + } + } + } + const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg"); const QFileInfo curseJson = findRecursive(extractDir.absolutePath(), "manifest.json"); if (instanceCfgFile.isFile()) { + qInfo() << "Pack appears to be exported from MultiMC."; processMultiMC(instanceCfgFile); } else if (curseJson.isFile()) { + qInfo() << "Pack appears to be from Curse."; processCurse(curseJson); } else { + qCritical() << "Archive does not contain a recognized modpack type."; m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Archive does not contain a recognized modpack type.")); } @@ -143,46 +182,101 @@ void InstanceImportTask::processCurse(const QFileInfo & manifest) } catch (JSONValidationError & e) { + m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Could not understand curse manifest:\n") + e.cause()); return; } m_packRoot = manifest.absolutePath(); QString configPath = FS::PathCombine(m_packRoot, "instance.cfg"); + if(!pack.overrides.isEmpty()) + { + QString overridePath = FS::PathCombine(m_packRoot, pack.overrides); + QString mcPath = FS::PathCombine(m_packRoot, "minecraft"); + if (!QFile::rename(overridePath, mcPath)) + { + m_target->destroyStagingPath(m_stagingPath); + emitFailed(tr("Could not rename the curse overrides:\n") + pack.overrides); + return; + } + } + + QString forgeVersion; + for(auto &loader: pack.minecraft.modLoaders) + { + auto id = loader.id; + if(id.startsWith("forge-")) + { + id.remove("forge-"); + forgeVersion = id; + continue; + } + qWarning() << "Unknown mod loader in curse manifest:" << id; + } + auto instanceSettings = std::make_shared(configPath); instanceSettings->registerSetting("InstanceType", "Legacy"); instanceSettings->set("InstanceType", "OneSix"); OneSixInstance instance(m_globalSettings, instanceSettings, m_packRoot); instance.setIntendedVersionId(pack.minecraft.version); + if(!forgeVersion.isEmpty()) + { + instance.setComponentVersion("net.minecraftforge", forgeVersion); + } instance.setName(m_instName); instance.setIconKey(m_instIcon); - m_curseResolver.reset(new Curse::FileResolvingTask(pack.files)); - connect(m_curseResolver.get(), &Curse::FileResolvingTask::succeeded, this, &InstanceImportTask::curseResolvingSucceeded); - connect(m_curseResolver.get(), &Curse::FileResolvingTask::failed, this, &InstanceImportTask::curseResolvingFailed); - m_curseResolver->start(); -} - -void InstanceImportTask::curseResolvingFailed(QString reason) -{ - m_target->destroyStagingPath(m_stagingPath); - m_curseResolver.reset(); - emitFailed(tr("Unable to resolve Curse mod IDs:\n") + reason); -} - -void InstanceImportTask::curseResolvingSucceeded() -{ - auto results = m_curseResolver->getResults(); - for(auto result: results) + m_curseResolver.reset(new Curse::FileResolvingTask(pack)); + connect(m_curseResolver.get(), &Curse::FileResolvingTask::succeeded, [&]() { - qDebug() << result.fileName << " = " << result.url; + auto results = m_curseResolver->getResults(); + m_filesNetJob.reset(new NetJob(tr("Curse mod download"))); + for(auto result: results.files) + { + auto path = FS::PathCombine(m_packRoot, "minecraft/mods", result.fileName); + auto dl = Net::Download::makeFile(result.url,path); + m_filesNetJob->addNetAction(dl); + } + m_curseResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() + { + m_filesNetJob.reset(); + if (!m_target->commitStagedInstance(m_stagingPath, m_packRoot, m_instName, m_instGroup)) + { + m_target->destroyStagingPath(m_stagingPath); + emitFailed(tr("Unable to commit instance")); + return; + } + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) + { + m_target->destroyStagingPath(m_stagingPath); + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); } - m_curseResolver.reset(); - if (!m_target->commitStagedInstance(m_stagingPath, m_packRoot, m_instName, m_instGroup)) + ); + connect(m_curseResolver.get(), &Curse::FileResolvingTask::failed, [&](QString reason) { m_target->destroyStagingPath(m_stagingPath); - emitFailed(tr("Unable to commit instance")); - return; - } - emitSucceeded(); + m_curseResolver.reset(); + emitFailed(tr("Unable to resolve Curse mod IDs:\n") + reason); + }); + connect(m_curseResolver.get(), &Curse::FileResolvingTask::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + connect(m_curseResolver.get(), &Curse::FileResolvingTask::status, [&](QString status) + { + setStatus(status); + }); + m_curseResolver->start(); } void InstanceImportTask::processMultiMC(const QFileInfo & config) diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h index 0bd7ddf8..a1c1857d 100644 --- a/api/logic/InstanceImportTask.h +++ b/api/logic/InstanceImportTask.h @@ -37,8 +37,6 @@ private slots: void downloadProgressChanged(qint64 current, qint64 total); void extractFinished(); void extractAborted(); - void curseResolvingSucceeded(); - void curseResolvingFailed(QString reason); private: /* data */ SettingsObjectPtr m_globalSettings; diff --git a/api/logic/minecraft/curse/FileResolvingTask.cpp b/api/logic/minecraft/curse/FileResolvingTask.cpp index 13308202..aaac404b 100644 --- a/api/logic/minecraft/curse/FileResolvingTask.cpp +++ b/api/logic/minecraft/curse/FileResolvingTask.cpp @@ -3,17 +3,19 @@ const char * metabase = "https://cursemeta.dries007.net"; -Curse::FileResolvingTask::FileResolvingTask(QVector& toProcess) +Curse::FileResolvingTask::FileResolvingTask(Curse::Manifest& toProcess) : m_toProcess(toProcess) { } void Curse::FileResolvingTask::executeTask() { + setStatus(tr("Resolving curse mod IDs...")); + setProgress(0, m_toProcess.files.size()); m_dljob.reset(new NetJob("Curse file resolver")); - results.resize(m_toProcess.size()); + results.resize(m_toProcess.files.size()); int index = 0; - for(auto & file: m_toProcess) + for(auto & file: m_toProcess.files) { auto projectIdStr = QString::number(file.projectId); auto fileIdStr = QString::number(file.fileId); @@ -42,7 +44,7 @@ void Curse::FileResolvingTask::netJobFinished() failed = true; continue; } - auto & out = m_toProcess[index]; + auto & out = m_toProcess.files[index]; out.fileName = Json::requireString(obj, "FileNameOnDisk"); out.url = Json::requireString(obj, "DownloadURL"); out.resolved = true; diff --git a/api/logic/minecraft/curse/FileResolvingTask.h b/api/logic/minecraft/curse/FileResolvingTask.h index b7ca85d3..a9357bfb 100644 --- a/api/logic/minecraft/curse/FileResolvingTask.h +++ b/api/logic/minecraft/curse/FileResolvingTask.h @@ -12,8 +12,8 @@ class MULTIMC_LOGIC_EXPORT FileResolvingTask : public Task { Q_OBJECT public: - explicit FileResolvingTask(QVector &toProcess); - const QVector &getResults() const + explicit FileResolvingTask(Curse::Manifest &toProcess); + const Curse::Manifest &getResults() const { return m_toProcess; } @@ -25,7 +25,7 @@ protected slots: void netJobFinished(); private: /* data */ - QVector m_toProcess; + Curse::Manifest m_toProcess; QVector results; NetJobPtr m_dljob; }; diff --git a/api/logic/minecraft/curse/PackManifest.cpp b/api/logic/minecraft/curse/PackManifest.cpp index a4ea703b..fd40a37c 100644 --- a/api/logic/minecraft/curse/PackManifest.cpp +++ b/api/logic/minecraft/curse/PackManifest.cpp @@ -11,7 +11,7 @@ static void loadFileV1(Curse::File & f, QJsonObject & file) static void loadModloaderV1(Curse::Modloader & m, QJsonObject & modLoader) { m.id = Json::requireString(modLoader, "id"); - m.primary = Json::ensureBoolean(modLoader, "primary", false); + m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); } static void loadMinecraftV1(Curse::Minecraft & m, QJsonObject & minecraft)