From 7c255b91cd136a38bb31d50d4fbc09e004843a3f Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:20:55 +0530 Subject: [PATCH 1/2] kernel32 + user32: implement FormatMessageA, WriteConsoleA, SendMessageA Three APIs needed by NT 3.5-era build tools (RC.EXE, LINK.EXE, MC.EXE) for error diagnostics: FormatMessageA: Full implementation of FROM_HMODULE, FROM_STRING, FROM_SYSTEM modes. Walks RT_MESSAGETABLE resources embedded in PE .rsrc sections (the MESSAGE_RESOURCE_DATA/BLOCK/ENTRY structures produced by MC.EXE). Handles %1..%99 argument substitution with !format! specifiers, FORMAT_MESSAGE_ALLOCATE_BUFFER, and FORMAT_MESSAGE_IGNORE_INSERTS. Previously commented out due to va_list trampoline issues; signature changed to LPVOID to avoid leaking GCC's __va_list_tag. WriteConsoleA: Mirrors the existing WriteConsoleW implementation. NT tools (RC, LINK) use WriteConsoleA to emit error messages after FormatMessageA formats them. Writes through to the FileObject's underlying fd for stdout/ stderr handles. SendMessageA: No-op stub returning 0 / ERROR_INVALID_WINDOW_HANDLE. NT's RC.EXE calls SendMessage(NULL, WM_USER+0x19, ...) as a vestigial IDE progress hook that's NULL in standalone CLI runs. Without the stub wibo aborts on the missing import. Also adds ERROR_INVALID_WINDOW_HANDLE (1400) to errors.h. --- dll/kernel32/winbase.cpp | 247 ++++++++++++++++++++++++++++++++++----- dll/kernel32/winbase.h | 8 +- dll/kernel32/wincon.cpp | 29 +++++ dll/kernel32/wincon.h | 2 + dll/user32.cpp | 14 +++ dll/user32.h | 1 + src/errors.h | 3 + 7 files changed, 270 insertions(+), 34 deletions(-) diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index 91db64c..957c064 100644 --- a/dll/kernel32/winbase.cpp +++ b/dll/kernel32/winbase.cpp @@ -8,6 +8,7 @@ #include "internal.h" #include "mimalloc/types.h" #include "modules.h" +#include "resources.h" #include "strutil.h" #include "types.h" @@ -557,49 +558,231 @@ UINT WINAPI SetHandleCount(UINT uNumber) { return 0x3FFE; } +// MESSAGETABLE resource layout (RT_MESSAGETABLE = 11). Produced by MC.EXE, +// embedded into the PE by RC.EXE. See WinNT.h / MSDN "Message Resources". +constexpr WORD kRtMessageTable = 11; +constexpr WORD kMessageResourceUnicode = 0x0001; + +struct MessageResourceBlock { + DWORD LowId; + DWORD HighId; + DWORD OffsetToEntries; +}; +struct MessageResourceData { + DWORD NumberOfBlocks; + MessageResourceBlock Blocks[1]; // Blocks[NumberOfBlocks] +}; +struct MessageResourceEntry { + WORD Length; // total size incl. this header + WORD Flags; // bit 0 set => UTF-16 text + BYTE Text[1]; // Length-4 bytes, null-terminated +}; + +// Locate and copy out the message string for dwMessageId in hModule's +// RT_MESSAGETABLE resource. Writes the resulting narrow-ANSI template +// into outTemplate. Returns false if the id isn't present. +static bool loadMessageTemplateFromModule(HMODULE hModule, DWORD dwMessageId, std::string &outTemplate) { + auto *exe = wibo::executableFromModule(hModule); + DEBUG_LOG(" MESSAGETABLE lookup: hMod=%p exe=%p id=%u\n", hModule, exe, dwMessageId); + if (!exe) return false; + auto typeId = wibo::ResourceIdentifier::fromID(kRtMessageTable); + auto nameId = wibo::ResourceIdentifier::fromID(1); + wibo::ResourceLocation loc; + if (!exe->findResource(typeId, nameId, std::nullopt, loc)) { + DEBUG_LOG(" MESSAGETABLE lookup: findResource failed for type=%u name=1\n", kRtMessageTable); + return false; + } + DEBUG_LOG(" MESSAGETABLE lookup: resource size=%zu at %p\n", loc.size, loc.data); + if (loc.size < sizeof(MessageResourceData)) return false; + const auto *data = reinterpret_cast(loc.data); + DEBUG_LOG(" MESSAGETABLE lookup: NumberOfBlocks=%u\n", data->NumberOfBlocks); + const auto *blocks = data->Blocks; + for (DWORD bi = 0; bi < data->NumberOfBlocks; bi++) { + const auto &blk = blocks[bi]; + DEBUG_LOG(" block %u: LowId=%u HighId=%u Offset=0x%x\n", bi, blk.LowId, blk.HighId, blk.OffsetToEntries); + if (dwMessageId < blk.LowId || dwMessageId > blk.HighId) continue; + auto index = dwMessageId - blk.LowId; + const auto *entry = + reinterpret_cast(static_cast(loc.data) + blk.OffsetToEntries); + for (DWORD ei = 0; ei < index; ei++) { + entry = reinterpret_cast( + reinterpret_cast(entry) + entry->Length); + } + const size_t textBytes = entry->Length > offsetof(MessageResourceEntry, Text) + ? entry->Length - offsetof(MessageResourceEntry, Text) + : 0; + if (entry->Flags & kMessageResourceUnicode) { + int wideLen = static_cast(textBytes / sizeof(uint16_t)); + outTemplate = wideStringToString(reinterpret_cast(entry->Text), wideLen); + } else { + outTemplate.assign(reinterpret_cast(entry->Text), textBytes); + } + // MC emits trailing \r\n\0 (or \0). Trim any trailing NULs. + while (!outTemplate.empty() && outTemplate.back() == '\0') outTemplate.pop_back(); + DEBUG_LOG(" MESSAGETABLE lookup: found template len=%zu\n", outTemplate.size()); + return true; + } + return false; +} + +// Substitute %1..%99 in a message template with the caller-supplied +// arguments. If ignoreInserts is true, inserts pass through verbatim. +// Arguments come from a va_list when FORMAT_MESSAGE_ARGUMENT_ARRAY is +// not set, or from a DWORD_PTR array otherwise. For the stubbed arg +// model here we accept a simple pointer-to-pointer array which covers +// RC / LINK / NMAKE usage. +static std::string applyMessageInserts(const std::string &tpl, const DWORD_PTR *args, DWORD argCount, + bool ignoreInserts) { + std::string out; + out.reserve(tpl.size()); + for (size_t i = 0; i < tpl.size(); ) { + char c = tpl[i]; + if (c != '%' || ignoreInserts) { + out.push_back(c); + i++; + continue; + } + // Past '%'. Parse an integer 1..99. + i++; + if (i >= tpl.size()) { out.push_back('%'); break; } + if (!std::isdigit(static_cast(tpl[i]))) { + // Escape forms: %%, %n (newline), %r, %b (space), %0 (terminator). + char esc = tpl[i++]; + switch (esc) { + case '0': return out; // %0 ends the message + case 'n': out.push_back('\n'); break; + case 'r': out.push_back('\r'); break; + case 'b': out.push_back(' '); break; + case '.': out.push_back('.'); break; + case '!': out.push_back('!'); break; + case 't': out.push_back('\t'); break; + case '%': + default: out.push_back('%'); out.push_back(esc); break; + } + continue; + } + unsigned int n = 0; + while (i < tpl.size() && std::isdigit(static_cast(tpl[i])) && n < 100) { + n = n * 10 + (tpl[i++] - '0'); + } + // Skip a trailing "!!" section (MC format spec). We don't + // apply it — just treat the argument as a string pointer or number. + if (i < tpl.size() && tpl[i] == '!') { + i++; + while (i < tpl.size() && tpl[i] != '!') i++; + if (i < tpl.size()) i++; // consume closing '!' + } + if (n >= 1 && n <= argCount && args) { + auto arg = args[n - 1]; + // Heuristic: treat pointer-looking values as C strings, else %d. + // This covers the common RC / LINK use (pass string pointer). + char tmp[32]; + const char *s = reinterpret_cast(arg); + if (arg > 0x10000 && s) { + out.append(s); + } else { + int written = std::snprintf(tmp, sizeof(tmp), "%ld", static_cast(arg)); + if (written > 0) out.append(tmp, written); + } + } else { + // Argument missing; emit "%N" verbatim so callers can tell. + char tmp[8]; + int written = std::snprintf(tmp, sizeof(tmp), "%%%u", n); + if (written > 0) out.append(tmp, written); + } + } + return out; +} + DWORD WINAPI FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer, - DWORD nSize, va_list *Arguments) { + DWORD nSize, LPVOID Arguments) { HOST_CONTEXT_GUARD(); - DEBUG_LOG("FormatMessageA(%u, %p, %u, %u, %p, %u, %p)\n", dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, + DEBUG_LOG("FormatMessageA(0x%x, %p, %u, %u, %p, %u, %p)\n", dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments); + (void)dwLanguageId; + + constexpr DWORD kAllocateBuffer = 0x00000100; + constexpr DWORD kIgnoreInserts = 0x00000200; + constexpr DWORD kFromString = 0x00000400; + constexpr DWORD kFromHModule = 0x00000800; + constexpr DWORD kFromSystem = 0x00001000; + constexpr DWORD kArgumentArray = 0x00002000; + + // Step 1: locate the message template. + std::string tpl; + bool haveTpl = false; + if (dwFlags & kFromString) { + if (lpSource) { + tpl.assign(static_cast(lpSource)); + haveTpl = true; + } + } else if (dwFlags & kFromHModule) { + // HMODULE is a small integer in wibo; lpSource carries it via a + // cast from HMODULE -> void* at the caller. Round-trip via uintptr. + HMODULE hMod = static_cast(reinterpret_cast(lpSource)); + haveTpl = loadMessageTemplateFromModule(hMod, dwMessageId, tpl); + } else if (dwFlags & kFromSystem) { + tpl = std::system_category().message(static_cast(dwMessageId)); + haveTpl = true; + } + + if (!haveTpl) { + setLastError(ERROR_RESOURCE_LANG_NOT_FOUND); + return 0; + } + + // Step 2: apply inserts if not suppressed. + std::string formatted; + if (dwFlags & kIgnoreInserts) { + formatted = std::move(tpl); + } else { + // Caller may pass a pointer array (ARGUMENT_ARRAY) or a va_list. + // We treat both as a flat array of DWORD_PTR — good enough for + // the tool users (RC, LINK, NMAKE) that only substitute strings. + const DWORD_PTR *args = nullptr; + DWORD argCount = 99; // we have no reliable count; let inserts self-terminate + if (dwFlags & kArgumentArray) { + args = reinterpret_cast(Arguments); + } else if (Arguments) { + args = reinterpret_cast(Arguments); + } + formatted = applyMessageInserts(tpl, args, argCount, false); + } - if (dwFlags & 0x00000100) { - // FORMAT_MESSAGE_ALLOCATE_BUFFER - } else if (dwFlags & 0x00002000) { - // FORMAT_MESSAGE_ARGUMENT_ARRAY - } else if (dwFlags & 0x00000800) { - // FORMAT_MESSAGE_FROM_HMODULE - } else if (dwFlags & 0x00000400) { - // FORMAT_MESSAGE_FROM_STRING - } else if (dwFlags & 0x00001000) { - // FORMAT_MESSAGE_FROM_SYSTEM - std::string message = std::system_category().message(static_cast(dwMessageId)); - size_t length = message.length(); - if (!lpBuffer || nSize == 0) { - setLastError(ERROR_INSUFFICIENT_BUFFER); + // Step 3: write out. + const size_t len = formatted.size(); + if (dwFlags & kAllocateBuffer) { + // lpBuffer is really LPSTR* — store an allocated pointer there. + auto **outPtr = reinterpret_cast(lpBuffer); + if (!outPtr) { + setLastError(ERROR_INVALID_PARAMETER); return 0; } - std::strncpy(lpBuffer, message.c_str(), static_cast(nSize)); - if (static_cast(nSize) <= length) { - if (static_cast(nSize) > 0) { - lpBuffer[nSize - 1] = '\0'; - } - setLastError(ERROR_INSUFFICIENT_BUFFER); + size_t allocSize = std::max(static_cast(nSize), len + 1); + auto *buf = reinterpret_cast(std::malloc(allocSize)); + if (!buf) { + setLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } - lpBuffer[length] = '\0'; - return static_cast(length); - } else if (dwFlags & 0x00000200) { - // FORMAT_MESSAGE_IGNORE_INSERTS - } else { - // unhandled? + std::memcpy(buf, formatted.data(), len); + buf[len] = '\0'; + *outPtr = buf; + return static_cast(len); } - if (lpBuffer && nSize > 0) { - lpBuffer[0] = '\0'; + if (!lpBuffer || nSize == 0) { + setLastError(ERROR_INSUFFICIENT_BUFFER); + return 0; } - setLastError(ERROR_CALL_NOT_IMPLEMENTED); - return 0; + if (len >= nSize) { + std::memcpy(lpBuffer, formatted.data(), nSize - 1); + lpBuffer[nSize - 1] = '\0'; + setLastError(ERROR_INSUFFICIENT_BUFFER); + return 0; + } + std::memcpy(lpBuffer, formatted.data(), len); + lpBuffer[len] = '\0'; + return static_cast(len); } PVOID WINAPI EncodePointer(PVOID Ptr) { diff --git a/dll/kernel32/winbase.h b/dll/kernel32/winbase.h index 0803910..1cfea43 100644 --- a/dll/kernel32/winbase.h +++ b/dll/kernel32/winbase.h @@ -81,8 +81,12 @@ ATOM WINAPI AddAtomW(LPCWSTR lpString); UINT WINAPI GetAtomNameA(ATOM nAtom, LPSTR lpBuffer, int nSize); UINT WINAPI GetAtomNameW(ATOM nAtom, LPWSTR lpBuffer, int nSize); UINT WINAPI SetHandleCount(UINT uNumber); -// DWORD WINAPI FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer, -// DWORD nSize, va_list *Arguments); +// Arguments is LPVOID here instead of va_list* to avoid leaking GCC's +// internal __va_list_tag through the trampoline code generator. The +// Win32 ABI passes a pointer to the caller's va_list / argument array +// anyway, so a typed pointer is enough. +DWORD WINAPI FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer, + DWORD nSize, LPVOID Arguments); PVOID WINAPI EncodePointer(PVOID Ptr); PVOID WINAPI DecodePointer(PVOID Ptr); BOOL WINAPI SetDllDirectoryA(LPCSTR lpPathName); diff --git a/dll/kernel32/wincon.cpp b/dll/kernel32/wincon.cpp index 8a26a6a..5500aac 100644 --- a/dll/kernel32/wincon.cpp +++ b/dll/kernel32/wincon.cpp @@ -93,6 +93,35 @@ BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumber return FALSE; } +BOOL WINAPI WriteConsoleA(HANDLE hConsoleOutput, LPCVOID lpBuffer, DWORD nNumberOfCharsToWrite, + LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("WriteConsoleA(%p, %p, %u, %p, %p)\n", hConsoleOutput, lpBuffer, nNumberOfCharsToWrite, + lpNumberOfCharsWritten, lpReserved); + (void)lpReserved; + if (lpNumberOfCharsWritten) { + *lpNumberOfCharsWritten = 0; + } + if (!lpBuffer && nNumberOfCharsToWrite != 0) { + setLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + auto file = wibo::handles().getAs(hConsoleOutput); + if (file && (file->fd == STDOUT_FILENO || file->fd == STDERR_FILENO)) { + auto io = files::write(file.get(), lpBuffer, nNumberOfCharsToWrite, std::nullopt, true); + if (lpNumberOfCharsWritten) { + *lpNumberOfCharsWritten = io.bytesTransferred; + } + if (io.unixError != 0) { + setLastError(wibo::winErrorFromErrno(io.unixError)); + return FALSE; + } + return TRUE; + } + setLastError(ERROR_INVALID_HANDLE); + return FALSE; +} + DWORD WINAPI GetConsoleTitleA(LPSTR lpConsoleTitle, DWORD nSize) { HOST_CONTEXT_GUARD(); DEBUG_LOG("GetConsoleTitleA(%p, %u)\n", lpConsoleTitle, nSize); diff --git a/dll/kernel32/wincon.h b/dll/kernel32/wincon.h index a34b8ac..31d75e3 100644 --- a/dll/kernel32/wincon.h +++ b/dll/kernel32/wincon.h @@ -34,6 +34,8 @@ UINT WINAPI GetConsoleCP(); UINT WINAPI GetConsoleOutputCP(); BOOL WINAPI SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine, BOOL Add); BOOL WINAPI GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *lpConsoleScreenBufferInfo); +BOOL WINAPI WriteConsoleA(HANDLE hConsoleOutput, LPCVOID lpBuffer, DWORD nNumberOfCharsToWrite, + LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved); BOOL WINAPI WriteConsoleW(HANDLE hConsoleOutput, LPCWSTR lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved); DWORD WINAPI GetConsoleTitleA(LPSTR lpConsoleTitle, DWORD nSize); diff --git a/dll/user32.cpp b/dll/user32.cpp index 1d30c1d..0b23d35 100644 --- a/dll/user32.cpp +++ b/dll/user32.cpp @@ -175,6 +175,20 @@ HWND WINAPI GetActiveWindow() { return NO_HANDLE; } +LONG WINAPI SendMessageA(HWND hWnd, UINT Msg, LONG wParam, LONG lParam) { + // No-op stub. Real Windows returns 0 with ERROR_INVALID_WINDOW_HANDLE + // when called with a NULL/invalid HWND. NT-era command-line tools (MC, + // RC) link against user32 and keep a vestigial SendMessage call for + // posting progress/errors to an IDE workbench HWND that's NULL in + // standalone runs. We don't host any windows so every call here is + // the NULL-HWND path. + DEBUG_LOG("STUB: SendMessageA(hwnd=%p, msg=0x%x, w=0x%lx, l=0x%lx) -> 0\n", hWnd, Msg, + static_cast(wParam), static_cast(lParam)); + (void)hWnd; (void)Msg; (void)wParam; (void)lParam; + kernel32::setLastError(ERROR_INVALID_WINDOW_HANDLE); + return 0; +} + } // namespace user32 #include "user32_trampolines.h" diff --git a/dll/user32.h b/dll/user32.h index 2fdd833..288a3f7 100644 --- a/dll/user32.h +++ b/dll/user32.h @@ -11,5 +11,6 @@ HKL WINAPI GetKeyboardLayout(DWORD idThread); HWINSTA WINAPI GetProcessWindowStation(); BOOL WINAPI GetUserObjectInformationA(HANDLE hObj, int nIndex, PVOID pvInfo, DWORD nLength, LPDWORD lpnLengthNeeded); HWND WINAPI GetActiveWindow(); +LONG WINAPI SendMessageA(HWND hWnd, UINT Msg, LONG wParam, LONG lParam); } // namespace user32 diff --git a/src/errors.h b/src/errors.h index 422db2d..eb3ed54 100644 --- a/src/errors.h +++ b/src/errors.h @@ -7,6 +7,9 @@ #define ERROR_PATH_NOT_FOUND 3 #define ERROR_ACCESS_DENIED 5 #define ERROR_INVALID_HANDLE 6 +// user32-domain error (0x578): the target HWND doesn't exist, is destroyed, +// or belongs to another thread/process. +#define ERROR_INVALID_WINDOW_HANDLE 1400 #define ERROR_NOT_ENOUGH_MEMORY 8 #define ERROR_NO_MORE_FILES 18 #define ERROR_NO_MORE_ITEMS 259 From 59a08465c21162ded218df3f21d849a9fef9e7d4 Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:15:57 +0530 Subject: [PATCH 2/2] FormatMessageA: fix 32-bit va_list handling, add user32 stubs for RC FormatMessageA: - Fix 32-bit va_list handling: dereference va_list* for non-ARGUMENT_ARRAY callers, use uint32_t indexing (not host DWORD_PTR) for guest stack args - Support !ws!/!ls! wide-string format specifiers in message inserts user32: - SendMessageW no-op stub (NULL HWND path for CLI tools) - CharUpperBuffW via towupper - CharNextA single-byte advance (non-DBCS) --- dll/kernel32/winbase.cpp | 57 +++++++++++++++++++++++----------------- dll/user32.cpp | 23 ++++++++++++++++ dll/user32.h | 3 +++ 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index 957c064..7cb2416 100644 --- a/dll/kernel32/winbase.cpp +++ b/dll/kernel32/winbase.cpp @@ -631,7 +631,7 @@ static bool loadMessageTemplateFromModule(HMODULE hModule, DWORD dwMessageId, st // not set, or from a DWORD_PTR array otherwise. For the stubbed arg // model here we accept a simple pointer-to-pointer array which covers // RC / LINK / NMAKE usage. -static std::string applyMessageInserts(const std::string &tpl, const DWORD_PTR *args, DWORD argCount, +static std::string applyMessageInserts(const std::string &tpl, const uint32_t *args, DWORD argCount, bool ignoreInserts) { std::string out; out.reserve(tpl.size()); @@ -642,14 +642,12 @@ static std::string applyMessageInserts(const std::string &tpl, const DWORD_PTR * i++; continue; } - // Past '%'. Parse an integer 1..99. i++; if (i >= tpl.size()) { out.push_back('%'); break; } if (!std::isdigit(static_cast(tpl[i]))) { - // Escape forms: %%, %n (newline), %r, %b (space), %0 (terminator). char esc = tpl[i++]; switch (esc) { - case '0': return out; // %0 ends the message + case '0': return out; case 'n': out.push_back('\n'); break; case 'r': out.push_back('\r'); break; case 'b': out.push_back(' '); break; @@ -665,27 +663,36 @@ static std::string applyMessageInserts(const std::string &tpl, const DWORD_PTR * while (i < tpl.size() && std::isdigit(static_cast(tpl[i])) && n < 100) { n = n * 10 + (tpl[i++] - '0'); } - // Skip a trailing "!!" section (MC format spec). We don't - // apply it — just treat the argument as a string pointer or number. + // Parse a trailing "!!" section (MC format spec). + std::string fmtSpec; if (i < tpl.size() && tpl[i] == '!') { i++; + size_t fmtStart = i; while (i < tpl.size() && tpl[i] != '!') i++; - if (i < tpl.size()) i++; // consume closing '!' + fmtSpec = tpl.substr(fmtStart, i - fmtStart); + if (i < tpl.size()) i++; } if (n >= 1 && n <= argCount && args) { - auto arg = args[n - 1]; - // Heuristic: treat pointer-looking values as C strings, else %d. - // This covers the common RC / LINK use (pass string pointer). - char tmp[32]; - const char *s = reinterpret_cast(arg); - if (arg > 0x10000 && s) { - out.append(s); + auto arg = static_cast(args[n - 1]); + if (fmtSpec == "ws" || fmtSpec == "ls") { + const auto *ws = reinterpret_cast(arg); + if (ws && arg > 0x10000) { + while (*ws) { + out.push_back(static_cast(*ws > 0x7f ? '?' : *ws)); + ws++; + } + } } else { - int written = std::snprintf(tmp, sizeof(tmp), "%ld", static_cast(arg)); - if (written > 0) out.append(tmp, written); + char tmp[32]; + const char *s = reinterpret_cast(arg); + if (arg > 0x10000 && s) { + out.append(s); + } else { + int written = std::snprintf(tmp, sizeof(tmp), "%u", static_cast(args[n - 1])); + if (written > 0) out.append(tmp, written); + } } } else { - // Argument missing; emit "%N" verbatim so callers can tell. char tmp[8]; int written = std::snprintf(tmp, sizeof(tmp), "%%%u", n); if (written > 0) out.append(tmp, written); @@ -736,15 +743,17 @@ DWORD WINAPI FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, if (dwFlags & kIgnoreInserts) { formatted = std::move(tpl); } else { - // Caller may pass a pointer array (ARGUMENT_ARRAY) or a va_list. - // We treat both as a flat array of DWORD_PTR — good enough for - // the tool users (RC, LINK, NMAKE) that only substitute strings. - const DWORD_PTR *args = nullptr; - DWORD argCount = 99; // we have no reliable count; let inserts self-terminate + // The guest is 32-bit: arguments are 4-byte values. + // FORMAT_MESSAGE_ARGUMENT_ARRAY: Arguments is a uint32_t[] directly. + // Otherwise: Arguments is a va_list* (pointer to a char* on i386). + // Dereference once to get the actual argument array pointer. + const uint32_t *args = nullptr; + DWORD argCount = 99; if (dwFlags & kArgumentArray) { - args = reinterpret_cast(Arguments); + args = reinterpret_cast(Arguments); } else if (Arguments) { - args = reinterpret_cast(Arguments); + uint32_t vaListPtr = *reinterpret_cast(Arguments); + args = reinterpret_cast(static_cast(vaListPtr)); } formatted = applyMessageInserts(tpl, args, argCount, false); } diff --git a/dll/user32.cpp b/dll/user32.cpp index 0b23d35..55611fa 100644 --- a/dll/user32.cpp +++ b/dll/user32.cpp @@ -8,6 +8,7 @@ #include "resources.h" #include +#include namespace user32 { @@ -175,6 +176,20 @@ HWND WINAPI GetActiveWindow() { return NO_HANDLE; } +DWORD WINAPI CharUpperBuffW(LPWSTR lpsz, DWORD cchLength) { + DEBUG_LOG("CharUpperBuffW(%p, %lu)\n", lpsz, static_cast(cchLength)); + if (!lpsz) return 0; + for (DWORD i = 0; i < cchLength; i++) + lpsz[i] = static_cast(std::towupper(lpsz[i])); + return cchLength; +} + +LPSTR WINAPI CharNextA(LPCSTR lpsz) { + DEBUG_LOG("CharNextA(%p)\n", lpsz); + if (!lpsz || !*lpsz) return const_cast(lpsz); + return const_cast(lpsz + 1); +} + LONG WINAPI SendMessageA(HWND hWnd, UINT Msg, LONG wParam, LONG lParam) { // No-op stub. Real Windows returns 0 with ERROR_INVALID_WINDOW_HANDLE // when called with a NULL/invalid HWND. NT-era command-line tools (MC, @@ -189,6 +204,14 @@ LONG WINAPI SendMessageA(HWND hWnd, UINT Msg, LONG wParam, LONG lParam) { return 0; } +LONG WINAPI SendMessageW(HWND hWnd, UINT Msg, LONG wParam, LONG lParam) { + DEBUG_LOG("STUB: SendMessageW(hwnd=%p, msg=0x%x, w=0x%lx, l=0x%lx) -> 0\n", hWnd, Msg, + static_cast(wParam), static_cast(lParam)); + (void)hWnd; (void)Msg; (void)wParam; (void)lParam; + kernel32::setLastError(ERROR_INVALID_WINDOW_HANDLE); + return 0; +} + } // namespace user32 #include "user32_trampolines.h" diff --git a/dll/user32.h b/dll/user32.h index 288a3f7..37c66d4 100644 --- a/dll/user32.h +++ b/dll/user32.h @@ -12,5 +12,8 @@ HWINSTA WINAPI GetProcessWindowStation(); BOOL WINAPI GetUserObjectInformationA(HANDLE hObj, int nIndex, PVOID pvInfo, DWORD nLength, LPDWORD lpnLengthNeeded); HWND WINAPI GetActiveWindow(); LONG WINAPI SendMessageA(HWND hWnd, UINT Msg, LONG wParam, LONG lParam); +LONG WINAPI SendMessageW(HWND hWnd, UINT Msg, LONG wParam, LONG lParam); +DWORD WINAPI CharUpperBuffW(LPWSTR lpsz, DWORD cchLength); +LPSTR WINAPI CharNextA(LPCSTR lpsz); } // namespace user32