/* * PROJECT: ReactOS Console Utilities Library * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: Provides basic abstraction wrappers around CRT streams or * Win32 console API I/O functions, to deal with i18n + Unicode * related problems. * COPYRIGHT: Copyright 2017-2018 ReactOS Team * Copyright 2017-2018 Hermes Belusca-Maito */ /** * @file outstream.c * @ingroup ConUtils * * @brief Console I/O utility API -- Output **/ /* * Enable this define if you want to only use CRT functions to output * UNICODE stream to the console, as in the way explained by * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html */ /** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/ // #define USE_CRT /* FIXME: Temporary HACK before we cleanly support UNICODE functions */ #define UNICODE #define _UNICODE #ifdef USE_CRT #include #include #endif /* USE_CRT */ #include // limits.h // For MB_LEN_MAX #include #include #include #include // MAKEINTRESOURCEW, RT_STRING #include // Console APIs (only if kernel32 support included) #include /* PSEH for SEH Support */ #include #include "conutils.h" #include "stream.h" #include "stream_private.h" // Also known as: RC_STRING_MAX_SIZE, MAX_BUFFER_SIZE (some programs: // wlanconf, shutdown, set it to 5024), OUTPUT_BUFFER_SIZE (name given // in cmd/console.c), MAX_STRING_SIZE (name given in diskpart) or // MAX_MESSAGE_SIZE (set to 512 in shutdown). #define CON_RC_STRING_MAX_SIZE 4096 /** * @name ConWrite * Writes a counted string to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] szStr * Pointer to the counted string to write. * * @param[in] len * Length of the string pointed by @p szStr, specified * in number of characters. * * @return * Numbers of characters successfully written to @p Stream. * * @note * This function is used as an internal function. * Use the ConStreamWrite() function instead. * * @remark * Should be called with the stream locked. **/ INT __stdcall ConWrite( IN PCON_STREAM Stream, IN PCTCH szStr, IN DWORD len) { #ifndef USE_CRT DWORD TotalLen = len, dwNumBytes = 0; LPCVOID p; /* If we do not write anything, just return */ if (!szStr || len == 0) return 0; /* Check whether we are writing to a console */ // if (IsConsoleHandle(Stream->hHandle)) if (Stream->IsConsole) { // TODO: Check if (Stream->Mode == WideText or UTF16Text) ?? /* * This code is inspired from _cputws, in particular from the fact that, * according to MSDN: https://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx * the buffer size must be less than 64 KB. * * A similar code can be used for implementing _cputs too. */ DWORD cchWrite; TotalLen = len, dwNumBytes = 0; while (len > 0) { cchWrite = min(len, 65535 / sizeof(WCHAR)); // FIXME: Check return value! WriteConsole(Stream->hHandle, szStr, cchWrite, &dwNumBytes, NULL); szStr += cchWrite; len -= cchWrite; } return (INT)TotalLen; // FIXME: Really return the number of chars written! } /* * We are redirected and writing to a file or pipe instead of the console. * Convert the string from TCHARs to the desired output format, if the two differ. * * Implementation NOTE: * MultiByteToWideChar (resp. WideCharToMultiByte) are equivalent to * OemToCharBuffW (resp. CharToOemBuffW), but these latter functions * uselessly depend on user32.dll, while MultiByteToWideChar and * WideCharToMultiByte only need kernel32.dll. */ if ((Stream->Mode == WideText) || (Stream->Mode == UTF16Text)) { #ifndef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16 /* Convert from the current process/thread's code page to UTF-16 */ PWCHAR buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR)); if (!buffer) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } len = (DWORD)MultiByteToWideChar(CP_THREAD_ACP, // CP_ACP, CP_OEMCP 0, szStr, (INT)len, buffer, (INT)len); szStr = (PVOID)buffer; #else /* * Do not perform any conversion since we are already in UTF-16, * that is the same encoding as the stream. */ #endif /* * Find any newline character in the buffer, * write the part BEFORE the newline, then emit * a carriage-return + newline sequence and finally * write the remaining part of the buffer. * * This fixes output in files and serial console. */ while (len > 0) { /* Loop until we find a newline character */ p = szStr; while (len > 0 && *(PCWCH)p != L'\n') { /* Advance one character */ p = (LPCVOID)((PCWCH)p + 1); --len; } /* Write everything up to \n */ dwNumBytes = ((PCWCH)p - (PCWCH)szStr) * sizeof(WCHAR); WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL); /* * If we hit a newline and the previous character is not a carriage-return, * emit a carriage-return + newline sequence, otherwise just emit the newline. */ if (len > 0 && *(PCWCH)p == L'\n') { if (p == (LPCVOID)szStr || (p > (LPCVOID)szStr && *((PCWCH)p - 1) != L'\r')) WriteFile(Stream->hHandle, L"\r\n", 2 * sizeof(WCHAR), &dwNumBytes, NULL); else WriteFile(Stream->hHandle, L"\n", sizeof(WCHAR), &dwNumBytes, NULL); /* Skip \n */ p = (LPCVOID)((PCWCH)p + 1); --len; } szStr = p; } #ifndef _UNICODE HeapFree(GetProcessHeap(), 0, buffer); #endif } else if ((Stream->Mode == UTF8Text) || (Stream->Mode == AnsiText)) { UINT CodePage; PCHAR buffer; /* * Resolve the current code page if it has not been assigned yet * (we do this only if the stream is in ANSI mode; in UTF8 mode * the code page is always set to CP_UTF8). Otherwise use the * current stream's code page. */ if (/*(Stream->Mode == AnsiText) &&*/ (Stream->CodePage == INVALID_CP)) CodePage = GetConsoleOutputCP(); // CP_ACP, CP_OEMCP else CodePage = Stream->CodePage; #ifdef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16 /* Convert from UTF-16 to either UTF-8 or ANSI, using the stream code page */ // NOTE: MB_LEN_MAX defined either in limits.h or in stdlib.h . buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * MB_LEN_MAX); if (!buffer) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return 0; } len = WideCharToMultiByte(CodePage, 0, szStr, len, buffer, len * MB_LEN_MAX, NULL, NULL); szStr = (PVOID)buffer; #else /* * Convert from the current process/thread's code page to either * UTF-8 or ANSI, using the stream code page. * We need to perform a double conversion, by going through UTF-16. */ // TODO! #error "Need to implement double conversion!" #endif /* * Find any newline character in the buffer, * write the part BEFORE the newline, then emit * a carriage-return + newline sequence and finally * write the remaining part of the buffer. * * This fixes output in files and serial console. */ while (len > 0) { /* Loop until we find a newline character */ p = szStr; while (len > 0 && *(PCCH)p != '\n') { /* Advance one character */ p = (LPCVOID)((PCCH)p + 1); --len; } /* Write everything up to \n */ dwNumBytes = ((PCCH)p - (PCCH)szStr) * sizeof(CHAR); WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL); /* * If we hit a newline and the previous character is not a carriage-return, * emit a carriage-return + newline sequence, otherwise just emit the newline. */ if (len > 0 && *(PCCH)p == '\n') { if (p == (LPCVOID)szStr || (p > (LPCVOID)szStr && *((PCCH)p - 1) != '\r')) WriteFile(Stream->hHandle, "\r\n", 2, &dwNumBytes, NULL); else WriteFile(Stream->hHandle, "\n", 1, &dwNumBytes, NULL); /* Skip \n */ p = (LPCVOID)((PCCH)p + 1); --len; } szStr = p; } #ifdef _UNICODE HeapFree(GetProcessHeap(), 0, buffer); #else // TODO! #endif } else // if (Stream->Mode == Binary) { /* Directly output the string */ WriteFile(Stream->hHandle, szStr, len, &dwNumBytes, NULL); } // FIXME! return (INT)TotalLen; #else /* defined(USE_CRT) */ DWORD total = len; DWORD written = 0; /* If we do not write anything, just return */ if (!szStr || len == 0) return 0; #if 1 /* * There is no "counted" printf-to-stream or puts-like function, therefore * we use this trick to output the counted string to the stream. */ while (1) { written = fwprintf(Stream->fStream, L"%.*s", total, szStr); if (written < total) { /* * Some embedded NULL or special character * was encountered, print it apart. */ if (written == 0) { fputwc(*szStr, Stream->fStream); written++; } szStr += written; total -= written; } else { break; } } return (INT)len; #else /* ANSI text or Binary output only */ _setmode(_fileno(Stream->fStream), _O_TEXT); // _O_BINARY return fwrite(szStr, sizeof(*szStr), len, Stream->fStream); #endif #endif /* defined(USE_CRT) */ } #define CON_STREAM_WRITE_CALL(Stream, Str, Len) \ (Stream)->WriteFunc((Stream), (Str), (Len)) /* Lock the stream only in non-USE_CRT mode (otherwise use the CRT stream lock) */ #ifndef USE_CRT #define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \ do { \ EnterCriticalSection(&(Stream)->Lock); \ (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ LeaveCriticalSection(&(Stream)->Lock); \ } while(0) #define CON_STREAM_WRITE(Stream, Str, Len) \ do { \ EnterCriticalSection(&(Stream)->Lock); \ CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ LeaveCriticalSection(&(Stream)->Lock); \ } while(0) #else #define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \ do { \ (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \ } while(0) #define CON_STREAM_WRITE(Stream, Str, Len) \ CON_STREAM_WRITE_CALL((Stream), (Str), (Len)) #endif /** * @name ConStreamWrite * Writes a counted string to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] szStr * Pointer to the counted string to write. * * @param[in] len * Length of the string pointed by @p szStr, specified * in number of characters. * * @return * Numbers of characters successfully written to @p Stream. **/ INT ConStreamWrite( IN PCON_STREAM Stream, IN PCTCH szStr, IN DWORD len) { INT Len; CON_STREAM_WRITE2(Stream, szStr, len, Len); return Len; } /** * @name ConPuts * Writes a NULL-terminated string to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] szStr * Pointer to the NULL-terminated string to write. * * @return * Numbers of characters successfully written to @p Stream. * * @remark * Contrary to the CRT puts() function, ConPuts() does not append * a terminating new-line character. In this way it behaves more like * the CRT fputs() function. **/ INT ConPuts( IN PCON_STREAM Stream, IN PCWSTR szStr) { INT Len; Len = wcslen(szStr); CON_STREAM_WRITE2(Stream, szStr, Len, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; return Len; } /** * @name ConPrintfV * Formats and writes a NULL-terminated string to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] szStr * Pointer to the NULL-terminated format string, that follows the same * specifications as the @a szStr format string in ConPrintf(). * * @param[in] args * Parameter describing a variable number of arguments, * initialized with va_start(), that can be expected by the function, * depending on the @p szStr format string. Each argument is used to * replace a format specifier in the format string. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), printf(), vprintf() **/ INT ConPrintfV( IN PCON_STREAM Stream, IN PCWSTR szStr, IN va_list args) { INT Len; WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; // Len = vfwprintf(Stream->fStream, szStr, args); // vfprintf for direct ANSI /* * Re-use szStr as the pointer to end-of-string, so as * to compute the string length instead of calling wcslen(). */ // StringCchVPrintfW(bufSrc, ARRAYSIZE(bufSrc), szStr, args); // Len = wcslen(bufSrc); StringCchVPrintfExW(bufSrc, ARRAYSIZE(bufSrc), (PWSTR*)&szStr, NULL, 0, szStr, args); Len = szStr - bufSrc; CON_STREAM_WRITE2(Stream, bufSrc, Len, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; return Len; } /** * @name ConPrintf * Formats and writes a NULL-terminated string to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] szStr * Pointer to the NULL-terminated format string, that follows the same * specifications as the @a format string in printf(). This string can * optionally contain embedded format specifiers that are * replaced by the values specified in subsequent additional arguments * and formatted as requested. * * @param[in] ... * Additional arguments that can be expected by the function, depending * on the @p szStr format string. Each argument is used to replace a * format specifier in the format string. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintfV(), printf(), vprintf() **/ INT __cdecl ConPrintf( IN PCON_STREAM Stream, IN PCWSTR szStr, ...) { INT Len; va_list args; // Len = vfwprintf(Stream->fStream, szMsgBuf, args); // vfprintf for direct ANSI // StringCchPrintfW va_start(args, szStr); Len = ConPrintfV(Stream, szStr, args); va_end(args); return Len; } /** * @name ConResPutsEx * Writes a string resource to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] hInstance * Optional handle to an instance of the module whose executable file * contains the string resource. Can be set to NULL to get the handle * to the application itself. * * @param[in] uID * The identifier of the string to be written. * * @param[in] LanguageId * The language identifier of the resource. If this parameter is * MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), the current language * associated with the calling thread is used. To specify a language other * than the current language, use the @c MAKELANGID macro to create this * parameter. * * @return * Numbers of characters successfully written to @p Stream. * * @remark * Similarly to ConPuts(), no terminating new-line character is appended. * * @see ConPuts(), ConResPuts() **/ INT ConResPutsEx( IN PCON_STREAM Stream, IN HINSTANCE hInstance OPTIONAL, IN UINT uID, IN LANGID LanguageId) { INT Len; PWCHAR szStr = NULL; Len = K32LoadStringExW(hInstance, uID, LanguageId, (PWSTR)&szStr, 0); if (szStr && Len) // Len = ConPuts(Stream, szStr); CON_STREAM_WRITE2(Stream, szStr, Len, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; return Len; } /** * @name ConResPuts * Writes a string resource contained in the current application * to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] uID * The identifier of the string to be written. * * @return * Numbers of characters successfully written to @p Stream. * * @remark * Similarly to ConPuts(), no terminating new-line character is appended. * * @see ConPuts(), ConResPutsEx() **/ INT ConResPuts( IN PCON_STREAM Stream, IN UINT uID) { return ConResPutsEx(Stream, NULL /*GetModuleHandleW(NULL)*/, uID, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); } /** * @name ConResPrintfExV * Formats and writes a string resource to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] hInstance * Optional handle to an instance of the module whose executable file * contains the string resource. Can be set to NULL to get the handle * to the application itself. * * @param[in] uID * The identifier of the format string. The format string follows the * same specifications as the @a szStr format string in ConPrintf(). * * @param[in] LanguageId * The language identifier of the resource. If this parameter is * MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), the current language * associated with the calling thread is used. To specify a language other * than the current language, use the @c MAKELANGID macro to create this * parameter. * * @param[in] args * Parameter describing a variable number of arguments, * initialized with va_start(), that can be expected by the function, * depending on the @p szStr format string. Each argument is used to * replace a format specifier in the format string. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintfEx(), ConResPrintfV(), ConResPrintf() **/ INT ConResPrintfExV( IN PCON_STREAM Stream, IN HINSTANCE hInstance OPTIONAL, IN UINT uID, IN LANGID LanguageId, IN va_list args) { INT Len; WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; // NOTE: We may use the special behaviour where nBufMaxSize == 0 Len = K32LoadStringExW(hInstance, uID, LanguageId, bufSrc, ARRAYSIZE(bufSrc)); if (Len) Len = ConPrintfV(Stream, bufSrc, args); return Len; } /** * @name ConResPrintfV * Formats and writes a string resource contained in the * current application to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] uID * The identifier of the format string. The format string follows the * same specifications as the @a szStr format string in ConPrintf(). * * @param[in] args * Parameter describing a variable number of arguments, * initialized with va_start(), that can be expected by the function, * depending on the @p szStr format string. Each argument is used to * replace a format specifier in the format string. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintfExV(), ConResPrintfEx(), ConResPrintf() **/ INT ConResPrintfV( IN PCON_STREAM Stream, IN UINT uID, IN va_list args) { return ConResPrintfExV(Stream, NULL /*GetModuleHandleW(NULL)*/, uID, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), args); } /** * @name ConResPrintfEx * Formats and writes a string resource to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] hInstance * Optional handle to an instance of the module whose executable file * contains the string resource. Can be set to NULL to get the handle * to the application itself. * * @param[in] uID * The identifier of the format string. The format string follows the * same specifications as the @a szStr format string in ConPrintf(). * * @param[in] LanguageId * The language identifier of the resource. If this parameter is * MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), the current language * associated with the calling thread is used. To specify a language other * than the current language, use the @c MAKELANGID macro to create this * parameter. * * @param[in] ... * Additional arguments that can be expected by the function, depending * on the @p szStr format string. Each argument is used to replace a * format specifier in the format string. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintfExV(), ConResPrintfV(), ConResPrintf() **/ INT __cdecl ConResPrintfEx( IN PCON_STREAM Stream, IN HINSTANCE hInstance OPTIONAL, IN UINT uID, IN LANGID LanguageId, ...) { INT Len; va_list args; va_start(args, LanguageId); Len = ConResPrintfExV(Stream, hInstance, uID, LanguageId, args); va_end(args); return Len; } /** * @name ConResPrintf * Formats and writes a string resource contained in the * current application to a stream. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] uID * The identifier of the format string. The format string follows the * same specifications as the @a szStr format string in ConPrintf(). * * @param[in] ... * Additional arguments that can be expected by the function, depending * on the @p szStr format string. Each argument is used to replace a * format specifier in the format string. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintfExV(), ConResPrintfEx(), ConResPrintfV() **/ INT __cdecl ConResPrintf( IN PCON_STREAM Stream, IN UINT uID, ...) { INT Len; va_list args; va_start(args, uID); Len = ConResPrintfV(Stream, uID, args); va_end(args); return Len; } /** * @name ConMsgPuts * Writes a message string to a stream without formatting. The function * requires a message definition as input. The message definition can come * from a buffer passed to the function. It can come from a message table * resource in an already-loaded module, or the caller can ask the function * to search the system's message table resource(s) for the message definition. * Please refer to the Win32 FormatMessage() function for more details. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] dwFlags * The formatting options, and how to interpret the @p lpSource parameter. * See FormatMessage() for more details. The @b FORMAT_MESSAGE_ALLOCATE_BUFFER * and @b FORMAT_MESSAGE_ARGUMENT_ARRAY flags are always ignored. * The function implicitly uses the @b FORMAT_MESSAGE_IGNORE_INSERTS flag * to implement its behaviour. * * @param[in] lpSource * The location of the message definition. The type of this parameter * depends upon the settings in the @p dwFlags parameter. * * @param[in] dwMessageId * The message identifier for the requested message. This parameter * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. * * @param[in] dwLanguageId * The language identifier for the requested message. This parameter * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. * * @return * Numbers of characters successfully written to @p Stream. * * @remark * Similarly to ConPuts(), no terminating new-line character is appended. * * @see ConPuts(), ConResPuts() and associated functions, * FormatMessage() (on MSDN) **/ INT ConMsgPuts( IN PCON_STREAM Stream, IN DWORD dwFlags, IN LPCVOID lpSource OPTIONAL, IN DWORD dwMessageId, IN DWORD dwLanguageId) { INT Len; DWORD dwLength = 0; LPWSTR lpMsgBuf = NULL; /* * Sanitize dwFlags. This version always ignores explicitly the inserts * as we emulate the behaviour of the (f)puts function. */ dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage. dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; /* * Retrieve the message string without appending extra newlines. * Wrap in SEH to protect from invalid string parameters. */ _SEH2_TRY { dwLength = FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, (LPWSTR)&lpMsgBuf, 0, NULL); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { } _SEH2_END; Len = (INT)dwLength; if (!lpMsgBuf) { // ASSERT(dwLength == 0); } else { // ASSERT(dwLength != 0); /* lpMsgBuf is NULL-terminated by FormatMessage */ // Len = ConPuts(Stream, lpMsgBuf); CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; /* Free the buffer allocated by FormatMessage */ LocalFree(lpMsgBuf); } return Len; } /** * @name ConMsgPrintf2V * Formats and writes a message string to a stream. * * @remark For internal use only. * * @see ConMsgPrintfV() **/ INT ConMsgPrintf2V( IN PCON_STREAM Stream, IN DWORD dwFlags, IN LPCVOID lpSource OPTIONAL, IN DWORD dwMessageId, IN DWORD dwLanguageId, IN va_list args) { INT Len; DWORD dwLength = 0; LPWSTR lpMsgBuf = NULL; /* * Sanitize dwFlags. This version always ignores explicitly the inserts. * The string that we will return to the user will not be pre-formatted. */ dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage. dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; /* * Retrieve the message string without appending extra newlines. * Wrap in SEH to protect from invalid string parameters. */ _SEH2_TRY { dwLength = FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, (LPWSTR)&lpMsgBuf, 0, NULL); } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { } _SEH2_END; Len = (INT)dwLength; if (!lpMsgBuf) { // ASSERT(dwLength == 0); } else { // ASSERT(dwLength != 0); /* lpMsgBuf is NULL-terminated by FormatMessage */ Len = ConPrintfV(Stream, lpMsgBuf, args); // CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; /* Free the buffer allocated by FormatMessage */ LocalFree(lpMsgBuf); } return Len; } /** * @name ConMsgPrintfV * Formats and writes a message string to a stream. The function requires * a message definition as input. The message definition can come from a * buffer passed to the function. It can come from a message table resource * in an already-loaded module, or the caller can ask the function to search * the system's message table resource(s) for the message definition. * Please refer to the Win32 FormatMessage() function for more details. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] dwFlags * The formatting options, and how to interpret the @p lpSource parameter. * See FormatMessage() for more details. * The @b FORMAT_MESSAGE_ALLOCATE_BUFFER flag is always ignored. * * @param[in] lpSource * The location of the message definition. The type of this parameter * depends upon the settings in the @p dwFlags parameter. * * @param[in] dwMessageId * The message identifier for the requested message. This parameter * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. * * @param[in] dwLanguageId * The language identifier for the requested message. This parameter * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. * * @param[in] Arguments * Optional pointer to an array of values describing a variable number of * arguments, depending on the message string. Each argument is used to * replace an insert sequence in the message string. * By default, the @p Arguments parameter is of type @c va_list*, initialized * with va_start(). The state of the @c va_list argument is undefined upon * return from the function. To use the @c va_list again, destroy the variable * argument list pointer using va_end() and reinitialize it with va_start(). * If you do not have a pointer of type @c va_list*, then specify the * @b FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array * of @c DWORD_PTR values; those values are input to the message formatted * as the insert values. Each insert must have a corresponding element in * the array. * * @remark * Contrary to printf(), ConPrintf(), ConResPrintf() and associated functions, * the ConMsg* functions work on format strings that contain insert sequences. * These sequences extend the standard format specifiers as they * allow to specify an insert number referring which precise value * given in arguments to use. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintf() and associated functions, ConMsgPrintf(), * FormatMessage() (on MSDN) **/ INT ConMsgPrintfV( IN PCON_STREAM Stream, IN DWORD dwFlags, IN LPCVOID lpSource OPTIONAL, IN DWORD dwMessageId, IN DWORD dwLanguageId, IN va_list *Arguments OPTIONAL) { INT Len; DWORD dwLength = 0; LPWSTR lpMsgBuf = NULL; /* Sanitize dwFlags */ dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. /* * Retrieve the message string without appending extra newlines. * Use the "safe" FormatMessage version (SEH-protected) to protect * from invalid string parameters. */ dwLength = FormatMessageSafeW(dwFlags, lpSource, dwMessageId, dwLanguageId, (LPWSTR)&lpMsgBuf, 0, Arguments); Len = (INT)dwLength; if (!lpMsgBuf) { // ASSERT(dwLength == 0); } else { // ASSERT(dwLength != 0); CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; /* Free the buffer allocated by FormatMessage */ LocalFree(lpMsgBuf); } return Len; } /** * @name ConMsgPrintf * Formats and writes a message string to a stream. The function requires * a message definition as input. The message definition can come from a * buffer passed to the function. It can come from a message table resource * in an already-loaded module, or the caller can ask the function to search * the system's message table resource(s) for the message definition. * Please refer to the Win32 FormatMessage() function for more details. * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] dwFlags * The formatting options, and how to interpret the @p lpSource parameter. * See FormatMessage() for more details. The @b FORMAT_MESSAGE_ALLOCATE_BUFFER * and @b FORMAT_MESSAGE_ARGUMENT_ARRAY flags are always ignored. * * @param[in] lpSource * The location of the message definition. The type of this parameter * depends upon the settings in the @p dwFlags parameter. * * @param[in] dwMessageId * The message identifier for the requested message. This parameter * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. * * @param[in] dwLanguageId * The language identifier for the requested message. This parameter * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. * * @param[in] ... * Additional arguments that can be expected by the function, depending * on the message string. Each argument is used to replace an * insert sequence in the message string. * * @remark * Contrary to printf(), ConPrintf(), ConResPrintf() and associated functions, * the ConMsg* functions work on format strings that contain insert sequences. * These sequences extend the standard format specifiers as they * allow to specify an insert number referring which precise value * given in arguments to use. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintf() and associated functions, ConMsgPrintfV(), * FormatMessage() (on MSDN) **/ INT __cdecl ConMsgPrintf( IN PCON_STREAM Stream, IN DWORD dwFlags, IN LPCVOID lpSource OPTIONAL, IN DWORD dwMessageId, IN DWORD dwLanguageId, ...) { INT Len; va_list args; /* Sanitize dwFlags */ dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; va_start(args, dwLanguageId); Len = ConMsgPrintfV(Stream, dwFlags, lpSource, dwMessageId, dwLanguageId, &args); va_end(args); return Len; } /** * @name ConResMsgPrintfExV * Formats and writes a message string to a stream. The function requires * a message definition as input. Contrary to the ConMsg* or the Win32 * FormatMessage() functions, the message definition comes from a resource * string table, much like the strings for ConResPrintf(), but is formatted * according to the rules of ConMsgPrintf(). * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] hInstance * Optional handle to an instance of the module whose executable file * contains the string resource. Can be set to NULL to get the handle * to the application itself. * * @param[in] dwFlags * The formatting options, see FormatMessage() for more details. * The only valid flags are @b FORMAT_MESSAGE_ARGUMENT_ARRAY, * @b FORMAT_MESSAGE_IGNORE_INSERTS and @b FORMAT_MESSAGE_MAX_WIDTH_MASK. * All the other flags are internally overridden by the function * to implement its behaviour. * * @param[in] uID * The identifier of the message string. The format string follows the * same specifications as the @a lpSource format string in ConMsgPrintf(). * * @param[in] LanguageId * The language identifier of the resource. If this parameter is * MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), the current language * associated with the calling thread is used. To specify a language other * than the current language, use the @c MAKELANGID macro to create this * parameter. * * @param[in] Arguments * Optional pointer to an array of values describing a variable number of * arguments, depending on the message string. Each argument is used to * replace an insert sequence in the message string. * By default, the @p Arguments parameter is of type @c va_list*, initialized * with va_start(). The state of the @c va_list argument is undefined upon * return from the function. To use the @c va_list again, destroy the variable * argument list pointer using va_end() and reinitialize it with va_start(). * If you do not have a pointer of type @c va_list*, then specify the * @b FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array * of @c DWORD_PTR values; those values are input to the message formatted * as the insert values. Each insert must have a corresponding element in * the array. * * @remark * Contrary to printf(), ConPrintf(), ConResPrintf() and associated functions, * the ConMsg* functions work on format strings that contain insert sequences. * These sequences extend the standard format specifiers as they * allow to specify an insert number referring which precise value * given in arguments to use. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintf() and associated functions, ConMsgPrintf(), * FormatMessage() (on MSDN) **/ INT ConResMsgPrintfExV( IN PCON_STREAM Stream, IN HINSTANCE hInstance OPTIONAL, IN DWORD dwFlags, IN UINT uID, IN LANGID LanguageId, IN va_list *Arguments OPTIONAL) { INT Len; DWORD dwLength = 0; LPWSTR lpMsgBuf = NULL; WCHAR bufSrc[CON_RC_STRING_MAX_SIZE]; /* Retrieve the string from the resource string table */ // NOTE: We may use the special behaviour where nBufMaxSize == 0 Len = K32LoadStringExW(hInstance, uID, LanguageId, bufSrc, ARRAYSIZE(bufSrc)); if (Len == 0) return Len; /* Sanitize dwFlags */ dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer. /* The string has already been manually loaded */ dwFlags &= ~(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM); dwFlags |= FORMAT_MESSAGE_FROM_STRING; /* * Retrieve the message string without appending extra newlines. * Use the "safe" FormatMessage version (SEH-protected) to protect * from invalid string parameters. */ dwLength = FormatMessageSafeW(dwFlags, bufSrc, 0, 0, (LPWSTR)&lpMsgBuf, 0, Arguments); Len = (INT)dwLength; if (!lpMsgBuf) { // ASSERT(dwLength == 0); } else { // ASSERT(dwLength != 0); CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len); /* Fixup returned length in case of errors */ if (Len < 0) Len = 0; /* Free the buffer allocated by FormatMessage */ LocalFree(lpMsgBuf); } return Len; } /** * @name ConResMsgPrintfV * Formats and writes a message string to a stream. The function requires * a message definition as input. Contrary to the ConMsg* or the Win32 * FormatMessage() functions, the message definition comes from a resource * string table, much like the strings for ConResPrintf(), but is formatted * according to the rules of ConMsgPrintf(). * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] dwFlags * The formatting options, see FormatMessage() for more details. * The only valid flags are @b FORMAT_MESSAGE_ARGUMENT_ARRAY, * @b FORMAT_MESSAGE_IGNORE_INSERTS and @b FORMAT_MESSAGE_MAX_WIDTH_MASK. * All the other flags are internally overridden by the function * to implement its behaviour. * * @param[in] uID * The identifier of the message string. The format string follows the * same specifications as the @a lpSource format string in ConMsgPrintf(). * * @param[in] Arguments * Optional pointer to an array of values describing a variable number of * arguments, depending on the message string. Each argument is used to * replace an insert sequence in the message string. * By default, the @p Arguments parameter is of type @c va_list*, initialized * with va_start(). The state of the @c va_list argument is undefined upon * return from the function. To use the @c va_list again, destroy the variable * argument list pointer using va_end() and reinitialize it with va_start(). * If you do not have a pointer of type @c va_list*, then specify the * @b FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array * of @c DWORD_PTR values; those values are input to the message formatted * as the insert values. Each insert must have a corresponding element in * the array. * * @remark * Contrary to printf(), ConPrintf(), ConResPrintf() and associated functions, * the ConMsg* functions work on format strings that contain insert sequences. * These sequences extend the standard format specifiers as they * allow to specify an insert number referring which precise value * given in arguments to use. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintf() and associated functions, ConMsgPrintf(), * FormatMessage() (on MSDN) **/ INT ConResMsgPrintfV( IN PCON_STREAM Stream, IN DWORD dwFlags, IN UINT uID, IN va_list *Arguments OPTIONAL) { return ConResMsgPrintfExV(Stream, NULL /*GetModuleHandleW(NULL)*/, dwFlags, uID, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), Arguments); } /** * @name ConResMsgPrintfEx * Formats and writes a message string to a stream. The function requires * a message definition as input. Contrary to the ConMsg* or the Win32 * FormatMessage() functions, the message definition comes from a resource * string table, much like the strings for ConResPrintf(), but is formatted * according to the rules of ConMsgPrintf(). * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] hInstance * Optional handle to an instance of the module whose executable file * contains the string resource. Can be set to NULL to get the handle * to the application itself. * * @param[in] dwFlags * The formatting options, see FormatMessage() for more details. * The only valid flags are @b FORMAT_MESSAGE_IGNORE_INSERTS and * @b FORMAT_MESSAGE_MAX_WIDTH_MASK. All the other flags are internally * overridden by the function to implement its behaviour. * * @param[in] uID * The identifier of the message string. The format string follows the * same specifications as the @a lpSource format string in ConMsgPrintf(). * * @param[in] LanguageId * The language identifier of the resource. If this parameter is * MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), the current language * associated with the calling thread is used. To specify a language other * than the current language, use the @c MAKELANGID macro to create this * parameter. * * @param[in] ... * Additional arguments that can be expected by the function, depending * on the message string. Each argument is used to replace an * insert sequence in the message string. * * @remark * Contrary to printf(), ConPrintf(), ConResPrintf() and associated functions, * the ConMsg* functions work on format strings that contain insert sequences. * These sequences extend the standard format specifiers as they * allow to specify an insert number referring which precise value * given in arguments to use. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintf() and associated functions, ConMsgPrintf(), * FormatMessage() (on MSDN) **/ INT __cdecl ConResMsgPrintfEx( IN PCON_STREAM Stream, IN HINSTANCE hInstance OPTIONAL, IN DWORD dwFlags, IN UINT uID, IN LANGID LanguageId, ...) { INT Len; va_list args; /* Sanitize dwFlags */ dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; va_start(args, LanguageId); Len = ConResMsgPrintfExV(Stream, hInstance, dwFlags, uID, LanguageId, &args); va_end(args); return Len; } /** * @name ConResMsgPrintf * Formats and writes a message string to a stream. The function requires * a message definition as input. Contrary to the ConMsg* or the Win32 * FormatMessage() functions, the message definition comes from a resource * string table, much like the strings for ConResPrintf(), but is formatted * according to the rules of ConMsgPrintf(). * * @param[in] Stream * Stream to which the write operation is issued. * * @param[in] dwFlags * The formatting options, see FormatMessage() for more details. * The only valid flags are @b FORMAT_MESSAGE_IGNORE_INSERTS and * @b FORMAT_MESSAGE_MAX_WIDTH_MASK. All the other flags are internally * overridden by the function to implement its behaviour. * * @param[in] uID * The identifier of the message string. The format string follows the * same specifications as the @a lpSource format string in ConMsgPrintf(). * * @param[in] ... * Additional arguments that can be expected by the function, depending * on the message string. Each argument is used to replace an * insert sequence in the message string. * * @remark * Contrary to printf(), ConPrintf(), ConResPrintf() and associated functions, * the ConMsg* functions work on format strings that contain insert sequences. * These sequences extend the standard format specifiers as they * allow to specify an insert number referring which precise value * given in arguments to use. * * @return * Numbers of characters successfully written to @p Stream. * * @see ConPrintf(), ConResPrintf() and associated functions, ConMsgPrintf(), * FormatMessage() (on MSDN) **/ INT __cdecl ConResMsgPrintf( IN PCON_STREAM Stream, IN DWORD dwFlags, IN UINT uID, ...) { INT Len; va_list args; /* Sanitize dwFlags */ dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; va_start(args, uID); Len = ConResMsgPrintfV(Stream, dwFlags, uID, &args); va_end(args); return Len; } VOID ConClearLine(IN PCON_STREAM Stream) { HANDLE hOutput = ConStreamGetOSHandle(Stream); /* * Erase the full line where the cursor is, and move * the cursor back to the beginning of the line. */ if (IsConsoleHandle(hOutput)) { CONSOLE_SCREEN_BUFFER_INFO csbi; DWORD dwWritten; GetConsoleScreenBufferInfo(hOutput, &csbi); csbi.dwCursorPosition.X = 0; // csbi.dwCursorPosition.Y; FillConsoleOutputCharacterW(hOutput, L' ', csbi.dwSize.X, csbi.dwCursorPosition, &dwWritten); SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition); } else if (IsTTYHandle(hOutput)) { ConPuts(Stream, L"\x1B[2K\x1B[1G"); // FIXME: Just use WriteFile } // else, do nothing for files } /* EOF */