From bbfb7998021ab52fd3195847c95b3bf7e20f9860 Mon Sep 17 00:00:00 2001 From: David Antunes Date: Fri, 6 Jun 2025 04:26:15 +0100 Subject: [PATCH] Automatically Update shadPS4 on OS Startup (#2700) Created a separate .exe for updating. Added a checkbox in the settings to enable the option to update on Windows startup. When the option is enabled, a PowerShell script adds the new updater exe to the Windows Task Scheduler and sets it to run at startup. If there are updates available at startup, the update windows pops up. Disabling the option runs another script to remove the updater from the Task Scheduler. Co-authored-by: Joao Ribeiro --- CMakeLists.txt | 129 +++++++++++++++++++++++++++++- src/common/config.cpp | 12 +++ src/common/config.h | 2 + src/qt_gui/check_update.cpp | 4 +- src/qt_gui/main_window_themes.cpp | 104 ++++++++++++++---------- src/qt_gui/settings_dialog.cpp | 116 +++++++++++++++++++++++++++ src/qt_gui/settings_dialog.h | 2 + src/qt_gui/settings_dialog.ui | 7 ++ src/updater/main.cpp | 46 +++++++++++ 9 files changed, 375 insertions(+), 47 deletions(-) create mode 100644 src/updater/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dfe9348a..0e6bdf660 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ endif() option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) option(ENABLE_DISCORD_RPC "Enable the Discord RPC integration" ON) option(ENABLE_UPDATER "Enables the options to updater" ON) +option(ENABLE_UPDATER_EXE "Enables the new executable for updates" ON) # First, determine whether to use CMAKE_OSX_ARCHITECTURES or CMAKE_SYSTEM_PROCESSOR. if (APPLE AND CMAKE_OSX_ARCHITECTURES) @@ -1074,6 +1075,26 @@ if (ENABLE_QT_GUI) ${EMULATOR} src/images/shadPS4.icns ) + if (ENABLE_UPDATER_EXE) + qt_add_executable(shadps4_updater + ${AUDIO_CORE} + ${IMGUI} + ${INPUT} + ${COMMON} + ${CORE} + ${SHADER_RECOMPILER} + ${VIDEO_CORE} + ${EMULATOR} + ${UPDATER} + ${RESOURCE_FILES} + src/qt_gui/main_window_themes.cpp + src/qt_gui/main_window_themes.h + src/qt_gui/background_music_player.cpp + src/qt_gui/background_music_player.h + src/updater/main.cpp + src/images/shadPS4.icns + ) + endif() else() add_executable(shadps4 ${AUDIO_CORE} @@ -1097,20 +1118,55 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers libusb::usb) + +if (ENABLE_UPDATER_EXE) + create_target_directory_groups(shadps4_updater) + + target_link_libraries(shadps4_updater PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) + target_link_libraries(shadps4_updater PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers libusb::usb) +endif() + +#Hide console for shadps4_updater +if (ENABLE_UPDATER_EXE) + if (WIN32) + set_target_properties(shadps4_updater PROPERTIES WIN32_EXECUTABLE TRUE) + endif() +endif() + +if(WIN32) + target_link_libraries(shadps4 PRIVATE shell32) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE shell32) + endif() +endif() + target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") +if (ENABLE_UPDATER_EXE) + target_compile_definitions(shadps4_updater PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") +endif() + if (ENABLE_DISCORD_RPC) target_compile_definitions(shadps4 PRIVATE ENABLE_DISCORD_RPC) + if (ENABLE_UPDATER_EXE) + target_compile_definitions(shadps4_updater PRIVATE ENABLE_DISCORD_RPC) + endif() endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") # Optional due to https://github.com/shadps4-emu/shadPS4/issues/1704 if (ENABLE_USERFAULTFD) target_compile_definitions(shadps4 PRIVATE ENABLE_USERFAULTFD) + if (ENABLE_UPDATER_EXE) + target_compile_definitions(shadps4_updater PRIVATE ENABLE_USERFAULTFD) + endif() endif() target_link_libraries(shadps4 PRIVATE uuid) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE uuid) + endif() endif() if (APPLE) @@ -1118,9 +1174,15 @@ if (APPLE) if (ENABLE_QT_GUI) set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d") set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}") + if (ENABLE_UPDATER_EXE) + set_property(TARGET shadps4_updater APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}") + endif() set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH}) else() set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path") + if (ENABLE_UPDATER_EXE) + set_property(TARGET shadps4_updater APPEND PROPERTY BUILD_RPATH "@executable_path") + endif() set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}) endif() @@ -1143,14 +1205,23 @@ if (APPLE) add_custom_target(CopyMoltenVK DEPENDS ${MVK_ICD_DST} ${MVK_DYLIB_DST}) add_dependencies(CopyMoltenVK MoltenVK) add_dependencies(shadps4 CopyMoltenVK) + if (ENABLE_UPDATER_EXE) + add_dependencies(shadps4_updater CopyMoltenVK) + endif() if (ARCHITECTURE STREQUAL "x86_64") # Reserve system-managed memory space. target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) + if (ENABLE_UPDATER_EXE) + target_link_options(shadps4_updater PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) + endif() endif() # Replacement for std::chrono::time_zone target_link_libraries(shadps4 PRIVATE date::date-tz) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE date::date-tz) + endif() endif() if (NOT ENABLE_QT_GUI) @@ -1159,6 +1230,9 @@ endif() if (ENABLE_QT_GUI) target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) + endif() add_definitions(-DENABLE_QT_GUI) if (ENABLE_UPDATER) add_definitions(-DENABLE_UPDATER) @@ -1167,6 +1241,9 @@ endif() if (WIN32) target_link_libraries(shadps4 PRIVATE mincore) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE mincore) + endif() if (MSVC) # MSVC likes putting opinions on what people can use, disable: @@ -1185,37 +1262,64 @@ if (WIN32) if (MSVC) target_link_libraries(shadps4 PRIVATE clang_rt.builtins-x86_64.lib) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE clang_rt.builtins-x86_64.lib) + endif() endif() # Disable ASLR so we can reserve the user area if (MSVC) target_link_options(shadps4 PRIVATE /DYNAMICBASE:NO) + if (ENABLE_UPDATER_EXE) + target_link_options(shadps4_updater PRIVATE /DYNAMICBASE:NO) + endif() else() target_link_options(shadps4 PRIVATE -Wl,--disable-dynamicbase) + if (ENABLE_UPDATER_EXE) + target_link_options(shadps4_updater PRIVATE -Wl,--disable-dynamicbase) + endif() endif() # Increase stack commit area (Needed, otherwise there are crashes) if (MSVC) target_link_options(shadps4 PRIVATE /STACK:0x200000,0x200000) + if (ENABLE_UPDATER_EXE) + target_link_options(shadps4_updater PRIVATE /STACK:0x200000,0x200000) + endif() else() target_link_options(shadps4 PRIVATE -Wl,--stack,2097152) + if (ENABLE_UPDATER_EXE) + target_link_options(shadps4_updater PRIVATE -Wl,--stack,2097152) + endif() endif() endif() if (WIN32) target_sources(shadps4 PRIVATE src/shadps4.rc) + if (ENABLE_UPDATER_EXE) + target_sources(shadps4_updater PRIVATE src/shadps4.rc) + endif() endif() add_definitions(-DBOOST_ASIO_STANDALONE) target_include_directories(shadps4 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +if (ENABLE_UPDATER_EXE) + target_include_directories(shadps4_updater PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +endif() # Shaders sources set(HOST_SHADERS_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/src/video_core/host_shaders) add_subdirectory(${HOST_SHADERS_INCLUDE}) add_dependencies(shadps4 host_shaders) +if (ENABLE_UPDATER_EXE) + add_dependencies(shadps4_updater host_shaders) +endif() target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) +if (ENABLE_UPDATER_EXE) + target_include_directories(shadps4_updater PRIVATE ${HOST_SHADERS_INCLUDE}) +endif() # embed resources @@ -1228,11 +1332,20 @@ cmrc_add_resource_library(embedded-resources src/images/platinum.png src/images/silver.png) target_link_libraries(shadps4 PRIVATE res::embedded) +if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE res::embedded) +endif() # ImGui resources add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer) add_dependencies(shadps4 ImGui_Resources) +if (ENABLE_UPDATER_EXE) + add_dependencies(shadps4_updater ImGui_Resources) +endif() target_include_directories(shadps4 PRIVATE ${IMGUI_RESOURCES_INCLUDE}) +if (ENABLE_UPDATER_EXE) + target_include_directories(shadps4_updater PRIVATE ${IMGUI_RESOURCES_INCLUDE}) +endif() if (ENABLE_QT_GUI) set_target_properties(shadps4 PROPERTIES @@ -1242,7 +1355,15 @@ if (ENABLE_QT_GUI) MACOSX_BUNDLE_ICON_FILE "shadPS4.icns" MACOSX_BUNDLE_SHORT_VERSION_STRING "${APP_VERSION}" ) - + if (ENABLE_UPDATER_EXE) + set_target_properties(shadps4 PROPERTIES + # WIN32_EXECUTABLE ON + MACOSX_BUNDLE ON + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/MacOSBundleInfo.plist.in" + MACOSX_BUNDLE_ICON_FILE "shadPS4.icns" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${APP_VERSION}" + ) + endif() set_source_files_properties(src/images/shadPS4.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif() @@ -1251,12 +1372,18 @@ if (UNIX AND NOT APPLE) if (ENABLE_QT_GUI) find_package(OpenSSL REQUIRED) target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES}) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE ${OPENSSL_LIBRARIES}) + endif() endif() endif() # Discord RPC if (ENABLE_DISCORD_RPC) target_link_libraries(shadps4 PRIVATE discord-rpc) + if (ENABLE_UPDATER_EXE) + target_link_libraries(shadps4_updater PRIVATE discord-rpc) + endif() endif() # Install rules diff --git a/src/common/config.cpp b/src/common/config.cpp index 6565ab82a..ce1272723 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -53,6 +53,7 @@ static bool isDebugDump = false; static bool isShaderDebug = false; static bool isShowSplash = false; static bool isAutoUpdate = false; +static bool isStartupUpdate = false; static bool isAlwaysShowChangelog = false; static std::string isSideTrophy = "right"; static bool isNullGpu = false; @@ -280,6 +281,10 @@ bool autoUpdate() { return isAutoUpdate; } +bool startupUpdate() { + return isStartupUpdate; +} + bool alwaysShowChangelog() { return isAlwaysShowChangelog; } @@ -388,6 +393,10 @@ void setAutoUpdate(bool enable) { isAutoUpdate = enable; } +void setStartupUpdate(bool enable) { + isStartupUpdate = enable; +} + void setAlwaysShowChangelog(bool enable) { isAlwaysShowChangelog = enable; } @@ -780,6 +789,7 @@ void load(const std::filesystem::path& path) { } isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); + isStartupUpdate = toml::find_or(general, "startupUpdate", false); isAlwaysShowChangelog = toml::find_or(general, "alwaysShowChangelog", false); isSideTrophy = toml::find_or(general, "sideTrophy", "right"); compatibilityData = toml::find_or(general, "compatibilityEnabled", false); @@ -976,6 +986,7 @@ void save(const std::filesystem::path& path) { data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; + data["General"]["startupUpdate"] = isStartupUpdate; data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog; data["General"]["sideTrophy"] = isSideTrophy; data["General"]["compatibilityEnabled"] = compatibilityData; @@ -1136,6 +1147,7 @@ void setDefaultValues() { isShaderDebug = false; isShowSplash = false; isAutoUpdate = false; + isStartupUpdate = false; isAlwaysShowChangelog = false; isSideTrophy = "right"; isNullGpu = false; diff --git a/src/common/config.h b/src/common/config.h index 404854ae2..e009d477e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -70,6 +70,7 @@ bool debugDump(); bool collectShadersForDebug(); bool showSplash(); bool autoUpdate(); +bool startupUpdate(); bool alwaysShowChangelog(); std::string sideTrophy(); bool nullGpu(); @@ -84,6 +85,7 @@ void setDebugDump(bool enable); void setCollectShaderForDebug(bool enable); void setShowSplash(bool enable); void setAutoUpdate(bool enable); +void setStartupUpdate(bool enable); void setAlwaysShowChangelog(bool enable); void setSideTrophy(std::string side); void setNullGpu(bool enable); diff --git a/src/qt_gui/check_update.cpp b/src/qt_gui/check_update.cpp index b0858840a..75abed61f 100644 --- a/src/qt_gui/check_update.cpp +++ b/src/qt_gui/check_update.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -444,8 +445,7 @@ void CheckUpdate::Install() { QString userPath; Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir)); - QString rootPath; - Common::FS::PathToQString(rootPath, std::filesystem::current_path()); + QString rootPath = QCoreApplication::applicationDirPath(); QString tempDirPath = userPath + "/temp_download_update"; QString startingUpdate = tr("Starting Update..."); diff --git a/src/qt_gui/main_window_themes.cpp b/src/qt_gui/main_window_themes.cpp index 624673cba..98a78ce9f 100644 --- a/src/qt_gui/main_window_themes.cpp +++ b/src/qt_gui/main_window_themes.cpp @@ -9,12 +9,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setStyleSheet(""); switch (theme) { case Theme::Dark: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #1e1e1e; color: #ffffff; border: 1px solid #ffffff; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #2A82DA; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #1e1e1e; color: #ffffff; border: 1px solid #ffffff; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #2A82DA; }"); + } themePalette.setColor(QPalette::Window, QColor(50, 50, 50)); themePalette.setColor(QPalette::WindowText, Qt::white); themePalette.setColor(QPalette::Base, QColor(20, 20, 20)); @@ -31,12 +33,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::Light: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #ffffff; color: #000000; border: 1px solid #000000; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #2A82DA; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #ffffff; color: #000000; border: 1px solid #000000; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #2A82DA; }"); + } themePalette.setColor(QPalette::Window, QColor(240, 240, 240)); // Light gray themePalette.setColor(QPalette::WindowText, Qt::black); // Black themePalette.setColor(QPalette::Base, QColor(230, 230, 230, 80)); // Grayish @@ -52,12 +56,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::Green: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #192819; color: #ffffff; border: 1px solid #ffffff; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #2A82DA; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #192819; color: #ffffff; border: 1px solid #ffffff; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #2A82DA; }"); + } themePalette.setColor(QPalette::Window, QColor(53, 69, 53)); // Dark green background themePalette.setColor(QPalette::WindowText, Qt::white); // White text themePalette.setColor(QPalette::Base, QColor(25, 40, 25)); // Darker green base @@ -76,12 +82,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::Blue: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #14283c; color: #ffffff; border: 1px solid #ffffff; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #2A82DA; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #14283c; color: #ffffff; border: 1px solid #ffffff; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #2A82DA; }"); + } themePalette.setColor(QPalette::Window, QColor(40, 60, 90)); // Dark blue background themePalette.setColor(QPalette::WindowText, Qt::white); // White text themePalette.setColor(QPalette::Base, QColor(20, 40, 60)); // Darker blue base @@ -101,12 +109,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::Violet: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #501e5a; color: #ffffff; border: 1px solid #ffffff; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #2A82DA; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #501e5a; color: #ffffff; border: 1px solid #ffffff; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #2A82DA; }"); + } themePalette.setColor(QPalette::Window, QColor(100, 50, 120)); // Violet background themePalette.setColor(QPalette::WindowText, Qt::white); // White text themePalette.setColor(QPalette::Base, QColor(80, 30, 90)); // Darker violet base @@ -126,12 +136,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::Gruvbox: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #1d2021; color: #f9f5d7; border: 1px solid #f9f5d7; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #83A598; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #1d2021; color: #f9f5d7; border: 1px solid #f9f5d7; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #83A598; }"); + } themePalette.setColor(QPalette::Window, QColor(29, 32, 33)); themePalette.setColor(QPalette::WindowText, QColor(249, 245, 215)); themePalette.setColor(QPalette::Base, QColor(29, 32, 33)); @@ -148,12 +160,14 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::TokyoNight: - mw_searchbar->setStyleSheet( - "QLineEdit {" - "background-color: #1a1b26; color: #9d7cd8; border: 1px solid #9d7cd8; " - "border-radius: 4px; padding: 5px; }" - "QLineEdit:focus {" - "border: 1px solid #7aa2f7; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet( + "QLineEdit {" + "background-color: #1a1b26; color: #9d7cd8; border: 1px solid #9d7cd8; " + "border-radius: 4px; padding: 5px; }" + "QLineEdit:focus {" + "border: 1px solid #7aa2f7; }"); + } themePalette.setColor(QPalette::Window, QColor(31, 35, 53)); themePalette.setColor(QPalette::WindowText, QColor(192, 202, 245)); themePalette.setColor(QPalette::Base, QColor(25, 28, 39)); @@ -170,8 +184,10 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) { qApp->setPalette(themePalette); break; case Theme::Oled: - mw_searchbar->setStyleSheet("QLineEdit:focus {" - "border: 1px solid #2A82DA; }"); + if (mw_searchbar) { + mw_searchbar->setStyleSheet("QLineEdit:focus {" + "border: 1px solid #2A82DA; }"); + } themePalette.setColor(QPalette::Window, Qt::black); themePalette.setColor(QPalette::WindowText, Qt::white); themePalette.setColor(QPalette::Base, Qt::black); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 914cc5470..1f3913f5e 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -3,11 +3,21 @@ #include #include +#include #include +#include #include #include #include +#include +#include +#include +#include +#include #include +#include +#include + #include "common/config.h" #include "common/scm_rev.h" @@ -178,12 +188,29 @@ SettingsDialog::SettingsDialog(std::shared_ptr m_compat_ connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [](int state) { Config::setAutoUpdate(state == Qt::Checked); }); + connect(ui->startupUpdateCheckBox, &QCheckBox::stateChanged, this, [this](int state) { + Config::setStartupUpdate(state == Qt::Checked); + if (state == Qt::Checked) + AddUpdaterToStartup(); + else + RemoveUpdaterFromStartup(); + }); + connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, [](int state) { Config::setAlwaysShowChangelog(state == Qt::Checked); }); #else connect(ui->updateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) { Config::setAutoUpdate(state == Qt::Checked); }); + connect(ui->startupUpdateCheckBox, &QCheckBox::checkStateChanged, this, + [this](Qt::CheckState state) { + Config::setStartupUpdate(state == Qt::Checked); + if (state == Qt::Checked) + AddUpdaterToStartup(); + else + RemoveUpdaterFromStartup(); + }); + connect(ui->changelogCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) { Config::setAlwaysShowChangelog(state == Qt::Checked); }); #endif @@ -502,6 +529,8 @@ void SettingsDialog::LoadValuesFromConfig() { #ifdef ENABLE_UPDATER ui->updateCheckBox->setChecked(toml::find_or(data, "General", "autoUpdate", false)); + ui->startupUpdateCheckBox->setChecked( + toml::find_or(data, "General", "startupUpdate", false)); ui->changelogCheckBox->setChecked( toml::find_or(data, "General", "alwaysShowChangelog", false)); @@ -785,6 +814,7 @@ void SettingsDialog::UpdateSettings() { Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked()); Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked()); Config::setAutoUpdate(ui->updateCheckBox->isChecked()); + Config::setStartupUpdate(ui->startupUpdateCheckBox->isChecked()); Config::setAlwaysShowChangelog(ui->changelogCheckBox->isChecked()); Config::setUpdateChannel(channelMap.value(ui->updateComboBox->currentText()).toStdString()); Config::setChooseHomeTab( @@ -862,3 +892,89 @@ void SettingsDialog::ResetInstallFolders() { Config::setAllGameInstallDirs(settings_install_dirs_config); } } + +void SettingsDialog::AddUpdaterToStartup() { + +#ifdef _WIN32 + + QString tempDirPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + "/Temp/temp_download_update"; + QDir().mkpath(tempDirPath); + + QString scriptFileName = + tempDirPath + "/create_task_" + QUuid::createUuid().toString(QUuid::WithoutBraces) + ".ps1"; + + QString taskName = "ShadPS4Updater"; + QString exePath = QCoreApplication::applicationFilePath() + .replace("shadps4.exe", "shadps4_updater.exe") + .replace("/", "\\"); + + QString scriptContent = + QStringLiteral("$Action = New-ScheduledTaskAction -Execute '%1'\n" + "$Trigger = New-ScheduledTaskTrigger -AtLogOn\n" + "$Principal = New-ScheduledTaskPrincipal -UserId \"$env:USERNAME\" " + "-LogonType Interactive -RunLevel Highest\n" + "$Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries " + "-DontStopIfGoingOnBatteries\n" + "Register-ScheduledTask -TaskName '%2' -Action $Action -Trigger $Trigger " + "-Principal $Principal -Settings $Settings -Force\n") + .arg(exePath, taskName); + + QFile scriptFile(scriptFileName); + if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&scriptFile); + out << scriptContent; + scriptFile.close(); + + // Elevate PowerShell to run the script + ShellExecuteW( + nullptr, + L"runas", // Triggers UAC prompt + L"powershell.exe", + (L"-ExecutionPolicy Bypass -File \"" + scriptFileName.toStdWString() + L"\"").c_str(), + nullptr, SW_HIDE); + } + +#endif +} + +void SettingsDialog::RemoveUpdaterFromStartup() { + +#ifdef _WIN32 + + QString tempDirPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + "/Temp/temp_download_update"; + QDir().mkpath(tempDirPath); + + QString scriptFileName = + tempDirPath + "/remove_task_" + QUuid::createUuid().toString(QUuid::WithoutBraces) + ".ps1"; + + QString taskName = "ShadPS4Updater"; + + QString scriptContent = + QStringLiteral( + "$taskName = '%1'\n" + "if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {\n" + " Unregister-ScheduledTask -TaskName $taskName -Confirm:$false\n" + "}\n" + "Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force\n" // Optional: + // self-delete the + // script + ) + .arg(taskName); + + QFile scriptFile(scriptFileName); + if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&scriptFile); + out << scriptContent; + scriptFile.close(); + + // Run PowerShell script with admin privileges + ShellExecuteW( + nullptr, L"runas", L"powershell.exe", + (L"-ExecutionPolicy Bypass -File \"" + scriptFileName.toStdWString() + L"\"").c_str(), + nullptr, SW_HIDE); + } + +#endif +} diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index cdf9be80e..ff5f99c1e 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -42,6 +42,8 @@ private: void OnLanguageChanged(int index); void OnCursorStateChanged(s16 index); void closeEvent(QCloseEvent* event) override; + void AddUpdaterToStartup(); + void RemoveUpdaterFromStartup(); std::unique_ptr ui; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 20e26775d..5dd73d544 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -415,6 +415,13 @@ + + + + Check for Updates at OS Startup + + + diff --git a/src/updater/main.cpp b/src/updater/main.cpp new file mode 100644 index 000000000..dcf197fc8 --- /dev/null +++ b/src/updater/main.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/config.h" +#include "common/path_util.h" +#include "core/file_sys/fs.h" +#include "qt_gui/check_update.h" +#include "qt_gui/main_window_themes.h" + +#ifdef _WIN32 +#include +#endif + +// Custom message handler to ignore Qt logs +void customMessageHandler(QtMsgType, const QMessageLogContext&, const QString&) {} + +int main(int argc, char* argv[]) { +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + + QApplication u(argc, argv); + + QApplication::setDesktopFileName("net.shadps4.shadPS4_updater"); + QApplication::setStyle("Fusion"); + + // Load configurations + const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); + Config::load(user_dir / "config.toml"); + + WindowThemes m_window_themes; + + Theme lastTheme = static_cast(Config::getMainWindowTheme()); + m_window_themes.SetWindowTheme(lastTheme, nullptr); + + if (Config::startupUpdate()) { + auto checkUpdate = new CheckUpdate(false); + checkUpdate->exec(); + } + + // Ignore Qt logs + qInstallMessageHandler(customMessageHandler); + + return 0; +}