1 /*
2 * PROJECT: ReactOS Mode Utility
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Provides fast mode setup for DOS devices.
5 * COPYRIGHT: Copyright 2002 Robert Dickenson
6 * Copyright 2016-2021 Hermes Belusca-Maito
7 */
8 /*
9 * ReactOS mode console command
10 *
11 * mode.c
12 *
13 * Copyright (C) 2002 Robert Dickenson <robd@reactos.org>
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 */
29
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include <windef.h>
34 #include <winbase.h>
35 #include <winuser.h>
36 #include <wincon.h>
37
38 #include <conutils.h>
39
40 #include "resource.h"
41
42 #define MAX_PORTNAME_LEN 20
43
44 #define ASSERT(a)
45
46 /*** For fixes, see also network/net/main.c ***/
47 // VOID PrintPadding(...)
48 VOID
49 __cdecl
UnderlinedResPrintf(IN PCON_STREAM Stream,IN UINT uID,...)50 UnderlinedResPrintf(
51 IN PCON_STREAM Stream,
52 IN UINT uID,
53 ...)
54 {
55 INT Len;
56 va_list args;
57
58 #define MAX_BUFFER_SIZE 4096
59 INT i;
60 WCHAR szMsgBuffer[MAX_BUFFER_SIZE];
61
62 va_start(args, uID);
63 Len = ConResPrintfV(Stream, uID, args);
64 va_end(args);
65
66 ConPuts(Stream, L"\n");
67 for (i = 0; i < Len; i++)
68 szMsgBuffer[i] = L'-';
69 szMsgBuffer[Len] = UNICODE_NULL;
70
71 ConStreamWrite(Stream, szMsgBuffer, Len);
72 }
73
ShowParallelStatus(INT nPortNum)74 int ShowParallelStatus(INT nPortNum)
75 {
76 WCHAR buffer[250];
77 WCHAR szPortName[MAX_PORTNAME_LEN];
78
79 swprintf(szPortName, L"LPT%d", nPortNum);
80
81 ConPuts(StdOut, L"\n");
82 UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, szPortName);
83 ConPuts(StdOut, L"\n");
84
85 if (QueryDosDeviceW(szPortName, buffer, ARRAYSIZE(buffer)))
86 {
87 PWSTR ptr = wcsrchr(buffer, L'\\');
88 if (ptr != NULL)
89 {
90 if (_wcsicmp(szPortName, ++ptr) == 0)
91 ConResPuts(StdOut, IDS_PRINTER_OUTPUT_NOT_REROUTED);
92 else
93 ConResPrintf(StdOut, IDS_PRINTER_OUTPUT_REROUTED_SERIAL, ptr);
94
95 return 0;
96 }
97 else
98 {
99 ConResPrintf(StdErr, IDS_ERROR_QUERY_DEVICES_FORM, szPortName, buffer);
100 }
101 }
102 else
103 {
104 ConPrintf(StdErr, L"ERROR: QueryDosDeviceW(%s) failed: 0x%lx\n", szPortName, GetLastError());
105 }
106 ConPuts(StdOut, L"\n");
107
108 return 1;
109 }
110
SetParallelState(INT nPortNum)111 int SetParallelState(INT nPortNum)
112 {
113 WCHAR szPortName[MAX_PORTNAME_LEN];
114 WCHAR szTargetPath[MAX_PORTNAME_LEN];
115
116 swprintf(szPortName, L"LPT%d", nPortNum);
117 swprintf(szTargetPath, L"COM%d", nPortNum);
118 if (!DefineDosDeviceW(DDD_REMOVE_DEFINITION, szPortName, szTargetPath))
119 {
120 ConPrintf(StdErr, L"ERROR: SetParallelState(%d) - DefineDosDevice(%s) failed: 0x%lx\n", nPortNum, szPortName, GetLastError());
121 }
122
123 ShowParallelStatus(nPortNum);
124 return 0;
125 }
126
127
128 static PCWSTR
ParseNumber(PCWSTR argStr,PDWORD Number)129 ParseNumber(PCWSTR argStr, PDWORD Number)
130 {
131 INT value, skip = 0;
132
133 value = swscanf(argStr, L"%lu%n", Number, &skip);
134 if (!value) return NULL;
135 argStr += skip;
136 return argStr;
137 }
138
139
140 /*
141 \??\COM1
142 \Device\NamedPipe\Spooler\LPT1
143 BOOL DefineDosDevice(
144 DWORD dwFlags, // options
145 LPCTSTR lpDeviceName, // device name
146 LPCTSTR lpTargetPath // path string
147 );
148 DWORD QueryDosDevice(
149 LPCTSTR lpDeviceName, // MS-DOS device name string
150 LPTSTR lpTargetPath, // query results buffer
151 DWORD ucchMax // maximum size of buffer
152 );
153 */
154
155
156 /*****************************************************************************\
157 ** C O N S O L E H E L P E R S **
158 \*****************************************************************************/
159
ShowConsoleStatus(VOID)160 int ShowConsoleStatus(VOID)
161 {
162 HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
163 CONSOLE_SCREEN_BUFFER_INFO csbi;
164 DWORD dwKbdDelay, dwKbdSpeed;
165
166 ConPuts(StdOut, L"\n");
167 UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, L"CON");
168 ConPuts(StdOut, L"\n");
169
170 if (GetConsoleScreenBufferInfo(hConOut, &csbi))
171 {
172 ConResPrintf(StdOut, IDS_CONSOLE_STATUS_LINES, csbi.dwSize.Y);
173 ConResPrintf(StdOut, IDS_CONSOLE_STATUS_COLS , csbi.dwSize.X);
174 }
175 if (SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
176 {
177 ConResPrintf(StdOut, IDS_CONSOLE_KBD_RATE, dwKbdSpeed);
178 }
179 if (SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
180 {
181 ConResPrintf(StdOut, IDS_CONSOLE_KBD_DELAY, dwKbdDelay);
182 }
183 ConResPrintf(StdOut, IDS_CONSOLE_CODEPAGE, GetConsoleOutputCP());
184 ConPuts(StdOut, L"\n");
185
186 return 0;
187 }
188
ShowConsoleCPStatus(VOID)189 int ShowConsoleCPStatus(VOID)
190 {
191 ConPuts(StdOut, L"\n");
192 UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, L"CON");
193 ConPuts(StdOut, L"\n");
194
195 ConResPrintf(StdOut, IDS_CONSOLE_CODEPAGE, GetConsoleOutputCP());
196 ConPuts(StdOut, L"\n");
197
198 return 0;
199 }
200
201 static VOID
ClearScreen(IN HANDLE hConOut,IN PCONSOLE_SCREEN_BUFFER_INFO pcsbi)202 ClearScreen(
203 IN HANDLE hConOut,
204 IN PCONSOLE_SCREEN_BUFFER_INFO pcsbi)
205 {
206 COORD coPos;
207 DWORD dwWritten;
208
209 coPos.X = 0;
210 coPos.Y = 0;
211 FillConsoleOutputAttribute(hConOut, pcsbi->wAttributes,
212 pcsbi->dwSize.X * pcsbi->dwSize.Y,
213 coPos, &dwWritten);
214 FillConsoleOutputCharacterW(hConOut, L' ',
215 pcsbi->dwSize.X * pcsbi->dwSize.Y,
216 coPos, &dwWritten);
217 SetConsoleCursorPosition(hConOut, coPos);
218 }
219
220 /*
221 * See, or adjust if needed, subsystems/mvdm/ntvdm/console/video.c!ResizeTextConsole()
222 * for more information.
223 */
224 static BOOL
ResizeTextConsole(IN HANDLE hConOut,IN OUT PCONSOLE_SCREEN_BUFFER_INFO pcsbi,IN COORD Resolution)225 ResizeTextConsole(
226 IN HANDLE hConOut,
227 IN OUT PCONSOLE_SCREEN_BUFFER_INFO pcsbi,
228 IN COORD Resolution)
229 {
230 BOOL Success;
231 SHORT Width, Height;
232 SMALL_RECT ConRect;
233
234 /*
235 * Use this trick to effectively resize the console buffer and window,
236 * because:
237 * - SetConsoleScreenBufferSize fails if the new console screen buffer size
238 * is smaller than the current console window size, and:
239 * - SetConsoleWindowInfo fails if the new console window size is larger
240 * than the current console screen buffer size.
241 */
242
243 /* Resize the screen buffer only if needed */
244 if (Resolution.X != pcsbi->dwSize.X || Resolution.Y != pcsbi->dwSize.Y)
245 {
246 Width = pcsbi->srWindow.Right - pcsbi->srWindow.Left + 1;
247 Height = pcsbi->srWindow.Bottom - pcsbi->srWindow.Top + 1;
248
249 /*
250 * If the current console window is too large for
251 * the new screen buffer, resize it first.
252 */
253 if (Width > Resolution.X || Height > Resolution.Y)
254 {
255 /*
256 * NOTE: This is not a problem if we move the window back to (0,0)
257 * because when we resize the screen buffer, the window will move back
258 * to where the cursor is. Or, if the screen buffer is not resized,
259 * when we readjust again the window, we will move back to a correct
260 * position. This is what we wanted after all...
261 */
262 ConRect.Left = ConRect.Top = 0;
263 ConRect.Right = ConRect.Left + min(Width , Resolution.X) - 1;
264 ConRect.Bottom = ConRect.Top + min(Height, Resolution.Y) - 1;
265
266 Success = SetConsoleWindowInfo(hConOut, TRUE, &ConRect);
267 if (!Success) return FALSE;
268 }
269
270 /*
271 * Now resize the screen buffer.
272 *
273 * SetConsoleScreenBufferSize automatically takes into account the current
274 * cursor position when it computes starting which row it should copy text
275 * when resizing the screen buffer, and scrolls the console window such that
276 * the cursor is placed in it again. We therefore do not need to care about
277 * the cursor position and do the maths ourselves.
278 */
279 Success = SetConsoleScreenBufferSize(hConOut, Resolution);
280 if (!Success) return FALSE;
281
282 /*
283 * Setting a new screen buffer size can change other information,
284 * so update the console screen buffer information.
285 */
286 GetConsoleScreenBufferInfo(hConOut, pcsbi);
287 }
288
289 /* Always resize the console window within the permitted maximum size */
290 Width = min(Resolution.X, pcsbi->dwMaximumWindowSize.X);
291 Height = min(Resolution.Y, pcsbi->dwMaximumWindowSize.Y);
292 ConRect.Left = 0;
293 ConRect.Right = ConRect.Left + Width - 1;
294 ConRect.Bottom = max(pcsbi->dwCursorPosition.Y, Height - 1);
295 ConRect.Top = ConRect.Bottom - Height + 1;
296
297 SetConsoleWindowInfo(hConOut, TRUE, &ConRect);
298
299 /* Update the console screen buffer information */
300 GetConsoleScreenBufferInfo(hConOut, pcsbi);
301
302 return TRUE;
303 }
304
SetConsoleStateOld(IN PCWSTR ArgStr)305 int SetConsoleStateOld(IN PCWSTR ArgStr)
306 {
307 PCWSTR argStr = ArgStr;
308
309 HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
310 CONSOLE_SCREEN_BUFFER_INFO csbi;
311 COORD Resolution;
312 DWORD value;
313
314 if (!GetConsoleScreenBufferInfo(hConOut, &csbi))
315 {
316 // TODO: Error message?
317 return 0;
318 }
319
320 Resolution = csbi.dwSize;
321
322 /* Parse the column number (only MANDATORY argument) */
323 value = 0;
324 argStr = ParseNumber(argStr, &value);
325 if (!argStr) goto invalid_parameter;
326 Resolution.X = (SHORT)value;
327
328 /* Parse the line number (OPTIONAL argument) */
329 while (*argStr == L' ') argStr++;
330 if (!*argStr) goto Quit;
331 if (*argStr++ != L',') goto invalid_parameter;
332 while (*argStr == L' ') argStr++;
333
334 value = 0;
335 argStr = ParseNumber(argStr, &value);
336 if (!argStr) goto invalid_parameter;
337 Resolution.Y = (SHORT)value;
338
339 /* This should be the end of the string */
340 while (*argStr == L' ') argStr++;
341 if (*argStr) goto invalid_parameter;
342
343 Quit:
344 ClearScreen(hConOut, &csbi);
345 if (!ResizeTextConsole(hConOut, &csbi, Resolution))
346 ConResPuts(StdErr, IDS_ERROR_SCREEN_LINES_COL);
347
348 return 0;
349
350 invalid_parameter:
351 ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
352 return 1;
353 }
354
SetConsoleState(IN PCWSTR ArgStr)355 int SetConsoleState(IN PCWSTR ArgStr)
356 {
357 PCWSTR argStr = ArgStr;
358 BOOL dispMode = FALSE, kbdMode = FALSE;
359
360 HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
361 CONSOLE_SCREEN_BUFFER_INFO csbi;
362 COORD Resolution;
363 DWORD dwKbdDelay, dwKbdSpeed;
364 DWORD value;
365
366 if (!GetConsoleScreenBufferInfo(hConOut, &csbi))
367 {
368 // TODO: Error message?
369 return 0;
370 }
371 if (!SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
372 {
373 // TODO: Error message?
374 return 0;
375 }
376 if (!SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
377 {
378 // TODO: Error message?
379 return 0;
380 }
381
382 Resolution = csbi.dwSize;
383
384 while (argStr && *argStr)
385 {
386 while (*argStr == L' ') argStr++;
387 if (!*argStr) break;
388
389 if (!kbdMode && _wcsnicmp(argStr, L"COLS=", 5) == 0)
390 {
391 dispMode = TRUE;
392
393 value = 0;
394 argStr = ParseNumber(argStr+5, &value);
395 if (!argStr) goto invalid_parameter;
396 Resolution.X = (SHORT)value;
397 }
398 else if (!kbdMode && _wcsnicmp(argStr, L"LINES=", 6) == 0)
399 {
400 dispMode = TRUE;
401
402 value = 0;
403 argStr = ParseNumber(argStr+6, &value);
404 if (!argStr) goto invalid_parameter;
405 Resolution.Y = (SHORT)value;
406 }
407 else if (!dispMode && _wcsnicmp(argStr, L"RATE=", 5) == 0)
408 {
409 kbdMode = TRUE;
410
411 argStr = ParseNumber(argStr+5, &dwKbdSpeed);
412 if (!argStr) goto invalid_parameter;
413 }
414 else if (!dispMode && _wcsnicmp(argStr, L"DELAY=", 6) == 0)
415 {
416 kbdMode = TRUE;
417
418 argStr = ParseNumber(argStr+6, &dwKbdDelay);
419 if (!argStr) goto invalid_parameter;
420 }
421 else
422 {
423 invalid_parameter:
424 ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
425 return 1;
426 }
427 }
428
429 if (dispMode)
430 {
431 ClearScreen(hConOut, &csbi);
432 if (!ResizeTextConsole(hConOut, &csbi, Resolution))
433 ConResPuts(StdErr, IDS_ERROR_SCREEN_LINES_COL);
434 }
435 else if (kbdMode)
436 {
437 /*
438 * Set the new keyboard settings. If those values are greater than
439 * their allowed range, they are automatically corrected as follows:
440 * dwKbdSpeed = min(dwKbdSpeed, 31);
441 * dwKbdDelay = (dwKbdDelay % 4);
442 */
443 SystemParametersInfoW(SPI_SETKEYBOARDDELAY, dwKbdDelay, NULL, 0);
444 // "Invalid keyboard delay."
445 SystemParametersInfoW(SPI_SETKEYBOARDSPEED, dwKbdSpeed, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
446 // "Invalid keyboard rate."
447 }
448
449 return 0;
450 }
451
SetConsoleCPState(IN PCWSTR ArgStr)452 int SetConsoleCPState(IN PCWSTR ArgStr)
453 {
454 PCWSTR argStr = ArgStr;
455 DWORD value = 0;
456 UINT uOldCodePage, uNewCodePage;
457
458 if ( (_wcsnicmp(argStr, L"SELECT=", 7) == 0 && (argStr += 7)) ||
459 (_wcsnicmp(argStr, L"SEL=", 4) == 0 && (argStr += 4)) )
460 {
461 argStr = ParseNumber(argStr, &value);
462 if (!argStr) goto invalid_parameter;
463
464 /* This should be the end of the string */
465 while (*argStr == L' ') argStr++;
466 if (*argStr) goto invalid_parameter;
467 }
468 else
469 {
470 invalid_parameter:
471 ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
472 return 1;
473 }
474
475 uNewCodePage = value;
476
477 /**
478 ** IMPORTANT NOTE: This code must be kept synchronized with CHCP.COM
479 **/
480
481 /*
482 * Save the original console code page to be restored
483 * in case SetConsoleCP() or SetConsoleOutputCP() fails.
484 */
485 uOldCodePage = GetConsoleCP();
486
487 /*
488 * Try changing the console input and output code pages.
489 * If it succeeds, refresh the local code page information.
490 */
491 if (SetConsoleCP(uNewCodePage))
492 {
493 if (SetConsoleOutputCP(uNewCodePage))
494 {
495 /* Success, reset the current thread UI language
496 * and update the streams cached code page. */
497 ConSetThreadUILanguage(0);
498 ConStdStreamsSetCacheCodePage(uNewCodePage, uNewCodePage);
499
500 /* Display the current console status */
501 ShowConsoleStatus();
502 return 0;
503 }
504 else
505 {
506 /* Failure, restore the original console code page */
507 SetConsoleCP(uOldCodePage);
508 }
509 }
510
511 /* An error happened, display an error and bail out */
512 ConResPuts(StdErr, IDS_ERROR_INVALID_CODEPAGE);
513 return 1;
514 }
515
516
517 /*****************************************************************************\
518 ** S E R I A L P O R T H E L P E R S **
519 \*****************************************************************************/
520
521 static BOOL
SerialPortQuery(INT nPortNum,LPDCB pDCB,LPCOMMTIMEOUTS pCommTimeouts,BOOL bWrite)522 SerialPortQuery(INT nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOOL bWrite)
523 {
524 BOOL Success;
525 HANDLE hPort;
526 WCHAR szPortName[MAX_PORTNAME_LEN];
527
528 ASSERT(pDCB);
529 ASSERT(pCommTimeouts);
530
531 swprintf(szPortName, L"COM%d", nPortNum);
532 hPort = CreateFileW(szPortName,
533 bWrite ? GENERIC_WRITE : GENERIC_READ,
534 0, // exclusive
535 NULL, // sec attr
536 OPEN_EXISTING,
537 0, // no attributes
538 NULL); // no template
539
540 if (hPort == INVALID_HANDLE_VALUE)
541 {
542 DWORD dwLastError = GetLastError();
543 if (dwLastError == ERROR_ACCESS_DENIED)
544 ConResPrintf(StdErr, IDS_ERROR_DEVICE_NOT_AVAILABLE, szPortName);
545 else
546 ConResPrintf(StdErr, IDS_ERROR_ILLEGAL_DEVICE_NAME, szPortName, dwLastError);
547 return FALSE;
548 }
549
550 Success = bWrite ? SetCommState(hPort, pDCB)
551 : GetCommState(hPort, pDCB);
552 if (!Success)
553 {
554 ConResPrintf(StdErr,
555 bWrite ? IDS_ERROR_STATUS_SET_DEVICE : IDS_ERROR_STATUS_GET_DEVICE,
556 szPortName);
557 goto Quit;
558 }
559
560 Success = bWrite ? SetCommTimeouts(hPort, pCommTimeouts)
561 : GetCommTimeouts(hPort, pCommTimeouts);
562 if (!Success)
563 {
564 ConResPrintf(StdErr,
565 bWrite ? IDS_ERROR_TIMEOUT_SET_DEVICE : IDS_ERROR_TIMEOUT_GET_DEVICE,
566 szPortName);
567 goto Quit;
568 }
569
570 Quit:
571 CloseHandle(hPort);
572 return Success;
573 }
574
ShowSerialStatus(INT nPortNum)575 int ShowSerialStatus(INT nPortNum)
576 {
577 static const LPCWSTR parity_strings[] =
578 {
579 L"None", // NOPARITY
580 L"Odd", // ODDPARITY
581 L"Even", // EVENPARITY
582 L"Mark", // MARKPARITY
583 L"Space" // SPACEPARITY
584 };
585 static const LPCWSTR control_strings[] = { L"OFF", L"ON", L"HANDSHAKE", L"TOGGLE" };
586 static const LPCWSTR stopbit_strings[] = { L"1", L"1.5", L"2" };
587
588 DCB dcb;
589 COMMTIMEOUTS CommTimeouts;
590 WCHAR szPortName[MAX_PORTNAME_LEN];
591
592 if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
593 {
594 return 1;
595 }
596 if (dcb.Parity >= ARRAYSIZE(parity_strings))
597 {
598 ConResPrintf(StdErr, IDS_ERROR_INVALID_PARITY_BITS, dcb.Parity);
599 dcb.Parity = 0;
600 }
601 if (dcb.StopBits >= ARRAYSIZE(stopbit_strings))
602 {
603 ConResPrintf(StdErr, IDS_ERROR_INVALID_STOP_BITS, dcb.StopBits);
604 dcb.StopBits = 0;
605 }
606
607 swprintf(szPortName, L"COM%d", nPortNum);
608
609 ConPuts(StdOut, L"\n");
610 UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, szPortName);
611 ConPuts(StdOut, L"\n");
612
613 ConResPrintf(StdOut, IDS_COM_STATUS_BAUD, dcb.BaudRate);
614 ConResPrintf(StdOut, IDS_COM_STATUS_PARITY, parity_strings[dcb.Parity]);
615 ConResPrintf(StdOut, IDS_COM_STATUS_DATA_BITS, dcb.ByteSize);
616 ConResPrintf(StdOut, IDS_COM_STATUS_STOP_BITS, stopbit_strings[dcb.StopBits]);
617 ConResPrintf(StdOut, IDS_COM_STATUS_TIMEOUT,
618 control_strings[(CommTimeouts.ReadTotalTimeoutConstant != 0) ||
619 (CommTimeouts.WriteTotalTimeoutConstant != 0) ? 1 : 0]);
620 ConResPrintf(StdOut, IDS_COM_STATUS_XON_XOFF,
621 control_strings[dcb.fOutX ? 1 : 0]);
622 ConResPrintf(StdOut, IDS_COM_STATUS_CTS_HANDSHAKING,
623 control_strings[dcb.fOutxCtsFlow ? 1 : 0]);
624 ConResPrintf(StdOut, IDS_COM_STATUS_DSR_HANDSHAKING,
625 control_strings[dcb.fOutxDsrFlow ? 1 : 0]);
626 ConResPrintf(StdOut, IDS_COM_STATUS_DSR_SENSITIVITY,
627 control_strings[dcb.fDsrSensitivity ? 1 : 0]);
628 ConResPrintf(StdOut, IDS_COM_STATUS_DTR_CIRCUIT, control_strings[dcb.fDtrControl]);
629 ConResPrintf(StdOut, IDS_COM_STATUS_RTS_CIRCUIT, control_strings[dcb.fRtsControl]);
630 ConPuts(StdOut, L"\n");
631
632 return 0;
633 }
634
635
636 /*
637 * Those procedures are inspired from Wine's dll/win32/kernel32/wine/comm.c
638 * Copyright 1996 Erik Bos and Marcus Meissner.
639 */
640
641 static PCWSTR
ParseModes(PCWSTR argStr,PBYTE Mode)642 ParseModes(PCWSTR argStr, PBYTE Mode)
643 {
644 if (_wcsnicmp(argStr, L"OFF", 3) == 0)
645 {
646 argStr += 3;
647 *Mode = 0;
648 }
649 else if (_wcsnicmp(argStr, L"ON", 2) == 0)
650 {
651 argStr += 2;
652 *Mode = 1;
653 }
654 else if (_wcsnicmp(argStr, L"HS", 2) == 0)
655 {
656 argStr += 2;
657 *Mode = 2;
658 }
659 else if (_wcsnicmp(argStr, L"TG", 2) == 0)
660 {
661 argStr += 2;
662 *Mode = 3;
663 }
664
665 return NULL;
666 }
667
668 static PCWSTR
ParseBaudRate(PCWSTR argStr,PDWORD BaudRate)669 ParseBaudRate(PCWSTR argStr, PDWORD BaudRate)
670 {
671 argStr = ParseNumber(argStr, BaudRate);
672 if (!argStr) return NULL;
673
674 /*
675 * Check for Baud Rate abbreviations. This means that using
676 * those values as real baud rates is impossible using MODE.
677 */
678 switch (*BaudRate)
679 {
680 /* BaudRate = 110, 150, 300, 600 */
681 case 11: case 15: case 30: case 60:
682 *BaudRate *= 10;
683 break;
684
685 /* BaudRate = 1200, 2400, 4800, 9600 */
686 case 12: case 24: case 48: case 96:
687 *BaudRate *= 100;
688 break;
689
690 case 19:
691 *BaudRate = 19200;
692 break;
693 }
694
695 return argStr;
696 }
697
698 static PCWSTR
ParseParity(PCWSTR argStr,PBYTE Parity)699 ParseParity(PCWSTR argStr, PBYTE Parity)
700 {
701 switch (towupper(*argStr++))
702 {
703 case L'N':
704 *Parity = NOPARITY;
705 break;
706
707 case L'O':
708 *Parity = ODDPARITY;
709 break;
710
711 case L'E':
712 *Parity = EVENPARITY;
713 break;
714
715 case L'M':
716 *Parity = MARKPARITY;
717 break;
718
719 case L'S':
720 *Parity = SPACEPARITY;
721 break;
722
723 default:
724 return NULL;
725 }
726
727 return argStr;
728 }
729
730 static PCWSTR
ParseByteSize(PCWSTR argStr,PBYTE ByteSize)731 ParseByteSize(PCWSTR argStr, PBYTE ByteSize)
732 {
733 DWORD value = 0;
734
735 argStr = ParseNumber(argStr, &value);
736 if (!argStr) return NULL;
737
738 *ByteSize = (BYTE)value;
739 if (*ByteSize < 5 || *ByteSize > 8)
740 return NULL;
741
742 return argStr;
743 }
744
745 static PCWSTR
ParseStopBits(PCWSTR argStr,PBYTE StopBits)746 ParseStopBits(PCWSTR argStr, PBYTE StopBits)
747 {
748 if (_wcsnicmp(argStr, L"1.5", 3) == 0)
749 {
750 argStr += 3;
751 *StopBits = ONE5STOPBITS;
752 }
753 else
754 {
755 if (*argStr == L'1')
756 *StopBits = ONESTOPBIT;
757 else if (*argStr == L'2')
758 *StopBits = TWOSTOPBITS;
759 else
760 return NULL;
761
762 argStr++;
763 }
764
765 return argStr;
766 }
767
768 /*
769 * Build a DCB using the old style settings string eg: "96,n,8,1"
770 *
771 * See dll/win32/kernel32/wine/comm.c!COMM_BuildOldCommDCB()
772 * for more information.
773 */
774 static BOOL
BuildOldCommDCB(OUT LPDCB pDCB,IN PCWSTR ArgStr)775 BuildOldCommDCB(
776 OUT LPDCB pDCB,
777 IN PCWSTR ArgStr)
778 {
779 PCWSTR argStr = ArgStr;
780 BOOL stop = FALSE;
781
782 /*
783 * Parse the baud rate (only MANDATORY argument)
784 */
785 argStr = ParseBaudRate(argStr, &pDCB->BaudRate);
786 if (!argStr) return FALSE;
787
788
789 /*
790 * Now parse the rest (OPTIONAL arguments)
791 */
792
793 while (*argStr == L' ') argStr++;
794 if (!*argStr) goto Quit;
795 if (*argStr++ != L',') return FALSE;
796 while (*argStr == L' ') argStr++;
797 if (!*argStr) goto Quit;
798
799 /* Parse the parity */
800 // Default: EVENPARITY
801 pDCB->Parity = EVENPARITY;
802 if (*argStr != L',')
803 {
804 argStr = ParseParity(argStr, &pDCB->Parity);
805 if (!argStr) return FALSE;
806 }
807
808 while (*argStr == L' ') argStr++;
809 if (!*argStr) goto Quit;
810 if (*argStr++ != L',') return FALSE;
811 while (*argStr == L' ') argStr++;
812 if (!*argStr) goto Quit;
813
814 /* Parse the data bits */
815 // Default: 7
816 pDCB->ByteSize = 7;
817 if (*argStr != L',')
818 {
819 argStr = ParseByteSize(argStr, &pDCB->ByteSize);
820 if (!argStr) return FALSE;
821 }
822
823 while (*argStr == L' ') argStr++;
824 if (!*argStr) goto Quit;
825 if (*argStr++ != L',') return FALSE;
826 while (*argStr == L' ') argStr++;
827 if (!*argStr) goto Quit;
828
829 /* Parse the stop bits */
830 // Default: 1, or 2 for BAUD=110
831 // pDCB->StopBits = ONESTOPBIT;
832 if (*argStr != L',')
833 {
834 stop = TRUE;
835 argStr = ParseStopBits(argStr, &pDCB->StopBits);
836 if (!argStr) return FALSE;
837 }
838
839 /* The last parameter (flow control "retry") is really optional */
840 while (*argStr == L' ') argStr++;
841 if (!*argStr) goto Quit;
842 if (*argStr++ != L',') return FALSE;
843 while (*argStr == L' ') argStr++;
844 if (!*argStr) goto Quit;
845
846 Quit:
847 switch (towupper(*argStr))
848 {
849 case L'\0':
850 pDCB->fInX = FALSE;
851 pDCB->fOutX = FALSE;
852 pDCB->fOutxCtsFlow = FALSE;
853 pDCB->fOutxDsrFlow = FALSE;
854 pDCB->fDtrControl = DTR_CONTROL_ENABLE;
855 pDCB->fRtsControl = RTS_CONTROL_ENABLE;
856 break;
857
858 case L'X':
859 pDCB->fInX = TRUE;
860 pDCB->fOutX = TRUE;
861 pDCB->fOutxCtsFlow = FALSE;
862 pDCB->fOutxDsrFlow = FALSE;
863 pDCB->fDtrControl = DTR_CONTROL_ENABLE;
864 pDCB->fRtsControl = RTS_CONTROL_ENABLE;
865 break;
866
867 case L'P':
868 pDCB->fInX = FALSE;
869 pDCB->fOutX = FALSE;
870 pDCB->fOutxCtsFlow = TRUE;
871 pDCB->fOutxDsrFlow = TRUE;
872 pDCB->fDtrControl = DTR_CONTROL_HANDSHAKE;
873 pDCB->fRtsControl = RTS_CONTROL_HANDSHAKE;
874 break;
875
876 default:
877 /* Unsupported */
878 return FALSE;
879 }
880 if (*argStr) argStr++;
881
882 /* This should be the end of the string */
883 while (*argStr == L' ') argStr++;
884 if (*argStr) return FALSE;
885
886 /* If stop bits were not specified, a default is always supplied */
887 if (!stop)
888 {
889 if (pDCB->BaudRate == 110)
890 pDCB->StopBits = TWOSTOPBITS;
891 else
892 pDCB->StopBits = ONESTOPBIT;
893 }
894 return TRUE;
895 }
896
897 /*
898 * Build a DCB using the new style settings string.
899 * eg: "baud=9600 parity=n data=8 stop=1 xon=on to=on"
900 *
901 * See dll/win32/kernel32/wine/comm.c!COMM_BuildNewCommDCB()
902 * for more information.
903 */
904 static BOOL
BuildNewCommDCB(OUT LPDCB pDCB,OUT LPCOMMTIMEOUTS pCommTimeouts,IN PCWSTR ArgStr)905 BuildNewCommDCB(
906 OUT LPDCB pDCB,
907 OUT LPCOMMTIMEOUTS pCommTimeouts,
908 IN PCWSTR ArgStr)
909 {
910 PCWSTR argStr = ArgStr;
911 BOOL baud = FALSE, stop = FALSE;
912 BYTE value;
913
914 while (argStr && *argStr)
915 {
916 while (*argStr == L' ') argStr++;
917 if (!*argStr) break;
918
919 if (_wcsnicmp(argStr, L"BAUD=", 5) == 0)
920 {
921 baud = TRUE;
922 argStr = ParseBaudRate(argStr+5, &pDCB->BaudRate);
923 if (!argStr) return FALSE;
924 }
925 else if (_wcsnicmp(argStr, L"PARITY=", 7) == 0)
926 {
927 // Default: EVENPARITY
928 argStr = ParseParity(argStr+7, &pDCB->Parity);
929 if (!argStr) return FALSE;
930 }
931 else if (_wcsnicmp(argStr, L"DATA=", 5) == 0)
932 {
933 // Default: 7
934 argStr = ParseByteSize(argStr+5, &pDCB->ByteSize);
935 if (!argStr) return FALSE;
936 }
937 else if (_wcsnicmp(argStr, L"STOP=", 5) == 0)
938 {
939 // Default: 1, or 2 for BAUD=110
940 stop = TRUE;
941 argStr = ParseStopBits(argStr+5, &pDCB->StopBits);
942 if (!argStr) return FALSE;
943 }
944 else if (_wcsnicmp(argStr, L"TO=", 3) == 0) // TO=ON|OFF
945 {
946 /* Only total time-outs are get/set by Windows' MODE.COM */
947 argStr = ParseModes(argStr+3, &value);
948 if (!argStr) return FALSE;
949 if (value == 0) // OFF
950 {
951 pCommTimeouts->ReadTotalTimeoutConstant = 0;
952 pCommTimeouts->WriteTotalTimeoutConstant = 0;
953 }
954 else if (value == 1) // ON
955 {
956 pCommTimeouts->ReadTotalTimeoutConstant = 60000;
957 pCommTimeouts->WriteTotalTimeoutConstant = 60000;
958 }
959 else
960 {
961 return FALSE;
962 }
963 }
964 else if (_wcsnicmp(argStr, L"XON=", 4) == 0) // XON=ON|OFF
965 {
966 argStr = ParseModes(argStr+4, &value);
967 if (!argStr) return FALSE;
968 if ((value == 0) || (value == 1))
969 {
970 pDCB->fOutX = value;
971 pDCB->fInX = value;
972 }
973 else
974 {
975 return FALSE;
976 }
977 }
978 else if (_wcsnicmp(argStr, L"ODSR=", 5) == 0) // ODSR=ON|OFF
979 {
980 value = 0;
981 argStr = ParseModes(argStr+5, &value);
982 if (!argStr) return FALSE;
983 if ((value == 0) || (value == 1))
984 pDCB->fOutxDsrFlow = value;
985 else
986 return FALSE;
987 }
988 else if (_wcsnicmp(argStr, L"OCTS=", 5) == 0) // OCTS=ON|OFF
989 {
990 value = 0;
991 argStr = ParseModes(argStr+5, &value);
992 if (!argStr) return FALSE;
993 if ((value == 0) || (value == 1))
994 pDCB->fOutxCtsFlow = value;
995 else
996 return FALSE;
997 }
998 else if (_wcsnicmp(argStr, L"DTR=", 4) == 0) // DTR=ON|OFF|HS
999 {
1000 value = 0;
1001 argStr = ParseModes(argStr+4, &value);
1002 if (!argStr) return FALSE;
1003 if ((value == 0) || (value == 1) || (value == 2))
1004 pDCB->fDtrControl = value;
1005 else
1006 return FALSE;
1007 }
1008 else if (_wcsnicmp(argStr, L"RTS=", 4) == 0) // RTS=ON|OFF|HS|TG
1009 {
1010 value = 0;
1011 argStr = ParseModes(argStr+4, &value);
1012 if (!argStr) return FALSE;
1013 if ((value == 0) || (value == 1) || (value == 2) || (value == 3))
1014 pDCB->fRtsControl = value;
1015 else
1016 return FALSE;
1017 }
1018 else if (_wcsnicmp(argStr, L"IDSR=", 5) == 0) // IDSR=ON|OFF
1019 {
1020 value = 0;
1021 argStr = ParseModes(argStr+5, &value);
1022 if (!argStr) return FALSE;
1023 if ((value == 0) || (value == 1))
1024 pDCB->fDsrSensitivity = value;
1025 else
1026 return FALSE;
1027 }
1028 else
1029 {
1030 return FALSE;
1031 }
1032 }
1033
1034 /* If stop bits were not specified, a default is always supplied */
1035 if (!stop)
1036 {
1037 if (baud && pDCB->BaudRate == 110)
1038 pDCB->StopBits = TWOSTOPBITS;
1039 else
1040 pDCB->StopBits = ONESTOPBIT;
1041 }
1042 return TRUE;
1043 }
1044
SetSerialState(INT nPortNum,IN PCWSTR ArgStr)1045 int SetSerialState(INT nPortNum, IN PCWSTR ArgStr)
1046 {
1047 BOOL Success;
1048 DCB dcb;
1049 COMMTIMEOUTS CommTimeouts;
1050
1051 if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
1052 {
1053 // TODO: Error message?
1054 return 0;
1055 }
1056
1057 /*
1058 * Check whether we should use the old or the new MODE syntax:
1059 * in the old syntax, the separators are both spaces and commas.
1060 */
1061 if (wcschr(ArgStr, L','))
1062 Success = BuildOldCommDCB(&dcb, ArgStr);
1063 else
1064 Success = BuildNewCommDCB(&dcb, &CommTimeouts, ArgStr);
1065
1066 if (!Success)
1067 {
1068 ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
1069 return 1;
1070 }
1071
1072 SerialPortQuery(nPortNum, &dcb, &CommTimeouts, TRUE);
1073 ShowSerialStatus(nPortNum);
1074
1075 return 0;
1076 }
1077
1078
1079 /*****************************************************************************\
1080 ** E N T R Y P O I N T **
1081 \*****************************************************************************/
1082
1083 static PCWSTR
FindPortNum(PCWSTR argStr,PINT PortNum)1084 FindPortNum(PCWSTR argStr, PINT PortNum)
1085 {
1086 PWSTR endptr = NULL;
1087
1088 *PortNum = wcstol(argStr, &endptr, 10);
1089 if (endptr == argStr)
1090 {
1091 *PortNum = -1;
1092 return NULL;
1093 }
1094
1095 return endptr;
1096 }
1097
EnumerateDevices(VOID)1098 int EnumerateDevices(VOID)
1099 {
1100 PWSTR Buffer, ptr;
1101 PCWSTR argStr;
1102 DWORD dwLen = MAX_PATH;
1103 INT nPortNum;
1104
1105 /* Pre-allocate a buffer for QueryDosDeviceW() */
1106 Buffer = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
1107 if (Buffer == NULL)
1108 {
1109 /* We failed, bail out */
1110 ConPuts(StdErr, L"ERROR: Not enough memory\n");
1111 return 0;
1112 }
1113
1114 for (;;)
1115 {
1116 *Buffer = UNICODE_NULL;
1117 if (QueryDosDeviceW(NULL, Buffer, dwLen))
1118 break;
1119
1120 if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
1121 {
1122 /* We failed, bail out */
1123 ConPrintf(StdErr, L"ERROR: QueryDosDeviceW(...) failed: 0x%lx\n", GetLastError());
1124 HeapFree(GetProcessHeap(), 0, Buffer);
1125 return 0;
1126 }
1127
1128 /* The buffer was too small, try to re-allocate it */
1129 dwLen *= 2;
1130 ptr = HeapReAlloc(GetProcessHeap(), 0, Buffer, dwLen * sizeof(WCHAR));
1131 if (ptr == NULL)
1132 {
1133 /* We failed, bail out */
1134 ConPuts(StdErr, L"ERROR: Not enough memory\n");
1135 HeapFree(GetProcessHeap(), 0, Buffer);
1136 return 0;
1137 }
1138 Buffer = ptr;
1139 }
1140
1141 for (ptr = Buffer; *ptr != UNICODE_NULL; ptr += wcslen(ptr) + 1)
1142 {
1143 if (_wcsnicmp(ptr, L"COM", 3) == 0)
1144 {
1145 argStr = FindPortNum(ptr+3, &nPortNum);
1146 if (!argStr || *argStr || nPortNum == -1)
1147 continue;
1148
1149 // ConResPrintf(StdOut, IDS_QUERY_SERIAL_FOUND, ptr);
1150 ShowSerialStatus(nPortNum);
1151 }
1152 else if (_wcsicmp(ptr, L"PRN") == 0)
1153 {
1154 ConResPrintf(StdOut, IDS_QUERY_PRINTER_FOUND, ptr);
1155 }
1156 else if (_wcsnicmp(ptr, L"LPT", 3) == 0)
1157 {
1158 argStr = FindPortNum(ptr+3, &nPortNum);
1159 if (!argStr || *argStr || nPortNum == -1)
1160 continue;
1161
1162 // ConResPrintf(StdOut, IDS_QUERY_PARALLEL_FOUND, ptr);
1163 ShowParallelStatus(nPortNum);
1164 }
1165 else if (_wcsicmp(ptr, L"AUX") == 0 || _wcsicmp(ptr, L"NUL") == 0)
1166 {
1167 ConResPrintf(StdOut, IDS_QUERY_DOSDEV_FOUND, ptr);
1168 }
1169 else
1170 {
1171 // ConResPrintf(StdOut, IDS_QUERY_MISC_FOUND, ptr);
1172 }
1173 }
1174
1175 ShowConsoleStatus();
1176
1177 /* Free the buffer and return success */
1178 HeapFree(GetProcessHeap(), 0, Buffer);
1179 return 1;
1180 }
1181
wmain(int argc,WCHAR * argv[])1182 int wmain(int argc, WCHAR* argv[])
1183 {
1184 int ret = 0;
1185 int arg;
1186 SIZE_T ArgStrSize;
1187 PCWSTR ArgStr, argStr;
1188
1189 INT nPortNum;
1190
1191 /* Initialize the Console Standard Streams */
1192 ConInitStdStreams();
1193
1194 /*
1195 * MODE.COM has a very peculiar way of parsing its arguments,
1196 * as they can be even not separated by any space. This extreme
1197 * behaviour certainly is present for backwards compatibility
1198 * with the oldest versions of the utility present on MS-DOS.
1199 *
1200 * For example, such a command:
1201 * "MODE.COM COM1baud=9600parity=ndata=8stop=1xon=onto=on"
1202 * will be correctly understood as:
1203 * "MODE.COM COM1 baud=9600 parity=n data=8 stop=1 xon=on to=on"
1204 *
1205 * Note also that the "/STATUS" switch is actually really "/STA".
1206 *
1207 * However we will not use GetCommandLine() because we do not want
1208 * to deal with the prepended application path and try to find
1209 * where the arguments start. Our approach here will consist in
1210 * flattening the arguments vector.
1211 */
1212 ArgStrSize = 0;
1213
1214 /* Compute the space needed for the new string, and allocate it */
1215 for (arg = 1; arg < argc; arg++)
1216 {
1217 ArgStrSize += wcslen(argv[arg]) + 1; // 1 for space
1218 }
1219 ArgStr = HeapAlloc(GetProcessHeap(), 0, (ArgStrSize + 1) * sizeof(WCHAR));
1220 if (ArgStr == NULL)
1221 {
1222 ConPuts(StdErr, L"ERROR: Not enough memory\n");
1223 return 1;
1224 }
1225
1226 /* Copy the contents and NULL-terminate the string */
1227 argStr = ArgStr;
1228 for (arg = 1; arg < argc; arg++)
1229 {
1230 wcscpy((PWSTR)argStr, argv[arg]);
1231 argStr += wcslen(argv[arg]);
1232 *(PWSTR)argStr++ = L' ';
1233 }
1234 *(PWSTR)argStr = L'\0';
1235
1236 /* Parse the command line */
1237 argStr = ArgStr;
1238
1239 while (*argStr == L' ') argStr++;
1240 if (!*argStr) goto show_status;
1241
1242 if (wcsstr(argStr, L"/?") || wcsstr(argStr, L"-?"))
1243 {
1244 ConResPuts(StdOut, IDS_USAGE);
1245 goto Quit;
1246 }
1247 else if (_wcsnicmp(argStr, L"/STA", 4) == 0)
1248 {
1249 /* Skip this parameter */
1250 while (*argStr != L' ') argStr++;
1251 /* Skip any delimiter */
1252 while (*argStr == L' ') argStr++;
1253
1254 /* The presence of any other parameter is invalid */
1255 if (*argStr)
1256 goto invalid_parameter;
1257
1258 goto show_status;
1259 }
1260 else if (_wcsnicmp(argStr, L"LPT", 3) == 0)
1261 {
1262 argStr = FindPortNum(argStr+3, &nPortNum);
1263 if (!argStr || nPortNum == -1)
1264 goto invalid_parameter;
1265
1266 if (*argStr == L':') argStr++;
1267 while (*argStr == L' ') argStr++;
1268
1269 if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1270 ret = ShowParallelStatus(nPortNum);
1271 else
1272 ConPuts(StdErr, L"ERROR: LPT port redirection is not implemented!\n");
1273 // TODO: Implement setting LPT port redirection using SetParallelState().
1274 goto Quit;
1275 }
1276 else if (_wcsnicmp(argStr, L"COM", 3) == 0)
1277 {
1278 argStr = FindPortNum(argStr+3, &nPortNum);
1279 if (!argStr || nPortNum == -1)
1280 goto invalid_parameter;
1281
1282 if (*argStr == L':') argStr++;
1283 while (*argStr == L' ') argStr++;
1284
1285 if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1286 ret = ShowSerialStatus(nPortNum);
1287 else
1288 ret = SetSerialState(nPortNum, argStr);
1289 goto Quit;
1290 }
1291 else if (_wcsnicmp(argStr, L"CON", 3) == 0)
1292 {
1293 argStr += 3;
1294
1295 if (*argStr == L':') argStr++;
1296 while (*argStr == L' ') argStr++;
1297
1298 if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1299 {
1300 ret = ShowConsoleStatus();
1301 }
1302 else if ( (_wcsnicmp(argStr, L"CP", 2) == 0 && (argStr += 2)) ||
1303 (_wcsnicmp(argStr, L"CODEPAGE", 8) == 0 && (argStr += 8)) )
1304 {
1305 while (*argStr == L' ') argStr++;
1306
1307 if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
1308 ret = ShowConsoleCPStatus();
1309 else
1310 ret = SetConsoleCPState(argStr);
1311 }
1312 else
1313 {
1314 ret = SetConsoleState(argStr);
1315 }
1316 goto Quit;
1317 }
1318 // else if (wcschr(argStr, L','))
1319 else
1320 {
1321 /* Old syntax: MODE [COLS],[LINES] */
1322 ret = SetConsoleStateOld(argStr);
1323 goto Quit;
1324 }
1325
1326 show_status:
1327 EnumerateDevices();
1328 goto Quit;
1329
1330 invalid_parameter:
1331 ConResPrintf(StdErr, IDS_ERROR_INVALID_PARAMETER, ArgStr);
1332 goto Quit;
1333
1334 Quit:
1335 /* Free the string and quit */
1336 HeapFree(GetProcessHeap(), 0, (PWSTR)ArgStr);
1337 return ret;
1338 }
1339