diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/mod_uploader/StarModUploader.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/mod_uploader/StarModUploader.cpp')
-rw-r--r-- | source/mod_uploader/StarModUploader.cpp | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/source/mod_uploader/StarModUploader.cpp b/source/mod_uploader/StarModUploader.cpp new file mode 100644 index 0000000..c8c86e6 --- /dev/null +++ b/source/mod_uploader/StarModUploader.cpp @@ -0,0 +1,394 @@ +#include <QApplication> +#include <QFileDialog> +#include <QHBoxLayout> +#include <QVBoxLayout> +#include <QProgressDialog> +#include <QMessageBox> + +#include "StarModUploader.hpp" +#include "StarFile.hpp" +#include "StarThread.hpp" +#include "StarLexicalCast.hpp" +#include "StarPackedAssetSource.hpp" +#include "StarStringConversion.hpp" + +namespace Star { + +ModUploader::ModUploader() + : QMainWindow() { + + auto selectDirectoryButton = new QPushButton("Select Mod Directory"); + m_directoryLabel = new QLabel(); + m_reloadButton = new QPushButton("Reload"); + m_nameEditor = new QLineEdit(); + m_titleEditor = new QLineEdit(); + m_authorEditor = new QLineEdit(); + m_versionEditor = new QLineEdit(); + m_descriptionEditor = new SPlainTextEdit(); + m_previewImageLabel = new QLabel(); + auto selectPreviewImageButton = new QPushButton("Select"); + m_modIdLabel = new QLabel(); + auto resetModIdButton = new QPushButton("Reset Steam Mod Information"); + auto userAgreementLabel = new QLabel("By submitting this item, you agree to the <a href=\"http://steamcommunity.com/sharedfiles/workshoplegalagreement\">workshop terms of service</a>"); + auto uploadButton = new QPushButton("Upload to Steam!"); + + m_categorySelectors.set("Armor and Clothes", new QCheckBox("Armor and Clothes")); + m_categorySelectors.set("Character Improvements", new QCheckBox("Character Improvements")); + m_categorySelectors.set("Cheats and God Items", new QCheckBox("Cheats and God Items")); + m_categorySelectors.set("Crafting and Building", new QCheckBox("Crafting and Building")); + m_categorySelectors.set("Dungeons", new QCheckBox("Dungeons")); + m_categorySelectors.set("Food and Farming", new QCheckBox("Food and Farming")); + m_categorySelectors.set("Furniture and Objects", new QCheckBox("Furniture and Objects")); + m_categorySelectors.set("In-Game Tools", new QCheckBox("In-Game Tools")); + m_categorySelectors.set("Mechanics", new QCheckBox("Mechanics")); + m_categorySelectors.set("Miscellaneous", new QCheckBox("Miscellaneous")); + m_categorySelectors.set("Musical Instruments and Songs", new QCheckBox("Musical Instruments and Songs")); + m_categorySelectors.set("NPCs and Creatures", new QCheckBox("NPCs and Creatures")); + m_categorySelectors.set("Planets and Environments", new QCheckBox("Planets and Environments")); + m_categorySelectors.set("Quests", new QCheckBox("Quests")); + m_categorySelectors.set("Species", new QCheckBox("Species")); + m_categorySelectors.set("Ships", new QCheckBox("Ships")); + m_categorySelectors.set("User Interface", new QCheckBox("User Interface")); + m_categorySelectors.set("Vehicles and Mounts", new QCheckBox("Vehicles and Mounts")); + m_categorySelectors.set("Weapons", new QCheckBox("Weapons")); + + m_modIdLabel->setOpenExternalLinks(true); + userAgreementLabel->setOpenExternalLinks(true); + + connect(selectDirectoryButton, SIGNAL(clicked()), this, SLOT(selectDirectory())); + connect(m_reloadButton, SIGNAL(clicked()), this, SLOT(loadDirectory())); + connect(selectPreviewImageButton, SIGNAL(clicked()), this, SLOT(selectPreview())); + connect(m_nameEditor, SIGNAL(editingFinished()), this, SLOT(writeMetadata())); + connect(m_titleEditor, SIGNAL(editingFinished()), this, SLOT(writeMetadata())); + connect(m_authorEditor, SIGNAL(editingFinished()), this, SLOT(writeMetadata())); + connect(m_versionEditor, SIGNAL(editingFinished()), this, SLOT(writeMetadata())); + connect(m_descriptionEditor, SIGNAL(editingFinished()), this, SLOT(writeMetadata())); + connect(resetModIdButton, SIGNAL(clicked()), this, SLOT(resetModId())); + connect(uploadButton, SIGNAL(clicked()), this, SLOT(uploadToSteam())); + + for (auto pair : m_categorySelectors) { + connect(pair.second, SIGNAL(clicked()), this, SLOT(writeMetadata())); + } + + auto loadDirectoryLayout = new QHBoxLayout(); + loadDirectoryLayout->addWidget(selectDirectoryButton); + loadDirectoryLayout->addWidget(m_directoryLabel, 1); + loadDirectoryLayout->addWidget(m_reloadButton); + + QGridLayout* editorLeftLayout = new QGridLayout(); + editorLeftLayout->addWidget(new QLabel("Name"), 0, 0); + editorLeftLayout->addWidget(m_nameEditor, 0, 1, 1, 2); + + editorLeftLayout->addWidget(new QLabel("Title"), 1, 0); + editorLeftLayout->addWidget(m_titleEditor, 1, 1, 1, 2); + + editorLeftLayout->addWidget(new QLabel("Author"), 2, 0); + editorLeftLayout->addWidget(m_authorEditor, 2, 1, 1, 2); + + editorLeftLayout->addWidget(new QLabel("Version"), 3, 0); + editorLeftLayout->addWidget(m_versionEditor, 3, 1, 1, 2); + + editorLeftLayout->addWidget(new QLabel("Description"), 4, 0); + editorLeftLayout->addWidget(m_descriptionEditor, 4, 1, 1, 2); + + editorLeftLayout->addWidget(new QLabel("Preview Image"), 5, 0); + editorLeftLayout->addWidget(m_previewImageLabel, 5, 1); + editorLeftLayout->addWidget(selectPreviewImageButton, 5, 2); + + editorLeftLayout->addWidget(new QLabel("Mod ID"), 6, 0); + editorLeftLayout->addWidget(m_modIdLabel, 6, 1); + editorLeftLayout->addWidget(resetModIdButton, 6, 2); + + editorLeftLayout->addWidget(userAgreementLabel, 7, 0, 1, 3, Qt::AlignCenter); + editorLeftLayout->addWidget(uploadButton, 8, 0, 1, 3); + + editorLeftLayout->setColumnStretch(1, 1); + + QVBoxLayout* categoryLayout = new QVBoxLayout(); + categoryLayout->addWidget(new QLabel("Categories")); + auto categoryKeys = m_categorySelectors.keys(); + categoryKeys.sort(); + for (auto k : categoryKeys) { + categoryLayout->addWidget(m_categorySelectors[k]); + } + categoryLayout-> addWidget(new QWidget(), 1); + + QHBoxLayout* editorLayout = new QHBoxLayout(); + editorLayout->addLayout(editorLeftLayout, 1); + editorLayout->addLayout(categoryLayout); + + m_editorSection = new QWidget(); + m_editorSection->setLayout(editorLayout); + + auto mainLayout = new QVBoxLayout(); + mainLayout->addLayout(loadDirectoryLayout); + mainLayout->addWidget(m_editorSection); + + auto* centralWidget = new QWidget(this); + centralWidget->setLayout(mainLayout); + setCentralWidget(centralWidget); + + m_reloadButton->setEnabled(false); + m_editorSection->setEnabled(false); + + setWindowTitle("Steam Mod Uploader"); + resize(1000, 600); +} + +void ModUploader::selectDirectory() { + QString dir = QFileDialog::getExistingDirectory(this, "Select the top-level mod directory"); + m_modDirectory = toSString(dir); + + loadDirectory(); +} + +void ModUploader::loadDirectory() { + QProgressDialog progress("Loading mod directory...", "", 0, 0, this); + progress.setWindowModality(Qt::WindowModal); + progress.setCancelButton(nullptr); + progress.setAutoReset(false); + progress.show(); + + if (m_modDirectory && !File::isDirectory(*m_modDirectory)) + m_modDirectory.reset(); + + if (!m_modDirectory) { + m_reloadButton->setEnabled(false); + m_directoryLabel->setText(""); + m_editorSection->setEnabled(false); + m_assetSource.reset(); + return; + } + + m_reloadButton->setEnabled(true); + m_directoryLabel->setText(toQString(*m_modDirectory)); + m_editorSection->setEnabled(true); + m_assetSource = DirectoryAssetSource(*m_modDirectory); + + JsonObject metadata = m_assetSource->metadata(); + m_nameEditor->setText(toQString(metadata.value("name", "").toString())); + m_titleEditor->setText(toQString(metadata.value("friendlyName", "").toString())); + m_authorEditor->setText(toQString(metadata.value("author", "").toString())); + m_versionEditor->setText(toQString(metadata.value("version", "").toString())); + m_descriptionEditor->setPlainText(toQString(metadata.value("description", "").toString())); + + for (auto pair : m_categorySelectors) + pair.second->setChecked(false); + + auto tagString = metadata.value("tags", "").toString(); + auto tagList = tagString.split('|'); + for (auto tag : tagList) { + if (m_categorySelectors.contains(tag)) + m_categorySelectors[tag]->setChecked(true); + } + + String modId = metadata.value("steamContentId", "").toString(); + m_modIdLabel->setText(toQString(strf("<a href=\"steam://url/CommunityFilePage/%s\">%s</a>", modId, modId))); + + String previewFile = File::relativeTo(*m_modDirectory, "_previewimage"); + if (File::isFile(previewFile)) { + m_modPreview = QImage(toQString(previewFile), "PNG"); + m_previewImageLabel->setPixmap(QPixmap::fromImage(m_modPreview)); + } else { + m_modPreview = {}; + m_previewImageLabel->setPixmap({}); + } +} + +void ModUploader::selectPreview() { + QString image = QFileDialog::getOpenFileName(this, "Select a mod preview image", "", "Images (*.png *.jpg)"); + + m_modPreview = {}; + m_previewImageLabel->setPixmap({}); + + if (!image.isEmpty()) { + if (m_modPreview.load(image)) + m_previewImageLabel->setPixmap(QPixmap::fromImage(m_modPreview)); + else + QMessageBox::critical(this, "Error", "Could not load preview image"); + } + + writePreview(); +} + +void ModUploader::writeMetadata() { + if (!m_assetSource) + return; + + auto metadata = m_assetSource->metadata(); + auto setMetadata = [&metadata](String const& key, String const& value) { + if (value.empty()) + metadata.remove(key); + else + metadata[key] = value; + }; + + setMetadata("name", toSString(m_nameEditor->text())); + setMetadata("friendlyName", toSString(m_titleEditor->text())); + setMetadata("author", toSString(m_authorEditor->text())); + setMetadata("version", toSString(m_versionEditor->text())); + setMetadata("description", toSString(m_descriptionEditor->toPlainText())); + + auto tagList = StringList(); + for (auto pair : m_categorySelectors) { + if (pair.second->isChecked()) + tagList.append(pair.first); + } + auto tagString = tagList.join("|"); + setMetadata("tags", tagString); + + m_assetSource->setMetadata(metadata); +} + +void ModUploader::writePreview() { + if (m_modPreview.isNull()) + return; + + String modPreviewFile = File::relativeTo(*m_modDirectory, "_previewimage"); + m_modPreview.save(toQString(modPreviewFile), "PNG"); +} + +void ModUploader::resetModId() { + m_modIdLabel->setText(""); + auto metadata = m_assetSource->metadata(); + metadata.remove("steamContentId"); + m_assetSource->setMetadata(metadata); +} + +void ModUploader::uploadToSteam() { + if (!m_modDirectory) + return; + + QProgressDialog progress("Uploading to Steam...", "", 0, 0, this); + progress.setWindowModality(Qt::WindowModal); + progress.setCancelButton(nullptr); + progress.setAutoReset(false); + progress.show(); + + if (m_assetSource->assetPaths().empty()) { + QMessageBox::critical(this, "Error", "Cannot upload, mod has no content"); + return; + } + + m_steamItemCreateResult = {}; + m_steamItemSubmitResult = {}; + + JsonObject metadata = m_assetSource->metadata(); + String modIdString = metadata.value("steamContentId", "").toString(); + if (modIdString.empty()) { + CCallResult<ModUploader, CreateItemResult_t> callResultCreate; + callResultCreate.Set(SteamUGC()->CreateItem(SteamUtils()->GetAppID(), k_EWorkshopFileTypeCommunity), + this, &ModUploader::onSteamCreateItem); + + progress.setLabelText("Creating new Steam UGC Item"); + while (!m_steamItemCreateResult) { + QApplication::processEvents(); + SteamAPI_RunCallbacks(); + Thread::sleep(20); + } + + if (m_steamItemCreateResult->second) { + QMessageBox::critical(this, "Error", "There was an IO error creating a new Steam UGC item"); + return; + } + + if (m_steamItemCreateResult->first.m_bUserNeedsToAcceptWorkshopLegalAgreement) { + QMessageBox::critical(this, "Error", "The current Steam user has not agreed to the workshop legal agreement"); + return; + } + + if (m_steamItemCreateResult->first.m_eResult == k_EResultInsufficientPrivilege) { + QMessageBox::critical(this, "Error", "Insufficient privileges to create a new Steam UGC item"); + return; + } + + if (m_steamItemCreateResult->first.m_eResult != k_EResultOK) { + QMessageBox::critical(this, "Error", strf("Error creating new Steam UGC Item (%s)", m_steamItemCreateResult->first.m_eResult).c_str()); + return; + } + + modIdString = toString(m_steamItemCreateResult->first.m_nPublishedFileId); + String modUrl = strf("steam://url/CommunityFilePage/%s", modIdString); + + metadata.set("steamContentId", modIdString); + metadata.set("link", modUrl); + m_assetSource->setMetadata(metadata); + + m_modIdLabel->setText(toQString(strf("<a href=\"%s\">%s</a>", modUrl, modIdString))); + } + + String steamUploadDir = File::temporaryDirectory(); + auto progressCallback = [&progress](size_t i, size_t total, String, String assetPath) { + progress.setLabelText(toQString(strf("Packing '%s'", assetPath))); + progress.setMaximum(total); + progress.setValue(i); + QApplication::processEvents(); + }; + + String packedPath = File::relativeTo(steamUploadDir, "contents.pak"); + PackedAssetSource::build(*m_assetSource, packedPath, {}, progressCallback); + + PublishedFileId_t modId = lexicalCast<PublishedFileId_t>(modIdString); + + UGCUpdateHandle_t updateHandle = SteamUGC()->StartItemUpdate(SteamUtils()->GetAppID(), modId); + SteamUGC()->SetItemTitle(updateHandle, toSString(m_titleEditor->text()).utf8Ptr()); + SteamUGC()->SetItemDescription(updateHandle, toSString(m_descriptionEditor->toPlainText()).utf8Ptr()); + if (!m_modPreview.isNull()) + SteamUGC()->SetItemPreview(updateHandle, File::relativeTo(*m_modDirectory, "_previewimage").utf8Ptr()); + SteamUGC()->SetItemContent(updateHandle, steamUploadDir.utf8Ptr()); + + // construct tags + auto tagList = StringList(); + for (auto entry : m_categorySelectors.pairs()) { + if (entry.second->isChecked()) + tagList.append(entry.first); + } + const char** tagStrings = new const char*[tagList.size()]; + for (int i = 0; i < tagList.size(); ++i) { + tagStrings[i] = tagList[i].utf8Ptr(); + } + SteamUGC()->SetItemTags(updateHandle, &SteamParamStringArray_t{tagStrings, (int32_t)tagList.size()}); + + CCallResult<ModUploader, SubmitItemUpdateResult_t> callResultSubmit; + callResultSubmit.Set(SteamUGC()->SubmitItemUpdate(updateHandle, nullptr), + this, &ModUploader::onSteamSubmitItem); + + progress.setLabelText("Updating Steam UGC Item"); + while (!m_steamItemSubmitResult) { + uint64 bytesProcessed; + uint64 bytesTotal; + SteamUGC()->GetItemUpdateProgress(updateHandle, &bytesProcessed, &bytesTotal); + progress.setMaximum(bytesTotal); + progress.setValue(bytesProcessed); + QApplication::processEvents(); + SteamAPI_RunCallbacks(); + Thread::sleep(20); + } + + File::removeDirectoryRecursive(steamUploadDir); + + if (m_steamItemSubmitResult->second) { + QMessageBox::critical(this, "Error", "There was an IO error submitting changes to the Steam UGC item"); + return; + } + + if (m_steamItemSubmitResult->first.m_bUserNeedsToAcceptWorkshopLegalAgreement) { + QMessageBox::critical(this, "Error", "The current Steam user has not agreed to the workshop legal agreement"); + return; + } + + if (m_steamItemSubmitResult->first.m_eResult != k_EResultOK) { + QMessageBox::critical(this, "Error", strf("Error submitting changes to the Steam UGC item (%s)", m_steamItemSubmitResult->first.m_eResult).c_str()); + return; + } +} + +void ModUploader::onSteamCreateItem(CreateItemResult_t* result, bool ioFailure) { + m_steamItemCreateResult = make_pair(*result, ioFailure); +} + +void ModUploader::onSteamSubmitItem(SubmitItemUpdateResult_t* result, bool ioFailure) { + m_steamItemSubmitResult = make_pair(*result, ioFailure); +} + +} |