diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index 7dba258..1e9889c 100644 --- a/dll/kernel32/winbase.cpp +++ b/dll/kernel32/winbase.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -1341,4 +1342,196 @@ BOOL WINAPI GetDiskFreeSpaceExW(LPCWSTR lpDirectoryName, PULARGE_INTEGER lpFreeB lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes); } +// --------------------------------------------------------------------------- +// String API family (lstr*). +// +// Pre-Unicode helpers documented at +// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ +// +// Microsoft's documented semantics, summarised: +// +// * lstrcpyn copies up to iMaxLength-1 chars from src to dst and ALWAYS +// writes a terminating nul — unlike strncpy. iMaxLength is declared INT +// but is treated as unsigned: a negative value behaves as a very large +// positive bound. +// * lstrcpy / lstrcat behave like strcpy / strcat with no bounds checks. +// * lstrlen returns the length excluding the terminator. +// * lstrcmp returns -1 / 0 / +1, NOT strcmp's arbitrary signed delta. +// The official implementation defers to CompareString with the thread +// locale; for our usage — driving Win95-era command-line compilers — +// a byte-wise compare is indistinguishable and avoids dragging in the +// full locale subsystem here. Same for lstrcmpi (case-insensitive). +// +// * On access violation the documented behaviour is to set +// ERROR_INVALID_PARAMETER and return NULL (or 0). wibo does not have +// SEH page-fault dispatch, so we only catch the obvious NULL case; +// anything else faults the guest as it would on a real Windows. +// --------------------------------------------------------------------------- + +// (lstrcpynA lives further up in this file — upstream 1.2.0 grew its own +// implementation; ours was dropped in the 1.2.0 rebase to avoid the +// duplicate definition.) + +LPWSTR WINAPI lstrcpynW(LPWSTR lpString1, LPCWSTR lpString2, int iMaxLength) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcpynW(%p, %p, %d)\n", lpString1, lpString2, iMaxLength); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + LPWSTR d = lpString1; + LPCWSTR s = lpString2; + UINT count = static_cast(iMaxLength); + while (count > 1 && *s) { + --count; + *d++ = *s++; + } + if (count) { + *d = 0; + } + return lpString1; +} + +LPSTR WINAPI lstrcpyA(LPSTR lpString1, LPCSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcpyA(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + std::strcpy(lpString1, lpString2); + return lpString1; +} + +LPWSTR WINAPI lstrcpyW(LPWSTR lpString1, LPCWSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcpyW(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + wstrcpy(reinterpret_cast(lpString1), reinterpret_cast(lpString2)); + return lpString1; +} + +LPSTR WINAPI lstrcatA(LPSTR lpString1, LPCSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcatA(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + std::strcat(lpString1, lpString2); + return lpString1; +} + +LPWSTR WINAPI lstrcatW(LPWSTR lpString1, LPCWSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcatW(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + wstrcat(reinterpret_cast(lpString1), reinterpret_cast(lpString2)); + return lpString1; +} + +int WINAPI lstrlenA(LPCSTR lpString) { + HOST_CONTEXT_GUARD(); + VERBOSE_LOG("lstrlenA(%p)\n", lpString); + if (!lpString) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + return static_cast(std::strlen(lpString)); +} + +int WINAPI lstrlenW(LPCWSTR lpString) { + HOST_CONTEXT_GUARD(); + VERBOSE_LOG("lstrlenW(%p)\n", lpString); + if (!lpString) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + return static_cast(wstrlen(reinterpret_cast(lpString))); +} + +int WINAPI lstrcmpA(LPCSTR lpString1, LPCSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpA(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + int cmp = std::strcmp(lpString1, lpString2); + return (cmp < 0) ? -1 : (cmp > 0) ? 1 : 0; +} + +int WINAPI lstrcmpW(LPCWSTR lpString1, LPCWSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpW(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + auto *a = reinterpret_cast(lpString1); + auto *b = reinterpret_cast(lpString2); + while (*a && *a == *b) { + ++a; + ++b; + } + if (*a == *b) { + return 0; + } + return (*a < *b) ? -1 : 1; +} + +int WINAPI lstrcmpiA(LPCSTR lpString1, LPCSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpiA(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + LPCSTR a = lpString1; + LPCSTR b = lpString2; + while (*a && *b) { + int ca = std::tolower(static_cast(*a)); + int cb = std::tolower(static_cast(*b)); + if (ca != cb) { + return (ca < cb) ? -1 : 1; + } + ++a; + ++b; + } + if (*a == *b) { + return 0; + } + return (*a == '\0') ? -1 : 1; +} + +int WINAPI lstrcmpiW(LPCWSTR lpString1, LPCWSTR lpString2) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("lstrcmpiW(%p, %p)\n", lpString1, lpString2); + if (!lpString1 || !lpString2) { + setLastError(ERROR_INVALID_PARAMETER); + return 0; + } + auto *a = reinterpret_cast(lpString1); + auto *b = reinterpret_cast(lpString2); + while (*a && *b) { + uint16_t ca = wcharToLower(*a); + uint16_t cb = wcharToLower(*b); + if (ca != cb) { + return (ca < cb) ? -1 : 1; + } + ++a; + ++b; + } + if (*a == *b) { + return 0; + } + return (*a == 0) ? -1 : 1; +} + } // namespace kernel32 diff --git a/dll/kernel32/winbase.h b/dll/kernel32/winbase.h index 62e6ba6..5cc72b1 100644 --- a/dll/kernel32/winbase.h +++ b/dll/kernel32/winbase.h @@ -133,4 +133,22 @@ BOOL WINAPI GetDiskFreeSpaceExA(LPCSTR lpDirectoryName, PULARGE_INTEGER lpFreeBy BOOL WINAPI GetDiskFreeSpaceExW(LPCWSTR lpDirectoryName, PULARGE_INTEGER lpFreeBytesAvailableToCaller, PULARGE_INTEGER lpTotalNumberOfBytes, PULARGE_INTEGER lpTotalNumberOfFreeBytes); +// String API family. Documented behaviour from Microsoft's WinAPI +// reference: these are simple length / copy / cat / compare helpers +// for null-terminated A/W strings with no bounds checking beyond the +// explicit limit on lstrcpyn. No locale-aware behaviour at our level; +// see CompareString{A,W} for that. (lstrcpynA is declared further up +// with the upstream implementation.) +LPWSTR WINAPI lstrcpynW(LPWSTR lpString1, LPCWSTR lpString2, int iMaxLength); +LPSTR WINAPI lstrcpyA(LPSTR lpString1, LPCSTR lpString2); +LPWSTR WINAPI lstrcpyW(LPWSTR lpString1, LPCWSTR lpString2); +LPSTR WINAPI lstrcatA(LPSTR lpString1, LPCSTR lpString2); +LPWSTR WINAPI lstrcatW(LPWSTR lpString1, LPCWSTR lpString2); +int WINAPI lstrlenA(LPCSTR lpString); +int WINAPI lstrlenW(LPCWSTR lpString); +int WINAPI lstrcmpA(LPCSTR lpString1, LPCSTR lpString2); +int WINAPI lstrcmpW(LPCWSTR lpString1, LPCWSTR lpString2); +int WINAPI lstrcmpiA(LPCSTR lpString1, LPCSTR lpString2); +int WINAPI lstrcmpiW(LPCWSTR lpString1, LPCWSTR lpString2); + } // namespace kernel32