all repos — mgba @ fa36a3da812701bbf163db20507c5253c9fb3c22

mGBA Game Boy Advance Emulator

Qt: Allow loading of specific files from archives
Jeffrey Pfau jeffrey@endrift.com
Sat, 27 Aug 2016 01:01:03 -0700
commit

fa36a3da812701bbf163db20507c5253c9fb3c22

parent

72fa184bac4f7eee1a07b6deba3147f1fa976c92

M CHANGESCHANGES

@@ -11,6 +11,7 @@ - GUI: Add fast-forward

- 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
A src/core/library.c

@@ -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); + } +}
A src/core/library.h

@@ -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
A src/platform/qt/ArchiveInspector.cpp

@@ -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); +}
A src/platform/qt/ArchiveInspector.h

@@ -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
A src/platform/qt/ArchiveInspector.ui

@@ -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>
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -68,6 +68,7 @@ endif()

set(SOURCE_FILES AboutScreen.cpp + ArchiveInspector.cpp AudioProcessor.cpp CheatsModel.cpp CheatsView.cpp

@@ -85,6 +86,7 @@ IOViewer.cpp

InputController.cpp InputProfile.cpp KeyEditor.cpp + LibraryModel.cpp LoadSaveState.cpp LogController.cpp LogView.cpp

@@ -110,6 +112,7 @@ VideoView.cpp)

qt5_wrap_ui(UI_FILES AboutScreen.ui + ArchiveInspector.ui CheatsView.ui GIFView.ui IOViewer.ui
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -45,6 +45,7 @@ , m_activeKeys(0)

, 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 @@ LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(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 @@ return;

} if (!biosOnly) { - m_threadContext.core = mCoreFind(m_fname.toUtf8().constData()); + 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 @@ m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height);

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 { + mCoreLoadFile(m_threadContext.core, base); + mDirectorySetDetachBase(&m_threadContext.core->dirs); + } } else { - char dirname[PATH_MAX]; - separatePath(m_bios.toUtf8().constData(), dirname, m_threadContext.core->dirs.baseName, 0); - mDirectorySetAttachBase(&m_threadContext.core->dirs, VDirOpen(dirname)); + 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 @@ } else {

mCoreAutoloadPatch(m_threadContext.core); } } + m_vf = nullptr; if (!mCoreThreadStart(&m_threadContext)) { m_gameOpen = false;
M src/platform/qt/GameController.hsrc/platform/qt/GameController.h

@@ -102,6 +102,7 @@ void postLog(int level, int category, const QString& log);

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 @@

bool m_gameOpen; QString m_fname; + VFile* m_vf; QString m_bios; bool m_useBios; QString m_patch;
A src/platform/qt/LibraryModel.cpp

@@ -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); +}
A src/platform/qt/LibraryModel.h

@@ -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
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -15,6 +15,7 @@ #include <QPainter>

#include <QStackedLayout> #include "AboutScreen.h" +#include "ArchiveInspector.h" #include "CheatsView.h" #include "ConfigController.h" #include "Display.h"

@@ -323,11 +324,43 @@ filters.prepend(tr("All ROMs (%1)").arg(formats.join(QChar(' '))));

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()) { m_controller->loadGame(filename); } +} + +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() {

@@ -850,13 +883,17 @@ m_shortcutController->addMenu(fileMenu);

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");
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -59,6 +59,7 @@ void fpsTargetChanged(float target);

public slots: void selectROM(); + void selectROMInArchive(); void selectSave(bool temporary); void selectBIOS(); void selectPatch();

@@ -148,6 +149,7 @@

void updateTitle(float fps = -1); QString getFilters() const; + QString getFiltersArchive() const; GameController* m_controller; Display* m_display;