[MODE] Improve few points in the MODE command. Adapted from a patch by Katayama Hirof...
[reactos.git] / base / applications / cmdutils / mode / mode.c
index aa49431..2981edf 100644 (file)
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
+/*
+ * COPYRIGHT:       See COPYING in the top level directory
+ * PROJECT:         ReactOS Mode Utility
+ * FILE:            base/applications/cmdutils/mode/mode.c
+ * PURPOSE:         Provides fast mode setup for DOS devices.
+ * PROGRAMMERS:     Robert Dickenson
+ *                  Hermes Belusca-Maito
+ */
 
-#include <windows.h>
 #include <stdio.h>
 
+#include <windef.h>
+#include <winbase.h>
+#include <winuser.h>
+#include <wincon.h>
+
+#include <conutils.h>
+
+#include "resource.h"
+
 #define MAX_PORTNAME_LEN 20
 #define MAX_COMPORT_NUM  10
-#define MAX_COMPARAM_LEN 20
 
-#define NUM_ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
 #define ASSERT(a)
 
-const WCHAR* const usage_strings[] =
-{
-    L"Device Status:     MODE [device] [/STATUS]",
-    L"Select code page:  MODE CON[:] CP SELECT=yyy",
-    L"Code page status:  MODE CON[:] CP [/STATUS]",
-    L"Display mode:      MODE CON[:] [COLS=c] [LINES=n]",
-    L"Typematic rate:    MODE CON[:] [RATE=r DELAY=d]",
-    L"Redirect printing: MODE LPTn[:]=COMm[:]",
-    L"Serial port:       MODE COMm[:] [BAUD=b] [PARITY=p] [DATA=d] [STOP=s]\n" \
-    L"                            [to=on|off] [xon=on|off] [odsr=on|off]\n"    \
-    L"                            [octs=on|off] [dtr=on|off|hs]\n"             \
-    L"                            [rts=on|off|hs|tg] [idsr=on|off]",
-};
-
-const WCHAR* const parity_strings[] =
+/*** For fixes, see also network/net/main.c ***/
+// VOID PrintPadding(...)
+VOID
+__cdecl
+UnderlinedResPrintf(
+    IN PCON_STREAM Stream,
+    IN UINT uID,
+    ...)
 {
-    L"None",    // default
-    L"Odd",     // only symbol in this set to have a 'd' in it
-    L"Even",    // ... 'v' in it
-    L"Mark",    // ... 'm' in it
-    L"Space"    // ... 's' and/or a 'c' in it
-};
+    INT Len;
+    va_list args;
+
+#define MAX_BUFFER_SIZE 4096
+    INT i;
+    WCHAR szMsgBuffer[MAX_BUFFER_SIZE];
 
-const WCHAR* const control_strings[] = { L"OFF", L"ON", L"HANDSHAKE", L"TOGGLE" };
+    va_start(args, uID);
+    Len = ConResPrintfV(Stream, uID, args);
+    va_end(args);
 
-const WCHAR* const stopbit_strings[] = { L"1", L"1.5", L"2" };
+    ConPuts(Stream, L"\n");
+    for (i = 0; i < Len; i++)
+         szMsgBuffer[i] = L'-';
+    szMsgBuffer[Len] = UNICODE_NULL;
 
+    ConStreamWrite(Stream, szMsgBuffer, Len);
+}
 
-int Usage()
+int QueryDevices(VOID)
 {
-    int i;
+    PWSTR Buffer, ptr;
+    DWORD dwLen = MAX_PATH;
 
-    wprintf(L"\nConfigures system devices.\n\n");
-    for (i = 0; i < NUM_ELEMENTS(usage_strings); i++)
+    /* Pre-allocate a buffer for QueryDosDeviceW() */
+    Buffer = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
+    if (Buffer == NULL)
     {
-        wprintf(L"%s\n", usage_strings[i]);
+        /* We failed, bail out */
+        wprintf(L"ERROR: Not enough memory\n");
+        return 0;
     }
-    wprintf(L"\n");
-    return 0;
-}
 
-int QueryDevices()
-{
-    WCHAR buffer[20240];
-    int len;
-    WCHAR* ptr = buffer;
-
-    *ptr = L'\0';
-    if (QueryDosDeviceW(NULL, buffer, NUM_ELEMENTS(buffer)))
+    for (;;)
     {
-        while (*ptr != L'\0')
+        *Buffer = UNICODE_NULL;
+        if (QueryDosDeviceW(NULL, Buffer, dwLen))
+            break;
+
+        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
         {
-            len = wcslen(ptr);
-            if (wcsstr(ptr, L"COM"))
-            {
-                wprintf(L"    Found serial device - %s\n", ptr);
-            }
-            else if (wcsstr(ptr, L"PRN"))
-            {
-                wprintf(L"    Found printer device - %s\n", ptr);
-            }
-            else if (wcsstr(ptr, L"LPT"))
-            {
-                wprintf(L"    Found parallel device - %s\n", ptr);
-            }
-            else
-            {
-                // wprintf(L"    Found other device - %s\n", ptr);
-            }
-            ptr += (len+1);
+            /* We failed, bail out */
+            wprintf(L"ERROR: QueryDosDeviceW(...) failed: 0x%lx\n", GetLastError());
+            HeapFree(GetProcessHeap(), 0, Buffer);
+            return 0;
+        }
+
+        /* The buffer was too small, try to re-allocate it */
+        dwLen *= 2;
+        ptr = HeapReAlloc(GetProcessHeap(), 0, Buffer, dwLen * sizeof(WCHAR));
+        if (ptr == NULL)
+        {
+            /* We failed, bail out */
+            wprintf(L"ERROR: Not enough memory\n");
+            HeapFree(GetProcessHeap(), 0, Buffer);
+            return 0;
         }
+        Buffer = ptr;
     }
-    else
+
+    for (ptr = Buffer; *ptr != UNICODE_NULL; ptr += wcslen(ptr) + 1)
     {
-        wprintf(L"    ERROR: QueryDosDeviceW(...) failed: 0x%lx\n", GetLastError());
+        if (wcsstr(ptr, L"COM"))
+        {
+            ConResPrintf(StdOut, IDS_QUERY_SERIAL_FOUND, ptr);
+        }
+        else if (wcsstr(ptr, L"PRN"))
+        {
+            ConResPrintf(StdOut, IDS_QUERY_PRINTER_FOUND, ptr);
+        }
+        else if (wcsstr(ptr, L"LPT"))
+        {
+            ConResPrintf(StdOut, IDS_QUERY_PARALLEL_FOUND, ptr);
+        }
+        else if (wcsstr(ptr, L"AUX") || wcsstr(ptr, L"NUL"))
+        {
+            ConResPrintf(StdOut, IDS_QUERY_DOSDEV_FOUND, ptr);
+        }
+        else
+        {
+            // ConResPrintf(StdOut, IDS_QUERY_MISC_FOUND, ptr);
+        }
     }
+
+    /* Free the buffer and return success */
+    HeapFree(GetProcessHeap(), 0, Buffer);
     return 1;
 }
 
-int ShowParallelStatus(int nPortNum)
+int ShowParallelStatus(INT nPortNum)
 {
     WCHAR buffer[250];
     WCHAR szPortName[MAX_PORTNAME_LEN];
 
     swprintf(szPortName, L"LPT%d", nPortNum);
-    wprintf(L"\nStatus for device LPT%d:\n", nPortNum);
-    wprintf(L"-----------------------\n");
-    if (QueryDosDeviceW(szPortName, buffer, NUM_ELEMENTS(buffer)))
+
+    ConPuts(StdOut, L"\n");
+    UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, szPortName);
+    ConPuts(StdOut, L"\n");
+
+    if (QueryDosDeviceW(szPortName, buffer, ARRAYSIZE(buffer)))
     {
-        WCHAR* ptr = wcsrchr(buffer, L'\\');
+        PWSTR ptr = wcsrchr(buffer, L'\\');
         if (ptr != NULL)
         {
-            if (0 == wcscmp(szPortName, ++ptr))
-            {
-                wprintf(L"    Printer output is not being rerouted.\n");
-            }
+            if (_wcsicmp(szPortName, ++ptr) == 0)
+                ConResPuts(StdOut, IDS_PRINTER_OUTPUT_NOT_REROUTED);
             else
-            {
-                wprintf(L"    Printer output is being rerouted to serial port %s\n", ptr);
-            }
+                ConResPrintf(StdOut, IDS_PRINTER_OUTPUT_REROUTED_SERIAL, ptr);
+
             return 0;
         }
         else
@@ -139,41 +172,387 @@ int ShowParallelStatus(int nPortNum)
     }
     else
     {
-        wprintf(L"    ERROR: QueryDosDeviceW(%s) failed: 0x%lx\n", szPortName, GetLastError());
+        wprintf(L"ERROR: QueryDosDeviceW(%s) failed: 0x%lx\n", szPortName, GetLastError());
     }
+
     return 1;
 }
 
-int ShowConsoleStatus()
+int SetParallelState(INT nPortNum)
 {
-    DWORD dwKbdDelay;
-    DWORD dwKbdSpeed;
-    CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo;
-    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+    WCHAR szPortName[MAX_PORTNAME_LEN];
+    WCHAR szTargetPath[MAX_PORTNAME_LEN];
 
-    wprintf(L"\nStatus for device CON:\n");
-    wprintf(L"-----------------------\n");
-    if (GetConsoleScreenBufferInfo(hConsoleOutput, &ConsoleScreenBufferInfo))
+    swprintf(szPortName, L"LPT%d", nPortNum);
+    swprintf(szTargetPath, L"COM%d", nPortNum);
+    if (!DefineDosDeviceW(DDD_REMOVE_DEFINITION, szPortName, szTargetPath))
     {
-        wprintf(L"    Lines:          %d\n", ConsoleScreenBufferInfo.dwSize.Y);
-        wprintf(L"    Columns:        %d\n", ConsoleScreenBufferInfo.dwSize.X);
+        wprintf(L"SetParallelState(%d) - DefineDosDevice(%s) failed: 0x%lx\n", nPortNum, szPortName, GetLastError());
     }
-    if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
+
+    ShowParallelStatus(nPortNum);
+    return 0;
+}
+
+
+static PCWSTR
+ParseNumber(PCWSTR argStr, PDWORD Number)
+{
+    INT value, skip = 0;
+
+    value = swscanf(argStr, L"%lu%n", Number, &skip);
+    if (!value) return NULL;
+    argStr += skip;
+    return argStr;
+}
+
+
+/*
+    \??\COM1
+    \Device\NamedPipe\Spooler\LPT1
+BOOL DefineDosDevice(
+  DWORD dwFlags,         // options
+  LPCTSTR lpDeviceName,  // device name
+  LPCTSTR lpTargetPath   // path string
+);
+DWORD QueryDosDevice(
+  LPCTSTR lpDeviceName, // MS-DOS device name string
+  LPTSTR lpTargetPath,  // query results buffer
+  DWORD ucchMax         // maximum size of buffer
+);
+ */
+
+
+/*****************************************************************************\
+ **                      C O N S O L E   H E L P E R S                      **
+\*****************************************************************************/
+
+int ShowConsoleStatus(VOID)
+{
+    HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    DWORD dwKbdDelay, dwKbdSpeed;
+
+    ConPuts(StdOut, L"\n");
+    UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, L"CON");
+    ConPuts(StdOut, L"\n");
+
+    if (GetConsoleScreenBufferInfo(hConOut, &csbi))
+    {
+        ConResPrintf(StdOut, IDS_CONSOLE_STATUS_LINES, csbi.dwSize.Y);
+        ConResPrintf(StdOut, IDS_CONSOLE_STATUS_COLS , csbi.dwSize.X);
+    }
+    if (SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
     {
-        wprintf(L"    Keyboard delay: %ld\n", dwKbdDelay);
+        ConResPrintf(StdOut, IDS_CONSOLE_KBD_RATE, dwKbdSpeed);
     }
-    if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
+    if (SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
     {
-        wprintf(L"    Keyboard rate:  %ld\n", dwKbdSpeed);
+        ConResPrintf(StdOut, IDS_CONSOLE_KBD_DELAY, dwKbdDelay);
     }
-    wprintf(L"    Code page:      %d\n", GetConsoleOutputCP());
+    ConResPrintf(StdOut, IDS_CONSOLE_CODEPAGE, GetConsoleOutputCP());
+    return 0;
+}
+
+int ShowConsoleCPStatus(VOID)
+{
+    ConPuts(StdOut, L"\n");
+    UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, L"CON");
+    ConPuts(StdOut, L"\n");
+
+    ConResPrintf(StdOut, IDS_CONSOLE_CODEPAGE, GetConsoleOutputCP());
     return 0;
 }
 
-static
-BOOL SerialPortQuery(int nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOOL bWrite)
+static VOID
+ClearScreen(
+    IN HANDLE hConOut,
+    IN PCONSOLE_SCREEN_BUFFER_INFO pcsbi)
 {
-    BOOL result;
+    COORD coPos;
+    DWORD dwWritten;
+
+    coPos.X = 0;
+    coPos.Y = 0;
+    FillConsoleOutputAttribute(hConOut, pcsbi->wAttributes,
+                               pcsbi->dwSize.X * pcsbi->dwSize.Y,
+                               coPos, &dwWritten);
+    FillConsoleOutputCharacterW(hConOut, L' ',
+                                pcsbi->dwSize.X * pcsbi->dwSize.Y,
+                                coPos, &dwWritten);
+    SetConsoleCursorPosition(hConOut, coPos);
+}
+
+/*
+ * See, or adjust if needed, subsystems/mvdm/ntvdm/console/video.c!ResizeTextConsole()
+ * for more information.
+ */
+static BOOL
+ResizeTextConsole(
+    IN HANDLE hConOut,
+    IN OUT PCONSOLE_SCREEN_BUFFER_INFO pcsbi,
+    IN COORD Resolution)
+{
+    BOOL Success;
+    SHORT Width, Height;
+    SMALL_RECT ConRect;
+
+    /*
+     * Use this trick to effectively resize the console buffer and window,
+     * because:
+     * - SetConsoleScreenBufferSize fails if the new console screen buffer size
+     *   is smaller than the current console window size, and:
+     * - SetConsoleWindowInfo fails if the new console window size is larger
+     *   than the current console screen buffer size.
+     */
+
+    /* Resize the screen buffer only if needed */
+    if (Resolution.X != pcsbi->dwSize.X || Resolution.Y != pcsbi->dwSize.Y)
+    {
+        Width  = pcsbi->srWindow.Right  - pcsbi->srWindow.Left + 1;
+        Height = pcsbi->srWindow.Bottom - pcsbi->srWindow.Top  + 1;
+
+        /*
+         * If the current console window is too large for
+         * the new screen buffer, resize it first.
+         */
+        if (Width > Resolution.X || Height > Resolution.Y)
+        {
+            /*
+             * NOTE: This is not a problem if we move the window back to (0,0)
+             * because when we resize the screen buffer, the window will move back
+             * to where the cursor is. Or, if the screen buffer is not resized,
+             * when we readjust again the window, we will move back to a correct
+             * position. This is what we wanted after all...
+             */
+            ConRect.Left   = ConRect.Top = 0;
+            ConRect.Right  = ConRect.Left + min(Width , Resolution.X) - 1;
+            ConRect.Bottom = ConRect.Top  + min(Height, Resolution.Y) - 1;
+
+            Success = SetConsoleWindowInfo(hConOut, TRUE, &ConRect);
+            if (!Success) return FALSE;
+        }
+
+        /*
+         * Now resize the screen buffer.
+         *
+         * SetConsoleScreenBufferSize automatically takes into account the current
+         * cursor position when it computes starting which row it should copy text
+         * when resizing the screen buffer, and scrolls the console window such that
+         * the cursor is placed in it again. We therefore do not need to care about
+         * the cursor position and do the maths ourselves.
+         */
+        Success = SetConsoleScreenBufferSize(hConOut, Resolution);
+        if (!Success) return FALSE;
+
+        /*
+         * Setting a new screen buffer size can change other information,
+         * so update the console screen buffer information.
+         */
+        GetConsoleScreenBufferInfo(hConOut, pcsbi);
+    }
+
+    /* Always resize the console window within the permitted maximum size */
+    Width  = min(Resolution.X, pcsbi->dwMaximumWindowSize.X);
+    Height = min(Resolution.Y, pcsbi->dwMaximumWindowSize.Y);
+    ConRect.Left   = 0;
+    ConRect.Right  = ConRect.Left + Width - 1;
+    ConRect.Bottom = max(pcsbi->dwCursorPosition.Y, Height - 1);
+    ConRect.Top    = ConRect.Bottom - Height + 1;
+
+    SetConsoleWindowInfo(hConOut, TRUE, &ConRect);
+
+    /* Update the console screen buffer information */
+    GetConsoleScreenBufferInfo(hConOut, pcsbi);
+    return TRUE;
+}
+
+int SetConsoleStateOld(IN PCWSTR ArgStr)
+{
+    PCWSTR argStr = ArgStr;
+
+    HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    COORD Resolution;
+    DWORD value;
+
+    if (!GetConsoleScreenBufferInfo(hConOut, &csbi))
+    {
+        // TODO: Error message?
+        return 0;
+    }
+
+    Resolution = csbi.dwSize;
+
+    /* Parse the column number (only MANDATORY argument) */
+    value = 0;
+    argStr = ParseNumber(argStr, &value);
+    if (!argStr) goto invalid_parameter;
+    Resolution.X = (SHORT)value;
+
+    /* Parse the line number (OPTIONAL argument) */
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+    if (*argStr++ != L',') goto invalid_parameter;
+    while (*argStr == L' ') argStr++;
+
+    value = 0;
+    argStr = ParseNumber(argStr, &value);
+    if (!argStr) goto invalid_parameter;
+    Resolution.Y = (SHORT)value;
+
+    /* This should be the end of the string */
+    while (*argStr == L' ') argStr++;
+    if (*argStr) goto invalid_parameter;
+
+Quit:
+    ClearScreen(hConOut, &csbi);
+    if (!ResizeTextConsole(hConOut, &csbi, Resolution))
+        wprintf(L"The screen cannot be set to the number of lines and columns specified.\n");
+
+    return 0;
+
+invalid_parameter:
+    wprintf(L"Invalid parameter - %s\n", ArgStr);
+    return 1;
+}
+
+int SetConsoleState(IN PCWSTR ArgStr)
+{
+    PCWSTR argStr = ArgStr;
+    BOOL dispMode = FALSE, kbdMode = FALSE;
+
+    HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    COORD Resolution;
+    DWORD dwKbdDelay, dwKbdSpeed;
+    DWORD value;
+
+    if (!GetConsoleScreenBufferInfo(hConOut, &csbi))
+    {
+        // TODO: Error message?
+        return 0;
+    }
+    if (!SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &dwKbdDelay, 0))
+    {
+        // TODO: Error message?
+        return 0;
+    }
+    if (!SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &dwKbdSpeed, 0))
+    {
+        // TODO: Error message?
+        return 0;
+    }
+
+    Resolution = csbi.dwSize;
+
+    while (argStr && *argStr)
+    {
+        while (*argStr == L' ') argStr++;
+        if (!*argStr) break;
+
+        if (!kbdMode && _wcsnicmp(argStr, L"COLS=", 5) == 0)
+        {
+            dispMode = TRUE;
+
+            value = 0;
+            argStr = ParseNumber(argStr+5, &value);
+            if (!argStr) goto invalid_parameter;
+            Resolution.X = (SHORT)value;
+        }
+        else if (!kbdMode && _wcsnicmp(argStr, L"LINES=", 6) == 0)
+        {
+            dispMode = TRUE;
+
+            value = 0;
+            argStr = ParseNumber(argStr+6, &value);
+            if (!argStr) goto invalid_parameter;
+            Resolution.Y = (SHORT)value;
+        }
+        else if (!dispMode && _wcsnicmp(argStr, L"RATE=", 5) == 0)
+        {
+            kbdMode = TRUE;
+
+            argStr = ParseNumber(argStr+5, &dwKbdSpeed);
+            if (!argStr) goto invalid_parameter;
+        }
+        else if (!dispMode && _wcsnicmp(argStr, L"DELAY=", 6) == 0)
+        {
+            kbdMode = TRUE;
+
+            argStr = ParseNumber(argStr+6, &dwKbdDelay);
+            if (!argStr) goto invalid_parameter;
+        }
+        else
+        {
+invalid_parameter:
+            wprintf(L"Invalid parameter - %s\n", ArgStr);
+            return 1;
+        }
+    }
+
+    if (dispMode)
+    {
+        ClearScreen(hConOut, &csbi);
+        if (!ResizeTextConsole(hConOut, &csbi, Resolution))
+            wprintf(L"The screen cannot be set to the number of lines and columns specified.\n");
+    }
+    else if (kbdMode)
+    {
+        /*
+         * Set the new keyboard settings. If those values are greater than
+         * their allowed range, they are automatically corrected as follows:
+         *   dwKbdSpeed = min(dwKbdSpeed, 31);
+         *   dwKbdDelay = (dwKbdDelay % 4);
+         */
+        SystemParametersInfoW(SPI_SETKEYBOARDDELAY, dwKbdDelay, NULL, 0);
+        // "Invalid keyboard delay."
+        SystemParametersInfoW(SPI_SETKEYBOARDSPEED, dwKbdSpeed, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
+        // "Invalid keyboard rate."
+    }
+
+    return 0;
+}
+
+int SetConsoleCPState(IN PCWSTR ArgStr)
+{
+    PCWSTR argStr = ArgStr;
+    DWORD CodePage = 0;
+
+    if ( (_wcsnicmp(argStr, L"SELECT=", 7) == 0 && (argStr += 7)) ||
+         (_wcsnicmp(argStr, L"SEL=", 4) == 0 && (argStr += 4)) )
+    {
+        argStr = ParseNumber(argStr, &CodePage);
+        if (!argStr) goto invalid_parameter;
+
+        /* This should be the end of the string */
+        while (*argStr == L' ') argStr++;
+        if (*argStr) goto invalid_parameter;
+
+        SetConsoleCP(CodePage);
+        SetConsoleOutputCP(CodePage);
+        // "The code page specified is not valid."
+        ShowConsoleCPStatus();
+    }
+    else
+    {
+invalid_parameter:
+        wprintf(L"Invalid parameter - %s\n", ArgStr);
+        return 1;
+    }
+
+    return 0;
+}
+
+
+/*****************************************************************************\
+ **                  S E R I A L   P O R T   H E L P E R S                  **
+\*****************************************************************************/
+
+static BOOL
+SerialPortQuery(INT nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOOL bWrite)
+{
+    BOOL Success;
     HANDLE hPort;
     WCHAR szPortName[MAX_PORTNAME_LEN];
 
@@ -182,7 +561,7 @@ BOOL SerialPortQuery(int nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOO
 
     swprintf(szPortName, L"COM%d", nPortNum);
     hPort = CreateFileW(szPortName,
-                        GENERIC_READ | GENERIC_WRITE,
+                        bWrite ? GENERIC_WRITE : GENERIC_READ,
                         0,     // exclusive
                         NULL,  // sec attr
                         OPEN_EXISTING,
@@ -196,356 +575,718 @@ BOOL SerialPortQuery(int nPortNum, LPDCB pDCB, LPCOMMTIMEOUTS pCommTimeouts, BOO
         return FALSE;
     }
 
-    result = bWrite ? SetCommState(hPort, pDCB)
-                    : GetCommState(hPort, pDCB);
-    if (!result)
+    Success = bWrite ? SetCommState(hPort, pDCB)
+                     : GetCommState(hPort, pDCB);
+    if (!Success)
     {
         wprintf(L"Failed to %s the status for device COM%d:\n", bWrite ? L"set" : L"get", nPortNum);
-        CloseHandle(hPort);
-        return FALSE;
+        goto Quit;
     }
 
-    result = bWrite ? SetCommTimeouts(hPort, pCommTimeouts)
-                    : GetCommTimeouts(hPort, pCommTimeouts);
-    if (!result)
+    Success = bWrite ? SetCommTimeouts(hPort, pCommTimeouts)
+                     : GetCommTimeouts(hPort, pCommTimeouts);
+    if (!Success)
     {
-        wprintf(L"Failed to %s Timeout status for device COM%d:\n", bWrite ? L"set" : L"get", nPortNum);
-        CloseHandle(hPort);
-        return FALSE;
+        wprintf(L"Failed to %s timeout status for device COM%d:\n", bWrite ? L"set" : L"get", nPortNum);
+        goto Quit;
     }
+
+Quit:
     CloseHandle(hPort);
-    return TRUE;
+    return Success;
 }
 
-int ShowSerialStatus(int nPortNum)
+int ShowSerialStatus(INT nPortNum)
 {
+    static const LPCWSTR parity_strings[] =
+    {
+        L"None",    // NOPARITY
+        L"Odd",     // ODDPARITY
+        L"Even",    // EVENPARITY
+        L"Mark",    // MARKPARITY
+        L"Space"    // SPACEPARITY
+    };
+    static const LPCWSTR control_strings[] = { L"OFF", L"ON", L"HANDSHAKE", L"TOGGLE" };
+    static const LPCWSTR stopbit_strings[] = { L"1", L"1.5", L"2" };
+
     DCB dcb;
     COMMTIMEOUTS CommTimeouts;
+    WCHAR szPortName[MAX_PORTNAME_LEN];
 
     if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
     {
         return 1;
     }
-    if (dcb.Parity > NUM_ELEMENTS(parity_strings))
+    if (dcb.Parity >= ARRAYSIZE(parity_strings))
     {
         wprintf(L"ERROR: Invalid value for Parity Bits %d:\n", dcb.Parity);
         dcb.Parity = 0;
     }
-    if (dcb.StopBits > NUM_ELEMENTS(stopbit_strings))
+    if (dcb.StopBits >= ARRAYSIZE(stopbit_strings))
     {
         wprintf(L"ERROR: Invalid value for Stop Bits %d:\n", dcb.StopBits);
         dcb.StopBits = 0;
     }
-    wprintf(L"\nStatus for device COM%d:\n", nPortNum);
-    wprintf(L"-----------------------\n");
-    wprintf(L"    Baud:            %ld\n", dcb.BaudRate);
-    wprintf(L"    Parity:          %s\n", parity_strings[dcb.Parity]);
-    wprintf(L"    Data Bits:       %d\n", dcb.ByteSize);
-    wprintf(L"    Stop Bits:       %s\n", stopbit_strings[dcb.StopBits]);
-    wprintf(L"    Timeout:         %s\n", CommTimeouts.ReadIntervalTimeout ? L"ON" : L"OFF");
-    wprintf(L"    XON/XOFF:        %s\n", dcb.fOutX           ? L"ON" : L"OFF");
-    wprintf(L"    CTS handshaking: %s\n", dcb.fOutxCtsFlow    ? L"ON" : L"OFF");
-    wprintf(L"    DSR handshaking: %s\n", dcb.fOutxDsrFlow    ? L"ON" : L"OFF");
-    wprintf(L"    DSR sensitivity: %s\n", dcb.fDsrSensitivity ? L"ON" : L"OFF");
-    wprintf(L"    DTR circuit:     %s\n", control_strings[dcb.fDtrControl]);
-    wprintf(L"    RTS circuit:     %s\n", control_strings[dcb.fRtsControl]);
+
+    swprintf(szPortName, L"COM%d", nPortNum);
+
+    ConPuts(StdOut, L"\n");
+    UnderlinedResPrintf(StdOut, IDS_DEVICE_STATUS_HEADER, szPortName);
+    ConPuts(StdOut, L"\n");
+
+    ConResPrintf(StdOut, IDS_COM_STATUS_BAUD, dcb.BaudRate);
+    ConResPrintf(StdOut, IDS_COM_STATUS_PARITY, parity_strings[dcb.Parity]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_DATA_BITS, dcb.ByteSize);
+    ConResPrintf(StdOut, IDS_COM_STATUS_STOP_BITS, stopbit_strings[dcb.StopBits]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_TIMEOUT,
+        control_strings[(CommTimeouts.ReadTotalTimeoutConstant  != 0) ||
+                        (CommTimeouts.WriteTotalTimeoutConstant != 0) ? 1 : 0]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_XON_XOFF,
+        control_strings[dcb.fOutX ? 1 : 0]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_CTS_HANDSHAKING,
+        control_strings[dcb.fOutxCtsFlow ? 1 : 0]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_DSR_HANDSHAKING,
+        control_strings[dcb.fOutxDsrFlow ? 1 : 0]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_DSR_SENSITIVITY,
+        control_strings[dcb.fDsrSensitivity ? 1 : 0]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_DTR_CIRCUIT, control_strings[dcb.fDtrControl]);
+    ConResPrintf(StdOut, IDS_COM_STATUS_RTS_CIRCUIT, control_strings[dcb.fRtsControl]);
     return 0;
 }
 
-int SetParallelState(int nPortNum)
+
+/*
+ * Those procedures are inspired from Wine's dll/win32/kernel32/wine/comm.c
+ * Copyright 1996 Erik Bos and Marcus Meissner.
+ */
+
+static PCWSTR
+ParseModes(PCWSTR argStr, PBYTE Mode)
 {
-    WCHAR szPortName[MAX_PORTNAME_LEN];
-    WCHAR szTargetPath[MAX_PORTNAME_LEN];
+    if (_wcsnicmp(argStr, L"OFF", 3) == 0)
+    {
+        argStr += 3;
+        *Mode = 0;
+    }
+    else if (_wcsnicmp(argStr, L"ON", 2) == 0)
+    {
+        argStr += 2;
+        *Mode = 1;
+    }
+    else if (_wcsnicmp(argStr, L"HS", 2) == 0)
+    {
+        argStr += 2;
+        *Mode = 2;
+    }
+    else if (_wcsnicmp(argStr, L"TG", 2) == 0)
+    {
+        argStr += 2;
+        *Mode = 3;
+    }
 
-    swprintf(szPortName, L"LPT%d", nPortNum);
-    swprintf(szTargetPath, L"COM%d", nPortNum);
-    if (!DefineDosDeviceW(DDD_REMOVE_DEFINITION, szPortName, szTargetPath))
+    return NULL;
+}
+
+static PCWSTR
+ParseBaudRate(PCWSTR argStr, PDWORD BaudRate)
+{
+    argStr = ParseNumber(argStr, BaudRate);
+    if (!argStr) return NULL;
+
+    /*
+     * Check for Baud Rate abbreviations. This means that using
+     * those values as real baud rates is impossible using MODE.
+     */
+    switch (*BaudRate)
     {
-        wprintf(L"SetParallelState(%d) - DefineDosDevice(%s) failed: 0x%lx\n", nPortNum, szPortName, GetLastError());
+        /* BaudRate = 110, 150, 300, 600 */
+        case 11: case 15: case 30: case 60:
+            *BaudRate *= 10;
+            break;
+
+        /* BaudRate = 1200, 2400, 4800, 9600 */
+        case 12: case 24: case 48: case 96:
+            *BaudRate *= 100;
+            break;
+
+        case 19:
+            *BaudRate = 19200;
+            break;
     }
-    return 0;
+
+    return argStr;
 }
 
-/*
-    \??\COM1
-    \Device\NamedPipe\Spooler\LPT1
-BOOL DefineDosDevice(
-  DWORD dwFlags,         // options
-  LPCTSTR lpDeviceName,  // device name
-  LPCTSTR lpTargetPath   // path string
-);
-DWORD QueryDosDevice(
-  LPCTSTR lpDeviceName, // MS-DOS device name string
-  LPTSTR lpTargetPath,  // query results buffer
-  DWORD ucchMax         // maximum size of buffer
-);
- */
+static PCWSTR
+ParseParity(PCWSTR argStr, PBYTE Parity)
+{
+    switch (towupper(*argStr++))
+    {
+        case L'N':
+            *Parity = NOPARITY;
+            break;
+
+        case L'O':
+            *Parity = ODDPARITY;
+            break;
+
+        case L'E':
+            *Parity = EVENPARITY;
+            break;
+
+        case L'M':
+            *Parity = MARKPARITY;
+            break;
+
+        case L'S':
+            *Parity = SPACEPARITY;
+            break;
 
-int SetConsoleState()
+        default:
+            return NULL;
+    }
+
+    return argStr;
+}
+
+static PCWSTR
+ParseByteSize(PCWSTR argStr, PBYTE ByteSize)
 {
-/*
-    "Select code page:  MODE CON[:] CP SELECT=yyy",
-    "Code page status:  MODE CON[:] CP [/STATUS]",
-    "Display mode:      MODE CON[:] [COLS=c] [LINES=n]",
-    "Typematic rate:    MODE CON[:] [RATE=r DELAY=d]",
- */
-    return 0;
+    DWORD value = 0;
+
+    argStr = ParseNumber(argStr, &value);
+    if (!argStr) return NULL;
+
+    *ByteSize = (BYTE)value;
+    if (*ByteSize < 5 || *ByteSize > 8)
+        return NULL;
+
+    return argStr;
 }
 
-static
-int ExtractModeSerialParams(const WCHAR* param)
+static PCWSTR
+ParseStopBits(PCWSTR argStr, PBYTE StopBits)
 {
-    if (wcsstr(param, L"OFF"))
-        return 0;
-    else if (wcsstr(param, L"ON"))
-        return 1;
-    else if (wcsstr(param, L"HS"))
-        return 2;
-    else if (wcsstr(param, L"TG"))
-        return 3;
+    if (_wcsnicmp(argStr, L"1.5", 3) == 0)
+    {
+        argStr += 3;
+        *StopBits = ONE5STOPBITS;
+    }
+    else
+    {
+        if (*argStr == L'1')
+            *StopBits = ONESTOPBIT;
+        else if (*argStr == L'2')
+            *StopBits = TWOSTOPBITS;
+        else
+            return NULL;
 
-    return -1;
+        argStr++;
+    }
+
+    return argStr;
 }
 
-int SetSerialState(int nPortNum, int args, WCHAR *argv[])
+/*
+ * Build a DCB using the old style settings string eg: "96,n,8,1"
+ *
+ * See dll/win32/kernel32/wine/comm.c!COMM_BuildOldCommDCB()
+ * for more information.
+ */
+static BOOL
+BuildOldCommDCB(
+    OUT LPDCB pDCB,
+    IN PCWSTR ArgStr)
 {
-    int arg;
-    int value;
-    DCB dcb;
-    COMMTIMEOUTS CommTimeouts;
-    WCHAR buf[MAX_COMPARAM_LEN+1];
+    PCWSTR argStr = ArgStr;
+    BOOL stop = FALSE;
+
+    /*
+     * Parse the baud rate (only MANDATORY argument)
+     */
+    argStr = ParseBaudRate(argStr, &pDCB->BaudRate);
+    if (!argStr) return FALSE;
+
+
+    /*
+     * Now parse the rest (OPTIONAL arguments)
+     */
 
-    if (SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+    if (*argStr++ != L',') return FALSE;
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+
+    /* Parse the parity */
+    // Default: EVENPARITY
+    pDCB->Parity = EVENPARITY;
+    if (*argStr != L',')
+    {
+        argStr = ParseParity(argStr, &pDCB->Parity);
+        if (!argStr) return FALSE;
+    }
+
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+    if (*argStr++ != L',') return FALSE;
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+
+    /* Parse the data bits */
+    // Default: 7
+    pDCB->ByteSize = 7;
+    if (*argStr != L',')
+    {
+        argStr = ParseByteSize(argStr, &pDCB->ByteSize);
+        if (!argStr) return FALSE;
+    }
+
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+    if (*argStr++ != L',') return FALSE;
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+
+    /* Parse the stop bits */
+    // Default: 1, or 2 for BAUD=110
+    // pDCB->StopBits = ONESTOPBIT;
+    if (*argStr != L',')
+    {
+        stop = TRUE;
+        argStr = ParseStopBits(argStr, &pDCB->StopBits);
+        if (!argStr) return FALSE;
+    }
+
+    /* The last parameter (flow control "retry") is really optional */
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+    if (*argStr++ != L',') return FALSE;
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto Quit;
+
+Quit:
+    switch (towupper(*argStr))
+    {
+        case L'\0':
+            pDCB->fInX  = FALSE;
+            pDCB->fOutX = FALSE;
+            pDCB->fOutxCtsFlow = FALSE;
+            pDCB->fOutxDsrFlow = FALSE;
+            pDCB->fDtrControl  = DTR_CONTROL_ENABLE;
+            pDCB->fRtsControl  = RTS_CONTROL_ENABLE;
+            break;
+
+        case L'X':
+            pDCB->fInX  = TRUE;
+            pDCB->fOutX = TRUE;
+            pDCB->fOutxCtsFlow = FALSE;
+            pDCB->fOutxDsrFlow = FALSE;
+            pDCB->fDtrControl  = DTR_CONTROL_ENABLE;
+            pDCB->fRtsControl  = RTS_CONTROL_ENABLE;
+            break;
+
+        case L'P':
+            pDCB->fInX  = FALSE;
+            pDCB->fOutX = FALSE;
+            pDCB->fOutxCtsFlow = TRUE;
+            pDCB->fOutxDsrFlow = TRUE;
+            pDCB->fDtrControl  = DTR_CONTROL_HANDSHAKE;
+            pDCB->fRtsControl  = RTS_CONTROL_HANDSHAKE;
+            break;
+
+        default:
+            /* Unsupported */
+            return FALSE;
+    }
+    if (*argStr) argStr++;
+
+    /* This should be the end of the string */
+    while (*argStr == L' ') argStr++;
+    if (*argStr) return FALSE;
+
+    /* If stop bits were not specified, a default is always supplied */
+    if (!stop)
     {
-        for (arg = 2; arg < args; arg++)
+        if (pDCB->BaudRate == 110)
+            pDCB->StopBits = TWOSTOPBITS;
+        else
+            pDCB->StopBits = ONESTOPBIT;
+    }
+    return TRUE;
+}
+
+/*
+ * Build a DCB using the new style settings string.
+ * eg: "baud=9600 parity=n data=8 stop=1 xon=on to=on"
+ *
+ * See dll/win32/kernel32/wine/comm.c!COMM_BuildNewCommDCB()
+ * for more information.
+ */
+static BOOL
+BuildNewCommDCB(
+    OUT LPDCB pDCB,
+    OUT LPCOMMTIMEOUTS pCommTimeouts,
+    IN PCWSTR ArgStr)
+{
+    PCWSTR argStr = ArgStr;
+    BOOL baud = FALSE, stop = FALSE;
+    BYTE value;
+
+    while (argStr && *argStr)
+    {
+        while (*argStr == L' ') argStr++;
+        if (!*argStr) break;
+
+        if (_wcsnicmp(argStr, L"BAUD=", 5) == 0)
         {
-            if (wcslen(argv[arg]) > MAX_COMPARAM_LEN)
-            {
-                wprintf(L"Invalid parameter (too long) - %s\n", argv[arg]);
-                return 1;
-            }
-            wcscpy(buf, argv[arg]);
-            _wcslwr(buf);
-            if (wcsstr(buf, L"baud="))
-            {
-                wscanf(buf+5, L"%lu", &dcb.BaudRate);
-            }
-            else if (wcsstr(buf, L"parity="))
-            {
-                if (wcschr(buf, L'D'))
-                    dcb.Parity = 1;
-                else if (wcschr(buf, L'V'))
-                    dcb.Parity = 2;
-                else if (wcschr(buf, L'M'))
-                    dcb.Parity = 3;
-                else if (wcschr(buf, L'S'))
-                    dcb.Parity = 4;
-                else
-                    dcb.Parity = 0;
-            }
-            else if (wcsstr(buf, L"data="))
-            {
-                wscanf(buf+5, L"%lu", &dcb.ByteSize);
-            }
-            else if (wcsstr(buf, L"stop="))
-            {
-                if (wcschr(buf, L'5'))
-                    dcb.StopBits = 1;
-                else if (wcschr(buf, L'2'))
-                    dcb.StopBits = 2;
-                else
-                    dcb.StopBits = 0;
-            }
-            else if (wcsstr(buf, L"to=")) // to=on|off
-            {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
-            }
-            else if (wcsstr(buf, L"xon=")) // xon=on|off
-            {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                    dcb.fOutX = value;
-                    dcb.fInX = value;
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
-            }
-            else if (wcsstr(buf, L"odsr=")) // odsr=on|off
-            {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                    dcb.fOutxDsrFlow = value;
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
-            }
-            else if (wcsstr(buf, L"octs=")) // octs=on|off
+            baud = TRUE;
+            argStr = ParseBaudRate(argStr+5, &pDCB->BaudRate);
+            if (!argStr) return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"PARITY=", 7) == 0)
+        {
+            // Default: EVENPARITY
+            argStr = ParseParity(argStr+7, &pDCB->Parity);
+            if (!argStr) return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"DATA=", 5) == 0)
+        {
+            // Default: 7
+            argStr = ParseByteSize(argStr+5, &pDCB->ByteSize);
+            if (!argStr) return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"STOP=", 5) == 0)
+        {
+            // Default: 1, or 2 for BAUD=110
+            stop = TRUE;
+            argStr = ParseStopBits(argStr+5, &pDCB->StopBits);
+            if (!argStr) return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"TO=", 3) == 0) // TO=ON|OFF
+        {
+            /* Only total time-outs are get/set by Windows' MODE.COM */
+            argStr = ParseModes(argStr+3, &value);
+            if (!argStr) return FALSE;
+            if (value == 0) // OFF
             {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                    dcb.fOutxCtsFlow = value;
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
+                pCommTimeouts->ReadTotalTimeoutConstant  = 0;
+                pCommTimeouts->WriteTotalTimeoutConstant = 0;
             }
-            else if (wcsstr(buf, L"dtr=")) // dtr=on|off|hs
+            else if (value == 1) // ON
             {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                    dcb.fDtrControl = value;
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
+                pCommTimeouts->ReadTotalTimeoutConstant  = 60000;
+                pCommTimeouts->WriteTotalTimeoutConstant = 60000;
             }
-            else if (wcsstr(buf, L"rts=")) // rts=on|off|hs|tg
+            else
             {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                    dcb.fRtsControl = value;
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
+                return FALSE;
             }
-            else if (wcsstr(buf, L"idsr=")) // idsr=on|off
+        }
+        else if (_wcsnicmp(argStr, L"XON=", 4) == 0) // XON=ON|OFF
+        {
+            argStr = ParseModes(argStr+4, &value);
+            if (!argStr) return FALSE;
+            if ((value == 0) || (value == 1))
             {
-                value = ExtractModeSerialParams(buf);
-                if (value != -1)
-                {
-                    dcb.fDsrSensitivity = value;
-                }
-                else
-                {
-                    goto invalid_serial_parameter;
-                }
+                pDCB->fOutX = value;
+                pDCB->fInX  = value;
             }
             else
             {
-invalid_serial_parameter:;
-                wprintf(L"Invalid parameter - %s\n", buf);
-                return 1;
+                return FALSE;
             }
         }
-        SerialPortQuery(nPortNum, &dcb, &CommTimeouts, TRUE);
+        else if (_wcsnicmp(argStr, L"ODSR=", 5) == 0) // ODSR=ON|OFF
+        {
+            value = 0;
+            argStr = ParseModes(argStr+5, &value);
+            if (!argStr) return FALSE;
+            if ((value == 0) || (value == 1))
+                pDCB->fOutxDsrFlow = value;
+            else
+                return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"OCTS=", 5) == 0) // OCTS=ON|OFF
+        {
+            value = 0;
+            argStr = ParseModes(argStr+5, &value);
+            if (!argStr) return FALSE;
+            if ((value == 0) || (value == 1))
+                pDCB->fOutxCtsFlow = value;
+            else
+                return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"DTR=", 4) == 0) // DTR=ON|OFF|HS
+        {
+            value = 0;
+            argStr = ParseModes(argStr+4, &value);
+            if (!argStr) return FALSE;
+            if ((value == 0) || (value == 1) || (value == 2))
+                pDCB->fDtrControl = value;
+            else
+                return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"RTS=", 4) == 0) // RTS=ON|OFF|HS|TG
+        {
+            value = 0;
+            argStr = ParseModes(argStr+4, &value);
+            if (!argStr) return FALSE;
+            if ((value == 0) || (value == 1) || (value == 2) || (value == 3))
+                pDCB->fRtsControl = value;
+            else
+                return FALSE;
+        }
+        else if (_wcsnicmp(argStr, L"IDSR=", 5) == 0) // IDSR=ON|OFF
+        {
+            value = 0;
+            argStr = ParseModes(argStr+5, &value);
+            if (!argStr) return FALSE;
+            if ((value == 0) || (value == 1))
+                pDCB->fDsrSensitivity = value;
+            else
+                return FALSE;
+        }
+        else
+        {
+            return FALSE;
+        }
     }
+
+    /* If stop bits were not specified, a default is always supplied */
+    if (!stop)
+    {
+        if (baud && pDCB->BaudRate == 110)
+            pDCB->StopBits = TWOSTOPBITS;
+        else
+            pDCB->StopBits = ONESTOPBIT;
+    }
+    return TRUE;
+}
+
+int SetSerialState(INT nPortNum, IN PCWSTR ArgStr)
+{
+    BOOL Success;
+    DCB dcb;
+    COMMTIMEOUTS CommTimeouts;
+
+    if (!SerialPortQuery(nPortNum, &dcb, &CommTimeouts, FALSE))
+    {
+        // TODO: Error message?
+        return 0;
+    }
+
+    /*
+     * Check whether we should use the old or the new MODE syntax:
+     * in the old syntax, the separators are both spaces and commas.
+     */
+    if (wcschr(ArgStr, L','))
+        Success = BuildOldCommDCB(&dcb, ArgStr);
+    else
+        Success = BuildNewCommDCB(&dcb, &CommTimeouts, ArgStr);
+
+    if (!Success)
+    {
+        wprintf(L"Invalid parameter - %s\n", ArgStr);
+        return 1;
+    }
+
+    SerialPortQuery(nPortNum, &dcb, &CommTimeouts, TRUE);
+    ShowSerialStatus(nPortNum);
+
     return 0;
 }
 
-int find_portnum(const WCHAR* cmdverb)
+
+/*****************************************************************************\
+ **                          E N T R Y   P O I N T                          **
+\*****************************************************************************/
+
+static PCWSTR
+FindPortNum(PCWSTR argStr, PINT PortNum)
 {
-    int portnum = -1;
+    *PortNum = -1;
 
-    if (cmdverb[3] >= L'0' && cmdverb[3] <= L'9')
+    if (*argStr >= L'0' && *argStr <= L'9')
     {
-        portnum = cmdverb[3] - L'0';
-        if (cmdverb[4] >= L'0' && cmdverb[4] <= L'9')
+        *PortNum = *argStr - L'0';
+        argStr++;
+        if (*argStr >= L'0' && *argStr <= L'9')
         {
-            portnum *= 10;
-            portnum += cmdverb[4] - L'0';
+            *PortNum *= 10;
+            *PortNum += *argStr - L'0';
         }
     }
-    return portnum;
+    else
+    {
+        return NULL;
+    }
+
+    return argStr;
 }
 
 int wmain(int argc, WCHAR* argv[])
 {
-    int nPortNum;
-    WCHAR param1[MAX_COMPARAM_LEN+1];
-    WCHAR param2[MAX_COMPARAM_LEN+1];
+    int ret = 0;
+    int arg;
+    SIZE_T ArgStrSize;
+    PCWSTR ArgStr, argStr;
+
+    INT nPortNum;
+
+    /* Initialize the Console Standard Streams */
+    ConInitStdStreams();
+
+    /*
+     * MODE.COM has a very peculiar way of parsing its arguments,
+     * as they can be even not separated by any space. This extreme
+     * behaviour certainly is present for backwards compatibility
+     * with the oldest versions of the utility present on MS-DOS.
+     *
+     * For example, such a command:
+     *   "MODE.COM COM1baud=9600parity=ndata=8stop=1xon=onto=on"
+     * will be correctly understood as:
+     *   "MODE.COM COM1 baud=9600 parity=n data=8 stop=1 xon=on to=on"
+     *
+     * Note also that the "/STATUS" switch is actually really "/STA".
+     *
+     * However we will not use GetCommandLine() because we do not want
+     * to deal with the prepended application path and try to find
+     * where the arguments start. Our approach here will consist in
+     * flattening the arguments vector.
+     */
+    ArgStrSize = 0;
 
-    if (argc > 1)
+    /* Compute the space needed for the new string, and allocate it */
+    for (arg = 1; arg < argc; arg++)
     {
-        if (wcslen(argv[1]) > MAX_COMPARAM_LEN)
-        {
-            wprintf(L"Invalid parameter (too long) - %s\n", argv[1]);
-            return 1;
-        }
-        wcscpy(param1, argv[1]);
-        _wcslwr(param1);
-        if (argc > 2)
-        {
-            if (wcslen(argv[2]) > MAX_COMPARAM_LEN)
-            {
-                wprintf(L"Invalid parameter (too long) - %s\n", argv[2]);
-                return 1;
-            }
-            wcscpy(param2, argv[2]);
-            _wcslwr(param2);
-        }
+        ArgStrSize += wcslen(argv[arg]) + 1; // 1 for space
+    }
+    ArgStr = HeapAlloc(GetProcessHeap(), 0, (ArgStrSize + 1) * sizeof(WCHAR));
+    if (ArgStr == NULL)
+    {
+        wprintf(L"ERROR: Not enough memory\n");
+        return 1;
+    }
+
+    /* Copy the contents and NULL-terminate the string */
+    argStr = ArgStr;
+    for (arg = 1; arg < argc; arg++)
+    {
+        wcscpy((PWSTR)argStr, argv[arg]);
+        argStr += wcslen(argv[arg]);
+        *(PWSTR)argStr++ = L' ';
+    }
+    *(PWSTR)argStr = L'\0';
+
+    /* Parse the command line */
+    argStr = ArgStr;
+
+    while (*argStr == L' ') argStr++;
+    if (!*argStr) goto show_status;
+
+    if (wcsstr(argStr, L"/?") || wcsstr(argStr, L"-?"))
+    {
+        ConResPuts(StdOut, IDS_USAGE);
+        goto Quit;
+    }
+    else if (_wcsnicmp(argStr, L"/STA", 4) == 0)
+    {
+        /* Skip this parameter */
+        while (*argStr != L' ') argStr++;
+        /* Skip any delimiter */
+        while (*argStr == L' ') argStr++;
+
+        /* The presence of any other parameter is invalid */
+        if (*argStr)
+            goto invalid_parameter;
+
+        goto show_status;
+    }
+    else if (_wcsnicmp(argStr, L"LPT", 3) == 0)
+    {
+        argStr = FindPortNum(argStr+3, &nPortNum);
+        if (!argStr || nPortNum == -1)
+            goto invalid_parameter;
+
+        if (*argStr == L':') argStr++;
+        while (*argStr == L' ') argStr++;
+
+        if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
+            ret = ShowParallelStatus(nPortNum);
         else
+            wprintf(L"ERROR: LPT port redirection is not implemented!\n");
+        // TODO: Implement setting LPT port redirection using SetParallelState().
+        goto Quit;
+    }
+    else if (_wcsnicmp(argStr, L"COM", 3) == 0)
+    {
+        argStr = FindPortNum(argStr+3, &nPortNum);
+        if (!argStr || nPortNum == -1)
+            goto invalid_parameter;
+
+        if (*argStr == L':') argStr++;
+        while (*argStr == L' ') argStr++;
+
+        if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
+            ret = ShowSerialStatus(nPortNum);
+        else
+            ret = SetSerialState(nPortNum, argStr);
+        goto Quit;
+    }
+    else if (_wcsnicmp(argStr, L"CON", 3) == 0)
+    {
+        argStr += 3;
+
+        if (*argStr == L':') argStr++;
+        while (*argStr == L' ') argStr++;
+
+        if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
         {
-            param2[0] = L'\0';
-        }
-        if (wcsstr(param1, L"/?") || wcsstr(param1, L"-?"))
-        {
-            return Usage();
-        }
-        else if (wcsstr(param1, L"/status"))
-        {
-            goto show_status;
-        }
-        else if (wcsstr(param1, L"lpt"))
-        {
-            nPortNum = find_portnum(param1);
-            if (nPortNum != -1)
-                return ShowParallelStatus(nPortNum);
+            ret = ShowConsoleStatus();
         }
-        else if (wcsstr(param1, L"con"))
+        else if ( (_wcsnicmp(argStr, L"CP", 2) == 0 && (argStr += 2)) ||
+                  (_wcsnicmp(argStr, L"CODEPAGE", 8) == 0 && (argStr += 8)) )
         {
-            return ShowConsoleStatus();
+            while (*argStr == L' ') argStr++;
+
+            if (!*argStr || _wcsnicmp(argStr, L"/STA", 4) == 0)
+                ret = ShowConsoleCPStatus();
+            else
+                ret = SetConsoleCPState(argStr);
         }
-        else if (wcsstr(param1, L"com"))
+        else
         {
-            nPortNum = find_portnum(param1);
-            if (nPortNum != -1)
-            {
-                if (param2[0] == L'\0' || wcsstr(param2, L"/status"))
-                {
-                    return ShowSerialStatus(nPortNum);
-                }
-                else
-                {
-                    return SetSerialState(nPortNum, argc, argv);
-                }
-            }
+            ret = SetConsoleState(argStr);
         }
-        wprintf(L"Invalid parameter - %s\n", param1);
-        return 1;
+        goto Quit;
     }
+    // else if (wcschr(argStr, L','))
     else
     {
-show_status:;
+        /* Old syntax: MODE [COLS],[LINES] */
+        ret = SetConsoleStateOld(argStr);
+        goto Quit;
+    }
 
-        QueryDevices();
+show_status:
+    QueryDevices();
 /*
-        ShowParallelStatus(1);
-        for (nPortNum = 0; nPortNum < MAX_COMPORT_NUM; nPortNum++)
-        {
-            ShowSerialStatus(nPortNum + 1);
-        }
-        ShowConsoleStatus();
- */
+    ShowParallelStatus(1);
+    for (nPortNum = 0; nPortNum < MAX_COMPORT_NUM; nPortNum++)
+    {
+        ShowSerialStatus(nPortNum + 1);
     }
-    return 0;
+    ShowConsoleStatus();
+*/
+    goto Quit;
+
+invalid_parameter:
+    wprintf(L"Invalid parameter - %s\n", ArgStr);
+    goto Quit;
+
+Quit:
+    /* Free the string and quit */
+    HeapFree(GetProcessHeap(), 0, (PWSTR)ArgStr);
+    return ret;
 }