mirror of https://github.com/mgba-emu/mgba.git
Qt: Add forwarder UI and Vita backend (closes #2267)
This commit is contained in:
parent
fec87062ca
commit
56c9065f70
|
@ -94,6 +94,11 @@ set(SOURCE_FILES
|
|||
Display.cpp
|
||||
DisplayGL.cpp
|
||||
DisplayQt.cpp
|
||||
ForwarderController.cpp
|
||||
ForwarderGenerator.cpp
|
||||
ForwarderGenerator3DS.cpp
|
||||
ForwarderGeneratorVita.cpp
|
||||
ForwarderView.cpp
|
||||
FrameView.cpp
|
||||
GBAApp.cpp
|
||||
GBAKeyEditor.cpp
|
||||
|
@ -151,6 +156,7 @@ set(UI_FILES
|
|||
CheatsView.ui
|
||||
DebuggerConsole.ui
|
||||
DolphinConnector.ui
|
||||
ForwarderView.ui
|
||||
FrameView.ui
|
||||
GIFView.ui
|
||||
IOViewer.ui
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ForwarderController.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "ConfigController.h"
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba/feature/updater.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
ForwarderController::ForwarderController(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_netman(new QNetworkAccessManager(this))
|
||||
{
|
||||
m_netman->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
connect(this, &ForwarderController::buildFailed, this, [this]() {
|
||||
m_inProgress = false;
|
||||
});
|
||||
connect(this, &ForwarderController::buildComplete, this, [this]() {
|
||||
m_inProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
void ForwarderController::startBuild(const QString& outFilename) {
|
||||
if (m_inProgress) {
|
||||
return;
|
||||
}
|
||||
m_inProgress = true;
|
||||
m_outFilename = outFilename;
|
||||
downloadManifest();
|
||||
}
|
||||
|
||||
void ForwarderController::downloadManifest() {
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(QUrl("https://mgba.io/latest.ini")));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
gotManifest(reply);
|
||||
});
|
||||
connect(reply, &QNetworkReply::errorOccurred, this, [this, reply]() {
|
||||
emit buildFailed();
|
||||
});
|
||||
}
|
||||
|
||||
void ForwarderController::gotManifest(QNetworkReply* reply) {
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray manifest = reply->readAll();
|
||||
QString platform = m_generator->systemName();
|
||||
|
||||
mUpdaterContext context;
|
||||
if (!mUpdaterInit(&context, manifest.constData())) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
QString bucket = QLatin1String(mUpdaterGetBucket(&context));
|
||||
|
||||
mUpdate update;
|
||||
mUpdaterGetUpdateForChannel(&context, platform.toUtf8().constData(), m_channel.toUtf8().constData(), &update);
|
||||
|
||||
downloadBuild({bucket + update.path});
|
||||
mUpdaterDeinit(&context);
|
||||
}
|
||||
|
||||
void ForwarderController::downloadBuild(const QUrl& url) {
|
||||
QString extension(QFileInfo(url.path()).suffix());
|
||||
// TODO: cache this
|
||||
QString configDir(ConfigController::configDir());
|
||||
m_sourceFile.setFileName(QString("%1/%2-%3-%4.%5").arg(configDir)
|
||||
.arg(projectName)
|
||||
.arg(m_generator->systemName())
|
||||
.arg(channel())
|
||||
.arg(extension));
|
||||
if (!m_sourceFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
QNetworkReply* reply = m_netman->get(QNetworkRequest(url));
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
gotBuild(reply);
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
|
||||
QByteArray data = reply->readAll();
|
||||
m_sourceFile.write(data);
|
||||
});
|
||||
}
|
||||
|
||||
void ForwarderController::gotBuild(QNetworkReply* reply) {
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
emit buildFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
#include "ForwarderGenerator.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ForwarderController(QObject* parent = nullptr);
|
||||
|
||||
void setGenerator(std::unique_ptr<ForwarderGenerator>&& generator) { m_generator = std::move(generator); }
|
||||
ForwarderGenerator* generator() { return m_generator.get(); }
|
||||
|
||||
QString channel() const { return m_channel; }
|
||||
|
||||
public slots:
|
||||
void startBuild(const QString& outFilename);
|
||||
|
||||
signals:
|
||||
void buildComplete();
|
||||
void buildFailed();
|
||||
|
||||
private slots:
|
||||
void gotManifest(QNetworkReply*);
|
||||
void gotBuild(QNetworkReply*);
|
||||
|
||||
private:
|
||||
void downloadManifest();
|
||||
void downloadBuild(const QUrl&);
|
||||
|
||||
QString m_channel{"dev"};
|
||||
QString m_outFilename;
|
||||
QNetworkAccessManager* m_netman;
|
||||
std::unique_ptr<ForwarderGenerator> m_generator;
|
||||
QFile m_sourceFile;
|
||||
bool m_inProgress = false;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ForwarderGenerator.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
|
||||
#include "ForwarderGenerator3DS.h"
|
||||
#include "ForwarderGeneratorVita.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
std::unique_ptr<ForwarderGenerator> ForwarderGenerator::createForSystem(System system) {
|
||||
switch (system) {
|
||||
case System::N3DS:
|
||||
return std::make_unique<ForwarderGenerator3DS>();
|
||||
case System::VITA:
|
||||
return std::make_unique<ForwarderGeneratorVita>();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ForwarderGenerator::ForwarderGenerator(int imageTypes, QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_images.resize(imageTypes);
|
||||
}
|
||||
|
||||
void ForwarderGenerator::setImage(int index, const QImage& image) {
|
||||
if (index < 0 || index >= m_images.count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_images[index] = image;
|
||||
}
|
||||
|
||||
QImage ForwarderGenerator::image(int index) const {
|
||||
if (index >= m_images.size()) {
|
||||
return {};
|
||||
}
|
||||
return m_images[index];
|
||||
}
|
||||
|
||||
QByteArray ForwarderGenerator::hashRom() const {
|
||||
if (m_romPath.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QFile romFile(m_romPath);
|
||||
if (!romFile.open(QIODevice::ReadOnly)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Sha256);
|
||||
if (!hash.addData(&romFile)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return hash.result();
|
||||
}
|
||||
|
||||
QString ForwarderGenerator::systemName(ForwarderGenerator::System system) {
|
||||
switch (system) {
|
||||
case ForwarderGenerator::System::N3DS:
|
||||
return QLatin1String("3ds");
|
||||
case ForwarderGenerator::System::VITA:
|
||||
return QLatin1String("vita");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderGenerator : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class System {
|
||||
N3DS,
|
||||
VITA,
|
||||
};
|
||||
|
||||
static std::unique_ptr<ForwarderGenerator> createForSystem(System);
|
||||
|
||||
void setTitle(const QString& title) { m_title = title; }
|
||||
void setRom(const QString& path) { m_romPath = path; }
|
||||
void setImage(int index, const QImage&);
|
||||
|
||||
QString title() const { return m_title; }
|
||||
QString rom() const { return m_romPath; }
|
||||
QImage image(int index) const;
|
||||
|
||||
QByteArray hashRom() const;
|
||||
|
||||
virtual QList<QPair<QString, QSize>> imageTypes() const = 0;
|
||||
virtual System system() const = 0;
|
||||
QString systemName() const { return systemName(system()); }
|
||||
virtual QString extension() const = 0;
|
||||
|
||||
static QString systemName(System);
|
||||
|
||||
virtual bool rebuild(const QString& source, const QString& target) = 0;
|
||||
|
||||
protected:
|
||||
ForwarderGenerator(int imageTypes, QObject* parent = nullptr);
|
||||
|
||||
private:
|
||||
QString m_title;
|
||||
QString m_romPath;
|
||||
QVector<QImage> m_images;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ForwarderGenerator3DS.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
ForwarderGenerator3DS::ForwarderGenerator3DS()
|
||||
: ForwarderGenerator(2)
|
||||
{
|
||||
}
|
||||
|
||||
QList<QPair<QString, QSize>> ForwarderGenerator3DS::imageTypes() const {
|
||||
return {
|
||||
{ tr("Icon"), QSize(48, 48) },
|
||||
{ tr("Banner"), QSize(256, 128) }
|
||||
};
|
||||
}
|
||||
|
||||
bool ForwarderGenerator3DS::rebuild(const QString& source, const QString& target) {
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include "ForwarderGenerator.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderGenerator3DS final : public ForwarderGenerator {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ForwarderGenerator3DS();
|
||||
|
||||
QList<QPair<QString, QSize>> imageTypes() const override;
|
||||
System system() const override { return System::N3DS; }
|
||||
QString extension() const override { return QLatin1String("cia"); }
|
||||
|
||||
bool rebuild(const QString& source, const QString& target) override;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ForwarderGeneratorVita.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <mgba-util/sfo.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
ForwarderGeneratorVita::ForwarderGeneratorVita()
|
||||
: ForwarderGenerator(3)
|
||||
{
|
||||
}
|
||||
|
||||
QList<QPair<QString, QSize>> ForwarderGeneratorVita::imageTypes() const {
|
||||
return {
|
||||
{ tr("Bubble"), QSize(128, 128) },
|
||||
{ tr("Background"), QSize(840, 500) },
|
||||
{ tr("Startup"), QSize(280, 158) }
|
||||
};
|
||||
}
|
||||
|
||||
bool ForwarderGeneratorVita::rebuild(const QString& source, const QString& target) {
|
||||
QString vpk = dumpVpk(source);
|
||||
if (vpk.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile vpkFile(vpk);
|
||||
VDir* outdir = VDirOpenZip(target.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (outdir && !copyAssets(vpk, outdir)) {
|
||||
outdir->close(outdir);
|
||||
outdir = nullptr;
|
||||
}
|
||||
vpkFile.remove();
|
||||
if (!outdir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VFile* sfo = outdir->openFile(outdir, "sce_sys/param.sfo", O_WRONLY);
|
||||
writeSfo(sfo);
|
||||
sfo->close(sfo);
|
||||
|
||||
QFileInfo info(rom());
|
||||
QByteArray buffer(info.fileName().toUtf8());
|
||||
VFile* filename = outdir->openFile(outdir, "filename", O_WRONLY);
|
||||
filename->write(filename, buffer.constData(), buffer.size());
|
||||
filename->close(filename);
|
||||
|
||||
VFile* romfileOut = outdir->openFile(outdir, buffer.constData(), O_WRONLY);
|
||||
VFileDevice romfileIn(rom(), QIODevice::ReadOnly);
|
||||
VFileDevice::copyFile(romfileIn, romfileOut);
|
||||
romfileIn.close();
|
||||
romfileOut->close(romfileOut);
|
||||
|
||||
if (!image(0).isNull()) {
|
||||
injectImage(outdir, "sce_sys/icon0.png", 0);
|
||||
}
|
||||
if (!image(1).isNull()) {
|
||||
injectImage(outdir, "sce_sys/livearea/contents/bg.png", 1);
|
||||
}
|
||||
if (!image(2).isNull()) {
|
||||
injectImage(outdir, "sce_sys/livearea/contents/startup.png", 2);
|
||||
}
|
||||
|
||||
outdir->close(outdir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ForwarderGeneratorVita::dumpVpk(const QString& archive) {
|
||||
bool gotFile = false;
|
||||
|
||||
VDir* inArchive = VFileDevice::openArchive(archive);
|
||||
if (!inArchive) {
|
||||
return {};
|
||||
}
|
||||
for (VDirEntry* dirent = inArchive->listNext(inArchive); dirent; dirent = inArchive->listNext(inArchive)) {
|
||||
if (dirent->type(dirent) != VFS_FILE) {
|
||||
continue;
|
||||
}
|
||||
QString filename(dirent->name(dirent));
|
||||
if (!filename.endsWith(".vpk")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VFile* outfile = VFileOpen("tmp.vpk", O_WRONLY | O_TRUNC | O_CREAT);
|
||||
VFile* vpk = inArchive->openFile(inArchive, dirent->name(dirent), O_RDONLY);
|
||||
VFileDevice::copyFile(vpk, outfile);
|
||||
vpk->close(vpk);
|
||||
outfile->close(outfile);
|
||||
gotFile = true;
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
for (VDirEntry* dirent = indir->listNext(indir); dirent; dirent = indir->listNext(indir)) {
|
||||
if (dirent->name(dirent) == QLatin1String("sce_sys/param.sfo")) {
|
||||
continue;
|
||||
}
|
||||
if (dirent->name(dirent) == QLatin1String("sce_sys/icon0.png") && !image(0).isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (dirent->name(dirent) == QLatin1String("sce_sys/livearea/contents/bg.png") && !image(1).isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (dirent->name(dirent) == QLatin1String("sce_sys/livearea/contents/startup.png") && !image(2).isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (dirent->type(dirent) != VFS_FILE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VFile* infile = indir->openFile(indir, dirent->name(dirent), O_RDONLY);
|
||||
if (!infile) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
VFile* outfile = outdir->openFile(outdir, dirent->name(dirent), O_WRONLY);
|
||||
if (!outfile) {
|
||||
infile->close(infile);
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
VFileDevice::copyFile(infile, outfile);
|
||||
|
||||
infile->close(infile);
|
||||
outfile->close(outfile);
|
||||
}
|
||||
|
||||
indir->close(indir);
|
||||
return ok;
|
||||
}
|
||||
|
||||
QString ForwarderGeneratorVita::makeSerial() const {
|
||||
QByteArray hash = hashRom();
|
||||
quint32 hashBits = (hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3];
|
||||
|
||||
QString serial("MFXXXXXXX");
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
static const char alphabet[37] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
serial[i + 2] = alphabet[hashBits % 36];
|
||||
hashBits /= 36;
|
||||
}
|
||||
|
||||
return serial;
|
||||
}
|
||||
|
||||
void ForwarderGeneratorVita::writeSfo(VFile* out) {
|
||||
Table sfo;
|
||||
QByteArray serial(makeSerial().toLocal8Bit());
|
||||
QByteArray titleBytes(title().toUtf8());
|
||||
SfoInit(&sfo);
|
||||
SfoSetTitle(&sfo, titleBytes.constData());
|
||||
SfoAddStrValue(&sfo, "TITLE_ID", serial.constData());
|
||||
SfoWrite(&sfo, out);
|
||||
SfoDeinit(&sfo);
|
||||
}
|
||||
|
||||
void ForwarderGeneratorVita::injectImage(VDir* out, const char* name, int index) {
|
||||
VFile* outfile = out->openFile(out, name, O_WRONLY);
|
||||
VFileDevice outdev(outfile);
|
||||
image(index).convertToFormat(QImage::Format_Indexed8).save(&outdev, "PNG");
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include "ForwarderGenerator.h"
|
||||
|
||||
struct VDir;
|
||||
struct VFile;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderGeneratorVita final : public ForwarderGenerator {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ForwarderGeneratorVita();
|
||||
|
||||
QList<QPair<QString, QSize>> imageTypes() const override;
|
||||
System system() const override { return System::VITA; }
|
||||
QString extension() const override { return QLatin1String("vpk"); }
|
||||
|
||||
bool 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);
|
||||
void injectImage(VDir* out, const char* name, int index);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/ */
|
||||
#include "ForwarderView.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QResizeEvent>
|
||||
|
||||
#include "ForwarderGenerator.h"
|
||||
#include "GBAApp.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
ForwarderView::ForwarderView(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
connectBrowseButton(m_ui.romBrowse, m_ui.romFilename, tr("Select ROM file"), false, romFilters());
|
||||
connectBrowseButton(m_ui.outputBrowse, m_ui.outputFilename, tr("Select output filename"), true);
|
||||
connectBrowseButton(m_ui.baseBrowse, m_ui.baseFilename, tr("Select base file"));
|
||||
|
||||
connect(m_ui.romFilename, &QLineEdit::textChanged, this, &ForwarderView::validate);
|
||||
connect(m_ui.outputFilename, &QLineEdit::textChanged, this, &ForwarderView::validate);
|
||||
connect(m_ui.baseFilename, &QLineEdit::textChanged, this, &ForwarderView::validate);
|
||||
connect(m_ui.title, &QLineEdit::textChanged, this, &ForwarderView::validate);
|
||||
connect(m_ui.baseType, qOverload<int>(&QComboBox::currentIndexChanged), this, &ForwarderView::validate);
|
||||
|
||||
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::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();
|
||||
});
|
||||
|
||||
connect(m_ui.system3DS, &QAbstractButton::clicked, this, [this]() {
|
||||
setSystem(ForwarderGenerator::System::N3DS);
|
||||
});
|
||||
connect(m_ui.systemVita, &QAbstractButton::clicked, this, [this]() {
|
||||
setSystem(ForwarderGenerator::System::VITA);
|
||||
});
|
||||
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, [this]() {
|
||||
m_controller.generator()->setRom(m_ui.romFilename->text());
|
||||
m_controller.startBuild(m_ui.outputFilename->text());
|
||||
});
|
||||
}
|
||||
|
||||
void ForwarderView::build() {
|
||||
if (!m_controller.generator()) {
|
||||
return;
|
||||
}
|
||||
m_controller.generator()->setTitle(m_ui.title->text());
|
||||
m_controller.generator()->setRom(m_ui.romFilename->text());
|
||||
m_controller.startBuild(m_ui.outputFilename->text());
|
||||
}
|
||||
|
||||
void ForwarderView::validate() {
|
||||
bool valid = true;
|
||||
if (m_ui.romFilename->text().isEmpty()) {
|
||||
valid = false;
|
||||
} else if (!QFileInfo(m_ui.romFilename->text()).exists()) {
|
||||
valid = false;
|
||||
}
|
||||
if (m_ui.outputFilename->text().isEmpty()) {
|
||||
valid = false;
|
||||
}
|
||||
if (m_ui.title->text().isEmpty()) {
|
||||
valid = false;
|
||||
}
|
||||
if (!m_ui.system->checkedButton()) {
|
||||
valid = false;
|
||||
}
|
||||
if (m_ui.baseType->currentIndex() != 1) {
|
||||
valid = false;
|
||||
}
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
|
||||
}
|
||||
|
||||
void ForwarderView::setSystem(ForwarderGenerator::System system) {
|
||||
m_controller.setGenerator(ForwarderGenerator::createForSystem(system));
|
||||
auto types = m_controller.generator()->imageTypes();
|
||||
m_images.clear();
|
||||
m_images.resize(types.count());
|
||||
m_ui.imageSelect->clear();
|
||||
for (const auto& pair : types) {
|
||||
m_ui.imageSelect->addItem(pair.first);
|
||||
}
|
||||
m_ui.imageSelect->setEnabled(true);
|
||||
m_ui.imagePreview->setEnabled(true);
|
||||
m_ui.imageBrowse->setEnabled(true);
|
||||
m_ui.imagesLabel->setEnabled(true);
|
||||
m_ui.preferredLabel->setEnabled(true);
|
||||
m_ui.preferredWidth->setEnabled(true);
|
||||
m_ui.preferredX->setEnabled(true);
|
||||
m_ui.preferredHeight->setEnabled(true);
|
||||
}
|
||||
|
||||
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;
|
||||
if (save) {
|
||||
filename = GBAApp::app()->getSaveFileName(this, title, filter);
|
||||
} else {
|
||||
filename = GBAApp::app()->getOpenFileName(this, title, filter);
|
||||
}
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
lineEdit->setText(filename);
|
||||
});
|
||||
}
|
||||
|
||||
void ForwarderView::selectImage() {
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select an image"), {});
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image(filename);
|
||||
if (image.isNull()) {
|
||||
return;
|
||||
}
|
||||
image = image.scaled(m_activeSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
m_ui.imagePreview->setPixmap(QPixmap::fromImage(image));
|
||||
m_ui.useDefaultImage->setChecked(false);
|
||||
m_controller.generator()->setImage(m_currentImage, image);
|
||||
}
|
||||
|
||||
void ForwarderView::setActiveImage(int index) {
|
||||
if (index < 0) {
|
||||
m_currentImage = -1;
|
||||
m_activeSize = QSize();
|
||||
return;
|
||||
}
|
||||
if (!m_controller.generator()) {
|
||||
return;
|
||||
}
|
||||
auto types = m_controller.generator()->imageTypes();
|
||||
if (index >= types.count()) {
|
||||
return;
|
||||
}
|
||||
m_currentImage = index;
|
||||
m_activeSize = types[index].second;
|
||||
m_ui.preferredWidth->setText(QString::number(m_activeSize.width()));
|
||||
m_ui.preferredHeight->setText(QString::number(m_activeSize.height()));
|
||||
m_ui.imagePreview->setMaximumSize(m_activeSize);
|
||||
m_ui.imagePreview->setPixmap(QPixmap::fromImage(m_controller.generator()->image(index)));
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include "ForwarderController.h"
|
||||
#include "ForwarderGenerator.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QVector>
|
||||
|
||||
#include "ui_ForwarderView.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ForwarderView : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ForwarderView(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void build();
|
||||
void validate();
|
||||
|
||||
private:
|
||||
void setSystem(ForwarderGenerator::System);
|
||||
void connectBrowseButton(QAbstractButton* button, QLineEdit* lineEdit, const QString& title, bool save = false, const QString& filter = {});
|
||||
void selectImage();
|
||||
void setActiveImage(int);
|
||||
|
||||
ForwarderController m_controller;
|
||||
QVector<QImage> m_images;
|
||||
int m_currentImage;
|
||||
QSize m_activeSize;
|
||||
|
||||
Ui::ForwarderView m_ui;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QGBA::ForwarderView</class>
|
||||
<widget class="QDialog" name="QGBA::ForwarderView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>710</width>
|
||||
<height>465</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<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">
|
||||
<string>Files</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>ROM file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="romFilename"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="romBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Output filename:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="outputFilename"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="outputBrowse">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Forwarder base:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="baseType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Latest stable verison</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Latest development build</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Specific file</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Base file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="baseFilename">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QPushButton" name="baseBrowse">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Presentation</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="title"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="imagesLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Images:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="imageSelect">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="useDefaultImage">
|
||||
<property name="text">
|
||||
<string>Use default image</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,1,0" columnstretch="0,1,0">
|
||||
<item row="0" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="imagePreview">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>128</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="2,1,0,1,0">
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="preferredLabel">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preferred size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="preferredWidth">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="QLabel" name="preferredX">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="preferredHeight">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="imageBrowse">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select image file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>QGBA::ForwarderView</receiver>
|
||||
<slot>deleteLater()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>354</x>
|
||||
<y>439</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>354</x>
|
||||
<y>232</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="system"/>
|
||||
</buttongroups>
|
||||
</ui>
|
|
@ -184,6 +184,15 @@ VDir* VFileDevice::openArchive(const QString& path) {
|
|||
return VDirOpenArchive(path.toUtf8().constData());
|
||||
}
|
||||
|
||||
bool VFileDevice::copyFile(VFile* input, VFile* output) {
|
||||
uint8_t buffer[0x800];
|
||||
ssize_t size;
|
||||
while ((size = input->read(input, buffer, sizeof(buffer))) > 0) {
|
||||
output->write(output, buffer, size);
|
||||
}
|
||||
return size >= 0;
|
||||
}
|
||||
|
||||
VFileAbstractWrapper::VFileAbstractWrapper(QIODevice* iodev)
|
||||
: m_iodev(iodev)
|
||||
{
|
||||
|
|
|
@ -42,6 +42,8 @@ public:
|
|||
static VDir* openDir(const QString& path);
|
||||
static VDir* openArchive(const QString& path);
|
||||
|
||||
static bool copyFile(VFile* input, VFile* output);
|
||||
|
||||
protected:
|
||||
virtual qint64 readData(char* data, qint64 maxSize) override;
|
||||
virtual qint64 writeData(const char* data, qint64 maxSize) override;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Display.h"
|
||||
#include "DolphinConnector.h"
|
||||
#include "CoreController.h"
|
||||
#include "ForwarderView.h"
|
||||
#include "FrameView.h"
|
||||
#include "GBAApp.h"
|
||||
#include "GDBController.h"
|
||||
|
@ -1615,6 +1616,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_actions.addAction(tr("Scripting..."), "scripting", this, &Window::scriptingOpen, "tools");
|
||||
#endif
|
||||
|
||||
m_actions.addAction(tr("Create forwarder..."), "createForwarder", openTView<ForwarderView>(), "tools");
|
||||
|
||||
m_actions.addSeparator("tools");
|
||||
m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools")->setRole(Action::Role::SETTINGS);
|
||||
m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "tools");
|
||||
|
|
Loading…
Reference in New Issue