1 /*
2 * PROJECT: ReactOS Console Utilities Library
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Base set of functions for loading string resources
5 * and message strings, and handle type identification.
6 * COPYRIGHT: Copyright 2017-2021 ReactOS Team
7 * Copyright 2017-2021 Hermes Belusca-Maito
8 */
9
10 /**
11 * @file utils.c
12 * @ingroup ConUtils
13 *
14 * @brief General-purpose utility functions (wrappers around
15 * or reimplementations of Win32 APIs).
16 **/
17
18 /* FIXME: Temporary HACK before we cleanly support UNICODE functions */
19 #define UNICODE
20 #define _UNICODE
21
22 #include <windef.h>
23 #include <winbase.h>
24 #include <winnls.h>
25 #include <winuser.h> // MAKEINTRESOURCEW, RT_STRING
26 #include <wincon.h> // Console APIs (only if kernel32 support included)
27 #include <strsafe.h>
28
29 /* PSEH for SEH Support */
30 #include <pseh/pseh2.h>
31
32 // #include "conutils.h"
33 #include "utils.h"
34
35 #if 0 // The following function may be useful in the future...
36
37 // Performs MultiByteToWideChar then WideCharToMultiByte .
38 // See https://github.com/pcman-bbs/pcman-windows/blob/master/Lite/StrUtils.h#l33
39 // and http://www.openfoundry.org/svn/pcman/branches/OpenPCMan_2009/Lite/StrUtils.cpp
40 // for the idea.
41 int
42 MultiByteToMultiByte(
43 // IN WORD wTranslations,
44 IN DWORD dwFlags,
45 IN UINT SrcCodePage,
46 IN LPCSTR lpSrcString,
47 IN int cbSrcChar,
48 IN UINT DestCodePage,
49 OUT LPSTR wDestString OPTIONAL,
50 IN int cbDestChar
51 );
52
53 #endif
54
55 /**
56 * @name K32LoadStringExW
57 * Loads a string resource from the executable file associated with a
58 * specified module, copies the string into a buffer, and appends a
59 * terminating null character.
60 * This is basically the LoadString() API ripped from user32.dll to
61 * remove any dependency of ConUtils from user32.dll, and to add support
62 * for loading strings from other languages than the current one.
63 *
64 * @param[in] hInstance
65 * Optional handle to an instance of the module whose executable file
66 * contains the string resource. Can be set to NULL to get the handle
67 * to the application itself.
68 *
69 * @param[in] uID
70 * The identifier of the string to be loaded.
71 *
72 * @param[in] LanguageId
73 * The language identifier of the resource. If this parameter is
74 * <tt>MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)</tt>, the current language
75 * associated with the calling thread is used. To specify a language other
76 * than the current language, use the @c MAKELANGID macro to create this
77 * parameter.
78 *
79 * @param[out] lpBuffer
80 * The buffer that receives the string. Must be of sufficient length
81 * to hold a pointer (8 bytes).
82 *
83 * @param[in] nBufferMax
84 * The size of the buffer, in characters. The string is truncated and
85 * NULL-terminated if it is longer than the number of characters specified.
86 * If this parameter is 0, then @p lpBuffer receives a read-only pointer
87 * to the resource itself.
88 *
89 * @return
90 * If the function succeeds, the return value is the number of characters
91 * copied into the buffer, not including the terminating null character,
92 * or zero if the string resource does not exist. To get extended error
93 * information, call GetLastError().
94 *
95 * @see LoadString(), K32LoadStringW()
96 **/
97 INT
98 WINAPI
K32LoadStringExW(IN HINSTANCE hInstance OPTIONAL,IN UINT uID,IN LANGID LanguageId,OUT LPWSTR lpBuffer,IN INT nBufferMax)99 K32LoadStringExW(
100 IN HINSTANCE hInstance OPTIONAL,
101 IN UINT uID,
102 IN LANGID LanguageId,
103 OUT LPWSTR lpBuffer,
104 IN INT nBufferMax)
105 {
106 HRSRC hrsrc;
107 HGLOBAL hmem;
108 WCHAR *p;
109 UINT i;
110
111 if (!lpBuffer)
112 return 0;
113
114 /* Use LOWORD (incremented by 1) as ResourceID */
115 /* There are always blocks of 16 strings */
116 hrsrc = FindResourceExW(hInstance,
117 (LPCWSTR)RT_STRING,
118 MAKEINTRESOURCEW((LOWORD(uID) >> 4) + 1),
119 LanguageId);
120 if (!hrsrc) return 0;
121
122 hmem = LoadResource(hInstance, hrsrc);
123 if (!hmem) return 0;
124
125 p = LockResource(hmem);
126 // FreeResource(hmem);
127
128 /* Find the string we're looking for */
129 uID &= 0x000F; /* Position in the block, same as % 16 */
130 for (i = 0; i < uID; i++)
131 p += *p + 1;
132
133 /*
134 * If nBufferMax == 0, then return a read-only pointer
135 * to the resource itself in lpBuffer it is assumed that
136 * lpBuffer is actually a (LPWSTR*).
137 */
138 if (nBufferMax == 0)
139 {
140 *((LPWSTR*)lpBuffer) = p + 1;
141 return *p;
142 }
143
144 i = min(nBufferMax - 1, *p);
145 if (i > 0)
146 {
147 memcpy(lpBuffer, p + 1, i * sizeof(WCHAR));
148 lpBuffer[i] = L'\0';
149 }
150 else
151 {
152 if (nBufferMax > 1)
153 {
154 lpBuffer[0] = L'\0';
155 return 0;
156 }
157 }
158
159 return i;
160 }
161
162 /**
163 * @name K32LoadStringW
164 * Loads a string resource from the executable file associated with a
165 * specified module, copies the string into a buffer, and appends a
166 * terminating null character.
167 * This is a restricted version of K32LoadStringExW().
168 *
169 * @see LoadString(), K32LoadStringExW()
170 **/
171 INT
172 WINAPI
K32LoadStringW(IN HINSTANCE hInstance OPTIONAL,IN UINT uID,OUT LPWSTR lpBuffer,IN INT nBufferMax)173 K32LoadStringW(
174 IN HINSTANCE hInstance OPTIONAL,
175 IN UINT uID,
176 OUT LPWSTR lpBuffer,
177 IN INT nBufferMax)
178 {
179 // NOTE: Instead of using LANG_NEUTRAL, one might use LANG_USER_DEFAULT...
180 return K32LoadStringExW(hInstance, uID,
181 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
182 lpBuffer, nBufferMax);
183 }
184
185 /**
186 * @name FormatMessageSafeW
187 * Loads and formats a message string. The function requires a message
188 * definition as input. The message definition can come from a buffer
189 * passed to the function. It can come from a message table resource in
190 * an already-loaded module, or the caller can ask the function to search
191 * the system's message table resource(s) for the message definition.
192 * Please refer to the Win32 FormatMessage() function for more details.
193 *
194 * @param[in] dwFlags
195 * The formatting options, and how to interpret the @p lpSource parameter.
196 * See FormatMessage() for more details.
197 *
198 * @param[in] lpSource
199 * The location of the message definition. The type of this parameter
200 * depends upon the settings in the @p dwFlags parameter.
201 *
202 * @param[in] dwMessageId
203 * The message identifier for the requested message. This parameter
204 * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING.
205 *
206 * @param[in] dwLanguageId
207 * The language identifier for the requested message. This parameter
208 * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING.
209 *
210 * @param[out] lpBuffer
211 * A pointer to a buffer that receives the null-terminated string that
212 * specifies the formatted message. If @p dwFlags includes
213 * @b FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer
214 * using the LocalAlloc() function, and places the pointer to the buffer
215 * at the address specified in @p lpBuffer.
216 * This buffer cannot be larger than 64kB.
217 *
218 * @param[in] nSize
219 * If the @b FORMAT_MESSAGE_ALLOCATE_BUFFER flag is not set, this parameter
220 * specifies the size of the output buffer, in @b TCHARs.
221 * If @b FORMAT_MESSAGE_ALLOCATE_BUFFER is set, this parameter specifies
222 * the minimum number of @b TCHARs to allocate for an output buffer.
223 * The output buffer cannot be larger than 64kB.
224 *
225 * @param[in] Arguments
226 * Optional pointer to an array of values describing a variable number of
227 * arguments, depending on the message string. Each argument is used to
228 * replace an <em>insert sequence</em> in the message string.
229 * By default, the @p Arguments parameter is of type @c va_list*, initialized
230 * with va_start(). The state of the @c va_list argument is undefined upon
231 * return from the function. To use the @c va_list again, destroy the variable
232 * argument list pointer using va_end() and reinitialize it with va_start().
233 * If you do not have a pointer of type @c va_list*, then specify the
234 * @b FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array
235 * of @c DWORD_PTR values; those values are input to the message formatted
236 * as the insert values. Each insert must have a corresponding element in
237 * the array.
238 *
239 * @return
240 * If the function succeeds, the return value is the number of characters
241 * copied into the buffer, not including the terminating null character,
242 * or zero if the string resource does not exist. To get extended error
243 * information, call GetLastError().
244 *
245 * @remark
246 * This function is a "safe" version of FormatMessage(), that does not
247 * crash if a malformed source string is retrieved and then being used
248 * for formatting. It basically wraps calls to FormatMessage() within SEH.
249 *
250 * @see <a href="https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessage">FormatMessage() (on MSDN)</a>
251 **/
252 DWORD
253 WINAPI
FormatMessageSafeW(IN DWORD dwFlags,IN LPCVOID lpSource OPTIONAL,IN DWORD dwMessageId,IN DWORD dwLanguageId,OUT LPWSTR lpBuffer,IN DWORD nSize,IN va_list * Arguments OPTIONAL)254 FormatMessageSafeW(
255 IN DWORD dwFlags,
256 IN LPCVOID lpSource OPTIONAL,
257 IN DWORD dwMessageId,
258 IN DWORD dwLanguageId,
259 OUT LPWSTR lpBuffer,
260 IN DWORD nSize,
261 IN va_list *Arguments OPTIONAL)
262 {
263 DWORD dwLength = 0;
264
265 _SEH2_TRY
266 {
267 /*
268 * Retrieve the message string. Wrap in SEH
269 * to protect from invalid string parameters.
270 */
271 _SEH2_TRY
272 {
273 dwLength = FormatMessageW(dwFlags,
274 lpSource,
275 dwMessageId,
276 dwLanguageId,
277 lpBuffer,
278 nSize,
279 Arguments);
280 }
281 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
282 {
283 dwLength = 0;
284
285 /*
286 * An exception occurred while calling FormatMessage, this is usually
287 * the sign that a parameter was invalid, either 'lpBuffer' was NULL
288 * but we did not pass the flag FORMAT_MESSAGE_ALLOCATE_BUFFER, or the
289 * array pointer 'Arguments' was NULL or did not contain enough elements,
290 * and we did not pass the flag FORMAT_MESSAGE_IGNORE_INSERTS, and the
291 * message string expected too many inserts.
292 * In this last case only, we can call again FormatMessage but ignore
293 * explicitly the inserts. The string that we will return to the user
294 * will not be pre-formatted.
295 */
296 if (((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) || lpBuffer) &&
297 !(dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS))
298 {
299 /* Remove any possible harmful flags and always ignore inserts */
300 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
301 dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS;
302
303 /* If this call also throws an exception, we are really dead */
304 dwLength = FormatMessageW(dwFlags,
305 lpSource,
306 dwMessageId,
307 dwLanguageId,
308 lpBuffer,
309 nSize,
310 NULL /* Arguments */);
311 }
312 }
313 _SEH2_END;
314 }
315 _SEH2_FINALLY
316 {
317 }
318 _SEH2_END;
319
320 return dwLength;
321 }
322
323 /**
324 * @name ConSetThreadUILanguage
325 * Sets the current thread's user interface language.
326 * Mostly used by console applications for selecting a
327 * language identifier that best supports the NT Console.
328 * This function dynamically loads and calls kernel32!SetThreadUILanguage()
329 * so as to be able to work on older environments where this
330 * API is not supported.
331 * The FormatMessage() API also bases itself on the thread's
332 * current language for its default behaviour (unless an explicit
333 * language identifier has been provided).
334 *
335 * @param[in,opt] LangId
336 * (Vista+) A non-zero language identifier that specifies the
337 * current thread's user interface language to set.
338 * (XP/2003) Set the language identifier to 0 for selecting a
339 * language identifier that best supports the NT Console.
340 *
341 * @return
342 * Returns LangId in case of success, or 0 in case of failure.
343 * If LangId was set to 0, the function always succeeds and returns
344 * the language identifier that best supports the NT Console.
345 *
346 * @remark
347 * This function is thread-safe.
348 *
349 * @see <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-setthreaduilanguage">SetThreadUILanguage() (on MSDN)</a>
350 **/
351 LANGID
ConSetThreadUILanguage(IN LANGID LangId OPTIONAL)352 ConSetThreadUILanguage(
353 IN LANGID LangId OPTIONAL)
354 {
355 /* The function pointer is shared amongst all threads */
356 static volatile LANGID (WINAPI *pfnSetThreadUILanguage)(LANGID) = NULL;
357
358 if (!pfnSetThreadUILanguage)
359 {
360 /* Load the API from kernel32 */
361 PVOID pFunc = (PVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetThreadUILanguage");
362 if (!pFunc)
363 {
364 /* Fail since the API is not available */
365 return 0;
366 }
367 /* Set the function pointer in case it hasn't been already set by another thread */
368 InterlockedCompareExchangePointer((PVOID*)&pfnSetThreadUILanguage, pFunc, NULL);
369 // ASSERT(pfnSetThreadUILanguage);
370 }
371 return pfnSetThreadUILanguage(LangId);
372 }
373
374 /**
375 * @name IsTTYHandle
376 * Checks whether a handle refers to a valid TTY object.
377 * A TTY object may be a console or a "communications" (e.g. serial) port.
378 *
379 * @param[in] hHandle
380 * Handle to the TTY object to check for.
381 *
382 * @return
383 * @b TRUE when the handle refers to a valid TTY object,
384 * @b FALSE if it does not.
385 *
386 * @remark
387 * This test is more general than IsConsoleHandle() as it is not limited
388 * to Win32 console objects only.
389 *
390 * @see IsConsoleHandle()
391 **/
392 BOOL
IsTTYHandle(IN HANDLE hHandle)393 IsTTYHandle(IN HANDLE hHandle)
394 {
395 /*
396 * More general test than IsConsoleHandle(). Consoles, as well as serial
397 * (communications) ports, etc... verify this test, but only consoles
398 * verify the IsConsoleHandle() test: indeed the latter checks whether
399 * the handle is really handled by the console subsystem.
400 */
401 return ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) == FILE_TYPE_CHAR);
402 }
403
404 /**
405 * @name IsConsoleHandle
406 * Checks whether a handle refers to a valid Win32 console object.
407 *
408 * @param[in] hHandle
409 * Handle to the Win32 console object to check for:
410 * console input buffer, console output buffer.
411 *
412 * @return
413 * @b TRUE when the handle refers to a valid Win32 console object,
414 * @b FALSE if it does not.
415 *
416 * @see IsTTYHandle()
417 **/
418 BOOL
IsConsoleHandle(IN HANDLE hHandle)419 IsConsoleHandle(IN HANDLE hHandle)
420 {
421 DWORD dwMode;
422
423 /* Check whether the handle may be that of a console... */
424 if ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR)
425 return FALSE;
426
427 /*
428 * It may be. Perform another test. The idea comes from the
429 * MSDN description of the WriteConsole API:
430 *
431 * "WriteConsole fails if it is used with a standard handle
432 * that is redirected to a file. If an application processes
433 * multilingual output that can be redirected, determine whether
434 * the output handle is a console handle (one method is to call
435 * the GetConsoleMode function and check whether it succeeds).
436 * If the handle is a console handle, call WriteConsole. If the
437 * handle is not a console handle, the output is redirected and
438 * you should call WriteFile to perform the I/O."
439 */
440 return GetConsoleMode(hHandle, &dwMode);
441 }
442
443 /* EOF */
444