Merge branch 'master' into fix_game_list

This commit is contained in:
Megamouse 2025-04-17 11:52:57 +02:00 committed by GitHub
commit 0d811b78de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 126 additions and 13807 deletions

View file

@ -8,7 +8,6 @@ ARTIFACT_DIR="$BUILD_ARTIFACTSTAGINGDIRECTORY"
# Remove unecessary files
rm -f ./bin/rpcs3.exp ./bin/rpcs3.lib ./bin/rpcs3.pdb ./bin/vc_redist.x64.exe
rm -rf ./bin/git
# Prepare compatibility and SDL database for packaging
mkdir ./bin/config

3
.gitignore vendored
View file

@ -55,9 +55,6 @@
/bin/GuiConfigs/*.dat
/bin/GuiConfigs/*.dat.*
# Some data from git
!/bin/git/
# Visual Studio Files
.vs/*
.vscode/*

View file

@ -1 +0,0 @@
Cached data from GitHub API.

File diff suppressed because one or more lines are too long

View file

@ -158,19 +158,16 @@ if (NOT ANDROID)
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/rpcs3.icns $<TARGET_FILE_DIR:rpcs3>/../Resources/rpcs3.icns
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/Icons $<TARGET_FILE_DIR:rpcs3>/../Resources/Icons
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $<TARGET_FILE_DIR:rpcs3>/../Resources/GuiConfigs
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/git $<TARGET_FILE_DIR:rpcs3>/../Resources/git
COMMAND "${MACDEPLOYQT_EXECUTABLE}" "${PROJECT_BINARY_DIR}/bin/rpcs3.app" "${QT_DEPLOY_FLAGS}")
elseif(UNIX)
add_custom_command(TARGET rpcs3 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/Icons $<TARGET_FILE_DIR:rpcs3>/Icons
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $<TARGET_FILE_DIR:rpcs3>/GuiConfigs
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/git $<TARGET_FILE_DIR:rpcs3>/git)
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $<TARGET_FILE_DIR:rpcs3>/GuiConfigs)
elseif(WIN32)
add_custom_command(TARGET rpcs3 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:OpenAL::OpenAL> $<TARGET_FILE_DIR:rpcs3>
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/Icons $<TARGET_FILE_DIR:rpcs3>/Icons
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $<TARGET_FILE_DIR:rpcs3>/GuiConfigs
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/git $<TARGET_FILE_DIR:rpcs3>/git
COMMAND "${WINDEPLOYQT_EXECUTABLE}" --no-compiler-runtime --no-opengl-sw --no-patchqt
--no-translations --no-system-d3d-compiler --no-system-dxc-compiler --no-ffmpeg --no-quick-import
--plugindir "$<IF:$<CXX_COMPILER_ID:MSVC>,$<TARGET_FILE_DIR:rpcs3>/plugins,$<TARGET_FILE_DIR:rpcs3>/share/qt6/plugins>"
@ -195,8 +192,6 @@ if (NOT ANDROID)
DESTINATION ${CMAKE_INSTALL_DATADIR}/rpcs3)
install(DIRECTORY ../bin/GuiConfigs
DESTINATION ${CMAKE_INSTALL_DATADIR}/rpcs3)
install(DIRECTORY ../bin/git
DESTINATION ${CMAKE_INSTALL_DATADIR}/rpcs3)
install(DIRECTORY ../bin/test
DESTINATION ${CMAKE_INSTALL_DATADIR}/rpcs3)
endif()

View file

@ -12,10 +12,6 @@
#include <QTimer>
#include <QObject>
#include <QStyleFactory>
#include <QByteArray>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QMessageBox>
#include <QMetaEnum>
#include <QStandardPaths>
@ -368,7 +364,6 @@ private:
// Arguments that force a headless application (need to be checked in create_application)
constexpr auto arg_headless = "headless";
constexpr auto arg_decrypt = "decrypt";
constexpr auto arg_commit_db = "get-commit-db";
// Arguments that can be used with a gui application
constexpr auto arg_no_gui = "no-gui";
@ -423,8 +418,7 @@ QCoreApplication* create_application(std::span<char* const> qt_argv)
static char** const s_argv = const_cast<char**>(qt_argv.data());
if (find_arg(arg_headless, qt_argv) != -1 ||
find_arg(arg_decrypt, qt_argv) != -1 ||
find_arg(arg_commit_db, qt_argv) != -1)
find_arg(arg_decrypt, qt_argv) != -1)
{
return new headless_application(s_argc, s_argv);
}
@ -806,7 +800,6 @@ int main(int argc, char** argv)
parser.addOption(QCommandLineOption(arg_q_debug, "Log qDebug to RPCS3.log."));
parser.addOption(QCommandLineOption(arg_error, "For internal usage."));
parser.addOption(QCommandLineOption(arg_updating, "For internal usage."));
parser.addOption(QCommandLineOption(arg_commit_db, "Update commits.lst cache. Optional arguments: <path> <sha>"));
parser.addOption(QCommandLineOption(arg_timer, "Enable high resolution timer for better performance (windows)", "enabled", "1"));
parser.addOption(QCommandLineOption(arg_verbose_curl, "Enable verbose curl logging."));
parser.addOption(QCommandLineOption(arg_any_location, "Allow RPCS3 to be run from any location. Dangerous"));
@ -869,223 +862,6 @@ int main(int argc, char** argv)
sys_log.always()("Enabled Curl verbose logging. Please look at your console output.");
}
// Handle update of commit database
if (parser.isSet(arg_commit_db))
{
utils::attach_console(utils::console_stream::std_out | utils::console_stream::std_err, true);
#ifdef _WIN32
std::string path;
#else
std::string path = "bin/git/commits.lst";
#endif
std::string from_sha;
if (const int i_arg_commit_db = find_arg(arg_commit_db, qt_argv); i_arg_commit_db != -1)
{
if (int i = i_arg_commit_db + 1; argc > i)
{
path = qt_argv[i++];
if (argc > i)
{
from_sha = qt_argv[i];
}
}
#ifdef _WIN32
else
{
fprintf(stderr, "Missing path argument.\n");
return 1;
}
#endif
}
else
{
fprintf(stderr, "Can not find argument --%s\n", arg_commit_db);
return 1;
}
fs::file file(path, fs::read + fs::write + fs::append + fs::create);
if (!file)
{
fprintf(stderr, "Failed to open file: '%s' (errno=%d)\n", path.c_str(), errno);
return 1;
}
fprintf(stdout, "\nAppending commits to '%s' ...\n", path.c_str());
// Get existing list
std::string data = file.to_string();
std::vector<std::string> list = fmt::split(data, {"\n"});
const bool was_empty = data.empty();
// SHA to start
std::string last;
if (!list.empty())
{
// Decode last entry to check last written commit
QByteArray buf(list.back().c_str(), list.back().size());
QJsonDocument doc = QJsonDocument::fromJson(buf);
if (doc.isObject() && doc["sha"].isString())
{
last = doc["sha"].toString().toStdString();
}
}
list.clear();
// JSON buffer
QByteArray buf;
// CURL handle to work with GitHub API
rpcs3::curl::curl_handle curl;
struct curl_slist* hhdr{};
hhdr = curl_slist_append(hhdr, "Accept: application/vnd.github.v3+json");
hhdr = curl_slist_append(hhdr, "User-Agent: curl/7.37.0");
CURLcode err = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hhdr);
if (err != CURLE_OK) fprintf(stderr, "curl_easy_setopt(CURLOPT_HTTPHEADER) error: %s", curl_easy_strerror(err));
err = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](const char* ptr, usz, usz size, void* json) -> usz
{
static_cast<QByteArray*>(json)->append(ptr, size);
return size;
});
if (err != CURLE_OK) fprintf(stderr, "curl_easy_setopt(CURLOPT_WRITEFUNCTION) error: %s", curl_easy_strerror(err));
err = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
if (err != CURLE_OK) fprintf(stderr, "curl_easy_setopt(CURLOPT_WRITEDATA) error: %s", curl_easy_strerror(err));
u32 page = 1;
constexpr u32 per_page = 100;
while (page <= 55)
{
fprintf(stdout, "Fetching page %d ...\n", page);
std::string url = "https://api.github.com/repos/RPCS3/rpcs3/commits?per_page=";
fmt::append(url, "%u&page=%u", per_page, page++);
if (!from_sha.empty())
fmt::append(url, "&sha=%s", from_sha);
err = curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
if (err != CURLE_OK)
{
fprintf(stderr, "curl_easy_setopt(CURLOPT_URL, %s) error: %s", url.c_str(), curl_easy_strerror(err));
break;
}
// Reset error buffer before we call curl_easy_perform
curl.reset_error_buffer();
err = curl_easy_perform(curl);
if (err != CURLE_OK)
{
const std::string error_string = curl.get_verbose_error(err);
fprintf(stderr, "curl_easy_perform(): %s", error_string.c_str());
break;
}
QJsonDocument info = QJsonDocument::fromJson(buf);
if (!info.isArray()) [[unlikely]]
{
fprintf(stderr, "Bad response:\n%s", buf.data());
break;
}
u32 count = 0;
for (auto&& ref : info.array())
{
if (!ref.isObject())
{
page = -1;
break;
}
count++;
QJsonObject result, author, committer;
QJsonObject commit = ref.toObject();
auto commit_ = commit["commit"].toObject();
auto author_ = commit_["author"].toObject();
auto committer_ = commit_["committer"].toObject();
auto _author = commit["author"].toObject();
auto _committer = commit["committer"].toObject();
result["sha"] = commit["sha"];
result["msg"] = commit_["message"];
author["name"] = author_["name"];
author["date"] = author_["date"];
author["email"] = author_["email"];
author["login"] = _author["login"];
author["avatar"] = _author["avatar_url"];
committer["name"] = committer_["name"];
committer["date"] = committer_["date"];
committer["email"] = committer_["email"];
committer["login"] = _committer["login"];
committer["avatar"] = _committer["avatar_url"];
result["author"] = author;
result["committer"] = committer;
QJsonDocument out(result);
buf = out.toJson(QJsonDocument::JsonFormat::Compact);
buf += "\n";
if (was_empty || !from_sha.empty())
{
data = buf.toStdString() + std::move(data);
}
else if (commit["sha"].toString().toStdString() == last)
{
page = -1;
break;
}
else
{
// Append to the list
list.emplace_back(buf.data(), buf.size());
}
}
buf.clear();
if (count < per_page)
{
break;
}
}
if (was_empty || !from_sha.empty())
{
file.trunc(0);
file.write(data);
}
else
{
// Append list in reverse order
for (usz i = list.size() - 1; ~i; --i)
{
file.write(list[i]);
}
}
curl_slist_free_all(hhdr);
fprintf(stdout, "Finished fetching commits: %s\n", path.c_str());
return 0;
}
if (parser.isSet(arg_q_debug))
{
qInstallMessageHandler(log_q_debug);

View file

@ -91,19 +91,19 @@ void game_list_grid::populate(
if (const QPixmap pixmap = item->get_movie_image(frame); item->get_active() && !pixmap.isNull())
{
item->set_icon(gui::utils::get_centered_pixmap(pixmap, m_icon_size, 0, 0, 1.0, Qt::FastTransformation));
return;
}
else
{
std::lock_guard lock(item->pixmap_mutex);
std::lock_guard lock(item->pixmap_mutex);
if (!game->pxmap.isNull())
{
item->set_icon(game->pxmap);
if (!game->has_hover_gif && !game->has_hover_pam)
{
game->pxmap = {};
}
item->stop_movie();
}
});

View file

@ -252,19 +252,19 @@ void game_list_table::populate(
if (const QPixmap pixmap = icon_item->get_movie_image(frame); icon_item->get_active() && !pixmap.isNull())
{
icon_item->setData(Qt::DecorationRole, pixmap.scaled(m_icon_size, Qt::KeepAspectRatio));
return;
}
else
{
std::lock_guard lock(icon_item->pixmap_mutex);
std::lock_guard lock(icon_item->pixmap_mutex);
if (!game->pxmap.isNull())
{
icon_item->setData(Qt::DecorationRole, game->pxmap);
if (!game->has_hover_gif && !game->has_hover_pam)
{
game->pxmap = {};
}
icon_item->stop_movie();
}
});

View file

@ -34,11 +34,11 @@ void qt_video_source::set_active(bool active)
}
}
void qt_video_source::image_change_callback() const
void qt_video_source::image_change_callback(const QVideoFrame& frame) const
{
if (m_image_change_callback)
{
m_image_change_callback({});
m_image_change_callback(frame);
}
}
@ -65,7 +65,7 @@ void qt_video_source::init_movie()
if (lower.endsWith(".gif"))
{
m_movie.reset(new QMovie(m_video_path));
m_movie = std::make_unique<QMovie>(m_video_path);
m_video_path.clear();
if (!m_movie->isValid())
@ -76,7 +76,7 @@ void qt_video_source::init_movie()
QObject::connect(m_movie.get(), &QMovie::frameChanged, m_movie.get(), [this](int)
{
m_image_change_callback({});
image_change_callback();
m_has_new = true;
});
return;
@ -98,18 +98,18 @@ void qt_video_source::init_movie()
return;
}
m_video_buffer.reset(new QBuffer(&m_video_data));
m_video_buffer = std::make_unique<QBuffer>(&m_video_data);
m_video_buffer->open(QIODevice::ReadOnly);
}
m_video_sink.reset(new QVideoSink());
m_video_sink = std::make_unique<QVideoSink>();
QObject::connect(m_video_sink.get(), &QVideoSink::videoFrameChanged, m_video_sink.get(), [this](const QVideoFrame& frame)
{
m_image_change_callback(frame);
image_change_callback(frame);
m_has_new = true;
});
m_media_player.reset(new QMediaPlayer());
m_media_player = std::make_unique<QMediaPlayer>();
m_media_player->setVideoSink(m_video_sink.get());
m_media_player->setLoops(QMediaPlayer::Infinite);

View file

@ -31,7 +31,7 @@ public:
QPixmap get_movie_image(const QVideoFrame& frame) const;
void image_change_callback() const;
void image_change_callback(const QVideoFrame& frame = {}) const;
void set_image_change_callback(const std::function<void(const QVideoFrame&)>& func);
protected:
@ -49,8 +49,8 @@ protected:
std::unique_ptr<QBuffer> m_video_buffer;
std::unique_ptr<QMediaPlayer> m_media_player;
std::shared_ptr<QVideoSink> m_video_sink;
std::shared_ptr<QMovie> m_movie;
std::unique_ptr<QVideoSink> m_video_sink;
std::unique_ptr<QMovie> m_movie;
std::function<void(const QVideoFrame&)> m_image_change_callback = nullptr;

View file

@ -16,14 +16,15 @@ vfs_dialog_path_widget::vfs_dialog_path_widget(const QString& name, const QStrin
for (const QString& dir : all_dirs)
{
QListWidgetItem* item = new QListWidgetItem(dir, m_dir_list);
QListWidgetItem* item = add_directory(dir);
if (dir == current_path)
selected_item = item;
}
// We must show the currently selected config.
if (!selected_item)
selected_item = new QListWidgetItem(current_path, m_dir_list);
selected_item = add_directory(current_path);
selected_item->setSelected(true);
@ -54,21 +55,64 @@ vfs_dialog_path_widget::vfs_dialog_path_widget(const QString& name, const QStrin
setLayout(vbox);
update_selection();
connect(m_dir_list->model(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex&, const QModelIndex&, const QList<int>& roles)
{
if (m_is_changing_data || (!roles.empty() && !roles.contains(Qt::ItemDataRole::CheckStateRole)))
{
return;
}
// Check if an item was selected
const QString selected_path = m_selected_config_label->text() == EmptyPath ? "" : m_selected_config_label->text();
QAbstractItemModel* model = m_dir_list->model();
for (int i = 0; i < model->rowCount(); ++i)
{
if (model->index(i, 0).data(Qt::ItemDataRole::CheckStateRole).toInt() != Qt::Checked) continue;
const QString path = model->index(i, 0).data(Qt::ItemDataRole::DisplayRole).toString();
if (path == selected_path) continue;
// Select new path
m_selected_config_label->setText(path.isEmpty() ? EmptyPath : path);
update_selection();
break;
}
});
connect(m_dir_list, &QListWidget::currentRowChanged, this, [this, button_remove_dir](int row)
{
QListWidgetItem* item = m_dir_list->item(row);
m_selected_config_label->setText((item && !item->text().isEmpty()) ? item->text() : EmptyPath);
button_remove_dir->setEnabled(item && row > 0);
button_remove_dir->setEnabled(m_dir_list->item(row) && row > 0);
});
}
void vfs_dialog_path_widget::reset() const
void vfs_dialog_path_widget::reset()
{
m_dir_list->clear();
m_dir_list->setCurrentItem(new QListWidgetItem(m_default_path, m_dir_list));
m_dir_list->setCurrentItem(add_directory(m_default_path));
update_selection();
}
void vfs_dialog_path_widget::add_new_directory() const
QListWidgetItem* vfs_dialog_path_widget::add_directory(const QString& path)
{
// Make sure to only add a path once
for (int i = 0; i < m_dir_list->count(); ++i)
{
if (m_dir_list->item(i)->text() == path)
{
return nullptr;
}
}
QListWidgetItem* item = new QListWidgetItem(path, m_dir_list);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
return item;
}
void vfs_dialog_path_widget::add_new_directory()
{
QString dir = QFileDialog::getExistingDirectory(nullptr, tr("Choose a directory"), QCoreApplication::applicationDirPath(), QFileDialog::DontResolveSymlinks);
@ -78,19 +122,57 @@ void vfs_dialog_path_widget::add_new_directory() const
if (!dir.endsWith("/"))
dir += '/';
m_dir_list->setCurrentItem(new QListWidgetItem(dir, m_dir_list));
m_dir_list->setCurrentItem(add_directory(dir));
update_selection();
}
void vfs_dialog_path_widget::remove_directory() const
void vfs_dialog_path_widget::remove_directory()
{
const int row = m_dir_list->currentRow();
if (row > 0)
if (const int row = m_dir_list->currentRow(); row > 0)
{
QListWidgetItem* item = m_dir_list->takeItem(row);
delete item;
update_selection();
}
}
void vfs_dialog_path_widget::update_selection()
{
const int count = m_dir_list->count();
if (count <= 0) return;
const QString selected_path = m_selected_config_label->text();
bool found_path = false;
m_is_changing_data = true;
for (int i = 0; i < count; i++)
{
if (QListWidgetItem* item = m_dir_list->item(i))
{
const bool is_selected = item->text() == selected_path;
item->setCheckState(is_selected ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
if (is_selected)
{
m_dir_list->setCurrentItem(item);
found_path = true;
}
}
}
if (!found_path)
{
QListWidgetItem* item = m_dir_list->item(0);
m_selected_config_label->setText(item->text().isEmpty() ? EmptyPath : item->text());
item->setCheckState(Qt::CheckState::Checked);
m_dir_list->setCurrentItem(item);
}
m_is_changing_data = false;
}
QStringList vfs_dialog_path_widget::get_dir_list() const
{
QStringList all_dirs;

View file

@ -22,11 +22,14 @@ public:
std::string get_selected_path() const;
// Reset this widget without saving the settings yet
void reset() const;
void reset();
protected:
void add_new_directory() const;
void remove_directory() const;
QListWidgetItem* add_directory(const QString& path);
void add_new_directory();
void remove_directory();
void update_selection();
const QString EmptyPath = tr("Empty Path");
@ -35,6 +38,7 @@ protected:
std::shared_ptr<gui_settings> m_gui_settings;
// UI variables needed in higher scope
QListWidget* m_dir_list;
QLabel* m_selected_config_label;
QListWidget* m_dir_list = nullptr;
QLabel* m_selected_config_label = nullptr;
bool m_is_changing_data = false;
};