xref: /reactos/sdk/lib/conutils/stream.c (revision c2c66aff)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Console Utilities Library
4  * FILE:            sdk/lib/conutils/stream.c
5  * PURPOSE:         Provides basic abstraction wrappers around CRT streams or
6  *                  Win32 console API I/O functions, to deal with i18n + Unicode
7  *                  related problems.
8  * PROGRAMMERS:     - Hermes Belusca-Maito (for the library);
9  *                  - All programmers who wrote the different console applications
10  *                    from which I took those functions and improved them.
11  */
12 
13 /*
14  * Enable this define if you want to only use CRT functions to output
15  * UNICODE stream to the console, as in the way explained by
16  * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
17  */
18 /** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/
19 // #define USE_CRT
20 
21 /* FIXME: Temporary HACK before we cleanly support UNICODE functions */
22 #define UNICODE
23 #define _UNICODE
24 
25 #ifdef USE_CRT
26 #include <fcntl.h>
27 #include <io.h>
28 #endif /* USE_CRT */
29 
30 #include <stdlib.h> // limits.h // For MB_LEN_MAX
31 
32 #include <windef.h>
33 #include <winbase.h>
34 #include <winnls.h>
35 #include <winuser.h> // MAKEINTRESOURCEW, RT_STRING
36 #include <wincon.h>  // Console APIs (only if kernel32 support included)
37 #include <strsafe.h>
38 
39 /* PSEH for SEH Support */
40 #include <pseh/pseh2.h>
41 
42 #include "conutils.h"
43 #include "stream.h"
44 
45 
46 // #define RC_STRING_MAX_SIZE  4096
47 #define CON_RC_STRING_MAX_SIZE  4096
48 // #define MAX_BUFFER_SIZE     4096    // Some programs (wlanconf, shutdown) set it to 5024
49 // #define OUTPUT_BUFFER_SIZE  4096    // Name given in cmd/console.c
50 // MAX_STRING_SIZE  // Name given in diskpart
51 
52 // #define MAX_MESSAGE_SIZE    512     // See shutdown...
53 
54 
55 /*
56  * Console I/O streams
57  */
58 
59 typedef struct _CON_STREAM
60 {
61     CON_WRITE_FUNC WriteFunc;
62 
63 #ifdef USE_CRT
64     FILE* fStream;
65 #else
66     BOOL IsInitialized;
67     CRITICAL_SECTION Lock;
68 
69     HANDLE hHandle;
70 
71     /*
72      * TRUE if 'hHandle' refers to a console, in which case I/O UTF-16
73      * is directly used. If 'hHandle' refers to a file or a pipe, the
74      * 'Mode' flag is used.
75      */
76     BOOL IsConsole;
77 
78     /*
79      * The 'Mode' flag is used to know the translation mode
80      * when 'hHandle' refers to a file or a pipe.
81      */
82     CON_STREAM_MODE Mode;
83     UINT CodePage;  // Used to convert UTF-16 text to some ANSI codepage.
84 #endif /* defined(USE_CRT) */
85 } CON_STREAM, *PCON_STREAM;
86 
87 /*
88  * Standard console streams, initialized by
89  * calls to ConStreamInit/ConInitStdStreams.
90  */
91 #if 0 // FIXME!
92 CON_STREAM StdStreams[3] =
93 {
94     {0}, // StdIn
95     {0}, // StdOut
96     {0}, // StdErr
97 };
98 #else
99 CON_STREAM csStdIn;
100 CON_STREAM csStdOut;
101 CON_STREAM csStdErr;
102 #endif
103 
104 
105 /* Stream translation modes */
106 #ifdef USE_CRT
107 /* Lookup table to convert CON_STREAM_MODE to CRT mode */
108 static int ConToCRTMode[] =
109 {
110     _O_BINARY,  // Binary    (untranslated)
111     _O_TEXT,    // AnsiText  (translated)
112     _O_WTEXT,   // WideText  (UTF16 with BOM; translated)
113     _O_U16TEXT, // UTF16Text (UTF16 without BOM; translated)
114     _O_U8TEXT,  // UTF8Text  (UTF8  without BOM; translated)
115 };
116 #endif
117 
118 #ifdef USE_CRT
119 
120 /*
121  * See http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
122  * and http://archives.miloush.net/michkap/archive/2009/08/14/9869928.html
123  * for more details.
124  */
125 
126 // NOTE: May the translated mode be cached somehow?
127 // NOTE2: We may also call IsConsoleHandle to directly set the mode to
128 //        _O_U16TEXT if it's ok??
129 // NOTE3: _setmode returns the previous mode, or -1 if failure.
130 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage)    \
131 do { \
132     fflush((Stream)->fStream); \
133     if ((Mode) < ARRAYSIZE(ConToCRTMode))   \
134         _setmode(_fileno((Stream)->fStream), ConToCRTMode[(Mode)]); \
135     else \
136         _setmode(_fileno((Stream)->fStream), _O_TEXT); /* Default to ANSI text */ \
137 } while(0)
138 
139 #else /* defined(USE_CRT) */
140 
141 /*
142  * We set Stream->CodePage to INVALID_CP (= -1) to signal that the codepage
143  * is either not assigned (if the mode is Binary, WideText, or UTF16Text), or
144  * is not cached yet (if the mode is AnsiText). In this latter case the cache
145  * is resolved inside ConWrite. Finally, if the mode is UTF8Text, the codepage
146  * cache is set to CP_UTF8.
147  * The codepage cache can be reset by an explicit call to CON_STREAM_SET_MODE
148  * (i.e. by calling ConStreamSetMode, or by reinitializing the stream with
149  * ConStreamInit(Ex)).
150  *
151  * NOTE: the magic value could not be '0' since it is reserved for CP_ACP.
152  */
153 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage)    \
154 do { \
155     (Stream)->Mode = (Mode); \
156 \
157     if ((Mode) == AnsiText)  \
158         (Stream)->CodePage = CacheCodePage; /* Possibly assigned */          \
159     else if ((Mode) == UTF8Text) \
160         (Stream)->CodePage = CP_UTF8;       /* Fixed */                      \
161     else /* Mode == Binary, WideText, UTF16Text */                           \
162         (Stream)->CodePage = INVALID_CP;    /* Not assigned (meaningless) */ \
163 } while(0)
164 
165 #endif /* defined(USE_CRT) */
166 
167 
168 BOOL
169 ConStreamInitEx(
170     OUT PCON_STREAM Stream,
171     IN  PVOID Handle,
172     IN  CON_STREAM_MODE Mode,
173     IN  UINT CacheCodePage OPTIONAL,
174     // IN  CON_READ_FUNC ReadFunc OPTIONAL,
175     IN  CON_WRITE_FUNC WriteFunc OPTIONAL)
176 {
177     /* Parameters validation */
178     if (!Stream || !Handle || (Mode > UTF8Text))
179         return FALSE;
180 
181 #ifdef USE_CRT
182 
183     Stream->fStream = (FILE*)Handle;
184 
185 #else
186 
187     if ((HANDLE)Handle == INVALID_HANDLE_VALUE)
188         return FALSE;
189 
190     /*
191      * As the user calls us by giving us an existing handle to attach on,
192      * it is not our duty to close it if we are called again. The user
193      * is responsible for having opened those handles, and is responsible
194      * for closing them!
195      */
196 #if 0
197     /* Attempt to close the handle of the old stream */
198     if (/* Stream->IsInitialized && */ Stream->hHandle &&
199         Stream->hHandle != INVALID_HANDLE_VALUE)
200     {
201         CloseHandle(Stream->hHandle);
202     }
203 #endif
204 
205     /* Initialize the stream critical section if not already done */
206     if (!Stream->IsInitialized)
207     {
208         InitializeCriticalSection/*AndSpinCount*/(&Stream->Lock /* , 4000 */);
209         Stream->IsInitialized = TRUE;
210     }
211 
212     Stream->hHandle   = (HANDLE)Handle;
213     Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
214 
215 #endif /* defined(USE_CRT) */
216 
217     /* Set the correct file translation mode */
218     CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
219 
220     /* Use the default 'ConWrite' helper if nothing is specified */
221     Stream->WriteFunc = (WriteFunc ? WriteFunc : ConWrite);
222 
223     return TRUE;
224 }
225 
226 BOOL
227 ConStreamInit(
228     OUT PCON_STREAM Stream,
229     IN  PVOID Handle,
230     IN  CON_STREAM_MODE Mode,
231     IN  UINT CacheCodePage OPTIONAL)
232 {
233     return ConStreamInitEx(Stream, Handle, Mode, CacheCodePage, ConWrite);
234 }
235 
236 BOOL
237 ConStreamSetMode(
238     IN PCON_STREAM Stream,
239     IN CON_STREAM_MODE Mode,
240     IN UINT CacheCodePage OPTIONAL)
241 {
242     /* Parameters validation */
243     if (!Stream || (Mode > UTF8Text))
244         return FALSE;
245 
246 #ifdef USE_CRT
247     if (!Stream->fStream)
248         return FALSE;
249 #endif
250 
251     /* Set the correct file translation mode */
252     CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
253     return TRUE;
254 }
255 
256 BOOL
257 ConStreamSetCacheCodePage(
258     IN PCON_STREAM Stream,
259     IN UINT CacheCodePage)
260 {
261 #ifdef USE_CRT
262 // FIXME!
263 #warning The ConStreamSetCacheCodePage function does not make much sense with the CRT!
264 #else
265     CON_STREAM_MODE Mode;
266 
267     /* Parameters validation */
268     if (!Stream)
269         return FALSE;
270 
271     /*
272      * Keep the original stream mode but set the correct file codepage
273      * (will be reset only if Mode == AnsiText).
274      */
275     Mode = Stream->Mode;
276     CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
277     return TRUE;
278 #endif
279 }
280 
281 HANDLE
282 ConStreamGetOSHandle(
283     IN PCON_STREAM Stream)
284 {
285     /* Parameters validation */
286     if (!Stream)
287         return INVALID_HANDLE_VALUE;
288 
289     /*
290      * See https://support.microsoft.com/kb/99173
291      * for more details.
292      */
293 
294 #ifdef USE_CRT
295     if (!Stream->fStream)
296         return INVALID_HANDLE_VALUE;
297 
298     return (HANDLE)_get_osfhandle(_fileno(Stream->fStream));
299 #else
300     return Stream->hHandle;
301 #endif
302 }
303 
304 BOOL
305 ConStreamSetOSHandle(
306     IN PCON_STREAM Stream,
307     IN HANDLE Handle)
308 {
309     /* Parameters validation */
310     if (!Stream)
311         return FALSE;
312 
313     /*
314      * See https://support.microsoft.com/kb/99173
315      * for more details.
316      */
317 
318 #ifdef USE_CRT
319     if (!Stream->fStream)
320         return FALSE;
321 
322     int fdOut = _open_osfhandle(Handle, _O_TEXT /* FIXME! */);
323     FILE* fpOut = _fdopen(fdOut, "w");
324     *Stream->fStream = *fpOut;
325     /// setvbuf(Stream->fStream, NULL, _IONBF, 0);
326 
327     return TRUE;
328 #else
329     /* Flush the stream and reset its handle */
330     if (Stream->hHandle != INVALID_HANDLE_VALUE)
331         FlushFileBuffers(Stream->hHandle);
332 
333     Stream->hHandle   = Handle;
334     Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
335 
336     // NOTE: Mode reset??
337 
338     return TRUE;
339 #endif
340 }
341 
342 
343 /*
344  * Console I/O utility API
345  * (for the moment, only Output)
346  */
347 
348 // NOTE: Should be called with the stream locked.
349 INT
350 __stdcall
351 ConWrite(
352     IN PCON_STREAM Stream,
353     IN PTCHAR szStr,
354     IN DWORD len)
355 {
356 #ifndef USE_CRT
357     DWORD TotalLen = len, dwNumBytes = 0;
358     PVOID p;
359 
360     // CHAR strOem[CON_RC_STRING_MAX_SIZE]; // Some static buffer...
361 
362     /* If we do not write anything, just return */
363     if (!szStr || len == 0)
364         return 0;
365 
366     /* Check whether we are writing to a console */
367     // if (IsConsoleHandle(Stream->hHandle))
368     if (Stream->IsConsole)
369     {
370         // TODO: Check if (ConStream->Mode == WideText or UTF16Text) ??
371 
372         /*
373          * This code is inspired from _cputws, in particular from the fact that,
374          * according to MSDN: https://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx
375          * the buffer size must be less than 64 KB.
376          *
377          * A similar code can be used for implementing _cputs too.
378          */
379 
380         DWORD cchWrite;
381         TotalLen = len, dwNumBytes = 0;
382 
383         while (len > 0)
384         {
385             cchWrite = min(len, 65535 / sizeof(WCHAR));
386 
387             // FIXME: Check return value!
388             WriteConsole(Stream->hHandle, szStr, cchWrite, &dwNumBytes, NULL);
389 
390             szStr += cchWrite;
391             len -= cchWrite;
392         }
393 
394         return (INT)TotalLen; // FIXME: Really return the number of chars written!
395     }
396 
397     /*
398      * We are redirected and writing to a file or pipe instead of the console.
399      * Convert the string from TCHARs to the desired output format, if the two differ.
400      *
401      * Implementation NOTE:
402      *   MultiByteToWideChar (resp. WideCharToMultiByte) are equivalent to
403      *   OemToCharBuffW (resp. CharToOemBuffW), but the latters uselessly
404      *   depend on user32.dll, while MultiByteToWideChar and WideCharToMultiByte
405      *   only need kernel32.dll.
406      */
407     if ((Stream->Mode == WideText) || (Stream->Mode == UTF16Text))
408     {
409 #ifndef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16
410         /* Convert from the current process/thread's codepage to UTF-16 */
411         WCHAR *buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR));
412         if (!buffer)
413         {
414             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
415             return 0;
416         }
417         len = (DWORD)MultiByteToWideChar(CP_THREAD_ACP, // CP_ACP, CP_OEMCP
418                                          0, szStr, (INT)len, buffer, (INT)len);
419         szStr = (PVOID)buffer;
420 #else
421         /*
422          * Do not perform any conversion since we are already in UTF-16,
423          * that is the same encoding as the stream.
424          */
425 #endif
426 
427         /*
428          * Find any newline character in the buffer,
429          * write the part BEFORE the newline, then write
430          * a carriage-return + newline, and then write
431          * the remaining part of the buffer.
432          *
433          * This fixes output in files and serial console.
434          */
435         while (len > 0)
436         {
437             /* Loop until we find a \r or \n character */
438             // FIXME: What about the pair \r\n ?
439             p = szStr;
440             while (len > 0 && *(PWCHAR)p != L'\r' && *(PWCHAR)p != L'\n')
441             {
442                 /* Advance one character */
443                 p = (PVOID)((PWCHAR)p + 1);
444                 len--;
445             }
446 
447             /* Write everything up to \r or \n */
448             dwNumBytes = ((PWCHAR)p - (PWCHAR)szStr) * sizeof(WCHAR);
449             WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL);
450 
451             /* If we hit \r or \n ... */
452             if (len > 0 && (*(PWCHAR)p == L'\r' || *(PWCHAR)p == L'\n'))
453             {
454                 /* ... send a carriage-return + newline sequence and skip \r or \n */
455                 WriteFile(Stream->hHandle, L"\r\n", 2 * sizeof(WCHAR), &dwNumBytes, NULL);
456                 szStr = (PVOID)((PWCHAR)p + 1);
457                 len--;
458             }
459         }
460 
461 #ifndef _UNICODE
462         HeapFree(GetProcessHeap(), 0, buffer);
463 #endif
464     }
465     else if ((Stream->Mode == UTF8Text) || (Stream->Mode == AnsiText))
466     {
467         CHAR *buffer;
468 
469         /*
470          * Resolve the codepage cache if it was not assigned yet
471          * (only if the stream is in ANSI mode; in UTF8 mode the
472          * codepage was already set to CP_UTF8).
473          */
474         if (/*(Stream->Mode == AnsiText) &&*/ (Stream->CodePage == INVALID_CP))
475             Stream->CodePage = GetConsoleOutputCP(); // CP_ACP, CP_OEMCP
476 
477 #ifdef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16
478         /* Convert from UTF-16 to either UTF-8 or ANSI, using stream codepage */
479         // NOTE: MB_LEN_MAX defined either in limits.h or in stdlib.h .
480         buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * MB_LEN_MAX);
481         if (!buffer)
482         {
483             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
484             return 0;
485         }
486         len = WideCharToMultiByte(Stream->CodePage, 0,
487                                   szStr, len, buffer, len * MB_LEN_MAX,
488                                   NULL, NULL);
489         szStr = (PVOID)buffer;
490 #else
491         /*
492          * Convert from the current process/thread's codepage to either
493          * UTF-8 or ANSI, using stream codepage.
494          * We need to perform a double conversion, by going through UTF-16.
495          */
496         // TODO!
497         #error "Need to implement double conversion!"
498 #endif
499 
500         /*
501          * Find any newline character in the buffer,
502          * write the part BEFORE the newline, then write
503          * a carriage-return + newline, and then write
504          * the remaining part of the buffer.
505          *
506          * This fixes output in files and serial console.
507          */
508         while (len > 0)
509         {
510             /* Loop until we find a \r or \n character */
511             // FIXME: What about the pair \r\n ?
512             p = szStr;
513             while (len > 0 && *(PCHAR)p != '\r' && *(PCHAR)p != '\n')
514             {
515                 /* Advance one character */
516                 p = (PVOID)((PCHAR)p + 1);
517                 len--;
518             }
519 
520             /* Write everything up to \r or \n */
521             dwNumBytes = ((PCHAR)p - (PCHAR)szStr) * sizeof(CHAR);
522             WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL);
523 
524             /* If we hit \r or \n ... */
525             if (len > 0 && (*(PCHAR)p == '\r' || *(PCHAR)p == '\n'))
526             {
527                 /* ... send a carriage-return + newline sequence and skip \r or \n */
528                 WriteFile(Stream->hHandle, "\r\n", 2, &dwNumBytes, NULL);
529                 szStr = (PVOID)((PCHAR)p + 1);
530                 len--;
531             }
532         }
533 
534 #ifdef _UNICODE
535         HeapFree(GetProcessHeap(), 0, buffer);
536 #else
537         // TODO!
538 #endif
539     }
540     else // if (Stream->Mode == Binary)
541     {
542         /* Directly output the string */
543         WriteFile(Stream->hHandle, szStr, len, &dwNumBytes, NULL);
544     }
545 
546     // FIXME!
547     return (INT)TotalLen;
548 
549 #else /* defined(USE_CRT) */
550 
551     DWORD total = len;
552     DWORD written = 0;
553 
554     /* If we do not write anything, just return */
555     if (!szStr || len == 0)
556         return 0;
557 
558 #if 1
559     /*
560      * There is no "counted" printf-to-stream or puts-like function, therefore
561      * we use this trick to output the counted string to the stream.
562      */
563     while (1)
564     {
565         written = fwprintf(Stream->fStream, L"%.*s", total, szStr);
566         if (written < total)
567         {
568             /*
569              * Some embedded NULL or special character
570              * was encountered, print it apart.
571              */
572             if (written == 0)
573             {
574                 fputwc(*szStr, Stream->fStream);
575                 written++;
576             }
577 
578             szStr += written;
579             total -= written;
580         }
581         else
582         {
583             break;
584         }
585     }
586     return (INT)len;
587 #else
588     /* ANSI text or Binary output only */
589     _setmode(_fileno(Stream->fStream), _O_TEXT); // _O_BINARY
590     return fwrite(szStr, sizeof(*szStr), len, Stream->fStream);
591 #endif
592 
593 #endif /* defined(USE_CRT) */
594 }
595 
596 
597 #define CON_STREAM_WRITE_CALL(Stream, Str, Len) \
598     (Stream)->WriteFunc((Stream), (Str), (Len));
599 
600 /* Lock the stream only in non-USE_CRT mode (otherwise use the CRT stream lock) */
601 #ifndef USE_CRT
602 
603 #define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \
604 do { \
605     EnterCriticalSection(&(Stream)->Lock); \
606     (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
607     LeaveCriticalSection(&(Stream)->Lock); \
608 } while(0)
609 
610 #define CON_STREAM_WRITE(Stream, Str, Len) \
611 do { \
612     EnterCriticalSection(&(Stream)->Lock); \
613     CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
614     LeaveCriticalSection(&(Stream)->Lock); \
615 } while(0)
616 
617 #else
618 
619 #define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \
620 do { \
621     (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
622 } while(0)
623 
624 #define CON_STREAM_WRITE(Stream, Str, Len) \
625 do { \
626     CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
627 } while(0)
628 
629 #endif
630 
631 
632 INT
633 ConStreamWrite(
634     IN PCON_STREAM Stream,
635     IN PTCHAR szStr,
636     IN DWORD len)
637 {
638     INT Len;
639     CON_STREAM_WRITE2(Stream, szStr, len, Len);
640     return Len;
641 }
642 
643 INT
644 ConPuts(
645     IN PCON_STREAM Stream,
646     IN LPWSTR szStr)
647 {
648     INT Len;
649 
650     Len = wcslen(szStr);
651     CON_STREAM_WRITE2(Stream, szStr, Len, Len);
652 
653     /* Fixup returned length in case of errors */
654     if (Len < 0)
655         Len = 0;
656 
657     return Len;
658 }
659 
660 INT
661 ConPrintfV(
662     IN PCON_STREAM Stream,
663     IN LPWSTR  szStr,
664     IN va_list args) // arg_ptr
665 {
666     INT Len;
667     WCHAR bufSrc[CON_RC_STRING_MAX_SIZE];
668 
669     // Len = vfwprintf(Stream->fStream, szStr, args); // vfprintf for direct ANSI
670 
671     /*
672      * Reuse szStr as the pointer to end-of-string, to compute
673      * the string length instead of calling wcslen().
674      */
675     // StringCchVPrintfW(bufSrc, ARRAYSIZE(bufSrc), szStr, args);
676     // Len = wcslen(bufSrc);
677     StringCchVPrintfExW(bufSrc, ARRAYSIZE(bufSrc), &szStr, NULL, 0, szStr, args);
678     Len = szStr - bufSrc;
679 
680     CON_STREAM_WRITE2(Stream, bufSrc, Len, Len);
681 
682     /* Fixup returned length in case of errors */
683     if (Len < 0)
684         Len = 0;
685 
686     return Len;
687 }
688 
689 INT
690 __cdecl
691 ConPrintf(
692     IN PCON_STREAM Stream,
693     IN LPWSTR szStr,
694     ...)
695 {
696     INT Len;
697     va_list args;
698 
699     // Len = vfwprintf(Stream->fStream, szMsgBuf, args); // vfprintf for direct ANSI
700 
701     // StringCchPrintfW
702     va_start(args, szStr);
703     Len = ConPrintfV(Stream, szStr, args);
704     va_end(args);
705 
706     return Len;
707 }
708 
709 INT
710 ConResPutsEx(
711     IN PCON_STREAM Stream,
712     IN HINSTANCE hInstance OPTIONAL,
713     IN UINT uID)
714 {
715     INT Len;
716     PWCHAR szStr = NULL;
717 
718     Len = K32LoadStringW(hInstance, uID, (PWSTR)&szStr, 0);
719     if (szStr && Len)
720         // Len = ConPuts(Stream, szStr);
721         CON_STREAM_WRITE2(Stream, szStr, Len, Len);
722 
723     /* Fixup returned length in case of errors */
724     if (Len < 0)
725         Len = 0;
726 
727     return Len;
728 }
729 
730 INT
731 ConResPuts(
732     IN PCON_STREAM Stream,
733     IN UINT uID)
734 {
735     return ConResPutsEx(Stream, NULL /*GetModuleHandleW(NULL)*/, uID);
736 }
737 
738 INT
739 ConResPrintfExV(
740     IN PCON_STREAM Stream,
741     IN HINSTANCE hInstance OPTIONAL,
742     IN UINT    uID,
743     IN va_list args) // arg_ptr
744 {
745     INT Len;
746     WCHAR bufSrc[CON_RC_STRING_MAX_SIZE];
747 
748     // NOTE: We may use the special behaviour where nBufMaxSize == 0
749     Len = K32LoadStringW(hInstance, uID, bufSrc, ARRAYSIZE(bufSrc));
750     if (Len)
751         Len = ConPrintfV(Stream, bufSrc, args);
752 
753     return Len;
754 }
755 
756 INT
757 ConResPrintfV(
758     IN PCON_STREAM Stream,
759     IN UINT    uID,
760     IN va_list args) // arg_ptr
761 {
762     return ConResPrintfExV(Stream, NULL /*GetModuleHandleW(NULL)*/, uID, args);
763 }
764 
765 INT
766 __cdecl
767 ConResPrintfEx(
768     IN PCON_STREAM Stream,
769     IN HINSTANCE hInstance OPTIONAL,
770     IN UINT uID,
771     ...)
772 {
773     INT Len;
774     va_list args;
775 
776     va_start(args, uID);
777     Len = ConResPrintfExV(Stream, hInstance, uID, args);
778     va_end(args);
779 
780     return Len;
781 }
782 
783 INT
784 __cdecl
785 ConResPrintf(
786     IN PCON_STREAM Stream,
787     IN UINT uID,
788     ...)
789 {
790     INT Len;
791     va_list args;
792 
793     va_start(args, uID);
794     Len = ConResPrintfV(Stream, uID, args);
795     va_end(args);
796 
797     return Len;
798 }
799 
800 INT
801 ConMsgPuts(
802     IN PCON_STREAM Stream,
803     IN DWORD   dwFlags,
804     IN LPCVOID lpSource OPTIONAL,
805     IN DWORD   dwMessageId,
806     IN DWORD   dwLanguageId)
807 {
808     INT Len;
809     DWORD dwLength  = 0;
810     LPWSTR lpMsgBuf = NULL;
811 
812     /*
813      * Sanitize dwFlags. This version always ignore explicitely the inserts
814      * as we emulate the behaviour of the *puts function.
815      */
816     dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer.
817     dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS;  // Ignore inserts for FormatMessage.
818     dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
819 
820     dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK;
821 
822     /*
823      * Retrieve the message string without appending extra newlines.
824      * Wrap in SEH to protect from invalid string parameters.
825      */
826     _SEH2_TRY
827     {
828         dwLength = FormatMessageW(dwFlags,
829                                   lpSource,
830                                   dwMessageId,
831                                   dwLanguageId,
832                                   (LPWSTR)&lpMsgBuf,
833                                   0, NULL);
834     }
835     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
836     {
837     }
838     _SEH2_END;
839 
840     Len = (INT)dwLength;
841 
842     if (!lpMsgBuf)
843     {
844         // ASSERT(dwLength == 0);
845     }
846     else
847     {
848         // ASSERT(dwLength != 0);
849 
850         /* lpMsgBuf is NULL-terminated by FormatMessage */
851         // Len = ConPuts(Stream, lpMsgBuf);
852         CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len);
853 
854         /* Fixup returned length in case of errors */
855         if (Len < 0)
856             Len = 0;
857 
858         /* Free the buffer allocated by FormatMessage */
859         LocalFree(lpMsgBuf);
860     }
861 
862     return Len;
863 }
864 
865 INT
866 ConMsgPrintf2V(
867     IN PCON_STREAM Stream,
868     IN DWORD   dwFlags,
869     IN LPCVOID lpSource OPTIONAL,
870     IN DWORD   dwMessageId,
871     IN DWORD   dwLanguageId,
872     IN va_list args) // arg_ptr
873 {
874     INT Len;
875     DWORD dwLength  = 0;
876     LPWSTR lpMsgBuf = NULL;
877 
878     /*
879      * Sanitize dwFlags. This version always ignore explicitely the inserts.
880      * The string that we will return to the user will not be pre-formatted.
881      */
882     dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer.
883     dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS;  // Ignore inserts for FormatMessage.
884     dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
885 
886     dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK;
887 
888     /*
889      * Retrieve the message string without appending extra newlines.
890      * Wrap in SEH to protect from invalid string parameters.
891      */
892     _SEH2_TRY
893     {
894         dwLength = FormatMessageW(dwFlags,
895                                   lpSource,
896                                   dwMessageId,
897                                   dwLanguageId,
898                                   (LPWSTR)&lpMsgBuf,
899                                   0, NULL);
900     }
901     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
902     {
903     }
904     _SEH2_END;
905 
906     Len = (INT)dwLength;
907 
908     if (!lpMsgBuf)
909     {
910         // ASSERT(dwLength == 0);
911     }
912     else
913     {
914         // ASSERT(dwLength != 0);
915 
916         /* lpMsgBuf is NULL-terminated by FormatMessage */
917         Len = ConPrintfV(Stream, lpMsgBuf, args);
918         // CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len);
919 
920         /* Fixup returned length in case of errors */
921         if (Len < 0)
922             Len = 0;
923 
924         /* Free the buffer allocated by FormatMessage */
925         LocalFree(lpMsgBuf);
926     }
927 
928     return Len;
929 }
930 
931 INT
932 ConMsgPrintfV(
933     IN PCON_STREAM Stream,
934     IN DWORD   dwFlags,
935     IN LPCVOID lpSource OPTIONAL,
936     IN DWORD   dwMessageId,
937     IN DWORD   dwLanguageId,
938     IN va_list args) // arg_ptr
939 {
940     INT Len;
941     DWORD dwLength  = 0;
942     LPWSTR lpMsgBuf = NULL;
943 
944     /* Sanitize dwFlags */
945     dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer.
946 //  dwFlags &= ~FORMAT_MESSAGE_IGNORE_INSERTS; // We always use arguments.
947     dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; // We always use arguments of type 'va_list'.
948 
949     //
950     // NOTE: Technique taken from eventvwr.c!GetMessageStringFromDll()
951     //
952 
953     dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK;
954 
955     /*
956      * Retrieve the message string without appending extra newlines.
957      * Use the "safe" FormatMessage version (SEH-protected) to protect
958      * from invalid string parameters.
959      */
960     dwLength = FormatMessageSafeW(dwFlags,
961                                   lpSource,
962                                   dwMessageId,
963                                   dwLanguageId,
964                                   (LPWSTR)&lpMsgBuf,
965                                   0, &args);
966 
967     Len = (INT)dwLength;
968 
969     if (!lpMsgBuf)
970     {
971         // ASSERT(dwLength == 0);
972     }
973     else
974     {
975         // ASSERT(dwLength != 0);
976 
977         // Len = ConPrintfV(Stream, lpMsgBuf, args);
978         CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len);
979 
980         /* Fixup returned length in case of errors */
981         if (Len < 0)
982             Len = 0;
983 
984         /* Free the buffer allocated by FormatMessage */
985         LocalFree(lpMsgBuf);
986     }
987 
988     return Len;
989 }
990 
991 INT
992 __cdecl
993 ConMsgPrintf(
994     IN PCON_STREAM Stream,
995     IN DWORD   dwFlags,
996     IN LPCVOID lpSource OPTIONAL,
997     IN DWORD   dwMessageId,
998     IN DWORD   dwLanguageId,
999     ...)
1000 {
1001     INT Len;
1002     va_list args;
1003 
1004     va_start(args, dwLanguageId);
1005     // ConMsgPrintf2V
1006     Len = ConMsgPrintfV(Stream,
1007                         dwFlags,
1008                         lpSource,
1009                         dwMessageId,
1010                         dwLanguageId,
1011                         args);
1012     va_end(args);
1013 
1014     return Len;
1015 }
1016 
1017 
1018 
1019 VOID
1020 ConClearLine(IN PCON_STREAM Stream)
1021 {
1022     HANDLE hOutput = ConStreamGetOSHandle(Stream);
1023 
1024     /*
1025      * Erase the full line where the cursor is, and move
1026      * the cursor back to the beginning of the line.
1027      */
1028 
1029     if (IsConsoleHandle(hOutput))
1030     {
1031         CONSOLE_SCREEN_BUFFER_INFO csbi;
1032         DWORD dwWritten;
1033 
1034         GetConsoleScreenBufferInfo(hOutput, &csbi);
1035 
1036         csbi.dwCursorPosition.X = 0;
1037         // csbi.dwCursorPosition.Y;
1038 
1039         FillConsoleOutputCharacterW(hOutput, L' ',
1040                                     csbi.dwSize.X,
1041                                     csbi.dwCursorPosition,
1042                                     &dwWritten);
1043         SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition);
1044     }
1045     else if (IsTTYHandle(hOutput))
1046     {
1047         ConPuts(Stream, L"\x1B[2K\x1B[1G"); // FIXME: Just use WriteFile
1048     }
1049     // else, do nothing for files
1050 }
1051 
1052