diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 5146ed74c..d46141cfe 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -67,7 +67,7 @@ target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR}/nid/include) target_link_libraries(libkernel PRIVATE ${CMAKE_DL_LIBS}) target_link_libraries(libkernel PRIVATE libc) if(WIN32) - target_link_libraries(libkernel PRIVATE ws2_32) + target_link_libraries(libkernel PRIVATE ws2_32 shell32) target_compile_definitions(libkernel PRIVATE _WIN32_WINNT=0x0602) endif() configure_windows_unwind(libkernel) @@ -125,6 +125,15 @@ if(BUILD_TESTING) set_tests_properties(guest_process_time guest_process_time_counter PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_process_arguments_tests ${LIBS_INCLUDE_DIR}/tests/GuestProcessArguments.cpp) + target_include_directories(guest_process_arguments_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_process_arguments_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_process_arguments_tests) + add_test(NAME guest_process_arguments COMMAND guest_process_arguments_tests plain "two words" "ü日") + if(WIN32) + set_tests_properties(guest_process_arguments PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_library(guest_module_fixture SHARED ${LIBS_INCLUDE_DIR}/tests/GuestModuleFixture.cpp) target_include_directories(guest_module_fixture PRIVATE ${LIBS_INCLUDE_DIR}) add_custom_command(TARGET guest_module_fixture POST_BUILD diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 425306a72..4df88ebd6 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -3,6 +3,7 @@ #define NOMINMAX #endif #include +#include #else #include #include @@ -46,17 +47,30 @@ constexpr unsigned freebsdWaitOptions = 0x8000003Fu; std::atomic gpoBits{0}; constexpr std::array openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1}; +#ifdef _WIN32 +std::string toUtf8(const wchar_t* value) { + const int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, nullptr, 0, nullptr, nullptr); + if (size <= 0) + throw std::system_error(GetLastError(), std::system_category(), "Converting a process argument to UTF-8"); + std::string text(static_cast(size), '\0'); + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, text.data(), size, nullptr, nullptr) != size) + throw std::system_error(GetLastError(), std::system_category(), "Converting a process argument to UTF-8"); + text.pop_back(); + return text; +} +#endif + class ProcessArguments { public: ProcessArguments() { #ifdef _WIN32 - std::array path{}; - const auto size = GetModuleFileNameA(nullptr, path.data(), static_cast(path.size())); - if (size == 0) - throw std::system_error(GetLastError(), std::system_category(), "Reading executable path"); - if (size >= path.size()) - throw std::runtime_error("Executable path exceeds the guest argument buffer"); - arguments.emplace_back(path.data(), size); + int count = 0; + const std::unique_ptr values(CommandLineToArgvW(GetCommandLineW(), &count), + [](LPWSTR* parsed) { LocalFree(parsed); }); + if (!values) + throw std::system_error(GetLastError(), std::system_category(), "Reading process arguments"); + for (int index = 0; index < count; ++index) + arguments.push_back(toUtf8(values.get()[index])); #else std::ifstream stream("/proc/self/cmdline", std::ios::binary); if (!stream) diff --git a/core/libs/tests/GuestProcessArguments.cpp b/core/libs/tests/GuestProcessArguments.cpp new file mode 100644 index 000000000..fa0f5584b --- /dev/null +++ b/core/libs/tests/GuestProcessArguments.cpp @@ -0,0 +1,29 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" int APS5_VABI getargc_nid_postfix(void); +extern "C" const char** APS5_VABI getargv_nid_postfix(void); + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +int main() { + const char* const expected[] = {"plain", "two words", "ü日"}; + constexpr int expectedCount = 1 + static_cast(sizeof(expected) / sizeof(expected[0])); + const int count = getargc_nid_postfix(); + const char** values = getargv_nid_postfix(); + Require(count == expectedCount, "getargc does not count every command-line argument"); + Require(values != nullptr && values[0] != nullptr && values[0][0] != '\0', "getargv has no program name"); + for (int index = 1; index < expectedCount; ++index) { + Require(values[index] != nullptr && std::strcmp(values[index], expected[index - 1]) == 0, + "getargv does not return a command-line argument as UTF-8"); + } + Require(values[count] == nullptr, "getargv is not terminated by a null pointer"); + return 0; +}