1 /*
2  *  Copyright (C) 2002-2015  The DOSBox Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  *  Wengier: LFN support
19  */
20 
21 
22 #include "dosbox.h"
23 #include "shell.h"
24 #include "callback.h"
25 #include "regs.h"
26 #include "bios.h"
27 #include "../dos/drives.h"
28 #include "support.h"
29 #include "control.h"
30 #include <cstring>
31 #include <cctype>
32 #include <cstdlib>
33 #include <vector>
34 #include <string>
35 #include <time.h>
36 #include <stdlib.h>
37 
38 static SHELL_Cmd cmd_list[]={
39 {	"DIR",		0,			&DOS_Shell::CMD_DIR,		"SHELL_CMD_DIR_HELP"},
40 {	"LS",		0,			&DOS_Shell::CMD_DIR,		"SHELL_CMD_DIR_HELP"},
41 {	"CHDIR",	1,			&DOS_Shell::CMD_CHDIR,		"SHELL_CMD_CHDIR_HELP"},
42 {	"ATTRIB",	1,			&DOS_Shell::CMD_ATTRIB,		"SHELL_CMD_ATTRIB_HELP"},
43 {	"CALL",		1,			&DOS_Shell::CMD_CALL,		"SHELL_CMD_CALL_HELP"},
44 {	"CD",		0,			&DOS_Shell::CMD_CHDIR,		"SHELL_CMD_CHDIR_HELP"},
45 {	"CHOICE",	1,			&DOS_Shell::CMD_CHOICE,		"SHELL_CMD_CHOICE_HELP"},
46 {	"CLS",		0,			&DOS_Shell::CMD_CLS,		"SHELL_CMD_CLS_HELP"},
47 {	"COPY",		0,			&DOS_Shell::CMD_COPY,		"SHELL_CMD_COPY_HELP"},
48 {	"DATE",		0,			&DOS_Shell::CMD_DATE,		"SHELL_CMD_DATE_HELP"},
49 {	"DEL",		0,			&DOS_Shell::CMD_DELETE,		"SHELL_CMD_DELETE_HELP"},
50 {	"DELETE",	1,			&DOS_Shell::CMD_DELETE,		"SHELL_CMD_DELETE_HELP"},
51 {	"ERASE",	1,			&DOS_Shell::CMD_DELETE,		"SHELL_CMD_DELETE_HELP"},
52 {	"ECHO",		1,			&DOS_Shell::CMD_ECHO,		"SHELL_CMD_ECHO_HELP"},
53 {	"EXIT",		0,			&DOS_Shell::CMD_EXIT,		"SHELL_CMD_EXIT_HELP"},
54 {	"GOTO",		1,			&DOS_Shell::CMD_GOTO,		"SHELL_CMD_GOTO_HELP"},
55 {	"HELP",		1,			&DOS_Shell::CMD_HELP,		"SHELL_CMD_HELP_HELP"},
56 {	"IF",		1,			&DOS_Shell::CMD_IF,			"SHELL_CMD_IF_HELP"},
57 {	"LOADHIGH",	1,			&DOS_Shell::CMD_LOADHIGH, 	"SHELL_CMD_LOADHIGH_HELP"},
58 {	"LH",		1,			&DOS_Shell::CMD_LOADHIGH,	"SHELL_CMD_LOADHIGH_HELP"},
59 {	"MKDIR",	1,			&DOS_Shell::CMD_MKDIR,		"SHELL_CMD_MKDIR_HELP"},
60 {	"MD",		0,			&DOS_Shell::CMD_MKDIR,		"SHELL_CMD_MKDIR_HELP"},
61 {	"PATH",		1,			&DOS_Shell::CMD_PATH,		"SHELL_CMD_PATH_HELP"},
62 {	"PAUSE",	1,			&DOS_Shell::CMD_PAUSE,		"SHELL_CMD_PAUSE_HELP"},
63 {	"RMDIR",	1,			&DOS_Shell::CMD_RMDIR,		"SHELL_CMD_RMDIR_HELP"},
64 {	"RD",		0,			&DOS_Shell::CMD_RMDIR,		"SHELL_CMD_RMDIR_HELP"},
65 {	"REM",		1,			&DOS_Shell::CMD_REM,		"SHELL_CMD_REM_HELP"},
66 {	"RENAME",	1,			&DOS_Shell::CMD_RENAME,		"SHELL_CMD_RENAME_HELP"},
67 {	"REN",		0,			&DOS_Shell::CMD_RENAME,		"SHELL_CMD_RENAME_HELP"},
68 {	"SET",		1,			&DOS_Shell::CMD_SET,		"SHELL_CMD_SET_HELP"},
69 {	"SHIFT",	1,			&DOS_Shell::CMD_SHIFT,		"SHELL_CMD_SHIFT_HELP"},
70 {	"SUBST",	1,			&DOS_Shell::CMD_SUBST,		"SHELL_CMD_SUBST_HELP"},
71 {	"TIME",		0,			&DOS_Shell::CMD_TIME,		"SHELL_CMD_TIME_HELP"},
72 {	"TYPE",		0,			&DOS_Shell::CMD_TYPE,		"SHELL_CMD_TYPE_HELP"},
73 {	"VER",		0,			&DOS_Shell::CMD_VER,		"SHELL_CMD_VER_HELP"},
74 {0,0,0,0}
75 };
76 
77 /* support functions */
78 static char empty_char = 0;
79 static char* empty_string = &empty_char;
StripSpaces(char * & args)80 static void StripSpaces(char*&args) {
81 	while(args && *args && isspace(*reinterpret_cast<unsigned char*>(args)))
82 		args++;
83 }
84 
StripSpaces(char * & args,char also)85 static void StripSpaces(char*&args,char also) {
86 	while(args && *args && (isspace(*reinterpret_cast<unsigned char*>(args)) || (*args == also)))
87 		args++;
88 }
89 
ExpandDot(char * args,char * buffer)90 static char* ExpandDot(char*args, char* buffer) {
91 	if(*args == '.') {
92 		if(*(args+1) == 0){
93 			strcpy(buffer,"*.*");
94 			return buffer;
95 		}
96 		if( (*(args+1) != '.') && (*(args+1) != '\\') ) {
97 			buffer[0] = '*';
98 			buffer[1] = 0;
99 			strcat(buffer,args);
100 			return buffer;
101 		} else
102 			strcpy (buffer, args);
103 	}
104 	else strcpy(buffer,args);
105 	return buffer;
106 }
107 
108 
109 
CheckConfig(char * cmd_in,char * line)110 bool DOS_Shell::CheckConfig(char* cmd_in,char*line) {
111 	Section* test = control->GetSectionFromProperty(cmd_in);
112 	if(!test) return false;
113 	if(line && !line[0]) {
114 		std::string val = test->GetPropValue(cmd_in);
115 		if(val != NO_SUCH_PROPERTY) WriteOut("%s\n",val.c_str());
116 		return true;
117 	}
118 	char newcom[1024]; newcom[0] = 0; strcpy(newcom,"z:\\config -set ");
119 	strcat(newcom,test->GetName());	strcat(newcom," ");
120 	strcat(newcom,cmd_in);strcat(newcom,line);
121 	DoCommand(newcom);
122 	return true;
123 }
124 
DoCommand(char * line)125 void DOS_Shell::DoCommand(char * line) {
126 /* First split the line into command and arguments */
127 	line=trim(line);
128 	char cmd_buffer[CMD_MAXLINE];
129 	char * cmd_write=cmd_buffer;
130 	while (*line) {
131 		if (*line == 32) break;
132 		if (*line == '/') break;
133 		if (*line == '\t') break;
134 		if (*line == '=') break;
135 //		if (*line == ':') break; //This breaks drive switching as that is handled at a later stage.
136 		if ((*line == '.') ||(*line == '\\')) {  //allow stuff like cd.. and dir.exe cd\kees
137 			*cmd_write=0;
138 			Bit32u cmd_index=0;
139 			while (cmd_list[cmd_index].name) {
140 				if (strcasecmp(cmd_list[cmd_index].name,cmd_buffer)==0) {
141 					(this->*(cmd_list[cmd_index].handler))(line);
142 			 		return;
143 				}
144 				cmd_index++;
145 			}
146 		}
147 		*cmd_write++=*line++;
148 	}
149 	*cmd_write=0;
150 	if (strlen(cmd_buffer)==0) return;
151 /* Check the internal list */
152 	Bit32u cmd_index=0;
153 	while (cmd_list[cmd_index].name) {
154 		if (strcasecmp(cmd_list[cmd_index].name,cmd_buffer)==0) {
155 			(this->*(cmd_list[cmd_index].handler))(line);
156 			return;
157 		}
158 		cmd_index++;
159 	}
160 /* This isn't an internal command execute it */
161 	if(Execute(cmd_buffer,line)) return;
162 	if(CheckConfig(cmd_buffer,line)) return;
163 	WriteOut(MSG_Get("SHELL_EXECUTE_ILLEGAL_COMMAND"),cmd_buffer);
164 }
165 
166 #define HELP(command) \
167 	if (ScanCMDBool(args,"?")) { \
168 		WriteOut(MSG_Get("SHELL_CMD_" command "_HELP")); \
169 		const char* long_m = MSG_Get("SHELL_CMD_" command "_HELP_LONG"); \
170 		WriteOut("\n"); \
171 		if(strcmp("Message not Found!\n",long_m)) WriteOut(long_m); \
172 		else WriteOut(command "\n"); \
173 		return; \
174 	}
175 
CMD_CLS(char * args)176 void DOS_Shell::CMD_CLS(char * args) {
177 	HELP("CLS");
178 	reg_ax=0x0003;
179 	CALLBACK_RunRealInt(0x10);
180 }
181 
CMD_DELETE(char * args)182 void DOS_Shell::CMD_DELETE(char * args) {
183 	HELP("DELETE");
184 	/* Command uses dta so set it to our internal dta */
185 	RealPt save_dta=dos.dta();
186 	dos.dta(dos.tables.tempdta);
187 
188 	char * rem=ScanCMDRemain(args);
189 	if (rem) {
190 		WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"),rem);
191 		return;
192 	}
193 	/* If delete accept switches mind the space infront of them. See the dir /p code */
194 
195 	char full[DOS_PATHLENGTH],sfull[DOS_PATHLENGTH+2];
196 	char buffer[CROSS_LEN];
197 	args = ExpandDot(args,buffer);
198 	StripSpaces(args);
199 	if (!DOS_Canonicalize(args,full)) { WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));return; }
200 //TODO Maybe support confirmation for *.* like dos does.
201 	char spath[DOS_PATHLENGTH],sargs[DOS_PATHLENGTH];
202 	if (!DOS_GetSFNPath(args,spath,false)) {
203 		WriteOut(MSG_Get("SHELL_CMD_DEL_ERROR"),args);
204 		return;
205 	}
206 	sprintf(sargs,"\"%s\"",spath);
207 	bool res=DOS_FindFirst(sargs,0xffff & ~DOS_ATTR_VOLUME);
208 	if (!res) {
209 		WriteOut(MSG_Get("SHELL_CMD_DEL_ERROR"),args);
210 		dos.dta(save_dta);
211 		return;
212 	}
213 	//end can't be 0, but if it is we'll get a nice crash, who cares :)
214 	char * end=strrchr(full,'\\')+1;*end=0;
215 	char name[DOS_NAMELENGTH_ASCII],lname[LFN_NAMELENGTH+1];
216 	Bit32u size;Bit16u time,date;Bit8u attr;
217 	DOS_DTA dta(dos.dta());
218 	while (res) {
219 		dta.GetResult(name,lname,size,date,time,attr);
220 		if (!(attr & (DOS_ATTR_DIRECTORY|DOS_ATTR_READ_ONLY))) {
221 			strcpy(end,name);
222 			strcpy(sfull,full);
223 			if (uselfn) sprintf(sfull,"\"%s\"",full);
224 			if (!DOS_UnlinkFile(sfull)) WriteOut(MSG_Get("SHELL_CMD_DEL_ERROR"),full);
225 		}
226 		res=DOS_FindNext();
227 	}
228 	dos.dta(save_dta);
229 }
230 
CMD_HELP(char * args)231 void DOS_Shell::CMD_HELP(char * args){
232 	HELP("HELP");
233 	bool optall=ScanCMDBool(args,"ALL");
234 	/* Print the help */
235 	if(!optall) WriteOut(MSG_Get("SHELL_CMD_HELP"));
236 	Bit32u cmd_index=0,write_count=0;
237 	while (cmd_list[cmd_index].name) {
238 		if (optall || !cmd_list[cmd_index].flags) {
239 			WriteOut("<\033[34;1m%-8s\033[0m> %s",cmd_list[cmd_index].name,MSG_Get(cmd_list[cmd_index].help));
240 			if(!(++write_count%22)) CMD_PAUSE(empty_string);
241 		}
242 		cmd_index++;
243 	}
244 }
245 
CMD_RENAME(char * args)246 void DOS_Shell::CMD_RENAME(char * args){
247 	HELP("RENAME");
248 	StripSpaces(args);
249 	if (!*args) {SyntaxError();return;}
250 	if ((strchr(args,'*')!=NULL) || (strchr(args,'?')!=NULL) ) { WriteOut(MSG_Get("SHELL_CMD_NO_WILD"));return;}
251 	char * arg1=StripArg(args);
252 	StripSpaces(args);
253 	if (!*args) {SyntaxError();return;}
254 	char* slash = strrchr(arg1,'\\');
255 	if (slash) {
256 		/* If directory specified (crystal caves installer)
257 		 * rename from c:\X : rename c:\abc.exe abc.shr.
258 		 * File must appear in C:\
259 		 * Ren X:\A\B C => ren X:\A\B X:\A\C */
260 
261 		char dir_source[DOS_PATHLENGTH + 4] = {0}; //not sure if drive portion is included in pathlength
262 		//Copy first and then modify, makes GCC happy
263 		safe_strncpy(dir_source,arg1,DOS_PATHLENGTH + 4);
264 		char* dummy = strrchr(dir_source,'\\');
265 		if (!dummy) { //Possible due to length
266 			WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
267 			return;
268 		}
269 		dummy++;
270 		*dummy = 0;
271 
272 		//Maybe check args for directory, as I think that isn't allowed
273 
274 		//dir_source and target are introduced for when we support multiple files being renamed.
275 		char target[DOS_PATHLENGTH+CROSS_LEN + 5] = {0};
276 		strcpy(target,dir_source);
277 		strncat(target,args,CROSS_LEN);
278 
279 		DOS_Rename(arg1,target);
280 
281 	} else {
282 		DOS_Rename(arg1,args);
283 	}
284 }
285 
CMD_ECHO(char * args)286 void DOS_Shell::CMD_ECHO(char * args){
287 	if (!*args) {
288 		if (echo) { WriteOut(MSG_Get("SHELL_CMD_ECHO_ON"));}
289 		else { WriteOut(MSG_Get("SHELL_CMD_ECHO_OFF"));}
290 		return;
291 	}
292 	char buffer[512];
293 	char* pbuffer = buffer;
294 	safe_strncpy(buffer,args,512);
295 	StripSpaces(pbuffer);
296 	if (strcasecmp(pbuffer,"OFF")==0) {
297 		echo=false;
298 		return;
299 	}
300 	if (strcasecmp(pbuffer,"ON")==0) {
301 		echo=true;
302 		return;
303 	}
304 	if(strcasecmp(pbuffer,"/?")==0) { HELP("ECHO"); }
305 
306 	args++;//skip first character. either a slash or dot or space
307 	size_t len = strlen(args); //TODO check input of else ook nodig is.
308 	if(len && args[len - 1] == '\r') {
309 		LOG(LOG_MISC,LOG_WARN)("Hu ? carriage return already present. Is this possible?");
310 		WriteOut("%s\n",args);
311 	} else WriteOut("%s\r\n",args);
312 }
313 
314 
CMD_EXIT(char * args)315 void DOS_Shell::CMD_EXIT(char * args) {
316 	HELP("EXIT");
317 	exit = true;
318 }
319 
CMD_CHDIR(char * args)320 void DOS_Shell::CMD_CHDIR(char * args) {
321 	HELP("CHDIR");
322 	StripSpaces(args);
323 	char sargs[CROSS_LEN];
324 	if (*args && !DOS_GetSFNPath(args,sargs,false)) {
325 		WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
326 		return;
327 	}
328 	Bit8u drive = DOS_GetDefaultDrive()+'A';
329 	char dir[DOS_PATHLENGTH];
330 	if (!*args) {
331 		DOS_GetCurrentDir(0,dir,true);
332 		WriteOut("%c:\\%s\n",drive,dir);
333 	} else if(strlen(args) == 2 && args[1]==':') {
334 		Bit8u targetdrive = (args[0] | 0x20)-'a' + 1;
335 		unsigned char targetdisplay = *reinterpret_cast<unsigned char*>(&args[0]);
336 		if(!DOS_GetCurrentDir(targetdrive,dir,true)) {
337 			if(drive == 'Z') {
338 				WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(targetdisplay));
339 			} else {
340 				WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
341 			}
342 			return;
343 		}
344 		WriteOut("%c:\\%s\n",toupper(targetdisplay),dir);
345 		if(drive == 'Z')
346 			WriteOut(MSG_Get("SHELL_CMD_CHDIR_HINT"),toupper(targetdisplay));
347 	} else 	if (!DOS_ChangeDir(sargs)) {
348 		/* Changedir failed. Check if the filename is longer then 8 and/or contains spaces */
349 
350 		std::string temps(args),slashpart;
351 		std::string::size_type separator = temps.find_first_of("\\/");
352 		if(!separator) {
353 			slashpart = temps.substr(0,1);
354 			temps.erase(0,1);
355 		}
356 		separator = temps.find_first_of("\\/");
357 		if(separator != std::string::npos) temps.erase(separator);
358 		separator = temps.find_first_of("\"");
359 		if(separator != std::string::npos) temps.erase(separator);
360 		separator = temps.rfind('.');
361 		if(separator != std::string::npos) temps.erase(separator);
362 		separator = temps.find(' ');
363 		if(separator != std::string::npos) {/* Contains spaces */
364 			temps.erase(separator);
365 			if(temps.size() >6) temps.erase(6);
366 			temps += "~1";
367 			WriteOut(MSG_Get("SHELL_CMD_CHDIR_HINT_2"),temps.insert(0,slashpart).c_str());
368 		} else {
369 			if (drive == 'Z') {
370 				WriteOut(MSG_Get("SHELL_CMD_CHDIR_HINT_3"));
371 			} else {
372 				WriteOut(MSG_Get("SHELL_CMD_CHDIR_ERROR"),args);
373 			}
374 		}
375 	}
376 }
377 
CMD_MKDIR(char * args)378 void DOS_Shell::CMD_MKDIR(char * args) {
379 	HELP("MKDIR");
380 	StripSpaces(args);
381 	char * rem=ScanCMDRemain(args);
382 	if (rem) {
383 		WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"),rem);
384 		return;
385 	}
386 	if (!DOS_MakeDir(args)) {
387 		WriteOut(MSG_Get("SHELL_CMD_MKDIR_ERROR"),args);
388 	}
389 }
390 
CMD_RMDIR(char * args)391 void DOS_Shell::CMD_RMDIR(char * args) {
392 	HELP("RMDIR");
393 	StripSpaces(args);
394 	char * rem=ScanCMDRemain(args);
395 	if (rem) {
396 		WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"),rem);
397 		return;
398 	}
399 	if (!DOS_RemoveDir(args)) {
400 		WriteOut(MSG_Get("SHELL_CMD_RMDIR_ERROR"),args);
401 	}
402 }
403 
FormatNumber(Bit32u num,char * buf)404 static void FormatNumber(Bit32u num,char * buf) {
405 	Bit32u numm,numk,numb,numg;
406 	numb=num % 1000;
407 	num/=1000;
408 	numk=num % 1000;
409 	num/=1000;
410 	numm=num % 1000;
411 	num/=1000;
412 	numg=num;
413 	if (numg) {
414 		sprintf(buf,"%d,%03d,%03d,%03d",numg,numm,numk,numb);
415 		return;
416 	};
417 	if (numm) {
418 		sprintf(buf,"%d,%03d,%03d",numm,numk,numb);
419 		return;
420 	};
421 	if (numk) {
422 		sprintf(buf,"%d,%03d",numk,numb);
423 		return;
424 	};
425 	sprintf(buf,"%d",numb);
426 }
427 
CMD_DIR(char * args)428 void DOS_Shell::CMD_DIR(char * args) {
429 	HELP("DIR");
430 	char numformat[16];
431 	char path[DOS_PATHLENGTH];
432 	char sargs[CROSS_LEN];
433 
434 	std::string line;
435 	if(GetEnvStr("DIRCMD",line)){
436 		std::string::size_type idx = line.find('=');
437 		std::string value=line.substr(idx +1 , std::string::npos);
438 		line = std::string(args) + " " + value;
439 		args=const_cast<char*>(line.c_str());
440 	}
441 
442 	bool optW=ScanCMDBool(args,"W");
443 	ScanCMDBool(args,"S");
444 	bool optP=ScanCMDBool(args,"P");
445 	if (ScanCMDBool(args,"WP") || ScanCMDBool(args,"PW")) {
446 		optW=optP=true;
447 	}
448 	bool optB=ScanCMDBool(args,"B");
449 	bool optAD=ScanCMDBool(args,"AD");
450 	char * rem=ScanCMDRemain(args);
451 	if (rem) {
452 		WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"),rem);
453 		return;
454 	}
455 	Bit32u byte_count,file_count,dir_count;
456 	Bitu w_count=0;
457 	Bitu p_count=0;
458 	Bitu w_size = optW?5:1;
459 	byte_count=file_count=dir_count=0;
460 
461 	char buffer[CROSS_LEN];
462 	args = trim(args);
463 	size_t argLen = strlen(args);
464 	if (argLen == 0) {
465 		strcpy(args,"*.*"); //no arguments.
466 	} else {
467 		switch (args[argLen-1])
468 		{
469 		case '\\':	// handle \, C:\, etc.
470 		case ':' :	// handle C:, etc.
471 			strcat(args,"*.*");
472 			break;
473 		default:
474 			break;
475 		}
476 	}
477 	args = ExpandDot(args,buffer);
478 
479 	if (!strrchr(args,'*') && !strrchr(args,'?')) {
480 		Bit16u attribute=0;
481 		if(!DOS_GetSFNPath(args,sargs,false)) {
482 			WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
483 			return;
484 		}
485 		if(DOS_GetFileAttr(sargs,&attribute) && (attribute&DOS_ATTR_DIRECTORY) ) {
486 			DOS_FindFirst(sargs,0xffff & ~DOS_ATTR_VOLUME);
487 			DOS_DTA dta(dos.dta());
488 			strcpy(args,sargs);
489 			strcat(args,"\\*.*");	// if no wildcard and a directory, get its files
490 		}
491 	}
492 	if (!DOS_GetSFNPath(args,sargs,false)) {
493 		WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
494 		return;
495 	}
496 	sprintf(args,"\"%s\"",sargs);
497 	if (!strrchr(args,'.')) {
498 		strcat(args,".*");	// if no extension, get them all
499 	}
500 
501 	/* Make a full path in the args */
502 	if (!DOS_Canonicalize(args,path)) {
503 		WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
504 		return;
505 	}
506 	*(strrchr(path,'\\')+1)=0;
507 	if (!DOS_GetSFNPath(path,sargs,true)) {
508 		WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
509 		return;
510 	}
511 	if (*(sargs+strlen(sargs)-1) != '\\') strcat(sargs,"\\");
512 	if (!optB) WriteOut(MSG_Get("SHELL_CMD_DIR_INTRO"),sargs);
513 
514 	/* Command uses dta so set it to our internal dta */
515 	RealPt save_dta=dos.dta();
516 	dos.dta(dos.tables.tempdta);
517 	DOS_DTA dta(dos.dta());
518 	bool ret=DOS_FindFirst(args,0xffff & ~DOS_ATTR_VOLUME);
519 	if (!ret) {
520 		if (!optB) WriteOut(MSG_Get("SHELL_CMD_FILE_NOT_FOUND"),args);
521 		dos.dta(save_dta);
522 		return;
523 	}
524 
525 	do {    /* File name and extension */
526 		char name[DOS_NAMELENGTH_ASCII], lname[LFN_NAMELENGTH+1];
527 		Bit32u size;Bit16u date;Bit16u time;Bit8u attr;
528 		dta.GetResult(name,lname,size,date,time,attr);
529 
530 		/* Skip non-directories if option AD is present */
531 		if(optAD && !(attr&DOS_ATTR_DIRECTORY) ) continue;
532 
533 		/* output the file */
534 		if (optB) {
535 			// this overrides pretty much everything
536 			if (strcmp(".",uselfn?lname:name) && strcmp("..",uselfn?lname:name)) {
537 				WriteOut("%s\n",uselfn?lname:name);
538 			}
539 		} else {
540 			char * ext = empty_string;
541 			if (!optW && (name[0] != '.')) {
542 				ext = strrchr(name, '.');
543 				if (!ext) ext = empty_string;
544 				else *ext++ = 0;
545 			}
546 			Bit8u day	= (Bit8u)(date & 0x001f);
547 			Bit8u month	= (Bit8u)((date >> 5) & 0x000f);
548 			Bit16u year = (Bit16u)((date >> 9) + 1980);
549 			Bit8u hour	= (Bit8u)((time >> 5 ) >> 6);
550 			Bit8u minute = (Bit8u)((time >> 5) & 0x003f);
551 
552 			if (attr & DOS_ATTR_DIRECTORY) {
553 				if (optW) {
554 					WriteOut("[%s]",name);
555 					size_t namelen = strlen(name);
556 					if (namelen <= 14) {
557 						for (size_t i=14-namelen;i>0;i--) WriteOut(" ");
558 					}
559 				} else {
560 					WriteOut("%-8s %-3s   %-16s %02d-%02d-%04d %2d:%02d %s\n",name,ext,"<DIR>",day,month,year,hour,minute,uselfn?lname:"");
561 				}
562 				dir_count++;
563 			} else {
564 				if (optW) {
565 					WriteOut("%-16s",name);
566 				} else {
567 					FormatNumber(size,numformat);
568 					WriteOut("%-8s %-3s   %16s %02d-%02d-%04d %2d:%02d %s\n",name,ext,numformat,day,month,year,hour,minute,uselfn?lname:"");
569 				}
570 				file_count++;
571 				byte_count+=size;
572 			}
573 			if (optW) {
574 				w_count++;
575 			}
576 		}
577 		if (optP && !(++p_count%(22*w_size))) {
578 			CMD_PAUSE(empty_string);
579 		}
580 	} while ( (ret=DOS_FindNext()) );
581 	if (optW) {
582 		if (w_count%5)	WriteOut("\n");
583 	}
584 	if (!optB) {
585 		/* Show the summary of results */
586 		FormatNumber(byte_count,numformat);
587 		WriteOut(MSG_Get("SHELL_CMD_DIR_BYTES_USED"),file_count,numformat);
588 		Bit8u drive=dta.GetSearchDrive();
589 		//TODO Free Space
590 		Bitu free_space=1024*1024*100;
591 		if (Drives[drive]) {
592 			Bit16u bytes_sector;Bit8u sectors_cluster;Bit16u total_clusters;Bit16u free_clusters;
593 			Drives[drive]->AllocationInfo(&bytes_sector,&sectors_cluster,&total_clusters,&free_clusters);
594 			free_space=bytes_sector*sectors_cluster*free_clusters;
595 		}
596 		FormatNumber(free_space,numformat);
597 		WriteOut(MSG_Get("SHELL_CMD_DIR_BYTES_FREE"),dir_count,numformat);
598 	}
599 	dos.dta(save_dta);
600 }
601 
602 struct copysource {
603 	std::string filename;
604 	bool concat;
copysourcecopysource605 	copysource(std::string filein,bool concatin):
606 		filename(filein),concat(concatin){ };
copysourcecopysource607 	copysource():filename(""),concat(false){ };
608 };
609 
610 
CMD_COPY(char * args)611 void DOS_Shell::CMD_COPY(char * args) {
612 	HELP("COPY");
613 	static char defaulttarget[] = ".";
614 	StripSpaces(args);
615 	/* Command uses dta so set it to our internal dta */
616 	RealPt save_dta=dos.dta();
617 	dos.dta(dos.tables.tempdta);
618 	DOS_DTA dta(dos.dta());
619 	Bit32u size;Bit16u date;Bit16u time;Bit8u attr;
620 	char name[DOS_NAMELENGTH_ASCII], lname[LFN_NAMELENGTH+1];
621 	std::vector<copysource> sources;
622 	// ignore /b and /t switches: always copy binary
623 	while(ScanCMDBool(args,"B")) ;
624 	while(ScanCMDBool(args,"T")) ; //Shouldn't this be A ?
625 	while(ScanCMDBool(args,"A")) ;
626 	ScanCMDBool(args,"Y");
627 	ScanCMDBool(args,"-Y");
628 	ScanCMDBool(args,"V");
629 
630 	char * rem=ScanCMDRemain(args);
631 	if (rem) {
632 		WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"),rem);
633 		dos.dta(save_dta);
634 		return;
635 	}
636 	// Gather all sources (extension to copy more then 1 file specified at command line)
637 	// Concatenating files go as follows: All parts except for the last bear the concat flag.
638 	// This construction allows them to be counted (only the non concat set)
639 	char q[]="\"";
640 	char* source_p = NULL;
641 	char source_x[DOS_PATHLENGTH+CROSS_LEN];
642 	while ( (source_p = StripArg(args)) && *source_p ) {
643 		do {
644 			char* plus = strchr(source_p,'+');
645 			// If StripWord() previously cut at a space before a plus then
646 			// set concatenate flag on last source and remove leading plus.
647 			if (plus == source_p && sources.size()) {
648 				sources[sources.size()-1].concat = true;
649 				// If spaces also followed plus then item is only a plus.
650 				if (strlen(++source_p)==0) break;
651 				plus = strchr(source_p,'+');
652 			}
653 			if (plus) *plus++ = 0;
654 			safe_strncpy(source_x,source_p,CROSS_LEN);
655 			bool has_drive_spec = false;
656 			size_t source_x_len = strlen(source_x);
657 			if (source_x_len>0) {
658 				if (source_x[source_x_len-1]==':') has_drive_spec = true;
659 			}
660 			if (!has_drive_spec  && !strpbrk(source_p,"*?") ) { //doubt that fu*\*.* is valid
661 				char spath[DOS_PATHLENGTH];
662 				if (DOS_GetSFNPath(source_p,spath,false) && DOS_FindFirst(spath,0xffff & ~DOS_ATTR_VOLUME)) {
663 					dta.GetResult(name,lname,size,date,time,attr);
664 					if (attr & DOS_ATTR_DIRECTORY)
665 						strcat(source_x,"\\*.*");
666 				}
667 			}
668 			sources.push_back(copysource(source_x,(plus)?true:false));
669 			source_p = plus;
670 		} while(source_p && *source_p);
671 	}
672 	// At least one source has to be there
673 	if (!sources.size() || !sources[0].filename.size()) {
674 		WriteOut(MSG_Get("SHELL_MISSING_PARAMETER"));
675 		dos.dta(save_dta);
676 		return;
677 	};
678 
679 	copysource target;
680 	// If more then one object exists and last target is not part of a
681 	// concat sequence then make it the target.
682 	if(sources.size()>1 && !sources[sources.size()-2].concat){
683 		target = sources.back();
684 		sources.pop_back();
685 	}
686 	//If no target => default target with concat flag true to detect a+b+c
687 	if(target.filename.size() == 0) target = copysource(defaulttarget,true);
688 
689 	copysource oldsource;
690 	copysource source;
691 	Bit32u count = 0;
692 	while(sources.size()) {
693 		/* Get next source item and keep track of old source for concat start end */
694 		oldsource = source;
695 		source = sources[0];
696 		sources.erase(sources.begin());
697 
698 		//Skip first file if doing a+b+c. Set target to first file
699 		if(!oldsource.concat && source.concat && target.concat) {
700 			target = source;
701 			continue;
702 		}
703 
704 		/* Make a full path in the args */
705 		char pathSourcePre[DOS_PATHLENGTH], pathSource[DOS_PATHLENGTH+2];
706 		char pathTarget[DOS_PATHLENGTH];
707 
708 		if (!DOS_Canonicalize(const_cast<char*>(source.filename.c_str()),pathSourcePre)) {
709 			WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
710 			dos.dta(save_dta);
711 			return;
712 		}
713 		strcpy(pathSource,pathSourcePre);
714 		if (uselfn) sprintf(pathSource,"\"%s\"",pathSourcePre);
715 		// cut search pattern
716 		char* pos = strrchr(pathSource,'\\');
717 		if (pos) *(pos+1) = 0;
718 
719 		if (!DOS_Canonicalize(const_cast<char*>(target.filename.c_str()),pathTarget)) {
720 			WriteOut(MSG_Get("SHELL_ILLEGAL_PATH"));
721 			dos.dta(save_dta);
722 			return;
723 		}
724 		char* temp = strstr(pathTarget,"*.*");
725 		if(temp) *temp = 0;//strip off *.* from target
726 
727 		// add '\\' if target is a directory
728 		bool target_is_file = true;
729 		if (pathTarget[strlen(pathTarget)-1]!='\\') {
730 			if (DOS_FindFirst(pathTarget,0xffff & ~DOS_ATTR_VOLUME)) {
731 				dta.GetResult(name,lname,size,date,time,attr);
732 				if (attr & DOS_ATTR_DIRECTORY) {
733 					strcat(pathTarget,"\\");
734 					target_is_file = false;
735 				}
736 			}
737 		} else target_is_file = false;
738 
739 		//Find first sourcefile
740 		char sPath[DOS_PATHLENGTH];
741 		bool ret = DOS_GetSFNPath(source.filename.c_str(),sPath,false) && DOS_FindFirst(const_cast<char*>(sPath),0xffff & ~DOS_ATTR_VOLUME);
742 		if (!ret) {
743 			WriteOut(MSG_Get("SHELL_CMD_FILE_NOT_FOUND"),const_cast<char*>(source.filename.c_str()));
744 			dos.dta(save_dta);
745 			return;
746 		}
747 
748 		Bit16u sourceHandle,targetHandle;
749 		char nameTarget[DOS_PATHLENGTH];
750 		char nameSource[DOS_PATHLENGTH];
751 
752 		bool second_file_of_current_source = false;
753 		while (ret) {
754 			dta.GetResult(name,lname,size,date,time,attr);
755 
756 			if ((attr & DOS_ATTR_DIRECTORY)==0) {
757 				strcpy(nameSource,pathSource);
758 				strcat(nameSource,name);
759 				// Open Source
760 				if (DOS_OpenFile(nameSource,0,&sourceHandle)) {
761 					// Create Target or open it if in concat mode
762 					strcpy(nameTarget,q);
763 					strcat(nameTarget,pathTarget);
764 					if (nameTarget[strlen(nameTarget)-1]=='\\') strcat(nameTarget,uselfn?lname:name);
765 					strcat(nameTarget,q);
766 
767 					//Special variable to ensure that copy * a_file, where a_file is not a directory concats.
768 					bool special = second_file_of_current_source && target_is_file;
769 					second_file_of_current_source = true;
770 					if (special) oldsource.concat = true;
771 					//Don't create a new file when in concat mode
772 					if (oldsource.concat || DOS_CreateFile(nameTarget,0,&targetHandle)) {
773 						Bit32u dummy=0;
774 						//In concat mode. Open the target and seek to the eof
775 						if (!oldsource.concat || (DOS_OpenFile(nameTarget,OPEN_READWRITE,&targetHandle) &&
776 					        	                  DOS_SeekFile(targetHandle,&dummy,DOS_SEEK_END))) {
777 							// Copy
778 							static Bit8u buffer[0x8000]; // static, otherwise stack overflow possible.
779 							bool	failed = false;
780 							Bit16u	toread = 0x8000;
781 							do {
782 								failed |= DOS_ReadFile(sourceHandle,buffer,&toread);
783 								failed |= DOS_WriteFile(targetHandle,buffer,&toread);
784 							} while (toread==0x8000);
785 							failed |= DOS_CloseFile(sourceHandle);
786 							failed |= DOS_CloseFile(targetHandle);
787 							if (strcmp(name,lname)&&uselfn)
788 								WriteOut(" %s [%s]\n",lname,name);
789 							else
790 								WriteOut(" %s\n",uselfn?lname:name);
791 							if(!source.concat && !special) count++; //Only count concat files once
792 						} else {
793 							DOS_CloseFile(sourceHandle);
794 							WriteOut(MSG_Get("SHELL_CMD_COPY_FAILURE"),const_cast<char*>(target.filename.c_str()));
795 						}
796 					} else {
797 						DOS_CloseFile(sourceHandle);
798 						WriteOut(MSG_Get("SHELL_CMD_COPY_FAILURE"),const_cast<char*>(target.filename.c_str()));
799 					}
800 				} else WriteOut(MSG_Get("SHELL_CMD_COPY_FAILURE"),const_cast<char*>(source.filename.c_str()));
801 			};
802 			//On to the next file if the previous one wasn't a device
803 			if ((attr&DOS_ATTR_DEVICE) == 0) ret = DOS_FindNext();
804 			else ret = false;
805 		};
806 	}
807 
808 	WriteOut(MSG_Get("SHELL_CMD_COPY_SUCCESS"),count);
809 	dos.dta(save_dta);
810 }
811 
CMD_SET(char * args)812 void DOS_Shell::CMD_SET(char * args) {
813 	HELP("SET");
814 	StripSpaces(args);
815 	std::string line;
816 	if (!*args) {
817 		/* No command line show all environment lines */
818 		Bitu count=GetEnvCount();
819 		for (Bitu a=0;a<count;a++) {
820 			if (GetEnvNum(a,line)) WriteOut("%s\n",line.c_str());
821 		}
822 		return;
823 	}
824 	//There are args:
825 	char * pcheck = args;
826 	while ( *pcheck && (*pcheck == ' ' || *pcheck == '\t')) pcheck++;
827 	if (*pcheck && strlen(pcheck) >3 && (strncasecmp(pcheck,"/p ",3) == 0)) E_Exit("Set /P is not supported. Use Choice!");
828 
829 	char * p=strpbrk(args, "=");
830 	if (!p) {
831 		if (!GetEnvStr(args,line)) WriteOut(MSG_Get("SHELL_CMD_SET_NOT_SET"),args);
832 		WriteOut("%s\n",line.c_str());
833 	} else {
834 		*p++=0;
835 		/* parse p for envirionment variables */
836 		char parsed[CMD_MAXLINE];
837 		char* p_parsed = parsed;
838 		while(*p) {
839 			if(*p != '%') *p_parsed++ = *p++; //Just add it (most likely path)
840 			else if( *(p+1) == '%') {
841 				*p_parsed++ = '%'; p += 2; //%% => %
842 			} else {
843 				char * second = strchr(++p,'%');
844 				if(!second) continue; *second++ = 0;
845 				std::string temp;
846 				if (GetEnvStr(p,temp)) {
847 					std::string::size_type equals = temp.find('=');
848 					if (equals == std::string::npos) continue;
849 					strcpy(p_parsed,temp.substr(equals+1).c_str());
850 					p_parsed += strlen(p_parsed);
851 				}
852 				p = second;
853 			}
854 		}
855 		*p_parsed = 0;
856 		/* Try setting the variable */
857 		if (!SetEnv(args,parsed)) {
858 			WriteOut(MSG_Get("SHELL_CMD_SET_OUT_OF_SPACE"));
859 		}
860 	}
861 }
862 
CMD_IF(char * args)863 void DOS_Shell::CMD_IF(char * args) {
864 	HELP("IF");
865 	StripSpaces(args,'=');
866 	bool has_not=false;
867 
868 	while (strncasecmp(args,"NOT",3) == 0) {
869 		if (!isspace(*reinterpret_cast<unsigned char*>(&args[3])) && (args[3] != '=')) break;
870 		args += 3;	//skip text
871 		//skip more spaces
872 		StripSpaces(args,'=');
873 		has_not = !has_not;
874 	}
875 
876 	if(strncasecmp(args,"ERRORLEVEL",10) == 0) {
877 		args += 10;	//skip text
878 		//Strip spaces and ==
879 		StripSpaces(args,'=');
880 		char* word = StripWord(args);
881 		if(!isdigit(*word)) {
882 			WriteOut(MSG_Get("SHELL_CMD_IF_ERRORLEVEL_MISSING_NUMBER"));
883 			return;
884 		}
885 
886 		Bit8u n = 0;
887 		do n = n * 10 + (*word - '0');
888 		while (isdigit(*++word));
889 		if(*word && !isspace(*word)) {
890 			WriteOut(MSG_Get("SHELL_CMD_IF_ERRORLEVEL_INVALID_NUMBER"));
891 			return;
892 		}
893 		/* Read the error code from DOS */
894 		if ((dos.return_code>=n) ==(!has_not)) DoCommand(args);
895 		return;
896 	}
897 
898 	if(strncasecmp(args,"EXIST ",6) == 0) {
899 		args += 6; //Skip text
900 		StripSpaces(args);
901 		char* word = StripArg(args);
902 		if (!*word) {
903 			WriteOut(MSG_Get("SHELL_CMD_IF_EXIST_MISSING_FILENAME"));
904 			return;
905 		}
906 
907 		{	/* DOS_FindFirst uses dta so set it to our internal dta */
908 			RealPt save_dta=dos.dta();
909 			dos.dta(dos.tables.tempdta);
910 			bool ret=DOS_FindFirst(word,0xffff & ~DOS_ATTR_VOLUME);
911 			dos.dta(save_dta);
912 			if (ret==(!has_not)) DoCommand(args);
913 		}
914 		return;
915 	}
916 
917 	/* Normal if string compare */
918 
919 	char* word1 = args;
920 	// first word is until space or =
921 	while (*args && !isspace(*reinterpret_cast<unsigned char*>(args)) && (*args != '='))
922 		args++;
923 	char* end_word1 = args;
924 
925 	// scan for =
926 	while (*args && (*args != '='))
927 		args++;
928 	// check for ==
929 	if ((*args==0) || (args[1] != '=')) {
930 		SyntaxError();
931 		return;
932 	}
933 	args += 2;
934 	StripSpaces(args,'=');
935 
936 	char* word2 = args;
937 	// second word is until space or =
938 	while (*args && !isspace(*reinterpret_cast<unsigned char*>(args)) && (*args != '='))
939 		args++;
940 
941 	if (*args) {
942 		*end_word1 = 0;		// mark end of first word
943 		*args++ = 0;		// mark end of second word
944 		StripSpaces(args,'=');
945 
946 		if ((strcmp(word1,word2)==0)==(!has_not)) DoCommand(args);
947 	}
948 }
949 
CMD_GOTO(char * args)950 void DOS_Shell::CMD_GOTO(char * args) {
951 	HELP("GOTO");
952 	StripSpaces(args);
953 	if (!bf) return;
954 	if (*args &&(*args==':')) args++;
955 	//label ends at the first space
956 	char* non_space = args;
957 	while (*non_space) {
958 		if((*non_space == ' ') || (*non_space == '\t'))
959 			*non_space = 0;
960 		else non_space++;
961 	}
962 	if (!*args) {
963 		WriteOut(MSG_Get("SHELL_CMD_GOTO_MISSING_LABEL"));
964 		return;
965 	}
966 	if (!bf->Goto(args)) {
967 		WriteOut(MSG_Get("SHELL_CMD_GOTO_LABEL_NOT_FOUND"),args);
968 		return;
969 	}
970 }
971 
CMD_SHIFT(char * args)972 void DOS_Shell::CMD_SHIFT(char * args ) {
973 	HELP("SHIFT");
974 	if(bf) bf->Shift();
975 }
976 
CMD_TYPE(char * args)977 void DOS_Shell::CMD_TYPE(char * args) {
978 	HELP("TYPE");
979 	StripSpaces(args);
980 	if (!*args) {
981 		WriteOut(MSG_Get("SHELL_SYNTAXERROR"));
982 		return;
983 	}
984 	Bit16u handle;
985 	char * word;
986 nextfile:
987 	word=StripArg(args);
988 	if (!DOS_OpenFile(word,0,&handle)) {
989 		WriteOut(MSG_Get("SHELL_CMD_FILE_NOT_FOUND"),word);
990 		return;
991 	}
992 	Bit16u n;Bit8u c;
993 	do {
994 		n=1;
995 		DOS_ReadFile(handle,&c,&n);
996 		if (c==0x1a) break; // stop at EOF
997 		DOS_WriteFile(STDOUT,&c,&n);
998 	} while (n);
999 	DOS_CloseFile(handle);
1000 	if (*args) goto nextfile;
1001 }
1002 
CMD_REM(char * args)1003 void DOS_Shell::CMD_REM(char * args) {
1004 	HELP("REM");
1005 }
1006 
CMD_PAUSE(char * args)1007 void DOS_Shell::CMD_PAUSE(char * args){
1008 	HELP("PAUSE");
1009 	WriteOut(MSG_Get("SHELL_CMD_PAUSE"));
1010 	Bit8u c;Bit16u n=1;
1011 	DOS_ReadFile(STDIN,&c,&n);
1012 	if (c==0) DOS_ReadFile(STDIN,&c,&n); // read extended key
1013 }
1014 
CMD_CALL(char * args)1015 void DOS_Shell::CMD_CALL(char * args){
1016 	HELP("CALL");
1017 	this->call=true; /* else the old batchfile will be closed first */
1018 	this->ParseLine(args);
1019 	this->call=false;
1020 }
1021 
CMD_DATE(char * args)1022 void DOS_Shell::CMD_DATE(char * args) {
1023 	HELP("DATE");
1024 	if(ScanCMDBool(args,"H")) {
1025 		// synchronize date with host parameter
1026 		time_t curtime;
1027 		struct tm *loctime;
1028 		curtime = time (NULL);
1029 		loctime = localtime (&curtime);
1030 
1031 		reg_cx = loctime->tm_year+1900;
1032 		reg_dh = loctime->tm_mon+1;
1033 		reg_dl = loctime->tm_mday;
1034 
1035 		reg_ah=0x2b; // set system date
1036 		CALLBACK_RunRealInt(0x21);
1037 		return;
1038 	}
1039 	// check if a date was passed in command line
1040 	Bit32u newday,newmonth,newyear;
1041 	if(sscanf(args,"%u-%u-%u",&newmonth,&newday,&newyear)==3) {
1042 		reg_cx = static_cast<Bit16u>(newyear);
1043 		reg_dh = static_cast<Bit8u>(newmonth);
1044 		reg_dl = static_cast<Bit8u>(newday);
1045 
1046 		reg_ah=0x2b; // set system date
1047 		CALLBACK_RunRealInt(0x21);
1048 		if(reg_al==0xff) WriteOut(MSG_Get("SHELL_CMD_DATE_ERROR"));
1049 		return;
1050 	}
1051 	// display the current date
1052 	reg_ah=0x2a; // get system date
1053 	CALLBACK_RunRealInt(0x21);
1054 
1055 	const char* datestring = MSG_Get("SHELL_CMD_DATE_DAYS");
1056 	Bit32u length;
1057 	char day[6] = {0};
1058 	if(sscanf(datestring,"%u",&length) && (length<5) && (strlen(datestring)==(length*7+1))) {
1059 		// date string appears valid
1060 		for(Bit32u i = 0; i < length; i++) day[i] = datestring[reg_al*length+1+i];
1061 	}
1062 	bool dateonly = ScanCMDBool(args,"T");
1063 	if(!dateonly) WriteOut(MSG_Get("SHELL_CMD_DATE_NOW"));
1064 
1065 	const char* formatstring = MSG_Get("SHELL_CMD_DATE_FORMAT");
1066 	if(strlen(formatstring)!=5) return;
1067 	char buffer[15] = {0};
1068 	Bitu bufferptr=0;
1069 	for(Bitu i = 0; i < 5; i++) {
1070 		if(i==1 || i==3) {
1071 			buffer[bufferptr] = formatstring[i];
1072 			bufferptr++;
1073 		} else {
1074 			if(formatstring[i]=='M') bufferptr += sprintf(buffer+bufferptr,"%02u",(Bit8u) reg_dh);
1075 			if(formatstring[i]=='D') bufferptr += sprintf(buffer+bufferptr,"%02u",(Bit8u) reg_dl);
1076 			if(formatstring[i]=='Y') bufferptr += sprintf(buffer+bufferptr,"%04u",(Bit16u) reg_cx);
1077 		}
1078 	}
1079 	WriteOut("%s %s\n",day, buffer);
1080 	if(!dateonly) WriteOut(MSG_Get("SHELL_CMD_DATE_SETHLP"));
1081 };
1082 
CMD_TIME(char * args)1083 void DOS_Shell::CMD_TIME(char * args) {
1084 	HELP("TIME");
1085 	if(ScanCMDBool(args,"H")) {
1086 		// synchronize time with host parameter
1087 		time_t curtime;
1088 		struct tm *loctime;
1089 		curtime = time (NULL);
1090 		loctime = localtime (&curtime);
1091 
1092 		//reg_cx = loctime->;
1093 		//reg_dh = loctime->;
1094 		//reg_dl = loctime->;
1095 
1096 		// reg_ah=0x2d; // set system time TODO
1097 		// CALLBACK_RunRealInt(0x21);
1098 
1099 		Bit32u ticks=(Bit32u)(((double)(loctime->tm_hour*3600+
1100 										loctime->tm_min*60+
1101 										loctime->tm_sec))*18.206481481);
1102 		mem_writed(BIOS_TIMER,ticks);
1103 		return;
1104 	}
1105 	bool timeonly = ScanCMDBool(args,"T");
1106 
1107 	reg_ah=0x2c; // get system time
1108 	CALLBACK_RunRealInt(0x21);
1109 /*
1110 		reg_dl= // 1/100 seconds
1111 		reg_dh= // seconds
1112 		reg_cl= // minutes
1113 		reg_ch= // hours
1114 */
1115 	if(timeonly) {
1116 		WriteOut("%2u:%02u\n",reg_ch,reg_cl);
1117 	} else {
1118 		WriteOut(MSG_Get("SHELL_CMD_TIME_NOW"));
1119 		WriteOut("%2u:%02u:%02u,%02u\n",reg_ch,reg_cl,reg_dh,reg_dl);
1120 	}
1121 };
1122 
CMD_SUBST(char * args)1123 void DOS_Shell::CMD_SUBST (char * args) {
1124 /* If more that one type can be substed think of something else
1125  * E.g. make basedir member dos_drive instead of localdrive
1126  */
1127 	HELP("SUBST");
1128 	localDrive* ldp=0;
1129 	char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
1130 	char temp_str[2] = { 0,0 };
1131 	try {
1132 		strcpy(mountstring,"MOUNT ");
1133 		StripSpaces(args);
1134 		std::string arg;
1135 		CommandLine command(0,args);
1136 
1137 		if (command.GetCount() != 2) throw 0 ;
1138 
1139 		command.FindCommand(1,arg);
1140 		if( (arg.size()>1) && arg[1] !=':')  throw(0);
1141 		temp_str[0]=(char)toupper(args[0]);
1142 		command.FindCommand(2,arg);
1143 		if((arg=="/D") || (arg=="/d")) {
1144 			if(!Drives[temp_str[0]-'A'] ) throw 1; //targetdrive not in use
1145 			strcat(mountstring,"-u ");
1146 			strcat(mountstring,temp_str);
1147 			this->ParseLine(mountstring);
1148 			return;
1149 		}
1150 		if(Drives[temp_str[0]-'A'] ) throw 0; //targetdrive in use
1151 		strcat(mountstring,temp_str);
1152 		strcat(mountstring," ");
1153 
1154   		Bit8u drive;char dir[DOS_PATHLENGTH+2],fulldir[DOS_PATHLENGTH];
1155    		if (strchr(arg.c_str(),'\"')==NULL)
1156 	   		sprintf(dir,"\"%s\"",arg.c_str());
1157 	   	else strcpy(dir,arg.c_str());
1158 		if (!DOS_MakeName(dir,fulldir,&drive)) throw 0;
1159 
1160 		if( ( ldp=dynamic_cast<localDrive*>(Drives[drive])) == 0 ) throw 0;
1161 		char newname[CROSS_LEN];
1162 		strcpy(newname, ldp->basedir);
1163 		strcat(newname,fulldir);
1164 		CROSS_FILENAME(newname);
1165 		ldp->dirCache.ExpandName(newname);
1166 		strcat(mountstring,"\"");
1167 		strcat(mountstring, newname);
1168 		strcat(mountstring,"\"");
1169 		this->ParseLine(mountstring);
1170 	}
1171 	catch(int a){
1172 		if(a == 0) {
1173 			WriteOut(MSG_Get("SHELL_CMD_SUBST_FAILURE"));
1174 		} else {
1175 		       	WriteOut(MSG_Get("SHELL_CMD_SUBST_NO_REMOVE"));
1176 		}
1177 		return;
1178 	}
1179 	catch(...) {		//dynamic cast failed =>so no localdrive
1180 		WriteOut(MSG_Get("SHELL_CMD_SUBST_FAILURE"));
1181 		return;
1182 	}
1183 
1184 	return;
1185 }
1186 
CMD_LOADHIGH(char * args)1187 void DOS_Shell::CMD_LOADHIGH(char *args){
1188 	HELP("LOADHIGH");
1189 	Bit16u umb_start=dos_infoblock.GetStartOfUMBChain();
1190 	Bit8u umb_flag=dos_infoblock.GetUMBChainState();
1191 	Bit8u old_memstrat=(Bit8u)(DOS_GetMemAllocStrategy()&0xff);
1192 	if (umb_start==0x9fff) {
1193 		if ((umb_flag&1)==0) DOS_LinkUMBsToMemChain(1);
1194 		DOS_SetMemAllocStrategy(0x80);	// search in UMBs first
1195 		this->ParseLine(args);
1196 		Bit8u current_umb_flag=dos_infoblock.GetUMBChainState();
1197 		if ((current_umb_flag&1)!=(umb_flag&1)) DOS_LinkUMBsToMemChain(umb_flag);
1198 		DOS_SetMemAllocStrategy(old_memstrat);	// restore strategy
1199 	} else this->ParseLine(args);
1200 }
1201 
CMD_CHOICE(char * args)1202 void DOS_Shell::CMD_CHOICE(char * args){
1203 	HELP("CHOICE");
1204 	static char defchoice[3] = {'y','n',0};
1205 	char *rem = NULL, *ptr;
1206 	bool optN = ScanCMDBool(args,"N");
1207 	bool optS = ScanCMDBool(args,"S"); //Case-sensitive matching
1208 	ScanCMDBool(args,"T"); //Default Choice after timeout
1209 	if (args) {
1210 		char *last = strchr(args,0);
1211 		StripSpaces(args);
1212 		rem = ScanCMDRemain(args);
1213 		if (rem && *rem && (tolower(rem[1]) != 'c')) {
1214 			WriteOut(MSG_Get("SHELL_ILLEGAL_SWITCH"),rem);
1215 			return;
1216 		}
1217 		if (args == rem) args = strchr(rem,0)+1;
1218 		if (rem) rem += 2;
1219 		if(rem && rem[0]==':') rem++; /* optional : after /c */
1220 		if (args > last) args = NULL;
1221 	}
1222 	if (!rem || !*rem) rem = defchoice; /* No choices specified use YN */
1223 	ptr = rem;
1224 	Bit8u c;
1225 	if(!optS) while ((c = *ptr)) *ptr++ = (char)toupper(c); /* When in no case-sensitive mode. make everything upcase */
1226 	if(args && *args ) {
1227 		StripSpaces(args);
1228 		size_t argslen = strlen(args);
1229 		if(argslen>1 && args[0] == '"' && args[argslen-1] =='"') {
1230 			args[argslen-1] = 0; //Remove quotes
1231 			args++;
1232 		}
1233 		WriteOut(args);
1234 	}
1235 	/* Show question prompt of the form [a,b]? where a b are the choice values */
1236 	if (!optN) {
1237 		if(args && *args) WriteOut(" ");
1238 		WriteOut("[");
1239 		size_t len = strlen(rem);
1240 		for(size_t t = 1; t < len; t++) {
1241 			WriteOut("%c,",rem[t-1]);
1242 		}
1243 		WriteOut("%c]?",rem[len-1]);
1244 	}
1245 
1246 	Bit16u n=1;
1247 	do {
1248 		DOS_ReadFile (STDIN,&c,&n);
1249 	} while (!c || !(ptr = strchr(rem,(optS?c:toupper(c)))));
1250 	c = optS?c:(Bit8u)toupper(c);
1251 	DOS_WriteFile (STDOUT,&c, &n);
1252 	dos.return_code = (Bit8u)(ptr-rem+1);
1253 }
1254 
CMD_ATTRIB(char * args)1255 void DOS_Shell::CMD_ATTRIB(char *args){
1256 	HELP("ATTRIB");
1257 	// No-Op for now.
1258 }
1259 
CMD_PATH(char * args)1260 void DOS_Shell::CMD_PATH(char *args){
1261 	HELP("PATH");
1262 	if(args && *args && strlen(args)){
1263 		char pathstring[DOS_PATHLENGTH+CROSS_LEN+20]={ 0 };
1264 		strcpy(pathstring,"set PATH=");
1265 		while(args && *args && (*args=='='|| *args==' '))
1266 		     args++;
1267 		strcat(pathstring,args);
1268 		this->ParseLine(pathstring);
1269 		return;
1270 	} else {
1271 		std::string line;
1272 		if(GetEnvStr("PATH",line)) {
1273         		WriteOut("%s",line.c_str());
1274 		} else {
1275 			WriteOut("PATH=(null)");
1276 		}
1277 	}
1278 }
1279 
CMD_VER(char * args)1280 void DOS_Shell::CMD_VER(char *args) {
1281 	HELP("VER");
1282 	if(args && *args) {
1283 		char* word = StripWord(args);
1284 		if(strcasecmp(word,"set")) return;
1285 		word = StripWord(args);
1286 		if (!*args && !*word) { //Reset
1287 			dos.version.major = 7;
1288 			dos.version.minor = 10;
1289 		} else if (*args == 0 && *word && (strchr(word,'.') != 0)) { //Allow: ver set 7.10
1290 			const char * p = strchr(word,'.');
1291 			dos.version.major = (Bit8u)(atoi(word));
1292 			dos.version.minor = (Bit8u)(atoi(p+1));
1293 		} else { //Official syntax: ver set 7 10
1294 			dos.version.major = (Bit8u)(atoi(word));
1295 			dos.version.minor = (Bit8u)(atoi(args));
1296 		}
1297 		if (autolfn) uselfn=dos.version.major>=7;
1298 	} else WriteOut(MSG_Get("SHELL_CMD_VER_VER"),VERSION,dos.version.major,dos.version.minor,uselfn?"enabled":"disabled");
1299 }
1300