From 4e5710c5eb683f1143dcb168b355bf0f8f30fff6 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Fri, 17 Apr 2026 00:13:32 -0700 Subject: [PATCH 1/6] feat: support multiple mod template URLs --- README.md | 7 ++++++- res/config/p2ce.json | 5 ++++- src/GameConfig.cpp | 12 ++++++++++-- src/GameConfig.h | 5 +++-- src/Window.cpp | 17 +++++++++++++---- src/Window.h | 4 ++-- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bbf5d00..4cc0a38 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,12 @@ Here is an example config file that may be loaded into the SDK launcher. "window_height": 300, // Optional, holds the download URL of the mod template for the game (must point to a zip file) // For reference, this is the P2CE template mod download URL: - "mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + //"mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + // OR it can be a JSON object if there are multiple mod templates: + "mod_template_url": { + "Full Support (HL2/P1/P2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + "Lite Support (Portal 2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/feat/lite.zip" + }, // Optional, the default is false (enables P2CE-style addons) "supports_p2ce_addons": false, // Sections hold titled groups of buttons diff --git a/res/config/p2ce.json b/res/config/p2ce.json index 4e61e81..a4909f2 100644 --- a/res/config/p2ce.json +++ b/res/config/p2ce.json @@ -2,7 +2,10 @@ "game_default": "p2ce", "window_width": 308, "window_height": 621, - "mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + "mod_template_url": { + "Full Support (HL2/P1/P2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + "Lite Support (Portal 2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/feat/lite.zip" + }, "supports_p2ce_addons": true, "sections": [ { diff --git a/src/GameConfig.cpp b/src/GameConfig.cpp index 5026570..95b03f2 100644 --- a/src/GameConfig.cpp +++ b/src/GameConfig.cpp @@ -76,8 +76,16 @@ std::optional GameConfig::parse(const QString& path) { gameConfig.windowHeight = configObject["window_height"].toInt(DEFAULT_WINDOW_HEIGHT); } - if (configObject.contains("mod_template_url") && configObject["mod_template_url"].isString()) { - gameConfig.modTemplateURL = configObject["mod_template_url"].toString(); + if (configObject.contains("mod_template_url")) { + if (configObject["mod_template_url"].isString()) { + gameConfig.modTemplateURL["Mod Template"] = configObject["mod_template_url"].toString(); + } else if (configObject["mod_template_url"].isObject()) { + for (const auto& modTemplateObject = configObject["mod_template_url"].toObject(); const auto& [desc, url] : modTemplateObject.asKeyValueRange()) { + if (url.isString()) { + gameConfig.modTemplateURL[desc.toString()] = url.toString(); + } + } + } } if (configObject.contains("supports_p2ce_addons") && configObject["supports_p2ce_addons"].isBool()) { diff --git a/src/GameConfig.h b/src/GameConfig.h index 8d50277..6e4e072 100644 --- a/src/GameConfig.h +++ b/src/GameConfig.h @@ -2,6 +2,7 @@ #include #include +#include #include constexpr int DEFAULT_WINDOW_WIDTH = 256; @@ -52,7 +53,7 @@ class GameConfig { [[nodiscard]] int getWindowHeight() const { return this->windowHeight; } - [[nodiscard]] const QString& getModTemplateURL() const { return this->modTemplateURL; } + [[nodiscard]] const QMap& getModTemplateURL() const { return this->modTemplateURL; } [[nodiscard]] bool supportsP2CEAddons() const { return this->p2ceAddonsSupported; } @@ -66,7 +67,7 @@ class GameConfig { bool usesLegacyBinDir = false; int windowWidth = DEFAULT_WINDOW_WIDTH; int windowHeight = DEFAULT_WINDOW_HEIGHT; - QString modTemplateURL; + QMap modTemplateURL; bool p2ceAddonsSupported = false; QList
sections; diff --git a/src/Window.cpp b/src/Window.cpp index 8b618aa..1527515 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -129,9 +129,7 @@ Window::Window(QWidget* parent) // Utilities menu auto* utilitiesMenu = this->menuBar()->addMenu(tr("Utilities")); - this->utilities_createNewMod = utilitiesMenu->addAction(this->style()->standardIcon(QStyle::SP_FileIcon), tr("Create New Mod"), [this] { - NewModDialog::open(::getRootPath(this->configUsingLegacyBinDir), this->configModTemplateURL, this); - }); + this->utilities_createNewMod = utilitiesMenu->addMenu(this->style()->standardIcon(QStyle::SP_FileIcon), tr("Create New Mod")); this->utilities_createNewAddon = utilitiesMenu->addAction(this->style()->standardIcon(QStyle::SP_FileIcon), tr("Create New Addon"), [this] { QString gameRoot; @@ -225,7 +223,18 @@ void Window::loadGameConfig(const QString& path) { this->configUsingLegacyBinDir = gameConfig->getUsesLegacyBinDir(); this->configModTemplateURL = gameConfig->getModTemplateURL(); - this->utilities_createNewMod->setDisabled(this->configModTemplateURL.isEmpty()); + + this->utilities_createNewMod->clear(); + if (this->configModTemplateURL.isEmpty()) { + this->utilities_createNewMod->setDisabled(true); + } else { + for (const auto& [desc, url] : this->configModTemplateURL.asKeyValueRange()) { + this->utilities_createNewMod->addAction(desc, [this, url] { + NewModDialog::open(::getRootPath(this->configUsingLegacyBinDir), url, this); + }); + } + } + this->utilities_createNewAddon->setDisabled(!gameConfig->supportsP2CEAddons()); auto recentConfigs = Options::get(STR_RECENT_CONFIGS); diff --git a/src/Window.h b/src/Window.h index 5cba48c..54071ee 100644 --- a/src/Window.h +++ b/src/Window.h @@ -32,13 +32,13 @@ class Window : public QMainWindow { private: QString gameDefault; bool configUsingLegacyBinDir; - QString configModTemplateURL; + QMap configModTemplateURL; QMenu* recent; QAction* config_loadDefault; QAction* game_overrideGame; QAction* game_resetToDefault; - QAction* utilities_createNewMod; + QMenu* utilities_createNewMod; QAction* utilities_createNewAddon; QWidget* main; From 204b3be7e7162fb797fde3bba1607695c09e66e4 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Fri, 17 Apr 2026 00:13:50 -0700 Subject: [PATCH 2/6] feat: add macOS platform check --- src/GameConfig.cpp | 3 +++ src/GameConfig.h | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GameConfig.cpp b/src/GameConfig.cpp index 95b03f2..a880a02 100644 --- a/src/GameConfig.cpp +++ b/src/GameConfig.cpp @@ -29,6 +29,9 @@ GameConfig::OS GameConfig::osFromString(const QString& string) { if (string.contains("linux")) { out |= static_cast(LINUX); } + if (string.contains("macos")) { + out |= static_cast(MACOS); + } if (out == static_cast(NONE)) { out = static_cast(ALL); } diff --git a/src/GameConfig.h b/src/GameConfig.h index 6e4e072..16b5cf4 100644 --- a/src/GameConfig.h +++ b/src/GameConfig.h @@ -23,7 +23,8 @@ class GameConfig { NONE = 0, WINDOWS = 1 << 0, LINUX = 1 << 1, - ALL = WINDOWS | LINUX, + MACOS = 1 << 2, + ALL = WINDOWS | LINUX | MACOS, }; [[nodiscard]] static OS osFromString(const QString& string); From b71da729d99e3709a6ab78e1d8a9926e76bcd3e1 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Fri, 17 Apr 2026 00:14:36 -0700 Subject: [PATCH 3/6] feat: add button to select parent mod folder --- src/NewModDialog.cpp | 35 +++++++++++++++++++++++++---------- src/NewModDialog.h | 2 +- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/NewModDialog.cpp b/src/NewModDialog.cpp index 76a3dfe..a028fae 100644 --- a/src/NewModDialog.cpp +++ b/src/NewModDialog.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include "Steam.h" @@ -155,10 +157,25 @@ NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* par this->parentFolder->addItem(tr("Custom Location")); layout->addRow(tr("Install Location"), this->parentFolder); - auto* parentFolderCustomLabel = new QLabel{tr("Custom Location"), this}; - this->parentFolderCustom = new QLineEdit{this}; - this->parentFolderCustom->setPlaceholderText(tr("path/to/mod/parent/folder")); - layout->addRow(parentFolderCustomLabel, this->parentFolderCustom); + auto* parentFolderCustomParent = new QWidget{this}; + auto* parentFolderCustomLayout = new QHBoxLayout{parentFolderCustomParent}; + parentFolderCustomLayout->setSpacing(4); + parentFolderCustomLayout->setContentsMargins(0, 0, 0, 0); + + this->parentFolderCustomPath = new QLineEdit{parentFolderCustomParent}; + parentFolderCustomLayout->addWidget(this->parentFolderCustomPath); + + auto* parentFolderCustomSearch = new QPushButton{parentFolderCustomParent}; + parentFolderCustomSearch->setIcon(this->style()->standardIcon(QStyle::SP_DirOpenIcon)); + parentFolderCustomLayout->addWidget(parentFolderCustomSearch); + + QObject::connect(parentFolderCustomSearch, &QPushButton::clicked, this, [this] { + if (const auto path = QFileDialog::getExistingDirectory(this, tr("Select Parent Folder")); !path.isEmpty()) { + this->parentFolderCustomPath->setText(path); + } + }); + + layout->addRow(tr("Custom Location"), parentFolderCustomParent); this->modID = new QLineEdit{this}; this->modID->setPlaceholderText(tr("For example: p2ce, revolution, portal2")); @@ -179,12 +196,10 @@ NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* par this->network = new QNetworkAccessManager{this}; // We want the custom input to be invisible unless the combo box is on the custom option - parentFolderCustomLabel->hide(); - this->parentFolderCustom->hide(); - QObject::connect(this->parentFolder, &QComboBox::currentIndexChanged, this, [this, knowsSourcemodsDirLocation, parentFolderCustomLabel](int index) { + layout->setRowVisible(parentFolderCustomParent, false); + QObject::connect(this->parentFolder, &QComboBox::currentIndexChanged, this, [knowsSourcemodsDirLocation, layout, parentFolderCustomParent](int index) { const int customIndex = knowsSourcemodsDirLocation ? 2 : 1; - parentFolderCustomLabel->setVisible(index == customIndex); - this->parentFolderCustom->setVisible(index == customIndex); + layout->setRowVisible(parentFolderCustomParent, index == customIndex); }); // Connect ok/cancel buttons to download stuff @@ -277,7 +292,7 @@ QString NewModDialog::getModInstallDirParent() const { return this->gameRoot; default: case 2: - return this->parentFolderCustom->text(); + return this->parentFolderCustomPath->text(); } } diff --git a/src/NewModDialog.h b/src/NewModDialog.h index f9e6f4c..fe5313a 100644 --- a/src/NewModDialog.h +++ b/src/NewModDialog.h @@ -28,7 +28,7 @@ public Q_SLOTS: QString downloadURL; QComboBox* parentFolder; - QLineEdit* parentFolderCustom; + QLineEdit* parentFolderCustomPath; QLineEdit* modID; QCheckBox* addShortcutOnDesktop; QProgressBar* downloadProgress; From 6bcf7689076123eebe764aebc6f1c83d604bd8da Mon Sep 17 00:00:00 2001 From: craftablescience Date: Fri, 17 Apr 2026 00:18:50 -0700 Subject: [PATCH 4/6] fix: always open created mod folder --- src/NewModDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NewModDialog.cpp b/src/NewModDialog.cpp index a028fae..2f0f643 100644 --- a/src/NewModDialog.cpp +++ b/src/NewModDialog.cpp @@ -271,9 +271,9 @@ NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* par // If installing to sourcemods, tell user they will need to restart steam if (this->parentFolder->count() == 3 && this->parentFolder->currentIndex() == 0) { QMessageBox::information(this, tr("Info"), tr("Your mod has been installed to Steam's SourceMods folder, which means it will show up in your Steam library! This requires you to restart Steam once.")); - QDesktopServices::openUrl({QString("file:///") + modInstallDir}); } + QDesktopServices::openUrl(QUrl::fromLocalFile(modInstallDir)); this->accept(); }); }); From 43925bb566a2a01cb5012c28a18b3afe2a51a3d2 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Fri, 17 Apr 2026 00:19:37 -0700 Subject: [PATCH 5/6] chore: bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 366aad2..3d1520d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.25 FATAL_ERROR) # Create project project(sdk_launcher DESCRIPTION "An SDK launcher for Strata Source engine games" - VERSION "1.0.1" + VERSION "1.1.0" HOMEPAGE_URL "https://github.com/StrataSource/sdk-launcher") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) From 7e281357fd576ce1c8071a94fe1df000d66a3717 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Fri, 17 Apr 2026 00:27:10 -0700 Subject: [PATCH 6/6] fix: outdated qt compile --- src/GameConfig.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/GameConfig.cpp b/src/GameConfig.cpp index a880a02..1ee4409 100644 --- a/src/GameConfig.cpp +++ b/src/GameConfig.cpp @@ -83,10 +83,11 @@ std::optional GameConfig::parse(const QString& path) { if (configObject["mod_template_url"].isString()) { gameConfig.modTemplateURL["Mod Template"] = configObject["mod_template_url"].toString(); } else if (configObject["mod_template_url"].isObject()) { - for (const auto& modTemplateObject = configObject["mod_template_url"].toObject(); const auto& [desc, url] : modTemplateObject.asKeyValueRange()) { - if (url.isString()) { - gameConfig.modTemplateURL[desc.toString()] = url.toString(); - } + for ( + const auto& modTemplateObject = configObject["mod_template_url"].toObject().toVariantMap(); + const auto& [desc, url] : modTemplateObject.asKeyValueRange() + ) { + gameConfig.modTemplateURL[desc] = url.toString(); } } }