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;