diff --git a/cslol-tools/dep/CMakeLists.txt b/cslol-tools/dep/CMakeLists.txt index 38b107b3..89ee9a8e 100644 --- a/cslol-tools/dep/CMakeLists.txt +++ b/cslol-tools/dep/CMakeLists.txt @@ -76,6 +76,10 @@ FetchContent_Declare( FetchContent_GetProperties(miniz) if(NOT miniz_POPULATED) FetchContent_Populate(miniz) + # Patch the CMakeLists.txt to use a newer minimum version + file(READ ${miniz_SOURCE_DIR}/CMakeLists.txt MINIZ_CMAKE) + string(REPLACE "cmake_minimum_required(VERSION 3.0)" "cmake_minimum_required(VERSION 3.20)" MINIZ_CMAKE ${MINIZ_CMAKE}) + file(WRITE ${miniz_SOURCE_DIR}/CMakeLists.txt ${MINIZ_CMAKE}) add_subdirectory(${miniz_SOURCE_DIR} ${miniz_BINARY_DIR}) target_compile_definitions(miniz PRIVATE -DMINIZ_DISABLE_ZIP_READER_CRC32_CHECKS=1) endif() diff --git a/src/CSLOLTools.cpp b/src/CSLOLTools.cpp index 334fbc30..f1ec3b72 100644 --- a/src/CSLOLTools.cpp +++ b/src/CSLOLTools.cpp @@ -30,6 +30,7 @@ CSLOLTools::CSLOLTools(QObject *parent) : QObject(parent) { connect(worker_, &CSLOLToolsImpl::modWadsAdded, this, &CSLOLTools::modWadsAdded); connect(worker_, &CSLOLToolsImpl::modWadsRemoved, this, &CSLOLTools::modWadsRemoved); connect(worker_, &CSLOLToolsImpl::updatedMods, this, &CSLOLTools::updatedMods); + connect(worker_, &CSLOLToolsImpl::conflictDetected, this, &CSLOLTools::conflictDetected); connect(this, &CSLOLTools::changeLeaguePath, worker_, &CSLOLToolsImpl::changeLeaguePath); connect(this, &CSLOLTools::changeBlacklist, worker_, &CSLOLToolsImpl::changeBlacklist); @@ -38,6 +39,9 @@ CSLOLTools::CSLOLTools(QObject *parent) : QObject(parent) { connect(this, &CSLOLTools::deleteMod, worker_, &CSLOLToolsImpl::deleteMod); connect(this, &CSLOLTools::exportMod, worker_, &CSLOLToolsImpl::exportMod); connect(this, &CSLOLTools::installFantomeZip, worker_, &CSLOLToolsImpl::installFantomeZip); + connect(this, &CSLOLTools::installFantomeZips, worker_, &CSLOLToolsImpl::installFantomeZips); + connect(this, &CSLOLTools::handleDroppedUrls, worker_, &CSLOLToolsImpl::handleDroppedUrls); + connect(this, &CSLOLTools::resolveConflict, worker_, &CSLOLToolsImpl::resolveConflict); connect(this, &CSLOLTools::saveProfile, worker_, &CSLOLToolsImpl::saveProfile); connect(this, &CSLOLTools::loadProfile, worker_, &CSLOLToolsImpl::loadProfile); connect(this, &CSLOLTools::deleteProfile, worker_, &CSLOLToolsImpl::deleteProfile); diff --git a/src/CSLOLTools.h b/src/CSLOLTools.h index a56f4ef9..d2c12b12 100644 --- a/src/CSLOLTools.h +++ b/src/CSLOLTools.h @@ -47,6 +47,8 @@ class CSLOLTools : public QObject { void updatedMods(QJsonArray mods); void reportError(QString name, QString message, QString stack_trace); + void conflictDetected(QString modName, QString newPath); + void changeLeaguePath(QString newLeaguePath); void changeBlacklist(bool blacklist); void changeIgnorebad(bool ignorebad); @@ -66,6 +68,10 @@ class CSLOLTools : public QObject { void refreshMods(); void runDiag(); + void installFantomeZips(QStringList paths); + void handleDroppedUrls(QStringList urls); + void resolveConflict(bool overwrite); + public slots: CSLOLToolsImpl::CSLOLState getState(); QString getStatus(); diff --git a/src/CSLOLToolsImpl.cpp b/src/CSLOLToolsImpl.cpp index 2e0c43a3..55ce6e35 100644 --- a/src/CSLOLToolsImpl.cpp +++ b/src/CSLOLToolsImpl.cpp @@ -369,38 +369,7 @@ void CSLOLToolsImpl::exportMod(QString name, QString dest) { void CSLOLToolsImpl::installFantomeZip(QString path) { if (state_ == CSLOLState::StateIdle && !path.isEmpty()) { - setState(CSLOLState::StateBusy); - - setStatus("Installing Mod"); - auto name = QFileInfo(path) - .fileName() - .replace(".zip", "") - .replace(".fantome", "") - .replace(".wad", "") - .replace(".client", ""); - auto dst = prog_ + "/installed/" + name; - if (QDir old(dst); old.exists()) { - auto info = modInfoRead(name); - doReportError("Install mod", "Already exists", ""); - setState(CSLOLState::StateIdle); - return; - } - - runTool( - { - "import", - path, - dst, - "--game:" + game_, - blacklist_ ? "--noTFT" : "", - }, - [=, this](int code, QProcess* process) { - if (code == 0) { - auto info = modInfoRead(name); - emit installedMod(name, info); - } - setState(CSLOLState::StateIdle); - }); + installFantomeZips({path}); } } @@ -704,3 +673,151 @@ void CSLOLToolsImpl::runDiagInternal(bool internal_once) { }); process->start(prog_ + DIAG_TOOL_EXE, QStringList{"d"}); } + +void CSLOLToolsImpl::installFantomeZips(QStringList paths) { + if (state_ == CSLOLState::StateIdle && !paths.isEmpty()) { + setState(CSLOLState::StateBusy); + fantomeInstallQueue_ = paths; + fantomeInstallConflictName_ = ""; + fantomeInstallConflictPath_ = ""; + processFantomeInstallQueue(); + } +} + +void CSLOLToolsImpl::handleDroppedUrls(QStringList urls) { + if (state_ != CSLOLState::StateIdle || urls.isEmpty()) { + return; + } + + setStatus("Processing dropped items..."); + + QStringList filesToInstall; + const QStringList nameFilters = {"*.zip", "*.fantome", "*.wad", "*.wad.client"}; + + for (const QString& urlString : urls) { + const QUrl url(urlString); + if (!url.isLocalFile()) { + continue; + } + + const QString path = url.toLocalFile(); + QFileInfo fileInfo(path); + + if (fileInfo.isDir()) { + // If the dropped folder itself is named "chromas", skip it entirely. + if (fileInfo.fileName().compare("chromas", Qt::CaseInsensitive) == 0) { + continue; + } + + QDirIterator it(path, nameFilters, QDir::Files, QDirIterator::Subdirectories); + while (it.hasNext()) { + const QString filePath = it.next(); + // If the file is inside a directory named "chromas", skip it. + // QDirIterator always uses '/', so we only need to check for that separator. + if (filePath.contains("/chromas/", Qt::CaseInsensitive)) { + continue; + } + filesToInstall.append(filePath); + } + } else if (fileInfo.isFile()) { + // This logic remains the same for individual files. + if (nameFilters.contains("*." + fileInfo.completeSuffix())) { + filesToInstall.append(path); + } + } + } + + if (!filesToInstall.isEmpty()) { + installFantomeZips(filesToInstall); + } else { + setStatus("No valid mod files found in dropped items."); + } +} + +void CSLOLToolsImpl::processFantomeInstallQueue() { + if (fantomeInstallQueue_.isEmpty()) { + setStatus("Finished installing mods."); + setState(CSLOLState::StateIdle); + return; + } + + auto path = fantomeInstallQueue_.takeFirst(); + setStatus("Installing: " + path); + + auto name = QFileInfo(path) + .fileName() + .replace(".zip", "") + .replace(".fantome", "") + .replace(".wad", "") + .replace(".client", ""); + auto dst = prog_ + "/installed/" + name; + + if (QDir(dst).exists()) { + fantomeInstallConflictName_ = name; + fantomeInstallConflictPath_ = path; + emit conflictDetected(name, path); + // We stop here and wait for resolveConflict to be called + return; + } + + runTool( + { + "import", + path, + dst, + "--game:" + game_, + blacklist_ ? "--noTFT" : "", + }, + [=, this](int code, QProcess* process) { + if (code == 0) { + auto info = modInfoRead(name); + emit installedMod(name, info); + } + // Process the next file in the queue + processFantomeInstallQueue(); + }); +} + +void CSLOLToolsImpl::resolveConflict(bool overwrite) { + if (fantomeInstallConflictName_.isEmpty()) { + // Should not happen, but as a safeguard + processFantomeInstallQueue(); + return; + } + + auto name = fantomeInstallConflictName_; + auto path = fantomeInstallConflictPath_; + auto dst = prog_ + "/installed/" + name; + + // Clear conflict state + fantomeInstallConflictName_ = ""; + fantomeInstallConflictPath_ = ""; + + if (!overwrite) { + setStatus("Skipping: " + name); + processFantomeInstallQueue(); // Skip to the next file + return; + } + + setStatus("Overwriting: " + name); + runTool( + { + "import", + path, + dst, + "--game:" + game_, + blacklist_ ? "--noTFT" : "", + }, + [=, this](int code, QProcess* process) { + if (code == 0) { + // The mod was overwritten, so we need to tell the UI to refresh. + QJsonObject mods; + for (auto const& modName : modList()) { + mods.insert(modName, modInfoRead(modName)); + } + emit refreshed(mods); + } + // Process the next file in the queue + processFantomeInstallQueue(); + }); +} diff --git a/src/CSLOLToolsImpl.h b/src/CSLOLToolsImpl.h index c0ac0913..e50a917b 100644 --- a/src/CSLOLToolsImpl.h +++ b/src/CSLOLToolsImpl.h @@ -52,6 +52,8 @@ class CSLOLToolsImpl : public QObject { void updatedMods(QJsonArray mods); void reportError(QString name, QString message, QString stack_trace); + void conflictDetected(QString modName, QString newPath); + public slots: void changeLeaguePath(QString newLeaguePath); void changeBlacklist(bool blacklist); @@ -73,6 +75,10 @@ public slots: void removeModWads(QString modFileName, QJsonArray wadNames); void addModWad(QString modFileName, QString wad, bool removeUnknownNames); + void installFantomeZips(QStringList paths); + void handleDroppedUrls(QStringList urls); + void resolveConflict(bool overwrite); + CSLOLToolsImpl::CSLOLState getState(); QString getLeaguePath(); @@ -108,6 +114,11 @@ public slots: void runPatcher(QStringList args); void runTool(QStringList args, std::function handle); void runDiagInternal(bool internal_once); + + QStringList fantomeInstallQueue_; + QString fantomeInstallConflictName_; + QString fantomeInstallConflictPath_; + void processFantomeInstallQueue(); }; #endif // QMODMANAGERWORKER_H diff --git a/src/qml/CSLOLDialogConflict.qml b/src/qml/CSLOLDialogConflict.qml new file mode 100644 index 00000000..5c03c5c6 --- /dev/null +++ b/src/qml/CSLOLDialogConflict.qml @@ -0,0 +1,44 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.12 + +Dialog { + id: dialog + modal: true + standardButtons: Dialog.NoButton + title: qsTr("Conflict Detected") + + property string modName: "" + property string newPath: "" + + signal resolve(bool overwrite) + + ColumnLayout { + Label { + text: qsTr("The mod '%1' already exists. Do you want to overwrite it?").arg(dialog.modName) + } + Label { + text: qsTr("New file: %1").arg(CSLOLUtils.fromFile(dialog.newPath)) + font.italic: true + elide: Text.ElideLeft + } + } + + footer: DialogButtonBox { + Button { + text: qsTr("Skip") + onClicked: { + dialog.resolve(false) + dialog.close() + } + } + Button { + text: qsTr("Overwrite") + onClicked: { + dialog.resolve(true) + dialog.close() + } + highlighted: true + } + } +} \ No newline at end of file diff --git a/src/qml/CSLOLDialogOpenZipFantome.qml b/src/qml/CSLOLDialogOpenZipFantome.qml index a31d9542..dbe35a5d 100644 --- a/src/qml/CSLOLDialogOpenZipFantome.qml +++ b/src/qml/CSLOLDialogOpenZipFantome.qml @@ -6,7 +6,7 @@ import Qt.labs.platform 1.0 FileDialog { id: cslolDialogOpenZipFantome visible: false - title: qsTr("Select Mod File") - fileMode: FileDialog.OpenFile - nameFilters: "Fantome Mod files (*.fantome *.zip)" -} + title: qsTr("Select Mod File(s)") + fileMode: FileDialog.OpenFiles + nameFilters: "Mod files (*.fantome *.zip *.wad *.wad.client)" +} \ No newline at end of file diff --git a/src/qml/CSLOLModsView.qml b/src/qml/CSLOLModsView.qml index 54f3520d..f6c058cb 100644 --- a/src/qml/CSLOLModsView.qml +++ b/src/qml/CSLOLModsView.qml @@ -25,7 +25,7 @@ ColumnLayout { signal modEdit(string fileName) - signal importFile(string file) + signal importFiles(var paths) signal installFantomeZip() @@ -278,7 +278,7 @@ ColumnLayout { enabled: !isBussy onDropped: function(drop) { if (drop.hasUrls && drop.urls.length > 0) { - cslolModsView.importFile(CSLOLUtils.fromFile(drop.urls[0])) + cslolModsView.importFiles(drop.urls) } } } diff --git a/src/qml/main.qml b/src/qml/main.qml index 542a9d5b..80d3e9f8 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -211,9 +211,13 @@ ApplicationWindow { onModEdit: function(fileName) { cslolTools.startEditMod(fileName) } - onImportFile: function(file) { + onImportFiles: function(urls) { if (checkGamePath()) { - cslolTools.installFantomeZip(file) + let urlStrings = [] + for (var i = 0; i < urls.length; ++i) { + urlStrings.push(urls[i].toString()) + } + cslolTools.handleDroppedUrls(urlStrings) } } onInstallFantomeZip: function() { @@ -239,7 +243,12 @@ ApplicationWindow { CSLOLDialogOpenZipFantome { id: cslolDialogOpenZipFantome onAccepted: function() { - cslolTools.installFantomeZip(CSLOLUtils.fromFile(file)) + // The 'files' property now contains a list of URLs + let paths = [] + for (var i = 0; i < files.length; ++i) { + paths.push(CSLOLUtils.fromFile(files[i])) + } + cslolTools.installFantomeZips(paths) } } @@ -314,6 +323,13 @@ ApplicationWindow { id: cslolDialogUserError } + CSLOLDialogConflict { + id: cslolDialogConflict + onResolve: function(overwrite) { + cslolTools.resolveConflict(overwrite) + } + } + CSLOLDialogUpdate { id: cslolDialogUpdate enableUpdates: settings.enableUpdates @@ -337,6 +353,11 @@ ApplicationWindow { cslolToolBar.saveProfileAndRun(true) } } + onConflictDetected: function(modName, newPath) { + cslolDialogConflict.modName = modName + cslolDialogConflict.newPath = newPath + cslolDialogConflict.open() + } onModDeleted: {} onInstalledMod: function(fileName, infoData) { cslolModsView.addMod(fileName, infoData, false) diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index 505892e8..0454ccdf 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -15,6 +15,7 @@ CSLOLDialogNewModRAWFolder.qml CSLOLDialogSaveZipFantome.qml CSLOLDialogEditMod.qml + CSLOLDialogConflict.qml icon.png CSLOLDialogUpdate.qml qtquickcontrols2.conf