Emu/UX: Automatic Cache Precompilation for PKG instal
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.7, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.7, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, .ci/build-mac.sh, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, .ci/build-mac-arm64.sh, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang (win64, clang, clang64) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run

This commit is contained in:
Elad 2025-11-29 09:15:23 +02:00
parent d625c1d004
commit e09be04df6
7 changed files with 97 additions and 38 deletions

View file

@ -3718,7 +3718,7 @@ extern void ppu_finalize(const ppu_module<lv2_obj>& info, bool force_mem_release
#endif #endif
} }
extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_module<lv2_obj>*>* loaded_modules) extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_module<lv2_obj>*>* loaded_modules, bool is_fast_compilation)
{ {
if (g_cfg.core.ppu_decoder != ppu_decoder_type::llvm) if (g_cfg.core.ppu_decoder != ppu_decoder_type::llvm)
{ {
@ -4166,6 +4166,12 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
break; break;
} }
if (is_fast_compilation)
{
// Skip overlays in fast mode
break;
}
if (!wait_for_memory()) if (!wait_for_memory())
{ {
// Emulation stopped // Emulation stopped
@ -4460,7 +4466,7 @@ extern void ppu_initialize()
progress_dialog.reset(); progress_dialog.reset();
ppu_precompile(dir_queue, &module_list); ppu_precompile(dir_queue, &module_list, false);
if (Emu.IsStopped()) if (Emu.IsStopped())
{ {

View file

@ -75,7 +75,7 @@ atomic_t<u64> g_watchdog_hold_ctr{0};
extern bool ppu_load_exec(const ppu_exec_object&, bool virtual_load, const std::string&, utils::serial* = nullptr); extern bool ppu_load_exec(const ppu_exec_object&, bool virtual_load, const std::string&, utils::serial* = nullptr);
extern void spu_load_exec(const spu_exec_object&); extern void spu_load_exec(const spu_exec_object&);
extern void spu_load_rel_exec(const spu_rel_object&); extern void spu_load_rel_exec(const spu_rel_object&);
extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_module<lv2_obj>*>* loaded_prx); extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_module<lv2_obj>*>* loaded_prx, bool is_fast_compilation);
extern bool ppu_initialize(const ppu_module<lv2_obj>&, bool check_only = false, u64 file_size = 0); extern bool ppu_initialize(const ppu_module<lv2_obj>&, bool check_only = false, u64 file_size = 0);
extern void ppu_finalize(const ppu_module<lv2_obj>&); extern void ppu_finalize(const ppu_module<lv2_obj>&);
extern void ppu_unload_prx(const lv2_prx&); extern void ppu_unload_prx(const lv2_prx&);
@ -1684,7 +1684,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
} }
} }
g_fxo->init<named_thread>("SPRX Loader"sv, [this, dir_queue]() mutable g_fxo->init<named_thread>("SPRX Loader"sv, [this, dir_queue, is_fast = m_precompilation_option.is_fast]() mutable
{ {
std::vector<ppu_module<lv2_obj>*> mod_list; std::vector<ppu_module<lv2_obj>*> mod_list;
@ -1705,7 +1705,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
return; return;
} }
ppu_precompile(dir_queue, mod_list.empty() ? nullptr : &mod_list); ppu_precompile(dir_queue, mod_list.empty() ? nullptr : &mod_list, is_fast);
if (Emu.IsStopped()) if (Emu.IsStopped())
{ {
@ -3230,6 +3230,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
read_used_savestate_versions(); read_used_savestate_versions();
m_savestate_extension_flags1 = {}; m_savestate_extension_flags1 = {};
m_emu_state_close_pending = false; m_emu_state_close_pending = false;
m_precompilation_option = {};
// Enable logging // Enable logging
rpcs3::utils::configure_logs(true); rpcs3::utils::configure_logs(true);
@ -3824,6 +3825,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
read_used_savestate_versions(); read_used_savestate_versions();
m_savestate_extension_flags1 = {}; m_savestate_extension_flags1 = {};
m_emu_state_close_pending = false; m_emu_state_close_pending = false;
m_precompilation_option = {};
initialize_timebased_time(0, true); initialize_timebased_time(0, true);

View file

@ -120,6 +120,11 @@ namespace utils
struct serial; struct serial;
}; };
struct emu_precompilation_option_t
{
bool is_fast = false;
};
class Emulator final class Emulator final
{ {
atomic_t<system_state> m_state{system_state::stopped}; atomic_t<system_state> m_state{system_state::stopped};
@ -188,6 +193,7 @@ class Emulator final
}; };
bs_t<SaveStateExtentionFlags1> m_savestate_extension_flags1{}; bs_t<SaveStateExtentionFlags1> m_savestate_extension_flags1{};
emu_precompilation_option_t m_precompilation_option{};
public: public:
static constexpr std::string_view game_id_boot_prefix = "%RPCS3_GAMEID%:"; static constexpr std::string_view game_id_boot_prefix = "%RPCS3_GAMEID%:";
@ -245,6 +251,11 @@ public:
m_state = system_state::running; m_state = system_state::running;
} }
void SetPrecompileCacheOption(emu_precompilation_option_t option)
{
m_precompilation_option = option;
}
void Init(); void Init();
std::vector<std::string> argv; std::vector<std::string> argv;

View file

@ -2011,10 +2011,11 @@ void game_list_frame::ShowContextMenu(const QPoint &pos)
menu.exec(global_pos); menu.exec(global_pos);
} }
bool game_list_frame::CreateCPUCaches(const std::string& path, const std::string& serial) bool game_list_frame::CreateCPUCaches(const std::string& path, const std::string& serial, bool is_fast_compilation)
{ {
Emu.GracefulShutdown(false); Emu.GracefulShutdown(false);
Emu.SetForceBoot(true); Emu.SetForceBoot(true);
Emu.SetPrecompileCacheOption(emu_precompilation_option_t{.is_fast = is_fast_compilation});
if (const auto error = Emu.BootGame(fs::is_file(path) ? fs::get_parent_dir(path) : path, serial, true); error != game_boot_result::no_errors) if (const auto error = Emu.BootGame(fs::is_file(path) ? fs::get_parent_dir(path) : path, serial, true); error != game_boot_result::no_errors)
{ {
@ -2026,9 +2027,9 @@ bool game_list_frame::CreateCPUCaches(const std::string& path, const std::string
return true; return true;
} }
bool game_list_frame::CreateCPUCaches(const game_info& game) bool game_list_frame::CreateCPUCaches(const game_info& game, bool is_fast_compilation)
{ {
return game && CreateCPUCaches(game->info.path, game->info.serial); return game && CreateCPUCaches(game->info.path, game->info.serial, is_fast_compilation);
} }
bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, const game_info& game, bool is_interactive) bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, const game_info& game, bool is_interactive)
@ -2404,6 +2405,9 @@ void game_list_frame::BatchActionBySerials(progress_dialog* pdlg, const std::set
connect(pdlg, &progress_dialog::canceled, this, [pdlg](){ pdlg->deleteLater(); }); connect(pdlg, &progress_dialog::canceled, this, [pdlg](){ pdlg->deleteLater(); });
QApplication::beep(); QApplication::beep();
// Signal termination back to the callback
action("");
if (refresh_on_finish && index) if (refresh_on_finish && index)
{ {
Refresh(true); Refresh(true);
@ -2414,7 +2418,7 @@ void game_list_frame::BatchActionBySerials(progress_dialog* pdlg, const std::set
QTimer::singleShot(1, this, *periodic_func); QTimer::singleShot(1, this, *periodic_func);
} }
void game_list_frame::BatchCreateCPUCaches(const std::vector<game_info>& game_data) void game_list_frame::BatchCreateCPUCaches(const std::vector<game_info>& game_data, bool is_fast_compilation)
{ {
std::set<std::string> serials; std::set<std::string> serials;
@ -2433,11 +2437,13 @@ void game_list_frame::BatchCreateCPUCaches(const std::vector<game_info>& game_da
if (total == 0) if (total == 0)
{ {
QMessageBox::information(this, tr("LLVM Cache Batch Creation"), tr("No titles found"), QMessageBox::Ok); QMessageBox::information(this, tr("LLVM Cache Batch Creation"), tr("No titles found"), QMessageBox::Ok);
Q_EMIT NotifyBatchedGameActionFinished();
return; return;
} }
if (!m_gui_settings->GetBootConfirmation(this)) if (!m_gui_settings->GetBootConfirmation(this))
{ {
Q_EMIT NotifyBatchedGameActionFinished();
return; return;
} }
@ -2459,13 +2465,19 @@ void game_list_frame::BatchCreateCPUCaches(const std::vector<game_info>& game_da
BatchActionBySerials(pdlg, serials, tr("%0\nProgress: %1/%2 caches compiled").arg(main_label), BatchActionBySerials(pdlg, serials, tr("%0\nProgress: %1/%2 caches compiled").arg(main_label),
[&, game_data](const std::string& serial) [&, game_data](const std::string& serial)
{ {
if (serial.empty())
{
Q_EMIT NotifyBatchedGameActionFinished();
return false;
}
if (Emu.IsStopped(true)) if (Emu.IsStopped(true))
{ {
const auto it = std::find_if(m_game_data.begin(), m_game_data.end(), FN(x->info.serial == serial)); const auto it = std::find_if(m_game_data.begin(), m_game_data.end(), FN(x->info.serial == serial));
if (it != m_game_data.end()) if (it != m_game_data.end())
{ {
return CreateCPUCaches((*it)->info.path, serial); return CreateCPUCaches((*it)->info.path, serial, is_fast_compilation);
} }
} }
@ -2512,7 +2524,7 @@ void game_list_frame::BatchRemovePPUCaches()
BatchActionBySerials(pdlg, serials, tr("%0/%1 caches cleared"), BatchActionBySerials(pdlg, serials, tr("%0/%1 caches cleared"),
[this](const std::string& serial) [this](const std::string& serial)
{ {
return Emu.IsStopped(true) && RemovePPUCache(GetCacheDirBySerial(serial)); return !serial.empty() &&Emu.IsStopped(true) && RemovePPUCache(GetCacheDirBySerial(serial));
}, },
[this](u32, u32) [this](u32, u32)
{ {
@ -2551,7 +2563,7 @@ void game_list_frame::BatchRemoveSPUCaches()
BatchActionBySerials(pdlg, serials, tr("%0/%1 caches cleared"), BatchActionBySerials(pdlg, serials, tr("%0/%1 caches cleared"),
[this](const std::string& serial) [this](const std::string& serial)
{ {
return Emu.IsStopped(true) && RemoveSPUCache(GetCacheDirBySerial(serial)); return !serial.empty() && Emu.IsStopped(true) && RemoveSPUCache(GetCacheDirBySerial(serial));
}, },
[this](u32 removed, u32 total) [this](u32 removed, u32 total)
{ {
@ -2586,7 +2598,7 @@ void game_list_frame::BatchRemoveCustomConfigurations()
BatchActionBySerials(pdlg, serials, tr("%0/%1 custom configurations cleared"), BatchActionBySerials(pdlg, serials, tr("%0/%1 custom configurations cleared"),
[this](const std::string& serial) [this](const std::string& serial)
{ {
return Emu.IsStopped(true) && RemoveCustomConfiguration(serial); return !serial.empty() && Emu.IsStopped(true) && RemoveCustomConfiguration(serial);
}, },
[this](u32 removed, u32 total) [this](u32 removed, u32 total)
{ {
@ -2620,7 +2632,7 @@ void game_list_frame::BatchRemoveCustomPadConfigurations()
BatchActionBySerials(pdlg, serials, tr("%0/%1 custom pad configurations cleared"), BatchActionBySerials(pdlg, serials, tr("%0/%1 custom pad configurations cleared"),
[this](const std::string& serial) [this](const std::string& serial)
{ {
return Emu.IsStopped(true) && RemoveCustomPadConfiguration(serial); return !serial.empty() && Emu.IsStopped(true) && RemoveCustomPadConfiguration(serial);
}, },
[this](u32 removed, u32 total) [this](u32 removed, u32 total)
{ {
@ -2659,7 +2671,7 @@ void game_list_frame::BatchRemoveShaderCaches()
BatchActionBySerials(pdlg, serials, tr("%0/%1 shader caches cleared"), BatchActionBySerials(pdlg, serials, tr("%0/%1 shader caches cleared"),
[this](const std::string& serial) [this](const std::string& serial)
{ {
return Emu.IsStopped(true) && RemoveShadersCache(GetCacheDirBySerial(serial)); return !serial.empty() && Emu.IsStopped(true) && RemoveShadersCache(GetCacheDirBySerial(serial));
}, },
[this](u32 removed, u32 total) [this](u32 removed, u32 total)
{ {

View file

@ -64,7 +64,7 @@ public:
bool IsEntryVisible(const game_info& game, bool search_fallback = false) const; bool IsEntryVisible(const game_info& game, bool search_fallback = false) const;
public Q_SLOTS: public Q_SLOTS:
void BatchCreateCPUCaches(const std::vector<game_info>& game_data = {}); void BatchCreateCPUCaches(const std::vector<game_info>& game_data = {}, bool is_fast_compilation = false);
void BatchRemovePPUCaches(); void BatchRemovePPUCaches();
void BatchRemoveSPUCaches(); void BatchRemoveSPUCaches();
void BatchRemoveCustomConfigurations(); void BatchRemoveCustomConfigurations();
@ -96,6 +96,7 @@ Q_SIGNALS:
void FocusToSearchBar(); void FocusToSearchBar();
void Refreshed(); void Refreshed();
void RequestSaveStateManager(const game_info& game); void RequestSaveStateManager(const game_info& game);
void NotifyBatchedGameActionFinished();
public: public:
template <typename KeyType> template <typename KeyType>
@ -135,8 +136,8 @@ private:
bool RemovePPUCache(const std::string& base_dir, bool is_interactive = false); bool RemovePPUCache(const std::string& base_dir, bool is_interactive = false);
bool RemoveSPUCache(const std::string& base_dir, bool is_interactive = false); bool RemoveSPUCache(const std::string& base_dir, bool is_interactive = false);
void RemoveHDD1Cache(const std::string& base_dir, const std::string& title_id, bool is_interactive = false); void RemoveHDD1Cache(const std::string& base_dir, const std::string& title_id, bool is_interactive = false);
static bool CreateCPUCaches(const std::string& path, const std::string& serial = {}); static bool CreateCPUCaches(const std::string& path, const std::string& serial = {}, bool is_fast_compilation = false);
static bool CreateCPUCaches(const game_info& game); static bool CreateCPUCaches(const game_info& game, bool is_fast_compilation = false);
static bool RemoveContentPath(const std::string& path, const std::string& desc); static bool RemoveContentPath(const std::string& path, const std::string& desc);
static u32 RemoveContentPathList(const std::vector<std::string>& path_list, const std::string& desc); static u32 RemoveContentPathList(const std::vector<std::string>& path_list, const std::string& desc);

View file

@ -1187,7 +1187,13 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
} }
} }
// Executes after PrecompileCachesFromInstalledPackages
m_notify_batch_game_action_cb = [this, paths]() mutable
{
ShowOptionalGamePreparations(tr("Success!"), tr("Successfully installed software from package(s)!"), std::move(paths)); ShowOptionalGamePreparations(tr("Success!"), tr("Successfully installed software from package(s)!"), std::move(paths));
};
PrecompileCachesFromInstalledPackages(paths);
}); });
} }
@ -2368,8 +2374,7 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
#else #else
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)")); QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
#endif #endif
QCheckBox* precompile_check = new QCheckBox(tr("Precompile caches")); QLabel* label = new QLabel(tr("%1\nWould you like to install shortcuts to the installed software? (%2 new software detected)\n\n").arg(message).arg(bootable_paths.size()), dlg);
QLabel* label = new QLabel(tr("%1\nWould you like to install shortcuts to the installed software and precompile caches? (%2 new software detected)\n\n").arg(message).arg(bootable_paths.size()), dlg);
vlayout->addWidget(label); vlayout->addWidget(label);
vlayout->addStretch(10); vlayout->addStretch(10);
@ -2377,10 +2382,6 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
vlayout->addStretch(3); vlayout->addStretch(3);
vlayout->addWidget(quick_check); vlayout->addWidget(quick_check);
vlayout->addStretch(3); vlayout->addStretch(3);
vlayout->addWidget(precompile_check);
vlayout->addStretch(3);
precompile_check->setToolTip(tr("Spend time building data needed for game boot now instead of at launch."));
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok); QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
@ -2391,7 +2392,6 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
{ {
const bool create_desktop_shortcuts = desk_check->isChecked(); const bool create_desktop_shortcuts = desk_check->isChecked();
const bool create_app_shortcut = quick_check->isChecked(); const bool create_app_shortcut = quick_check->isChecked();
const bool create_caches = precompile_check->isChecked();
dlg->hide(); dlg->hide();
dlg->accept(); dlg->accept();
@ -2411,12 +2411,11 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
locations.insert(gui::utils::shortcut_location::applications); locations.insert(gui::utils::shortcut_location::applications);
} }
if (locations.empty() && !create_caches) if (locations.empty())
{ {
return; return;
} }
std::vector<game_info> game_data;
std::vector<game_info> game_data_shortcuts; std::vector<game_info> game_data_shortcuts;
for (const auto& [boot_path, title_id] : paths) for (const auto& [boot_path, title_id] : paths)
@ -2431,11 +2430,6 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
{ {
game_data_shortcuts.push_back(gameinfo); game_data_shortcuts.push_back(gameinfo);
} }
if (create_caches)
{
game_data.push_back(gameinfo);
}
} }
break; break;
@ -2447,17 +2441,39 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
{ {
m_game_list_frame->CreateShortcuts(game_data_shortcuts, locations); m_game_list_frame->CreateShortcuts(game_data_shortcuts, locations);
} }
if (!game_data.empty())
{
m_game_list_frame->BatchCreateCPUCaches(game_data);
}
}); });
dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->open(); dlg->open();
} }
void main_window::PrecompileCachesFromInstalledPackages(const std::map<std::string, QString>& bootable_paths)
{
std::vector<game_info> game_data;
for (const auto& [boot_path, title_id] : bootable_paths)
{
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo())
{
if (gameinfo && gameinfo->info.serial == title_id.toStdString())
{
if (Emu.IsPathInsideDir(boot_path, gameinfo->info.path))
{
game_data.push_back(gameinfo);
}
break;
}
}
}
if (!game_data.empty())
{
m_game_list_frame->BatchCreateCPUCaches(game_data, true);
}
}
void main_window::CreateActions() void main_window::CreateActions()
{ {
ui->exitAct->setShortcuts(QKeySequence::Quit); ui->exitAct->setShortcuts(QKeySequence::Quit);
@ -3401,6 +3417,15 @@ void main_window::CreateConnects()
connect(ui->mw_searchbar, &QLineEdit::textChanged, m_game_list_frame, &game_list_frame::SetSearchText); connect(ui->mw_searchbar, &QLineEdit::textChanged, m_game_list_frame, &game_list_frame::SetSearchText);
connect(ui->mw_searchbar, &QLineEdit::returnPressed, m_game_list_frame, &game_list_frame::FocusAndSelectFirstEntryIfNoneIs); connect(ui->mw_searchbar, &QLineEdit::returnPressed, m_game_list_frame, &game_list_frame::FocusAndSelectFirstEntryIfNoneIs);
connect(m_game_list_frame, &game_list_frame::FocusToSearchBar, this, [this]() { ui->mw_searchbar->setFocus(); }); connect(m_game_list_frame, &game_list_frame::FocusToSearchBar, this, [this]() { ui->mw_searchbar->setFocus(); });
connect(m_game_list_frame, &game_list_frame::NotifyBatchedGameActionFinished, this, [this]() mutable
{
if (m_notify_batch_game_action_cb)
{
m_notify_batch_game_action_cb();
m_notify_batch_game_action_cb = {};
}
});
} }
void main_window::CreateDockWindows() void main_window::CreateDockWindows()

View file

@ -48,6 +48,7 @@ class main_window : public QMainWindow
bool m_save_slider_pos = false; bool m_save_slider_pos = false;
bool m_requested_show_logs_on_exit = false; bool m_requested_show_logs_on_exit = false;
int m_other_slider_pos = 0; int m_other_slider_pos = 0;
std::function<void()> m_notify_batch_game_action_cb;
QIcon m_app_icon; QIcon m_app_icon;
QIcon m_icon_play; QIcon m_icon_play;
@ -141,6 +142,7 @@ private:
void CreateDockWindows(); void CreateDockWindows();
void EnableMenus(bool enabled) const; void EnableMenus(bool enabled) const;
void ShowTitleBars(bool show) const; void ShowTitleBars(bool show) const;
void PrecompileCachesFromInstalledPackages(const std::map<std::string, QString>& bootable_paths);
void ShowOptionalGamePreparations(const QString& title, const QString& message, std::map<std::string, QString> game_path); void ShowOptionalGamePreparations(const QString& title, const QString& message, std::map<std::string, QString> game_path);
static bool InstallFileInExData(const std::string& extension, const QString& path, const std::string& filename); static bool InstallFileInExData(const std::string& extension, const QString& path, const std::string& filename);