1 /*
2  *  Copyright (C) 2002-2010  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 
19 /* $Id: shell.cpp,v 1.100 2009-07-08 20:05:41 c2woody Exp $ */
20 
21 #include <stdlib.h>
22 #include <stdarg.h>
23 #include <string.h>
24 #include "dosbox.h"
25 #include "regs.h"
26 #include "control.h"
27 #include "shell.h"
28 #include "callback.h"
29 #include "support.h"
30 
31 
32 Bitu call_shellstop;
33 /* Larger scope so shell_del autoexec can use it to
34  * remove things from the environment */
35 Program * first_shell = 0;
36 
shellstop_handler(void)37 static Bitu shellstop_handler(void) {
38 	return CBRET_STOP;
39 }
40 
SHELL_ProgramStart(Program ** make)41 static void SHELL_ProgramStart(Program * * make) {
42 	*make = new DOS_Shell;
43 }
44 
45 #define AUTOEXEC_SIZE 4096
46 static char autoexec_data[AUTOEXEC_SIZE] = { 0 };
47 static std::list<std::string> autoexec_strings;
48 typedef std::list<std::string>::iterator auto_it;
49 
50 void VFILE_Remove(const char *name);
51 
Install(const std::string & in)52 void AutoexecObject::Install(const std::string &in) {
53 	if(GCC_UNLIKELY(installed)) E_Exit("autoexec: allready created %s",buf.c_str());
54 	installed = true;
55 	buf = in;
56 	autoexec_strings.push_back(buf);
57 	this->CreateAutoexec();
58 
59 	//autoexec.bat is normally created AUTOEXEC_Init.
60 	//But if we are allready running (first_shell)
61 	//we have to update the envirionment to display changes
62 
63 	if(first_shell)	{
64 		//create a copy as the string will be modified
65 		std::string::size_type n = buf.size();
66 		char* buf2 = new char[n + 1];
67 		safe_strncpy(buf2, buf.c_str(), n + 1);
68 		if((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
69 			char* after_set = buf2 + 4;//move to variable that is being set
70 			char* test = strpbrk(after_set,"=");
71 			if(!test) {first_shell->SetEnv(after_set,"");return;}
72 			*test++ = 0;
73 			//If the shell is running/exists update the environment
74 			first_shell->SetEnv(after_set,test);
75 		}
76 		delete [] buf2;
77 	}
78 }
79 
InstallBefore(const std::string & in)80 void AutoexecObject::InstallBefore(const std::string &in) {
81 	if(GCC_UNLIKELY(installed)) E_Exit("autoexec: allready created %s",buf.c_str());
82 	installed = true;
83 	buf = in;
84 	autoexec_strings.push_front(buf);
85 	this->CreateAutoexec();
86 }
87 
CreateAutoexec(void)88 void AutoexecObject::CreateAutoexec(void) {
89 	/* Remove old autoexec.bat if the shell exists */
90 	if(first_shell)	VFILE_Remove("AUTOEXEC.BAT");
91 
92 	//Create a new autoexec.bat
93 	autoexec_data[0] = 0;
94 	size_t auto_len;
95 	for(auto_it it=  autoexec_strings.begin(); it != autoexec_strings.end(); it++) {
96 		auto_len = strlen(autoexec_data);
97 		if ((auto_len+(*it).length()+3)>AUTOEXEC_SIZE) {
98 			E_Exit("SYSTEM:Autoexec.bat file overflow");
99 		}
100 		sprintf((autoexec_data+auto_len),"%s\r\n",(*it).c_str());
101 	}
102 	if(first_shell) VFILE_Register("AUTOEXEC.BAT",(Bit8u *)autoexec_data,(Bit32u)strlen(autoexec_data));
103 }
104 
~AutoexecObject()105 AutoexecObject::~AutoexecObject(){
106 	if(!installed) return;
107 
108 	// Remove the line from the autoexecbuffer and update environment
109 	for(auto_it it = autoexec_strings.begin(); it != autoexec_strings.end(); ) {
110 		if((*it) == buf) {
111 			it = autoexec_strings.erase(it);
112 			std::string::size_type n = buf.size();
113 			char* buf2 = new char[n + 1];
114 			safe_strncpy(buf2, buf.c_str(), n + 1);
115 			// If it's a environment variable remove it from there as well
116 			if((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
117 				char* after_set = buf2 + 4;//move to variable that is being set
118 				char* test = strpbrk(after_set,"=");
119 				if(!test) continue;
120 				*test = 0;
121 				//If the shell is running/exists update the environment
122 				if(first_shell) first_shell->SetEnv(after_set,"");
123 			}
124 			delete [] buf2;
125 		} else it++;
126 	}
127 	this->CreateAutoexec();
128 }
129 
DOS_Shell()130 DOS_Shell::DOS_Shell():Program(){
131 	input_handle=STDIN;
132 	echo=true;
133 	exit=false;
134 	bf=0;
135 	call=false;
136 	completion_start = NULL;
137 }
138 
GetRedirection(char * s,char ** ifn,char ** ofn,bool * append)139 Bitu DOS_Shell::GetRedirection(char *s, char **ifn, char **ofn,bool * append) {
140 
141 	char * lr=s;
142 	char * lw=s;
143 	char ch;
144 	Bitu num=0;
145 	bool quote = false;
146 	char* t;
147 
148 	while ( (ch=*lr++) ) {
149 		if(quote && ch != '"') { /* don't parse redirection within quotes. Not perfect yet. Escaped quotes will mess the count up */
150 			*lw++ = ch;
151 			continue;
152 		}
153 
154 		switch (ch) {
155 		case '"':
156 			quote = !quote;
157 			break;
158 		case '>':
159 			*append=((*lr)=='>');
160 			if (*append) lr++;
161 			lr=ltrim(lr);
162 			if (*ofn) free(*ofn);
163 			*ofn=lr;
164 			while (*lr && *lr!=' ' && *lr!='<' && *lr!='|') lr++;
165 			//if it ends on a : => remove it.
166 			if((*ofn != lr) && (lr[-1] == ':')) lr[-1] = 0;
167 //			if(*lr && *(lr+1))
168 //				*lr++=0;
169 //			else
170 //				*lr=0;
171 			t = (char*)malloc(lr-*ofn+1);
172 			safe_strncpy(t,*ofn,lr-*ofn+1);
173 			*ofn=t;
174 			continue;
175 		case '<':
176 			if (*ifn) free(*ifn);
177 			lr=ltrim(lr);
178 			*ifn=lr;
179 			while (*lr && *lr!=' ' && *lr!='>' && *lr != '|') lr++;
180 			if((*ifn != lr) && (lr[-1] == ':')) lr[-1] = 0;
181 //			if(*lr && *(lr+1))
182 //				*lr++=0;
183 //			else
184 //				*lr=0;
185 			t = (char*)malloc(lr-*ifn+1);
186 			safe_strncpy(t,*ifn,lr-*ifn+1);
187 			*ifn=t;
188 			continue;
189 		case '|':
190 			ch=0;
191 			num++;
192 		}
193 		*lw++=ch;
194 	}
195 	*lw=0;
196 	return num;
197 }
198 
ParseLine(char * line)199 void DOS_Shell::ParseLine(char * line) {
200 	LOG(LOG_EXEC,LOG_ERROR)("Parsing command line: %s",line);
201 	/* Check for a leading @ */
202  	if (line[0] == '@') line[0] = ' ';
203 	line = trim(line);
204 
205 	/* Do redirection and pipe checks */
206 
207 	char * in  = 0;
208 	char * out = 0;
209 
210 	Bit16u dummy,dummy2;
211 	Bit32u bigdummy = 0;
212 	Bitu num = 0;		/* Number of commands in this line */
213 	bool append;
214 	bool normalstdin  = false;	/* wether stdin/out are open on start. */
215 	bool normalstdout = false;	/* Bug: Assumed is they are "con"      */
216 
217 	num = GetRedirection(line,&in, &out,&append);
218 	if (num>1) LOG_MSG("SHELL:Multiple command on 1 line not supported");
219 	if (in || out) {
220 		normalstdin  = (psp->GetFileHandle(0) != 0xff);
221 		normalstdout = (psp->GetFileHandle(1) != 0xff);
222 	}
223 	if (in) {
224 		if(DOS_OpenFile(in,OPEN_READ,&dummy)) {	//Test if file exists
225 			DOS_CloseFile(dummy);
226 			LOG_MSG("SHELL:Redirect input from %s",in);
227 			if(normalstdin) DOS_CloseFile(0);	//Close stdin
228 			DOS_OpenFile(in,OPEN_READ,&dummy);	//Open new stdin
229 		}
230 	}
231 	if (out){
232 		LOG_MSG("SHELL:Redirect output to %s",out);
233 		if(normalstdout) DOS_CloseFile(1);
234 		if(!normalstdin && !in) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
235 		bool status = true;
236 		/* Create if not exist. Open if exist. Both in read/write mode */
237 		if(append) {
238 			if( (status = DOS_OpenFile(out,OPEN_READWRITE,&dummy)) ) {
239 				 DOS_SeekFile(1,&bigdummy,DOS_SEEK_END);
240 			} else {
241 				status = DOS_CreateFile(out,DOS_ATTR_ARCHIVE,&dummy);	//Create if not exists.
242 			}
243 		} else {
244 			status = DOS_OpenFileExtended(out,OPEN_READWRITE,DOS_ATTR_ARCHIVE,0x12,&dummy,&dummy2);
245 		}
246 
247 		if(!status && normalstdout) DOS_OpenFile("con",OPEN_READWRITE,&dummy); //Read only file, open con again
248 		if(!normalstdin && !in) DOS_CloseFile(0);
249 	}
250 	/* Run the actual command */
251 	DoCommand(line);
252 	/* Restore handles */
253 	if(in) {
254 		DOS_CloseFile(0);
255 		if(normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
256 		free(in);
257 	}
258 	if(out) {
259 		DOS_CloseFile(1);
260 		if(!normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
261 		if(normalstdout) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
262 		if(!normalstdin) DOS_CloseFile(0);
263 		free(out);
264 	}
265 }
266 
267 
268 
RunInternal(void)269 void DOS_Shell::RunInternal(void)
270 {
271 	char input_line[CMD_MAXLINE] = {0};
272 	while(bf && bf->ReadLine(input_line))
273 	{
274 		if (echo) {
275 				if (input_line[0] != '@') {
276 					ShowPrompt();
277 					WriteOut_NoParsing(input_line);
278 					WriteOut_NoParsing("\n");
279 				};
280 			};
281 		ParseLine(input_line);
282 	}
283 	return;
284 }
285 
Run(void)286 void DOS_Shell::Run(void) {
287 	char input_line[CMD_MAXLINE] = {0};
288 	std::string line;
289 	if (cmd->FindStringRemain("/C",line)) {
290 		strcpy(input_line,line.c_str());
291 		char* sep = strpbrk(input_line,"\r\n"); //GTA installer
292 		if (sep) *sep = 0;
293 		DOS_Shell temp;
294 		temp.echo = echo;
295 		temp.ParseLine(input_line);		//for *.exe *.com  |*.bat creates the bf needed by runinternal;
296 		temp.RunInternal();				// exits when no bf is found.
297 		return;
298 	}
299 	/* Start a normal shell and check for a first command init */
300 	WriteOut(MSG_Get("SHELL_STARTUP_BEGIN"),VERSION);
301 #if C_DEBUG
302 	WriteOut(MSG_Get("SHELL_STARTUP_DEBUG"));
303 #endif
304 	if (machine == MCH_CGA) WriteOut(MSG_Get("SHELL_STARTUP_CGA"));
305 	if (machine == MCH_HERC) WriteOut(MSG_Get("SHELL_STARTUP_HERC"));
306 	WriteOut(MSG_Get("SHELL_STARTUP_END"));
307 
308 	if (cmd->FindString("/INIT",line,true)) {
309 		strcpy(input_line,line.c_str());
310 		line.erase();
311 		ParseLine(input_line);
312 	}
313 	do {
314 		if (bf){
315 			if(bf->ReadLine(input_line)) {
316 				if (echo) {
317 					if (input_line[0]!='@') {
318 						ShowPrompt();
319 						WriteOut_NoParsing(input_line);
320 						WriteOut_NoParsing("\n");
321 					};
322 				};
323 				ParseLine(input_line);
324 				if (echo) WriteOut("\n");
325 			}
326 		} else {
327 			if (echo) ShowPrompt();
328 			InputCommand(input_line);
329 			ParseLine(input_line);
330 			if (echo && !bf) WriteOut_NoParsing("\n");
331 		}
332 	} while (!exit);
333 }
334 
SyntaxError(void)335 void DOS_Shell::SyntaxError(void) {
336 	WriteOut(MSG_Get("SHELL_SYNTAXERROR"));
337 }
338 
339 class AUTOEXEC:public Module_base {
340 private:
341 	AutoexecObject autoexec[17];
342 	AutoexecObject autoexec_echo;
343 public:
AUTOEXEC(Section * configuration)344 	AUTOEXEC(Section* configuration):Module_base(configuration) {
345 		/* Register a virtual AUOEXEC.BAT file */
346 		std::string line;
347 		Section_line * section=static_cast<Section_line *>(configuration);
348 
349 		/* Check -securemode switch to disable mount/imgmount/boot after running autoexec.bat */
350 		bool secure = control->cmdline->FindExist("-securemode",true);
351 
352 		/* add stuff from the configfile unless -noautexec or -securemode is specified. */
353 		char * extra = const_cast<char*>(section->data.c_str());
354 		if (extra && !secure && !control->cmdline->FindExist("-noautoexec",true)) {
355 			/* detect if "echo off" is the first line */
356 			bool echo_off  = !strncasecmp(extra,"echo off",8);
357 			if (!echo_off) echo_off = !strncasecmp(extra,"@echo off",9);
358 
359 			/* if "echo off" add it to the front of autoexec.bat */
360 			if(echo_off) autoexec_echo.InstallBefore("@echo off");
361 
362 			/* Install the stuff from the configfile */
363 			autoexec[0].Install(section->data);
364 		}
365 
366 		/* Check to see for extra command line options to be added (before the command specified on commandline) */
367 		/* Maximum of extra commands: 10 */
368 		Bitu i = 1;
369 		while (control->cmdline->FindString("-c",line,true) && (i <= 11)) {
370 #if defined (WIN32) || defined (OS2)
371 			//replace single with double quotes so that mount commands can contain spaces
372 			for(Bitu temp = 0;temp < line.size();++temp) if(line[temp] == '\'') line[temp]='\"';
373 #endif //Linux users can simply use \" in their shell
374 			autoexec[i++].Install(line);
375 		}
376 
377 		/* Check for the -exit switch which causes dosbox to when the command on the commandline has finished */
378 		bool addexit = control->cmdline->FindExist("-exit",true);
379 
380 		/* Check for first command being a directory or file */
381 		char buffer[CROSS_LEN];
382 		char orig[CROSS_LEN];
383 		char cross_filesplit[2] = {CROSS_FILESPLIT , 0};
384 		/* Combining -securemode and no parameter leaves you with a lovely Z:\. */
385 		if ( !control->cmdline->FindCommand(1,line) ) {
386 			if ( secure ) autoexec[12].Install("z:\\config.com -securemode");
387 		} else {
388 			struct stat test;
389 			strcpy(buffer,line.c_str());
390 			if (stat(buffer,&test)){
391 				getcwd(buffer,CROSS_LEN);
392 				strcat(buffer,cross_filesplit);
393 				strcat(buffer,line.c_str());
394 				if (stat(buffer,&test)) goto nomount;
395 			}
396 			if (test.st_mode & S_IFDIR) {
397 				autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
398 				autoexec[13].Install("C:");
399 				if(secure) autoexec[14].Install("z:\\config.com -securemode");
400 			} else {
401 				char* name = strrchr(buffer,CROSS_FILESPLIT);
402 				if (!name) { //Only a filename
403 					line = buffer;
404 					getcwd(buffer,CROSS_LEN);
405 					strcat(buffer,cross_filesplit);
406 					strcat(buffer,line.c_str());
407 					if(stat(buffer,&test)) goto nomount;
408 					name = strrchr(buffer,CROSS_FILESPLIT);
409 					if(!name) goto nomount;
410 				}
411 				*name++ = 0;
412 				if (access(buffer,F_OK)) goto nomount;
413 				autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
414 				autoexec[13].Install("C:");
415 				/* Save the non modified filename (so boot and imgmount can use it (long filenames, case sensivitive)*/
416 				strcpy(orig,name);
417 				upcase(name);
418 				if(strstr(name,".BAT") != 0) {
419 					if(secure) autoexec[14].Install("z:\\config.com -securemode");
420 					/* BATch files are called else exit will not work */
421 					autoexec[15].Install(std::string("CALL ") + name);
422 					if(addexit) autoexec[16].Install("exit");
423 				} else if((strstr(name,".IMG") != 0) || (strstr(name,".IMA") !=0 )) {
424 					//No secure mode here as boot is destructive and enabling securemode disables boot
425 					/* Boot image files */
426 					autoexec[15].Install(std::string("BOOT ") + orig);
427 				} else if((strstr(name,".ISO") != 0) || (strstr(name,".CUE") !=0 )) {
428 					if(secure) autoexec[14].Install("z:\\config.com -securemode");
429 					/* imgmount CD image files */
430 					autoexec[15].Install(std::string("IMGMOUNT D \"") + orig + std::string("\" -t iso"));
431 					//autoexec[16].Install("D:");
432 					/* Makes no sense to exit here */
433 				} else {
434 					if(secure) autoexec[14].Install("z:\\config.com -securemode");
435 					autoexec[15].Install(name);
436 					if(addexit) autoexec[16].Install("exit");
437 				}
438 			}
439 		}
440 nomount:
441 		VFILE_Register("AUTOEXEC.BAT",(Bit8u *)autoexec_data,(Bit32u)strlen(autoexec_data));
442 	}
443 };
444 
445 static AUTOEXEC* test;
446 
AUTOEXEC_Init(Section * sec)447 void AUTOEXEC_Init(Section * sec) {
448 	test = new AUTOEXEC(sec);
449 }
450 
451 static char const * const path_string="PATH=Z:\\";
452 static char const * const comspec_string="COMSPEC=Z:\\COMMAND.COM";
453 static char const * const full_name="Z:\\COMMAND.COM";
454 static char const * const init_line="/INIT AUTOEXEC.BAT";
455 
SHELL_Init()456 void SHELL_Init() {
457 	/* Add messages */
458 	MSG_Add("SHELL_ILLEGAL_PATH","Illegal Path.\n");
459 	MSG_Add("SHELL_CMD_HELP","If you want a list of all supported commands type \033[33;1mhelp /all\033[0m .\nA short list of the most often used commands:\n");
460 	MSG_Add("SHELL_CMD_ECHO_ON","ECHO is on.\n");
461 	MSG_Add("SHELL_CMD_ECHO_OFF","ECHO is off.\n");
462 	MSG_Add("SHELL_ILLEGAL_SWITCH","Illegal switch: %s.\n");
463 	MSG_Add("SHELL_MISSING_PARAMETER","Required parameter missing.\n");
464 	MSG_Add("SHELL_CMD_CHDIR_ERROR","Unable to change to: %s.\n");
465 	MSG_Add("SHELL_CMD_CHDIR_HINT","To change to different drive type \033[31m%c:\033[0m\n");
466 	MSG_Add("SHELL_CMD_CHDIR_HINT_2","directoryname is longer than 8 characters and/or contains spaces.\nTry \033[31mcd %s\033[0m\n");
467 	MSG_Add("SHELL_CMD_CHDIR_HINT_3","You are still on drive Z:, change to a mounted drive with \033[31mC:\033[0m.\n");
468 	MSG_Add("SHELL_CMD_MKDIR_ERROR","Unable to make: %s.\n");
469 	MSG_Add("SHELL_CMD_RMDIR_ERROR","Unable to remove: %s.\n");
470 	MSG_Add("SHELL_CMD_DEL_ERROR","Unable to delete: %s.\n");
471 	MSG_Add("SHELL_SYNTAXERROR","The syntax of the command is incorrect.\n");
472 	MSG_Add("SHELL_CMD_SET_NOT_SET","Environment variable %s not defined.\n");
473 	MSG_Add("SHELL_CMD_SET_OUT_OF_SPACE","Not enough environment space left.\n");
474 	MSG_Add("SHELL_CMD_IF_EXIST_MISSING_FILENAME","IF EXIST: Missing filename.\n");
475 	MSG_Add("SHELL_CMD_IF_ERRORLEVEL_MISSING_NUMBER","IF ERRORLEVEL: Missing number.\n");
476 	MSG_Add("SHELL_CMD_IF_ERRORLEVEL_INVALID_NUMBER","IF ERRORLEVEL: Invalid number.\n");
477 	MSG_Add("SHELL_CMD_GOTO_MISSING_LABEL","No label supplied to GOTO command.\n");
478 	MSG_Add("SHELL_CMD_GOTO_LABEL_NOT_FOUND","GOTO: Label %s not found.\n");
479 	MSG_Add("SHELL_CMD_FILE_NOT_FOUND","File %s not found.\n");
480 	MSG_Add("SHELL_CMD_FILE_EXISTS","File %s already exists.\n");
481 	MSG_Add("SHELL_CMD_DIR_INTRO","Directory of %s.\n");
482 	MSG_Add("SHELL_CMD_DIR_BYTES_USED","%5d File(s) %17s Bytes.\n");
483 	MSG_Add("SHELL_CMD_DIR_BYTES_FREE","%5d Dir(s)  %17s Bytes free.\n");
484 	MSG_Add("SHELL_EXECUTE_DRIVE_NOT_FOUND","Drive %c does not exist!\nYou must \033[31mmount\033[0m it first. Type \033[1;33mintro\033[0m or \033[1;33mintro mount\033[0m for more information.\n");
485 	MSG_Add("SHELL_EXECUTE_ILLEGAL_COMMAND","Illegal command: %s.\n");
486 	MSG_Add("SHELL_CMD_PAUSE","Press any key to continue.\n");
487 	MSG_Add("SHELL_CMD_PAUSE_HELP","Waits for 1 keystroke to continue.\n");
488 	MSG_Add("SHELL_CMD_COPY_FAILURE","Copy failure : %s.\n");
489 	MSG_Add("SHELL_CMD_COPY_SUCCESS","   %d File(s) copied.\n");
490 	MSG_Add("SHELL_CMD_SUBST_NO_REMOVE","Removing drive not supported. Doing nothing.\n");
491 	MSG_Add("SHELL_CMD_SUBST_FAILURE","SUBST failed. You either made an error in your commandline or the target drive is already used.\nIt's only possible to use SUBST on Local drives");
492 
493 	MSG_Add("SHELL_STARTUP_BEGIN",
494 		"\033[44;1m\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
495 		"\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
496 		"\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB\n"
497 		"\xBA \033[32mWelcome to DOSBox v%-8s\033[37m                                        \xBA\n"
498 		"\xBA                                                                    \xBA\n"
499 //		"\xBA DOSBox runs real and protected mode games.                         \xBA\n"
500 		"\xBA For a short introduction for new users type: \033[33mINTRO\033[37m                 \xBA\n"
501 		"\xBA For supported shell commands type: \033[33mHELP\033[37m                            \xBA\n"
502 		"\xBA                                                                    \xBA\n"
503 		"\xBA To adjust the emulated CPU speed, use \033[31mctrl-F11\033[37m and \033[31mctrl-F12\033[37m.       \xBA\n"
504 		"\xBA To activate the keymapper \033[31mctrl-F1\033[37m.                                 \xBA\n"
505 		"\xBA For more information read the \033[36mREADME\033[37m file in the DOSBox directory. \xBA\n"
506 		"\xBA                                                                    \xBA\n"
507 	);
508 	MSG_Add("SHELL_STARTUP_CGA","\xBA DOSBox supports Composite CGA mode.                                \xBA\n"
509 	        "\xBA Use \033[31m(alt-)F11\033[37m to change the colours when in this mode.             \xBA\n"
510 	        "\xBA                                                                    \xBA\n"
511 	);
512 	MSG_Add("SHELL_STARTUP_HERC","\xBA Use \033[31mF11\033[37m to cycle through white, amber, and green monochrome color. \xBA\n"
513 	        "\xBA                                                                    \xBA\n"
514 	);
515 	MSG_Add("SHELL_STARTUP_DEBUG",
516 	        "\xBA Press \033[31malt-Pause\033[37m to enter the debugger or start the exe with \033[33mDEBUG\033[37m. \xBA\n"
517 	        "\xBA                                                                    \xBA\n"
518 	);
519 	MSG_Add("SHELL_STARTUP_END",
520 	        "\xBA \033[32mHAVE FUN!\033[37m                                                          \xBA\n"
521 	        "\xBA \033[32mThe DOSBox Team \033[33mhttp://www.dosbox.com\033[37m                              \xBA\n"
522 	        "\xC8\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
523 	        "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
524 	        "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC\033[0m\n"
525 	        //"\n" //Breaks the startup message if you type a mount and a drive change.
526 	);
527 	MSG_Add("SHELL_CMD_CHDIR_HELP","Displays/changes the current directory.\n");
528 	MSG_Add("SHELL_CMD_CHDIR_HELP_LONG","CHDIR [drive:][path]\n"
529 	        "CHDIR [..]\n"
530 	        "CD [drive:][path]\n"
531 	        "CD [..]\n\n"
532 	        "  ..   Specifies that you want to change to the parent directory.\n\n"
533 	        "Type CD drive: to display the current directory in the specified drive.\n"
534 	        "Type CD without parameters to display the current drive and directory.\n");
535 	MSG_Add("SHELL_CMD_CLS_HELP","Clear screen.\n");
536 	MSG_Add("SHELL_CMD_DIR_HELP","Directory View.\n");
537 	MSG_Add("SHELL_CMD_ECHO_HELP","Display messages and enable/disable command echoing.\n");
538 	MSG_Add("SHELL_CMD_EXIT_HELP","Exit from the shell.\n");
539 	MSG_Add("SHELL_CMD_HELP_HELP","Show help.\n");
540 	MSG_Add("SHELL_CMD_MKDIR_HELP","Make Directory.\n");
541 	MSG_Add("SHELL_CMD_MKDIR_HELP_LONG","MKDIR [drive:][path]\n"
542 	        "MD [drive:][path]\n");
543 	MSG_Add("SHELL_CMD_RMDIR_HELP","Remove Directory.\n");
544 	MSG_Add("SHELL_CMD_RMDIR_HELP_LONG","RMDIR [drive:][path]\n"
545 	        "RD [drive:][path]\n");
546 	MSG_Add("SHELL_CMD_SET_HELP","Change environment variables.\n");
547 	MSG_Add("SHELL_CMD_IF_HELP","Performs conditional processing in batch programs.\n");
548 	MSG_Add("SHELL_CMD_GOTO_HELP","Jump to a labeled line in a batch script.\n");
549 	MSG_Add("SHELL_CMD_SHIFT_HELP","Leftshift commandline parameters in a batch script.\n");
550 	MSG_Add("SHELL_CMD_TYPE_HELP","Display the contents of a text-file.\n");
551 	MSG_Add("SHELL_CMD_TYPE_HELP_LONG","TYPE [drive:][path][filename]\n");
552 	MSG_Add("SHELL_CMD_REM_HELP","Add comments in a batch file.\n");
553 	MSG_Add("SHELL_CMD_REM_HELP_LONG","REM [comment]\n");
554 	MSG_Add("SHELL_CMD_NO_WILD","This is a simple version of the command, no wildcards allowed!\n");
555 	MSG_Add("SHELL_CMD_RENAME_HELP","Renames one or more files.\n");
556 	MSG_Add("SHELL_CMD_RENAME_HELP_LONG","RENAME [drive:][path]filename1 filename2.\n"
557 	        "REN [drive:][path]filename1 filename2.\n\n"
558 	        "Note that you can not specify a new drive or path for your destination file.\n");
559 	MSG_Add("SHELL_CMD_DELETE_HELP","Removes one or more files.\n");
560 	MSG_Add("SHELL_CMD_COPY_HELP","Copy files.\n");
561 	MSG_Add("SHELL_CMD_CALL_HELP","Start a batch file from within another batch file.\n");
562 	MSG_Add("SHELL_CMD_SUBST_HELP","Assign an internal directory to a drive.\n");
563 	MSG_Add("SHELL_CMD_LOADHIGH_HELP","Loads a program into upper memory (requires xms=true,umb=true).\n");
564 	MSG_Add("SHELL_CMD_CHOICE_HELP","Waits for a keypress and sets ERRORLEVEL.\n");
565 	MSG_Add("SHELL_CMD_CHOICE_HELP_LONG","CHOICE [/C:choices] [/N] [/S] text\n"
566 	        "  /C[:]choices  -  Specifies allowable keys.  Default is: yn.\n"
567 	        "  /N  -  Do not display the choices at end of prompt.\n"
568 	        "  /S  -  Enables case-sensitive choices to be selected.\n"
569 	        "  text  -  The text to display as a prompt.\n");
570 	MSG_Add("SHELL_CMD_ATTRIB_HELP","Does nothing. Provided for compatibility.\n");
571 	MSG_Add("SHELL_CMD_PATH_HELP","Provided for compatibility.\n");
572 	MSG_Add("SHELL_CMD_VER_HELP","View and set the reported DOS version.\n");
573 	MSG_Add("SHELL_CMD_VER_VER","DOSBox version %s. Reported DOS version %d.%02d.\n");
574 
575 	/* Regular startup */
576 	call_shellstop=CALLBACK_Allocate();
577 	/* Setup the startup CS:IP to kill the last running machine when exitted */
578 	RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
579 	SegSet16(cs,RealSeg(newcsip));
580 	reg_ip=RealOff(newcsip);
581 
582 	CALLBACK_Setup(call_shellstop,shellstop_handler,CB_IRET,"shell stop");
583 	PROGRAMS_MakeFile("COMMAND.COM",SHELL_ProgramStart);
584 
585 	/* Now call up the shell for the first time */
586 	Bit16u psp_seg=DOS_FIRST_SHELL;
587 	Bit16u env_seg=DOS_FIRST_SHELL+19; //DOS_GetMemory(1+(4096/16))+1;
588 	Bit16u stack_seg=DOS_GetMemory(2048/16);
589 	SegSet16(ss,stack_seg);
590 	reg_sp=2046;
591 
592 	/* Set up int 24 and psp (Telarium games) */
593 	real_writeb(psp_seg+16+1,0,0xea);		/* far jmp */
594 	real_writed(psp_seg+16+1,1,real_readd(0,0x24*4));
595 	real_writed(0,0x24*4,((Bit32u)psp_seg<<16) | ((16+1)<<4));
596 
597 	/* Set up int 23 to "int 20" in the psp. Fixes what.exe */
598 	real_writed(0,0x23*4,((Bit32u)psp_seg<<16));
599 
600 	/* Setup MCBs */
601 	DOS_MCB pspmcb((Bit16u)(psp_seg-1));
602 	pspmcb.SetPSPSeg(psp_seg);	// MCB of the command shell psp
603 	pspmcb.SetSize(0x10+2);
604 	pspmcb.SetType(0x4d);
605 	DOS_MCB envmcb((Bit16u)(env_seg-1));
606 	envmcb.SetPSPSeg(psp_seg);	// MCB of the command shell environment
607 	envmcb.SetSize(DOS_MEM_START-env_seg);
608 	envmcb.SetType(0x4d);
609 
610 	/* Setup environment */
611 	PhysPt env_write=PhysMake(env_seg,0);
612 	MEM_BlockWrite(env_write,path_string,(Bitu)(strlen(path_string)+1));
613 	env_write += (PhysPt)(strlen(path_string)+1);
614 	MEM_BlockWrite(env_write,comspec_string,(Bitu)(strlen(comspec_string)+1));
615 	env_write += (PhysPt)(strlen(comspec_string)+1);
616 	mem_writeb(env_write++,0);
617 	mem_writew(env_write,1);
618 	env_write+=2;
619 	MEM_BlockWrite(env_write,full_name,(Bitu)(strlen(full_name)+1));
620 
621 	DOS_PSP psp(psp_seg);
622 	psp.MakeNew(0);
623 	dos.psp(psp_seg);
624 
625 	/* The start of the filetable in the psp must look like this:
626 	 * 01 01 01 00 02
627 	 * In order to achieve this: First open 2 files. Close the first and
628 	 * duplicate the second (so the entries get 01) */
629 	Bit16u dummy=0;
630 	DOS_OpenFile("CON",OPEN_READWRITE,&dummy);	/* STDIN  */
631 	DOS_OpenFile("CON",OPEN_READWRITE,&dummy);	/* STDOUT */
632 	DOS_CloseFile(0);							/* Close STDIN */
633 	DOS_ForceDuplicateEntry(1,0);				/* "new" STDIN */
634 	DOS_ForceDuplicateEntry(1,2);				/* STDERR */
635 	DOS_OpenFile("CON",OPEN_READWRITE,&dummy);	/* STDAUX */
636 	DOS_OpenFile("CON",OPEN_READWRITE,&dummy);	/* STDPRN */
637 
638 	psp.SetParent(psp_seg);
639 	/* Set the environment */
640 	psp.SetEnvironment(env_seg);
641 	/* Set the command line for the shell start up */
642 	CommandTail tail;
643 	tail.count=(Bit8u)strlen(init_line);
644 	strcpy(tail.buffer,init_line);
645 	MEM_BlockWrite(PhysMake(psp_seg,128),&tail,128);
646 
647 	/* Setup internal DOS Variables */
648 	dos.dta(RealMake(psp_seg,0x80));
649 	dos.psp(psp_seg);
650 
651 
652 	SHELL_ProgramStart(&first_shell);
653 	first_shell->Run();
654 	delete first_shell;
655 	first_shell = 0;//Make clear that it shouldn't be used anymore
656 }
657