From f3eeddf8bdd4c3f5db6b50ccf445a45271746556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 2 Jan 2023 15:00:13 +0100 Subject: [PATCH 1/4] Fix some registry keys not being closed if values are unchanged --- src/base/system.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/base/system.cpp b/src/base/system.cpp index 6e2311591..fbb78c633 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -4414,6 +4414,10 @@ bool shell_register_protocol(const char *protocol_name, const char *executable, *updated = true; } + else + { + RegCloseKey(handle_subkey_shell_open_command); + } return true; } @@ -4546,6 +4550,10 @@ bool shell_register_extension(const char *extension, const char *description, co *updated = true; } + else + { + RegCloseKey(handle_subkey_extension); + } return true; } From a0df1ebfc0ae1e83be834c8186ca382f249677be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 2 Jan 2023 15:06:25 +0100 Subject: [PATCH 2/4] Fix `updated` not being set correctly when deleting shell class --- src/base/system.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index fbb78c633..7fb72ee5b 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -4574,11 +4574,13 @@ bool shell_unregister(const char *shell_class, bool *updated) // Delete the registry keys for the shell class (protocol or program ID) LRESULT result_delete = RegDeleteTreeW(handle_subkey_classes, class_wide.c_str()); RegCloseKey(handle_subkey_classes); - if(result_delete != ERROR_SUCCESS && result_delete != ERROR_FILE_NOT_FOUND) + if(result_delete == ERROR_SUCCESS) + { + *updated = true; + } + else if(result_delete != ERROR_FILE_NOT_FOUND) { windows_print_error("shell_unregister", "Error deleting registry key", result_delete); - if(result_delete == ERROR_SUCCESS) - *updated = true; return false; } From 6e28ca6fe442227035149f29774034f015f29c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 2 Jan 2023 15:22:27 +0100 Subject: [PATCH 3/4] Extract method `IStorage::GetBinaryPathAbsolute` --- src/engine/client/client.cpp | 16 +--------------- src/engine/shared/storage.cpp | 19 +++++++++++++++++++ src/engine/storage.h | 1 + 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index eb037fb7d..45d0adca0 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -4906,22 +4906,8 @@ int CClient::UdpConnectivity(int NetType) #if defined(CONF_FAMILY_WINDOWS) void CClient::ShellRegister() { - char aBinaryPath[IO_MAX_PATH_LENGTH]; - Storage()->GetBinaryPath(PLAT_CLIENT_EXEC, aBinaryPath, sizeof(aBinaryPath)); char aFullPath[IO_MAX_PATH_LENGTH]; - if(fs_is_relative_path(aBinaryPath)) - { - if(fs_getcwd(aFullPath, sizeof(aFullPath))) - { - str_append(aFullPath, "/", sizeof(aFullPath)); - str_append(aFullPath, aBinaryPath, sizeof(aFullPath)); - } - else - aFullPath[0] = '\0'; - } - else - str_copy(aFullPath, aBinaryPath); - + Storage()->GetBinaryPathAbsolute(PLAT_CLIENT_EXEC, aFullPath, sizeof(aFullPath)); if(!aFullPath[0]) { dbg_msg("client", "Failed to register protocol and file extensions: could not determine absolute path"); diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 2a5e8a677..b64c4dded 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -677,6 +677,25 @@ public: return pBuffer; } + const char *GetBinaryPathAbsolute(const char *pFilename, char *pBuffer, unsigned BufferSize) override + { + char aBinaryPath[IO_MAX_PATH_LENGTH]; + GetBinaryPath(PLAT_CLIENT_EXEC, aBinaryPath, sizeof(aBinaryPath)); + if(fs_is_relative_path(aBinaryPath)) + { + if(fs_getcwd(pBuffer, BufferSize)) + { + str_append(pBuffer, "/", BufferSize); + str_append(pBuffer, aBinaryPath, BufferSize); + } + else + pBuffer[0] = '\0'; + } + else + str_copy(pBuffer, aBinaryPath, BufferSize); + return pBuffer; + } + static IStorage *Create(int StorageType, int NumArgs, const char **ppArguments) { CStorage *pStorage = new CStorage(); diff --git a/src/engine/storage.h b/src/engine/storage.h index e52e7b595..e3f69841f 100644 --- a/src/engine/storage.h +++ b/src/engine/storage.h @@ -57,6 +57,7 @@ public: virtual bool RemoveBinaryFile(const char *pFilename) = 0; virtual bool RenameBinaryFile(const char *pOldFilename, const char *pNewFilename) = 0; virtual const char *GetBinaryPath(const char *pFilename, char *pBuffer, unsigned BufferSize) = 0; + virtual const char *GetBinaryPathAbsolute(const char *pFilename, char *pBuffer, unsigned BufferSize) = 0; static void StripPathAndExtension(const char *pFilename, char *pBuffer, int BufferSize); static const char *FormatTmpPath(char *aBuf, unsigned BufSize, const char *pPath); From 179e3b1c4cbfb764fdbd1ff7bf2615cb013a7767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Mon, 2 Jan 2023 15:28:53 +0100 Subject: [PATCH 4/4] Register application separately to specify its displayed name The application itself must also be registered in a separate registry key, so its displayed name can be set. See: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration --- src/base/system.cpp | 90 ++++++++++++++++++++++++++++++++++-- src/base/system.h | 31 ++++++++++++- src/engine/client/client.cpp | 18 ++++++-- 3 files changed, 132 insertions(+), 7 deletions(-) diff --git a/src/base/system.cpp b/src/base/system.cpp index 7fb72ee5b..1e936d3e2 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -4322,6 +4322,12 @@ static std::wstring utf8_to_wstring(const char *str) return wide_string; } +static std::wstring filename_from_path(const std::wstring &path) +{ + const size_t pos = path.find_last_of(L"/\\"); + return pos == std::wstring::npos ? path : path.substr(pos + 1); +} + bool shell_register_protocol(const char *protocol_name, const char *executable, bool *updated) { const std::wstring protocol_name_wide = utf8_to_wstring(protocol_name); @@ -4558,7 +4564,56 @@ bool shell_register_extension(const char *extension, const char *description, co return true; } -bool shell_unregister(const char *shell_class, bool *updated) +bool shell_register_application(const char *name, const char *executable, bool *updated) +{ + const std::wstring name_wide = utf8_to_wstring(name); + const std::wstring executable_filename = filename_from_path(utf8_to_wstring(executable)); + + // Open registry key for application registrations + HKEY handle_subkey_applications; + const LRESULT result_subkey_applications = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\Applications", 0, KEY_ALL_ACCESS, &handle_subkey_applications); + if(result_subkey_applications != ERROR_SUCCESS) + { + windows_print_error("shell_register_application", "Error opening registry key", result_subkey_applications); + return false; + } + + // Create the program key + HKEY handle_subkey_program; + const LRESULT result_subkey_program = RegCreateKeyExW(handle_subkey_applications, executable_filename.c_str(), 0, NULL, 0, KEY_ALL_ACCESS, NULL, &handle_subkey_program, NULL); + RegCloseKey(handle_subkey_applications); + if(result_subkey_program != ERROR_SUCCESS) + { + windows_print_error("shell_register_application", "Error creating registry key", result_subkey_program); + return false; + } + + // Get the previous default value for the key, so we can determine if it changed + wchar_t old_value_executable[MAX_PATH]; + DWORD old_size_executable = sizeof(old_value_executable); + const LRESULT result_old_value_executable = RegGetValueW(handle_subkey_program, NULL, L"FriendlyAppName", RRF_RT_REG_SZ, NULL, (BYTE *)old_value_executable, &old_size_executable); + if(result_old_value_executable != ERROR_SUCCESS || wcscmp(old_value_executable, name_wide.c_str()) != 0) + { + // Set the "FriendlyAppName" value, which specifies the displayed name of the application + const LRESULT result_program_name = RegSetValueExW(handle_subkey_program, L"FriendlyAppName", 0, REG_SZ, (BYTE *)name_wide.c_str(), (name_wide.length() + 1) * sizeof(wchar_t)); + RegCloseKey(handle_subkey_program); + if(result_program_name != ERROR_SUCCESS) + { + windows_print_error("shell_register_application", "Error setting registry value", result_program_name); + return false; + } + + *updated = true; + } + else + { + RegCloseKey(handle_subkey_program); + } + + return true; +} + +bool shell_unregister_class(const char *shell_class, bool *updated) { const std::wstring class_wide = utf8_to_wstring(shell_class); @@ -4567,7 +4622,7 @@ bool shell_unregister(const char *shell_class, bool *updated) const LRESULT result_subkey_classes = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, KEY_ALL_ACCESS, &handle_subkey_classes); if(result_subkey_classes != ERROR_SUCCESS) { - windows_print_error("shell_unregister", "Error opening registry key", result_subkey_classes); + windows_print_error("shell_unregister_class", "Error opening registry key", result_subkey_classes); return false; } @@ -4580,7 +4635,36 @@ bool shell_unregister(const char *shell_class, bool *updated) } else if(result_delete != ERROR_FILE_NOT_FOUND) { - windows_print_error("shell_unregister", "Error deleting registry key", result_delete); + windows_print_error("shell_unregister_class", "Error deleting registry key", result_delete); + return false; + } + + return true; +} + +bool shell_unregister_application(const char *executable, bool *updated) +{ + const std::wstring executable_filename = filename_from_path(utf8_to_wstring(executable)); + + // Open registry key for application registrations + HKEY handle_subkey_applications; + const LRESULT result_subkey_applications = RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Classes\\Applications", 0, KEY_ALL_ACCESS, &handle_subkey_applications); + if(result_subkey_applications != ERROR_SUCCESS) + { + windows_print_error("shell_unregister_application", "Error opening registry key", result_subkey_applications); + return false; + } + + // Delete the registry keys for the application description + LRESULT result_delete = RegDeleteTreeW(handle_subkey_applications, executable_filename.c_str()); + RegCloseKey(handle_subkey_applications); + if(result_delete == ERROR_SUCCESS) + { + *updated = true; + } + else if(result_delete != ERROR_FILE_NOT_FOUND) + { + windows_print_error("shell_unregister_application", "Error deleting registry key", result_delete); return false; } diff --git a/src/base/system.h b/src/base/system.h index 9d4b97ecf..26d58a078 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -2614,6 +2614,21 @@ bool shell_register_protocol(const char *protocol_name, const char *executable, */ bool shell_register_extension(const char *extension, const char *description, const char *executable_name, const char *executable, bool *updated); +/** + * Registers an application. + * + * @ingroup Shell + * + * @param name Readable name of the application. + * @param executable The absolute path of the executable being registered. + * @param updated Pointer to a variable that will be set to true, iff the shell needs to be updated. + * + * @return true on success, false on failure. + * + * @remark The caller must later call shell_update, iff the shell needs to be updated. + */ +bool shell_register_application(const char *name, const char *executable, bool *updated); + /** * Unregisters a protocol or file extension handler. * @@ -2628,7 +2643,21 @@ bool shell_register_extension(const char *extension, const char *description, co * * @remark The caller must later call shell_update, iff the shell needs to be updated. */ -bool shell_unregister(const char *shell_class, bool *updated); +bool shell_unregister_class(const char *shell_class, bool *updated); + +/** + * Unregisters an application. + * + * @ingroup Shell + * + * @param executable The absolute path of the executable being unregistered. + * @param updated Pointer to a variable that will be set to true, iff the shell needs to be updated. + * + * @return true on success, false on failure. + * + * @remark The caller must later call shell_update, iff the shell needs to be updated. + */ +bool shell_unregister_application(const char *executable, bool *updated); /** * Notifies the system that a protocol or file extension has been changed and the shell needs to be updated. diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 45d0adca0..2c64d4a1d 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -4921,19 +4921,31 @@ void CClient::ShellRegister() dbg_msg("client", "Failed to register .map file extension"); if(!shell_register_extension(".demo", "Demo File", GAME_NAME, aFullPath, &Updated)) dbg_msg("client", "Failed to register .demo file extension"); + if(!shell_register_application(GAME_NAME, aFullPath, &Updated)) + dbg_msg("client", "Failed to register application"); if(Updated) shell_update(); } void CClient::ShellUnregister() { + char aFullPath[IO_MAX_PATH_LENGTH]; + Storage()->GetBinaryPathAbsolute(PLAT_CLIENT_EXEC, aFullPath, sizeof(aFullPath)); + if(!aFullPath[0]) + { + dbg_msg("client", "Failed to unregister protocol and file extensions: could not determine absolute path"); + return; + } + bool Updated = false; - if(!shell_unregister("ddnet", &Updated)) + if(!shell_unregister_class("ddnet", &Updated)) dbg_msg("client", "Failed to unregister ddnet protocol"); - if(!shell_unregister(GAME_NAME ".map", &Updated)) + if(!shell_unregister_class(GAME_NAME ".map", &Updated)) dbg_msg("client", "Failed to unregister .map file extension"); - if(!shell_unregister(GAME_NAME ".demo", &Updated)) + if(!shell_unregister_class(GAME_NAME ".demo", &Updated)) dbg_msg("client", "Failed to unregister .demo file extension"); + if(!shell_unregister_application(aFullPath, &Updated)) + dbg_msg("client", "Failed to unregister application"); if(Updated) shell_update(); }