From 658f4e1a34db775f37e00edb17b225e05ca40c8e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 31 Oct 2022 19:50:37 -0700 Subject: [PATCH 1/5] Qt: First pass at 3DS forwarder generator --- src/platform/qt/ForwarderController.cpp | 51 ++- src/platform/qt/ForwarderController.h | 5 +- src/platform/qt/ForwarderGenerator.h | 9 +- src/platform/qt/ForwarderGenerator3DS.cpp | 347 ++++++++++++++++++++- src/platform/qt/ForwarderGenerator3DS.h | 32 +- src/platform/qt/ForwarderGeneratorVita.cpp | 10 +- src/platform/qt/ForwarderGeneratorVita.h | 2 +- src/platform/qt/resources.qrc | 1 + 8 files changed, 442 insertions(+), 15 deletions(-) diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp index 9edbe50b9..108169cd4 100644 --- a/src/platform/qt/ForwarderController.cpp +++ b/src/platform/qt/ForwarderController.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ForwarderController.h" +#include #include #include #include @@ -16,6 +17,14 @@ using namespace QGBA; +#ifdef Q_OS_WIN +const QChar LIST_SPLIT{';'}; +const char* SUFFIX = ".exe"; +#else +const QChar LIST_SPLIT{':'}; +const char* SUFFIX = ""; +#endif + ForwarderController::ForwarderController(QObject* parent) : QObject(parent) , m_netman(new QNetworkAccessManager(this)) @@ -29,15 +38,36 @@ ForwarderController::ForwarderController(QObject* parent) }); } +void ForwarderController::setGenerator(std::unique_ptr&& generator) { + m_generator = std::move(generator); + connect(m_generator.get(), &ForwarderGenerator::buildFailed, this, &ForwarderController::buildFailed); + connect(m_generator.get(), &ForwarderGenerator::buildFailed, this, &ForwarderController::cleanup); + connect(m_generator.get(), &ForwarderGenerator::buildComplete, this, &ForwarderController::buildComplete); + connect(m_generator.get(), &ForwarderGenerator::buildComplete, this, &ForwarderController::cleanup); +} + void ForwarderController::startBuild(const QString& outFilename) { if (m_inProgress) { return; } m_inProgress = true; m_outFilename = outFilename; + + QStringList neededTools = m_generator->externalTools(); + for (const auto& tool : neededTools) { + if (!toolInstalled(tool)) { + downloadForwarderKit(); + return; + } + } downloadManifest(); } +void ForwarderController::downloadForwarderKit() { + // TODO + emit buildFailed(); +} + void ForwarderController::downloadManifest() { QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl("https://mgba.io/latest.ini"))); connect(reply, &QNetworkReply::finished, this, [this, reply]() { @@ -109,10 +139,21 @@ void ForwarderController::gotBuild(QNetworkReply* reply) { QByteArray data = reply->readAll(); m_sourceFile.write(data); m_sourceFile.close(); - if (!m_generator->rebuild(m_sourceFile.fileName(), m_outFilename)) { - emit buildFailed(); - } else { - emit buildComplete(); - } + m_generator->rebuild(m_sourceFile.fileName(), m_outFilename); +} + +void ForwarderController::cleanup() { m_sourceFile.remove(); } + +bool ForwarderController::toolInstalled(const QString& tool) { + QByteArray arr = qgetenv("PATH"); + QStringList path = QString::fromUtf8(arr).split(LIST_SPLIT); + for (QDir dir : path) { + QFileInfo exe(dir, tool + SUFFIX); + if (exe.isExecutable()) { + return true; + } + } + return false; +} diff --git a/src/platform/qt/ForwarderController.h b/src/platform/qt/ForwarderController.h index 47baa3949..391fe0e76 100644 --- a/src/platform/qt/ForwarderController.h +++ b/src/platform/qt/ForwarderController.h @@ -23,7 +23,7 @@ Q_OBJECT public: ForwarderController(QObject* parent = nullptr); - void setGenerator(std::unique_ptr&& generator) { m_generator = std::move(generator); } + void setGenerator(std::unique_ptr&& generator); ForwarderGenerator* generator() { return m_generator.get(); } QString channel() const { return m_channel; } @@ -40,8 +40,11 @@ private slots: void gotBuild(QNetworkReply*); private: + void downloadForwarderKit(); void downloadManifest(); void downloadBuild(const QUrl&); + bool toolInstalled(const QString& tool); + void cleanup(); QString m_channel{"dev"}; QString m_outFilename; diff --git a/src/platform/qt/ForwarderGenerator.h b/src/platform/qt/ForwarderGenerator.h index 0fdf85b2e..c471910f2 100644 --- a/src/platform/qt/ForwarderGenerator.h +++ b/src/platform/qt/ForwarderGenerator.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -41,9 +42,15 @@ public: QString systemName() const { return systemName(system()); } virtual QString extension() const = 0; + virtual QStringList externalTools() const { return {}; } + static QString systemName(System); - virtual bool rebuild(const QString& source, const QString& target) = 0; + virtual void rebuild(const QString& source, const QString& target) = 0; + +signals: + void buildComplete(); + void buildFailed(); protected: ForwarderGenerator(int imageTypes, QObject* parent = nullptr); diff --git a/src/platform/qt/ForwarderGenerator3DS.cpp b/src/platform/qt/ForwarderGenerator3DS.cpp index 123406a3d..89fc609e8 100644 --- a/src/platform/qt/ForwarderGenerator3DS.cpp +++ b/src/platform/qt/ForwarderGenerator3DS.cpp @@ -5,11 +5,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ForwarderGenerator3DS.h" +#include "ConfigController.h" +#include "utils.h" +#include "VFileDevice.h" + +#include +#include +#include +#include + +#include +#include + +#include + using namespace QGBA; ForwarderGenerator3DS::ForwarderGenerator3DS() : ForwarderGenerator(2) { + connect(this, &ForwarderGenerator::buildFailed, this, &ForwarderGenerator3DS::cleanup); + connect(this, &ForwarderGenerator::buildComplete, this, &ForwarderGenerator3DS::cleanup); } QList> ForwarderGenerator3DS::imageTypes() const { @@ -19,6 +35,333 @@ QList> ForwarderGenerator3DS::imageTypes() const { }; } -bool ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) { - return false; +void ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) { + m_cia = dumpCia(source); + if (m_cia.isNull()) { + emit buildFailed(); + return; + } + + m_target = target; + extractCia(); +} + +QString ForwarderGenerator3DS::dumpCia(const QString& archive) { + VDir* inArchive = VFileDevice::openArchive(archive); + if (!inArchive) { + return {}; + } + bool gotFile = extractMatchingFile(inArchive, [](VDirEntry* dirent) -> QString { + if (dirent->type(dirent) != VFS_FILE) { + return {}; + } + QString filename(dirent->name(dirent)); + if (!filename.endsWith(".cia")) { + return {}; + } + return "tmp.cia"; + }); + inArchive->close(inArchive); + + if (gotFile) { + return QLatin1String("tmp.cia"); + } + return {}; +} + +void ForwarderGenerator3DS::extractCia() { + m_currentProc = std::make_unique(); + m_currentProc->setProgram("ctrtool"); + + QStringList args; + args << QLatin1String("--contents=%0/cxi").arg(ConfigController::cacheDir()); + args << m_cia; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::extractCxi); + m_currentProc->start(QIODevice::ReadOnly); +} + +void ForwarderGenerator3DS::extractCxi() { + QStringList output = QString::fromUtf8(m_currentProc->readAll()).split("\n"); + QString index; + for (const QString& line : output) { + if (!line.contains("|- ContentId:")) { + continue; + } + index = line.trimmed().right(8); + } + m_cxi = ConfigController::cacheDir() + "/cxi.0000." + index; + + m_currentProc = std::make_unique(); + m_currentProc->setProgram("3dstool"); + + QStringList args; + init3dstoolArgs(args, m_cxi); + args << "--exh" << ConfigController::cacheDir() + "/exheader.bin"; + args << "--header" << ConfigController::cacheDir() + "/header.bin"; + args << "--exefs" << ConfigController::cacheDir() + "/exefs.bin"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::extractExefs); + m_currentProc->start(QIODevice::ReadOnly); +} + +void ForwarderGenerator3DS::extractExefs() { + m_currentProc = std::make_unique(); + m_currentProc->setProgram("3dstool"); + + QStringList args; + init3dstoolArgs(args, ConfigController::cacheDir() + "/exefs.bin"); + args << "--header" << ConfigController::cacheDir() + "/exeheader.bin"; + args << "--exefs-dir" << ConfigController::cacheDir() + "/exefs"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::processCxi); + m_currentProc->start(QIODevice::ReadOnly); +} + +void ForwarderGenerator3DS::processCxi() { + QByteArray hash = hashRom(); + QByteArray tid = hash.left(4); + quint32 tidNum; + LOAD_32LE(tidNum, 0, tid.data()); + tidNum &= 0x7FFFFFF; + tidNum += 0x0300000; + STORE_32LE(tidNum, 0, tid.data()); + + QFile header(ConfigController::cacheDir() + "/header.bin"); + if (!header.open(QIODevice::ReadWrite | QIODevice::ExistingOnly)) { + emit buildFailed(); + return; + } + header.seek(0x108); + header.write(tid); + header.seek(0x118); + header.write(tid); + + QByteArray productCode("MGBA-"); + productCode += base36(hash, 11).toLatin1(); + header.seek(0x150); + header.write(productCode); + + header.seek(0x18D); + QByteArray type = header.read(3); + type[0] = type[0] | 1; // Has romfs + type[2] = type[2] & ~2; // Can mount romfs + header.seek(0x18D); + header.write(type); + header.close(); + + QFile exheader(ConfigController::cacheDir() + "/exheader.bin"); + if (!exheader.open(QIODevice::ReadWrite | QIODevice::ExistingOnly)) { + emit buildFailed(); + return; + } + exheader.seek(0x1C8); + exheader.write(tid); + exheader.seek(0x200); + exheader.write(tid); + exheader.seek(0x600); + exheader.write(tid); + exheader.close(); + + prepareRomfs(); +} + +void ForwarderGenerator3DS::prepareRomfs() { + QDir romfsDir(ConfigController::cacheDir()); + + romfsDir.mkdir("romfs"); + romfsDir.cd("romfs"); + + QFileInfo info(rom()); + QByteArray buffer(info.fileName().toUtf8()); + QFile filename(romfsDir.filePath("filename")); + if (!filename.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + emit buildFailed(); + return; + } + if (filename.write(buffer) != filename.size()) { + emit buildFailed(); + return; + } + filename.close(); + + if (!QFile::copy(info.filePath(), romfsDir.filePath(info.fileName()))) { + emit buildFailed(); + return; + } + + buildRomfs(); +} + +void ForwarderGenerator3DS::buildRomfs() { + m_currentProc = std::make_unique(); + m_currentProc->setProgram("3dstool"); + + QStringList args; + init3dstoolArgs(args, ConfigController::cacheDir() + "/romfs.bin", "romfs"); + args << "--romfs-dir"; + args << ConfigController::cacheDir() + "/romfs"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildSmdh); + m_currentProc->start(QIODevice::NotOpen); +} + +void ForwarderGenerator3DS::buildSmdh() { + m_currentProc = std::make_unique(); + m_currentProc->setProgram("bannertool"); + + if (image(0).isNull()) { + QFile::copy(":/res/mgba-48.png", ConfigController::cacheDir() + "/smdh.png"); + } else { + image(0).save(ConfigController::cacheDir() + "/smdh.png", "PNG"); + } + + QStringList args; + args << "makesmdh"; + + args << "-s" << title(); + args << "-l" << title(); + args << "-p" << projectName + QString(" Forwarder"); + args << "-i" << ConfigController::cacheDir() + "/smdh.png"; + args << "-o" << ConfigController::cacheDir() + "/exefs/icon.icn"; + m_currentProc->setArguments(args); + + if (image(1).isNull()) { + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildExefs); + } else { + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildBanner); + } + m_currentProc->start(QIODevice::ReadOnly); +} + +void ForwarderGenerator3DS::buildBanner() { + QFile banner(ConfigController::cacheDir() + "/exefs/banner.bnr"); + if (!banner.open(QIODevice::ReadOnly)) { + emit buildFailed(); + return; + } + + banner.seek(0x84); + QByteArray bcwavOffsetBuffer(banner.read(4)); + qint64 bcwavOffset; + LOAD_64LE(bcwavOffset, 0, bcwavOffsetBuffer.data()); + banner.seek(bcwavOffset); + QByteArray bcwav(banner.readAll()); + QFile bcwavFile(ConfigController::cacheDir() + "/banner.bcwav"); + if (!bcwavFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + emit buildFailed(); + return; + } + bcwavFile.write(bcwav); + banner.close(); + + m_currentProc = std::make_unique(); + m_currentProc->setProgram("bannertool"); + + image(1).save(ConfigController::cacheDir() + "/banner.png", "PNG"); + + QStringList args; + args << "makebanner"; + + args << "-i" << ConfigController::cacheDir() + "/banner.png"; + args << "-ca" << ConfigController::cacheDir() + "/banner.bcwav"; + args << "-o" << ConfigController::cacheDir() + "/exefs/banner.bnr"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildExefs); + m_currentProc->start(QIODevice::ReadOnly); +} + +void ForwarderGenerator3DS::buildExefs() { + QByteArray out = m_currentProc->readAll(); + qDebug() << out; + m_currentProc = std::make_unique(); + m_currentProc->setProgram("3dstool"); + + QStringList args; + init3dstoolArgs(args, ConfigController::cacheDir() + "/exefs.bin", "exefs"); + args << "--header" << ConfigController::cacheDir() + "/exeheader.bin"; + args << "--exefs-dir" << ConfigController::cacheDir() + "/exefs"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildCxi); + m_currentProc->start(QIODevice::NotOpen); +} + +void ForwarderGenerator3DS::buildCxi() { + m_currentProc = std::make_unique(); + m_currentProc->setProgram("3dstool"); + + QFile cxi(m_cxi); + cxi.remove(); + + QStringList args; + init3dstoolArgs(args, m_cxi, "cxi"); + args << "--exh" << ConfigController::cacheDir() + "/exheader.bin"; + args << "--header" << ConfigController::cacheDir() + "/header.bin"; + args << "--exefs" << ConfigController::cacheDir() + "/exefs.bin"; + args << "--romfs" << ConfigController::cacheDir() + "/romfs.bin"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildCia); + m_currentProc->start(QIODevice::NotOpen); +} + +void ForwarderGenerator3DS::buildCia() { + m_currentProc = std::make_unique(); + m_currentProc->setProgram("makerom"); + + QStringList args; + args << "-f" << "cia"; + args << "-o" << m_target; + args << "-content" << m_cxi + ":0:0"; + m_currentProc->setArguments(args); + + connect(m_currentProc.get(), qOverload(&QProcess::finished), this, &ForwarderGenerator3DS::buildComplete); + m_currentProc->start(QIODevice::NotOpen); +} + +void ForwarderGenerator3DS::cleanup() { + for (const QString& path : {m_cia, m_cxi}) { + QFile file(path); + if (file.exists()) { + file.remove(); + } + } + + QDir cacheDir(ConfigController::cacheDir()); + QStringList files{ + "romfs.bin", + "exefs.bin", + "exheader.bin", + "exeheader.bin", + "header.bin", + "smdh.png", + "banner.png", + "banner.bcwav", + }; + for (QString path : files) { + QFile file(cacheDir.filePath(path)); + if (file.exists()) { + file.remove(); + } + } + + for (QString path : {"romfs", "exefs"}) { + QDir dir(cacheDir.filePath(path)); + dir.removeRecursively(); + } +} + +void ForwarderGenerator3DS::init3dstoolArgs(QStringList& args, const QString& file, const QString& createType) { + if (createType.isEmpty()) { + args << "-xf" << file; + } else { + args << "-cf" << file; + args << "-t" << createType; + } } diff --git a/src/platform/qt/ForwarderGenerator3DS.h b/src/platform/qt/ForwarderGenerator3DS.h index ba6a6131a..d50d87e42 100644 --- a/src/platform/qt/ForwarderGenerator3DS.h +++ b/src/platform/qt/ForwarderGenerator3DS.h @@ -7,6 +7,10 @@ #include "ForwarderGenerator.h" +#include + +#include + namespace QGBA { class ForwarderGenerator3DS final : public ForwarderGenerator { @@ -19,7 +23,33 @@ public: System system() const override { return System::N3DS; } QString extension() const override { return QLatin1String("cia"); } - bool rebuild(const QString& source, const QString& target) override; + virtual QStringList externalTools() const { return {"bannertool", "3dstool", "ctrtool", "makerom"}; } + + void rebuild(const QString& source, const QString& target) override; + +private slots: + void extractCia(); + void extractCxi(); + void extractExefs(); + void processCxi(); + void prepareRomfs(); + void buildRomfs(); + void buildSmdh(); + void buildBanner(); + void buildExefs(); + void buildCxi(); + void buildCia(); + + void cleanup(); + +private: + QString dumpCia(const QString& archive); + void init3dstoolArgs(QStringList& args, const QString& file, const QString& createType = {}); + + std::unique_ptr m_currentProc; + QString m_cia; + QString m_cxi; + QString m_target; }; } diff --git a/src/platform/qt/ForwarderGeneratorVita.cpp b/src/platform/qt/ForwarderGeneratorVita.cpp index 1f5897825..97b3ef802 100644 --- a/src/platform/qt/ForwarderGeneratorVita.cpp +++ b/src/platform/qt/ForwarderGeneratorVita.cpp @@ -29,10 +29,11 @@ QList> ForwarderGeneratorVita::imageTypes() const { }; } -bool ForwarderGeneratorVita::rebuild(const QString& source, const QString& target) { +void ForwarderGeneratorVita::rebuild(const QString& source, const QString& target) { QString vpk = dumpVpk(source); if (vpk.isNull()) { - return false; + emit buildFailed(); + return; } QFile vpkFile(vpk); @@ -43,7 +44,8 @@ bool ForwarderGeneratorVita::rebuild(const QString& source, const QString& targe } vpkFile.remove(); if (!outdir) { - return false; + emit buildFailed(); + return; } VFile* sfo = outdir->openFile(outdir, "sce_sys/param.sfo", O_WRONLY); @@ -74,7 +76,7 @@ bool ForwarderGeneratorVita::rebuild(const QString& source, const QString& targe outdir->close(outdir); - return true; + emit buildComplete(); } QString ForwarderGeneratorVita::dumpVpk(const QString& archive) { diff --git a/src/platform/qt/ForwarderGeneratorVita.h b/src/platform/qt/ForwarderGeneratorVita.h index abb85fea1..ee2a5b17e 100644 --- a/src/platform/qt/ForwarderGeneratorVita.h +++ b/src/platform/qt/ForwarderGeneratorVita.h @@ -22,7 +22,7 @@ public: System system() const override { return System::VITA; } QString extension() const override { return QLatin1String("vpk"); } - bool rebuild(const QString& source, const QString& target) override; + void rebuild(const QString& source, const QString& target) override; private: QString dumpVpk(const QString& archive); diff --git a/src/platform/qt/resources.qrc b/src/platform/qt/resources.qrc index 737ec55f0..e4b8fab5f 100644 --- a/src/platform/qt/resources.qrc +++ b/src/platform/qt/resources.qrc @@ -3,6 +3,7 @@ ../../../res/mgba-1024.png ../../../res/mgba-256.png + ../../../res/mgba-48.png ../../../res/keymap.qpic ../../../res/patrons.txt ../../../res/no-cam.png From 25bb7a9192b9380604bd51c31566f39cea8d5e79 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 31 Oct 2022 20:41:46 -0700 Subject: [PATCH 2/5] Qt: Fix build --- src/platform/qt/ForwarderGenerator3DS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/ForwarderGenerator3DS.cpp b/src/platform/qt/ForwarderGenerator3DS.cpp index 89fc609e8..7f435b6b9 100644 --- a/src/platform/qt/ForwarderGenerator3DS.cpp +++ b/src/platform/qt/ForwarderGenerator3DS.cpp @@ -74,7 +74,7 @@ void ForwarderGenerator3DS::extractCia() { m_currentProc->setProgram("ctrtool"); QStringList args; - args << QLatin1String("--contents=%0/cxi").arg(ConfigController::cacheDir()); + args << QString("--contents=%0/cxi").arg(ConfigController::cacheDir()); args << m_cia; m_currentProc->setArguments(args); From 6bdb3470e7ce90ad53efd45a38edb5c647a12dd4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 31 Oct 2022 20:45:46 -0700 Subject: [PATCH 3/5] Qt: Fine, whatever, build fixed more --- src/platform/qt/ForwarderGenerator3DS.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/ForwarderGenerator3DS.cpp b/src/platform/qt/ForwarderGenerator3DS.cpp index 7f435b6b9..a9e32f2ce 100644 --- a/src/platform/qt/ForwarderGenerator3DS.cpp +++ b/src/platform/qt/ForwarderGenerator3DS.cpp @@ -131,7 +131,7 @@ void ForwarderGenerator3DS::processCxi() { STORE_32LE(tidNum, 0, tid.data()); QFile header(ConfigController::cacheDir() + "/header.bin"); - if (!header.open(QIODevice::ReadWrite | QIODevice::ExistingOnly)) { + if (!header.open(QIODevice::ReadWrite)) { emit buildFailed(); return; } @@ -154,7 +154,7 @@ void ForwarderGenerator3DS::processCxi() { header.close(); QFile exheader(ConfigController::cacheDir() + "/exheader.bin"); - if (!exheader.open(QIODevice::ReadWrite | QIODevice::ExistingOnly)) { + if (!exheader.open(QIODevice::ReadWrite)) { emit buildFailed(); return; } From 7f30bdc850f8eb8f5abf6fe9b3c7aba9a17d7f2b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 1 Nov 2022 01:59:40 -0700 Subject: [PATCH 4/5] Qt: Initial support for forwarder-kit --- src/platform/qt/ForwarderController.cpp | 105 ++++++++++++++++++++---- src/platform/qt/ForwarderController.h | 4 + 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/platform/qt/ForwarderController.cpp b/src/platform/qt/ForwarderController.cpp index 108169cd4..ca4676859 100644 --- a/src/platform/qt/ForwarderController.cpp +++ b/src/platform/qt/ForwarderController.cpp @@ -11,9 +11,11 @@ #include #include "ConfigController.h" +#include "VFileDevice.h" #include #include +#include using namespace QGBA; @@ -28,22 +30,17 @@ const char* SUFFIX = ""; ForwarderController::ForwarderController(QObject* parent) : QObject(parent) , m_netman(new QNetworkAccessManager(this)) + , m_originalPath(qgetenv("PATH")) { m_netman->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); - connect(this, &ForwarderController::buildFailed, this, [this]() { - m_inProgress = false; - }); - connect(this, &ForwarderController::buildComplete, this, [this]() { - m_inProgress = false; - }); + connect(this, &ForwarderController::buildFailed, this, &ForwarderController::cleanup); + connect(this, &ForwarderController::buildComplete, this, &ForwarderController::cleanup); } void ForwarderController::setGenerator(std::unique_ptr&& generator) { m_generator = std::move(generator); connect(m_generator.get(), &ForwarderGenerator::buildFailed, this, &ForwarderController::buildFailed); - connect(m_generator.get(), &ForwarderGenerator::buildFailed, this, &ForwarderController::cleanup); connect(m_generator.get(), &ForwarderGenerator::buildComplete, this, &ForwarderController::buildComplete); - connect(m_generator.get(), &ForwarderGenerator::buildComplete, this, &ForwarderController::cleanup); } void ForwarderController::startBuild(const QString& outFilename) { @@ -53,6 +50,15 @@ void ForwarderController::startBuild(const QString& outFilename) { m_inProgress = true; m_outFilename = outFilename; +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + // Amend the path for downloaded programs forwarder-kit + QByteArray arr = m_originalPath; + QStringList path = QString::fromUtf8(arr).split(LIST_SPLIT); + path << ConfigController::cacheDir(); + arr = path.join(LIST_SPLIT).toUtf8(); + qputenv("PATH", arr); +#endif + QStringList neededTools = m_generator->externalTools(); for (const auto& tool : neededTools) { if (!toolInstalled(tool)) { @@ -64,8 +70,67 @@ void ForwarderController::startBuild(const QString& outFilename) { } void ForwarderController::downloadForwarderKit() { + QString fkUrl("https://github.com/mgba-emu/forwarder-kit/releases/latest/download/forwarder-kit-%1.zip"); +#ifdef Q_OS_WIN64 + fkUrl = fkUrl.arg("win64"); +#elif defined(Q_OS_WIN32) + fkUrl = fkUrl.arg("win32"); +#elif defined(Q_OS_MAC) && (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + // Modern macOS build + fkUrl = fkUrl.arg("macos"); +#else // TODO emit buildFailed(); + return; +#endif + QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl(fkUrl))); + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + gotForwarderKit(reply); + }); + connectErrorFailure(reply); +} + +void ForwarderController::gotForwarderKit(QNetworkReply* reply) { + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { + emit buildFailed(); + return; + } + + QFile fkZip(ConfigController::cacheDir() + "/forwarder-kit.zip"); + fkZip.open(QIODevice::WriteOnly | QIODevice::Truncate); + QByteArray arr; + do { + arr = reply->read(0x800); + fkZip.write(arr); + } while (!arr.isEmpty()); + fkZip.close(); + + VDir* fkDir = VFileDevice::openArchive(fkZip.fileName()); + + // This has to be done in multiple passes to avoid seeking breaking the listing + QStringList files; + for (VDirEntry* entry = fkDir->listNext(fkDir); entry; entry = fkDir->listNext(fkDir)) { + if (entry->type(entry) != VFS_FILE) { + continue; + } + files << entry->name(entry); + } + + for (const QString& source : files) { + VFile* sourceVf = fkDir->openFile(fkDir, source.toUtf8().constData(), O_RDONLY); + VFile* targetVf = VFileDevice::open(ConfigController::cacheDir() + "/" + source, O_CREAT | O_TRUNC | O_WRONLY); + VFileDevice::copyFile(sourceVf, targetVf); + sourceVf->close(sourceVf); + targetVf->close(targetVf); + + QFile target(ConfigController::cacheDir() + "/" + source); + target.setPermissions(target.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeUser); + } + + fkDir->close(fkDir); + fkZip.remove(); + + downloadManifest(); } void ForwarderController::downloadManifest() { @@ -73,13 +138,7 @@ void ForwarderController::downloadManifest() { connect(reply, &QNetworkReply::finished, this, [this, reply]() { gotManifest(reply); }); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() { -#else - connect(reply, qOverload<>(&QNetworkReply::error), this, [this, reply]() { -#endif - emit buildFailed(); - }); + connectErrorFailure(reply); } void ForwarderController::gotManifest(QNetworkReply* reply) { @@ -128,6 +187,7 @@ void ForwarderController::downloadBuild(const QUrl& url) { QByteArray data = reply->readAll(); m_sourceFile.write(data); }); + connectErrorFailure(reply); } void ForwarderController::gotBuild(QNetworkReply* reply) { @@ -144,6 +204,11 @@ void ForwarderController::gotBuild(QNetworkReply* reply) { void ForwarderController::cleanup() { m_sourceFile.remove(); + m_inProgress = false; + +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + qputenv("PATH", m_originalPath); +#endif } bool ForwarderController::toolInstalled(const QString& tool) { @@ -157,3 +222,13 @@ bool ForwarderController::toolInstalled(const QString& tool) { } return false; } + +void ForwarderController::connectErrorFailure(QNetworkReply* reply) { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() { +#else + connect(reply, qOverload<>(&QNetworkReply::error), this, [this, reply]() { +#endif + emit buildFailed(); + }); +} diff --git a/src/platform/qt/ForwarderController.h b/src/platform/qt/ForwarderController.h index 391fe0e76..09902a397 100644 --- a/src/platform/qt/ForwarderController.h +++ b/src/platform/qt/ForwarderController.h @@ -38,6 +38,7 @@ signals: private slots: void gotManifest(QNetworkReply*); void gotBuild(QNetworkReply*); + void gotForwarderKit(QNetworkReply*); private: void downloadForwarderKit(); @@ -46,12 +47,15 @@ private: bool toolInstalled(const QString& tool); void cleanup(); + void connectErrorFailure(QNetworkReply*); + QString m_channel{"dev"}; QString m_outFilename; QNetworkAccessManager* m_netman; std::unique_ptr m_generator; QFile m_sourceFile; bool m_inProgress = false; + QByteArray m_originalPath; }; } From b8c7196dd9da7a56b78b43395a19f78ec57c0c0b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 1 Nov 2022 03:21:15 -0700 Subject: [PATCH 5/5] Qt: Cleanup --- src/platform/qt/ForwarderGenerator3DS.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/platform/qt/ForwarderGenerator3DS.cpp b/src/platform/qt/ForwarderGenerator3DS.cpp index a9e32f2ce..8b52d734a 100644 --- a/src/platform/qt/ForwarderGenerator3DS.cpp +++ b/src/platform/qt/ForwarderGenerator3DS.cpp @@ -17,8 +17,6 @@ #include #include -#include - using namespace QGBA; ForwarderGenerator3DS::ForwarderGenerator3DS() @@ -277,8 +275,6 @@ void ForwarderGenerator3DS::buildBanner() { } void ForwarderGenerator3DS::buildExefs() { - QByteArray out = m_currentProc->readAll(); - qDebug() << out; m_currentProc = std::make_unique(); m_currentProc->setProgram("3dstool");