Qt: First pass at 3DS forwarder generator

This commit is contained in:
Vicki Pfau 2022-10-31 19:50:37 -07:00
parent 2b7f5ba4d0
commit 658f4e1a34
8 changed files with 442 additions and 15 deletions

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ForwarderController.h"
#include <QDir>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@ -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<ForwarderGenerator>&& 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;
}

View File

@ -23,7 +23,7 @@ Q_OBJECT
public:
ForwarderController(QObject* parent = nullptr);
void setGenerator(std::unique_ptr<ForwarderGenerator>&& generator) { m_generator = std::move(generator); }
void setGenerator(std::unique_ptr<ForwarderGenerator>&& 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;

View File

@ -9,6 +9,7 @@
#include <QImage>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QVector>
#include <memory>
@ -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);

View File

@ -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 <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <mgba/core/version.h>
#include <mgba-util/vfs.h>
#include <QDebug>
using namespace QGBA;
ForwarderGenerator3DS::ForwarderGenerator3DS()
: ForwarderGenerator(2)
{
connect(this, &ForwarderGenerator::buildFailed, this, &ForwarderGenerator3DS::cleanup);
connect(this, &ForwarderGenerator::buildComplete, this, &ForwarderGenerator3DS::cleanup);
}
QList<QPair<QString, QSize>> ForwarderGenerator3DS::imageTypes() const {
@ -19,6 +35,333 @@ QList<QPair<QString, QSize>> 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<QProcess>();
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<int, QProcess::ExitStatus>(&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<QProcess>();
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<int, QProcess::ExitStatus>(&QProcess::finished), this, &ForwarderGenerator3DS::extractExefs);
m_currentProc->start(QIODevice::ReadOnly);
}
void ForwarderGenerator3DS::extractExefs() {
m_currentProc = std::make_unique<QProcess>();
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<int, QProcess::ExitStatus>(&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<QProcess>();
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<int, QProcess::ExitStatus>(&QProcess::finished), this, &ForwarderGenerator3DS::buildSmdh);
m_currentProc->start(QIODevice::NotOpen);
}
void ForwarderGenerator3DS::buildSmdh() {
m_currentProc = std::make_unique<QProcess>();
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<int, QProcess::ExitStatus>(&QProcess::finished), this, &ForwarderGenerator3DS::buildExefs);
} else {
connect(m_currentProc.get(), qOverload<int, QProcess::ExitStatus>(&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<QProcess>();
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<int, QProcess::ExitStatus>(&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<QProcess>();
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<int, QProcess::ExitStatus>(&QProcess::finished), this, &ForwarderGenerator3DS::buildCxi);
m_currentProc->start(QIODevice::NotOpen);
}
void ForwarderGenerator3DS::buildCxi() {
m_currentProc = std::make_unique<QProcess>();
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<int, QProcess::ExitStatus>(&QProcess::finished), this, &ForwarderGenerator3DS::buildCia);
m_currentProc->start(QIODevice::NotOpen);
}
void ForwarderGenerator3DS::buildCia() {
m_currentProc = std::make_unique<QProcess>();
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<int, QProcess::ExitStatus>(&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;
}
}

View File

@ -7,6 +7,10 @@
#include "ForwarderGenerator.h"
#include <QProcess>
#include <memory>
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<QProcess> m_currentProc;
QString m_cia;
QString m_cxi;
QString m_target;
};
}

View File

@ -29,10 +29,11 @@ QList<QPair<QString, QSize>> 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) {

View File

@ -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);

View File

@ -3,6 +3,7 @@
<qresource>
<file>../../../res/mgba-1024.png</file>
<file>../../../res/mgba-256.png</file>
<file>../../../res/mgba-48.png</file>
<file>../../../res/keymap.qpic</file>
<file>../../../res/patrons.txt</file>
<file>../../../res/no-cam.png</file>