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