From 36060218d023e596b525118831c0a89c6b45bde2 Mon Sep 17 00:00:00 2001 From: Simon Brakhane Date: Sat, 13 Jun 2026 09:05:21 +0200 Subject: [PATCH] kernel32: add the lstr* string API family Adds the legacy lstr{cpy,cat,len,cmp,cmpi}{A,W} + lstrcpynW functions to kernel32, declared in dll/kernel32/winbase.h with implementations in dll/kernel32/winbase.cpp. (lstrcpynA already exists since #124 and is kept as-is.) These are a documented Win32 1.x / Win95-era surface that wibo does not yet cover, and wibo's own embedded msvcrt40 (the encounter/winedll build, WIBO_ENABLE_WINE_DLLS) imports the family from kernel32: hosting MSVC 4.x's cl.exe stops at the first missing lstr* import inside c1.exe without them. With this patch a clean cl.exe + link.exe + nmake.exe pipeline (MSVC 4.00 and 4.20) runs end-to-end and produces output byte-identical to the equivalent Wine-hosted build. Behavioural notes: * lstrcpyn copies up to iMaxLength-1 chars and ALWAYS writes a terminating nul, unlike strncpy. iMaxLength is declared int but is treated as unsigned per Windows' historical behaviour. * lstrcmp(i) returns -1 / 0 / +1 (not strcmp's arbitrary delta). The official implementation defers to CompareString with the thread locale; a byte-wise compare is used here because the consumers exercised (cl.exe, link.exe, nmake.exe, msvcrt) only feed ASCII. * Access-violation behaviour (SetLastError + return 0) is emulated only for the obvious NULL case -- wibo has no SEH dispatcher, so arbitrary bad pointers fault the guest as they would on a real Windows. --- dll/kernel32/winbase.cpp | 193 +++++++++++++++++++++++++++++++++++++++ dll/kernel32/winbase.h | 18 ++++ 2 files changed, 211 insertions(+) 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