mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
b1f9a98937
1
CHANGES
1
CHANGES
|
@ -53,6 +53,7 @@ Other fixes:
|
|||
- VFS: Fix minizip write returning 0 on success instead of size
|
||||
Misc:
|
||||
- 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: Fix modern build with libepoxy (fixes mgba.io/i/2700)
|
||||
- Qt: Keep track of current pslette preset name (fixes mgba.io/i/2680)
|
||||
|
|
|
@ -154,6 +154,12 @@ void GBAHalt(struct GBA* gba);
|
|||
void GBAStop(struct GBA* gba);
|
||||
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
|
||||
struct mDebugger;
|
||||
void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger);
|
||||
|
|
|
@ -523,7 +523,7 @@ static bool _GBACoreLoadROM(struct mCore* core, struct VFile* vf) {
|
|||
#ifdef USE_ELF
|
||||
struct ELF* elf = ELFOpen(vf);
|
||||
if (elf) {
|
||||
if (ELFEntry(elf) == BASE_CART0) {
|
||||
if (GBAVerifyELFEntry(elf, BASE_CART0)) {
|
||||
GBALoadNull(core->board);
|
||||
}
|
||||
bool success = mCoreLoadELF(core, elf);
|
||||
|
|
|
@ -594,6 +594,63 @@ void GBADebug(struct GBA* gba, uint16_t flags) {
|
|||
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) {
|
||||
if (!vf) {
|
||||
return false;
|
||||
|
@ -605,7 +662,7 @@ bool GBAIsROM(struct VFile* vf) {
|
|||
uint32_t entry = ELFEntry(elf);
|
||||
bool isGBA = true;
|
||||
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);
|
||||
return isGBA;
|
||||
}
|
||||
|
@ -661,7 +718,7 @@ bool GBAIsMB(struct VFile* vf) {
|
|||
#ifdef USE_ELF
|
||||
struct ELF* elf = ELFOpen(vf);
|
||||
if (elf) {
|
||||
bool isMB = ELFEntry(elf) == BASE_WORKING_RAM + 0xC0;
|
||||
bool isMB = GBAVerifyELFEntry(elf, BASE_WORKING_RAM + 0xC0);
|
||||
ELFClose(elf);
|
||||
return isMB;
|
||||
}
|
||||
|
|
|
@ -8,16 +8,17 @@
|
|||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "GBAApp.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AbstractUpdater::AbstractUpdater(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_netman(new QNetworkAccessManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractUpdater::checkUpdate() {
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(manifestLocation()));
|
||||
QNetworkReply* reply = GBAApp::app()->httpGet(manifestLocation());
|
||||
chaseRedirects(reply, &AbstractUpdater::manifestDownloaded);
|
||||
}
|
||||
|
||||
|
@ -36,7 +37,7 @@ void AbstractUpdater::downloadUpdate() {
|
|||
return;
|
||||
}
|
||||
m_isUpdating = true;
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(url));
|
||||
QNetworkReply* reply = GBAApp::app()->httpGet(url);
|
||||
chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,7 @@ void AbstractUpdater::chaseRedirects(QNetworkReply* reply, void (AbstractUpdater
|
|||
connect(reply, &QNetworkReply::finished, this, [this, reply, cb]() {
|
||||
// TODO: check domains, etc
|
||||
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);
|
||||
} else {
|
||||
(this->*cb)(reply);
|
||||
|
@ -69,7 +70,7 @@ void AbstractUpdater::manifestDownloaded(QNetworkReply* reply) {
|
|||
if (!url.isValid()) {
|
||||
emit updateDone(false);
|
||||
} else {
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(url));
|
||||
QNetworkReply* reply = GBAApp::app()->httpGet(url);
|
||||
chaseRedirects(reply, &AbstractUpdater::updateDownloaded);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -44,7 +43,6 @@ private:
|
|||
void updateDownloaded(QNetworkReply*);
|
||||
|
||||
bool m_isUpdating = false;
|
||||
QNetworkAccessManager* m_netman;
|
||||
QByteArray m_manifest;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <QNetworkReply>
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "GBAApp.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
|
@ -29,10 +30,8 @@ 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, &ForwarderController::cleanup);
|
||||
connect(this, &ForwarderController::buildComplete, this, &ForwarderController::cleanup);
|
||||
}
|
||||
|
@ -83,14 +82,12 @@ void ForwarderController::downloadForwarderKit() {
|
|||
emit buildFailed();
|
||||
return;
|
||||
#endif
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl(fkUrl)));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
gotForwarderKit(reply);
|
||||
});
|
||||
connectErrorFailure(reply);
|
||||
QNetworkReply* reply = GBAApp::app()->httpGet(QUrl(fkUrl));
|
||||
connectReply(reply, FORWARDER_KIT, &ForwarderController::gotForwarderKit);
|
||||
}
|
||||
|
||||
void ForwarderController::gotForwarderKit(QNetworkReply* reply) {
|
||||
emit downloadComplete(FORWARDER_KIT);
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
|
@ -134,14 +131,12 @@ void ForwarderController::gotForwarderKit(QNetworkReply* reply) {
|
|||
}
|
||||
|
||||
void ForwarderController::downloadManifest() {
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl("https://mgba.io/latest.ini")));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
gotManifest(reply);
|
||||
});
|
||||
connectErrorFailure(reply);
|
||||
QNetworkReply* reply = GBAApp::app()->httpGet(QUrl("https://mgba.io/latest.ini"));
|
||||
connectReply(reply, MANIFEST, &ForwarderController::gotManifest);
|
||||
}
|
||||
|
||||
void ForwarderController::gotManifest(QNetworkReply* reply) {
|
||||
emit downloadComplete(MANIFEST);
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
|
@ -177,20 +172,17 @@ void ForwarderController::downloadBuild(const QUrl& url) {
|
|||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(url));
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
gotBuild(reply);
|
||||
});
|
||||
QNetworkReply* reply = GBAApp::app()->httpGet(url);
|
||||
|
||||
connectReply(reply, BASE, &ForwarderController::gotBuild);
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
|
||||
QByteArray data = reply->readAll();
|
||||
m_sourceFile.write(data);
|
||||
});
|
||||
connectErrorFailure(reply);
|
||||
}
|
||||
|
||||
void ForwarderController::gotBuild(QNetworkReply* reply) {
|
||||
emit downloadComplete(BASE);
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
|
@ -199,7 +191,13 @@ void ForwarderController::gotBuild(QNetworkReply* reply) {
|
|||
QByteArray data = reply->readAll();
|
||||
m_sourceFile.write(data);
|
||||
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() {
|
||||
|
@ -223,7 +221,7 @@ bool ForwarderController::toolInstalled(const QString& tool) {
|
|||
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))
|
||||
connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() {
|
||||
#else
|
||||
|
@ -231,4 +229,12 @@ void ForwarderController::connectErrorFailure(QNetworkReply* reply) {
|
|||
#endif
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -21,17 +20,27 @@ class ForwarderController : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Download : int {
|
||||
MANIFEST,
|
||||
BASE,
|
||||
FORWARDER_KIT
|
||||
};
|
||||
ForwarderController(QObject* parent = nullptr);
|
||||
|
||||
void setGenerator(std::unique_ptr<ForwarderGenerator>&& generator);
|
||||
ForwarderGenerator* generator() { return m_generator.get(); }
|
||||
|
||||
QString channel() const { return m_channel; }
|
||||
bool inProgress() const { return m_inProgress; }
|
||||
|
||||
public slots:
|
||||
void startBuild(const QString& outFilename);
|
||||
|
||||
signals:
|
||||
void buildStarted(bool needsForwarderKit);
|
||||
void downloadStarted(Download which);
|
||||
void downloadComplete(Download which);
|
||||
void downloadProgress(Download which, qint64 bytesGotten, qint64 bytesTotal);
|
||||
void buildComplete();
|
||||
void buildFailed();
|
||||
|
||||
|
@ -47,11 +56,10 @@ private:
|
|||
bool toolInstalled(const QString& tool);
|
||||
void cleanup();
|
||||
|
||||
void connectErrorFailure(QNetworkReply*);
|
||||
void connectReply(QNetworkReply*, Download, void (ForwarderController::*next)(QNetworkReply*));
|
||||
|
||||
QString m_channel{"dev"};
|
||||
QString m_outFilename;
|
||||
QNetworkAccessManager* m_netman;
|
||||
std::unique_ptr<ForwarderGenerator> m_generator;
|
||||
QFile m_sourceFile;
|
||||
bool m_inProgress = false;
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
#include "ForwarderGenerator3DS.h"
|
||||
#include "ForwarderGeneratorVita.h"
|
||||
#include "utils.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -73,6 +77,40 @@ QString ForwarderGenerator::systemName(ForwarderGenerator::System system) {
|
|||
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) {
|
||||
static const char* alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
QString buffer(length, 'X');
|
||||
|
|
|
@ -40,12 +40,15 @@ public:
|
|||
virtual QList<QPair<QString, QSize>> imageTypes() const = 0;
|
||||
virtual System system() const = 0;
|
||||
QString systemName() const { return systemName(system()); }
|
||||
QString systemHumanName() const { return systemHumanName(system()); }
|
||||
virtual QString extension() const = 0;
|
||||
|
||||
virtual QStringList externalTools() const { return {}; }
|
||||
|
||||
static QString systemName(System);
|
||||
static QString systemHumanName(System);
|
||||
|
||||
virtual QString extract(const QString& archive);
|
||||
virtual void rebuild(const QString& source, const QString& target) = 0;
|
||||
|
||||
signals:
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
#include "ForwarderGenerator3DS.h"
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "utils.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
@ -34,39 +32,11 @@ QList<QPair<QString, QSize>> ForwarderGenerator3DS::imageTypes() const {
|
|||
}
|
||||
|
||||
void ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) {
|
||||
m_cia = dumpCia(source);
|
||||
if (m_cia.isNull()) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
m_cia = source;
|
||||
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");
|
||||
|
|
|
@ -43,7 +43,6 @@ private slots:
|
|||
void cleanup();
|
||||
|
||||
private:
|
||||
QString dumpCia(const QString& archive);
|
||||
void init3dstoolArgs(QStringList& args, const QString& file, const QString& createType = {});
|
||||
|
||||
std::unique_ptr<QProcess> m_currentProc;
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <QFileInfo>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "utils.h"
|
||||
#include "VFileDevice.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) {
|
||||
QString vpk = dumpVpk(source);
|
||||
if (vpk.isNull()) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
QFile vpkFile(vpk);
|
||||
QFile vpkFile(source);
|
||||
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 = nullptr;
|
||||
}
|
||||
|
@ -79,29 +72,6 @@ void ForwarderGeneratorVita::rebuild(const QString& source, const QString& targe
|
|||
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) {
|
||||
VDir* indir = VDirOpenZip(vpk.toLocal8Bit().constData(), O_RDONLY);
|
||||
if (!indir) {
|
||||
|
|
|
@ -25,7 +25,6 @@ public:
|
|||
void rebuild(const QString& source, const QString& target) override;
|
||||
|
||||
private:
|
||||
QString dumpVpk(const QString& archive);
|
||||
bool copyAssets(const QString& vpk, VDir* out);
|
||||
QString makeSerial() const;
|
||||
void writeSfo(VFile* out);
|
||||
|
|
|
@ -32,13 +32,49 @@ ForwarderView::ForwarderView(QWidget* parent)
|
|||
connect(m_ui.imageSelect, qOverload<int>(&QComboBox::currentIndexChanged), this, &ForwarderView::setActiveImage);
|
||||
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]() {
|
||||
QMessageBox* error = new QMessageBox(QMessageBox::Critical, tr("Build failed"),
|
||||
tr("Failed to build forwarder"),
|
||||
QMessageBox::Ok, this, Qt::Sheet);
|
||||
error->setAttribute(Qt::WA_DeleteOnClose);
|
||||
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]() {
|
||||
|
@ -59,6 +95,13 @@ void ForwarderView::build() {
|
|||
m_controller.generator()->setTitle(m_ui.title->text());
|
||||
m_controller.generator()->setRom(m_ui.romFilename->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() {
|
||||
|
@ -80,6 +123,9 @@ void ForwarderView::validate() {
|
|||
if (m_ui.baseType->currentIndex() != 1) {
|
||||
valid = false;
|
||||
}
|
||||
if (m_controller.inProgress()) {
|
||||
valid = false;
|
||||
}
|
||||
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) {
|
||||
connect(button, &QAbstractButton::clicked, lineEdit, [this, lineEdit, save, title, filter]() {
|
||||
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) {
|
||||
filename = GBAApp::app()->getSaveFileName(this, title, filter);
|
||||
filename = GBAApp::app()->getSaveFileName(this, title, usedFilter);
|
||||
} else {
|
||||
filename = GBAApp::app()->getOpenFileName(this, title, filter);
|
||||
filename = GBAApp::app()->getOpenFileName(this, title, usedFilter);
|
||||
}
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
|
@ -154,3 +208,25 @@ void ForwarderView::setActiveImage(int index) {
|
|||
m_ui.imagePreview->setMaximumSize(m_activeSize);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,17 @@ private:
|
|||
void connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save = false, const QString& filter = {});
|
||||
void selectImage();
|
||||
void setActiveImage(int);
|
||||
void updateProgress();
|
||||
|
||||
ForwarderController m_controller;
|
||||
QVector<QImage> m_images;
|
||||
int m_currentImage;
|
||||
QSize m_activeSize;
|
||||
|
||||
qreal m_downloadProgress;
|
||||
ForwarderController::Download m_currentDownload;
|
||||
bool m_needsForwarderKit;
|
||||
|
||||
Ui::ForwarderView m_ui;
|
||||
};
|
||||
|
||||
|
|
|
@ -14,51 +14,6 @@
|
|||
<string>Create forwarder</string>
|
||||
</property>
|
||||
<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">
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
|
@ -168,6 +123,51 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
|
@ -399,6 +399,19 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QFontDatabase>
|
||||
#include <QIcon>
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/feature/updater.h>
|
||||
#include <mgba-util/socket.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
@ -81,6 +82,8 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
|
|||
m_configController->updateOption("useDiscordPresence");
|
||||
#endif
|
||||
|
||||
m_netman.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
|
||||
cleanupAfterUpdate();
|
||||
|
||||
connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup);
|
||||
|
@ -240,6 +243,19 @@ bool GBAApp::reloadGameDB() {
|
|||
}
|
||||
#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) {
|
||||
return submitWorkerJob(job, nullptr, callback);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QRunnable>
|
||||
#include <QString>
|
||||
#include <QThreadPool>
|
||||
|
@ -70,6 +71,9 @@ public:
|
|||
const NoIntroDB* gameDB() const { return m_db; }
|
||||
bool reloadGameDB();
|
||||
|
||||
QNetworkAccessManager* netman();
|
||||
QNetworkReply* httpGet(const QUrl&);
|
||||
|
||||
qint64 submitWorkerJob(std::function<void ()> job, std::function<void ()> callback = {});
|
||||
qint64 submitWorkerJob(std::function<void ()> job, QObject* context, std::function<void ()> callback);
|
||||
bool removeWorkerJob(qint64 jobId);
|
||||
|
@ -128,6 +132,8 @@ private:
|
|||
QFont m_monospace;
|
||||
|
||||
NoIntroDB* m_db = nullptr;
|
||||
|
||||
QNetworkAccessManager m_netman;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue