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