diff --git a/core/libs/prx/libcurl/CMakeLists.txt b/core/libs/prx/libcurl/CMakeLists.txt new file mode 100644 index 0000000000..3fe63c1edb --- /dev/null +++ b/core/libs/prx/libcurl/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libcurl SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libcurl PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libcurl) + +if(BUILD_TESTING) + add_test_executable(guest_curl_strings_tests ${LIBS_INCLUDE_DIR}/tests/GuestCurlStrings.cpp) + target_include_directories(guest_curl_strings_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_curl_strings_tests PRIVATE libcurl libc) + configure_windows_unwind(guest_curl_strings_tests) + add_test(NAME guest_curl_strings COMMAND guest_curl_strings_tests) + set_tests_properties(guest_curl_strings PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_curl_strings PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libcurl/Export.cpp b/core/libs/prx/libcurl/Export.cpp index ac41f59fe9..d682bc2d66 100644 --- a/core/libs/prx/libcurl/Export.cpp +++ b/core/libs/prx/libcurl/Export.cpp @@ -1,6 +1,11 @@ #include "prx/libcurl/include/InventoryExports.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/ExportMacros.hpp" +#include + +static char RawUpper(char value) { + return value >= 'a' && value <= 'z' ? static_cast('A' + value - 'a') : value; +} extern "C" { @@ -76,8 +81,13 @@ int APS5_VABI curl_multi_socket_all() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI curl_strequal() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI curl_strequal(const char* first, const char* second) { + if (!first || !second) NotImplemented_nid_no_patch(__func__); + while (*first && *second && RawUpper(*first) == RawUpper(*second)) { + ++first; + ++second; + } + return RawUpper(*first) == RawUpper(*second); } int APS5_VABI curl_url_get() { @@ -160,8 +170,15 @@ int APS5_VABI curl_easy_init() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI curl_strnequal() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI curl_strnequal(const char* first, const char* second, size_t max) { + if (!first || !second) NotImplemented_nid_no_patch(__func__); + while (*first && *second && max && RawUpper(*first) == RawUpper(*second)) { + --max; + ++first; + ++second; + } + if (max == 0) return 1; + return RawUpper(*first) == RawUpper(*second); } int APS5_VABI curl_mime_data_cb() { diff --git a/core/libs/prx/libcurl/include/InventoryExports.hpp b/core/libs/prx/libcurl/include/InventoryExports.hpp index 744f5ccd90..2cef3ed4f3 100644 --- a/core/libs/prx/libcurl/include/InventoryExports.hpp +++ b/core/libs/prx/libcurl/include/InventoryExports.hpp @@ -1,6 +1,7 @@ #pragma once #include "SceTypes.hpp" +#include extern "C" { @@ -22,7 +23,7 @@ int APS5_VABI curl_version(); int APS5_VABI curl_free(); int APS5_VABI curl_mvsnprintf(); int APS5_VABI curl_multi_socket_all(); -int APS5_VABI curl_strequal(); +int APS5_VABI curl_strequal(const char* first, const char* second); int APS5_VABI curl_url_get(); int APS5_VABI curl_multi_strerror(); int APS5_VABI curl_easy_setopt(); @@ -43,7 +44,7 @@ int APS5_VABI curl_easy_send(); int APS5_VABI curl_easy_reset(); int APS5_VABI curl_easy_strerror(); int APS5_VABI curl_easy_init(); -int APS5_VABI curl_strnequal(); +int APS5_VABI curl_strnequal(const char* first, const char* second, size_t max); int APS5_VABI curl_mime_data_cb(); int APS5_VABI curl_mvaprintf(); int APS5_VABI curl_url(); diff --git a/core/libs/tests/GuestCurlStrings.cpp b/core/libs/tests/GuestCurlStrings.cpp new file mode 100644 index 0000000000..ee4a5ad8cd --- /dev/null +++ b/core/libs/tests/GuestCurlStrings.cpp @@ -0,0 +1,71 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libcurl/libcurl.hpp" +#include +#include +#include +#include + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "curl string check failed at line %d\n", line); + std::abort(); + } +} +#define Check(value) Require((value), __LINE__) + +template +static bool ReportsNotImplemented(const char* name, TCall call) { + try { + call(); + } catch (const std::runtime_error& error) { + return std::string(error.what()) == std::string(name) + " not implemented"; + } + return false; +} + +static void CheckEqual() { + Check(curl_strequal("", "") == 1); + Check(curl_strequal("Content-Type", "content-TYPE") == 1); + Check(curl_strequal("azAZ", "AZaz") == 1); + Check(curl_strequal("a1-_", "A1-_") == 1); + Check(curl_strequal("abc", "abd") == 0); + Check(curl_strequal("abc", "ab") == 0); + Check(curl_strequal("ab", "abc") == 0); + Check(curl_strequal("", "a") == 0); + Check(curl_strequal("a", "") == 0); + Check(curl_strequal("@", "`") == 0); + Check(curl_strequal("[", "{") == 0); + Check(curl_strequal("\xE9", "\xC9") == 0); + Check(curl_strequal("\xE9", "\xE9") == 1); +} + +static void CheckBounded() { + Check(curl_strnequal("abc", "xyz", 0) == 1); + Check(curl_strnequal("", "a", 0) == 1); + Check(curl_strnequal("abcX", "ABCy", 3) == 1); + Check(curl_strnequal("abcX", "ABCy", 4) == 0); + Check(curl_strnequal("abc", "ab", 2) == 1); + Check(curl_strnequal("abc", "ab", 3) == 0); + Check(curl_strnequal("ab", "abc", 3) == 0); + Check(curl_strnequal("ab", "AB", 2) == 1); + Check(curl_strnequal("ab", "AB", 100) == 1); + Check(curl_strnequal("ab\0x", "ab\0y", 4) == 1); + Check(curl_strnequal("", "", 5) == 1); + Check(curl_strnequal("\xE9", "\xC9", 1) == 0); +} + +static void CheckNullPointers() { + Check(ReportsNotImplemented("curl_strequal", [] { curl_strequal(nullptr, "a"); })); + Check(ReportsNotImplemented("curl_strequal", [] { curl_strequal("a", nullptr); })); + Check(ReportsNotImplemented("curl_strequal", [] { curl_strequal(nullptr, nullptr); })); + Check(ReportsNotImplemented("curl_strnequal", [] { curl_strnequal(nullptr, "a", 1); })); + Check(ReportsNotImplemented("curl_strnequal", [] { curl_strnequal("a", nullptr, 1); })); + Check(ReportsNotImplemented("curl_strnequal", [] { curl_strnequal(nullptr, nullptr, 0); })); +} + +int main() { + CheckEqual(); + CheckBounded(); + CheckNullPointers(); + return 0; +} diff --git a/docs/dev/debt/libcurl.md b/docs/dev/debt/libcurl.md index f5c3c3c376..726bc1984e 100644 --- a/docs/dev/debt/libcurl.md +++ b/docs/dev/debt/libcurl.md @@ -1,8 +1,12 @@ # libcurl technical debt +### Unknown function info + +- [curl_strequal, curl_strnequal](../../../core/libs/prx/libcurl/Export.cpp) (libcurl) - signatures and ASCII-only matching follow curl's public [curl_strequal documentation](https://curl.se/libcurl/c/curl_strequal.html) and [strcase.c](https://github.com/curl/curl/blob/curl-7_73_0/lib/strcase.c). No console or title caller was read. A null argument throws, because the module's curl version is unknown and curl differs: 7.73 dereferences it, while 8.10 treats two nulls as equal. + ### Callable exports -- [Callable exports](../../../core/libs/prx/libcurl/Export.cpp) - 85 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libcurl.c) and the [selected export metadata](https://gist.github.com/kyon-eth/996975adc28d0d270d032fb47eb297a7/52f3b6179ff69fbc0725e02ba146279ecd451f49) from two version-labelled images (SHA-256 `69baa557f91eba487275aa1b8e0aecd8dde1caa261eb2dd04106f8fa00e65361`). The labels are not authenticated. Prototypes and behavior are unverified; these declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. +- [Callable exports](../../../core/libs/prx/libcurl/Export.cpp) - 83 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libcurl.c) and the [selected export metadata](https://gist.github.com/kyon-eth/996975adc28d0d270d032fb47eb297a7/52f3b6179ff69fbc0725e02ba146279ecd451f49) from two version-labelled images (SHA-256 `69baa557f91eba487275aa1b8e0aecd8dde1caa261eb2dd04106f8fa00e65361`). The labels are not authenticated. Prototypes and behavior are unverified; these declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. | Name | NID | | --- | --- | @@ -24,7 +28,6 @@ | `curl_free` | `AAHmHrXOtv4` | | `curl_mvsnprintf` | `BAz8Ji7lJls` | | `curl_multi_socket_all` | `BhNAR5P2VEg` | -| `curl_strequal` | `CDG6wR08Ntw` | | `curl_url_get` | `Cc+Qhdd4ons` | | `curl_multi_strerror` | `Dfdbc8trvZg` | | `curl_easy_setopt` | `DiBmbfiH8J4` | @@ -45,7 +48,6 @@ | `curl_easy_reset` | `N5yPp5dHBQE` | | `curl_easy_strerror` | `NGTkAHTkViY` | | `curl_easy_init` | `NUbq2aNGojo` | -| `curl_strnequal` | `OJiHNjSOguc` | | `curl_mime_data_cb` | `PoT9ByTU4ws` | | `curl_mvaprintf` | `Pyz55DYmOrI` | | `curl_url` | `QwbCXdumE84` |