DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/shell/shell.cpp
00001 /*
00002  *  Copyright (C) 2002-2015  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00017  */
00018 
00019 #include <assert.h>
00020 #include <stdlib.h>
00021 #include <stdarg.h>
00022 #include <string.h>
00023 #include "dosbox.h"
00024 #include "regs.h"
00025 #include "control.h"
00026 #include "shell.h"
00027 #include "menu.h"
00028 #include "cpu.h"
00029 #include "callback.h"
00030 #include "support.h"
00031 #include "builtin.h"
00032 #include "build_timestamp.h"
00033 
00034 extern bool enable_config_as_shell_commands;
00035 extern bool dos_shell_running_program;
00036 
00037 Bitu shell_psp = 0;
00038 
00039 void CALLBACK_DeAllocate(Bitu in);
00040 
00041 Bitu call_shellstop = 0;
00042 /* Larger scope so shell_del autoexec can use it to
00043  * remove things from the environment */
00044 Program * first_shell = 0; 
00045 
00046 static Bitu shellstop_handler(void) {
00047         return CBRET_STOP;
00048 }
00049 
00050 static void SHELL_ProgramStart(Program * * make) {
00051         *make = new DOS_Shell;
00052 }
00053 
00054 #define AUTOEXEC_SIZE 4096
00055 static char autoexec_data[AUTOEXEC_SIZE] = { 0 };
00056 static std::list<std::string> autoexec_strings;
00057 typedef std::list<std::string>::iterator auto_it;
00058 
00059 void VFILE_Remove(const char *name);
00060 
00061 void AutoexecObject::Install(const std::string &in) {
00062         if(GCC_UNLIKELY(installed)) E_Exit("autoexec: already created %s",buf.c_str());
00063         installed = true;
00064         buf = in;
00065         autoexec_strings.push_back(buf);
00066         this->CreateAutoexec();
00067 
00068         //autoexec.bat is normally created AUTOEXEC_Init.
00069         //But if we are already running (first_shell)
00070         //we have to update the envirionment to display changes
00071 
00072         if(first_shell) {
00073                 //create a copy as the string will be modified
00074                 std::string::size_type n = buf.size();
00075                 char* buf2 = new char[n + 1];
00076                 safe_strncpy(buf2, buf.c_str(), n + 1);
00077                 if((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
00078                         char* after_set = buf2 + 4;//move to variable that is being set
00079                         char* test = strpbrk(after_set,"=");
00080                         if(!test) {first_shell->SetEnv(after_set,"");return;}
00081                         *test++ = 0;
00082                         //If the shell is running/exists update the environment
00083                         first_shell->SetEnv(after_set,test);
00084                 }
00085                 delete [] buf2;
00086         }
00087 }
00088 
00089 void AutoexecObject::InstallBefore(const std::string &in) {
00090         if(GCC_UNLIKELY(installed)) E_Exit("autoexec: already created %s",buf.c_str());
00091         installed = true;
00092         buf = in;
00093         autoexec_strings.push_front(buf);
00094         this->CreateAutoexec();
00095 }
00096 
00097 void AutoexecObject::CreateAutoexec(void) {
00098         /* Remove old autoexec.bat if the shell exists */
00099         if(first_shell) VFILE_Remove("AUTOEXEC.BAT");
00100 
00101         //Create a new autoexec.bat
00102         autoexec_data[0] = 0;
00103         size_t auto_len;
00104         for(auto_it it=  autoexec_strings.begin(); it != autoexec_strings.end(); it++) {
00105                 auto_len = strlen(autoexec_data);
00106                 if ((auto_len+(*it).length()+3)>AUTOEXEC_SIZE) {
00107                         E_Exit("SYSTEM:Autoexec.bat file overflow");
00108                 }
00109                 sprintf((autoexec_data+auto_len),"%s\r\n",(*it).c_str());
00110         }
00111         if(first_shell) VFILE_Register("AUTOEXEC.BAT",(Bit8u *)autoexec_data,(Bit32u)strlen(autoexec_data));
00112 }
00113 
00114 void AutoexecObject::Uninstall() {
00115         if(!installed) return;
00116 
00117         // Remove the line from the autoexecbuffer and update environment
00118         for(auto_it it = autoexec_strings.begin(); it != autoexec_strings.end(); ) {
00119                 if((*it) == buf) {
00120                         it = autoexec_strings.erase(it);
00121                         std::string::size_type n = buf.size();
00122                         char* buf2 = new char[n + 1];
00123                         safe_strncpy(buf2, buf.c_str(), n + 1);
00124                         // If it's a environment variable remove it from there as well
00125                         if((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
00126                                 char* after_set = buf2 + 4;//move to variable that is being set
00127                                 char* test = strpbrk(after_set,"=");
00128                                 if(!test) continue;
00129                                 *test = 0;
00130                                 //If the shell is running/exists update the environment
00131                                 if(first_shell) first_shell->SetEnv(after_set,"");
00132                         }
00133                         delete [] buf2;
00134                 } else it++;
00135         }
00136         installed=false;
00137         this->CreateAutoexec();
00138 }
00139 
00140 AutoexecObject::~AutoexecObject(){
00141         Uninstall();
00142 }
00143 
00144 DOS_Shell::~DOS_Shell() {
00145         if (bf != NULL) delete bf; /* free batch file */
00146 }
00147 
00148 DOS_Shell::DOS_Shell():Program(){
00149         input_handle=STDIN;
00150         echo=true;
00151         exit=false;
00152         bf=0;
00153         call=false;
00154     input_eof=false;
00155 }
00156 
00157 Bitu DOS_Shell::GetRedirection(char *s, char **ifn, char **ofn,bool * append) {
00158 
00159         char * lr=s;
00160         char * lw=s;
00161         char ch;
00162         Bitu num=0;
00163         bool quote = false;
00164         char* t;
00165 
00166         while ( (ch=*lr++) ) {
00167                 if(quote && ch != '"') { /* don't parse redirection within quotes. Not perfect yet. Escaped quotes will mess the count up */
00168                         *lw++ = ch;
00169                         continue;
00170                 }
00171 
00172                 switch (ch) {
00173                 case '"':
00174                         quote = !quote;
00175                         break;
00176                 case '>':
00177                         *append=((*lr)=='>');
00178                         if (*append) lr++;
00179                         lr=ltrim(lr);
00180                         if (*ofn) free(*ofn);
00181                         *ofn=lr;
00182                         while (*lr && *lr!=' ' && *lr!='<' && *lr!='|') lr++;
00183                         //if it ends on a : => remove it.
00184                         if((*ofn != lr) && (lr[-1] == ':')) lr[-1] = 0;
00185 //                      if(*lr && *(lr+1)) 
00186 //                              *lr++=0; 
00187 //                      else 
00188 //                              *lr=0;
00189                         t = (char*)malloc(lr-*ofn+1); // FIXME: *ofn is signed char, so if extended ASCII, could cause an error here!
00190                         safe_strncpy(t,*ofn,lr-*ofn+1); // FIXME: *ofn is signed char, so if extended ASCII, could cause an error here!
00191                         *ofn=t;
00192                         continue;
00193                 case '<':
00194                         if (*ifn) free(*ifn);
00195                         lr=ltrim(lr);
00196                         *ifn=lr;
00197                         while (*lr && *lr!=' ' && *lr!='>' && *lr != '|') lr++;
00198                         if((*ifn != lr) && (lr[-1] == ':')) lr[-1] = 0;
00199 //                      if(*lr && *(lr+1)) 
00200 //                              *lr++=0; 
00201 //                      else 
00202 //                              *lr=0;
00203                         t = (char*)malloc(lr-*ifn+1); // FIXME: *ofn is signed char, so if extended ASCII, could cause an error here!
00204                         safe_strncpy(t,*ifn,lr-*ifn+1); // FIXME: *ofn is signed char, so if extended ASCII, could cause an error here!
00205                         *ifn=t;
00206                         continue;
00207                 case '|':
00208                         ch=0;
00209                         num++;
00210                 }
00211                 *lw++=ch;
00212         }
00213         *lw=0;
00214         return num;
00215 }       
00216 
00217 void DOS_Shell::ParseLine(char * line) {
00218         LOG(LOG_EXEC,LOG_DEBUG)("Parsing command line: %s",line);
00219         /* Check for a leading @ */
00220         if (line[0] == '@') line[0] = ' ';
00221         line = trim(line);
00222 
00223         /* Do redirection and pipe checks */
00224         
00225         char * in  = 0;
00226         char * out = 0;
00227 
00228         Bit16u dummy,dummy2;
00229         Bit32u bigdummy = 0;
00230         Bitu num = 0;           /* Number of commands in this line */
00231         bool append;
00232         bool normalstdin  = false;      /* wether stdin/out are open on start. */
00233         bool normalstdout = false;      /* Bug: Assumed is they are "con"      */
00234         
00235         num = GetRedirection(line,&in, &out,&append);
00236         if (num>1) LOG_MSG("SHELL:Multiple command on 1 line not supported");
00237         if (in || out) {
00238                 normalstdin  = (psp->GetFileHandle(0) != 0xff); 
00239                 normalstdout = (psp->GetFileHandle(1) != 0xff); 
00240         }
00241         if (in) {
00242                 if(DOS_OpenFile(in,OPEN_READ,&dummy)) { //Test if file exists
00243                         DOS_CloseFile(dummy);
00244                         LOG_MSG("SHELL:Redirect input from %s",in);
00245                         if(normalstdin) DOS_CloseFile(0);       //Close stdin
00246                         DOS_OpenFile(in,OPEN_READ,&dummy);      //Open new stdin
00247                 }
00248         }
00249         if (out){
00250                 LOG_MSG("SHELL:Redirect output to %s",out);
00251                 if(normalstdout) DOS_CloseFile(1);
00252                 if(!normalstdin && !in) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00253                 bool status = true;
00254                 /* Create if not exist. Open if exist. Both in read/write mode */
00255                 if(append) {
00256                         if( (status = DOS_OpenFile(out,OPEN_READWRITE,&dummy)) ) {
00257                                  DOS_SeekFile(1,&bigdummy,DOS_SEEK_END);
00258                         } else {
00259                                 status = DOS_CreateFile(out,DOS_ATTR_ARCHIVE,&dummy);   //Create if not exists.
00260                         }
00261                 } else {
00262                         status = DOS_OpenFileExtended(out,OPEN_READWRITE,DOS_ATTR_ARCHIVE,0x12,&dummy,&dummy2);
00263                 }
00264                 
00265                 if(!status && normalstdout) DOS_OpenFile("con",OPEN_READWRITE,&dummy); //Read only file, open con again
00266                 if(!normalstdin && !in) DOS_CloseFile(0);
00267         }
00268         /* Run the actual command */
00269 
00270         if (this == first_shell) dos_shell_running_program = true;
00271 #if defined(WIN32) && !defined(C_SDL2)
00272         int Reflect_Menu(void);
00273         Reflect_Menu();
00274 #endif
00275 
00276         DoCommand(line);
00277 
00278         if (this == first_shell) dos_shell_running_program = false;
00279 #if defined(WIN32) && !defined(C_SDL2)
00280         int Reflect_Menu(void);
00281         Reflect_Menu();
00282 #endif
00283 
00284         /* Restore handles */
00285         if(in) {
00286                 DOS_CloseFile(0);
00287                 if(normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00288                 free(in);
00289         }
00290         if(out) {
00291                 DOS_CloseFile(1);
00292                 if(!normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00293                 if(normalstdout) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00294                 if(!normalstdin) DOS_CloseFile(0);
00295                 free(out);
00296         }
00297 }
00298 
00299 
00300 
00301 void DOS_Shell::RunInternal(void) {
00302         char input_line[CMD_MAXLINE] = {0};
00303         while (bf) {
00304                 if (bf->ReadLine(input_line)) {
00305                         if (echo) {
00306                                 if (input_line[0] != '@') {
00307                                         ShowPrompt();
00308                                         WriteOut_NoParsing(input_line);
00309                                         WriteOut_NoParsing("\n");
00310                                 }
00311                         }
00312                         ParseLine(input_line);
00313                         if (echo) WriteOut_NoParsing("\n");
00314                 }
00315         }
00316 }
00317 
00318 void DOS_Shell::Run(void) {
00319         char input_line[CMD_MAXLINE] = {0};
00320         std::string line;
00321         if (cmd->FindStringRemainBegin("/C",line)) {
00322                 strcpy(input_line,line.c_str());
00323                 char* sep = strpbrk(input_line,"\r\n"); //GTA installer
00324                 if (sep) *sep = 0;
00325                 DOS_Shell temp;
00326                 temp.echo = echo;
00327                 temp.ParseLine(input_line);             //for *.exe *.com  |*.bat creates the bf needed by runinternal;
00328                 temp.RunInternal();                             // exits when no bf is found.
00329                 return;
00330         }
00331 
00332     if (this == first_shell) {
00333         /* Start a normal shell and check for a first command init */
00334         WriteOut(MSG_Get("SHELL_STARTUP_BEGIN"),VERSION,UPDATED_STR);
00335 #if C_DEBUG
00336         WriteOut(MSG_Get("SHELL_STARTUP_DEBUG"));
00337 #endif
00338         if (machine == MCH_CGA || machine == MCH_AMSTRAD) WriteOut(MSG_Get("SHELL_STARTUP_CGA"));
00339         if (machine == MCH_PC98) WriteOut(MSG_Get("SHELL_STARTUP_PC98"));
00340         if (machine == MCH_HERC || machine == MCH_MDA) WriteOut(MSG_Get("SHELL_STARTUP_HERC"));
00341         WriteOut(MSG_Get("SHELL_STARTUP_END"));
00342     }
00343     else {
00344         WriteOut("DOSBox command shell %s %s\n\n",VERSION,UPDATED_STR);
00345     }
00346 
00347         if (cmd->FindString("/INIT",line,true)) {
00348                 strcpy(input_line,line.c_str());
00349                 line.erase();
00350                 ParseLine(input_line);
00351         }
00352         do {
00353                 /* Get command once a line */
00354                 if (bf) {
00355                         if (bf->ReadLine(input_line)) {
00356                                 if (echo) {
00357                                         if (input_line[0]!='@') {
00358                                                 ShowPrompt();
00359                                                 WriteOut_NoParsing(input_line);
00360                                                 WriteOut_NoParsing("\n");
00361                                         };
00362                                 };
00363                         } else input_line[0]='\0';
00364                 } else {
00365                         if (echo) ShowPrompt();
00366                         InputCommand(input_line);
00367                         if (echo && !input_eof) WriteOut("\n");
00368 
00369             /* Bugfix: CTTY NUL will return immediately, the shell input will return
00370              *         immediately, and if we don't consume CPU cycles to compensate,
00371              *         will leave DOSBox-X running in an endless loop, hung. */
00372             if (input_eof) CALLBACK_Idle();
00373                 }
00374 
00375                 /* do it */
00376                 if(strlen(input_line)!=0) {
00377                         ParseLine(input_line);
00378                         if (echo && !bf) WriteOut_NoParsing("\n");
00379                 }
00380         } while (!exit);
00381 }
00382 
00383 void DOS_Shell::SyntaxError(void) {
00384         WriteOut(MSG_Get("SHELL_SYNTAXERROR"));
00385 }
00386 
00387 class AUTOEXEC:public Module_base {
00388 private:
00389         AutoexecObject autoexec[17];
00390         AutoexecObject autoexec_echo;
00391     AutoexecObject autoexec_auto_bat;
00392 public:
00393         AUTOEXEC(Section* configuration):Module_base(configuration) {
00394                 /* Register a virtual AUOEXEC.BAT file */
00395                 std::string line;
00396                 Section_line * section=static_cast<Section_line *>(configuration);
00397 
00398                 /* Check -securemode switch to disable mount/imgmount/boot after running autoexec.bat */
00399                 bool secure = control->opt_securemode;
00400 
00401         /* The user may have given .BAT files to run on the command line */
00402         if (!control->auto_bat_additional.empty()) {
00403             std::string cmd;
00404 
00405             for (unsigned int i=0;i<control->auto_bat_additional.size();i++) {
00406                                 std::string batname;
00407                                 /* NTS: this code might have problems with DBCS filenames - yksoft1 */
00408                                 size_t pos = control->auto_bat_additional[i].find_last_of(CROSS_FILESPLIT);
00409                                 if(pos == std::string::npos) {  //Only a filename, mount current directory
00410                                         batname = control->auto_bat_additional[i];
00411                                         cmd += "@mount c: . -q\n";
00412                                 } else { //Parse the path of .BAT file
00413                                         std::string batpath = control->auto_bat_additional[i].substr(0,pos+1);
00414                                         batname = control->auto_bat_additional[i].substr(pos+1);
00415                                         cmd += "@mount c: " + batpath + " -q\n";
00416                                 }
00417                                 cmd += "@c:\n";
00418                                 cmd += "@cd \\\n";
00419                 /* NTS: "CALL" does not support quoting the filename.
00420                  *      This will break if the batch file name has spaces in it. */
00421                 cmd += "@CALL ";
00422                 cmd += batname;
00423                 cmd += "\n";
00424                                 cmd += "@mount -u c: -q\n";
00425             }
00426 
00427             autoexec_auto_bat.Install(cmd);
00428         }
00429 
00430                 /* add stuff from the configfile unless -noautexec or -securemode is specified. */
00431                 char * extra = const_cast<char*>(section->data.c_str());
00432                 if (extra && !secure && !control->opt_noautoexec) {
00433                         /* detect if "echo off" is the first line */
00434                         bool echo_off  = !strncasecmp(extra,"echo off",8);
00435                         if (!echo_off) echo_off = !strncasecmp(extra,"@echo off",9);
00436 
00437                         /* if "echo off" add it to the front of autoexec.bat */
00438                         if(echo_off) autoexec_echo.InstallBefore("@echo off");
00439 
00440                         /* Install the stuff from the configfile */
00441                         autoexec[0].Install(section->data);
00442                 }
00443 
00444                 /* Check to see for extra command line options to be added (before the command specified on commandline) */
00445                 /* Maximum of extra commands: 10 */
00446                 Bitu i = 1;
00447                 for (auto it=control->opt_c.begin();i <= 11 && it!=control->opt_c.end();it++) /* -c switches */
00448                         autoexec[i++].Install(*it);
00449 
00450                 /* Check for the -exit switch which causes dosbox to when the command on the commandline has finished */
00451                 bool addexit = control->opt_exit;
00452 
00453 #if 0/*FIXME: This is ugly. I don't care to follow through on this nonsense for now. When needed, port to new command line switching. */
00454                 /* Check for first command being a directory or file */
00455                 char buffer[CROSS_LEN];
00456                 char orig[CROSS_LEN];
00457                 char cross_filesplit[2] = {CROSS_FILESPLIT , 0};
00458                 /* Combining -securemode and no parameter leaves you with a lovely Z:\. */ 
00459                 if ( !control->cmdline->FindCommand(1,line) ) { 
00460                         if ( secure ) autoexec[12].Install("z:\\config.com -securemode");
00461                 } else {
00462                         if (line.find(':',((line[0]|0x20) >= 'a' && (line[0]|0x20) <= 'z')?2:0) != std::string::npos) {
00463                                 /* a physfs source */
00464                                 autoexec[12].Install(std::string("MOUNT C \"") + line + std::string("\""));
00465                                 autoexec[13].Install("C:");
00466                                 if(secure) autoexec[14].Install("z:\\config.com -securemode");
00467                                 goto nomount;
00468                         }
00469 
00470                         struct stat test;
00471                         strcpy(buffer,line.c_str());
00472                         if (stat(buffer,&test)){
00473                                 getcwd(buffer,CROSS_LEN);
00474                                 strcat(buffer,cross_filesplit);
00475                                 strcat(buffer,line.c_str());
00476                                 if (stat(buffer,&test)) goto nomount;
00477                         }
00478                         if (test.st_mode & S_IFDIR) { 
00479                                 autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
00480                                 autoexec[13].Install("C:");
00481                                 if(secure) autoexec[14].Install("z:\\config.com -securemode");
00482                         } else {
00483                                 char* name = strrchr(buffer,CROSS_FILESPLIT);
00484                                 if (!name) { //Only a filename 
00485                                         line = buffer;
00486                                         getcwd(buffer,CROSS_LEN);
00487                                         strcat(buffer,cross_filesplit);
00488                                         strcat(buffer,line.c_str());
00489                                         if(stat(buffer,&test)) goto nomount;
00490                                         name = strrchr(buffer,CROSS_FILESPLIT);
00491                                         if(!name) goto nomount;
00492                                 }
00493                                 *name++ = 0;
00494                                 if (access(buffer,F_OK)) goto nomount;
00495                                 upcase(name);
00496                                 autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
00497                                 autoexec[13].Install("C:");
00498                                 /* Save the non-modified filename (so boot and imgmount can use it (long filenames, case sensivitive)) */
00499                                 strcpy(orig,name);
00500                                 if(strstr(name,".BAT") != 0) {
00501                                         if(secure) autoexec[14].Install("z:\\config.com -securemode");
00502                                         /* BATch files are called else exit will not work */
00503                                         autoexec[15].Install(std::string("CALL ") + name);
00504                                         if(addexit) autoexec[16].Install("exit");
00505                                 } else if((strstr(name,".IMG") != 0) || (strstr(name,".IMA") !=0 )) {
00506                                         //No secure mode here as boot is destructive and enabling securemode disables boot
00507                                         /* Boot image files */
00508                                         autoexec[15].Install(std::string("BOOT ") + orig);
00509                                 } else if((strstr(name,".ISO") != 0) || (strstr(name,".CUE") !=0 )) {
00510                                         /* imgmount CD image files */
00511                                         /* securemode gets a different number from the previous branches! */
00512                                         autoexec[14].Install(std::string("IMGMOUNT D \"") + orig + std::string("\" -t iso"));
00513                                         //autoexec[16].Install("D:");
00514                                         if(secure) autoexec[15].Install("z:\\config.com -securemode");
00515                                         /* Makes no sense to exit here */
00516                                 } else {
00517                                         if(secure) autoexec[14].Install("z:\\config.com -securemode");
00518                                         autoexec[15].Install(name);
00519                                         if(addexit) autoexec[16].Install("exit");
00520                                 }
00521                         }
00522                 }
00523 nomount:
00524 #endif
00525 
00526                 if (addexit) autoexec[i++].Install("exit");
00527 
00528                 assert(i <= 17); /* FIXME: autoexec[] should not be fixed size */
00529 
00530                 VFILE_Register("AUTOEXEC.BAT",(Bit8u *)autoexec_data,(Bit32u)strlen(autoexec_data));
00531         }
00532 };
00533 
00534 static AUTOEXEC* test = NULL;
00535         
00536 static void AUTOEXEC_ShutDown(Section * sec) {
00537     (void)sec;//UNUSED
00538         if (test != NULL) {
00539                 delete test;
00540                 test = NULL;
00541         }
00542     if (first_shell != NULL) {
00543                 delete first_shell;
00544                 first_shell = 0;//Make clear that it shouldn't be used anymore
00545     }
00546     if (call_shellstop != 0) {
00547         CALLBACK_DeAllocate(call_shellstop);
00548         call_shellstop = 0;
00549     }
00550 }
00551 
00552 void AUTOEXEC_Startup(Section *sec) {
00553     (void)sec;//UNUSED
00554         if (test == NULL) {
00555                 LOG(LOG_MISC,LOG_DEBUG)("Allocating AUTOEXEC.BAT emulation");
00556                 test = new AUTOEXEC(control->GetSection("autoexec"));
00557         }
00558 }
00559 
00560 void AUTOEXEC_Init() {
00561         LOG(LOG_MISC,LOG_DEBUG)("Initializing AUTOEXEC.BAT emulation");
00562 
00563         AddExitFunction(AddExitFunctionFuncPair(AUTOEXEC_ShutDown));
00564         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00565         AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00566         AddVMEventFunction(VM_EVENT_DOS_EXIT_REBOOT_BEGIN,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00567         AddVMEventFunction(VM_EVENT_DOS_SURPRISE_REBOOT,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00568 }
00569 
00570 static Bitu INT2E_Handler(void) {
00571         /* Save return address and current process */
00572         RealPt save_ret=real_readd(SegValue(ss),reg_sp);
00573         Bit16u save_psp=dos.psp();
00574 
00575         /* Set first shell as process and copy command */
00576         dos.psp(shell_psp);//DOS_FIRST_SHELL);
00577         DOS_PSP psp(shell_psp);//DOS_FIRST_SHELL);
00578         psp.SetCommandTail(RealMakeSeg(ds,reg_si));
00579         SegSet16(ss,RealSeg(psp.GetStack()));
00580         reg_sp=2046;
00581 
00582         /* Read and fix up command string */
00583         CommandTail tail;
00584         MEM_BlockRead(PhysMake(dos.psp(),128),&tail,128);
00585         if (tail.count<127) tail.buffer[tail.count]=0;
00586         else tail.buffer[126]=0;
00587         char* crlf=strpbrk(tail.buffer,"\r\n");
00588         if (crlf) *crlf=0;
00589 
00590         /* Execute command */
00591         if (strlen(tail.buffer)) {
00592                 DOS_Shell temp;
00593                 temp.ParseLine(tail.buffer);
00594                 temp.RunInternal();
00595         }
00596 
00597         /* Restore process and "return" to caller */
00598         dos.psp(save_psp);
00599         SegSet16(cs,RealSeg(save_ret));
00600         reg_ip=RealOff(save_ret);
00601         reg_ax=0;
00602         return CBRET_NONE;
00603 }
00604 
00605 static char const * const path_string="PATH=Z:\\";
00606 static char const * const comspec_string="COMSPEC=Z:\\COMMAND.COM";
00607 static char const * const prompt_string="PROMPT=$P$G";
00608 static char const * const full_name="Z:\\COMMAND.COM";
00609 static char const * const init_line="/INIT AUTOEXEC.BAT";
00610 
00611 extern unsigned int dosbox_shell_env_size;
00612 
00613 /* TODO: Why is all this DOS kernel and VFILE registration here in SHELL_Init()?
00614  *       That's like claiming that DOS memory and device initialization happens from COMMAND.COM!
00615  *       We need to move the DOS kernel initialization into another function, and the VFILE
00616  *       registration to another function, and then message initialization to another function,
00617  *       and then those functions need to be called before SHELL_Init() -J.C. */
00618 void SHELL_Init() {
00619         LOG(LOG_MISC,LOG_DEBUG)("Initializing DOS shell");
00620 
00621         /* Add messages */
00622         MSG_Add("SHELL_CMD_VOL_DRIVE","\n Volume in drive %c ");
00623         MSG_Add("SHELL_CMD_VOL_DRIVEERROR","Cannot find the drive specified\n");
00624         MSG_Add("SHELL_CMD_VOL_SERIAL"," Volume Serial Number is ");
00625         MSG_Add("SHELL_CMD_VOL_SERIAL_NOLABEL","has no label\n");
00626         MSG_Add("SHELL_CMD_VOL_SERIAL_LABEL","is %s\n");
00627         MSG_Add("SHELL_ILLEGAL_PATH","Illegal Path.\n");
00628         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");
00629         MSG_Add("SHELL_CMD_ECHO_ON","ECHO is on.\n");
00630         MSG_Add("SHELL_CMD_ECHO_OFF","ECHO is off.\n");
00631         MSG_Add("SHELL_ILLEGAL_SWITCH","Illegal switch: %s.\n");
00632         MSG_Add("SHELL_MISSING_PARAMETER","Required parameter missing.\n");
00633         MSG_Add("SHELL_CMD_CHDIR_ERROR","Unable to change to: %s.\n");
00634         MSG_Add("SHELL_CMD_CHDIR_HINT","Hint: To change to different drive type \033[31m%c:\033[0m\n");
00635         MSG_Add("SHELL_CMD_CHDIR_HINT_2","directoryname is longer than 8 characters and/or contains spaces.\nTry \033[31mcd %s\033[0m\n");
00636         MSG_Add("SHELL_CMD_CHDIR_HINT_3","You are still on drive Z:, change to a mounted drive with \033[31mC:\033[0m.\n");
00637         MSG_Add("SHELL_CMD_DATE_HELP","Displays or changes the internal date.\n");
00638         MSG_Add("SHELL_CMD_DATE_ERROR","The specified date is not correct.\n");
00639         MSG_Add("SHELL_CMD_DATE_DAYS","3SunMonTueWedThuFriSat"); // "2SoMoDiMiDoFrSa"
00640         MSG_Add("SHELL_CMD_DATE_NOW","Current date: ");
00641         MSG_Add("SHELL_CMD_DATE_SETHLP","Type 'date MM-DD-YYYY' to change.\n");
00642         MSG_Add("SHELL_CMD_DATE_FORMAT","M/D/Y");
00643         MSG_Add("SHELL_CMD_DATE_HELP_LONG","DATE [[/T] [/H] [/S] | MM-DD-YYYY]\n"\
00644                                                                         "  MM-DD-YYYY: new date to set\n"\
00645                                                                         "  /S:         Permanently use host time and date as DOS time\n"\
00646                                     "  /F:         Switch back to DOSBox internal time (opposite of /S)\n"\
00647                                                                         "  /T:         Only display date\n"\
00648                                                                         "  /H:         Synchronize with host\n");
00649         MSG_Add("SHELL_CMD_TIME_HELP","Displays the internal time.\n");
00650         MSG_Add("SHELL_CMD_TIME_NOW","Current time: ");
00651         MSG_Add("SHELL_CMD_TIME_HELP_LONG","TIME [/T] [/H]\n"\
00652                                                                         "  /T:         Display simple time\n"\
00653                                                                         "  /H:         Synchronize with host\n");
00654         MSG_Add("SHELL_CMD_MKDIR_ERROR","Unable to make: %s.\n");
00655         MSG_Add("SHELL_CMD_RMDIR_ERROR","Unable to remove: %s.\n");
00656         MSG_Add("SHELL_CMD_DEL_ERROR","Unable to delete: %s.\n");
00657         MSG_Add("SHELL_CMD_DEL_SURE","Are you sure[Y,N]?");
00658         MSG_Add("SHELL_SYNTAXERROR","The syntax of the command is incorrect.\n");
00659         MSG_Add("SHELL_CMD_SET_NOT_SET","Environment variable %s not defined.\n");
00660         MSG_Add("SHELL_CMD_SET_OUT_OF_SPACE","Not enough environment space left.\n");
00661         MSG_Add("SHELL_CMD_IF_EXIST_MISSING_FILENAME","IF EXIST: Missing filename.\n");
00662         MSG_Add("SHELL_CMD_IF_ERRORLEVEL_MISSING_NUMBER","IF ERRORLEVEL: Missing number.\n");
00663         MSG_Add("SHELL_CMD_IF_ERRORLEVEL_INVALID_NUMBER","IF ERRORLEVEL: Invalid number.\n");
00664         MSG_Add("SHELL_CMD_GOTO_MISSING_LABEL","No label supplied to GOTO command.\n");
00665         MSG_Add("SHELL_CMD_GOTO_LABEL_NOT_FOUND","GOTO: Label %s not found.\n");
00666         MSG_Add("SHELL_CMD_FILE_NOT_FOUND","File %s not found.\n");
00667         MSG_Add("SHELL_CMD_FILE_EXISTS","File %s already exists.\n");
00668         MSG_Add("SHELL_CMD_DIR_INTRO"," Directory of %s.\n\n");
00669         MSG_Add("SHELL_CMD_DIR_BYTES_USED","%5d File(s) %17s Bytes.\n");
00670         MSG_Add("SHELL_CMD_DIR_BYTES_FREE","%5d Dir(s)  %17s Bytes free.\n");
00671         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");
00672         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_CDROM","Do you want to give DOSBox access to your real CD-ROM drive %c [Y/N]?");
00673         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY","Do you want to give DOSBox access to your real floppy drive %c [Y/N]?");
00674         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_FIXED","Do you really want to give DOSBox access to everything\non your real drive %c [Y/N]?");
00675         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_WARNING_WIN","Mounting c:\\ is NOT recommended.\n");
00676         MSG_Add("SHELL_EXECUTE_ILLEGAL_COMMAND","Illegal command: %s.\n");
00677         MSG_Add("SHELL_CMD_PAUSE","Press any key to continue.\n");
00678         MSG_Add("SHELL_CMD_PAUSE_HELP","Waits for 1 keystroke to continue.\n");
00679         MSG_Add("SHELL_CMD_COPY_FAILURE","Copy failure : %s.\n");
00680         MSG_Add("SHELL_CMD_COPY_SUCCESS","   %d File(s) copied.\n");
00681         MSG_Add("SHELL_CMD_SUBST_NO_REMOVE","Unable to remove, drive not in use.\n");
00682         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\n");
00683 
00684     if (machine == MCH_PC98) {
00685         MSG_Add("SHELL_STARTUP_BEGIN",
00686                 "\x86\x52\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00687                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00688                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00689                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00690                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00691                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x56\n"
00692                 "\x86\x46 \033[32mWelcome to DOSBox-X %-8s %-25s\033[37m             \x86\x46\n"
00693                 "\x86\x46                                                                    \x86\x46\n"
00694                 "\x86\x46 For a short introduction for new users type: \033[33mINTRO\033[37m                 \x86\x46\n"
00695                 "\x86\x46 For supported shell commands type: \033[33mHELP\033[37m                            \x86\x46\n"
00696                 "\x86\x46                                                                    \x86\x46\n"
00697                 "\x86\x46 To adjust the emulated CPU speed, use \033[31mhost -\033[37m and \033[31mhost +\033[37m.           \x86\x46\n"
00698                 "\x86\x46 To activate the keymapper \033[31mhost+m\033[37m. Host key is F11.                 \x86\x46\n"
00699                 "\x86\x46 For more information read the \033[36mREADME\033[37m file in the DOSBox directory. \x86\x46\n"
00700                 "\x86\x46                                                                    \x86\x46\n"
00701                );
00702         MSG_Add("SHELL_STARTUP_PC98","\x86\x46 DOSBox-X is now running in NEC PC-98 emulation mode.               \x86\x46\n"
00703                 "\x86\x46 \033[31mPC-98 emulation is INCOMPLETE and CURRENTLY IN DEVELOPMENT.\033[37m        \x86\x46\n");
00704         MSG_Add("SHELL_STARTUP_DEBUG",
00705 #if defined(MACOSX)
00706                 "\x86\x46 Debugger is available, use \033[31malt-F12\033[37m to enter.                       \x86\x46\n"
00707 #else
00708                 "\x86\x46 Debugger is available, use \033[31malt-Pause\033[37m to enter.                     \x86\x46\n"
00709 #endif
00710                 "\x86\x46                                                                    \x86\x46\n"
00711                );
00712         MSG_Add("SHELL_STARTUP_END",
00713                 "\x86\x46 \033[32mDOSBox-X project \033[33mhttp://dosbox-x.software\033[32m      PentiumPro support \033[37m \x86\x46\n"
00714                 "\x86\x46 \033[32mDerived from DOSBox \033[33mhttp://www.dosbox.com\033[37m                          \x86\x46\n"
00715                 "\x86\x5A\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00716                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00717                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00718                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00719                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00720                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x5E\033[0m\n"
00721                 "\033[1m\033[32mHAVE FUN!\033[0m\n"
00722                );
00723     }
00724     else {
00725         MSG_Add("SHELL_STARTUP_BEGIN",
00726                 "\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"
00727                 "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
00728                 "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB\n"
00729                 "\xBA \033[32mWelcome to DOSBox-X %-8s %-25s\033[37m             \xBA\n"
00730                 "\xBA                                                                    \xBA\n"
00731                 "\xBA For a short introduction for new users type: \033[33mINTRO\033[37m                 \xBA\n"
00732                 "\xBA For supported shell commands type: \033[33mHELP\033[37m                            \xBA\n"
00733                 "\xBA                                                                    \xBA\n"
00734                 "\xBA To adjust the emulated CPU speed, use \033[31mhost -\033[37m and \033[31mhost +\033[37m.           \xBA\n"
00735                 "\xBA To activate the keymapper \033[31mhost+m\033[37m. Host key is F11.                 \xBA\n"
00736                 "\xBA For more information read the \033[36mREADME\033[37m file in the DOSBox directory. \xBA\n"
00737                 "\xBA                                                                    \xBA\n"
00738                );
00739         if (!mono_cga) {
00740             MSG_Add("SHELL_STARTUP_CGA","\xBA DOSBox supports Composite CGA mode.                                \xBA\n"
00741                     "\xBA Use \033[31mF12\033[37m to set composite output ON, OFF, or AUTO (default).        \xBA\n"
00742                     "\xBA \033[31m(Alt-)F11\033[37m changes hue; \033[31mctrl-alt-F11\033[37m selects early/late CGA model.  \xBA\n"
00743                     "\xBA                                                                    \xBA\n"
00744                    );
00745         } else {
00746             MSG_Add("SHELL_STARTUP_CGA","\xBA Use \033[31mF11\033[37m to cycle through green, amber, and white monochrome color, \xBA\n"
00747                     "\xBA and \033[31mAlt-F11\033[37m to change contrast/brightness settings.                \xBA\n"
00748                    );
00749         }
00750         MSG_Add("SHELL_STARTUP_PC98","\xBA DOSBox-X is now running in NEC PC-98 emulation mode.               \xBA\n"
00751                 "\xBA \033[31mPC-98 emulation is INCOMPLETE and CURRENTLY IN DEVELOPMENT.\033[37m        \xBA\n");
00752         MSG_Add("SHELL_STARTUP_HERC","\xBA Use F11 to cycle through white, amber, and green monochrome color. \xBA\n"
00753                 "\xBA Use alt-F11 to toggle horizontal blending (only in graphics mode). \xBA\n"
00754                 "\xBA                                                                    \xBA\n"
00755                );
00756         MSG_Add("SHELL_STARTUP_DEBUG",
00757 #if defined(MACOSX)
00758                 "\xBA Debugger is available, use \033[31malt-F12\033[37m to enter.                       \xBA\n"
00759 #else
00760                 "\xBA Debugger is available, use \033[31malt-Pause\033[37m to enter.                     \xBA\n"
00761 #endif
00762                 "\xBA                                                                    \xBA\n"
00763                );
00764         MSG_Add("SHELL_STARTUP_END",
00765                 "\xBA \033[32mDOSBox-X project \033[33mhttp://dosbox-x.software\033[32m      PentiumPro support \033[37m \xBA\n"
00766                 "\xBA \033[32mDerived from DOSBox \033[33mhttp://www.dosbox.com\033[37m                          \xBA\n"
00767                 "\xC8\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
00768                 "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
00769                 "\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"
00770                 "\033[1m\033[32mHAVE FUN!\033[0m\n"
00771                );
00772     }
00773 
00774         MSG_Add("SHELL_CMD_CHDIR_HELP","Displays/changes the current directory.\n");
00775         MSG_Add("SHELL_CMD_CHDIR_HELP_LONG","CHDIR [drive:][path]\n"
00776                 "CHDIR [..]\n"
00777                 "CD [drive:][path]\n"
00778                 "CD [..]\n\n"
00779                 "  ..   Specifies that you want to change to the parent directory.\n\n"
00780                 "Type CD drive: to display the current directory in the specified drive.\n"
00781                 "Type CD without parameters to display the current drive and directory.\n");
00782         MSG_Add("SHELL_CMD_CLS_HELP","Clear screen.\n");
00783         MSG_Add("SHELL_CMD_DIR_HELP","Directory View.\n");
00784         MSG_Add("SHELL_CMD_DIR_HELP_LONG","DIR [drive:][path][filename] [/[W|B]] [/S] [/P] [/AD] [/O[N|E|S|D]\n\n"
00785                    "   [drive:][path][filename]\n"
00786                    "       Specifies drive, directory, and/or files to list.\n\n"
00787                    "   /W\tUses wide list format.\n"
00788                    "   /B\tUses bare format (no heading information or summary).\n"
00789                    "   /S\tDisplays files in specified directory and all subdirectories.\n\t(not supported)\n"
00790                    "   /P\tPauses after each screenful of information.\n"
00791                    "   /AD\tDisplays directories.\n"
00792                    "   /ON\tList files sorted by name (alphabetic).\n"
00793                    "   /OE\tList files sorted by extension (alphabetic).\n"
00794                    "   /OS\tList files sorted by size (smallest first).\n"
00795                    "   /OD\tList files sorted by date (oldest first).\n");
00796         MSG_Add("SHELL_CMD_ECHO_HELP","Display messages and enable/disable command echoing.\n");
00797         MSG_Add("SHELL_CMD_EXIT_HELP","Exit from the shell.\n");
00798         MSG_Add("SHELL_CMD_HELP_HELP","Show help.\n");
00799 //      MSG_Add("SHELL_CMD_HELP_HELP_LONG","HELP [command]\n");
00800         MSG_Add("SHELL_CMD_MKDIR_HELP","Make Directory.\n");
00801         MSG_Add("SHELL_CMD_MKDIR_HELP_LONG","MKDIR [drive:][path]\n"
00802                 "MD [drive:][path]\n");
00803         MSG_Add("SHELL_CMD_RMDIR_HELP","Remove Directory.\n");
00804         MSG_Add("SHELL_CMD_RMDIR_HELP_LONG","RMDIR [drive:][path]\n"
00805                 "RD [drive:][path]\n");
00806         MSG_Add("SHELL_CMD_SET_HELP","Change environment variables.\n");
00807         MSG_Add("SHELL_CMD_SET_HELP_LONG","SET [variable=[string]]\n\n"
00808                    "   variable\tSpecifies the environment-variable name.\n"
00809                    "   string\tSpecifies a series of characters to assign to the variable.\n\n"
00810                    "* If no string is specified, the variable is removed from the environment.\n\n"
00811                    "Type SET without parameters to display the current environment variables.\n");
00812         MSG_Add("SHELL_CMD_IF_HELP","Performs conditional processing in batch programs.\n");
00813         MSG_Add("SHELL_CMD_IF_HELP_LONG","IF [NOT] ERRORLEVEL number command\n"
00814                    "IF [NOT] string1==string2 command\n"
00815                    "IF [NOT] EXIST filename command\n\n"
00816                    "  NOT               Specifies that DOS should carry out\n"
00817                    "                    the command only if the condition is false.\n\n"
00818                    "  ERRORLEVEL number Specifies a true condition if the last program run\n"
00819                    "                    returned an exit code equal to or greater than the number\n"
00820                    "                    specified.\n\n"
00821                    "  string1==string2  Specifies a true condition if the specified text strings\n"
00822                    "                    match.\n\n"
00823                    "  EXIST filename    Specifies a true condition if the specified filename\n"
00824                    "                    exists.\n\n"
00825                    "  command           Specifies the command to carry out if the condition is\n"
00826                    "                    met.  Command can be followed by ELSE command which\n"
00827                    "                    will execute the command after the ELSE keyword if the\n"
00828                    "                    specified condition is FALSE\n");
00829         MSG_Add("SHELL_CMD_GOTO_HELP","Jump to a labeled line in a batch script.\n");
00830         MSG_Add("SHELL_CMD_GOTO_HELP_LONG","GOTO label\n\n"
00831                    "   label   Specifies a text string used in the batch program as a label.\n\n"
00832                    "You type a label on a line by itself, beginning with a colon.\n");
00833         MSG_Add("SHELL_CMD_SHIFT_HELP","Leftshift commandline parameters in a batch script.\n");
00834         MSG_Add("SHELL_CMD_FOR_HELP","Does nothing. Provided for compatibility.\n");
00835         MSG_Add("SHELL_CMD_TYPE_HELP","Display the contents of a text-file.\n");
00836         MSG_Add("SHELL_CMD_TYPE_HELP_LONG","TYPE [drive:][path][filename]\n");
00837         MSG_Add("SHELL_CMD_REM_HELP","Add comments in a batch file.\n");
00838         MSG_Add("SHELL_CMD_REM_HELP_LONG","REM [comment]\n");
00839         MSG_Add("SHELL_CMD_NO_WILD","This is a simple version of the command, no wildcards allowed!\n");
00840         MSG_Add("SHELL_CMD_RENAME_HELP","Renames one or more files.\n");
00841         MSG_Add("SHELL_CMD_RENAME_HELP_LONG","RENAME [drive:][path]filename1 filename2.\n"
00842                 "REN [drive:][path]filename1 filename2.\n\n"
00843                 "Note that you can not specify a new drive or path for your destination file.\n");
00844         MSG_Add("SHELL_CMD_DELETE_HELP","Removes one or more files.\n");
00845         MSG_Add("SHELL_CMD_COPY_HELP","Copy files.\n");
00846         MSG_Add("SHELL_CMD_DELETE_HELP_LONG","DEL [/P] [/Q] names\n"
00847                    "ERASE [/P] [/Q] names\n\n"
00848                    "  names\t\tSpecifies a list of one or more files or directories.\n"
00849                    "\t\tWildcards may be used to delete multiple files. If a\n"
00850                    "\t\tdirectory is specified, all files within the directory\n"
00851                    "\t\twill be deleted.\n"
00852                    "  /P\t\tPrompts for confirmation before deleting one or more files.\n"
00853                    "  /Q\t\tQuiet mode, do not ask if ok to delete on global wildcard\n");
00854         MSG_Add("SHELL_CMD_CALL_HELP","Start a batch file from within another batch file.\n");
00855         MSG_Add("SHELL_CMD_CALL_HELP_LONG","CALL [drive:][path]filename [batch-parameters]\n\n"
00856                    "batch-parameters   Specifies any command-line information required by\n"
00857                    "                   the batch program.\n");
00858         MSG_Add("SHELL_CMD_SUBST_HELP","Assign an internal directory to a drive.\n");
00859         MSG_Add("SHELL_CMD_LOADHIGH_HELP","Loads a program into upper memory (requires xms=true,umb=true).\n");
00860         MSG_Add("SHELL_CMD_CHOICE_HELP","Waits for a keypress and sets ERRORLEVEL.\n");
00861         MSG_Add("SHELL_CMD_CHOICE_HELP_LONG","CHOICE [/C:choices] [/N] [/S] text\n"
00862                 "  /C[:]choices  -  Specifies allowable keys.  Default is: yn.\n"
00863                 "  /N  -  Do not display the choices at end of prompt.\n"
00864                 "  /S  -  Enables case-sensitive choices to be selected.\n"
00865                 "  text  -  The text to display as a prompt.\n");
00866         MSG_Add("SHELL_CMD_ATTRIB_HELP","Does nothing. Provided for compatibility.\n");
00867         MSG_Add("SHELL_CMD_PATH_HELP","Displays/Sets a search path for executable files.\n");
00868         MSG_Add("SHELL_CMD_PATH_HELP_LONG","PATH [[drive:]path[;...][;%PATH%]\n"
00869                    "PATH ;\n\n"
00870                    "Type PATH ; to clear all search path settings.\n"
00871                    "Type PATH without parameters to display the current path.\n");
00872         MSG_Add("SHELL_CMD_VER_HELP","View and set the reported DOS version.\n");
00873         MSG_Add("SHELL_CMD_VER_HELP_LONG","VER\n" 
00874                    "VER SET [major minor]\n\n" 
00875                    "  major minor   Set the reported DOS version. (e.g. VER SET 5 1)\n\n" 
00876                    "Type VER without parameters to display the current DOS version.\n");
00877         MSG_Add("SHELL_CMD_VER_VER","DOSBox version %s. Reported DOS version %d.%02d.\n");
00878         MSG_Add("SHELL_CMD_ADDKEY_HELP","Generates artificial keypresses.\n");
00879         MSG_Add("SHELL_CMD_VOL_HELP","Displays the disk volume label and serial number, if they exist.\n");
00880         MSG_Add("SHELL_CMD_VOL_HELP_LONG","VOL [drive]\n");
00881         MSG_Add("SHELL_CMD_PROMPT_HELP","Change the command prompt.\n");
00882         MSG_Add("SHELL_CMD_PROMPT_HELP_LONG","PROMPT [text]\n"
00883                    "  text    Specifies a new command prompt.\n\n"
00884                    "Prompt can be made up of normal characters and the following special codes:\n"
00885                    "  $A   & (Ampersand)\n"
00886                    "  $B   | (pipe)\n"
00887                    "  $C   ( (Left parenthesis)\n"
00888                    "  $D   Current date\n"
00889                    "  $E   Escape code (ASCII code 27)\n"
00890                    "  $F   ) (Right parenthesis)\n"
00891                    "  $G   > (greater-than sign)\n"
00892                    "  $H   Backspace (erases previous character)\n"
00893                    "  $L   < (less-than sign)\n"
00894                    "  $N   Current drive\n"
00895                    "  $P   Current drive and path\n"
00896                    "  $Q   = (equal sign)\n"
00897                    "  $S     (space)\n"
00898                    "  $T   Current time\n"
00899                    "  $V   DOS version number\n"
00900                    "  $_   Carriage return and linefeed\n"
00901                    "  $$   $ (dollar sign)\n");
00902         MSG_Add("SHELL_CMD_LABEL_HELP","Creates or changes the volume label of a disk.\n");
00903         MSG_Add("SHELL_CMD_LABEL_HELP_LONG","LABEL [volume]\n\n\tvolume\t\tSpecifies the drive letter.\n");
00904         MSG_Add("SHELL_CMD_MORE_HELP","Displays output one screen at a time.\n");
00905         MSG_Add("SHELL_CMD_MORE_HELP_LONG","MORE [filename]\n");
00906 
00907         /* Regular startup */
00908         call_shellstop=CALLBACK_Allocate();
00909         /* Setup the startup CS:IP to kill the last running machine when exitted */
00910         RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
00911         SegSet16(cs,RealSeg(newcsip));
00912         reg_ip=RealOff(newcsip);
00913 
00914         CALLBACK_Setup(call_shellstop,shellstop_handler,CB_IRET,"shell stop");
00915         PROGRAMS_MakeFile("COMMAND.COM",SHELL_ProgramStart);
00916 
00917     /* NTS: Some DOS programs behave badly if run from a command interpreter
00918      *      who's PSP segment is too low in memory and does not appear in
00919      *      the MCB chain (SimCity 2000). So allocate shell memory normally
00920      *      as any DOS application would do.
00921      *
00922      *      That includes allocating COMMAND.COM stack NORMALLY (not up in
00923      *      the UMB as DOSBox SVN would do) */
00924 
00925         /* Now call up the shell for the first time */
00926         Bit16u psp_seg;//=DOS_FIRST_SHELL;
00927         Bit16u env_seg;//=DOS_FIRST_SHELL+19; //DOS_GetMemory(1+(4096/16))+1;
00928         Bit16u stack_seg;//=DOS_GetMemory(2048/16,"COMMAND.COM stack");
00929     Bit16u tmp,total_sz;
00930 
00931     // decide shell env size
00932     if (dosbox_shell_env_size == 0)
00933         dosbox_shell_env_size = (0x158u - (0x118u + 19u)) << 4u; /* equivalent to mainline DOSBox */
00934     else
00935         dosbox_shell_env_size = (dosbox_shell_env_size+15u)&(~15u); /* round up to paragraph */
00936 
00937     LOG_MSG("COMMAND.COM env size:             %u bytes",dosbox_shell_env_size);
00938 
00939     // According to some sources, 0x0008 is a special PSP segment value used by DOS before the first
00940     // program is used. We need the current PSP segment to be nonzero so that DOS_AllocateMemory()
00941     // can properly allocate memory.
00942     dos.psp(8);
00943 
00944     // COMMAND.COM environment block
00945     tmp = dosbox_shell_env_size>>4;
00946         if (!DOS_AllocateMemory(&env_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate environment block segment");
00947     LOG_MSG("COMMAND.COM environment block:    0x%04x sz=0x%04x",env_seg,tmp);
00948 
00949     // COMMAND.COM main binary (including PSP and stack)
00950     tmp = 0x1A + (2048/16);
00951     total_sz = tmp;
00952         if (!DOS_AllocateMemory(&psp_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate main body + PSP segment");
00953     LOG_MSG("COMMAND.COM main body (PSP):      0x%04x sz=0x%04x",psp_seg,tmp);
00954 
00955     // now COMMAND.COM has a main body and PSP segment, reflect it
00956     dos.psp(psp_seg);
00957     shell_psp = psp_seg;
00958 
00959     {
00960         DOS_MCB mcb((Bit16u)(env_seg-1));
00961         mcb.SetPSPSeg(psp_seg);
00962         mcb.SetFileName("COMMAND");
00963     }
00964 
00965     {
00966         DOS_MCB mcb((Bit16u)(psp_seg-1));
00967         mcb.SetPSPSeg(psp_seg);
00968         mcb.SetFileName("COMMAND");
00969     }
00970 
00971     // set the stack at 0x1A
00972     stack_seg = psp_seg + 0x1A;
00973     LOG_MSG("COMMAND.COM stack:                0x%04x",stack_seg);
00974 
00975     // set the stack pointer
00976         SegSet16(ss,stack_seg);
00977         reg_sp=2046;
00978 
00979         /* Set up int 24 and psp (Telarium games) */
00980         real_writeb(psp_seg+16+1,0,0xea);               /* far jmp */
00981         real_writed(psp_seg+16+1,1,real_readd(0,0x24*4));
00982         real_writed(0,0x24*4,((Bit32u)psp_seg<<16) | ((16+1)<<4));
00983 
00984         /* Set up int 23 to "int 20" in the psp. Fixes what.exe */
00985         real_writed(0,0x23*4,((Bit32u)psp_seg<<16));
00986 
00987         /* Set up int 2e handler */
00988         Bitu call_int2e=CALLBACK_Allocate();
00989         RealPt addr_int2e=RealMake(psp_seg+16+1,8);
00990         CALLBACK_Setup(call_int2e,&INT2E_Handler,CB_IRET_STI,Real2Phys(addr_int2e),"Shell Int 2e");
00991         RealSetVec(0x2e,addr_int2e);
00992 
00993         /* Setup environment */
00994         PhysPt env_write=PhysMake(env_seg,0);
00995         MEM_BlockWrite(env_write,path_string,(Bitu)(strlen(path_string)+1));
00996         env_write += (PhysPt)(strlen(path_string)+1);
00997         MEM_BlockWrite(env_write,comspec_string,(Bitu)(strlen(comspec_string)+1));
00998         env_write += (PhysPt)(strlen(comspec_string)+1);
00999         MEM_BlockWrite(env_write,prompt_string,(Bitu)(strlen(prompt_string)+1));
01000         env_write +=(PhysPt)(strlen(prompt_string)+1);
01001         mem_writeb(env_write++,0);
01002         mem_writew(env_write,1);
01003         env_write+=2;
01004         MEM_BlockWrite(env_write,full_name,(Bitu)(strlen(full_name)+1));
01005 
01006 //    extern bool Mouse_Vertical;
01007         extern bool Mouse_Drv;
01008         Mouse_Drv = true;
01009 
01010         VFILE_RegisterBuiltinFileBlob(bfb_DEBUG_EXE);
01011         VFILE_RegisterBuiltinFileBlob(bfb_MOVE_EXE);
01012         VFILE_RegisterBuiltinFileBlob(bfb_FIND_EXE);
01013         VFILE_RegisterBuiltinFileBlob(bfb_LASTDRIV_COM);
01014         VFILE_RegisterBuiltinFileBlob(bfb_FCBS_COM);
01015         VFILE_RegisterBuiltinFileBlob(bfb_XCOPY_EXE);
01016         VFILE_RegisterBuiltinFileBlob(bfb_APPEND_EXE);
01017         VFILE_RegisterBuiltinFileBlob(bfb_DEVICE_COM);
01018         VFILE_RegisterBuiltinFileBlob(bfb_BUFFERS_COM);
01019         VFILE_RegisterBuiltinFileBlob(bfb_COPY_EXE);
01020 
01021     /* These are IBM PC/XT/AT ONLY. They will not work in PC-98 mode. */
01022     if (!IS_PC98_ARCH) {
01023         VFILE_RegisterBuiltinFileBlob(bfb_HEXMEM16_EXE);
01024         VFILE_RegisterBuiltinFileBlob(bfb_HEXMEM32_EXE);
01025         VFILE_RegisterBuiltinFileBlob(bfb_DOSIDLE_EXE);
01026         VFILE_RegisterBuiltinFileBlob(bfb_CWSDPMI_EXE);
01027         VFILE_RegisterBuiltinFileBlob(bfb_DOS32A_EXE);
01028         VFILE_RegisterBuiltinFileBlob(bfb_DOS4GW_EXE);
01029         VFILE_RegisterBuiltinFileBlob(bfb_EDIT_COM);
01030         VFILE_RegisterBuiltinFileBlob(bfb_TREE_EXE);
01031         VFILE_RegisterBuiltinFileBlob(bfb_25_COM);
01032     }
01033 
01034     /* MEM.COM is not compatible with PC-98 and/or 8086 emulation */
01035     if (!IS_PC98_ARCH && CPU_ArchitectureType >= CPU_ARCHTYPE_80186)
01036         VFILE_RegisterBuiltinFileBlob(bfb_MEM_COM);
01037 
01038     /* DSXMENU.EXE */
01039     if (IS_PC98_ARCH)
01040         VFILE_RegisterBuiltinFileBlob(bfb_DSXMENU_EXE_PC98);
01041     else
01042         VFILE_RegisterBuiltinFileBlob(bfb_DSXMENU_EXE_PC);
01043 
01044         /* don't register 28.com unless EGA/VGA */
01045         if (IS_EGAVGA_ARCH) VFILE_RegisterBuiltinFileBlob(bfb_28_COM);
01046 
01047         /* don't register 50 unless VGA */
01048         if (IS_VGA_ARCH) VFILE_RegisterBuiltinFileBlob(bfb_50_COM);
01049 
01050         DOS_PSP psp(psp_seg);
01051         psp.MakeNew(0);
01052         dos.psp(psp_seg);
01053    
01054         /* The start of the filetable in the psp must look like this:
01055          * 01 01 01 00 02
01056          * In order to achieve this: First open 2 files. Close the first and
01057          * duplicate the second (so the entries get 01) */
01058         Bit16u dummy=0;
01059         DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDIN  */
01060         DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDOUT */
01061         DOS_CloseFile(0);                                                       /* Close STDIN */
01062         DOS_ForceDuplicateEntry(1,0);                           /* "new" STDIN */
01063         DOS_ForceDuplicateEntry(1,2);                           /* STDERR */
01064         DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDAUX */
01065         if (!DOS_OpenFile("PRN",OPEN_READWRITE,&dummy)) DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDPRN */
01066 
01067     psp.SetSize(psp_seg + total_sz);
01068     psp.SetStack(((unsigned int)stack_seg << 16u) + (unsigned int)reg_sp);
01069         psp.SetParent(psp_seg);
01070         /* Set the environment */
01071         psp.SetEnvironment(env_seg);
01072         /* Set the command line for the shell start up */
01073         CommandTail tail;
01074         tail.count=(Bit8u)strlen(init_line);
01075         strcpy(tail.buffer,init_line);
01076         MEM_BlockWrite(PhysMake(psp_seg,128),&tail,128);
01077         
01078         /* Setup internal DOS Variables */
01079         dos.dta(RealMake(psp_seg,0x80));
01080         dos.psp(psp_seg);
01081 
01082     /* settings */
01083     {
01084         Section_prop * section=static_cast<Section_prop *>(control->GetSection("dos"));
01085         enable_config_as_shell_commands = section->Get_bool("shell configuration as commands");
01086     }
01087 }
01088 
01089 /* Pfff... starting and running the shell from a configuration section INIT
01090  * What the hell were you guys thinking? --J.C. */
01091 void SHELL_Run() {
01092         dos_shell_running_program = false;
01093 #if defined(WIN32) && !defined(C_SDL2)
01094         int Reflect_Menu(void);
01095         Reflect_Menu();
01096 #endif
01097 
01098         LOG(LOG_MISC,LOG_DEBUG)("Running DOS shell now");
01099 
01100         if (first_shell != NULL) E_Exit("Attempt to start shell when shell already running");
01101         SHELL_ProgramStart(&first_shell);
01102 
01103         try {
01104                 first_shell->Run();
01105                 delete first_shell;
01106                 first_shell = 0;//Make clear that it shouldn't be used anymore
01107                 dos_shell_running_program = false;
01108 #if defined(WIN32) && !defined(C_SDL2)
01109                 int Reflect_Menu(void);
01110                 Reflect_Menu();
01111 #endif
01112         }
01113         catch (...) {
01114                 delete first_shell;
01115                 first_shell = 0;//Make clear that it shouldn't be used anymore
01116                 dos_shell_running_program = false;
01117 #if defined(WIN32) && !defined(C_SDL2)
01118                 int Reflect_Menu(void);
01119                 Reflect_Menu();
01120 #endif
01121                 throw;
01122         }
01123 }
01124