Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2023-05-03 02:30:16 -07:00
commit b1f9a98937
19 changed files with 320 additions and 148 deletions

View File

@ -53,6 +53,7 @@ Other fixes:
- VFS: Fix minizip write returning 0 on success instead of size - VFS: Fix minizip write returning 0 on success instead of size
Misc: Misc:
- GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GB Serialize: Add missing savestate support for MBC6 and NT (newer)
- GBA: Improve detection of valid ELF ROMs
- macOS: Add category to plist (closes mgba.io/i/2691) - macOS: Add category to plist (closes mgba.io/i/2691)
- macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700) - macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700)
- Qt: Keep track of current pslette preset name (fixes mgba.io/i/2680) - Qt: Keep track of current pslette preset name (fixes mgba.io/i/2680)

View File

@ -154,6 +154,12 @@ void GBAHalt(struct GBA* gba);
void GBAStop(struct GBA* gba); void GBAStop(struct GBA* gba);
void GBADebug(struct GBA* gba, uint16_t value); void GBADebug(struct GBA* gba, uint16_t value);
#ifdef USE_ELF
struct ELF;
bool GBAVerifyELFEntry(struct ELF* elf, uint32_t target);
#endif
#ifdef USE_DEBUGGERS #ifdef USE_DEBUGGERS
struct mDebugger; struct mDebugger;
void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger); void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger);

View File

@ -245,7 +245,7 @@ static bool _GBACoreInit(struct mCore* core) {
#ifndef MINIMAL_CORE #ifndef MINIMAL_CORE
core->inputInfo = &GBAInputInfo; core->inputInfo = &GBAInputInfo;
#endif #endif
return true; return true;
} }
@ -523,7 +523,7 @@ static bool _GBACoreLoadROM(struct mCore* core, struct VFile* vf) {
#ifdef USE_ELF #ifdef USE_ELF
struct ELF* elf = ELFOpen(vf); struct ELF* elf = ELFOpen(vf);
if (elf) { if (elf) {
if (ELFEntry(elf) == BASE_CART0) { if (GBAVerifyELFEntry(elf, BASE_CART0)) {
GBALoadNull(core->board); GBALoadNull(core->board);
} }
bool success = mCoreLoadELF(core, elf); bool success = mCoreLoadELF(core, elf);

View File

@ -594,6 +594,63 @@ void GBADebug(struct GBA* gba, uint16_t flags) {
gba->debugFlags = GBADebugFlagsClearSend(gba->debugFlags); gba->debugFlags = GBADebugFlagsClearSend(gba->debugFlags);
} }
#ifdef USE_ELF
bool GBAVerifyELFEntry(struct ELF* elf, uint32_t target) {
if (ELFEntry(elf) == target) {
return true;
}
struct ELFProgramHeaders ph;
ELFProgramHeadersInit(&ph, 0);
ELFGetProgramHeaders(elf, &ph);
size_t i;
for (i = 0; i < ELFProgramHeadersSize(&ph); ++i) {
Elf32_Phdr* phdr = ELFProgramHeadersGetPointer(&ph, i);
if (!phdr->p_filesz) {
continue;
}
size_t phdrS = phdr->p_paddr;
size_t phdrE = phdrS + phdr->p_filesz;
// Does the segment contain our target address?
if (target < phdrS || target + 4 > phdrE) {
continue;
}
// File offset to what should be the rom entry instruction
size_t off = phdr->p_offset + target - phdrS;
size_t eSize;
const char* bytes = ELFBytes(elf, &eSize);
// Bounds and alignment check
if (off >= eSize || off & 3) {
continue;
}
uint32_t opcode;
LOAD_32(opcode, off, bytes);
struct ARMInstructionInfo info;
ARMDecodeARM(opcode, &info);
if (info.branchType != ARM_BRANCH && info.branchType != ARM_BRANCH_LINKED) {
continue;
}
uint32_t bTarget = target + info.op1.immediate + 8;
if (ELFEntry(elf) == bTarget) {
ELFProgramHeadersDeinit(&ph);
return true;
}
}
ELFProgramHeadersDeinit(&ph);
return false;
}
#endif
bool GBAIsROM(struct VFile* vf) { bool GBAIsROM(struct VFile* vf) {
if (!vf) { if (!vf) {
return false; return false;
@ -605,7 +662,7 @@ bool GBAIsROM(struct VFile* vf) {
uint32_t entry = ELFEntry(elf); uint32_t entry = ELFEntry(elf);
bool isGBA = true; bool isGBA = true;
isGBA = isGBA && ELFMachine(elf) == EM_ARM; isGBA = isGBA && ELFMachine(elf) == EM_ARM;
isGBA = isGBA && (entry == BASE_CART0 || entry == BASE_WORKING_RAM + 0xC0); isGBA = isGBA && (GBAVerifyELFEntry(elf, BASE_CART0) || GBAVerifyELFEntry(elf, BASE_WORKING_RAM + 0xC0));
ELFClose(elf); ELFClose(elf);
return isGBA; return isGBA;
} }
@ -661,7 +718,7 @@ bool GBAIsMB(struct VFile* vf) {
#ifdef USE_ELF #ifdef USE_ELF
struct ELF* elf = ELFOpen(vf); struct ELF* elf = ELFOpen(vf);
if (elf) { if (elf) {
bool isMB = ELFEntry(elf) == BASE_WORKING_RAM + 0xC0; bool isMB = GBAVerifyELFEntry(elf, BASE_WORKING_RAM + 0xC0);
ELFClose(elf); ELFClose(elf);
return isMB; return isMB;
} }

View File

@ -8,16 +8,17 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include "GBAApp.h"
using namespace QGBA; using namespace QGBA;
AbstractUpdater::AbstractUpdater(QObject* parent) AbstractUpdater::AbstractUpdater(QObject* parent)
: QObject(parent) : QObject(parent)
, m_netman(new QNetworkAccessManager(this))
{ {
} }
void AbstractUpdater::checkUpdate() { void AbstractUpdater::checkUpdate() {
QNetworkReply* reply = m_netman->get(QNetworkRequest(manifestLocation())); QNetworkReply* reply = GBAApp::app()->httpGet(manifestLocation());
chaseRedirects(reply, &AbstractUpdater::manifestDownloaded); chaseRedirects(reply, &AbstractUpdater::manifestDownloaded);
} }
@ -36,7 +37,7 @@ void AbstractUpdater::downloadUpdate() {
return; return;
} }
m_isUpdating = true; m_isUpdating = true;
QNetworkReply* reply = m_netman->get(QNetworkRequest(url)); QNetworkReply* reply = GBAApp::app()->httpGet(url);
chaseRedirects(reply, &AbstractUpdater::updateDownloaded); chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
} }
@ -54,7 +55,7 @@ void AbstractUpdater::chaseRedirects(QNetworkReply* reply, void (AbstractUpdater
connect(reply, &QNetworkReply::finished, this, [this, reply, cb]() { connect(reply, &QNetworkReply::finished, this, [this, reply, cb]() {
// TODO: check domains, etc // TODO: check domains, etc
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 == 3) { if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 == 3) {
QNetworkReply* newReply = m_netman->get(QNetworkRequest(reply->header(QNetworkRequest::LocationHeader).toString())); QNetworkReply* newReply = GBAApp::app()->httpGet(reply->header(QNetworkRequest::LocationHeader).toString());
chaseRedirects(newReply, cb); chaseRedirects(newReply, cb);
} else { } else {
(this->*cb)(reply); (this->*cb)(reply);
@ -69,7 +70,7 @@ void AbstractUpdater::manifestDownloaded(QNetworkReply* reply) {
if (!url.isValid()) { if (!url.isValid()) {
emit updateDone(false); emit updateDone(false);
} else { } else {
QNetworkReply* reply = m_netman->get(QNetworkRequest(url)); QNetworkReply* reply = GBAApp::app()->httpGet(url);
chaseRedirects(reply, &AbstractUpdater::updateDownloaded); chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
} }
} else { } else {

View File

@ -9,7 +9,6 @@
#include <QFile> #include <QFile>
#include <QObject> #include <QObject>
class QNetworkAccessManager;
class QNetworkReply; class QNetworkReply;
namespace QGBA { namespace QGBA {
@ -44,7 +43,6 @@ private:
void updateDownloaded(QNetworkReply*); void updateDownloaded(QNetworkReply*);
bool m_isUpdating = false; bool m_isUpdating = false;
QNetworkAccessManager* m_netman;
QByteArray m_manifest; QByteArray m_manifest;
}; };

View File

@ -11,6 +11,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include "ConfigController.h" #include "ConfigController.h"
#include "GBAApp.h"
#include "VFileDevice.h" #include "VFileDevice.h"
#include <mgba/core/version.h> #include <mgba/core/version.h>
@ -29,10 +30,8 @@ const char* SUFFIX = "";
ForwarderController::ForwarderController(QObject* parent) ForwarderController::ForwarderController(QObject* parent)
: QObject(parent) : QObject(parent)
, m_netman(new QNetworkAccessManager(this))
, m_originalPath(qgetenv("PATH")) , m_originalPath(qgetenv("PATH"))
{ {
m_netman->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
connect(this, &ForwarderController::buildFailed, this, &ForwarderController::cleanup); connect(this, &ForwarderController::buildFailed, this, &ForwarderController::cleanup);
connect(this, &ForwarderController::buildComplete, this, &ForwarderController::cleanup); connect(this, &ForwarderController::buildComplete, this, &ForwarderController::cleanup);
} }
@ -83,14 +82,12 @@ void ForwarderController::downloadForwarderKit() {
emit buildFailed(); emit buildFailed();
return; return;
#endif #endif
QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl(fkUrl))); QNetworkReply* reply = GBAApp::app()->httpGet(QUrl(fkUrl));
connect(reply, &QNetworkReply::finished, this, [this, reply]() { connectReply(reply, FORWARDER_KIT, &ForwarderController::gotForwarderKit);
gotForwarderKit(reply);
});
connectErrorFailure(reply);
} }
void ForwarderController::gotForwarderKit(QNetworkReply* reply) { void ForwarderController::gotForwarderKit(QNetworkReply* reply) {
emit downloadComplete(FORWARDER_KIT);
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
emit buildFailed(); emit buildFailed();
return; return;
@ -134,14 +131,12 @@ void ForwarderController::gotForwarderKit(QNetworkReply* reply) {
} }
void ForwarderController::downloadManifest() { void ForwarderController::downloadManifest() {
QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl("https://mgba.io/latest.ini"))); QNetworkReply* reply = GBAApp::app()->httpGet(QUrl("https://mgba.io/latest.ini"));
connect(reply, &QNetworkReply::finished, this, [this, reply]() { connectReply(reply, MANIFEST, &ForwarderController::gotManifest);
gotManifest(reply);
});
connectErrorFailure(reply);
} }
void ForwarderController::gotManifest(QNetworkReply* reply) { void ForwarderController::gotManifest(QNetworkReply* reply) {
emit downloadComplete(MANIFEST);
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
emit buildFailed(); emit buildFailed();
return; return;
@ -177,20 +172,17 @@ void ForwarderController::downloadBuild(const QUrl& url) {
emit buildFailed(); emit buildFailed();
return; return;
} }
QNetworkReply* reply = m_netman->get(QNetworkRequest(url)); QNetworkReply* reply = GBAApp::app()->httpGet(url);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
gotBuild(reply);
});
connectReply(reply, BASE, &ForwarderController::gotBuild);
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
m_sourceFile.write(data); m_sourceFile.write(data);
}); });
connectErrorFailure(reply);
} }
void ForwarderController::gotBuild(QNetworkReply* reply) { void ForwarderController::gotBuild(QNetworkReply* reply) {
emit downloadComplete(BASE);
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
emit buildFailed(); emit buildFailed();
return; return;
@ -199,7 +191,13 @@ void ForwarderController::gotBuild(QNetworkReply* reply) {
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
m_sourceFile.write(data); m_sourceFile.write(data);
m_sourceFile.close(); m_sourceFile.close();
m_generator->rebuild(m_sourceFile.fileName(), m_outFilename);
QString extracted = m_generator->extract(m_sourceFile.fileName());
if (extracted.isNull()) {
emit buildFailed();
return;
}
m_generator->rebuild(extracted, m_outFilename);
} }
void ForwarderController::cleanup() { void ForwarderController::cleanup() {
@ -223,7 +221,7 @@ bool ForwarderController::toolInstalled(const QString& tool) {
return false; return false;
} }
void ForwarderController::connectErrorFailure(QNetworkReply* reply) { void ForwarderController::connectReply(QNetworkReply* reply, Download download, void (ForwarderController::*next)(QNetworkReply*)) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() { connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() {
#else #else
@ -231,4 +229,12 @@ void ForwarderController::connectErrorFailure(QNetworkReply* reply) {
#endif #endif
emit buildFailed(); emit buildFailed();
}); });
connect(reply, &QNetworkReply::finished, this, [this, reply, next]() {
(this->*next)(reply);
});
connect(reply, &QNetworkReply::downloadProgress, this, [this, download](qint64 bytesReceived, qint64 bytesTotal) {
emit downloadProgress(download, bytesReceived, bytesTotal);
});
emit downloadStarted(download);
} }

View File

@ -12,7 +12,6 @@
#include <memory> #include <memory>
class QNetworkAccessManager;
class QNetworkReply; class QNetworkReply;
namespace QGBA { namespace QGBA {
@ -21,17 +20,27 @@ class ForwarderController : public QObject {
Q_OBJECT Q_OBJECT
public: public:
enum Download : int {
MANIFEST,
BASE,
FORWARDER_KIT
};
ForwarderController(QObject* parent = nullptr); ForwarderController(QObject* parent = nullptr);
void setGenerator(std::unique_ptr<ForwarderGenerator>&& generator); void setGenerator(std::unique_ptr<ForwarderGenerator>&& generator);
ForwarderGenerator* generator() { return m_generator.get(); } ForwarderGenerator* generator() { return m_generator.get(); }
QString channel() const { return m_channel; } QString channel() const { return m_channel; }
bool inProgress() const { return m_inProgress; }
public slots: public slots:
void startBuild(const QString& outFilename); void startBuild(const QString& outFilename);
signals: signals:
void buildStarted(bool needsForwarderKit);
void downloadStarted(Download which);
void downloadComplete(Download which);
void downloadProgress(Download which, qint64 bytesGotten, qint64 bytesTotal);
void buildComplete(); void buildComplete();
void buildFailed(); void buildFailed();
@ -47,11 +56,10 @@ private:
bool toolInstalled(const QString& tool); bool toolInstalled(const QString& tool);
void cleanup(); void cleanup();
void connectErrorFailure(QNetworkReply*); void connectReply(QNetworkReply*, Download, void (ForwarderController::*next)(QNetworkReply*));
QString m_channel{"dev"}; QString m_channel{"dev"};
QString m_outFilename; QString m_outFilename;
QNetworkAccessManager* m_netman;
std::unique_ptr<ForwarderGenerator> m_generator; std::unique_ptr<ForwarderGenerator> m_generator;
QFile m_sourceFile; QFile m_sourceFile;
bool m_inProgress = false; bool m_inProgress = false;

View File

@ -10,6 +10,10 @@
#include "ForwarderGenerator3DS.h" #include "ForwarderGenerator3DS.h"
#include "ForwarderGeneratorVita.h" #include "ForwarderGeneratorVita.h"
#include "utils.h"
#include "VFileDevice.h"
#include <mgba-util/vfs.h>
using namespace QGBA; using namespace QGBA;
@ -73,6 +77,40 @@ QString ForwarderGenerator::systemName(ForwarderGenerator::System system) {
return {}; return {};
} }
QString ForwarderGenerator::systemHumanName(ForwarderGenerator::System system) {
switch (system) {
case ForwarderGenerator::System::N3DS:
return tr("3DS");
case ForwarderGenerator::System::VITA:
return tr("Vita");
}
return {};
}
QString ForwarderGenerator::extract(const QString& archive) {
VDir* inArchive = VFileDevice::openArchive(archive);
if (!inArchive) {
return {};
}
bool gotFile = extractMatchingFile(inArchive, [this](VDirEntry* dirent) -> QString {
if (dirent->type(dirent) != VFS_FILE) {
return {};
}
QString filename(dirent->name(dirent));
if (!filename.endsWith("." + extension())) {
return {};
}
return "tmp." + extension();
});
inArchive->close(inArchive);
if (gotFile) {
return QLatin1String("tmp.") + extension();
}
return {};
}
QString ForwarderGenerator::base36(const QByteArray& bytes, int length) { QString ForwarderGenerator::base36(const QByteArray& bytes, int length) {
static const char* alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char* alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
QString buffer(length, 'X'); QString buffer(length, 'X');

View File

@ -40,12 +40,15 @@ public:
virtual QList<QPair<QString, QSize>> imageTypes() const = 0; virtual QList<QPair<QString, QSize>> imageTypes() const = 0;
virtual System system() const = 0; virtual System system() const = 0;
QString systemName() const { return systemName(system()); } QString systemName() const { return systemName(system()); }
QString systemHumanName() const { return systemHumanName(system()); }
virtual QString extension() const = 0; virtual QString extension() const = 0;
virtual QStringList externalTools() const { return {}; } virtual QStringList externalTools() const { return {}; }
static QString systemName(System); static QString systemName(System);
static QString systemHumanName(System);
virtual QString extract(const QString& archive);
virtual void rebuild(const QString& source, const QString& target) = 0; virtual void rebuild(const QString& source, const QString& target) = 0;
signals: signals:

View File

@ -6,8 +6,6 @@
#include "ForwarderGenerator3DS.h" #include "ForwarderGenerator3DS.h"
#include "ConfigController.h" #include "ConfigController.h"
#include "utils.h"
#include "VFileDevice.h"
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
@ -34,39 +32,11 @@ QList<QPair<QString, QSize>> ForwarderGenerator3DS::imageTypes() const {
} }
void ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) { void ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) {
m_cia = dumpCia(source); m_cia = source;
if (m_cia.isNull()) {
emit buildFailed();
return;
}
m_target = target; m_target = target;
extractCia(); 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() { void ForwarderGenerator3DS::extractCia() {
m_currentProc = std::make_unique<QProcess>(); m_currentProc = std::make_unique<QProcess>();
m_currentProc->setProgram("ctrtool"); m_currentProc->setProgram("ctrtool");

View File

@ -43,7 +43,6 @@ private slots:
void cleanup(); void cleanup();
private: private:
QString dumpCia(const QString& archive);
void init3dstoolArgs(QStringList& args, const QString& file, const QString& createType = {}); void init3dstoolArgs(QStringList& args, const QString& file, const QString& createType = {});
std::unique_ptr<QProcess> m_currentProc; std::unique_ptr<QProcess> m_currentProc;

View File

@ -8,7 +8,6 @@
#include <QFileInfo> #include <QFileInfo>
#include <QTemporaryFile> #include <QTemporaryFile>
#include "utils.h"
#include "VFileDevice.h" #include "VFileDevice.h"
#include <mgba-util/sfo.h> #include <mgba-util/sfo.h>
@ -30,15 +29,9 @@ QList<QPair<QString, QSize>> ForwarderGeneratorVita::imageTypes() const {
} }
void ForwarderGeneratorVita::rebuild(const QString& source, const QString& target) { void ForwarderGeneratorVita::rebuild(const QString& source, const QString& target) {
QString vpk = dumpVpk(source); QFile vpkFile(source);
if (vpk.isNull()) {
emit buildFailed();
return;
}
QFile vpkFile(vpk);
VDir* outdir = VDirOpenZip(target.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC); VDir* outdir = VDirOpenZip(target.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC);
if (outdir && !copyAssets(vpk, outdir)) { if (outdir && !copyAssets(source, outdir)) {
outdir->close(outdir); outdir->close(outdir);
outdir = nullptr; outdir = nullptr;
} }
@ -79,29 +72,6 @@ void ForwarderGeneratorVita::rebuild(const QString& source, const QString& targe
emit buildComplete(); emit buildComplete();
} }
QString ForwarderGeneratorVita::dumpVpk(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(".vpk")) {
return {};
}
return "tmp.vpk";
});
inArchive->close(inArchive);
if (gotFile) {
return QLatin1String("tmp.vpk");
}
return {};
}
bool ForwarderGeneratorVita::copyAssets(const QString& vpk, VDir* outdir) { bool ForwarderGeneratorVita::copyAssets(const QString& vpk, VDir* outdir) {
VDir* indir = VDirOpenZip(vpk.toLocal8Bit().constData(), O_RDONLY); VDir* indir = VDirOpenZip(vpk.toLocal8Bit().constData(), O_RDONLY);
if (!indir) { if (!indir) {

View File

@ -25,7 +25,6 @@ public:
void rebuild(const QString& source, const QString& target) override; void rebuild(const QString& source, const QString& target) override;
private: private:
QString dumpVpk(const QString& archive);
bool copyAssets(const QString& vpk, VDir* out); bool copyAssets(const QString& vpk, VDir* out);
QString makeSerial() const; QString makeSerial() const;
void writeSfo(VFile* out); void writeSfo(VFile* out);

View File

@ -32,13 +32,49 @@ ForwarderView::ForwarderView(QWidget* parent)
connect(m_ui.imageSelect, qOverload<int>(&QComboBox::currentIndexChanged), this, &ForwarderView::setActiveImage); connect(m_ui.imageSelect, qOverload<int>(&QComboBox::currentIndexChanged), this, &ForwarderView::setActiveImage);
connect(m_ui.imageBrowse, &QAbstractButton::clicked, this, &ForwarderView::selectImage); connect(m_ui.imageBrowse, &QAbstractButton::clicked, this, &ForwarderView::selectImage);
connect(&m_controller, &ForwarderController::buildComplete, this, &QDialog::accept); connect(&m_controller, &ForwarderController::buildComplete, this, [this]() {
QMessageBox* message = new QMessageBox(QMessageBox::Information, tr("Build finished"),
tr("Forwarder finished building"),
QMessageBox::Ok, parentWidget(), Qt::Sheet);
message->setAttribute(Qt::WA_DeleteOnClose);
message->show();
accept();
});
connect(&m_controller, &ForwarderController::buildFailed, this, [this]() { connect(&m_controller, &ForwarderController::buildFailed, this, [this]() {
QMessageBox* error = new QMessageBox(QMessageBox::Critical, tr("Build failed"), QMessageBox* error = new QMessageBox(QMessageBox::Critical, tr("Build failed"),
tr("Failed to build forwarder"), tr("Failed to build forwarder"),
QMessageBox::Ok, this, Qt::Sheet); QMessageBox::Ok, this, Qt::Sheet);
error->setAttribute(Qt::WA_DeleteOnClose); error->setAttribute(Qt::WA_DeleteOnClose);
error->show(); error->show();
m_ui.progressBar->setValue(0);
m_ui.progressBar->setEnabled(false);
validate();
});
connect(&m_controller, &ForwarderController::downloadStarted, this, [this](ForwarderController::Download download) {
m_currentDownload = download;
m_downloadProgress = 0;
if (download == ForwarderController::FORWARDER_KIT) {
m_needsForwarderKit = true;
}
updateProgress();
});
connect(&m_controller, &ForwarderController::downloadComplete, this, [this](ForwarderController::Download download) {
if (m_currentDownload != download) {
return;
}
m_downloadProgress = 1;
updateProgress();
});
connect(&m_controller, &ForwarderController::downloadProgress, this, [this](ForwarderController::Download download, qint64 bytesReceived, qint64 bytesTotal) {
if (m_currentDownload != download) {
return;
}
if (bytesTotal <= 0 || bytesTotal < bytesReceived) {
return;
}
m_downloadProgress = bytesReceived / static_cast<qreal>(bytesTotal);
updateProgress();
}); });
connect(m_ui.system3DS, &QAbstractButton::clicked, this, [this]() { connect(m_ui.system3DS, &QAbstractButton::clicked, this, [this]() {
@ -59,6 +95,13 @@ void ForwarderView::build() {
m_controller.generator()->setTitle(m_ui.title->text()); m_controller.generator()->setTitle(m_ui.title->text());
m_controller.generator()->setRom(m_ui.romFilename->text()); m_controller.generator()->setRom(m_ui.romFilename->text());
m_controller.startBuild(m_ui.outputFilename->text()); m_controller.startBuild(m_ui.outputFilename->text());
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
m_ui.progressBar->setEnabled(true);
m_currentDownload = ForwarderController::FORWARDER_KIT;
m_downloadProgress = 0;
m_needsForwarderKit = false;
updateProgress();
} }
void ForwarderView::validate() { void ForwarderView::validate() {
@ -80,6 +123,9 @@ void ForwarderView::validate() {
if (m_ui.baseType->currentIndex() != 1) { if (m_ui.baseType->currentIndex() != 1) {
valid = false; valid = false;
} }
if (m_controller.inProgress()) {
valid = false;
}
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
} }
@ -105,10 +151,18 @@ void ForwarderView::setSystem(ForwarderGenerator::System system) {
void ForwarderView::connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save, const QString& filter) { void ForwarderView::connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save, const QString& filter) {
connect(button, &QAbstractButton::clicked, lineEdit, [this, lineEdit, save, title, filter]() { connect(button, &QAbstractButton::clicked, lineEdit, [this, lineEdit, save, title, filter]() {
QString filename; QString filename;
QString usedFilter = filter;
if (filter.isEmpty()) {
// Use the forwarder type, if selected
ForwarderGenerator* generator = m_controller.generator();
if (generator) {
usedFilter = tr("%1 installable package (*.%2)").arg(generator->systemHumanName()).arg(generator->extension());
}
}
if (save) { if (save) {
filename = GBAApp::app()->getSaveFileName(this, title, filter); filename = GBAApp::app()->getSaveFileName(this, title, usedFilter);
} else { } else {
filename = GBAApp::app()->getOpenFileName(this, title, filter); filename = GBAApp::app()->getOpenFileName(this, title, usedFilter);
} }
if (filename.isEmpty()) { if (filename.isEmpty()) {
return; return;
@ -154,3 +208,25 @@ void ForwarderView::setActiveImage(int index) {
m_ui.imagePreview->setMaximumSize(m_activeSize); m_ui.imagePreview->setMaximumSize(m_activeSize);
m_ui.imagePreview->setPixmap(QPixmap::fromImage(m_controller.generator()->image(index))); m_ui.imagePreview->setPixmap(QPixmap::fromImage(m_controller.generator()->image(index)));
} }
void ForwarderView::updateProgress() {
switch (m_currentDownload) {
case ForwarderController::FORWARDER_KIT:
m_ui.progressBar->setValue(m_downloadProgress * 450);
break;
case ForwarderController::MANIFEST:
if (m_needsForwarderKit) {
m_ui.progressBar->setValue(450 + m_downloadProgress * 50);
} else {
m_ui.progressBar->setValue(m_downloadProgress * 100);
}
break;
case ForwarderController::BASE:
if (m_needsForwarderKit) {
m_ui.progressBar->setValue(500 + m_downloadProgress * 500);
} else {
m_ui.progressBar->setValue(100 + m_downloadProgress * 900);
}
break;
}
}

View File

@ -30,12 +30,17 @@ private:
void connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save = false, const QString& filter = {}); void connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save = false, const QString& filter = {});
void selectImage(); void selectImage();
void setActiveImage(int); void setActiveImage(int);
void updateProgress();
ForwarderController m_controller; ForwarderController m_controller;
QVector<QImage> m_images; QVector<QImage> m_images;
int m_currentImage; int m_currentImage;
QSize m_activeSize; QSize m_activeSize;
qreal m_downloadProgress;
ForwarderController::Download m_currentDownload;
bool m_needsForwarderKit;
Ui::ForwarderView m_ui; Ui::ForwarderView m_ui;
}; };

View File

@ -14,51 +14,6 @@
<string>Create forwarder</string> <string>Create forwarder</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>System</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item alignment="Qt::AlignHCenter">
<widget class="QRadioButton" name="system3DS">
<property name="text">
<string>3DS</string>
</property>
<attribute name="buttonGroup">
<string notr="true">system</string>
</attribute>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QRadioButton" name="systemVita">
<property name="text">
<string>Vita</string>
</property>
<attribute name="buttonGroup">
<string notr="true">system</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QGroupBox" name="groupBox_3"> <widget class="QGroupBox" name="groupBox_3">
<property name="title"> <property name="title">
@ -168,6 +123,51 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>System</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item alignment="Qt::AlignHCenter">
<widget class="QRadioButton" name="system3DS">
<property name="text">
<string>3DS</string>
</property>
<attribute name="buttonGroup">
<string notr="true">system</string>
</attribute>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QRadioButton" name="systemVita">
<property name="text">
<string>Vita</string>
</property>
<attribute name="buttonGroup">
<string notr="true">system</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2"> <item row="0" column="1" rowspan="2">
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="title"> <property name="title">
@ -399,6 +399,19 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2">
<widget class="QProgressBar" name="progressBar">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -19,6 +19,7 @@
#include <QFontDatabase> #include <QFontDatabase>
#include <QIcon> #include <QIcon>
#include <mgba/core/version.h>
#include <mgba/feature/updater.h> #include <mgba/feature/updater.h>
#include <mgba-util/socket.h> #include <mgba-util/socket.h>
#include <mgba-util/vfs.h> #include <mgba-util/vfs.h>
@ -81,6 +82,8 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
m_configController->updateOption("useDiscordPresence"); m_configController->updateOption("useDiscordPresence");
#endif #endif
m_netman.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
cleanupAfterUpdate(); cleanupAfterUpdate();
connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup); connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup);
@ -240,6 +243,19 @@ bool GBAApp::reloadGameDB() {
} }
#endif #endif
QNetworkAccessManager* GBAApp::netman() {
return &m_netman;
}
QNetworkReply* GBAApp::httpGet(const QUrl& url) {
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader,
QString("%1/%2 (+https://mgba.io) is definitely not Mozilla/5.0")
.arg(projectName)
.arg(projectVersion));
return m_netman.get(req);
}
qint64 GBAApp::submitWorkerJob(std::function<void ()> job, std::function<void ()> callback) { qint64 GBAApp::submitWorkerJob(std::function<void ()> job, std::function<void ()> callback) {
return submitWorkerJob(job, nullptr, callback); return submitWorkerJob(job, nullptr, callback);
} }

View File

@ -11,7 +11,8 @@
#include <QList> #include <QList>
#include <QMap> #include <QMap>
#include <QMultiMap> #include <QMultiMap>
#include <QObject> #include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRunnable> #include <QRunnable>
#include <QString> #include <QString>
#include <QThreadPool> #include <QThreadPool>
@ -70,6 +71,9 @@ public:
const NoIntroDB* gameDB() const { return m_db; } const NoIntroDB* gameDB() const { return m_db; }
bool reloadGameDB(); bool reloadGameDB();
QNetworkAccessManager* netman();
QNetworkReply* httpGet(const QUrl&);
qint64 submitWorkerJob(std::function<void ()> job, std::function<void ()> callback = {}); qint64 submitWorkerJob(std::function<void ()> job, std::function<void ()> callback = {});
qint64 submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback); qint64 submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback);
bool removeWorkerJob(qint64 jobId); bool removeWorkerJob(qint64 jobId);
@ -128,6 +132,8 @@ private:
QFont m_monospace; QFont m_monospace;
NoIntroDB* m_db = nullptr; NoIntroDB* m_db = nullptr;
QNetworkAccessManager m_netman;
}; };
} }