diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index 91db64c..7cb2416 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,240 @@ 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 uint32_t *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; + } + i++; + if (i >= tpl.size()) { out.push_back('%'); break; } + if (!std::isdigit(static_cast(tpl[i]))) { + char esc = tpl[i++]; + switch (esc) { + case '0': return out; + 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'); + } + // 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++; + fmtSpec = tpl.substr(fmtStart, i - fmtStart); + if (i < tpl.size()) i++; + } + if (n >= 1 && n <= argCount && args) { + 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 { + 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 { + 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 { + // 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); + } else if (Arguments) { + uint32_t vaListPtr = *reinterpret_cast(Arguments); + args = reinterpret_cast(static_cast(vaListPtr)); + } + 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..55611fa 100644 --- a/dll/user32.cpp +++ b/dll/user32.cpp @@ -8,6 +8,7 @@ #include "resources.h" #include +#include namespace user32 { @@ -175,6 +176,42 @@ 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, + // 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; +} + +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 2fdd833..37c66d4 100644 --- a/dll/user32.h +++ b/dll/user32.h @@ -11,5 +11,9 @@ 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); +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 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