diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5553da1d..034e6cfa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -524,6 +524,7 @@ set(SRC_DRIVERS_SDL ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/sdl-throttle.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/unix-netplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/AviRecord.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/AviRiffViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/avi/avi-utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/avi/fileio.cpp ${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/avi/gwavi.cpp diff --git a/src/drivers/Qt/AviRiffViewer.cpp b/src/drivers/Qt/AviRiffViewer.cpp new file mode 100644 index 00000000..02bccff0 --- /dev/null +++ b/src/drivers/Qt/AviRiffViewer.cpp @@ -0,0 +1,419 @@ +/* FCE Ultra - NES/Famicom Emulator + * + * Copyright notice for this file: + * Copyright (C) 2020 mjbudd77 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +// AviRiffViewer.cpp +// +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Qt/main.h" +#include "Qt/config.h" +#include "Qt/fceuWrapper.h" +#include "Qt/AviRiffViewer.h" +#include "Qt/ConsoleUtilities.h" + +static AviRiffViewerDialog *win = NULL; +//---------------------------------------------------------------------------- +static int riffWalkCallback( int type, long long int fpos, const char *fourcc, size_t size, void *userData ) +{ + int ret = 0; + + if ( win ) + { + ret = win->riffWalkCallback( type, fpos, fourcc, size ); + } + return ret; +} +//---------------------------------------------------------------------------- +//--- AVI RIFF Viewer Dialog +//---------------------------------------------------------------------------- +AviRiffViewerDialog::AviRiffViewerDialog(QWidget *parent) + : QDialog(parent) +{ + QMenuBar *menuBar; + QVBoxLayout *mainLayout; + QHBoxLayout *hbox; + QPushButton *closeButton; + QTreeWidgetItem *item; + + win = this; + avi = NULL; + + setWindowTitle("AVI RIFF Viewer"); + + resize(512, 512); + + menuBar = buildMenuBar(); + mainLayout = new QVBoxLayout(); + setLayout(mainLayout); + mainLayout->setMenuBar( menuBar ); + + riffTree = new AviRiffTree(); + + riffTree->setColumnCount(4); + riffTree->setSelectionMode( QAbstractItemView::SingleSelection ); + riffTree->setAlternatingRowColors(true); + + item = new QTreeWidgetItem(); + item->setText(0, QString::fromStdString("Block")); + item->setText(1, QString::fromStdString("FCC")); + item->setText(2, QString::fromStdString("Size")); + item->setText(3, QString::fromStdString("FilePos")); + item->setTextAlignment(0, Qt::AlignLeft); + item->setTextAlignment(1, Qt::AlignLeft); + item->setTextAlignment(2, Qt::AlignLeft); + item->setTextAlignment(3, Qt::AlignLeft); + + riffTree->setHeaderItem(item); + + riffTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + + //connect( riffTree, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(hotKeyActivated(QTreeWidgetItem*,int) ) ); + + mainLayout->addWidget(riffTree); + + closeButton = new QPushButton( tr("Close") ); + closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); + connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); + + hbox = new QHBoxLayout(); + hbox->addStretch(5); + hbox->addWidget( closeButton, 1 ); + mainLayout->addLayout( hbox ); + +} +//---------------------------------------------------------------------------- +AviRiffViewerDialog::~AviRiffViewerDialog(void) +{ + printf("Destroy AVI RIFF Viewer Window\n"); + + if ( avi ) + { + delete avi; avi = NULL; + } + win = NULL; +} +//---------------------------------------------------------------------------- +void AviRiffViewerDialog::closeEvent(QCloseEvent *event) +{ + printf("AVI RIFF Viewer Window Event\n"); + done(0); + deleteLater(); + event->accept(); +} +//---------------------------------------------------------------------------- +void AviRiffViewerDialog::closeWindow(void) +{ + //printf("Close Window\n"); + done(0); + deleteLater(); +} +//---------------------------------------------------------------------------- +QMenuBar *AviRiffViewerDialog::buildMenuBar(void) +{ + QMenu *fileMenu; + //QActionGroup *actGroup; + QAction *act; + int opt, useNativeMenuBar=0; + + QMenuBar *menuBar = new QMenuBar(); + + // This is needed for menu bar to show up on MacOS + g_config->getOption( "SDL.UseNativeMenuBar", &useNativeMenuBar ); + + menuBar->setNativeMenuBar( useNativeMenuBar ? true : false ); + + //----------------------------------------------------------------------- + // Menu Start + //----------------------------------------------------------------------- + // File + fileMenu = menuBar->addMenu(tr("&File")); + + // File -> Open + act = new QAction(tr("&Open"), this); + act->setShortcut(QKeySequence::Open); + act->setStatusTip(tr("Open AVI File")); + connect(act, SIGNAL(triggered()), this, SLOT(openAviFileDialog(void)) ); + + fileMenu->addAction(act); + + // File -> Close + act = new QAction(tr("&Close"), this); + act->setShortcut(QKeySequence::Close); + act->setStatusTip(tr("Close Window")); + connect(act, SIGNAL(triggered()), this, SLOT(closeWindow(void)) ); + + fileMenu->addAction(act); + + return menuBar; +} +//---------------------------------------------------------------------------- +void AviRiffViewerDialog::openAviFileDialog(void) +{ + std::string last; + int ret, useNativeFileDialogVal; + QString filename; + std::string lastPath; + //char dir[512]; + const char *base, *rom; + QFileDialog dialog(this, tr("Open AVI Movie for Inspection") ); + QList urls; + QDir d; + + dialog.setFileMode(QFileDialog::ExistingFile); + + dialog.setNameFilter(tr("AVI Movies (*.avi) ;; All files (*)")); + + dialog.setViewMode(QFileDialog::List); + dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden ); + dialog.setLabelText( QFileDialog::Accept, tr("Open") ); + + base = FCEUI_GetBaseDirectory(); + + urls << QUrl::fromLocalFile( QDir::rootPath() ); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first()); + + if ( base ) + { + urls << QUrl::fromLocalFile( QDir( base ).absolutePath() ); + + d.setPath( QString(base) + "/avi"); + + if ( d.exists() ) + { + urls << QUrl::fromLocalFile( d.absolutePath() ); + } + + dialog.setDirectory( d.absolutePath() ); + } + dialog.setDefaultSuffix( tr(".avi") ); + + g_config->getOption ("SDL.AviFilePath", &lastPath); + if ( lastPath.size() > 0 ) + { + dialog.setDirectory( QString::fromStdString(lastPath) ); + } + + rom = getRomFile(); + + if ( rom ) + { + char baseName[512]; + getFileBaseName( rom, baseName ); + + if ( baseName[0] != 0 ) + { + dialog.selectFile(baseName); + } + } + + // Check config option to use native file dialog or not + g_config->getOption ("SDL.UseNativeFileDialog", &useNativeFileDialogVal); + + dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal); + dialog.setSidebarUrls(urls); + + ret = dialog.exec(); + + if ( ret ) + { + QStringList fileList; + fileList = dialog.selectedFiles(); + + if ( fileList.size() > 0 ) + { + filename = fileList[0]; + } + } + + if ( filename.isNull() ) + { + return; + } + //qDebug() << "selected file path : " << filename.toUtf8(); + + printf( "AVI Debug movie %s\n", filename.toStdString().c_str() ); + + lastPath = QFileInfo(filename).absolutePath().toStdString(); + + if ( lastPath.size() > 0 ) + { + g_config->setOption ("SDL.AviFilePath", lastPath); + } + + openFile( filename.toStdString().c_str() ); +} +//---------------------------------------------------------------------------- +int AviRiffViewerDialog::openFile( const char *filepath ) +{ + if ( avi ) + { + closeFile(); + } + + avi = new gwavi_t(); + + if ( avi->openIn( filepath ) ) + { + return -1; + } + + itemStack.clear(); + + avi->setRiffWalkCallback( ::riffWalkCallback, this ); + avi->printHeaders(); + + return 0; +} +//---------------------------------------------------------------------------- +int AviRiffViewerDialog::closeFile(void) +{ + if ( avi ) + { + delete avi; avi = NULL; + } + riffTree->clear(); + itemStack.clear(); + + return 0; +} +//---------------------------------------------------------------------------- +int AviRiffViewerDialog::riffWalkCallback( int type, long long int fpos, const char *fourcc, size_t size ) +{ + AviRiffTreeItem *item, *groupItem; + + switch ( type ) + { + case gwavi_t::RIFF_START: + { + item = new AviRiffTreeItem(type, fpos, fourcc, size); + + itemStack.push_back(item); + + riffTree->addTopLevelItem(item); + } + break; + case gwavi_t::RIFF_END: + { + itemStack.pop_back(); + } + break; + case gwavi_t::LIST_START: + { + item = new AviRiffTreeItem(type, fpos, fourcc, size); + + groupItem = itemStack.back(); + + itemStack.push_back(item); + + groupItem->addChild(item); + } + break; + case gwavi_t::LIST_END: + { + itemStack.pop_back(); + } + break; + case gwavi_t::CHUNK_START: + { + item = new AviRiffTreeItem(type, fpos, fourcc, size); + + groupItem = itemStack.back(); + + groupItem->addChild(item); + } + break; + default: + // UnHandled Type + break; + } + + return 0; +} +//---------------------------------------------------------------------------- +//--- AVI RIFF Tree View +//---------------------------------------------------------------------------- +AviRiffTree::AviRiffTree(QWidget *parent) + : QTreeWidget(parent) +{ + +} +//---------------------------------------------------------------------------- +AviRiffTree::~AviRiffTree(void) +{ + +} +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +//--- AVI RIFF Viewer Dialog +//---------------------------------------------------------------------------- +AviRiffTreeItem::AviRiffTreeItem(int typeIn, long long int fposIn, const char *fourccIn, size_t sizeIn, QTreeWidgetItem *parent) + : QTreeWidgetItem(parent) +{ + char stmp[64]; + + type = typeIn; + fpos = fposIn; + size = sizeIn; + + strcpy( fourcc, fourccIn ); + + //sprintf( stmp, "0x%08llX", fposIn ); + + switch ( type ) + { + case gwavi_t::RIFF_START: + case gwavi_t::RIFF_END: + setText( 0, QString("RIFF") ); + break; + case gwavi_t::LIST_START: + case gwavi_t::LIST_END: + setText( 0, QString("LIST") ); + break; + default: + case gwavi_t::CHUNK_START: + setText( 0, QString("CHUNK") ); + break; + } + + setText( 1, QString(fourcc) ); + + sprintf( stmp, "%zu", size ); + + setText( 2, QString(stmp) ); + + sprintf( stmp, "0x%08llX", fposIn ); + + setText( 3, QString(stmp) ); +} +//---------------------------------------------------------------------------- +AviRiffTreeItem::~AviRiffTreeItem(void) +{ + +} +//---------------------------------------------------------------------------- diff --git a/src/drivers/Qt/AviRiffViewer.h b/src/drivers/Qt/AviRiffViewer.h new file mode 100644 index 00000000..b32e32e5 --- /dev/null +++ b/src/drivers/Qt/AviRiffViewer.h @@ -0,0 +1,83 @@ +// AviRiffViewer.h +// + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Qt/avi/gwavi.h" + +class AviRiffTreeItem : public QTreeWidgetItem +{ + //Q_OBJECT + + public: + AviRiffTreeItem(int type, long long int fpos, const char *fourcc, size_t size, QTreeWidgetItem *parent = nullptr); + ~AviRiffTreeItem(void); + + private: + int type; + char fourcc[8]; + size_t size; + long long int fpos; +}; + +class AviRiffTree : public QTreeWidget +{ + Q_OBJECT + +public: + AviRiffTree(QWidget *parent = 0); + ~AviRiffTree(void); + +protected: +}; + +class AviRiffViewerDialog : public QDialog +{ + Q_OBJECT + +public: + AviRiffViewerDialog(QWidget *parent = 0); + ~AviRiffViewerDialog(void); + + int riffWalkCallback( int type, long long int fpos, const char *fourcc, size_t size ); + +protected: + void closeEvent(QCloseEvent *event); + + QMenuBar *buildMenuBar(void); + + int openFile( const char *filepath ); + int closeFile(void); + + gwavi_t *avi; + AviRiffTree *riffTree; + std::list itemStack; + +private: +public slots: + void closeWindow(void); +private slots: + void openAviFileDialog(void); + //void ItemActivated(QTreeWidgetItem *item, int column); +}; diff --git a/src/drivers/Qt/ConsoleWindow.cpp b/src/drivers/Qt/ConsoleWindow.cpp index 891d7d28..283b93cc 100644 --- a/src/drivers/Qt/ConsoleWindow.cpp +++ b/src/drivers/Qt/ConsoleWindow.cpp @@ -72,6 +72,7 @@ #include "Qt/HelpPages.h" #include "Qt/GuiConf.h" #include "Qt/AviRecord.h" +#include "Qt/AviRiffViewer.h" #include "Qt/MoviePlay.h" #include "Qt/MovieRecord.h" #include "Qt/MovieOptions.h" @@ -1595,6 +1596,14 @@ void consoleWin_t::createMainMenu(void) act->setStatusTip(tr("Open Palette Editor Window")); connect(act, SIGNAL(triggered()), this, SLOT(openPaletteEditorWin(void)) ); + toolsMenu->addAction(act); + + // Tools -> AVI RIFF Viewer + act = new QAction(tr("&AVI RIFF Viewer ..."), this); + //act->setShortcut( QKeySequence(tr("Shift+F7"))); + act->setStatusTip(tr("Open AVI RIFF Viewer Window")); + connect(act, SIGNAL(triggered()), this, SLOT(openAviRiffViewer(void)) ); + toolsMenu->addAction(act); //----------------------------------------------------------------------- @@ -2799,6 +2808,17 @@ void consoleWin_t::openPaletteEditorWin(void) win->show(); } +void consoleWin_t::openAviRiffViewer(void) +{ + AviRiffViewerDialog *win; + + //printf("Open AVI RIFF Viewer Window\n"); + + win = new AviRiffViewerDialog(this); + + win->show(); +} + void consoleWin_t::openMovieOptWin(void) { MovieOptionsDialog_t *win; diff --git a/src/drivers/Qt/ConsoleWindow.h b/src/drivers/Qt/ConsoleWindow.h index 225cdf81..f4b59c29 100644 --- a/src/drivers/Qt/ConsoleWindow.h +++ b/src/drivers/Qt/ConsoleWindow.h @@ -334,6 +334,7 @@ class consoleWin_t : public QMainWindow void openGuiConfWin(void); void openTimingConfWin(void); void openPaletteEditorWin(void); + void openAviRiffViewer(void); void openTimingStatWin(void); void openMovieOptWin(void); void openCodeDataLogger(void); diff --git a/src/drivers/Qt/avi/gwavi.cpp b/src/drivers/Qt/avi/gwavi.cpp index 9f990590..31773773 100644 --- a/src/drivers/Qt/avi/gwavi.cpp +++ b/src/drivers/Qt/avi/gwavi.cpp @@ -83,6 +83,7 @@ gwavi_t::gwavi_t(void) bits_per_pixel = 24; avi_std = 2; audioEnabled = false; + riffWalkCallback = NULL; } gwavi_t::~gwavi_t(void) @@ -660,11 +661,13 @@ int gwavi_t::printHeaders(void) { char fourcc[8]; unsigned int ret, fileSize, size; + long long int fpos; if ( in == NULL ) { return -1; } + fpos = ftell(in); if (read_chars_bin(in, fourcc, 4) == -1) return -1; @@ -687,6 +690,11 @@ int gwavi_t::printHeaders(void) fourcc[4] = 0; printf("FileType: '%s'\n", fourcc ); + if ( riffWalkCallback ) + { + riffWalkCallback( RIFF_START, fpos, fourcc, fileSize, riffWalkUserData ); + } + while ( size >= 4 ) { if (read_chars_bin(in, fourcc, 4) == -1) @@ -728,10 +736,13 @@ unsigned int gwavi_t::readList(int lvl) char fourcc[8], listType[8], pad[4]; unsigned int size, listSize=0; char indent[256]; + long long int fpos; memset( indent, ' ', lvl*3); indent[lvl*3] = 0; + fpos = ftell(in); + if (read_uint(in, listSize) == -1) { (void)fprintf(stderr, "readList: read_int() failed\n"); @@ -754,6 +765,10 @@ unsigned int gwavi_t::readList(int lvl) size -= 4; bytesRead += 4; + if ( riffWalkCallback ) + { + riffWalkCallback( LIST_START, fpos-4, listType, listSize, riffWalkUserData ); + } printf("%sList Start: '%s' %u\n", indent, listType, listSize ); while ( size >= 4 ) @@ -805,6 +820,12 @@ unsigned int gwavi_t::readList(int lvl) } printf("%sList End: %s %u\n", indent, listType, bytesRead); + if ( riffWalkCallback ) + { + fpos = ftell(in); + + riffWalkCallback( LIST_END, fpos, listType, listSize, riffWalkUserData ); + } return bytesRead+4; } @@ -813,10 +834,13 @@ unsigned int gwavi_t::readChunk(const char *id, int lvl) unsigned int r, ret, size, chunkSize, bytesRead=0; unsigned short dataWord; char indent[256]; + long long int fpos; memset( indent, ' ', lvl*3); indent[lvl*3] = 0; + fpos = ftell(in); + if (read_uint(in, chunkSize) == -1) { (void)fprintf(stderr, "readChunk: read_uint() failed\n"); @@ -824,9 +848,14 @@ unsigned int gwavi_t::readChunk(const char *id, int lvl) } printf("%sChunk Start: %s %u\n", indent, id, chunkSize); + if ( riffWalkCallback ) + { + riffWalkCallback( CHUNK_START, fpos-4, id, chunkSize, riffWalkUserData ); + } + if ( chunkSize == 0 ) { - return 0; + return 4; } size = chunkSize; diff --git a/src/drivers/Qt/avi/gwavi.h b/src/drivers/Qt/avi/gwavi.h index d45727e2..f9a34ae7 100644 --- a/src/drivers/Qt/avi/gwavi.h +++ b/src/drivers/Qt/avi/gwavi.h @@ -175,6 +175,15 @@ class gwavi_t static const unsigned int IF_KEYFRAME = 0x00000010; static const unsigned int IF_NO_TIME = 0x00000100; + enum + { + RIFF_START, + RIFF_END, + LIST_START, + LIST_END, + CHUNK_START + }; + gwavi_t(void); ~gwavi_t(void); @@ -198,6 +207,12 @@ class gwavi_t int printHeaders(void); + void setRiffWalkCallback( int (*cb)( int type, long long int fpos, const char *fourcc, size_t size, void *userData ), void *userData ) + { + riffWalkCallback = cb; + riffWalkUserData = userData; + }; + private: FILE *in; FILE *out; @@ -252,6 +267,9 @@ class gwavi_t unsigned int readAviHeader(void); unsigned int readStreamHeader(void); unsigned int readIndexBlock( unsigned int chunkSize ); + + void *riffWalkUserData; + int (*riffWalkCallback)( int type, long long int fpos, const char *fourcc, size_t size, void *userData ); }; #endif /* ndef H_GWAVI */