Qt: Allow loading of specific files from archives

This commit is contained in:
Jeffrey Pfau 2016-08-27 01:01:03 -07:00
parent 72fa184bac
commit fa36a3da81
13 changed files with 459 additions and 6 deletions

View File

@ -11,6 +11,7 @@ Features:
- Wii: 240p support
- 3DS: Adjustable screen darkening
- Ability to temporarily load a savegame
- Load specific files out of archives
Bugfixes:
- SDL: Fix axes being mapped wrong
- GBA Memory: Fix mirror on non-overdumped Classic NES games

60
src/core/library.c Normal file
View File

@ -0,0 +1,60 @@
/* Copyright (c) 2013-2016 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 "library.h"
#include "util/vfs.h"
DEFINE_VECTOR(mLibraryListing, struct mLibraryEntry);
void mLibraryInit(struct mLibrary* library) {
mLibraryListingInit(&library->listing, 0);
}
void mLibraryDeinit(struct mLibrary* library) {
size_t i;
for (i = 0; i < mLibraryListingSize(&library->listing); ++i) {
struct mLibraryEntry* entry = mLibraryListingGetPointer(&library->listing, i);
free(entry->filename);
free(entry->title);
}
mLibraryListingDeinit(&library->listing);
}
void mLibraryLoadDirectory(struct mLibrary* library, struct VDir* dir) {
struct VDirEntry* dirent = dir->listNext(dir);
while (dirent) {
struct VFile* vf = dir->openFile(dir, dirent->name(dirent), O_RDONLY);
if (!vf) {
dirent = dir->listNext(dir);
continue;
}
mLibraryAddEntry(library, dirent->name(dirent), vf);
dirent = dir->listNext(dir);
}
}
void mLibraryAddEntry(struct mLibrary* library, const char* filename, struct VFile* vf) {
struct mCore* core;
if (!vf) {
vf = VFileOpen(filename, O_RDONLY);
}
core = mCoreFindVF(vf);
if (core) {
core->init(core);
core->loadROM(core, vf);
struct mLibraryEntry* entry = mLibraryListingAppend(&library->listing);
core->getGameTitle(core, entry->internalTitle);
core->getGameCode(core, entry->internalCode);
entry->title = NULL;
entry->filename = strdup(filename);
entry->filesize = vf->size(vf);
// Note: this destroys the VFile
core->deinit(core);
} else {
vf->close(vf);
}
}

37
src/core/library.h Normal file
View File

@ -0,0 +1,37 @@
/* Copyright (c) 2013-2016 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/. */
#ifndef M_LIBRARY_H
#define M_LIBRARY_H
#include "util/common.h"
#include "core/core.h"
#include "util/vector.h"
struct mLibraryEntry {
char* filename;
char* title;
char internalTitle[17];
char internalCode[9];
size_t filesize;
enum mPlatform platform;
};
DECLARE_VECTOR(mLibraryListing, struct mLibraryEntry);
struct mLibrary {
struct mLibraryListing listing;
};
void mLibraryInit(struct mLibrary*);
void mLibraryDeinit(struct mLibrary*);
struct VDir;
struct VFile;
void mLibraryLoadDirectory(struct mLibrary* library, struct VDir* dir);
void mLibraryAddEntry(struct mLibrary* library, const char* filename, struct VFile* vf);
#endif

View File

@ -0,0 +1,37 @@
/* Copyright (c) 2013-2016 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 "ArchiveInspector.h"
extern "C" {
#include "util/vfs.h"
}
using namespace QGBA;
ArchiveInspector::ArchiveInspector(const QString& filename, QWidget* parent)
: QDialog(parent)
{
m_ui.setupUi(this);
m_dir = VDirOpenArchive(filename.toUtf8().constData());
if (m_dir) {
m_model.loadDirectory(m_dir);
}
m_ui.archiveListing->setModel(&m_model);
}
ArchiveInspector::~ArchiveInspector() {
if (m_dir) {
m_dir->close(m_dir);
}
}
VFile* ArchiveInspector::selectedVFile() const {
QModelIndex index = m_ui.archiveListing->selectionModel()->currentIndex();
if (!index.isValid()) {
return nullptr;
}
return m_dir->openFile(m_dir, m_model.entryAt(index.row())->filename, O_RDONLY);
}

View File

@ -0,0 +1,36 @@
/* Copyright (c) 2013-2016 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/. */
#ifndef QGBA_ARCHIVE_INSPECTOR
#define QGBA_ARCHIVE_INSPECTOR
#include "LibraryModel.h"
#include "ui_ArchiveInspector.h"
struct VDir;
struct VFile;
namespace QGBA {
class ArchiveInspector : public QDialog {
Q_OBJECT
public:
ArchiveInspector(const QString& filename, QWidget* parent = nullptr);
virtual ~ArchiveInspector();
VFile* selectedVFile() const;
private:
Ui::ArchiveInspector m_ui;
LibraryModel m_model;
VDir* m_dir;
};
}
#endif

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ArchiveInspector</class>
<widget class="QDialog" name="ArchiveInspector">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Open in archive...</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Open</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QListView" name="archiveListing"/>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ArchiveInspector</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>279</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ArchiveInspector</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>279</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
<connection>
<sender>archiveListing</sender>
<signal>doubleClicked(QModelIndex)</signal>
<receiver>ArchiveInspector</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>129</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -68,6 +68,7 @@ endif()
set(SOURCE_FILES
AboutScreen.cpp
ArchiveInspector.cpp
AudioProcessor.cpp
CheatsModel.cpp
CheatsView.cpp
@ -85,6 +86,7 @@ set(SOURCE_FILES
InputController.cpp
InputProfile.cpp
KeyEditor.cpp
LibraryModel.cpp
LoadSaveState.cpp
LogController.cpp
LogView.cpp
@ -110,6 +112,7 @@ set(SOURCE_FILES
qt5_wrap_ui(UI_FILES
AboutScreen.ui
ArchiveInspector.ui
CheatsView.ui
GIFView.ui
IOViewer.ui

View File

@ -45,6 +45,7 @@ GameController::GameController(QObject* parent)
, m_inactiveKeys(0)
, m_logLevels(0)
, m_gameOpen(false)
, m_vf(nullptr)
, m_useBios(false)
, m_audioThread(new QThread(this))
, m_audioProcessor(AudioProcessor::create())
@ -315,6 +316,14 @@ void GameController::loadGame(const QString& path) {
return;
}
m_fname = info.canonicalFilePath();
m_vf = nullptr;
openGame();
}
void GameController::loadGame(VFile* vf, const QString& base) {
closeGame();
m_fname = base;
m_vf = vf;
openGame();
}
@ -330,7 +339,11 @@ void GameController::openGame(bool biosOnly) {
}
if (!biosOnly) {
if (m_vf) {
m_threadContext.core = mCoreFindVF(m_vf);
} else {
m_threadContext.core = mCoreFind(m_fname.toUtf8().constData());
}
} else {
m_threadContext.core = GBACoreCreate();
}
@ -357,13 +370,21 @@ void GameController::openGame(bool biosOnly) {
m_drawContext = new uint32_t[width * height];
m_frontBuffer = new uint32_t[width * height];
const char* base;
if (!biosOnly) {
mCoreLoadFile(m_threadContext.core, m_fname.toUtf8().constData());
base = m_fname.toUtf8().constData();
if (m_vf) {
m_threadContext.core->loadROM(m_threadContext.core, m_vf);
} else {
char dirname[PATH_MAX];
separatePath(m_bios.toUtf8().constData(), dirname, m_threadContext.core->dirs.baseName, 0);
mDirectorySetAttachBase(&m_threadContext.core->dirs, VDirOpen(dirname));
mCoreLoadFile(m_threadContext.core, base);
mDirectorySetDetachBase(&m_threadContext.core->dirs);
}
} else {
base = m_bios.toUtf8().constData();
}
char dirname[PATH_MAX];
separatePath(base, dirname, m_threadContext.core->dirs.baseName, 0);
mDirectorySetAttachBase(&m_threadContext.core->dirs, VDirOpen(dirname));
m_threadContext.core->setVideoBuffer(m_threadContext.core, m_drawContext, width);
@ -396,6 +417,7 @@ void GameController::openGame(bool biosOnly) {
mCoreAutoloadPatch(m_threadContext.core);
}
}
m_vf = nullptr;
if (!mCoreThreadStart(&m_threadContext)) {
m_gameOpen = false;

View File

@ -102,6 +102,7 @@ signals:
public slots:
void loadGame(const QString& path);
void loadGame(VFile* vf, const QString& base = QString());
void loadBIOS(const QString& path);
void loadSave(const QString& path, bool temporary = true);
void yankPak();
@ -184,6 +185,7 @@ private:
bool m_gameOpen;
QString m_fname;
VFile* m_vf;
QString m_bios;
bool m_useBios;
QString m_patch;

View File

@ -0,0 +1,90 @@
/* Copyright (c) 2013-2016 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 "LibraryModel.h"
extern "C" {
#include "util/vfs.h"
}
using namespace QGBA;
LibraryModel::LibraryModel(QObject* parent)
: QAbstractItemModel(parent)
{
mLibraryInit(&m_library);
}
LibraryModel::~LibraryModel() {
mLibraryDeinit(&m_library);
}
void LibraryModel::loadDirectory(VDir* dir) {
mLibraryLoadDirectory(&m_library, dir);
}
const mLibraryEntry* LibraryModel::entryAt(int row) const {
if ((unsigned) row < mLibraryListingSize(&m_library.listing)) {
return mLibraryListingGetConstPointer(&m_library.listing, row);
}
return nullptr;
}
QVariant LibraryModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) {
return QVariant();
}
if (role != Qt::DisplayRole) {
return QVariant();
}
const mLibraryEntry* entry = mLibraryListingGetConstPointer(&m_library.listing, index.row());
switch (index.column()) {
case 0:
return entry->filename;
case 1:
return (unsigned long long) entry->filesize;
}
return QVariant();
}
QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (role != Qt::DisplayRole) {
return QAbstractItemModel::headerData(section, orientation, role);
}
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("Filename");
case 1:
return tr("Size");
}
}
return section;
}
QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const {
if (parent.isValid()) {
return QModelIndex();
}
return createIndex(row, column, nullptr);
}
QModelIndex LibraryModel::parent(const QModelIndex&) const {
return QModelIndex();
}
int LibraryModel::columnCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return 2;
}
int LibraryModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
return mLibraryListingSize(&m_library.listing);
}

View File

@ -0,0 +1,46 @@
/* Copyright (c) 2013-2016 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/. */
#ifndef QGBA_LIBRARY_MODEL
#define QGBA_LIBRARY_MODEL
#include <QAbstractItemModel>
extern "C" {
#include "core/library.h"
}
struct VDir;
namespace QGBA {
class LibraryModel : public QAbstractItemModel {
Q_OBJECT
public:
LibraryModel(QObject* parent = nullptr);
virtual ~LibraryModel();
void loadDirectory(VDir* dir);
const mLibraryEntry* entryAt(int row) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override;
virtual QModelIndex parent(const QModelIndex& index) const override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
private:
mLibrary m_library;
};
}
#endif

View File

@ -15,6 +15,7 @@
#include <QStackedLayout>
#include "AboutScreen.h"
#include "ArchiveInspector.h"
#include "CheatsView.h"
#include "ConfigController.h"
#include "Display.h"
@ -323,6 +324,21 @@ QString Window::getFilters() const {
return filters.join(";;");
}
QString Window::getFiltersArchive() const {
QStringList filters;
QStringList formats{
#if defined(USE_LIBZIP) || defined(USE_ZLIB)
"*.zip",
#endif
#ifdef USE_LZMA
"*.7z",
#endif
};
filters.append(tr("Archives (%1)").arg(formats.join(QChar(' '))));
return filters.join(";;");
}
void Window::selectROM() {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters());
if (!filename.isEmpty()) {
@ -330,6 +346,23 @@ void Window::selectROM() {
}
}
void Window::selectROMInArchive() {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFiltersArchive());
if (filename.isEmpty()) {
return;
}
ArchiveInspector* archiveInspector = new ArchiveInspector(filename);
connect(archiveInspector, &QDialog::accepted, [this, archiveInspector]() {
VFile* output = archiveInspector->selectedVFile();
if (output) {
m_controller->loadGame(output);
}
archiveInspector->close();
});
archiveInspector->setAttribute(Qt::WA_DeleteOnClose);
archiveInspector->show();
}
void Window::replaceROM() {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters());
if (!filename.isEmpty()) {
@ -850,13 +883,17 @@ void Window::setupMenu(QMenuBar* menubar) {
installEventFilter(m_shortcutController);
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open),
"loadROM");
addControlledAction(fileMenu, fileMenu->addAction(tr("Load ROM in archive..."), this, SLOT(selectROMInArchive())),
"loadROMInArchive");
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS");
QAction* loadTemporarySave = new QAction(tr("Load temporary save..."), fileMenu);
connect(loadTemporarySave, &QAction::triggered, [this]() { this->selectSave(true); });
m_gameActions.append(loadTemporarySave);
m_gbaActions.append(loadTemporarySave);
addControlledAction(fileMenu, loadTemporarySave, "loadTemporarySave");
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS");
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch");
addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS");

View File

@ -59,6 +59,7 @@ signals:
public slots:
void selectROM();
void selectROMInArchive();
void selectSave(bool temporary);
void selectBIOS();
void selectPatch();
@ -148,6 +149,7 @@ private:
void updateTitle(float fps = -1);
QString getFilters() const;
QString getFiltersArchive() const;
GameController* m_controller;
Display* m_display;