2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS text-mode setup
4 * FILE: base/setup/usetup/cmdcons.c
5 * PURPOSE: Recovery console
15 //#define FEATURE_HISTORY
17 typedef struct _CONSOLE_STATE
23 } CONSOLE_STATE
, *PCONSOLE_STATE
;
25 typedef struct tagCOMMAND
29 INT (*func
)(PCONSOLE_STATE
, LPSTR
);
31 } COMMAND
, *LPCOMMAND
;
87 {"cls", 0, CommandCls
, HelpCls
},
88 {"dumpsector", 0, CommandDumpSector
, HelpDumpSector
},
89 {"exit", 0, CommandExit
, HelpExit
},
90 {"help", 0, CommandHelp
, HelpHelp
},
91 {"partinfo", 0, CommandPartInfo
, HelpPartInfo
},
108 RtlFreeHeap(ProcessHeap
, 0, *q
++);
110 RtlFreeHeap(ProcessHeap
, 0, p
);
140 q
= RtlAllocateHeap(ProcessHeap
, 0, strlen(entry
) + 1);
146 *arg
= RtlReAllocateHeap(ProcessHeap
, 0, oldarg
, (*ac
+ 2) * sizeof(LPSTR
));
149 RtlFreeHeap(ProcessHeap
, 0, q
);
156 (*arg
)[++(*ac
)] = NULL
;
174 arg
= RtlAllocateHeap(ProcessHeap
, 0 , sizeof(LPTSTR
));
185 /* skip leading spaces */
186 while (*s
&& (isspace(*s
) || iscntrl(*s
)))
191 /* the first character can be '/' */
195 /* skip to next word delimiter or start of next option */
198 /* if quote (") then set bQuoted */
199 bQuoted
^= (*s
== '\"');
201 /* Check if we have unquoted text */
204 /* check for separators */
205 if (isspace(*s
) || (*s
== '/'))
207 /* Make length at least one character */
217 /* a word was found */
221 q
= RtlAllocateHeap(ProcessHeap
, 0, len
+ 1);
228 memcpy(q
, start
, len
);
233 if (!add_entry(&ac
, &arg
, q
))
235 RtlFreeHeap(ProcessHeap
, 0, q
);
240 RtlFreeHeap(ProcessHeap
, 0, q
);
254 CONSOLE_ConOutPrintf("CLS\n\nClears the screen.\n\n");
261 PCONSOLE_STATE State
,
264 if (!strcmp(param
, "/?"))
270 CONSOLE_ClearScreen();
271 CONSOLE_SetCursorXY(0, 0);
277 void HexDump(PUCHAR buffer
, ULONG size
)
282 while (offset
< (size
& ~15))
284 ptr
= (PUCHAR
)((ULONG_PTR
)buffer
+ offset
);
285 CONSOLE_ConOutPrintf("%04lx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx\n",
308 ptr
= (PUCHAR
)((ULONG_PTR
)buffer
+ offset
);
309 CONSOLE_ConOutPrintf("%04lx ", offset
);
310 while (offset
< size
)
312 CONSOLE_ConOutPrintf(" %02hx", *ptr
);
317 CONSOLE_ConOutPrintf("\n");
320 CONSOLE_ConOutPrintf("\n");
328 CONSOLE_ConOutPrintf("DUMPSECT DiskNumber Sector\n\nDumps a disk sector to the screen.\n\n");
335 PCONSOLE_STATE State
,
338 OBJECT_ATTRIBUTES ObjectAttributes
;
339 IO_STATUS_BLOCK IoStatusBlock
;
340 UNICODE_STRING PathName
;
342 DISK_GEOMETRY DiskGeometry
;
350 LARGE_INTEGER Sector
, SectorCount
, Offset
;
351 PUCHAR Buffer
= NULL
;
353 DPRINT1("param: %s\n", param
);
355 if (!strcmp(param
, "/?"))
361 argv
= split(param
, &argc
);
363 DPRINT1("argc: %d\n", argc
);
364 DPRINT1("argv: %p\n", argv
);
371 DPRINT1("Device: %s\n", argv
[0]);
372 DPRINT1("Sector: %s\n", argv
[1]);
374 ulDrive
= strtoul(argv
[0], NULL
, 0);
375 // ulSector = strtoul(argv[1], NULL, 0);
376 Sector
.QuadPart
= _atoi64(argv
[1]);
378 /* Build full drive name */
379 // swprintf(DriveName, L"\\\\.\\PHYSICALDRIVE%lu", ulDrive);
380 swprintf(DriveName
, L
"\\Device\\Harddisk%lu\\Partition0", ulDrive
);
382 RtlInitUnicodeString(&PathName
,
385 InitializeObjectAttributes(&ObjectAttributes
,
387 OBJ_CASE_INSENSITIVE
| OBJ_INHERIT
,
391 Status
= NtOpenFile(&hDisk
,
392 GENERIC_READ
| SYNCHRONIZE
,
396 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE
| FILE_RANDOM_ACCESS
);
397 if (!NT_SUCCESS(Status
))
399 DPRINT1("NtCreateFile failed (Status 0x%08lx)\n", Status
);
403 Status
= NtDeviceIoControlFile(hDisk
,
408 IOCTL_DISK_GET_DRIVE_GEOMETRY
,
412 sizeof(DISK_GEOMETRY
));
413 if (!NT_SUCCESS(Status
))
415 DPRINT1("NtDeviceIoControlFile failed (Status 0x%08lx)\n", Status
);
419 DPRINT1("Drive number: %lu\n", ulDrive
);
420 DPRINT1("Cylinders: %I64u\nMediaType: %x\nTracksPerCylinder: %lu\n"
421 "SectorsPerTrack: %lu\nBytesPerSector: %lu\n\n",
422 DiskGeometry
.Cylinders
.QuadPart
,
423 DiskGeometry
.MediaType
,
424 DiskGeometry
.TracksPerCylinder
,
425 DiskGeometry
.SectorsPerTrack
,
426 DiskGeometry
.BytesPerSector
);
428 DPRINT1("Sector: %I64u\n", Sector
.QuadPart
);
430 SectorCount
.QuadPart
= DiskGeometry
.Cylinders
.QuadPart
*
431 (ULONGLONG
)DiskGeometry
.TracksPerCylinder
*
432 (ULONGLONG
)DiskGeometry
.SectorsPerTrack
;
433 if (Sector
.QuadPart
>= SectorCount
.QuadPart
)
435 CONSOLE_ConOutPrintf("Invalid sector number! Valid range: [0 - %I64u]\n", SectorCount
.QuadPart
- 1);
439 Buffer
= RtlAllocateHeap(ProcessHeap
, 0, DiskGeometry
.BytesPerSector
);
442 DPRINT1("Buffer allocation failed\n");
447 Offset
.QuadPart
= Sector
.QuadPart
* DiskGeometry
.BytesPerSector
;
448 DPRINT1("Offset: %I64u\n", Offset
.QuadPart
);
450 Status
= NtReadFile(hDisk
,
456 DiskGeometry
.BytesPerSector
,
459 if (!NT_SUCCESS(Status
))
461 DPRINT1("NtReadFile failed (Status 0x%08lx)\n", Status
);
465 HexDump(Buffer
, DiskGeometry
.BytesPerSector
);
469 RtlFreeHeap(ProcessHeap
, 0, Buffer
);
484 CONSOLE_ConOutPrintf("EXIT\n\nExits the repair console.\n\n");
491 PCONSOLE_STATE State
,
494 if (!strcmp(param
, "/?"))
510 CONSOLE_ConOutPrintf("HELP [Command]\n\nShows help on repair console commands.\n\n");
517 PCONSOLE_STATE State
,
522 DPRINT1("param: %p %u '%s'\n", param
, strlen(param
), param
);
524 if (!strcmp(param
, "/?"))
530 if (param
!= NULL
&& strlen(param
) > 0)
532 for (cmdptr
= Commands
; cmdptr
->name
!= NULL
; cmdptr
++)
534 if (!stricmp(param
, cmdptr
->name
))
536 if (cmdptr
->help
!= NULL
)
545 CONSOLE_ConOutPrintf("CLS\n");
546 CONSOLE_ConOutPrintf("DUMPSECTOR\n");
547 CONSOLE_ConOutPrintf("EXIT\n");
548 CONSOLE_ConOutPrintf("HELP\n");
549 CONSOLE_ConOutPrintf("\n");
559 CONSOLE_ConOutPrintf("PARTINFO DiskNumber\n\nDumps a partition table to the screen.\n\n");
566 PCONSOLE_STATE State
,
569 OBJECT_ATTRIBUTES ObjectAttributes
;
570 IO_STATUS_BLOCK IoStatusBlock
;
571 UNICODE_STRING PathName
;
573 DISK_GEOMETRY DiskGeometry
;
580 PDRIVE_LAYOUT_INFORMATION LayoutBuffer
= NULL
;
581 PPARTITION_INFORMATION PartitionInfo
;
583 DPRINT1("param: %s\n", param
);
585 if (!strcmp(param
, "/?"))
591 argv
= split(param
, &argc
);
593 DPRINT1("argc: %d\n", argc
);
594 DPRINT1("argv: %p\n", argv
);
601 DPRINT1("Device: %s\n", argv
[0]);
603 ulDrive
= strtoul(argv
[0], NULL
, 0);
605 /* Build full drive name */
606 swprintf(DriveName
, L
"\\Device\\Harddisk%lu\\Partition0", ulDrive
);
608 RtlInitUnicodeString(&PathName
,
611 InitializeObjectAttributes(&ObjectAttributes
,
613 OBJ_CASE_INSENSITIVE
| OBJ_INHERIT
,
617 Status
= NtOpenFile(&hDisk
,
618 GENERIC_READ
| SYNCHRONIZE
,
622 FILE_SYNCHRONOUS_IO_NONALERT
| FILE_NON_DIRECTORY_FILE
| FILE_RANDOM_ACCESS
);
623 if (!NT_SUCCESS(Status
))
625 DPRINT1("NtCreateFile failed (Status 0x%08lx)\n", Status
);
629 Status
= NtDeviceIoControlFile(hDisk
,
634 IOCTL_DISK_GET_DRIVE_GEOMETRY
,
638 sizeof(DISK_GEOMETRY
));
639 if (!NT_SUCCESS(Status
))
641 DPRINT1("NtDeviceIoControlFile failed (Status 0x%08lx)\n", Status
);
645 CONSOLE_ConOutPrintf("Drive number: %lu\n", ulDrive
);
646 CONSOLE_ConOutPrintf("Cylinders: %I64u\nMediaType: %x\nTracksPerCylinder: %lu\n"
647 "SectorsPerTrack: %lu\nBytesPerSector: %lu\n\n",
648 DiskGeometry
.Cylinders
.QuadPart
,
649 DiskGeometry
.MediaType
,
650 DiskGeometry
.TracksPerCylinder
,
651 DiskGeometry
.SectorsPerTrack
,
652 DiskGeometry
.BytesPerSector
);
654 LayoutBuffer
= RtlAllocateHeap(ProcessHeap
,
657 if (LayoutBuffer
== NULL
)
659 DPRINT1("LayoutBuffer allocation failed\n");
663 Status
= NtDeviceIoControlFile(hDisk
,
668 IOCTL_DISK_GET_DRIVE_LAYOUT
,
673 if (!NT_SUCCESS(Status
))
675 DPRINT1("NtDeviceIoControlFile(IOCTL_DISK_GET_DRIVE_LAYOUT) failed (Status 0x%08lx)\n", Status
);
679 CONSOLE_ConOutPrintf("Partitions: %lu Signature: %lx\n\n",
680 LayoutBuffer
->PartitionCount
,
681 LayoutBuffer
->Signature
);
683 CONSOLE_ConOutPrintf(" # Start Size Hidden Nr Type Boot\n");
684 CONSOLE_ConOutPrintf("-- --------------- --------------- ------------ -- ---- ----\n");
686 for (i
= 0; i
< LayoutBuffer
->PartitionCount
; i
++)
688 PartitionInfo
= &LayoutBuffer
->PartitionEntry
[i
];
690 CONSOLE_ConOutPrintf("%2lu %15I64u %15I64u %12lu %2lu %2x %c\n",
692 PartitionInfo
->StartingOffset
.QuadPart
/ DiskGeometry
.BytesPerSector
,
693 PartitionInfo
->PartitionLength
.QuadPart
/ DiskGeometry
.BytesPerSector
,
694 PartitionInfo
->HiddenSectors
,
695 PartitionInfo
->PartitionNumber
,
696 PartitionInfo
->PartitionType
,
697 PartitionInfo
->BootIndicator
? '*': ' ');
700 CONSOLE_ConOutPrintf("\n");
703 if (LayoutBuffer
!= NULL
)
704 RtlFreeHeap(ProcessHeap
, 0, LayoutBuffer
);
725 CONSOLE_SetCursorXY(orgx
, orgy
);
726 for (count
= 0; count
< (INT
)strlen(str
); count
++)
727 CONSOLE_ConOutChar(' ');
728 memset(str
, 0, maxlen
);
729 CONSOLE_SetCursorXY(orgx
, orgy
);
736 PCONSOLE_STATE State
,
740 SHORT orgx
; /* origin x/y */
742 SHORT curx
; /*current x/y cursor position*/
745 INT count
; /*used in some for loops*/
746 INT current
= 0; /*the position of the cursor in the string (str)*/
747 INT charcount
= 0;/*chars in the string (str)*/
750 BOOL bReturn
= FALSE
;
752 #ifdef FEATURE_HISTORY
753 //BOOL bContinue=FALSE;/*is TRUE the second case will not be executed*/
758 CONSOLE_GetCursorXY(&orgx
, &orgy
);
762 memset(str
, 0, maxlen
* sizeof(CHAR
));
764 CONSOLE_SetCursorType(State
->bInsert
, TRUE
);
769 CONSOLE_ConInKey(&ir
);
771 if (ir
.Event
.KeyEvent
.dwControlKeyState
&
772 (RIGHT_ALT_PRESSED
|LEFT_ALT_PRESSED
|
773 RIGHT_CTRL_PRESSED
|LEFT_CTRL_PRESSED
) )
775 switch (ir
.Event
.KeyEvent
.wVirtualKeyCode
)
777 #ifdef FEATURE_HISTORY
779 /*add the current command line to the history*/
780 if (ir
.Event
.KeyEvent
.dwControlKeyState
&
781 (LEFT_CTRL_PRESSED
|RIGHT_CTRL_PRESSED
))
786 ClearCommandLine (str
, maxlen
, orgx
, orgy
);
787 current
= charcount
= 0;
795 /*delete current history entry*/
796 if (ir
.Event
.KeyEvent
.dwControlKeyState
&
797 (LEFT_CTRL_PRESSED
|RIGHT_CTRL_PRESSED
))
799 ClearCommandLine (str
, maxlen
, orgx
, orgy
);
800 History_del_current_entry(str
);
801 current
= charcount
= strlen (str
);
802 ConOutPrintf("%s", str
);
803 GetCursorXY(&curx
, &cury
);
808 #endif /*FEATURE_HISTORY*/
814 switch (ir
.Event
.KeyEvent
.wVirtualKeyCode
)
817 /* <BACKSPACE> - delete character to left of cursor */
818 if (current
> 0 && charcount
> 0)
820 if (current
== charcount
)
822 /* if at end of line */
823 str
[current
- 1] = L
'\0';
824 if (CONSOLE_GetCursorX () != 0)
826 CONSOLE_ConOutPrintf("\b \b");
831 CONSOLE_SetCursorXY((SHORT
)(State
->maxx
- 1), (SHORT
)(CONSOLE_GetCursorY () - 1));
832 CONSOLE_ConOutChar(' ');
833 CONSOLE_SetCursorXY((SHORT
)(State
->maxx
- 1), (SHORT
)(CONSOLE_GetCursorY () - 1));
835 curx
= State
->maxx
- 1;
840 for (count
= current
- 1; count
< charcount
; count
++)
841 str
[count
] = str
[count
+ 1];
842 if (CONSOLE_GetCursorX () != 0)
844 CONSOLE_SetCursorXY ((SHORT
)(CONSOLE_GetCursorX () - 1), CONSOLE_GetCursorY ());
849 CONSOLE_SetCursorXY ((SHORT
)(State
->maxx
- 1), (SHORT
)(CONSOLE_GetCursorY () - 1));
851 curx
= State
->maxx
- 1;
853 CONSOLE_GetCursorXY(&curx
, &cury
);
854 CONSOLE_ConOutPrintf("%s ", &str
[current
- 1]);
855 CONSOLE_SetCursorXY(curx
, cury
);
863 /* toggle insert/overstrike mode */
864 State
->bInsert
^= TRUE
;
865 CONSOLE_SetCursorType(State
->bInsert
, TRUE
);
869 /* delete character under cursor */
870 if (current
!= charcount
&& charcount
> 0)
872 for (count
= current
; count
< charcount
; count
++)
873 str
[count
] = str
[count
+ 1];
875 CONSOLE_GetCursorXY(&curx
, &cury
);
876 CONSOLE_ConOutPrintf("%s ", &str
[current
]);
877 CONSOLE_SetCursorXY(curx
, cury
);
882 /* goto beginning of string */
885 CONSOLE_SetCursorXY(orgx
, orgy
);
893 /* goto end of string */
894 if (current
!= charcount
)
896 CONSOLE_SetCursorXY(orgx
, orgy
);
897 CONSOLE_ConOutPrintf("%s", str
);
898 CONSOLE_GetCursorXY(&curx
, &cury
);
905 /* ^M does the same as return */
907 if (!(ir
.Event
.KeyEvent
.dwControlKeyState
&
908 (RIGHT_CTRL_PRESSED
|LEFT_CTRL_PRESSED
)))
914 /* end input, return to main */
915 #ifdef FEATURE_HISTORY
916 /* add to the history */
920 str
[charcount
] = '\0';
921 CONSOLE_ConOutChar('\n');
926 /* clear str Make this callable! */
927 ClearCommandLine (str
, maxlen
, orgx
, orgy
);
930 current
= charcount
= 0;
933 #ifdef FEATURE_HISTORY
935 History_move_to_bottom();
938 #ifdef FEATURE_HISTORY
939 /* get previous command from buffer */
940 ClearCommandLine (str
, maxlen
, orgx
, orgy
);
942 current
= charcount
= strlen (str
);
943 if (((charcount
+ orgx
) / maxx
) + orgy
> maxy
- 1)
944 orgy
+= maxy
- ((charcount
+ orgx
) / maxx
+ orgy
+ 1);
945 CONSOLE_ConOutPrintf("%s", str
);
946 CONSOLE_GetCursorXY(&curx
, &cury
);
951 #ifdef FEATURE_HISTORY
952 /* get next command from buffer */
953 ClearCommandLine (str
, maxlen
, orgx
, orgy
);
955 current
= charcount
= strlen (str
);
956 if (((charcount
+ orgx
) / maxx
) + orgy
> maxy
- 1)
957 orgy
+= maxy
- ((charcount
+ orgx
) / maxx
+ orgy
+ 1);
958 CONSOLE_ConOutPrintf("%s", str
);
959 CONSOLE_GetCursorXY(&curx
, &cury
);
964 /* move cursor left */
968 if (CONSOLE_GetCursorX() == 0)
970 CONSOLE_SetCursorXY((SHORT
)(State
->maxx
- 1), (SHORT
)(CONSOLE_GetCursorY () - 1));
971 curx
= State
->maxx
- 1;
976 CONSOLE_SetCursorXY((SHORT
)(CONSOLE_GetCursorX () - 1), CONSOLE_GetCursorY ());
983 /* move cursor right */
984 if (current
!= charcount
)
987 if (CONSOLE_GetCursorX() == State
->maxx
- 1)
989 CONSOLE_SetCursorXY(0, (SHORT
)(CONSOLE_GetCursorY () + 1));
995 CONSOLE_SetCursorXY((SHORT
)(CONSOLE_GetCursorX () + 1), CONSOLE_GetCursorY ());
999 #ifdef FEATURE_HISTORY
1002 LPCSTR last
= PeekHistory(-1);
1003 if (last
&& charcount
< (INT
)strlen (last
))
1005 PreviousChar
= last
[current
];
1006 CONSOLE_ConOutChar(PreviousChar
);
1007 CONSOLE_GetCursorXY(&curx
, &cury
);
1008 str
[current
++] = PreviousChar
;
1016 /* This input is just a normal char */
1021 ch
= ir
.Event
.KeyEvent
.uChar
.UnicodeChar
;
1022 if (ch
>= 32 && (charcount
!= (maxlen
- 2)) && bCharInput
)
1024 /* insert character into string... */
1025 if (State
->bInsert
&& current
!= charcount
)
1027 /* If this character insertion will cause screen scrolling,
1028 * adjust the saved origin of the command prompt. */
1029 tempscreen
= strlen(str
+ current
) + curx
;
1030 if ((tempscreen
% State
->maxx
) == (State
->maxx
- 1) &&
1031 (tempscreen
/ State
->maxx
) + cury
== (State
->maxy
- 1))
1037 for (count
= charcount
; count
> current
; count
--)
1038 str
[count
] = str
[count
- 1];
1039 str
[current
++] = ch
;
1040 if (curx
== State
->maxx
- 1)
1044 CONSOLE_ConOutPrintf("%s", &str
[current
- 1]);
1045 CONSOLE_SetCursorXY(curx
, cury
);
1050 if (current
== charcount
)
1052 str
[current
++] = ch
;
1053 if (CONSOLE_GetCursorX () == State
->maxx
- 1 && CONSOLE_GetCursorY () == State
->maxy
- 1)
1055 if (CONSOLE_GetCursorX () == State
->maxx
- 1)
1059 CONSOLE_ConOutChar(ch
);
1065 CONSOLE_SetCursorType(State
->bInsert
, TRUE
);
1076 return (c
== '/' || c
== '=' || c
== '\0' || isspace(c
));
1083 PCONSOLE_STATE State
,
1086 CHAR com
[MAX_PATH
]; /* the first word in the command */
1089 LPSTR rest
= line
; /* pointer to the rest of the command line */
1093 DPRINT1("DoCommand: (\'%s\')\n", line
);
1095 /* Skip over initial white space */
1096 while (isspace(*rest
))
1101 /* Anything to do ? */
1104 /* Copy over 1st word as lower case */
1105 while (!IsDelimiter(*rest
))
1106 *cp
++ = tolower(*rest
++);
1108 /* Terminate first word */
1111 /* Skip over whitespace to rest of line */
1112 while (isspace (*rest
))
1115 /* Scan internal command table */
1116 for (cmdptr
= Commands
; ; cmdptr
++)
1118 /* If end of table execute ext cmd */
1119 if (cmdptr
->name
== NULL
)
1121 CONSOLE_ConOutPuts("Unknown command. Enter HELP to get a list of commands.");
1125 if (stricmp(com
, cmdptr
->name
) == 0)
1127 cmdptr
->func(State
, rest
);
1132 /* The following code handles the case of commands like CD which
1133 * are recognised even when the command name and parameter are
1134 * not space separated.
1140 /* Get length of command name */
1141 cl
= strlen(cmdptr
->name
);
1143 if ((cmdptr
->flags
& CMD_SPECIAL
) &&
1144 (!strncmp (cmdptr
->name
, com
, cl
)) &&
1145 (strchr("\\.-", *(com
+ cl
))))
1147 /* OK its one of the specials...*/
1149 /* Call with new rest */
1150 cmdptr
->func(State
, cstart
+ cl
);
1160 RecoveryConsole(VOID
)
1162 CHAR szInputBuffer
[256];
1163 CONSOLE_SCREEN_BUFFER_INFO csbi
;
1164 CONSOLE_STATE State
;
1166 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &csbi
);
1168 /* get screen size */
1169 State
.maxx
= csbi
.dwSize
.X
;
1170 State
.maxy
= csbi
.dwSize
.Y
;
1171 State
.bInsert
= TRUE
;
1172 State
.bExit
= FALSE
;
1174 CONSOLE_ClearScreen();
1175 CONSOLE_SetCursorXY(0, 0);
1177 CONSOLE_ConOutPrintf("ReactOS Recovery Console\n\nEnter HELP to get a list of commands.\n\n");
1179 while (!State
.bExit
)
1182 CONSOLE_ConOutPrintf(">");
1184 ReadCommand(&State
, szInputBuffer
, 256);
1185 DPRINT1("%s\n", szInputBuffer
);
1187 DoCommand(&State
, szInputBuffer
);
1189 // Cmd = ParseCommand(NULL);
1193 // ExecuteCommand(Cmd);
1194 // FreeCommand(Cmd);