Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions dll/kernel32/winbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <algorithm>
#include <cassert>
#include <cctype>
#include <cerrno>
#include <cstdarg>
#include <cstdlib>
Expand Down Expand Up @@ -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<UINT>(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<uint16_t *>(lpString1), reinterpret_cast<const uint16_t *>(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<uint16_t *>(lpString1), reinterpret_cast<const uint16_t *>(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<int>(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<int>(wstrlen(reinterpret_cast<const uint16_t *>(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<const uint16_t *>(lpString1);
auto *b = reinterpret_cast<const uint16_t *>(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<unsigned char>(*a));
int cb = std::tolower(static_cast<unsigned char>(*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<const uint16_t *>(lpString1);
auto *b = reinterpret_cast<const uint16_t *>(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
18 changes: 18 additions & 0 deletions dll/kernel32/winbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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