Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2023-05-03 02:28:22 -07:00
commit 69594abe8a
8 changed files with 531 additions and 29 deletions

View File

@ -5,28 +5,42 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ForwarderController.h"
#include <QDir>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "ConfigController.h"
#include "VFileDevice.h"
#include <mgba/core/version.h>
#include <mgba/feature/updater.h>
#include <mgba-util/vfs.h>
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))
, 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<ForwarderGenerator>&& generator) {
m_generator = std::move(generator);
connect(m_generator.get(), &ForwarderGenerator::buildFailed, this, &ForwarderController::buildFailed);
connect(m_generator.get(), &ForwarderGenerator::buildComplete, this, &ForwarderController::buildComplete);
}
void ForwarderController::startBuild(const QString& outFilename) {
@ -35,6 +49,87 @@ 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)) {
downloadForwarderKit();
return;
}
}
downloadManifest();
}
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();
}
@ -43,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) {
@ -98,6 +187,7 @@ void ForwarderController::downloadBuild(const QUrl& url) {
QByteArray data = reply->readAll();
m_sourceFile.write(data);
});
connectErrorFailure(reply);
}
void ForwarderController::gotBuild(QNetworkReply* reply) {
@ -109,10 +199,36 @@ 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_sourceFile.remove();
m_generator->rebuild(m_sourceFile.fileName(), m_outFilename);
}
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) {
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;
}
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();
});
}

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; }
@ -38,10 +38,16 @@ signals:
private slots:
void gotManifest(QNetworkReply*);
void gotBuild(QNetworkReply*);
void gotForwarderKit(QNetworkReply*);
private:
void downloadForwarderKit();
void downloadManifest();
void downloadBuild(const QUrl&);
bool toolInstalled(const QString& tool);
void cleanup();
void connectErrorFailure(QNetworkReply*);
QString m_channel{"dev"};
QString m_outFilename;
@ -49,6 +55,7 @@ private:
std::unique_ptr<ForwarderGenerator> m_generator;
QFile m_sourceFile;
bool m_inProgress = false;
QByteArray m_originalPath;
};
}

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,25 @@
* 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>
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 +33,331 @@ 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 << QString("--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)) {
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)) {
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() {
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

@ -2,6 +2,7 @@
<RCC version="1.0">
<qresource>
<file>../../../res/medusa-bg.jpg</file>
<file>../../../res/keymap.qpic</file>
<file>../../../res/patrons.txt</file>
<file>../../../res/no-cam.png</file>
<file>input/default-profiles.ini</file>