Qt: Add steam shortcuts

This commit is contained in:
Megamouse 2026-03-10 20:20:12 +01:00
parent 2c2ec6e109
commit 8b74ea8757
15 changed files with 1136 additions and 76 deletions

View file

@ -131,7 +131,7 @@ std::set<std::string> get_one_drive_paths()
do
{
path_buffer.resize(path_buffer.size() + MAX_PATH);
DWORD buffer_size = static_cast<DWORD>(path_buffer.size() - 1);
DWORD buffer_size = static_cast<DWORD>((path_buffer.size() - 1) * sizeof(wchar_t));
status = RegQueryValueExW(hkey, L"UserFolder", NULL, &type, reinterpret_cast<LPBYTE>(path_buffer.data()), &buffer_size);
}
while (status == ERROR_MORE_DATA);

View file

@ -891,6 +891,7 @@
<ClCompile Include="rpcs3qt\shortcut_settings.cpp" />
<ClCompile Include="rpcs3qt\shortcut_utils.cpp" />
<ClCompile Include="rpcs3qt\skylander_dialog.cpp" />
<ClCompile Include="rpcs3qt\steam_utils.cpp" />
<ClCompile Include="rpcs3qt\system_cmd_dialog.cpp" />
<ClCompile Include="rpcs3qt\table_item_delegate.cpp" />
<ClCompile Include="rpcs3qt\tooltips.cpp" />
@ -1719,6 +1720,7 @@
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL3 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\shortcut_utils.h" />
<ClInclude Include="rpcs3qt\steam_utils.h" />
<ClInclude Include="rpcs3qt\stylesheets.h" />
<CustomBuild Include="rpcs3qt\skylander_dialog.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>

View file

@ -1278,6 +1278,9 @@
<ClCompile Include="rpcs3qt\log_level_dialog.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\steam_utils.cpp">
<Filter>Gui\utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1523,6 +1526,9 @@
<ClInclude Include="rpcs3qt\log_level_dialog.h">
<Filter>Gui\settings</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\steam_utils.h">
<Filter>Gui\utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">

View file

@ -104,6 +104,7 @@ add_library(rpcs3_ui STATIC
shortcut_settings.cpp
skylander_dialog.cpp
sound_effect_manager_dialog.cpp
steam_utils.cpp
syntax_highlighter.cpp
system_cmd_dialog.cpp
table_item_delegate.cpp

View file

@ -1093,7 +1093,7 @@ void debugger_frame::UpdateUnitList()
idm::select<named_thread<ppu_thread>>(on_select, idm::unlocked);
}
m_hw_ppu_idx = cpu_list.size() + 1; // Account for NoThreadString thread
m_hw_ppu_idx = ::size32(cpu_list) + 1; // Account for NoThreadString thread
for (u32 i = 0; i < g_cfg.core.ppu_threads + 0u; i++)
{

View file

@ -1496,13 +1496,6 @@ void game_list_actions::CreateShortcuts(const std::vector<game_info>& games, con
gameid_token_value = gameinfo->info.serial;
}
#ifdef __linux__
const std::string target_cli_args = gameinfo->info.path.starts_with(dev_flash) ? fmt::format("--no-gui \"%%%%RPCS3_VFS%%%%:dev_flash/%s\"", gameinfo->info.path.substr(dev_flash.size()))
: fmt::format("--no-gui \"%%%%RPCS3_GAMEID%%%%:%s\"", gameid_token_value);
#else
const std::string target_cli_args = gameinfo->info.path.starts_with(dev_flash) ? fmt::format("--no-gui \"%%RPCS3_VFS%%:dev_flash/%s\"", gameinfo->info.path.substr(dev_flash.size()))
: fmt::format("--no-gui \"%%RPCS3_GAMEID%%:%s\"", gameid_token_value);
#endif
const std::string target_icon_dir = fmt::format("%sIcons/game_icons/%s/", fs::get_config_dir(), gameinfo->info.serial);
if (!fs::create_path(target_icon_dir))
@ -1512,7 +1505,11 @@ void game_list_actions::CreateShortcuts(const std::vector<game_info>& games, con
continue;
}
for (const gui::utils::shortcut_location& location : locations)
const bool is_vsh = gameinfo->info.path.starts_with(dev_flash);
const std::string cli_arg_token = is_vsh ? "RPCS3_VFS" : "RPCS3_GAMEID";
const std::string cli_arg_value = is_vsh ? ("dev_flash/" + gameinfo->info.path.substr(dev_flash.size())) : gameid_token_value;
for (gui::utils::shortcut_location location : locations)
{
std::string destination;
@ -1524,6 +1521,9 @@ void game_list_actions::CreateShortcuts(const std::vector<game_info>& games, con
case gui::utils::shortcut_location::applications:
destination = "application menu";
break;
case gui::utils::shortcut_location::steam:
destination = "Steam";
break;
#ifdef _WIN32
case gui::utils::shortcut_location::rpcs3_shortcuts:
destination = "/games/shortcuts/";
@ -1531,6 +1531,13 @@ void game_list_actions::CreateShortcuts(const std::vector<game_info>& games, con
#endif
}
#ifdef __linux__
const std::string percent = location == gui::utils::shortcut_location::steam ? "%" : "%%";
#else
const std::string percent = "%";
#endif
const std::string target_cli_args = fmt::format("--no-gui \"%s%s%s:%s\"", percent, cli_arg_token, percent, cli_arg_value);
if (!gameid_token_value.empty() && gui::utils::create_shortcut(gameinfo->info.name, gameinfo->icon_in_archive ? gameinfo->info.path : "", gameinfo->info.serial, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, location))
{
game_list_log.success("Created %s shortcut for %s", destination, QString::fromStdString(gameinfo->info.name).simplified());

View file

@ -6,6 +6,7 @@
#include "input_dialog.h"
#include "qt_utils.h"
#include "shortcut_utils.h"
#include "steam_utils.h"
#include "settings_dialog.h"
#include "pad_settings_dialog.h"
#include "patch_manager_dialog.h"
@ -273,6 +274,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
{
m_game_list_actions->CreateShortcuts({gameinfo}, {gui::utils::shortcut_location::desktop});
});
#ifdef _WIN32
QAction* create_start_menu_shortcut = manage_game_menu->addAction(tr("&Create Start Menu Shortcut"));
#elif defined(__APPLE__)
@ -285,6 +287,17 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
m_game_list_actions->CreateShortcuts({gameinfo}, {gui::utils::shortcut_location::applications});
});
if (gui::utils::steam_shortcut::steam_installed())
{
const bool steam_running = gui::utils::steam_shortcut::is_steam_running();
QAction* create_steam_shortcut = manage_game_menu->addAction(steam_running ? tr("&Create Steam Shortcut (Steam must be closed)") : tr("&Create Steam Shortcut"));
connect(create_steam_shortcut, &QAction::triggered, this, [this, gameinfo]()
{
m_game_list_actions->CreateShortcuts({gameinfo}, {gui::utils::shortcut_location::steam});
});
create_steam_shortcut->setEnabled(!steam_running);
}
manage_game_menu->addSeparator();
// Hide/rename game in game list
@ -843,6 +856,20 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
m_game_list_actions->CreateShortcuts(games, {gui::utils::shortcut_location::applications});
});
if (gui::utils::steam_shortcut::steam_installed())
{
const bool steam_running = gui::utils::steam_shortcut::is_steam_running();
QAction* create_steam_shortcut = manage_game_menu->addAction(steam_running ? tr("&Create Steam Shortcut (Steam must be closed)") : tr("&Create Steam Shortcut"));
connect(create_steam_shortcut, &QAction::triggered, this, [this, games]()
{
if (QMessageBox::question(m_game_list_frame, tr("Confirm Creation"), tr("Create Steam shortcut?")) != QMessageBox::Yes)
return;
m_game_list_actions->CreateShortcuts(games, {gui::utils::shortcut_location::steam});
});
create_steam_shortcut->setEnabled(!steam_running);
}
manage_game_menu->addSeparator();
// Hide game in game list

View file

@ -35,9 +35,9 @@
#include "camera_settings_dialog.h"
#include "ps_move_tracker_dialog.h"
#include "ipc_settings_dialog.h"
#include "shortcut_utils.h"
#include "config_checker.h"
#include "shortcut_dialog.h"
#include "steam_utils.h"
#include "system_cmd_dialog.h"
#include "emulated_pad_settings_dialog.h"
#include "emulated_logitech_g27_settings_dialog.h"
@ -935,9 +935,8 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
}
std::vector<compat::package_info> packages;
std::set<gui::utils::shortcut_location> shortcut_locations;
bool precompile_caches = false;
bool create_desktop_shortcuts = false;
bool create_app_shortcut = false;
bool canceled = false;
game_compatibility* compat = m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr;
@ -954,8 +953,15 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
packages = dlg.get_paths_to_install();
precompile_caches = dlg.precompile_caches();
create_desktop_shortcuts = dlg.create_desktop_shortcuts();
create_app_shortcut = dlg.create_app_shortcut();
if (dlg.create_desktop_shortcuts())
shortcut_locations.insert(gui::utils::shortcut_location::desktop);
if (dlg.create_app_shortcut())
shortcut_locations.insert(gui::utils::shortcut_location::applications);
if (dlg.create_steam_shortcut())
shortcut_locations.insert(gui::utils::shortcut_location::steam);
});
dlg.exec();
@ -1136,7 +1142,7 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
if (!bootable_paths_installed.empty())
{
m_game_list_frame->AddRefreshedSlot([this, create_desktop_shortcuts, precompile_caches, create_app_shortcut, paths = std::move(bootable_paths_installed)](std::set<std::string>& claimed_paths) mutable
m_game_list_frame->AddRefreshedSlot([this, shortcut_locations, precompile_caches, paths = std::move(bootable_paths_installed)](std::set<std::string>& claimed_paths) mutable
{
// Try to claim operations on ID
for (auto it = paths.begin(); it != paths.end();)
@ -1154,7 +1160,7 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
}
}
CreateShortCuts(paths, create_desktop_shortcuts, create_app_shortcut);
CreateShortCuts(paths, shortcut_locations);
if (precompile_caches)
{
@ -2370,6 +2376,7 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
#else
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
#endif
QLabel* label = new QLabel(tr("%1\nWould you like to precompile caches and install shortcuts to the installed software? (%2 new software detected)\n\n").arg(message).arg(bootable_paths.size()), dlg);
vlayout->addWidget(label);
@ -2381,6 +2388,17 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
vlayout->addWidget(quick_check);
vlayout->addStretch(3);
QCheckBox* steam_check = nullptr;
if (gui::utils::steam_shortcut::steam_installed())
{
const bool steam_running = gui::utils::steam_shortcut::is_steam_running();
steam_check = new QCheckBox(steam_running ? tr("Add Steam Shortcut(s) (Steam must be closed)") : tr("Add Steam shortcut(s)"));
steam_check->setEnabled(!steam_running);
vlayout->addWidget(steam_check);
vlayout->addStretch(3);
}
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
vlayout->addWidget(btn_box);
@ -2389,13 +2407,22 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
connect(btn_box, &QDialogButtonBox::accepted, this, [=, this, paths = std::move(bootable_paths)]()
{
const bool precompile_caches = precompile_check->isChecked();
const bool create_desktop_shortcuts = desk_check->isChecked();
const bool create_app_shortcut = quick_check->isChecked();
std::set<gui::utils::shortcut_location> shortcut_locations;
if (desk_check->isChecked())
shortcut_locations.insert(gui::utils::shortcut_location::desktop);
if (quick_check->isChecked())
shortcut_locations.insert(gui::utils::shortcut_location::applications);
if (steam_check && steam_check->isChecked())
shortcut_locations.insert(gui::utils::shortcut_location::steam);
dlg->hide();
dlg->accept();
CreateShortCuts(paths, create_desktop_shortcuts, create_app_shortcut);
CreateShortCuts(paths, shortcut_locations);
if (precompile_caches)
{
@ -2407,52 +2434,40 @@ void main_window::ShowOptionalGamePreparations(const QString& title, const QStri
dlg->open();
}
void main_window::CreateShortCuts(const std::map<std::string, QString>& paths, bool create_desktop_shortcuts, bool create_app_shortcut)
void main_window::CreateShortCuts(const std::map<std::string, QString>& paths, std::set<gui::utils::shortcut_location> locations)
{
if (paths.empty()) return;
std::set<gui::utils::shortcut_location> locations;
#ifdef _WIN32
locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts);
#else
if (locations.empty())
{
return;
}
#endif
if (create_desktop_shortcuts)
{
locations.insert(gui::utils::shortcut_location::desktop);
}
if (create_app_shortcut)
{
locations.insert(gui::utils::shortcut_location::applications);
}
std::vector<game_info> game_data_shortcuts;
if (!locations.empty())
for (const auto& [boot_path, title_id] : paths)
{
std::vector<game_info> game_data_shortcuts;
for (const auto& [boot_path, title_id] : paths)
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo())
{
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo())
if (gameinfo && gameinfo->info.serial == title_id.toStdString())
{
if (gameinfo && gameinfo->info.serial == title_id.toStdString())
if (Emu.IsPathInsideDir(boot_path, gameinfo->info.path))
{
if (Emu.IsPathInsideDir(boot_path, gameinfo->info.path))
{
if (!locations.empty())
{
game_data_shortcuts.push_back(gameinfo);
}
}
break;
game_data_shortcuts.push_back(gameinfo);
}
break;
}
}
}
if (!game_data_shortcuts.empty() && !locations.empty())
{
m_game_list_frame->actions()->CreateShortcuts(game_data_shortcuts, locations);
}
if (!game_data_shortcuts.empty())
{
m_game_list_frame->actions()->CreateShortcuts(game_data_shortcuts, locations);
}
}

View file

@ -10,6 +10,7 @@
#include "update_manager.h"
#include "settings.h"
#include "shortcut_handler.h"
#include "shortcut_utils.h"
#include "Emu/config_mode.h"
#include "Emu/System.h"
@ -145,7 +146,7 @@ private:
static bool InstallFileInExData(const std::string& extension, const QString& path, const std::string& filename);
bool HandlePackageInstallation(QStringList file_paths, bool from_boot);
void CreateShortCuts(const std::map<std::string, QString>& paths, bool create_desktop_shortcuts, bool create_app_shortcut);
void CreateShortCuts(const std::map<std::string, QString>& paths, std::set<gui::utils::shortcut_location> locations);
void HandlePupInstallation(const QString& file_path, const QString& dir_path = "");
void ExtractPup();

View file

@ -3,6 +3,7 @@
#include "numbered_widget_item.h"
#include "richtext_item_delegate.h"
#include "qt_utils.h"
#include "steam_utils.h"
#include "Emu/system_utils.hpp"
#include "Utilities/File.h"
@ -194,6 +195,17 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
vbox->addWidget(precompile_check);
vbox->addWidget(desk_check);
vbox->addWidget(quick_check);
if (gui::utils::steam_shortcut::steam_installed())
{
const bool steam_running = gui::utils::steam_shortcut::is_steam_running();
QCheckBox* steam_check = new QCheckBox(steam_running ? tr("Add Steam Shortcut(s) (Steam must be closed)") : tr("Add Steam shortcut(s)"));
connect(steam_check, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state){ m_create_steam_shortcut = state != Qt::CheckState::Unchecked; });
steam_check->setEnabled(!steam_running);
vbox->addWidget(steam_check);
}
vbox->addWidget(installation_info);
vbox->addWidget(buttons);

View file

@ -22,6 +22,7 @@ public:
bool precompile_caches() const { return m_precompile_caches; }
bool create_desktop_shortcuts() const { return m_create_desktop_shortcuts; }
bool create_app_shortcut() const { return m_create_app_shortcut; }
bool create_steam_shortcut() const { return m_create_steam_shortcut; }
private:
void update_info(QLabel* installation_info, QDialogButtonBox* buttons) const;
@ -31,4 +32,5 @@ private:
bool m_precompile_caches = false;
bool m_create_desktop_shortcuts = false;
bool m_create_app_shortcut = false;
bool m_create_steam_shortcut = false;
};

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "shortcut_utils.h"
#include "steam_utils.h"
#include "qt_utils.h"
#include "Emu/VFS.h"
#include "Utilities/File.h"
@ -30,6 +31,25 @@
LOG_CHANNEL(sys_log, "SYS");
template <>
void fmt_class_string<gui::utils::shortcut_location>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](gui::utils::shortcut_location value)
{
switch (value)
{
case gui::utils::shortcut_location::desktop: return "desktop";
case gui::utils::shortcut_location::applications: return "applications";
case gui::utils::shortcut_location::steam: return "steam";
#ifdef _WIN32
case gui::utils::shortcut_location::rpcs3_shortcuts: return "rpcs3";
#endif
}
return unknown;
});
}
namespace gui::utils
{
#ifdef _WIN32
@ -109,6 +129,7 @@ namespace gui::utils
}
std::string link_path;
bool append_rpcs3 = false;
switch (location)
{
@ -117,6 +138,13 @@ namespace gui::utils
break;
case shortcut_location::applications:
link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ApplicationsLocation).toStdString();
append_rpcs3 = true;
break;
case shortcut_location::steam:
#ifdef __APPLE__
link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ApplicationsLocation).toStdString();
append_rpcs3 = true;
#endif
break;
#ifdef _WIN32
case shortcut_location::rpcs3_shortcuts:
@ -126,13 +154,13 @@ namespace gui::utils
#endif
}
if (!fs::is_dir(link_path) && !fs::create_dir(link_path))
if (!link_path.empty() && !fs::is_dir(link_path) && !fs::create_dir(link_path))
{
sys_log.error("Failed to create shortcut. Folder does not exist: %s", link_path);
return false;
}
if (location == shortcut_location::applications)
if (append_rpcs3)
{
link_path += "/RPCS3";
@ -144,6 +172,29 @@ namespace gui::utils
}
#ifdef _WIN32
const std::string working_dir{fs::get_executable_dir()};
const std::string rpcs3_path{fs::get_executable_path()};
std::string target_icon_path;
if (!src_icon_path.empty() && !target_icon_dir.empty())
{
if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, 512))
{
sys_log.error("Failed to create shortcut: .ico creation failed");
return false;
}
}
if (location == shortcut_location::steam)
{
sys_log.notice("Creating %s shortcut with arguments '%s' and icon path '%s'", location, target_cli_args, target_icon_path);
steam_shortcut steam_sc{};
steam_sc.add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path);
return steam_sc.write_file();
}
sys_log.notice("Creating %s shortcut '%s' with arguments '%s' and .ico dir '%s'", location, link_path, target_cli_args, target_icon_dir);
const auto str_error = [](HRESULT hr) -> std::string
{
_com_error err(hr);
@ -153,8 +204,6 @@ namespace gui::utils
fmt::append(link_path, "/%s.lnk", simple_name);
sys_log.notice("Creating shortcut '%s' with arguments '%s' and .ico dir '%s'", link_path, target_cli_args, target_icon_dir);
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
HRESULT res = CoInitialize(NULL);
if (FAILED(res))
@ -177,9 +226,6 @@ namespace gui::utils
if (FAILED(res))
return cleanup(false, "CoCreateInstance failed");
const std::string working_dir{ fs::get_executable_dir() };
const std::string rpcs3_path{ working_dir + "rpcs3.exe" };
const std::wstring w_target_file = utf8_to_wchar(rpcs3_path);
res = pShellLink->SetPath(w_target_file.c_str());
if (FAILED(res))
@ -206,12 +252,8 @@ namespace gui::utils
return cleanup(false, fmt::format("SetDescription failed (%s)", str_error(res)));
}
if (!src_icon_path.empty() && !target_icon_dir.empty())
if (!target_icon_path.empty())
{
std::string target_icon_path;
if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, 512))
return cleanup(false, ".ico creation failed");
const std::wstring w_icon_path = utf8_to_wchar(target_icon_path);
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
if (FAILED(res))
@ -241,9 +283,10 @@ namespace gui::utils
return cleanup(true, {});
#elif defined(__APPLE__)
fmt::append(link_path, "/%s.app", simple_name);
sys_log.notice("Creating shortcut '%s' with arguments '%s'", link_path, target_cli_args);
sys_log.notice("Creating %s shortcut '%s' with arguments '%s'", location, link_path, target_cli_args);
const std::string contents_dir = link_path + "/Contents/";
const std::string macos_dir = contents_dir + "MacOS/";
@ -320,9 +363,10 @@ namespace gui::utils
}
plist_file.close();
std::string target_icon_path;
if (!src_icon_path.empty())
{
std::string target_icon_path = resources_dir;
target_icon_path = resources_dir;
if (!create_square_shortcut_icon_file(path, src_icon_path, resources_dir, target_icon_path, 512))
{
// Error is logged in create_square_shortcut_icon_file
@ -330,10 +374,16 @@ namespace gui::utils
}
}
if (location == shortcut_location::steam)
{
steam_shortcut steam_sc{};
steam_sc.add_shortcut(simple_name, launcher_path, macos_dir, ""/*target_cli_args are already in the launcher*/, target_icon_path);
return steam_sc.write_file();
}
return true;
#else
const std::string exe_path = fs::get_executable_path();
if (exe_path.empty())
{
@ -341,9 +391,28 @@ namespace gui::utils
return false;
}
std::string target_icon_path;
if (!src_icon_path.empty() && !target_icon_dir.empty())
{
if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, 512))
{
// Error is logged in create_square_shortcut_icon_file
return false;
}
}
if (location == shortcut_location::steam)
{
sys_log.notice("Creating %s shortcut with arguments '%s' and icon path '%s'", location, target_cli_args, target_icon_path);
const std::string working_dir{fs::get_executable_dir()};
steam_shortcut steam_sc{};
steam_sc.add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path);
return steam_sc.write_file();
}
fmt::append(link_path, "/%s.desktop", simple_name);
sys_log.notice("Creating shortcut '%s' for '%s' with arguments '%s'", link_path, exe_path, target_cli_args);
sys_log.notice("Creating %s shortcut '%s' for '%s' with arguments '%s'", location, link_path, exe_path, target_cli_args);
std::string file_content;
fmt::append(file_content, "[Desktop Entry]\n");
@ -360,15 +429,8 @@ namespace gui::utils
fmt::append(file_content, "Comment=%s\n", QString::fromStdString(description).simplified());
}
if (!src_icon_path.empty() && !target_icon_dir.empty())
if (!target_icon_path.empty())
{
std::string target_icon_path;
if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, 512))
{
// Error is logged in create_square_shortcut_icon_file
return false;
}
fmt::append(file_content, "Icon=%s\n", target_icon_path);
}
@ -438,7 +500,8 @@ namespace gui::utils
std::vector<shortcut_location> locations = {
shortcut_location::desktop,
shortcut_location::applications
shortcut_location::applications,
shortcut_location::steam,
};
#ifdef _WIN32
locations.push_back(shortcut_location::rpcs3_shortcuts);
@ -457,6 +520,18 @@ namespace gui::utils
link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ApplicationsLocation).toStdString();
link_path += "/RPCS3";
break;
case shortcut_location::steam:
{
const std::string exe_path = fs::get_executable_path();
const std::string working_dir = fs::get_executable_dir();
steam_shortcut steam_sc{};
steam_sc.remove_shortcut(simple_name, exe_path, working_dir);
if (!steam_sc.write_file())
{
sys_log.error("Failed to remove steam shortcut for '%s'", simple_name);
}
continue;
}
#ifdef _WIN32
case shortcut_location::rpcs3_shortcuts:
link_path = rpcs3::utils::get_games_shortcuts_dir();

View file

@ -6,6 +6,7 @@ namespace gui::utils
{
desktop,
applications,
steam,
#ifdef _WIN32
rpcs3_shortcuts,
#endif

View file

@ -0,0 +1,807 @@
#include "stdafx.h"
#include "steam_utils.h"
#include <filesystem>
#ifdef _WIN32
#include <Windows.h>
#include <tlhelp32.h>
#else
#include <signal.h>
#include <errno.h>
#endif
LOG_CHANNEL(sys_log, "SYS");
namespace gui::utils
{
void steam_shortcut::add_shortcut(
const std::string& app_name,
const std::string& exe,
const std::string& start_dir,
const std::string& launch_options,
const std::string& icon_path)
{
shortcut_entry entry{};
entry.app_name = app_name;
entry.exe = quote(fix_slashes(exe), true);
entry.start_dir = quote(fix_slashes(start_dir), false);
entry.launch_options = launch_options;
entry.icon = quote(fix_slashes(icon_path), false);
entry.appid = steam_appid(exe, app_name);
m_entries_to_add.push_back(std::move(entry));
}
void steam_shortcut::remove_shortcut(
const std::string& app_name,
const std::string& exe,
const std::string& start_dir)
{
shortcut_entry entry{};
entry.app_name = app_name;
entry.exe = quote(fix_slashes(exe), true);
entry.start_dir = quote(fix_slashes(start_dir), false);
entry.appid = steam_appid(exe, app_name);
m_entries_to_remove.push_back(std::move(entry));
}
bool steam_shortcut::parse_file(const std::string& path)
{
m_vdf_entries.clear();
fs::file vdf(path);
if (!vdf)
{
sys_log.error("Failed to parse steam shortcut file '%s': %s", path, fs::g_tls_error);
return false;
}
const std::vector<u8> data = vdf.to_vector<u8>();
usz last_pos = 0;
usz pos = 0;
const auto read_type_id = [&]() -> u8
{
if (pos >= data.size())
{
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: read_type_id: end of file", path, pos);
return umax;
}
last_pos = pos;
return data[pos++];
};
const auto read_u32 = [&]() -> std::optional<u32>
{
if ((pos + sizeof(u32)) > data.size())
{
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: read_u32: end of file", path, pos);
return {};
}
last_pos = pos;
u32 v {};
std::memcpy(&v, &data[pos], sizeof(u32));
pos += sizeof(u32);
return v;
};
const auto read_string = [&]() -> std::optional<std::string>
{
if (pos >= data.size())
{
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: read_string: end of file", path, pos);
return {};
}
last_pos = pos;
std::string str;
while (pos < data.size())
{
const u8 c = data[pos++];
if (!c) break;
str += c;
}
if (pos >= data.size())
{
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: read_string: not null terminated", path, last_pos);
return {};
}
return str;
};
#define CHECK_VDF(cond, msg) \
if (!(cond)) \
{ \
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: %s", path, last_pos, msg); \
return false; \
}
#define READ_VDF_STRING(name) \
const std::optional<std::string> name##_opt = read_string(); \
if (!name##_opt.has_value()) return false; \
std::string name = name##_opt.value();
#define READ_VDF_U32(name) \
const std::optional<u32> name##_opt = read_u32(); \
if (!name##_opt.has_value()) return false; \
const u32 name = name##_opt.value();
CHECK_VDF(read_type_id() == type_id::Start, "expected type_id::Start for shortcuts");
READ_VDF_STRING(shortcuts);
CHECK_VDF(shortcuts == "shortcuts", "expected 'shortcuts' key");
for (usz index = 0; true; index++)
{
vdf_shortcut_entry entry {};
u8 type = read_type_id();
if (type == type_id::End)
{
// End of shortcuts
break;
}
CHECK_VDF(type == type_id::Start, "expected type_id::Start for entry");
READ_VDF_STRING(entry_index_str);
u64 entry_index = 0;
CHECK_VDF(try_to_uint64(&entry_index, entry_index_str, 0, umax), "failed to convert entry index");
CHECK_VDF(entry_index == index, "unexpected entry index");
type = umax;
while (type != type_id::Start)
{
type = read_type_id();
switch (type)
{
case type_id::String:
{
READ_VDF_STRING(key);
READ_VDF_STRING(value);
CHECK_VDF(!key.empty(), "key is empty");
entry.values.push_back({std::move(key), std::move(value)});
break;
}
case type_id::Integer:
{
READ_VDF_STRING(key);
READ_VDF_U32(value);
CHECK_VDF(!key.empty(), "key is empty");
entry.values.push_back({std::move(key), value});
break;
}
case type_id::Start:
// Expect tags next
break;
default:
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: unexpected type id 0x%x", path, last_pos, type);
return false;
}
}
CHECK_VDF(type == type_id::Start, "expected type_id::Start for tags");
READ_VDF_STRING(tags);
CHECK_VDF(tags == "tags", "key is empty");
type = umax;
while (type != type_id::End)
{
type = read_type_id();
switch (type)
{
case type_id::String:
{
READ_VDF_STRING(key);
READ_VDF_STRING(value);
CHECK_VDF(!key.empty(), "key is empty");
entry.tags.push_back({std::move(key), std::move(value)});
break;
}
case type_id::End:
break;
default:
sys_log.error("Failed to parse steam shortcut file '%s' at pos 0x%x: unexpected type id 0x%x", path, last_pos, type);
return false;
}
}
CHECK_VDF(type == type_id::End, "expected type_id::End for tags");
CHECK_VDF(read_type_id() == type_id::End, "expected type_id::End for entry");
m_vdf_entries.push_back(std::move(entry));
}
CHECK_VDF(read_type_id() == type_id::End, "expected type_id::End for end of file");
CHECK_VDF(pos == data.size(), fmt::format("bytes found at end of file (pos=%d, size=%d)", pos, data.size()));
#undef CHECK_VDF_OPT
#undef CHECK_VDF
return true;
}
bool steam_shortcut::write_file()
{
if (m_entries_to_add.empty() && m_entries_to_remove.empty())
{
sys_log.error("Failed to create steam shortcut: No entries.");
return false;
}
const std::string steam_path = get_steam_path();
if (steam_path.empty())
{
sys_log.error("Failed to create steam shortcut: Steam directory not found.");
return false;
}
if (!fs::is_dir(steam_path))
{
sys_log.error("Failed to create steam shortcut: '%s' not a directory.", steam_path);
return false;
}
const std::string user_id = get_last_active_steam_user(steam_path);
if (user_id.empty())
{
sys_log.error("Failed to create steam shortcut: last active user not found.");
return false;
}
const std::string user_dir = steam_path + "/userdata/" + user_id + "/config/";
if (!fs::is_dir(user_dir))
{
sys_log.error("Failed to create steam shortcut: '%s' not a directory.", user_dir);
return false;
}
if (is_steam_running())
{
sys_log.error("Failed to create steam shortcut: steam is running.");
return false;
}
const std::string file_path = user_dir + "shortcuts.vdf";
const std::string backup_path = fs::get_config_dir() + "/shortcuts.vdf.backup";
if (fs::is_file(file_path))
{
if (!fs::copy_file(file_path, backup_path, true))
{
sys_log.error("Failed to backup steam shortcut file '%s'", file_path);
return false;
}
if (!parse_file(file_path))
{
sys_log.error("Failed to parse steam shortcut file '%s'", file_path);
return false;
}
}
std::vector<shortcut_entry> removed_entries;
for (const shortcut_entry& entry : m_entries_to_remove)
{
bool removed_entry = false;
for (auto it = m_vdf_entries.begin(); it != m_vdf_entries.end();)
{
const auto appid = it->value<u32>("appid");
const auto exe = it->value<std::string>("Exe");
const auto start_dir = it->value<std::string>("StartDir");
if (appid.has_value() && appid.value() == entry.appid &&
exe.has_value() && exe.value() == entry.exe &&
start_dir.has_value() && start_dir.value() == entry.start_dir)
{
sys_log.notice("Removing steam shortcut for '%s'", entry.app_name);
it = m_vdf_entries.erase(it);
removed_entry = true;
}
else
{
it++;
}
}
if (removed_entry)
{
removed_entries.push_back(entry);
}
if (m_vdf_entries.empty())
{
break;
}
}
for (const vdf_shortcut_entry& entry : m_vdf_entries)
{
for (auto it = m_entries_to_add.begin(); it != m_entries_to_add.end();)
{
const auto appid = entry.value<u32>("appid");
const auto exe = entry.value<std::string>("Exe");
const auto start_dir = entry.value<std::string>("StartDir");
const auto launch_options = entry.value<std::string>("LaunchOptions");
const auto icon = entry.value<std::string>("icon");
if (appid.has_value() && appid.value() == it->appid &&
exe.has_value() && exe.value() == it->exe &&
start_dir.has_value() && start_dir.value() == it->start_dir &&
launch_options.has_value() && launch_options.value() == it->launch_options &&
icon.has_value() && icon.value() == it->icon)
{
sys_log.notice("Entry '%s' already exists in steam shortcut file '%s'.", it->app_name, file_path);
it = m_entries_to_add.erase(it);
}
else
{
it++;
}
}
if (m_entries_to_add.empty())
{
break;
}
}
if (m_entries_to_add.empty() && removed_entries.empty())
{
sys_log.notice("No matching entries found in steam shortcut file '%s'.", file_path);
return true;
}
usz index = 0;
std::string content;
content += type_id::Start;
append(content, "shortcuts");
for (const vdf_shortcut_entry& entry : m_vdf_entries)
{
const auto val = entry.build_binary_blob(index++);
if (!val.has_value())
{
sys_log.error("Failed to create steam shortcut '%s': '%s'", file_path, val.error());
return false;
}
content += val.value();
}
for (const shortcut_entry& entry : m_entries_to_add)
{
content += entry.build_binary_blob(index++);
}
content += type_id::End;
content += type_id::End; // End of file
if (!fs::write_file(file_path, fs::rewrite, content))
{
sys_log.error("Failed to create steam shortcut '%s': '%s'", file_path, fs::g_tls_error);
if (!fs::copy_file(backup_path, file_path, true))
{
sys_log.error("Failed to restore steam shortcuts backup: '%s'", fs::g_tls_error);
}
return false;
}
for (const shortcut_entry& entry : m_entries_to_add)
{
sys_log.success("Created steam shortcut for '%s'", entry.app_name);
}
for (const shortcut_entry& entry : removed_entries)
{
sys_log.success("Removed steam shortcut(s) for '%s'", entry.app_name);
}
return true;
}
u32 steam_shortcut::crc32(const std::string& data)
{
u32 crc = 0xFFFFFFFF;
for (u8 c : data)
{
crc ^= c;
for (int i = 0; i < 8; i++)
{
crc = (crc >> 1) ^ (0xEDB88320 & -static_cast<int>(crc & 1));
}
}
return ~crc;
}
bool steam_shortcut::steam_installed()
{
const std::string path = get_steam_path();
return !path.empty() && fs::is_dir(path);
}
u32 steam_shortcut::steam_appid(const std::string& exe, const std::string& name)
{
return crc32(exe + name) | 0x80000000;
}
void steam_shortcut::append(std::string& s, const std::string& val)
{
s += val;
s += '\0'; // append null terminator
}
std::string steam_shortcut::quote(const std::string& s, bool force)
{
if (force || s.contains(" "))
{
return "\"" + s + "\"";
}
return s;
}
std::string steam_shortcut::fix_slashes(const std::string& s)
{
#ifdef _WIN32
return fmt::replace_all(s, "/", "\\");
#else
return s;
#endif
}
std::string steam_shortcut::kv_string(const std::string& key, const std::string& value)
{
std::string ret;
ret += type_id::String;
append(ret, key);
append(ret, value);
return ret;
}
std::string steam_shortcut::kv_int(const std::string& key, u32 value)
{
std::string str;
str.reserve(64);
str += type_id::Integer;
append(str, key);
str += static_cast<char>(value & 0xFF);
str += static_cast<char>((value >> 8) & 0xFF);
str += static_cast<char>((value >> 16) & 0xFF);
str += static_cast<char>((value >> 24) & 0xFF);
return str;
}
std::string steam_shortcut::shortcut_entry::build_binary_blob(usz index) const
{
std::string str;
str.reserve(1024);
str += type_id::Start;
append(str, std::to_string(index));
str += kv_int("appid", appid);
str += kv_string("AppName", app_name);
str += kv_string("Exe", exe);
str += kv_string("StartDir", start_dir);
str += kv_string("icon", icon);
str += kv_string("ShortcutPath", "");
str += kv_string("LaunchOptions", launch_options);
str += kv_int("IsHidden", 0);
str += kv_int("AllowDesktopConfig", 1);
str += kv_int("AllowOverlay", 1);
str += kv_int("OpenVR", 0);
str += kv_int("Devkit", 0);
str += kv_string("DevkitGameID", "");
str += kv_int("DevkitOverrideAppID", 0);
str += kv_int("LastPlayTime", 0);
str += kv_string("FlatpakAppID", "");
str += kv_string("sortas", "");
str += type_id::Start;
append(str, "tags");
str += type_id::End;
str += type_id::End;
return str;
}
std::expected<std::string, std::string> steam_shortcut::vdf_shortcut_entry::build_binary_blob(usz index) const
{
std::string str;
str.reserve(1024);
str += type_id::Start;
append(str, std::to_string(index));
std::optional<std::string> error = std::nullopt;
for (const auto& [key, value] : values)
{
std::visit([&key, &str, &error](const auto& value)
{
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<T, std::string>)
{
str += kv_string(key, value);
}
else if constexpr (std::is_same_v<T, u32>)
{
str += kv_int(key, value);
}
else
{
error = fmt::format("vdf entry for key '%s' has unexpected type '%s'", key, typeid(value).name());
}
},
value);
if (error.has_value())
{
return std::unexpected(error.value());
}
}
str += type_id::Start;
append(str, "tags");
for (const auto& [key, value] : tags)
{
str += kv_string(key, value);
}
str += type_id::End;
str += type_id::End;
return str;
}
#ifdef _WIN32
std::string get_registry_string(const wchar_t* key, const wchar_t* name)
{
HKEY hkey = NULL;
LSTATUS status = RegOpenKeyW(HKEY_CURRENT_USER, key, &hkey);
if (status != ERROR_SUCCESS)
{
sys_log.trace("get_registry_string: RegOpenKeyW failed: %s (key='%s', name='%s')", fmt::win_error{static_cast<unsigned long>(status), nullptr}, wchar_to_utf8(key), wchar_to_utf8(name));
return "";
}
std::wstring path_buffer;
DWORD type = 0U;
do
{
path_buffer.resize(path_buffer.size() + MAX_PATH);
DWORD buffer_size = static_cast<DWORD>((path_buffer.size() - 1) * sizeof(wchar_t));
status = RegQueryValueExW(hkey, name, NULL, &type, reinterpret_cast<LPBYTE>(path_buffer.data()), &buffer_size);
}
while (status == ERROR_MORE_DATA);
const LSTATUS close_status = RegCloseKey(hkey);
if (close_status != ERROR_SUCCESS)
{
sys_log.error("get_registry_string: RegCloseKey failed: %s (key='%s', name='%s')", fmt::win_error{static_cast<unsigned long>(close_status), nullptr}, wchar_to_utf8(key), wchar_to_utf8(name));
}
if (status != ERROR_SUCCESS)
{
sys_log.trace("get_registry_string: RegQueryValueExW failed: %s (key='%s', name='%s')", fmt::win_error{static_cast<unsigned long>(status), nullptr}, wchar_to_utf8(key), wchar_to_utf8(name));
return "";
}
if ((type == REG_SZ) || (type == REG_EXPAND_SZ) || (type == REG_MULTI_SZ))
{
return wchar_to_utf8(path_buffer.data());
}
return "";
}
#endif
std::string steam_shortcut::steamid64_to_32(const std::string& steam_id)
{
u64 id = 0;
if (!try_to_uint64(&id, steam_id, 0, umax))
{
sys_log.error("Failed to convert steam id '%s' to u64", steam_id);
return "";
}
constexpr u64 base = 76561197960265728ULL;
const u32 id32 = static_cast<u32>(id - base);
return std::to_string(id32);
}
std::string steam_shortcut::get_steam_path()
{
#ifdef _WIN32
const std::string path = get_registry_string(L"Software\\Valve\\Steam", L"SteamPath");
if (path.empty())
{
sys_log.notice("get_steam_path: SteamPath not found in registry");
return "";
}
// The path might be lowercase... sigh
std::error_code ec;
const std::filesystem::path canonical_path = std::filesystem::canonical(std::filesystem::path(path), ec);
if (ec)
{
sys_log.error("get_steam_path: Failed to canonicalize path '%s': %s", path, ec.message());
return "";
}
const std::string path_fixed = canonical_path.string();
sys_log.notice("get_steam_path: Found steam registry path: '%s'", path_fixed);
return path_fixed;
#else
if (const char* home = ::getenv("HOME"))
{
#if __APPLE__
const std::string path = std::string(home) + "/Library/Application Support/Steam/";
#else
const std::string path = std::string(home) + "/.local/share/Steam/";
#endif
return path;
}
return "";
#endif
}
std::string steam_shortcut::get_last_active_steam_user(const std::string& steam_path)
{
const std::string vdf_path = steam_path + "/config/loginusers.vdf";
fs::file vdf(vdf_path);
if (!vdf)
{
sys_log.error("get_last_active_steam_user: Failed to parse steam loginusers file '%s': %s", vdf_path, fs::g_tls_error);
return "";
}
// The file looks roughly like this. We need the numerical ID.
// "users"
// {
// "12345678901234567"
// {
// "AccountName" "myusername"
// "MostRecent" "1"
// ...
// }
// ...
// }
const std::string content = vdf.to_string();
usz user_count = 0;
const auto find_user_id = [&content, &user_count](const std::string& key, const std::string& comp) -> std::string
{
user_count = 0;
usz pos = 0;
while (true)
{
pos = content.find(key, pos);
if (pos == umax) break;
user_count++;
const usz val_start = content.find('"', pos + key.size());
if (val_start == umax) break;
const usz val_end = content.find('"', val_start + 1);
if (val_end == umax) break;
const std::string value = content.substr(val_start + 1, val_end - val_start - 1);
if (value != comp)
{
pos = val_end + 1;
continue;
}
const usz pos_brace = content.rfind('{', pos - 2);
if (pos_brace == umax) return "";
const usz pos_end = content.rfind('"', pos_brace - 2);
if (pos_end == umax) return "";
const usz pos_start = content.rfind('"', pos_end - 1);
if (pos_start == umax) return "";
const std::string user_id_64 = content.substr(pos_start + 1, pos_end - pos_start - 1);
return steamid64_to_32(user_id_64);
}
return "";
};
if (const std::string id = find_user_id("\"MostRecent\"", "1"); !id.empty())
{
return id;
}
#ifdef _WIN32
// Fallback to AutoLoginUser
const std::string username = get_registry_string(L"Software\\Valve\\Steam", L"AutoLoginUser");
if (username.empty())
{
sys_log.notice("get_last_active_steam_user: AutoLoginUser not found in registry");
return "";
}
sys_log.notice("get_last_active_steam_user: Found steam user: '%s'", username);
if (const std::string id = find_user_id("\"AccountName\"", username); !id.empty())
{
return id;
}
#endif
sys_log.error("get_last_active_steam_user: Failed to parse steam loginusers file '%s' (user_count=%d)", vdf_path, user_count);
return "";
}
bool steam_shortcut::is_steam_running()
{
#ifdef _WIN32
if (HANDLE mutex = OpenMutexA(SYNCHRONIZE, FALSE, "Global\\SteamClientRunning"))
{
CloseHandle(mutex);
return true;
}
// Fallback to check process
PROCESSENTRY32 entry{};
entry.dwSize = sizeof(entry);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (Process32First(snapshot, &entry))
{
do
{
if (lstrcmpiW(entry.szExeFile, L"steam.exe") == 0)
{
CloseHandle(snapshot);
return true;
}
} while (Process32Next(snapshot, &entry));
}
CloseHandle(snapshot);
#else
std::vector<std::string> pid_paths = { get_steam_path() };
#ifdef __linux__
if (const char* home = ::getenv("HOME"))
{
pid_paths.push_back(std::string(home) + "/.steam");
pid_paths.push_back(std::string(home) + "/.steam/steam");
}
#endif
for (const std::string& pid_path : pid_paths)
{
if (fs::file pid_file(pid_path + "/steam.pid"); pid_file)
{
const std::string pid = pid_file.to_string();
pid_file.close();
if (pid.empty())
{
continue;
}
const pid_t pid_val = std::stoi(pid);
return kill(pid_val, 0) == 0 || errno != ESRCH;
}
}
#endif
return false;
}
}

104
rpcs3/rpcs3qt/steam_utils.h Normal file
View file

@ -0,0 +1,104 @@
#pragma once
#include "util/types.hpp"
#include "Utilities/StrFmt.h"
#include <variant>
#include <expected>
namespace gui::utils
{
class steam_shortcut
{
public:
steam_shortcut() {}
~steam_shortcut() {}
void add_shortcut(
const std::string& app_name,
const std::string& exe,
const std::string& start_dir,
const std::string& launch_options,
const std::string& icon_path);
void remove_shortcut(
const std::string& app_name,
const std::string& exe,
const std::string& start_dir);
bool write_file();
static bool steam_installed();
static bool is_steam_running();
private:
enum type_id
{
Null = 0x00,
Start = Null,
String = 0x01,
Integer = 0x02,
Float = 0x03,
Pointer = 0x04,
Nested = 0x05,
Array = 0x06,
Bool = 0x07,
End = 0x08,
};
struct shortcut_entry
{
std::string app_name;
std::string exe;
std::string start_dir;
std::string launch_options;
std::string icon;
u32 appid = 0;
std::string build_binary_blob(usz index) const;
};
struct vdf_shortcut_entry
{
std::vector<std::pair<std::string, std::variant<u32, std::string>>> values;
std::vector<std::pair<std::string, std::string>> tags;
template <typename T>
std::expected<T, std::string> value(const std::string& key) const
{
const auto it = std::find_if(values.cbegin(), values.cend(), [&key](const auto& v){ return v.first == key; });
if (it == values.cend())
{
return std::unexpected(fmt::format("key '%s' not found", key));
}
if (const auto* p = std::get_if<T>(&it->second))
{
return *p;
}
return std::unexpected(fmt::format("value for key '%s' has wrong type", key));
}
std::expected<std::string, std::string> build_binary_blob(usz index) const;
};
bool parse_file(const std::string& path);
static u32 crc32(const std::string& data);
static u32 steam_appid(const std::string& exe, const std::string& name);
static void append(std::string& s, const std::string& val);
static std::string quote(const std::string& s, bool force);
static std::string fix_slashes(const std::string& s);
static std::string kv_string(const std::string& key, const std::string& value);
static std::string kv_int(const std::string& key, u32 value);
static std::string steamid64_to_32(const std::string& steam_id);
static std::string get_steam_path();
static std::string get_last_active_steam_user(const std::string& steam_path);
std::vector<shortcut_entry> m_entries_to_add;
std::vector<shortcut_entry> m_entries_to_remove;
std::vector<vdf_shortcut_entry> m_vdf_entries;
};
}