From 9b5595363402d1258324fcedf6a7babca2fc045b Mon Sep 17 00:00:00 2001 From: craftablescience Date: Thu, 8 Aug 2024 21:19:11 -0400 Subject: [PATCH] feat: add mod creation dialog --- .gitmodules | 3 + CMakeLists.txt | 16 +- README.md | 7 +- ext/miniz | 1 + res/config/p2ce.json | 1 + src/GameConfig.cpp | 24 +-- src/GameConfig.h | 9 +- src/NewModDialog.cpp | 363 +++++++++++++++++++++++++++++++++++++++++++ src/NewModDialog.h | 37 +++++ src/Window.cpp | 45 ++++-- src/Window.h | 4 + 11 files changed, 466 insertions(+), 44 deletions(-) create mode 100644 .gitmodules create mode 160000 ext/miniz create mode 100644 src/NewModDialog.cpp create mode 100644 src/NewModDialog.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ba8dc5b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ext/miniz"] + path = ext/miniz + url = https://github.com/richgel999/miniz diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ffbbc3..ab08588 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 "A minimal SDK launcher for Strata Source engine games" - VERSION "0.3.0" + VERSION "0.4.0" HOMEPAGE_URL "https://github.com/StrataSource/sdk-launcher") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -102,6 +102,9 @@ function(sdk_launcher_configure_target TARGET) endif() endfunction() +# miniz +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/ext/miniz") + # Qt if(WIN32 AND NOT DEFINED QT_BASEDIR) message(FATAL_ERROR "Please define your QT install dir with -DQT_BASEDIR=\"C:/your/qt6/here\"") @@ -123,7 +126,7 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Network) # Generate config header configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/Config.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/Config.h") @@ -135,6 +138,8 @@ add_executable(${PROJECT_TARGET_NAME} WIN32 "${CMAKE_CURRENT_SOURCE_DIR}/src/GameConfig.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/GameConfig.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/Main.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/NewModDialog.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/NewModDialog.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/Window.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/Window.h") @@ -142,13 +147,16 @@ sdk_launcher_configure_target(${PROJECT_TARGET_NAME}) target_link_libraries( ${PROJECT_TARGET_NAME} PRIVATE + miniz Qt::Core Qt::Gui - Qt::Widgets) + Qt::Widgets + Qt::Network) target_include_directories( ${PROJECT_TARGET_NAME} PRIVATE "${QT_INCLUDE}" "${QT_INCLUDE}/QtCore" "${QT_INCLUDE}/QtGui" - "${QT_INCLUDE}/QtWidgets") + "${QT_INCLUDE}/QtWidgets" + "${QT_INCLUDE}/QtNetwork") diff --git a/README.md b/README.md index 99d3efd..59cfa50 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,11 @@ Here is an example config file that may be loaded into the SDK launcher. "game_icon": "${ROOT}/${GAME}/resource/game.ico", // Optional, the default is false (set this to true if the SDK launcher is inside bin/ instead of bin/${PLATFORM}/) "uses_legacy_bin_dir": false, - // Optional, the default is 256 (changes the fixed width of the window) - "window_width": 256, - // Optional, the default is 450 (changes the fixed height of the window) + // Optional, the default is 450 (changes the default height of the window) "window_height": 450, + // 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", // Sections hold titled groups of buttons "sections": [ { diff --git a/ext/miniz b/ext/miniz new file mode 160000 index 0000000..1ff82be --- /dev/null +++ b/ext/miniz @@ -0,0 +1 @@ +Subproject commit 1ff82be7d67f5c2f8b5497f538eea247861e0717 diff --git a/res/config/p2ce.json b/res/config/p2ce.json index 51e5199..eb66130 100644 --- a/res/config/p2ce.json +++ b/res/config/p2ce.json @@ -1,6 +1,7 @@ { "game_default": "p2ce", "window_height": 575, + "mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", "sections": [ { "name": "Game", diff --git a/src/GameConfig.cpp b/src/GameConfig.cpp index 7239236..98eea7b 100644 --- a/src/GameConfig.cpp +++ b/src/GameConfig.cpp @@ -66,14 +66,14 @@ std::optional GameConfig::parse(const QString& path) { gameConfig.usesLegacyBinDir = configObject["uses_legacy_bin_dir"].toBool(); } - if (configObject.contains("window_width")) { - gameConfig.windowWidth = configObject["window_width"].toInt(DEFAULT_WINDOW_WIDTH); - } - if (configObject.contains("window_height")) { 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("sections") || !configObject["sections"].isArray()) { return std::nullopt; } @@ -141,9 +141,6 @@ std::optional GameConfig::parse(const QString& path) { } void GameConfig::setVariable(const QString& variable, const QString& replacement) { - if (this->finalized) { - return; - } const auto setVar = [&variable, &replacement](QString& str) { str.replace(QString("${%1}").arg(variable), replacement); }; @@ -160,16 +157,3 @@ void GameConfig::setVariable(const QString& variable, const QString& replacement } } } - -void GameConfig::finalize() { - for (auto& section : this->sections) { - for (auto& entry : section.entries) { - if (entry.type == ActionType::COMMAND || entry.type == ActionType::DIRECTORY) { - if (auto cleanPath = QDir::cleanPath(entry.action); !cleanPath.isEmpty()) { - entry.action = cleanPath; - } - } - } - } - this->finalized = true; -} diff --git a/src/GameConfig.h b/src/GameConfig.h index 9487f92..77ce97c 100644 --- a/src/GameConfig.h +++ b/src/GameConfig.h @@ -48,24 +48,23 @@ class GameConfig { [[nodiscard]] bool getUsesLegacyBinDir() const { return this->usesLegacyBinDir; } - [[nodiscard]] int getWindowWidth() const { return this->windowWidth; } + [[nodiscard]] int getWindowWidth() const { return DEFAULT_WINDOW_WIDTH; } [[nodiscard]] int getWindowHeight() const { return this->windowHeight; } + [[nodiscard]] const QString& getModTemplateURL() const { return this->modTemplateURL; } + [[nodiscard]] const QList
& getSections() const { return this->sections; } void setVariable(const QString& variable, const QString& replacement); - void finalize(); - private: QString gameDefault; QString gameIcon; bool usesLegacyBinDir = false; - int windowWidth = DEFAULT_WINDOW_WIDTH; int windowHeight = DEFAULT_WINDOW_HEIGHT; + QString modTemplateURL; QList
sections; - bool finalized = false; GameConfig() = default; }; diff --git a/src/NewModDialog.cpp b/src/NewModDialog.cpp new file mode 100644 index 0000000..43ab4a8 --- /dev/null +++ b/src/NewModDialog.cpp @@ -0,0 +1,363 @@ +#include "NewModDialog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #include +#else + #include +#endif + +namespace { + +/// Copied from sourcepp +[[nodiscard]] QString getSourceModsDirLocationHelper() { + std::filesystem::path steamLocation; + std::error_code ec; + +#ifdef _WIN32 + { + // 16383 being the maximum length of a path + static constexpr DWORD STEAM_LOCATION_MAX_SIZE = 16383; + std::unique_ptr steamLocationData{new char[STEAM_LOCATION_MAX_SIZE]}; + + HKEY steam; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Valve\Steam)", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &steam) != ERROR_SUCCESS) { + return ""; + } + + DWORD steamLocationSize = STEAM_LOCATION_MAX_SIZE; + if (RegQueryValueExA(steam, "InstallPath", nullptr, nullptr, reinterpret_cast(steamLocationData.get()), &steamLocationSize) != ERROR_SUCCESS) { + return ""; + } + + RegCloseKey(steam); + steamLocation = steamLocationSize > 0 ? std::string(steamLocationData.get(), steamLocationSize - 1) : ""; + } +#else + { + std::filesystem::path home{std::getenv("HOME")}; +#ifdef __APPLE__ + steamLocation = home / "Library" / "Application Support" / "Steam"; +#else + // Snap install takes priority, the .steam symlink may exist simultaneously with Snap installs + steamLocation = home / "snap" / "steam" / "common" / ".steam" / "steam"; + + if (!std::filesystem::exists(steamLocation, ec)) { + // Use the regular install path + steamLocation = home / ".steam" / "steam"; + } +#endif + } + + if (!std::filesystem::exists(steamLocation, ec)) { + std::string location; + std::filesystem::path d{"cwd/steamclient64.dll"}; + for (const auto& entry : std::filesystem::directory_iterator{"/proc/"}) { + if (std::filesystem::exists(entry / d, ec)) { + ec.clear(); + const auto s = std::filesystem::read_symlink(entry.path() / "cwd", ec); + if (ec) { + continue; + } + location = s.string(); + break; + } + } + if (location.empty()) { + return ""; + } else { + steamLocation = location; + } + } +#endif + + if (auto sourceModPath = (steamLocation / "steamapps" / "sourcemods").string(); std::filesystem::exists(sourceModPath, ec)) { + return sourceModPath.c_str(); + } + return ""; +} + +[[nodiscard]] const QString& getSourceModsDirLocation() { + static QString location = ::getSourceModsDirLocationHelper(); + return location; +} + +[[nodiscard]] QString join(const QStringList& list, const QString& separator) { + if (list.isEmpty()) { + return ""; + } + QString result = list.first(); + for (int i = 1; i < list.size(); ++i) { + result += separator + list[i]; + } + return result; +} + +[[nodiscard]] bool writeDataToFile(const QByteArray& data, const QString& path) { + std::filesystem::create_directories(std::filesystem::path{path.toLocal8Bit().constData()}.parent_path()); + + QFile file{path}; + file.open(QIODevice::WriteOnly); + if (!file.isOpen()) { + return false; + } + file.write(data); + file.close(); + return true; +} + +[[nodiscard]] bool extractZIP(const QByteArray& zip, const QString& outputDir) { + mz_zip_archive zipArchive; + std::memset(&zipArchive, 0, sizeof(zipArchive)); + + if (!mz_zip_reader_init_mem(&zipArchive, zip.data(), zip.size(), 0)) { + return false; + } + + // Collect file data + QMap files; + QStringList filePaths; + const int fileCount = mz_zip_reader_get_num_files(&zipArchive); + for (int i = 0; i < fileCount; i++) { + if (mz_zip_reader_is_file_a_directory(&zipArchive, i)) { + continue; + } + + mz_zip_archive_file_stat fileStat; + if (!mz_zip_reader_file_stat(&zipArchive, i, &fileStat)) { + return false; + } + + files[i] = fileStat; + + filePaths.push_back(fileStat.m_filename); + filePaths.back().replace('\\', '/'); + } + + // Find root dir(s) using probably the slowest algorithm ever + QList pathSplits; + for (const auto& path : filePaths) { + pathSplits.push_back(path.split('/')); + } + QStringList rootDirList; + while (true) { + bool allTheSame = true; + QString first = pathSplits[0][0]; + for (const auto& path : pathSplits) { + if (path.length() == 1) { + allTheSame = false; + break; + } + if (path[0] != first) { + allTheSame = false; + break; + } + } + if (!allTheSame) { + break; + } + rootDirList.push_back(std::move(first)); + for (auto& path : pathSplits) { + path.pop_front(); + } + } + const qsizetype rootDirLen = ::join(rootDirList, "/").length(); + + // Write file without root dir(s) + for (auto file = files.cbegin(); file != files.cend(); ++file) { + QByteArray fileData; + fileData.resize(file.value().m_uncomp_size); + if (!mz_zip_reader_extract_to_mem(&zipArchive, file.key(), fileData.data(), fileData.size(), 0)) { + return false; + } + + if (!::writeDataToFile(fileData, outputDir + QDir::separator() + QString{file.value().m_filename}.sliced(rootDirLen))) { + return false; + } + } + + mz_zip_reader_end(&zipArchive); + return true; +} + +} // namespace + +NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* parent) + : QDialog(parent) + , gameRoot(std::move(gameRoot_)) + , downloadURL(std::move(downloadURL_)) { + // Check for sourcemods + const bool knowsSourcemodsDirLocation = !::getSourceModsDirLocation().isEmpty(); + + // Window setup + this->setModal(true); + this->setWindowTitle(tr("New Mod")); + this->setMinimumWidth(350); + + // Create UI elements + auto* layout = new QFormLayout{this}; + + this->parentFolder = new QComboBox{this}; + if (knowsSourcemodsDirLocation) { + this->parentFolder->addItem("Steam's SourceMods Folder"); + } + this->parentFolder->addItem(tr("Game Folder")); + 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); + + this->modID = new QLineEdit{this}; + this->modID->setPlaceholderText("For example: p2ce, revolution, portal2"); + layout->addRow(tr("Mod ID"), this->modID); + + this->addShortcutOnDesktop = new QCheckBox{this}; + this->addShortcutOnDesktop->setCheckState(Qt::Checked); + layout->addRow(tr("Create Desktop Shortcut"), this->addShortcutOnDesktop); + + auto* buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this}; + layout->addWidget(buttonBox); + + this->downloadProgress = new QProgressBar{this}; + this->downloadProgress->setFormat(tr("%vkb / %mkb")); + layout->addWidget(downloadProgress); + + // Create network + 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) { + const int customIndex = knowsSourcemodsDirLocation ? 2 : 1; + parentFolderCustomLabel->setVisible(index == customIndex); + this->parentFolderCustom->setVisible(index == customIndex); + }); + + // Connect ok/cancel buttons to download stuff + buttonBox->show(); + this->downloadProgress->hide(); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, [this, buttonBox] { + // Validate mod ID + if (this->modID->text().trimmed().isEmpty()) { + QMessageBox::critical(this, tr("Incorrect Input"), tr("Mod ID must not be empty.")); + return; + } + + const auto modInstallDir = this->getModInstallDir(); + + // Validate install location + if (modInstallDir.isEmpty() || !std::filesystem::exists(this->getModInstallDirParent().toLocal8Bit().constData())) { + QMessageBox::critical(this, tr("Incorrect Input"), tr("Install location does not exist.")); + return; + } + if (std::filesystem::exists(modInstallDir.toLocal8Bit().constData())) { + QMessageBox::critical(this, tr("Incorrect Input"), tr("A folder with the name of the mod ID already exists at this install location.")); + return; + } + + // Initiate the download + auto* reply = this->network->get(QNetworkRequest{QUrl(this->downloadURL)}); + + // Connect download progress to progress bar, measured in kb + QObject::connect(reply, &QNetworkReply::downloadProgress, this, [this, buttonBox](qint64 recv, qint64 total) { + buttonBox->hide(); + this->downloadProgress->show(); + if (total < 0) { + this->downloadProgress->setRange(0, 0); + this->downloadProgress->setTextVisible(false); + } else { + this->downloadProgress->setRange(0, total / 1000); + this->downloadProgress->setValue(recv / 1000); + this->downloadProgress->setTextVisible(true); + } + }); + + // Connect finished downloading response to the rest of the processing code + QObject::connect(reply, &QNetworkReply::finished, this, [this, modInstallDir, reply] { + // Check for a download error + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::critical(this, tr("Error"), tr("An error occurred while downloading: %1").arg(reply->errorString())); + this->accept(); + return; + } + + // Extract zip contents in memory to destination + if (!::extractZIP(reply->readAll(), modInstallDir)) { + QMessageBox::critical(this, tr("Error"), tr("An error occurred while extracting the mod template.")); + this->accept(); + return; + } + + // Create desktop shortcut + if (this->addShortcutOnDesktop->isChecked()) { +#ifdef _WIN32 + QFile::link(modInstallDir, QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + QDir::separator() + this->modID->text().trimmed() + ".lnk"); +#else + QFile::link(modInstallDir, QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + QDir::separator() + this->modID->text().trimmed()); +#endif + } + + // 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.")); + } + + this->accept(); + }); + }); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &NewModDialog::reject); +} + +QString NewModDialog::getModInstallDirParent() const { + auto selectedIndex = this->parentFolder->currentIndex(); + if (this->parentFolder->count() == 2) { + selectedIndex++; + } + switch (selectedIndex) { + case 0: + return ::getSourceModsDirLocation(); + case 1: + return this->gameRoot; + case 2: + return this->parentFolderCustom->text(); + } + return ""; +} + +QString NewModDialog::getModInstallDir() const { + return this->getModInstallDirParent() + QDir::separator() + this->modID->text().trimmed(); +} + +void NewModDialog::open(QString gameRoot, QString downloadURL, QWidget* parent) { + auto* dialog = new NewModDialog{std::move(gameRoot), std::move(downloadURL), parent}; + dialog->exec(); + dialog->deleteLater(); +} + +void NewModDialog::reject() { + if (!this->downloadProgress->isVisible()) { + QDialog::reject(); + } +} diff --git a/src/NewModDialog.h b/src/NewModDialog.h new file mode 100644 index 0000000..f9e6f4c --- /dev/null +++ b/src/NewModDialog.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +class QCheckBox; +class QComboBox; +class QLineEdit; +class QNetworkAccessManager; +class QProgressBar; + +class NewModDialog : public QDialog { + Q_OBJECT; + +public: + NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* parent = nullptr); + + [[nodiscard]] QString getModInstallDirParent() const; + + [[nodiscard]] QString getModInstallDir() const; + + static void open(QString gameRoot, QString downloadURL, QWidget* parent = nullptr); + +public Q_SLOTS: + void reject() override; + +private: + QString gameRoot; + QString downloadURL; + + QComboBox* parentFolder; + QLineEdit* parentFolderCustom; + QLineEdit* modID; + QCheckBox* addShortcutOnDesktop; + QProgressBar* downloadProgress; + + QNetworkAccessManager* network = nullptr; +}; diff --git a/src/Window.cpp b/src/Window.cpp index 6c315fb..7933a87 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,7 @@ #include "Config.h" #include "GameConfig.h" +#include "NewModDialog.h" #ifdef _WIN32 #include @@ -40,7 +42,7 @@ void clearLayout(QLayout* layout, bool deleteWidgets = true) { } #ifdef _WIN32 -QIcon getExecutableIcon(const QString& path) { +[[nodiscard]] QIcon getExecutableIcon(const QString& path) { HICON hIcon; if (SHDefExtractIconA(path.toLocal8Bit().constData(), 0, 0, &hIcon, nullptr, 16) != S_OK) { return QIcon{}; @@ -51,13 +53,27 @@ QIcon getExecutableIcon(const QString& path) { } #endif +[[nodiscard]] QString getRootPath(bool usesLegacyBinDir) { + QString rootPath = QCoreApplication::applicationDirPath(); + if (usesLegacyBinDir) { + rootPath += "/.."; + } else { + rootPath += "/../.."; + } + if (auto cleanPath = QDir::cleanPath(rootPath); !cleanPath.isEmpty()) { + return cleanPath; + } + return rootPath; +} + constexpr std::string_view STR_RECENT_CONFIGS = "str_recent_configs"; constexpr std::string_view STR_GAME_OVERRIDE = "str_game_override"; } // namespace Window::Window(QWidget* parent) - : QMainWindow(parent) { + : QMainWindow(parent) + , configUsingLegacyBinDir(false) { this->setWindowTitle(PROJECT_NAME.data()); // Default settings (recent configs are set later on) @@ -109,6 +125,13 @@ 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); + }); + // Help menu auto* helpMenu = this->menuBar()->addMenu(tr("Help")); @@ -169,7 +192,9 @@ void Window::loadGameConfig(const QString& path) { return; } - this->setFixedSize(gameConfig->getWindowWidth(), gameConfig->getWindowHeight()); + this->configUsingLegacyBinDir = gameConfig->getUsesLegacyBinDir(); + this->configModTemplateURL = gameConfig->getModTemplateURL(); + this->utilities_createNewMod->setDisabled(this->configModTemplateURL.isEmpty()); QSettings settings; auto recentConfigs = settings.value(STR_RECENT_CONFIGS).value(); @@ -184,12 +209,7 @@ void Window::loadGameConfig(const QString& path) { this->regenerateRecentConfigs(); // Set ${ROOT} - QString rootPath = QCoreApplication::applicationDirPath(); - if (gameConfig->getUsesLegacyBinDir()) { - rootPath += "/.."; - } else { - rootPath += "/../.."; - } + const auto rootPath = ::getRootPath(this->configUsingLegacyBinDir); gameConfig->setVariable("ROOT", rootPath); // Set ${PLATFORM} @@ -230,9 +250,6 @@ void Window::loadGameConfig(const QString& path) { // Set ${STRATA_ICON} gameConfig->setVariable("STRATA_ICON", getStrataIconPath()); - // Done with variables, necessary to fix up paths - gameConfig->finalize(); - for (int i = 0; i < gameConfig->getSections().size(); i++) { auto& section = gameConfig->getSections()[i]; @@ -340,6 +357,10 @@ void Window::loadGameConfig(const QString& path) { } layout->addStretch(); + + // Set window sizing + this->resize(gameConfig->getWindowWidth(), gameConfig->getWindowHeight()); + this->setFixedWidth(gameConfig->getWindowWidth()); } void Window::regenerateRecentConfigs() { diff --git a/src/Window.h b/src/Window.h index d42c786..e370cd1 100644 --- a/src/Window.h +++ b/src/Window.h @@ -18,10 +18,14 @@ class Window : public QMainWindow { void regenerateRecentConfigs(); private: + bool configUsingLegacyBinDir; + QString configModTemplateURL; + QMenu* recent; QAction* config_loadDefault; QAction* game_resetToDefault; QAction* game_overrideGame; + QAction* utilities_createNewMod; QWidget* main; };