From 78f984c41215b5207379ed67f0579c88d1d8f78e Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 24 Jan 2020 14:51:25 +1000 Subject: [PATCH] Qt: Add automatic download of redump disc database for game list --- src/core/game_list.h | 3 + src/duckstation-qt/duckstation-qt.vcxproj | 35 +-- .../duckstation-qt.vcxproj.filters | 4 + src/duckstation-qt/gamelistsettingswidget.cpp | 207 +++++++++++++++++- src/duckstation-qt/gamelistsettingswidget.h | 2 + 5 files changed, 234 insertions(+), 17 deletions(-) diff --git a/src/core/game_list.h b/src/core/game_list.h index d80dd8ecf..0b51d1fea 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -63,6 +63,9 @@ public: const GameListEntry* GetEntryForPath(const char* path) const; const GameListDatabaseEntry* GetDatabaseEntryForCode(const std::string& code) const; + const std::string& GetCacheFilename() const { return m_cache_filename; } + const std::string& GetDatabaseFilename() const { return m_database_filename; } + void SetCacheFilename(std::string filename) { m_cache_filename = std::move(filename); } void SetDatabaseFilename(std::string filename) { m_database_filename = std::move(filename); } void SetSearchDirectoriesFromSettings(SettingsInterface& si); diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 66dc4578f..22cba77f4 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -79,6 +79,9 @@ {bb08260f-6fbc-46af-8924-090ee71360c6} + + {8bda439c-6358-45fb-9994-2ff083babe06} + {ee054e08-3799-4a59-a422-18259c105ffd} @@ -278,7 +281,7 @@ _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -287,7 +290,7 @@ Console true $(SolutionDir)dep\msvc\lib32-debug;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Networkd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) @@ -299,7 +302,7 @@ _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -308,7 +311,7 @@ Console true $(SolutionDir)dep\msvc\lib64-debug;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Networkd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) @@ -320,7 +323,7 @@ _ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) Default true false @@ -331,7 +334,7 @@ Console true $(SolutionDir)dep\msvc\lib32-debug;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Networkd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) @@ -343,7 +346,7 @@ _ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) Default true false @@ -354,7 +357,7 @@ Console true $(SolutionDir)dep\msvc\lib64-debug;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;Qt5Networkd.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) @@ -365,7 +368,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -376,7 +379,7 @@ true true $(SolutionDir)dep\msvc\lib32;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Network.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) @@ -387,7 +390,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x86\include;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -399,7 +402,7 @@ true true $(SolutionDir)dep\msvc\lib32;$(SolutionDir)dep\msvc\qt5-x86\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Network.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration @@ -411,7 +414,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -422,7 +425,7 @@ true true $(SolutionDir)dep\msvc\lib64;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Network.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) @@ -433,7 +436,7 @@ MaxSpeed true _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\glad\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;$(SolutionDir)dep\msvc\qt5-x64\include;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -445,7 +448,7 @@ true true $(SolutionDir)dep\msvc\lib64;$(SolutionDir)dep\msvc\qt5-x64\lib;%(AdditionalLibraryDirectories) - Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5Network.lib;d3d11.lib;dxgi.lib;%(AdditionalDependencies) UseLinkTimeCodeGeneration diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index 4687ecc90..dbda330ba 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -31,6 +31,8 @@ + + @@ -59,6 +61,7 @@ + @@ -66,6 +69,7 @@ + diff --git a/src/duckstation-qt/gamelistsettingswidget.cpp b/src/duckstation-qt/gamelistsettingswidget.cpp index daa5912cc..9350098d0 100644 --- a/src/duckstation-qt/gamelistsettingswidget.cpp +++ b/src/duckstation-qt/gamelistsettingswidget.cpp @@ -1,13 +1,25 @@ #include "gamelistsettingswidget.h" +#include "common/assert.h" +#include "common/string_util.h" +#include "core/game_list.h" #include "qthostinterface.h" #include "qtutils.h" +#include #include +#include #include +#include +#include +#include +#include #include #include #include +#include #include +static constexpr char REDUMP_DOWNLOAD_URL[] = "http://redump.org/datfile/psx/serial,version,description"; + class GameListSearchDirectoriesModel : public QAbstractTableModel { public: @@ -271,5 +283,198 @@ void GameListSettingsWidget::onRefreshGameListButtonPressed() void GameListSettingsWidget::onUpdateRedumpDatabaseButtonPressed() { - QMessageBox::information(this, tr("TODO"), tr("TODO")); + if (QMessageBox::question(this, tr("Download database from redump.org?"), + tr("Do you wish to download the disc database from redump.org?\n\nThis will download " + "approximately 4 megabytes over your current internet connection.")) != QMessageBox::Yes) + { + return; + } + + if (downloadRedumpDatabase(QString::fromStdString(m_host_interface->getGameList()->GetDatabaseFilename()))) + m_host_interface->refreshGameList(true, true); +} + +static bool ExtractRedumpDatabase(const QByteArray& data, const QString& destination_path) +{ + if (data.isEmpty()) + return false; + + struct MemoryFileInfo + { + const QByteArray& data; + int position; + }; + + MemoryFileInfo fi{data, 0}; + +#define FI static_cast(stream) + + zlib_filefunc64_def funcs = { + [](voidpf opaque, const void* filename, int mode) -> voidpf { return opaque; }, // open + [](voidpf opaque, voidpf stream, void* buf, uLong size) -> uLong { // read + const int remaining = FI->data.size() - FI->position; + const int to_read = std::min(remaining, static_cast(size)); + if (to_read > 0) + { + std::memcpy(buf, FI->data.constData() + FI->position, to_read); + FI->position += to_read; + } + + return static_cast(to_read); + }, + [](voidpf opaque, voidpf stream, const void* buf, uLong size) -> uLong { return 0; }, // write + [](voidpf opaque, voidpf stream) -> ZPOS64_T { return static_cast(FI->position); }, // tell + [](voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) -> long { // seek + int new_position = FI->position; + if (origin == SEEK_SET) + new_position = static_cast(offset); + else if (origin == SEEK_CUR) + new_position += static_cast(offset); + else + new_position = FI->data.size(); + if (new_position < 0 || new_position > FI->data.size()) + return -1; + + FI->position = new_position; + return 0; + }, + [](voidpf opaque, voidpf stream) -> int { return 0; }, // close + [](voidpf opaque, voidpf stream) -> int { return 0; }, // testerror + static_cast(&fi)}; + +#undef FI + + unzFile zf = unzOpen2_64("", &funcs); + if (!zf) + { + qCritical() << "unzOpen2_64() failed"; + return false; + } + + // find the first file with a .dat extension (in case there's others) + if (unzGoToFirstFile(zf) != UNZ_OK) + { + qCritical() << "unzGoToFirstFile() failed"; + unzClose(zf); + return false; + } + + int dat_size = 0; + for (;;) + { + char zip_filename_buffer[256]; + unz_file_info64 file_info; + if (unzGetCurrentFileInfo64(zf, &file_info, zip_filename_buffer, sizeof(zip_filename_buffer), nullptr, 0, nullptr, + 0) != UNZ_OK) + { + qCritical() << "unzGetCurrentFileInfo() failed"; + unzClose(zf); + return false; + } + + const char* extension = std::strrchr(zip_filename_buffer, '.'); + if (extension && StringUtil::Strcasecmp(extension, ".dat") == 0 && file_info.uncompressed_size > 0) + { + dat_size = static_cast(file_info.uncompressed_size); + qInfo() << "Found redump dat file in zip: " << zip_filename_buffer << "(" << dat_size << " bytes)"; + break; + } + + if (unzGoToNextFile(zf) != UNZ_OK) + { + qCritical() << "dat file not found in downloaded redump zip"; + unzClose(zf); + return false; + } + } + + if (unzOpenCurrentFile(zf) != UNZ_OK) + { + qCritical() << "unzOpenCurrentFile() failed"; + unzClose(zf); + return false; + } + + QByteArray dat_buffer; + dat_buffer.resize(dat_size); + if (unzReadCurrentFile(zf, dat_buffer.data(), dat_size) != dat_size) + { + qCritical() << "unzReadCurrentFile() failed"; + unzClose(zf); + return false; + } + + unzCloseCurrentFile(zf); + unzClose(zf); + + QFile dat_output_file(destination_path); + if (!dat_output_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + qCritical() << "QFile::open() failed"; + return false; + } + + if (static_cast(dat_output_file.write(dat_buffer)) != dat_buffer.size()) + { + qCritical() << "QFile::write() failed"; + return false; + } + + dat_output_file.close(); + qInfo() << "Wrote redump dat to " << destination_path; + return true; +} + +bool GameListSettingsWidget::downloadRedumpDatabase(const QString& download_path) +{ + Assert(!download_path.isEmpty()); + + QNetworkAccessManager manager; + + QUrl url(QUrl::fromEncoded(QByteArray(REDUMP_DOWNLOAD_URL, sizeof(REDUMP_DOWNLOAD_URL) - 1))); + QNetworkRequest request(url); + + QNetworkReply* reply = manager.get(request); + + QProgressDialog progress(tr("Downloading %1...").arg(REDUMP_DOWNLOAD_URL), tr("Cancel"), 0, 1); + progress.setAutoClose(false); + + connect(reply, &QNetworkReply::downloadProgress, [&progress](quint64 received, quint64 total) { + progress.setRange(0, static_cast(total)); + progress.setValue(static_cast(received)); + }); + + connect(&manager, &QNetworkAccessManager::finished, [this, &progress, &download_path](QNetworkReply* reply) { + if (reply->error() != QNetworkReply::NoError) + { + QMessageBox::critical(this, tr("Download failed"), reply->errorString()); + progress.done(-1); + return; + } + + progress.setRange(0, 100); + progress.setValue(100); + progress.setLabelText(tr("Extracting...")); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + const QByteArray data = reply->readAll(); + if (!ExtractRedumpDatabase(data, download_path)) + { + QMessageBox::critical(this, tr("Extract failed"), tr("Extracting game database failed.")); + progress.done(-1); + return; + } + + progress.done(1); + }); + + const int result = progress.exec(); + if (result == 0) + { + // cancelled + reply->abort(); + } + + reply->deleteLater(); + return (result == 1); } diff --git a/src/duckstation-qt/gamelistsettingswidget.h b/src/duckstation-qt/gamelistsettingswidget.h index 26fa09305..e2997d51d 100644 --- a/src/duckstation-qt/gamelistsettingswidget.h +++ b/src/duckstation-qt/gamelistsettingswidget.h @@ -30,6 +30,8 @@ protected: void resizeEvent(QResizeEvent* event); private: + bool downloadRedumpDatabase(const QString& download_path); + QtHostInterface* m_host_interface; Ui::GameListSettingsWidget m_ui;