Skip to content

Commit ab52e27

Browse files
fix(audio-info): crash when device name contains special characters (#4095)
1 parent fd2bfaa commit ab52e27

File tree

14 files changed

+476
-163
lines changed

14 files changed

+476
-163
lines changed

cmake/compile_definitions/windows.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ set(PLATFORM_TARGET_FILES
5959
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp"
6060
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_wgc.cpp"
6161
"${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp"
62+
"${CMAKE_SOURCE_DIR}/src/platform/windows/utf_utils.cpp"
63+
"${CMAKE_SOURCE_DIR}/src/platform/windows/utf_utils.h"
6264
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp"
6365
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Client.h"
6466
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Common.h"

src/platform/windows/audio.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
#include <synchapi.h>
1717

1818
// local includes
19-
#include "misc.h"
2019
#include "src/config.h"
2120
#include "src/logging.h"
2221
#include "src/platform/common.h"
22+
#include "utf_utils.h"
2323

2424
// Must be the last included file
2525
// clang-format off
@@ -703,7 +703,7 @@ namespace platf::audio {
703703
audio::wstring_t id;
704704
device->GetId(&id);
705705

706-
sink.host = to_utf8(id.get());
706+
sink.host = utf_utils::to_utf8(id.get());
707707
}
708708

709709
// Prepare to search for the device_id of the virtual audio sink device,
@@ -713,14 +713,14 @@ namespace platf::audio {
713713
if (config::audio.virtual_sink.empty()) {
714714
match_list = match_steam_speakers();
715715
} else {
716-
match_list = match_all_fields(from_utf8(config::audio.virtual_sink));
716+
match_list = match_all_fields(utf_utils::from_utf8(config::audio.virtual_sink));
717717
}
718718

719719
// Search for the virtual audio sink device currently present in the system.
720720
auto matched = find_device_id(match_list);
721721
if (matched) {
722722
// Prepare to fill virtual audio sink names with device_id.
723-
auto device_id = to_utf8(matched->second);
723+
auto device_id = utf_utils::to_utf8(matched->second);
724724
// Also prepend format name (basically channel layout at the moment)
725725
// because we don't want to extend the platform interface.
726726
sink.null = std::make_optional(sink_t::null_t {
@@ -736,7 +736,7 @@ namespace platf::audio {
736736
}
737737

738738
bool is_sink_available(const std::string &sink) override {
739-
const auto match_list = match_all_fields(from_utf8(sink));
739+
const auto match_list = match_all_fields(utf_utils::from_utf8(sink));
740740
const auto matched = find_device_id(match_list);
741741
return static_cast<bool>(matched);
742742
}
@@ -758,7 +758,7 @@ namespace platf::audio {
758758
for (const auto &format : formats) {
759759
auto &name = format.name;
760760
if (current.find(name) == 0) {
761-
auto device_id = from_utf8(current.substr(name.size(), current.size() - name.size()));
761+
auto device_id = utf_utils::from_utf8(current.substr(name.size(), current.size() - name.size()));
762762
return std::make_pair(device_id, std::reference_wrapper(format));
763763
}
764764
}
@@ -805,7 +805,7 @@ namespace platf::audio {
805805
// Sink name does not begin with virtual-(format name), hence it's not a virtual sink
806806
// and we don't want to change playback format of the corresponding device.
807807
// Also need to perform matching, sink name is not necessarily device_id in this case.
808-
auto matched = find_device_id(match_all_fields(from_utf8(sink)));
808+
auto matched = find_device_id(match_all_fields(utf_utils::from_utf8(sink)));
809809
if (matched) {
810810
return matched->second;
811811
} else {

src/platform/windows/display_base.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#include <boost/process/v1.hpp>
1515
#include <MinHook.h>
1616

17+
// local includes
18+
#include "utf_utils.h"
19+
1720
// We have to include boost/process/v1.hpp before display.h due to WinSock.h,
1821
// but that prevents the definition of NTSTATUS so we must define it ourself.
1922
typedef long NTSTATUS;
@@ -474,8 +477,8 @@ namespace platf::dxgi {
474477
return -1;
475478
}
476479

477-
auto adapter_name = from_utf8(config::video.adapter_name);
478-
auto output_name = from_utf8(display_name);
480+
auto adapter_name = utf_utils::from_utf8(config::video.adapter_name);
481+
auto output_name = utf_utils::from_utf8(display_name);
479482

480483
adapter_t::pointer adapter_p;
481484
for (int tries = 0; tries < 2; ++tries) {
@@ -589,7 +592,7 @@ namespace platf::dxgi {
589592
DXGI_ADAPTER_DESC adapter_desc;
590593
adapter->GetDesc(&adapter_desc);
591594

592-
auto description = to_utf8(adapter_desc.Description);
595+
auto description = utf_utils::to_utf8(adapter_desc.Description);
593596
BOOST_LOG(info)
594597
<< std::endl
595598
<< "Device Description : " << description << std::endl
@@ -1068,7 +1071,7 @@ namespace platf {
10681071
BOOST_LOG(debug)
10691072
<< std::endl
10701073
<< "====== ADAPTER ====="sv << std::endl
1071-
<< "Device Name : "sv << to_utf8(adapter_desc.Description) << std::endl
1074+
<< "Device Name : "sv << utf_utils::to_utf8(adapter_desc.Description) << std::endl
10721075
<< "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl
10731076
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
10741077
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
@@ -1084,7 +1087,7 @@ namespace platf {
10841087
DXGI_OUTPUT_DESC desc;
10851088
output->GetDesc(&desc);
10861089

1087-
auto device_name = to_utf8(desc.DeviceName);
1090+
auto device_name = utf_utils::to_utf8(desc.DeviceName);
10881091

10891092
auto width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
10901093
auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;

src/platform/windows/display_vram.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extern "C" {
2828
#include "src/nvenc/nvenc_d3d11_on_cuda.h"
2929
#include "src/nvenc/nvenc_utils.h"
3030
#include "src/video.h"
31+
#include "utf_utils.h"
3132

3233
#if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install
3334
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx"
@@ -359,7 +360,7 @@ namespace platf::dxgi {
359360
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
360361
#endif
361362

362-
auto wFile = from_utf8(file);
363+
auto wFile = utf_utils::from_utf8(file);
363364
auto status = D3DCompileFromFile(wFile.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p);
364365

365366
if (msg_p) {

src/platform/windows/misc.cpp

Lines changed: 27 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "src/logging.h"
4646
#include "src/platform/common.h"
4747
#include "src/utility.h"
48+
#include "utf_utils.h"
4849

4950
// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK
5051
#ifndef UDP_SEND_MSG_SIZE
@@ -314,15 +315,15 @@ namespace platf {
314315
// Parse the environment block and populate env
315316
for (auto c = (PWCHAR) env_block; *c != UNICODE_NULL; c += wcslen(c) + 1) {
316317
// Environment variable entries end with a null-terminator, so std::wstring() will get an entire entry.
317-
std::string env_tuple = to_utf8(std::wstring {c});
318+
std::string env_tuple = utf_utils::to_utf8(std::wstring {c});
318319
std::string env_name = env_tuple.substr(0, env_tuple.find('='));
319320
std::string env_val = env_tuple.substr(env_tuple.find('=') + 1);
320321

321322
// Perform a case-insensitive search to see if this variable name already exists
322-
auto itr = std::find_if(env.cbegin(), env.cend(), [&](const auto &e) {
323-
return boost::iequals(e.get_name(), env_name);
324-
});
325-
if (itr != env.cend()) {
323+
if (auto itr = std::find_if(env.begin(), env.end(), [&](const auto &e) {
324+
return boost::iequals(e.get_name(), env_name);
325+
});
326+
itr != env.end()) {
326327
// Use this existing name if it is already present to ensure we merge properly
327328
env_name = itr->get_name();
328329
}
@@ -379,33 +380,36 @@ namespace platf {
379380
offset += wstr.length();
380381
}
381382

382-
std::wstring create_environment_block(bp::environment &env) {
383+
std::wstring create_environment_block(const bp::environment &env) {
383384
int size = 0;
384385
for (const auto &entry : env) {
385386
auto name = entry.get_name();
386387
auto value = entry.to_string();
387-
size += from_utf8(name).length() + 1 /* L'=' */ + from_utf8(value).length() + 1 /* L'\0' */;
388+
size += utf_utils::from_utf8(name).length() + 1 /* L'=' */ + utf_utils::from_utf8(value).length() + 1 /* L'\0' */;
388389
}
389390

390391
size += 1 /* L'\0' */;
391392

392-
wchar_t env_block[size];
393+
std::vector<wchar_t> env_block(size);
393394
int offset = 0;
394395
for (const auto &entry : env) {
395396
auto name = entry.get_name();
396397
auto value = entry.to_string();
397398

398399
// Construct the NAME=VAL\0 string
399-
append_string_to_environment_block(env_block, offset, from_utf8(name));
400-
env_block[offset++] = L'=';
401-
append_string_to_environment_block(env_block, offset, from_utf8(value));
402-
env_block[offset++] = L'\0';
400+
append_string_to_environment_block(env_block.data(), offset, utf_utils::from_utf8(name));
401+
env_block[offset] = L'=';
402+
offset++;
403+
append_string_to_environment_block(env_block.data(), offset, utf_utils::from_utf8(value));
404+
env_block[offset] = L'\0';
405+
offset++;
403406
}
404407

405408
// Append a final null terminator
406-
env_block[offset++] = L'\0';
409+
env_block[offset] = L'\0';
410+
offset++;
407411

408-
return std::wstring(env_block, offset);
412+
return std::wstring(env_block.data(), offset);
409413
}
410414

411415
LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) {
@@ -676,14 +680,14 @@ namespace platf {
676680
* @return A command string suitable for use by CreateProcess().
677681
*/
678682
std::wstring resolve_command_string(const std::string &raw_cmd, const std::wstring &working_dir, HANDLE token, DWORD &creation_flags) {
679-
std::wstring raw_cmd_w = from_utf8(raw_cmd);
683+
std::wstring raw_cmd_w = utf_utils::from_utf8(raw_cmd);
680684

681685
// First, convert the given command into parts so we can get the executable/file/URL without parameters
682686
auto raw_cmd_parts = boost::program_options::split_winmain(raw_cmd_w);
683687
if (raw_cmd_parts.empty()) {
684688
// This is highly unexpected, but we'll just return the raw string and hope for the best.
685689
BOOST_LOG(warning) << "Failed to split command string: "sv << raw_cmd;
686-
return from_utf8(raw_cmd);
690+
return utf_utils::from_utf8(raw_cmd);
687691
}
688692

689693
auto raw_target = raw_cmd_parts.at(0);
@@ -697,7 +701,7 @@ namespace platf {
697701
res = UrlGetPartW(raw_target.c_str(), scheme.data(), &out_len, URL_PART_SCHEME, 0);
698702
if (res != S_OK) {
699703
BOOST_LOG(warning) << "Failed to extract URL scheme from URL: "sv << raw_target << " ["sv << util::hex(res).to_string_view() << ']';
700-
return from_utf8(raw_cmd);
704+
return utf_utils::from_utf8(raw_cmd);
701705
}
702706

703707
// If the target is a URL, the class is found using the URL scheme (prior to and not including the ':')
@@ -708,13 +712,13 @@ namespace platf {
708712
if (extension == nullptr || *extension == 0) {
709713
// If the file has no extension, assume it's a command and allow CreateProcess()
710714
// to try to find it via PATH
711-
return from_utf8(raw_cmd);
715+
return utf_utils::from_utf8(raw_cmd);
712716
} else if (boost::iequals(extension, L".exe")) {
713717
// If the file has an .exe extension, we will bypass the resolution here and
714718
// directly pass the unmodified command string to CreateProcess(). The argument
715719
// escaping rules are subtly different between CreateProcess() and ShellExecute(),
716720
// and we want to preserve backwards compatibility with older configs.
717-
return from_utf8(raw_cmd);
721+
return utf_utils::from_utf8(raw_cmd);
718722
}
719723

720724
// For regular files, the class is found using the file extension (including the dot)
@@ -731,7 +735,7 @@ namespace platf {
731735

732736
// Override HKEY_CLASSES_ROOT and HKEY_CURRENT_USER to ensure we query the correct class info
733737
if (!override_per_user_predefined_keys(token)) {
734-
return from_utf8(raw_cmd);
738+
return utf_utils::from_utf8(raw_cmd);
735739
}
736740

737741
// Find the command string for the specified class
@@ -762,7 +766,7 @@ namespace platf {
762766

763767
if (res != S_OK) {
764768
BOOST_LOG(warning) << "Failed to query command string for raw command: "sv << raw_cmd << " ["sv << util::hex(res).to_string_view() << ']';
765-
return from_utf8(raw_cmd);
769+
return utf_utils::from_utf8(raw_cmd);
766770
}
767771

768772
// Finally, construct the real command string that will be passed into CreateProcess().
@@ -896,7 +900,7 @@ namespace platf {
896900
* @return A `bp::child` object representing the new process, or an empty `bp::child` object if the launch fails.
897901
*/
898902
bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
899-
std::wstring start_dir = from_utf8(working_dir.string());
903+
std::wstring start_dir = utf_utils::from_utf8(working_dir.string());
900904
HANDLE job = group ? group->native_handle() : nullptr;
901905
STARTUPINFOEXW startup_info = create_startup_info(file, job ? &job : nullptr, ec);
902906
PROCESS_INFORMATION process_info;
@@ -1690,65 +1694,13 @@ namespace platf {
16901694
return {};
16911695
}
16921696

1693-
std::wstring from_utf8(const std::string &string) {
1694-
// No conversion needed if the string is empty
1695-
if (string.empty()) {
1696-
return {};
1697-
}
1698-
1699-
// Get the output size required to store the string
1700-
auto output_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), string.size(), nullptr, 0);
1701-
if (output_size == 0) {
1702-
auto winerr = GetLastError();
1703-
BOOST_LOG(error) << "Failed to get UTF-16 buffer size: "sv << winerr;
1704-
return {};
1705-
}
1706-
1707-
// Perform the conversion
1708-
std::wstring output(output_size, L'\0');
1709-
output_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), string.size(), output.data(), output.size());
1710-
if (output_size == 0) {
1711-
auto winerr = GetLastError();
1712-
BOOST_LOG(error) << "Failed to convert string to UTF-16: "sv << winerr;
1713-
return {};
1714-
}
1715-
1716-
return output;
1717-
}
1718-
1719-
std::string to_utf8(const std::wstring &string) {
1720-
// No conversion needed if the string is empty
1721-
if (string.empty()) {
1722-
return {};
1723-
}
1724-
1725-
// Get the output size required to store the string
1726-
auto output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), nullptr, 0, nullptr, nullptr);
1727-
if (output_size == 0) {
1728-
auto winerr = GetLastError();
1729-
BOOST_LOG(error) << "Failed to get UTF-8 buffer size: "sv << winerr;
1730-
return {};
1731-
}
1732-
1733-
// Perform the conversion
1734-
std::string output(output_size, '\0');
1735-
output_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), string.size(), output.data(), output.size(), nullptr, nullptr);
1736-
if (output_size == 0) {
1737-
auto winerr = GetLastError();
1738-
BOOST_LOG(error) << "Failed to convert string to UTF-8: "sv << winerr;
1739-
return {};
1740-
}
1741-
1742-
return output;
1743-
}
1744-
17451697
std::string get_host_name() {
17461698
WCHAR hostname[256];
17471699
if (GetHostNameW(hostname, ARRAYSIZE(hostname)) == SOCKET_ERROR) {
17481700
BOOST_LOG(error) << "GetHostNameW() failed: "sv << WSAGetLastError();
17491701
return "Sunshine"s;
17501702
}
1751-
return to_utf8(hostname);
1703+
return utf_utils::to_utf8(hostname);
17521704
}
17531705

17541706
class win32_high_precision_timer: public high_precision_timer {

src/platform/windows/misc.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,4 @@ namespace platf {
1919
int64_t qpc_counter();
2020

2121
std::chrono::nanoseconds qpc_time_difference(int64_t performance_counter1, int64_t performance_counter2);
22-
23-
/**
24-
* @brief Convert a UTF-8 string into a UTF-16 wide string.
25-
* @param string The UTF-8 string.
26-
* @return The converted UTF-16 wide string.
27-
*/
28-
std::wstring from_utf8(const std::string &string);
29-
30-
/**
31-
* @brief Convert a UTF-16 wide string into a UTF-8 string.
32-
* @param string The UTF-16 wide string.
33-
* @return The converted UTF-8 string.
34-
*/
35-
std::string to_utf8(const std::wstring &string);
3622
} // namespace platf

src/platform/windows/publish.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "src/nvhttp.h"
2020
#include "src/platform/common.h"
2121
#include "src/thread_safe.h"
22+
#include "utf_utils.h"
2223

2324
#define _FN(x, ret, args) \
2425
typedef ret(*x##_fn) args; \
@@ -109,8 +110,8 @@ namespace platf::publish {
109110
std::wstring domain {SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size()};
110111

111112
auto hostname = platf::get_host_name();
112-
auto name = from_utf8(net::mdns_instance_name(hostname) + '.') + domain;
113-
auto host = from_utf8(hostname + ".local");
113+
auto name = utf_utils::from_utf8(net::mdns_instance_name(hostname) + '.') + domain;
114+
auto host = utf_utils::from_utf8(hostname + ".local");
114115

115116
DNS_SERVICE_INSTANCE instance {};
116117
instance.pszInstanceName = name.data();

0 commit comments

Comments
 (0)