Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/mod_uploader/StarModUploader.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/mod_uploader/StarModUploader.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/mod_uploader/StarModUploader.cpp')
-rw-r--r--source/mod_uploader/StarModUploader.cpp394
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);
+}
+
+}