DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/shell/shell.cpp
00001 /*
00002  *  Copyright (C) 2002-2020  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 along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "mapper.h"
00033 #include "../dos/drives.h"
00034 #include <unistd.h>
00035 #include <time.h>
00036 #include <string>
00037 #include <sstream>
00038 #include "build_timestamp.h"
00039 
00040 extern bool enable_config_as_shell_commands;
00041 extern bool dos_shell_running_program;
00042 extern Bit16u countryNo;
00043 bool usecon = true;
00044 
00045 Bit16u shell_psp = 0;
00046 Bitu call_int2e = 0;
00047 
00048 void MSG_Replace(const char * _name, const char* _val);
00049 void DOS_SetCountry(Bit16u countryNo);
00050 void CALLBACK_DeAllocate(Bitu in);
00051 
00052 Bitu call_shellstop = 0;
00053 /* Larger scope so shell_del autoexec can use it to
00054  * remove things from the environment */
00055 DOS_Shell * first_shell = 0; 
00056 
00057 static Bitu shellstop_handler(void) {
00058         return CBRET_STOP;
00059 }
00060 
00061 static void SHELL_ProgramStart(Program * * make) {
00062         *make = new DOS_Shell;
00063 }
00064 //Repeat it with the correct type, could do it in the function below, but this way it should be 
00065 //clear that if the above function is changed, this function might need a change as well.
00066 static void SHELL_ProgramStart_First_shell(DOS_Shell * * make) {
00067         *make = new DOS_Shell;
00068 }
00069 
00070 #define CONFIG_SIZE 4096
00071 #define AUTOEXEC_SIZE 4096
00072 static char config_data[CONFIG_SIZE] = { 0 };
00073 static char autoexec_data[AUTOEXEC_SIZE] = { 0 };
00074 static std::list<std::string> autoexec_strings;
00075 typedef std::list<std::string>::iterator auto_it;
00076 
00077 void VFILE_Remove(const char *name);
00078 
00079 void AutoexecObject::Install(const std::string &in) {
00080         if(GCC_UNLIKELY(installed)) E_Exit("autoexec: already created %s",buf.c_str());
00081         installed = true;
00082         buf = in;
00083         autoexec_strings.push_back(buf);
00084         this->CreateAutoexec();
00085 
00086         //autoexec.bat is normally created AUTOEXEC_Init.
00087         //But if we are already running (first_shell)
00088         //we have to update the envirionment to display changes
00089 
00090         if(first_shell) {
00091                 //create a copy as the string will be modified
00092                 std::string::size_type n = buf.size();
00093                 char* buf2 = new char[n + 1];
00094                 safe_strncpy(buf2, buf.c_str(), n + 1);
00095                 if((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
00096                         char* after_set = buf2 + 4;//move to variable that is being set
00097                         char* test2 = strpbrk(after_set,"=");
00098                         if(!test2) {first_shell->SetEnv(after_set,"");return;}
00099                         *test2++ = 0;
00100                         //If the shell is running/exists update the environment
00101                         first_shell->SetEnv(after_set,test2);
00102                 }
00103                 delete [] buf2;
00104         }
00105 }
00106 
00107 void AutoexecObject::InstallBefore(const std::string &in) {
00108         if(GCC_UNLIKELY(installed)) E_Exit("autoexec: already created %s",buf.c_str());
00109         installed = true;
00110         buf = in;
00111         autoexec_strings.push_front(buf);
00112         this->CreateAutoexec();
00113 }
00114 
00115 void AutoexecObject::CreateAutoexec(void) {
00116         /* Remove old autoexec.bat if the shell exists */
00117         if(first_shell) VFILE_Remove("AUTOEXEC.BAT");
00118 
00119         //Create a new autoexec.bat
00120         autoexec_data[0] = 0;
00121         size_t auto_len;
00122         for(auto_it it = autoexec_strings.begin(); it != autoexec_strings.end(); ++it) {
00123 
00124                 std::string linecopy = *it;
00125                 std::string::size_type offset = 0;
00126                 //Lets have \r\n as line ends in autoexec.bat.
00127                 while(offset < linecopy.length()) {
00128                         std::string::size_type  n = linecopy.find("\n",offset);
00129                         if ( n == std::string::npos ) break;
00130                         std::string::size_type rn = linecopy.find("\r\n",offset);
00131                         if ( rn != std::string::npos && rn + 1 == n) {offset = n + 1; continue;}
00132                         // \n found without matching \r
00133                         linecopy.replace(n,1,"\r\n");
00134                         offset = n + 2;
00135                 }
00136 
00137                 auto_len = strlen(autoexec_data);
00138                 if ((auto_len+linecopy.length() + 3) > AUTOEXEC_SIZE) {
00139                         E_Exit("SYSTEM:Autoexec.bat file overflow");
00140                 }
00141                 sprintf((autoexec_data + auto_len),"%s\r\n",linecopy.c_str());
00142         }
00143         if (first_shell) VFILE_Register("AUTOEXEC.BAT",(Bit8u *)autoexec_data,(Bit32u)strlen(autoexec_data));
00144 }
00145 
00146 void AutoexecObject::Uninstall() {
00147         if(!installed) return;
00148 
00149         // Remove the line from the autoexecbuffer and update environment
00150         for(auto_it it = autoexec_strings.begin(); it != autoexec_strings.end(); ) {
00151                 if ((*it) == buf) {
00152                         std::string::size_type n = buf.size();
00153                         char* buf2 = new char[n + 1];
00154                         safe_strncpy(buf2, buf.c_str(), n + 1);
00155                         bool stringset = false;
00156                         // If it's a environment variable remove it from there as well
00157                         if ((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
00158                                 char* after_set = buf2 + 4;//move to variable that is being set
00159                                 char* test2 = strpbrk(after_set,"=");
00160                                 if (!test2) continue;
00161                                 *test2 = 0;
00162                                 stringset = true;
00163                                 //If the shell is running/exists update the environment
00164                                 if (first_shell) first_shell->SetEnv(after_set,"");
00165                         }
00166                         delete [] buf2;
00167                         if (stringset && first_shell && first_shell->bf && first_shell->bf->filename.find("AUTOEXEC.BAT") != std::string::npos) {
00168                                 //Replace entry with spaces if it is a set and from autoexec.bat, as else the location counter will be off.
00169                                 *it = buf.assign(buf.size(),' ');
00170                                 ++it;
00171                         } else {
00172                                 it = autoexec_strings.erase(it);
00173                         }
00174                 } else ++it;
00175         }
00176         installed=false;
00177         this->CreateAutoexec();
00178 }
00179 
00180 AutoexecObject::~AutoexecObject(){
00181         Uninstall();
00182 }
00183 
00184 DOS_Shell::~DOS_Shell() {
00185         if (bf != NULL) delete bf; /* free batch file */
00186 }
00187 
00188 DOS_Shell::DOS_Shell():Program(){
00189         input_handle=STDIN;
00190         echo=true;
00191         exit=false;
00192         bf=0;
00193         call=false;
00194         lfnfor = uselfn;
00195     input_eof=false;
00196     completion_index = 0;
00197 }
00198 
00199 Bitu DOS_Shell::GetRedirection(char *s, char **ifn, char **ofn, char **toc,bool * append) {
00200 
00201         char * lr=s;
00202         char * lw=s;
00203         char ch;
00204         Bitu num=0;
00205         bool quote = false;
00206         char* t;
00207         int q;
00208 
00209         while ( (ch=*lr++) ) {
00210                 if(quote && ch != '"') { /* don't parse redirection within quotes. Not perfect yet. Escaped quotes will mess the count up */
00211                         *lw++ = ch;
00212                         continue;
00213                 }
00214 
00215                 switch (ch) {
00216                 case '"':
00217                         quote = !quote;
00218                         break;
00219                 case '>':
00220                         *append = ((*lr) == '>');
00221                         if (*append)
00222                                 lr++;
00223                         lr = ltrim(lr);
00224                         if (*ofn)
00225                                 free(*ofn);
00226                         *ofn = lr;
00227                         q = 0;
00228                         while (*lr && (q/2*2!=q || *lr != ' ') && *lr != '<' && *lr != '|') {
00229                                 if (*lr=='"')
00230                                         q++;
00231                                 lr++;
00232                         }
00233                         // if it ends on a : => remove it.
00234                         if ((*ofn != lr) && (lr[-1] == ':'))
00235                                 lr[-1] = 0;
00236                         t = (char*)malloc(lr-*ofn+1);
00237                         safe_strncpy(t, *ofn, lr-*ofn+1);
00238                         *ofn = t;
00239                         continue;
00240                 case '<':
00241                         if (*ifn)
00242                                 free(*ifn);
00243                         lr = ltrim(lr);
00244                         *ifn = lr;
00245                         q = 0;
00246                         while (*lr && (q/2*2!=q || *lr != ' ') && *lr != '>' && *lr != '|') {
00247                                 if (*lr=='"')
00248                                         q++;
00249                                 lr++;
00250                         }
00251                         if ((*ifn != lr) && (lr[-1] == ':'))
00252                                 lr[-1] = 0;
00253                         t = (char*)malloc(lr-*ifn+1);
00254                         safe_strncpy(t, *ifn, lr-*ifn+1);
00255                         *ifn = t;
00256                         continue;
00257                 case '|':
00258                         num++;
00259                         if (*toc)
00260                                 free(*toc);
00261                         lr = ltrim(lr);
00262                         *toc = lr;
00263                         while (*lr)
00264                                 lr++;
00265                         t = (char*)malloc(lr-*toc+1);
00266                         safe_strncpy(t, *toc, lr-*toc+1);
00267                         *toc = t;
00268                         continue;
00269                 }
00270                 *lw++=ch;
00271         }
00272         *lw=0;
00273         return num;
00274 }       
00275 
00276 void DOS_Shell::ParseLine(char * line) {
00277         LOG(LOG_EXEC,LOG_DEBUG)("Parsing command line: %s",line);
00278         /* Check for a leading @ */
00279         if (line[0] == '@') line[0] = ' ';
00280         line = trim(line);
00281 
00282         /* Do redirection and pipe checks */
00283         
00284         char * in  = 0;
00285         char * out = 0;
00286         char * toc = 0;
00287 
00288         Bit16u dummy,dummy2;
00289         Bit32u bigdummy = 0;
00290         bool append;
00291         bool normalstdin  = false;      /* wether stdin/out are open on start. */
00292         bool normalstdout = false;      /* Bug: Assumed is they are "con"      */
00293         
00294     GetRedirection(line, &in, &out, &toc, &append);
00295         if (in || out || toc) {
00296                 normalstdin  = (psp->GetFileHandle(0) != 0xff); 
00297                 normalstdout = (psp->GetFileHandle(1) != 0xff); 
00298         }
00299         if (in) {
00300                 if(DOS_OpenFile(in,OPEN_READ,&dummy)) { //Test if file exists
00301                         DOS_CloseFile(dummy);
00302                         LOG_MSG("SHELL:Redirect input from %s",in);
00303                         if(normalstdin) DOS_CloseFile(0);       //Close stdin
00304                         DOS_OpenFile(in,OPEN_READ,&dummy);      //Open new stdin
00305                 } else
00306                         WriteOut(!*in?"File open error\n":(dos.errorcode==DOSERR_ACCESS_DENIED?"Access denied - %s\n":"File open error - %s\n"), in);
00307         }
00308         bool fail=false;
00309         char pipetmp[270];
00310         Bit16u fattr;
00311         if (toc) {
00312 #ifdef WIN32
00313                 srand(GetTickCount());
00314 #else
00315                 struct timespec ts;
00316                 unsigned theTick = 0U;
00317                 clock_gettime( CLOCK_REALTIME, &ts );
00318                 theTick  = ts.tv_nsec / 1000000;
00319                 theTick += ts.tv_sec * 1000;
00320                 srand(theTick);
00321 #endif
00322                 std::string line;
00323                 if (!GetEnvStr("TEMP",line)&&!GetEnvStr("TMP",line))
00324                         sprintf(pipetmp, "pipe%d.tmp", rand()%10000);
00325                 else {
00326                         std::string::size_type idx = line.find('=');
00327                         std::string temp=line.substr(idx +1 , std::string::npos);
00328                         if (DOS_GetFileAttr(temp.c_str(), &fattr) && fattr&DOS_ATTR_DIRECTORY)
00329                                 sprintf(pipetmp, "%s\\pipe%d.tmp", temp.c_str(), rand()%10000);
00330                         else
00331                                 sprintf(pipetmp, "pipe%d.tmp", rand()%10000);
00332                 }
00333         }
00334         if (out||toc) {
00335                 if (out&&toc)
00336                         WriteOut(!*out?"Duplicate redirection\n":"Duplicate redirection - %s\n", out);
00337                 LOG_MSG("SHELL:Redirect output to %s",toc?pipetmp:out);
00338                 if(normalstdout) DOS_CloseFile(1);
00339                 if(!normalstdin && !in) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00340                 bool status = true;
00341                 /* Create if not exist. Open if exist. Both in read/write mode */
00342                 if(!toc&&append) {
00343                         if (DOS_GetFileAttr(out, &fattr) && fattr&DOS_ATTR_READ_ONLY) {
00344                                 DOS_SetError(DOSERR_ACCESS_DENIED);
00345                                 status = false;
00346                         } else if( (status = DOS_OpenFile(out,OPEN_READWRITE,&dummy)) ) {
00347                                  DOS_SeekFile(1,&bigdummy,DOS_SEEK_END);
00348                         } else {
00349                                 status = DOS_CreateFile(out,DOS_ATTR_ARCHIVE,&dummy);   //Create if not exists.
00350                         }
00351                 } else if (!toc&&DOS_GetFileAttr(out, &fattr) && fattr&DOS_ATTR_READ_ONLY) {
00352                         DOS_SetError(DOSERR_ACCESS_DENIED);
00353                         status = false;
00354                 } else {
00355                         if (toc&&DOS_FindFirst(pipetmp, ~DOS_ATTR_VOLUME)&&!DOS_UnlinkFile(pipetmp))
00356                                 fail=true;
00357                         status = DOS_OpenFileExtended(toc?pipetmp:out,OPEN_READWRITE,DOS_ATTR_ARCHIVE,0x12,&dummy,&dummy2);
00358                 }
00359                 
00360                 if(!status && normalstdout) {
00361                         DOS_OpenFile("con", OPEN_READWRITE, &dummy);                                                    // Read only file, open con again
00362                         if (!toc) {
00363                                 WriteOut(!*out?"File creation error\n":(dos.errorcode==DOSERR_ACCESS_DENIED?"Access denied - %s\n":"File creation error - %s\n"), out);
00364                                 DOS_CloseFile(1);
00365                                 DOS_OpenFile("nul", OPEN_READWRITE, &dummy);
00366                         }
00367                 }
00368                 if(!normalstdin && !in) DOS_CloseFile(0);
00369         }
00370         /* Run the actual command */
00371 
00372         if (this == first_shell) dos_shell_running_program = true;
00373 #if defined(WIN32) && !defined(C_SDL2)
00374         int Reflect_Menu(void);
00375         Reflect_Menu();
00376 #endif
00377         if (toc||(!toc&&((out&&DOS_FindDevice(out)!=DOS_FindDevice("con"))||(in&&DOS_FindDevice(in)!=DOS_FindDevice("con"))))) usecon=false;
00378 
00379         DoCommand(line);
00380 
00381         if (this == first_shell) dos_shell_running_program = false;
00382 #if defined(WIN32) && !defined(C_SDL2)
00383         int Reflect_Menu(void);
00384         Reflect_Menu();
00385 #endif
00386 
00387         /* Restore handles */
00388         if(in) {
00389                 DOS_CloseFile(0);
00390                 if(normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00391                 free(in);
00392         }
00393         if(out||toc) {
00394                 DOS_CloseFile(1);
00395                 if(!normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00396                 if(normalstdout) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
00397                 if(!normalstdin) DOS_CloseFile(0);
00398                 if (out) free(out);
00399         }
00400         if (toc)
00401                 {
00402                 if (!fail&&DOS_OpenFile(pipetmp, OPEN_READ, &dummy))                                    // Test if file can be opened for reading
00403                         {
00404                         DOS_CloseFile(dummy);
00405                         if (normalstdin)
00406                                 DOS_CloseFile(0);                                                                                               // Close stdin
00407                         DOS_OpenFile(pipetmp, OPEN_READ, &dummy);                                                       // Open new stdin
00408                         ParseLine(toc);
00409                         DOS_CloseFile(0);
00410                         if (normalstdin)
00411                                 DOS_OpenFile("con", OPEN_READWRITE, &dummy);
00412                         }
00413                 else
00414                         WriteOut("\nFailed to create/open a temporary file for piping. Check the %%TEMP%% variable.\n");
00415                 free(toc);
00416                 if (DOS_FindFirst(pipetmp, ~DOS_ATTR_VOLUME)) DOS_UnlinkFile(pipetmp);
00417                 }
00418         usecon=true;
00419 }
00420 
00421 
00422 
00423 void DOS_Shell::RunInternal(void) {
00424         char input_line[CMD_MAXLINE] = {0};
00425         while (bf) {
00426                 if (bf->ReadLine(input_line)) {
00427                         if (echo) {
00428                                 if (input_line[0] != '@') {
00429                                         ShowPrompt();
00430                                         WriteOut_NoParsing(input_line);
00431                                         WriteOut_NoParsing("\n");
00432                                 }
00433                         }
00434                         ParseLine(input_line);
00435                         if (echo) WriteOut_NoParsing("\n");
00436                 }
00437         }
00438 }
00439 
00440 void DOS_Shell::Run(void) {
00441         char input_line[CMD_MAXLINE] = {0};
00442         std::string line;
00443         bool optC=cmd->FindStringRemainBegin("/C",line), optK=false;
00444         if (!optC) optK=cmd->FindStringRemainBegin("/K",line);  
00445         if (optC||optK) {
00446                 input_line[CMD_MAXLINE-1u] = 0;
00447                 strncpy(input_line,line.c_str(),CMD_MAXLINE-1u);
00448                 char* sep = strpbrk(input_line,"\r\n"); //GTA installer
00449                 if (sep) *sep = 0;
00450                 DOS_Shell temp;
00451                 temp.echo = echo;
00452                 temp.ParseLine(input_line);             //for *.exe *.com  |*.bat creates the bf needed by runinternal;
00453                 temp.RunInternal();                             // exits when no bf is found.
00454                 if (!optK||temp.exit)
00455                         return;
00456         } else if (cmd->FindStringRemain("/?",line)) {
00457                 WriteOut(MSG_Get("SHELL_CMD_COMMAND_HELP"));
00458                 return;
00459         }
00460 
00461     if (this == first_shell) {
00462         /* Start a normal shell and check for a first command init */
00463         WriteOut(MSG_Get("SHELL_STARTUP_BEGIN"),VERSION,SDL_STRING,UPDATED_STR);
00464         WriteOut(MSG_Get("SHELL_STARTUP_BEGIN2"));
00465         WriteOut(MSG_Get("SHELL_STARTUP_BEGIN3"));
00466 #if C_DEBUG
00467         WriteOut(MSG_Get("SHELL_STARTUP_DEBUG"));
00468 #endif
00469         if (machine == MCH_CGA || machine == MCH_AMSTRAD) WriteOut(MSG_Get("SHELL_STARTUP_CGA"));
00470         if (machine == MCH_PC98) WriteOut(MSG_Get("SHELL_STARTUP_PC98"));
00471         if (machine == MCH_HERC || machine == MCH_MDA) WriteOut(MSG_Get("SHELL_STARTUP_HERC"));
00472         WriteOut(MSG_Get("SHELL_STARTUP_END"));
00473                 if (!countryNo) {
00474 #if defined(WIN32)      
00475                         char buffer[128];
00476                         if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICOUNTRY, buffer, 128)) {
00477                                 countryNo = Bit16u(atoi(buffer));
00478                                 DOS_SetCountry(countryNo);
00479                         }
00480                         else
00481                                 countryNo = 1;                                                                                          // Defaults to 1 (US) if failed
00482 #endif
00483                 }
00484                 strcpy(config_data, "");
00485                 Section_prop *section = static_cast<Section_prop *>(control->GetSection("config"));
00486                 if (section!=NULL&&!control->opt_noconfig&&!control->opt_securemode&&!control->SecureMode()) {
00487                         int country = section->Get_int("country");
00488                         if (country>0) {
00489                                 countryNo = country;
00490                                 DOS_SetCountry(countryNo);
00491                         }
00492                         const char * extra = const_cast<char*>(section->data.c_str());
00493                         if (extra) {
00494                                 std::istringstream in(extra);
00495                                 char linestr[CROSS_LEN+1], cmdstr[CROSS_LEN], valstr[CROSS_LEN], tmpstr[CROSS_LEN];
00496                                 char *cmd=cmdstr, *val=valstr, *tmp=tmpstr, *p;
00497                                 if (in) for (std::string line; std::getline(in, line); ) {
00498                                         if (line.length()>CROSS_LEN) {
00499                                                 strncpy(linestr, line.c_str(), CROSS_LEN);
00500                                                 linestr[CROSS_LEN]=0;
00501                                         } else
00502                                                 strcpy(linestr, line.c_str());
00503                                         p=strchr(linestr, '=');
00504                                         if (p!=NULL) {
00505                                                 *p=0;
00506                                                 strcpy(cmd, linestr);
00507                                                 strcpy(val, p+1);
00508                                                 cmd=trim(cmd);
00509                                                 val=trim(val);
00510                                                 if (strlen(config_data)+strlen(cmd)+strlen(val)+3<CONFIG_SIZE) {
00511                                                         strcat(config_data, cmd);
00512                                                         strcat(config_data, "=");
00513                                                         strcat(config_data, val);
00514                                                         strcat(config_data, "\r\n");
00515                                                 }
00516                                                 if (!strncasecmp(cmd, "set ", 4))
00517                                                         DoCommand((char *)(std::string(cmd)+"="+std::string(val)).c_str());
00518                                                 else if (!strcasecmp(cmd, "install")||!strcasecmp(cmd, "installhigh")||!strcasecmp(cmd, "device")||!strcasecmp(cmd, "devicehigh")) {
00519                                                         strcpy(tmp, val);
00520                                                         char *name=StripArg(tmp);
00521                                                         if (!*name) continue;
00522                                                         if (!DOS_FileExists(name)) {
00523                                                                 WriteOut("The following file is missing or corrupted: %s\n", name);
00524                                                                 continue;
00525                                                         }
00526                                                         if (!strcasecmp(cmd, "install"))
00527                                                                 DoCommand(val);
00528                                                         else if (!strcasecmp(cmd, "installhigh"))
00529                                                                 DoCommand((char *)("lh "+std::string(val)).c_str());
00530                                                         else if (!strcasecmp(cmd, "device"))
00531                                                                 DoCommand((char *)("device "+std::string(val)).c_str());
00532                                                         else if (!strcasecmp(cmd, "devicehigh"))
00533                                                                 DoCommand((char *)("lh device "+std::string(val)).c_str());
00534                                                 }
00535                                         } else if (!strncasecmp(line.c_str(), "rem ", 4)) {
00536                                                 strcat(config_data, line.c_str());
00537                                                 strcat(config_data, "\r\n");
00538                                         }
00539                                 }
00540                         }
00541                 }
00542                 if (!strlen(config_data)) {
00543                         strcat(config_data, "rem=");
00544                         strcat(config_data, (char *)section->Get_string("rem"));
00545                         strcat(config_data, "\r\n");
00546                 }
00547                 VFILE_Register("CONFIG.SYS",(Bit8u *)config_data,(Bit32u)strlen(config_data));
00548 #if defined(WIN32)
00549                 if (!control->opt_securemode&&!control->SecureMode())
00550                 {
00551                         const Section_prop* sec = 0; sec = static_cast<Section_prop*>(control->GetSection("dos"));
00552                         if(sec->Get_bool("automountall")) {
00553                                 Bit32u drives = GetLogicalDrives();
00554                                 char name[4]="A:\\";
00555                                 for (int i=0; i<25; i++) {
00556                                         if ((drives & (1<<i)) && !Drives[i])
00557                                         {
00558                                                 name[0]='A'+i;
00559                                                 int type=GetDriveType(name);
00560                                                 if (type!=DRIVE_NO_ROOT_DIR) {
00561                                                         WriteOut("Mounting %c: => %s..\n", name[0], name);
00562                                                         char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
00563                                                         sprintf(mountstring,"MOUNT %c ",name[0]);                                               
00564                                                         if(type==DRIVE_CDROM) strcat(mountstring,"-t cdrom ");
00565                                                         else if(type==DRIVE_REMOVABLE && (strcasecmp(name,"A:\\")==0||strcasecmp(name,"B:\\")==0)) strcat(mountstring,"-t floppy ");
00566                                                         strcat(mountstring,name);
00567                                                         strcat(mountstring," >nul");
00568                                                         ParseLine(mountstring);
00569                                                         if (!Drives[i]) WriteOut("Drive %c: failed to mount.\n",name[0]);
00570                                                         else if(type==DRIVE_FIXED && (strcasecmp(name,"C:\\")==0)) WriteOut("Warning: %s", MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_WARNING_WIN"));
00571                                                 }
00572                                         }
00573                                 }
00574                         }
00575                 }
00576 #endif
00577 
00578     }
00579     else {
00580         WriteOut(optK?"\n":"DOSBox-X command shell [Version %s %s]\nCopyright DOSBox-X Team. All rights reserved\n\n",VERSION,SDL_STRING);
00581     }
00582 
00583         if (cmd->FindString("/INIT",line,true)) {
00584                 input_line[CMD_MAXLINE-1u] = 0;
00585                 strncpy(input_line,line.c_str(),CMD_MAXLINE-1u);
00586                 line.erase();
00587                 ParseLine(input_line);
00588         }
00589         do {
00590                 /* Get command once a line */
00591                 if (bf) {
00592                         if (bf->ReadLine(input_line)) {
00593                                 if (echo) {
00594                                         if (input_line[0]!='@') {
00595                                                 ShowPrompt();
00596                                                 WriteOut_NoParsing(input_line);
00597                                                 WriteOut_NoParsing("\n");
00598                                         }
00599                                 }
00600                         } else input_line[0]='\0';
00601                 } else {
00602                         if (echo) ShowPrompt();
00603                         InputCommand(input_line);
00604                         if (echo && !input_eof) WriteOut("\n");
00605 
00606             /* Bugfix: CTTY NUL will return immediately, the shell input will return
00607              *         immediately, and if we don't consume CPU cycles to compensate,
00608              *         will leave DOSBox-X running in an endless loop, hung. */
00609             if (input_eof) CALLBACK_Idle();
00610                 }
00611 
00612                 /* do it */
00613                 if(strlen(input_line)!=0) {
00614                         ParseLine(input_line);
00615                         if (echo && !bf) WriteOut_NoParsing("\n");
00616                 }
00617         } while (!exit);
00618 }
00619 
00620 void DOS_Shell::SyntaxError(void) {
00621         WriteOut(MSG_Get("SHELL_SYNTAXERROR"));
00622 }
00623 
00624 class AUTOEXEC:public Module_base {
00625 private:
00626         AutoexecObject autoexec[17];
00627         AutoexecObject autoexec_echo;
00628     AutoexecObject autoexec_auto_bat;
00629 public:
00630         AUTOEXEC(Section* configuration):Module_base(configuration) {
00631                 /* Register a virtual AUTOEXEC.BAT file */
00632                 const Section_line * section=static_cast<Section_line *>(configuration);
00633 
00634                 /* Check -securemode switch to disable mount/imgmount/boot after running autoexec.bat */
00635                 bool secure = control->opt_securemode;
00636 
00637         /* The user may have given .BAT files to run on the command line */
00638         if (!control->auto_bat_additional.empty()) {
00639             std::string cmd;
00640 
00641             for (unsigned int i=0;i<control->auto_bat_additional.size();i++) {
00642                                 std::string batname;
00643                                 /* NTS: this code might have problems with DBCS filenames - yksoft1 */
00644 
00645                                 std::replace(control->auto_bat_additional[i].begin(),control->auto_bat_additional[i].end(),'/','\\');
00646                                 size_t pos = control->auto_bat_additional[i].find_last_of('\\');
00647                                 if(pos == std::string::npos) {  //Only a filename, mount current directory
00648                                         batname = control->auto_bat_additional[i];
00649                                         cmd += "@mount c: . -q\n";
00650                                 } else { //Parse the path of .BAT file
00651                                         std::string batpath = control->auto_bat_additional[i].substr(0,pos+1);
00652                                         if (batpath==".\\") batpath=".";
00653                                         else if (batpath=="..\\") batpath="..";
00654                                         batname = control->auto_bat_additional[i].substr(pos+1);
00655                                         cmd += "@mount c: \"" + batpath + "\" -q\n";
00656                                 }
00657                                 cmd += "@c:\n";
00658                                 cmd += "@cd \\\n";
00659                 cmd += "@CALL \"";
00660                 cmd += batname;
00661                 cmd += "\"\n";
00662                                 cmd += "@mount -u c: -q\n";
00663             }
00664 
00665             autoexec_auto_bat.Install(cmd);
00666         }
00667 
00668                 /* add stuff from the configfile unless -noautexec or -securemode is specified. */
00669                 const char * extra = const_cast<char*>(section->data.c_str());
00670                 if (extra && !secure && !control->opt_noautoexec) {
00671                         /* detect if "echo off" is the first line */
00672                         size_t firstline_length = strcspn(extra,"\r\n");
00673                         bool echo_off  = !strncasecmp(extra,"echo off",8);
00674                         if (echo_off && firstline_length == 8) extra += 8;
00675                         else {
00676                                 echo_off = !strncasecmp(extra,"@echo off",9);
00677                                 if (echo_off && firstline_length == 9) extra += 9;
00678                                 else echo_off = false;
00679                         }
00680 
00681                         /* if "echo off" move it to the front of autoexec.bat */
00682                         if (echo_off)  { 
00683                                 autoexec_echo.InstallBefore("@echo off");
00684                                 if (*extra == '\r') extra++; //It can point to \0
00685                                 if (*extra == '\n') extra++; //same
00686                         }
00687 
00688                         /* Install the stuff from the configfile if anything left after moving echo off */
00689 
00690                         if (*extra) autoexec[0].Install(std::string(extra));
00691                 }
00692 
00693                 /* Check to see for extra command line options to be added (before the command specified on commandline) */
00694                 /* Maximum of extra commands: 10 */
00695                 Bitu i = 1;
00696                 for (auto it=control->opt_c.begin();i <= 11 && it!=control->opt_c.end();it++) /* -c switches */
00697                         autoexec[i++].Install(*it);
00698 
00699                 /* Check for the -exit switch which causes dosbox to when the command on the commandline has finished */
00700                 bool addexit = control->opt_exit;
00701 
00702 #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. */
00703                 /* Check for first command being a directory or file */
00704                 char buffer[CROSS_LEN+1];
00705                 char orig[CROSS_LEN+1];
00706                 char cross_filesplit[2] = {CROSS_FILESPLIT , 0};
00707 
00708                 Bitu dummy = 1;
00709                 bool command_found = false;
00710                 while (control->cmdline->FindCommand(dummy++,line) && !command_found) {
00711                         struct stat test;
00712                         if (line.length() > CROSS_LEN) continue; 
00713                         strcpy(buffer,line.c_str());
00714                         if (stat(buffer,&test)) {
00715                                 if (getcwd(buffer,CROSS_LEN) == NULL) continue;
00716                                 if (strlen(buffer) + line.length() + 1 > CROSS_LEN) continue;
00717                                 strcat(buffer,cross_filesplit);
00718                                 strcat(buffer,line.c_str());
00719                                 if (stat(buffer,&test)) continue;
00720                         }
00721                         if (test.st_mode & S_IFDIR) { 
00722                                 autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
00723                                 autoexec[13].Install("C:");
00724                                 if(secure) autoexec[14].Install("z:\\config.com -securemode");
00725                                 command_found = true;
00726                         } else {
00727                                 char* name = strrchr(buffer,CROSS_FILESPLIT);
00728                                 if (!name) { //Only a filename 
00729                                         line = buffer;
00730                                         if (getcwd(buffer,CROSS_LEN) == NULL) continue;
00731                                         if (strlen(buffer) + line.length() + 1 > CROSS_LEN) continue;
00732                                         strcat(buffer,cross_filesplit);
00733                                         strcat(buffer,line.c_str());
00734                                         if(stat(buffer,&test)) continue;
00735                                         name = strrchr(buffer,CROSS_FILESPLIT);
00736                                         if(!name) continue;
00737                                 }
00738                                 *name++ = 0;
00739                                 if (access(buffer,F_OK)) continue;
00740                                 autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
00741                                 autoexec[13].Install("C:");
00742                                 /* Save the non-modified filename (so boot and imgmount can use it (long filenames, case sensivitive)) */
00743                                 strcpy(orig,name);
00744                                 upcase(name);
00745                                 if(strstr(name,".BAT") != 0) {
00746                                         if(secure) autoexec[14].Install("z:\\config.com -securemode");
00747                                         /* BATch files are called else exit will not work */
00748                                         autoexec[15].Install(std::string("CALL ") + name);
00749                                         if(addexit) autoexec[16].Install("exit");
00750                                 } else if((strstr(name,".IMG") != 0) || (strstr(name,".IMA") !=0 )) {
00751                                         //No secure mode here as boot is destructive and enabling securemode disables boot
00752                                         /* Boot image files */
00753                                         autoexec[15].Install(std::string("BOOT ") + orig);
00754                                 } else if((strstr(name,".ISO") != 0) || (strstr(name,".CUE") !=0 )) {
00755                                         /* imgmount CD image files */
00756                                         /* securemode gets a different number from the previous branches! */
00757                                         autoexec[14].Install(std::string("IMGMOUNT D \"") + orig + std::string("\" -t iso"));
00758                                         //autoexec[16].Install("D:");
00759                                         if(secure) autoexec[15].Install("z:\\config.com -securemode");
00760                                         /* Makes no sense to exit here */
00761                                 } else {
00762                                         if(secure) autoexec[14].Install("z:\\config.com -securemode");
00763                                         autoexec[15].Install(name);
00764                                         if(addexit) autoexec[16].Install("exit");
00765                                 }
00766                                 command_found = true;
00767                         }
00768                 }
00769 
00770                 /* Combining -securemode, noautoexec and no parameters leaves you with a lovely Z:\. */
00771                 if ( !command_found ) { 
00772                         if ( secure ) autoexec[12].Install("z:\\config.com -securemode");
00773                 }
00774 #else
00775                 if (secure) autoexec[i++].Install("z:\\config.com -securemode");
00776 #endif
00777 
00778                 if (addexit) autoexec[i++].Install("exit");
00779 
00780                 assert(i <= 17); /* FIXME: autoexec[] should not be fixed size */
00781 
00782                 VFILE_Register("AUTOEXEC.BAT",(Bit8u *)autoexec_data,(Bit32u)strlen(autoexec_data));
00783         }
00784 };
00785 
00786 static AUTOEXEC* test = NULL;
00787         
00788 static void AUTOEXEC_ShutDown(Section * sec) {
00789     (void)sec;//UNUSED
00790         if (test != NULL) {
00791                 delete test;
00792                 test = NULL;
00793         }
00794     if (first_shell != NULL) {
00795                 delete first_shell;
00796                 first_shell = 0;//Make clear that it shouldn't be used anymore
00797     }
00798     if (call_shellstop != 0) {
00799         CALLBACK_DeAllocate(call_shellstop);
00800         call_shellstop = 0;
00801     }
00802 }
00803 
00804 void AUTOEXEC_Startup(Section *sec) {
00805     (void)sec;//UNUSED
00806         if (test == NULL) {
00807                 LOG(LOG_MISC,LOG_DEBUG)("Allocating AUTOEXEC.BAT emulation");
00808                 test = new AUTOEXEC(control->GetSection("autoexec"));
00809         }
00810 }
00811 
00812 void AUTOEXEC_Init() {
00813         LOG(LOG_MISC,LOG_DEBUG)("Initializing AUTOEXEC.BAT emulation");
00814 
00815         AddExitFunction(AddExitFunctionFuncPair(AUTOEXEC_ShutDown));
00816         AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00817         AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00818         AddVMEventFunction(VM_EVENT_DOS_EXIT_REBOOT_BEGIN,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00819         AddVMEventFunction(VM_EVENT_DOS_SURPRISE_REBOOT,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
00820 }
00821 
00822 static Bitu INT2E_Handler(void) {
00823         /* Save return address and current process */
00824         RealPt save_ret=real_readd(SegValue(ss),reg_sp);
00825         Bit16u save_psp=dos.psp();
00826 
00827         /* Set first shell as process and copy command */
00828         dos.psp(shell_psp);//DOS_FIRST_SHELL);
00829         DOS_PSP psp(shell_psp);//DOS_FIRST_SHELL);
00830         psp.SetCommandTail(RealMakeSeg(ds,reg_si));
00831         SegSet16(ss,RealSeg(psp.GetStack()));
00832         reg_sp=2046;
00833 
00834         /* Read and fix up command string */
00835         CommandTail tail;
00836         MEM_BlockRead(PhysMake(dos.psp(),CTBUF+1),&tail,CTBUF+1);
00837         if (tail.count<CTBUF) tail.buffer[tail.count]=0;
00838         else tail.buffer[CTBUF-1]=0;
00839         char* crlf=strpbrk(tail.buffer,"\r\n");
00840         if (crlf) *crlf=0;
00841 
00842         /* Execute command */
00843         if (strlen(tail.buffer)) {
00844                 DOS_Shell temp;
00845                 temp.ParseLine(tail.buffer);
00846                 temp.RunInternal();
00847         }
00848 
00849         /* Restore process and "return" to caller */
00850         dos.psp(save_psp);
00851         SegSet16(cs,RealSeg(save_ret));
00852         reg_ip=RealOff(save_ret);
00853         reg_ax=0;
00854         return CBRET_NONE;
00855 }
00856 
00857 static char const * const path_string="PATH=Z:\\";
00858 static char const * const comspec_string="COMSPEC=Z:\\COMMAND.COM";
00859 static char const * const prompt_string="PROMPT=$P$G";
00860 static char const * const full_name="Z:\\COMMAND.COM";
00861 static char const * const init_line="/INIT AUTOEXEC.BAT";
00862 
00863 extern unsigned int dosbox_shell_env_size;
00864 
00865 /* TODO: Why is all this DOS kernel and VFILE registration here in SHELL_Init()?
00866  *       That's like claiming that DOS memory and device initialization happens from COMMAND.COM!
00867  *       We need to move the DOS kernel initialization into another function, and the VFILE
00868  *       registration to another function, and then message initialization to another function,
00869  *       and then those functions need to be called before SHELL_Init() -J.C. */
00870 void SHELL_Init() {
00871         LOG(LOG_MISC,LOG_DEBUG)("Initializing DOS shell");
00872 
00873         /* Add messages */
00874         MSG_Add("SHELL_CMD_VOL_DRIVE","\n Volume in drive %c ");
00875         MSG_Add("SHELL_CMD_VOL_DRIVEERROR","Cannot find the drive specified\n");
00876         MSG_Add("SHELL_CMD_VOL_SERIAL"," Volume Serial Number is ");
00877         MSG_Add("SHELL_CMD_VOL_SERIAL_NOLABEL","has no label\n");
00878         MSG_Add("SHELL_CMD_VOL_SERIAL_LABEL","is %s\n");
00879         MSG_Add("SHELL_ILLEGAL_PATH","Illegal Path.\n");
00880         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");
00881         MSG_Add("SHELL_CMD_ECHO_ON","ECHO is on.\n");
00882         MSG_Add("SHELL_CMD_ECHO_OFF","ECHO is off.\n");
00883         MSG_Add("SHELL_ILLEGAL_CONTROL_CHARACTER","Unexpected control character: Dec %03u and Hex %#04x.\n");
00884         MSG_Add("SHELL_ILLEGAL_SWITCH","Illegal switch: %s.\n");
00885         MSG_Add("SHELL_MISSING_PARAMETER","Required parameter missing.\n");
00886         MSG_Add("SHELL_CMD_CHDIR_ERROR","Unable to change to: %s.\n");
00887         MSG_Add("SHELL_CMD_CHDIR_HINT","Hint: To change to different drive type \033[31m%c:\033[0m\n");
00888         MSG_Add("SHELL_CMD_CHDIR_HINT_2","directoryname contains unquoted spaces.\nTry \033[31mcd %s\033[0m or properly quote them with quotation marks.\n");
00889         MSG_Add("SHELL_CMD_CHDIR_HINT_3","You are still on drive Z:, change to a mounted drive with \033[31mC:\033[0m.\n");
00890         MSG_Add("SHELL_CMD_DATE_HELP","Displays or changes the internal date.\n");
00891         MSG_Add("SHELL_CMD_DATE_ERROR","The specified date is not correct.\n");
00892         MSG_Add("SHELL_CMD_DATE_DAYS","3SunMonTueWedThuFriSat"); // "2SoMoDiMiDoFrSa"
00893         MSG_Add("SHELL_CMD_DATE_NOW","Current date: ");
00894         MSG_Add("SHELL_CMD_DATE_SETHLP","Type 'date %s' to change.\n");
00895         MSG_Add("SHELL_CMD_DATE_HELP_LONG","DATE [[/T] [/H] [/S] | date]\n"\
00896                                                                         "  date:       New date to set\n"\
00897                                                                         "  /S:         Permanently use host time and date as DOS time\n"\
00898                                     "  /F:         Switch back to DOSBox-X internal time (opposite of /S)\n"\
00899                                                                         "  /T:         Only display date\n"\
00900                                                                         "  /H:         Synchronize with host\n");
00901         MSG_Add("SHELL_CMD_TIME_HELP","Displays or changes the internal time.\n");
00902         MSG_Add("SHELL_CMD_TIME_ERROR","The specified time is not correct.\n");
00903         MSG_Add("SHELL_CMD_TIME_NOW","Current time: ");
00904         MSG_Add("SHELL_CMD_TIME_SETHLP","Type 'time %s' to change.\n");
00905         MSG_Add("SHELL_CMD_TIME_HELP_LONG","TIME [[/T] [/H] | time]\n"\
00906                                                                         "  time:       New time to set\n"\
00907                                                                         "  /T:         Display simple time\n"\
00908                                                                         "  /H:         Synchronize with host\n");
00909         MSG_Add("SHELL_CMD_MKDIR_ERROR","Unable to make: %s.\n");
00910         MSG_Add("SHELL_CMD_RMDIR_ERROR","Unable to remove: %s.\n");
00911     MSG_Add("SHELL_CMD_RENAME_ERROR","Unable to rename: %s.\n");
00912         MSG_Add("SHELL_CMD_ATTRIB_GET_ERROR","Unable to get attributes: %s\n");
00913         MSG_Add("SHELL_CMD_ATTRIB_SET_ERROR","Unable to set attributes: %s\n");
00914         MSG_Add("SHELL_CMD_DEL_ERROR","Unable to delete: %s.\n");
00915         MSG_Add("SHELL_CMD_DEL_SURE","All files in directory will be deleted!\nAre you sure [Y/N]?");
00916         MSG_Add("SHELL_SYNTAXERROR","The syntax of the command is incorrect.\n");
00917         MSG_Add("SHELL_CMD_SET_NOT_SET","Environment variable %s not defined.\n");
00918         MSG_Add("SHELL_CMD_SET_OUT_OF_SPACE","Not enough environment space left.\n");
00919         MSG_Add("SHELL_CMD_IF_EXIST_MISSING_FILENAME","IF EXIST: Missing filename.\n");
00920         MSG_Add("SHELL_CMD_IF_ERRORLEVEL_MISSING_NUMBER","IF ERRORLEVEL: Missing number.\n");
00921         MSG_Add("SHELL_CMD_IF_ERRORLEVEL_INVALID_NUMBER","IF ERRORLEVEL: Invalid number.\n");
00922         MSG_Add("SHELL_CMD_GOTO_MISSING_LABEL","No label supplied to GOTO command.\n");
00923         MSG_Add("SHELL_CMD_GOTO_LABEL_NOT_FOUND","GOTO: Label %s not found.\n");
00924         MSG_Add("SHELL_CMD_FILE_NOT_FOUND","File %s not found.\n");
00925         MSG_Add("SHELL_CMD_FILE_EXISTS","File %s already exists.\n");
00926         MSG_Add("SHELL_CMD_DIR_INTRO"," Directory of %s\n\n");
00927         MSG_Add("SHELL_CMD_DIR_BYTES_USED","%5d File(s) %17s Bytes\n");
00928         MSG_Add("SHELL_CMD_DIR_BYTES_FREE","%5d Dir(s)  %17s Bytes free\n");
00929         MSG_Add("SHELL_CMD_DIR_FILES_LISTED","Total files listed:\n");
00930         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");
00931         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_CDROM","Do you want to give DOSBox-X access to your real CD-ROM drive %c [Y/N]?");
00932         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY","Do you want to give DOSBox-X access to your real floppy drive %c [Y/N]?");
00933         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_REMOVABLE","Do you want to give DOSBox-X access to your real removable drive %c [Y/N]?");
00934         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_NETWORK","Do you want to give DOSBox-X access to your real network drive %c [Y/N]?");
00935         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_FIXED","Do you really want to give DOSBox-X access to everything\non your real drive %c [Y/N]?");
00936         MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_WARNING_WIN","Mounting C:\\ is NOT recommended.\n");
00937         MSG_Add("SHELL_EXECUTE_ILLEGAL_COMMAND","Illegal command: %s.\n");
00938         MSG_Add("SHELL_CMD_PAUSE","Press any key to continue.\n");
00939         MSG_Add("SHELL_CMD_PAUSE_HELP","Waits for one keystroke to continue.\n");
00940         MSG_Add("SHELL_CMD_PAUSE_HELP_LONG","PAUSE\n");
00941         MSG_Add("SHELL_CMD_COPY_FAILURE","Copy failure : %s.\n");
00942         MSG_Add("SHELL_CMD_COPY_SUCCESS","   %d File(s) copied.\n");
00943         MSG_Add("SHELL_CMD_COPY_CONFIRM","Overwrite %s (Yes/No/All)?");
00944         MSG_Add("SHELL_CMD_COPY_NOSPACE","Insufficient disk space - %s\n");
00945         MSG_Add("SHELL_CMD_COPY_ERROR","Error in copying file %s\n");
00946         MSG_Add("SHELL_CMD_SUBST_DRIVE_LIST","The currently mounted local drives are:\n");
00947         MSG_Add("SHELL_CMD_SUBST_NO_REMOVE","Unable to remove, drive not in use.\n");
00948         MSG_Add("SHELL_CMD_SUBST_IN_USE","Target drive is already in use.\n");
00949         MSG_Add("SHELL_CMD_SUBST_NOT_LOCAL","It is only possible to use SUBST on local drives.\n");
00950         MSG_Add("SHELL_CMD_SUBST_INVALID_PATH","The specified drive or path is invalid.\n");
00951         MSG_Add("SHELL_CMD_SUBST_FAILURE","SUBST: There is an error in your command line.\n");
00952 
00953     std::string mapper_keybind = mapper_event_keybind_string("host");
00954     if (mapper_keybind.empty()) mapper_keybind = "unbound";
00955 
00956     /* Capitalize the binding */
00957     if (mapper_keybind.size() > 0)
00958         mapper_keybind[0] = toupper(mapper_keybind[0]);
00959 
00960     /* Punctuation is important too. */
00961     mapper_keybind += ".";
00962 
00963     /* NTS: MSG_Add() takes the string as const char * but it does make a copy of the string when entering into the message map,
00964      *      so there is no problem here of causing use-after-free crashes when we exit. */
00965     std::string host_key_help; // SHELL_STARTUP_BEGIN2
00966 
00967     if (machine == MCH_PC98) {
00968 // "\x86\x46 To activate the keymapper \033[31mhost+M\033[37m. Host key is F12.                 \x86\x46\n"
00969         host_key_help =
00970             std::string("\x86\x46 To activate the keymapper \033[31mhost+M\033[37m. Host key is ") +
00971             (mapper_keybind + "                                     ").substr(0,20) +
00972             std::string(" \x86\x46\n");
00973     }
00974     else {
00975 // "\xBA To activate the keymapper \033[31mhost+M\033[37m. Host key is F12.                 \xBA\n"
00976         host_key_help =
00977             std::string("\033[44;1m\xBA To activate the keymapper \033[31mhost+M\033[37m. Host key is ") +
00978             (mapper_keybind + "                                     ").substr(0,20) +
00979             std::string(" \xBA\033[0m\n");
00980     }
00981 
00982     if (machine == MCH_PC98) {
00983         MSG_Add("SHELL_STARTUP_BEGIN",
00984                 "\x86\x52\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00985                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00986                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00987                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00988                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
00989                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x56\n"
00990                 "\x86\x46 \033[32mWelcome to DOSBox-X %-8s (%-4s) %-25s\033[37m      \x86\x46\n"
00991                 "\x86\x46                                                                    \x86\x46\n"
00992                 "\x86\x46 For a short introduction for new users type: \033[33mINTRO\033[37m                 \x86\x46\n"
00993                 "\x86\x46 For supported shell commands type: \033[33mHELP\033[37m                            \x86\x46\n"
00994                 "\x86\x46                                                                    \x86\x46\n"
00995                 "\x86\x46 To adjust the emulated CPU speed, use \033[31mhost -\033[37m and \033[31mhost +\033[37m.           \x86\x46\n");
00996         MSG_Replace("SHELL_STARTUP_BEGIN2",
00997                 host_key_help.c_str());
00998         MSG_Add("SHELL_STARTUP_BEGIN3",
00999                 "\x86\x46 For more information read the online guide in the \033[36mDOSBox-X Wiki\033[37m.   \x86\x46\n"
01000                 "\x86\x46                                                                    \x86\x46\n"
01001                );
01002         MSG_Add("SHELL_STARTUP_PC98","\x86\x46 DOSBox-X is now running in NEC PC-98 emulation mode.               \x86\x46\n"
01003                 "\x86\x46 \033[31mPC-98 emulation is INCOMPLETE and CURRENTLY IN DEVELOPMENT.\033[37m        \x86\x46\n");
01004         MSG_Add("SHELL_STARTUP_DEBUG",
01005 #if defined(MACOSX)
01006                 "\x86\x46 Debugger is available, use \033[31mAlt-F12\033[37m to enter.                       \x86\x46\n"
01007 #else
01008                 "\x86\x46 Debugger is available, use \033[31mAlt-Pause\033[37m to enter.                     \x86\x46\n"
01009 #endif
01010                 "\x86\x46                                                                    \x86\x46\n"
01011                );
01012         MSG_Add("SHELL_STARTUP_END",
01013                 "\x86\x46 \033[32mDOSBox-X project \033[33mhttp://dosbox-x.software\033[32m      PentiumPro support \033[37m \x86\x46\n"
01014                 "\x86\x46 \033[32mDerived from DOSBox \033[33mhttp://www.dosbox.com\033[37m                          \x86\x46\n"
01015                 "\x86\x5A\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
01016                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
01017                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
01018                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
01019                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
01020                 "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x5E\033[0m\n"
01021                 "\033[1m\033[32mHAVE FUN!\033[0m\n"
01022                );
01023     }
01024     else {
01025         MSG_Add("SHELL_STARTUP_BEGIN",
01026                 "\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"
01027                 "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
01028                 "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB\033[0m\n"
01029                 "\033[44;1m\xBA \033[32mWelcome to DOSBox-X %-8s (%-4s) %-25s\033[37m      \xBA\033[0m\n"
01030                 "\033[44;1m\xBA                                                                    \xBA\033[0m\n"
01031                 "\033[44;1m\xBA For a short introduction for new users type: \033[33mINTRO\033[37m                 \xBA\033[0m\n"
01032                 "\033[44;1m\xBA For supported shell commands type: \033[33mHELP\033[37m                            \xBA\033[0m\n"
01033                 "\033[44;1m\xBA                                                                    \xBA\033[0m\n"
01034                 "\033[44;1m\xBA To adjust the emulated CPU speed, use \033[31mhost -\033[37m and \033[31mhost +\033[37m.           \xBA\033[0m\n"
01035                            );
01036         MSG_Replace("SHELL_STARTUP_BEGIN2",
01037                 host_key_help.c_str());
01038         MSG_Add("SHELL_STARTUP_BEGIN3",
01039                 "\033[44;1m\xBA For more information read the online guide in the \033[36mDOSBox-X Wiki\033[37m.   \xBA\033[0m\n"
01040                 "\033[44;1m\xBA                                                                    \xBA\033[0m\n"
01041                );
01042         if (!mono_cga) {
01043             MSG_Add("SHELL_STARTUP_CGA","\033[44;1m\xBA DOSBox-X supports Composite CGA mode.                              \xBA\033[0m\n"
01044                     "\033[44;1m\xBA Use \033[31mF12\033[37m to set composite output ON, OFF, or AUTO (default).        \xBA\033[0m\n"
01045                     "\033[44;1m\xBA \033[31m(Alt-)F11\033[37m changes hue; \033[31mctrl-alt-F11\033[37m selects early/late CGA model.  \xBA\033[0m\n"
01046                     "\033[44;1m\xBA                                                                    \xBA\033[0m\n"
01047                    );
01048         } else {
01049             MSG_Add("SHELL_STARTUP_CGA","\033[44;1m\xBA Use \033[31mF11\033[37m to cycle through green, amber, and white monochrome color, \xBA\033[0m\n"
01050                     "\033[44;1m\xBA and \033[31mAlt-F11\033[37m to change contrast/brightness settings.                \xBA\033[0m\n"
01051                    );
01052         }
01053         MSG_Add("SHELL_STARTUP_PC98","\xBA DOSBox-X is now running in NEC PC-98 emulation mode.               \xBA\n"
01054                 "\xBA \033[31mPC-98 emulation is INCOMPLETE and CURRENTLY IN DEVELOPMENT.\033[37m        \xBA\n");
01055         MSG_Add("SHELL_STARTUP_HERC","\033[44;1m\xBA Use F11 to cycle through white, amber, and green monochrome color. \xBA\033[0m\n"
01056                 "\033[44;1m\xBA Use alt-F11 to toggle horizontal blending (only in graphics mode). \xBA\033[0m\n"
01057                 "\033[44;1m\xBA                                                                    \xBA\033[0m\n"
01058                );
01059         MSG_Add("SHELL_STARTUP_DEBUG",
01060 #if defined(MACOSX)
01061                 "\033[44;1m\xBA Debugger is available, use \033[31mAlt-F12\033[37m to enter.                       \xBA\033[0m\n"
01062 #else
01063                 "\033[44;1m\xBA Debugger is available, use \033[31mAlt-Pause\033[37m to enter.                     \xBA\033[0m\n"
01064 #endif
01065                 "\033[44;1m\xBA                                                                    \xBA\033[0m\n"
01066                );
01067         MSG_Add("SHELL_STARTUP_END",
01068                 "\033[44;1m\xBA \033[32mDOSBox-X project \033[33mhttp://dosbox-x.software\033[32m      PentiumPro support \033[37m \xBA\033[0m\n"
01069                 "\033[44;1m\xBA \033[32mDerived from DOSBox \033[33mhttp://www.dosbox.com\033[37m                          \xBA\033[0m\n"
01070                 "\033[44;1m\xC8\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
01071                 "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
01072                 "\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"
01073                 "\033[1m\033[32mHAVE FUN!\033[0m\n"
01074                );
01075     }
01076 
01077         MSG_Add("SHELL_CMD_BREAK_HELP","Sets or clears extended CTRL+C checking.\n");
01078         MSG_Add("SHELL_CMD_BREAK_HELP_LONG","BREAK [ON | OFF]\n\nType BREAK without a parameter to display the current BREAK setting.\n");
01079         MSG_Add("SHELL_CMD_CHDIR_HELP","Displays or changes the current directory.\n");
01080         MSG_Add("SHELL_CMD_CHDIR_HELP_LONG","CHDIR [drive:][path]\n"
01081                 "CHDIR [..]\n"
01082                 "CD [drive:][path]\n"
01083                 "CD [..]\n\n"
01084                 "  ..   Specifies that you want to change to the parent directory.\n\n"
01085                 "Type CD drive: to display the current directory in the specified drive.\n"
01086                 "Type CD without parameters to display the current drive and directory.\n");
01087         MSG_Add("SHELL_CMD_CLS_HELP","Clears screen.\n");
01088         MSG_Add("SHELL_CMD_CLS_HELP_LONG","CLS\n");
01089         MSG_Add("SHELL_CMD_DIR_HELP","Displays a list of files and subdirectories in a directory.\n");
01090         MSG_Add("SHELL_CMD_DIR_HELP_LONG","DIR [drive:][path][filename] [/[W|B]] [/S] [/P] [/A[D|H|S|R|A]] [/O[N|E|G|S|D]]\n\n"
01091                    "  [drive:][path][filename]\n"
01092                    "              Specifies drive, directory, and/or files to list.\n"
01093                    "  /W          Uses wide list format.\n"
01094                    "  /B          Uses bare format (no heading information or summary).\n"
01095                    "  /S          Displays files in specified directory and all subdirectories.\n"
01096                    "  /P          Pauses after each screenful of information.\n"
01097                    "  /A          Displays files with specified attributes.\n"
01098                    "  attributes   D  Directories                R  Read-only files\n"
01099                    "               H  Hidden files               A  Files ready for archiving\n"
01100                    "               S  System files               -  Prefix meaning not\n"
01101                    "  /O          List by files in sorted order.\n"
01102                    "  sortorder    N  By name (alphabetic)       S  By size (smallest first)\n"
01103                    "               E  By extension (alphabetic)  D  By date & time (earlist first)\n"
01104                    "               G  Group directories first    -  Prefix to reverse order\n\n"
01105                    "Switches may be preset in the DIRCMD environment variable.  Override\n"
01106                    "preset switches by prefixing any switch with - (hyphen)--for example, /-W.\n"
01107                    );
01108         MSG_Add("SHELL_CMD_ECHO_HELP","Displays messages, or turns command-echoing on or off.\n");
01109         MSG_Add("SHELL_CMD_ECHO_HELP_LONG","  ECHO [ON | OFF]\n  ECHO [message]\n\nType ECHO without parameters to display the current echo setting.\n");
01110         MSG_Add("SHELL_CMD_EXIT_HELP","Exits from the command shell.\n");
01111         MSG_Add("SHELL_CMD_EXIT_HELP_LONG","EXIT\n");
01112         MSG_Add("SHELL_CMD_HELP_HELP","Shows DOSBox-X command help.\n");
01113         MSG_Add("SHELL_CMD_HELP_HELP_LONG","HELP [/A or /ALL]\nHELP [command]\n\n"
01114                     "   /A or /ALL\tLists all supported internal commands.\n\n"
01115                         "Note: HELP will not list external commands such as MOUNT and IMGMOUNT.\n");
01116         MSG_Add("SHELL_CMD_MKDIR_HELP","Creates a directory.\n");
01117         MSG_Add("SHELL_CMD_MKDIR_HELP_LONG","MKDIR [drive:][path]\n"
01118                 "MD [drive:][path]\n");
01119         MSG_Add("SHELL_CMD_RMDIR_HELP","Removes a directory.\n");
01120         MSG_Add("SHELL_CMD_RMDIR_HELP_LONG","RMDIR [drive:][path]\n"
01121                 "RD [drive:][path]\n");
01122         MSG_Add("SHELL_CMD_SET_HELP","Displays or changes environment variables.\n");
01123         MSG_Add("SHELL_CMD_SET_HELP_LONG","SET [variable=[string]]\n\n"
01124                    "   variable\tSpecifies the environment-variable name.\n"
01125                    "   string\tSpecifies a series of characters to assign to the variable.\n\n"
01126                    "* If no string is specified, the variable is removed from the environment.\n\n"
01127                    "Type SET without parameters to display the current environment variables.\n");
01128         MSG_Add("SHELL_CMD_IF_HELP","Performs conditional processing in batch programs.\n");
01129         MSG_Add("SHELL_CMD_IF_HELP_LONG","IF [NOT] ERRORLEVEL number command\n"
01130                    "IF [NOT] string1==string2 command\n"
01131                    "IF [NOT] EXIST filename command\n\n"
01132                    "  NOT               Specifies that DOS should carry out\n"
01133                    "                    the command only if the condition is false.\n\n"
01134                    "  ERRORLEVEL number Specifies a true condition if the last program run\n"
01135                    "                    returned an exit code equal to or greater than the number\n"
01136                    "                    specified.\n\n"
01137                    "  string1==string2  Specifies a true condition if the specified text strings\n"
01138                    "                    match.\n\n"
01139                    "  EXIST filename    Specifies a true condition if the specified filename\n"
01140                    "                    exists.\n\n"
01141                    "  command           Specifies the command to carry out if the condition is\n"
01142                    "                    met.  Command can be followed by ELSE command which\n"
01143                    "                    will execute the command after the ELSE keyword if the\n"
01144                    "                    specified condition is FALSE\n");
01145         MSG_Add("SHELL_CMD_GOTO_HELP","Jumps to a labeled line in a batch program.\n");
01146         MSG_Add("SHELL_CMD_GOTO_HELP_LONG","GOTO label\n\n"
01147                    "   label   Specifies a text string used in the batch program as a label.\n\n"
01148                    "You type a label on a line by itself, beginning with a colon.\n");
01149         MSG_Add("SHELL_CMD_SHIFT_HELP","Changes the position of replaceable parameters in a batch file.\n");
01150         MSG_Add("SHELL_CMD_SHIFT_HELP_LONG","SHIFT\n");
01151         MSG_Add("SHELL_CMD_FOR_HELP","Runs a specified command for each file in a set of files.\n");
01152         MSG_Add("SHELL_CMD_FOR_HELP_LONG","FOR %%variable IN (set) DO command [command-parameters]\n\n  %%variable  Specifies a replaceable parameter.\n  (set)      Specifies a set of one or more files. Wildcards may be used.\n  command    Specifies the command to carry out for each file.\n  command-parameters\n             Specifies parameters or switches for the specified command.\n\nTo use the command in a batch program, specify %%%%variable instead of %%variable.\n");
01153         MSG_Add("SHELL_CMD_LFNFOR_HELP","Enables or disables long filenames when processing FOR wildcards.\n");
01154         MSG_Add("SHELL_CMD_LFNFOR_HELP_LONG","LFNFOR [ON | OFF]\n\nType LFNFOR without a parameter to display the current LFNFOR setting.\n\nThis command is only useful if LFN support is currently enabled.\n");
01155         MSG_Add("SHELL_CMD_TYPE_HELP","Displays the contents of a text-file.\n");
01156         MSG_Add("SHELL_CMD_TYPE_HELP_LONG","TYPE [drive:][path][filename]\n");
01157         MSG_Add("SHELL_CMD_REM_HELP","Adds comments in a batch file.\n");
01158         MSG_Add("SHELL_CMD_REM_HELP_LONG","REM [comment]\n");
01159         MSG_Add("SHELL_CMD_RENAME_HELP","Renames a file/directory or files.\n");
01160         MSG_Add("SHELL_CMD_RENAME_HELP_LONG","RENAME [drive:][path][directoryname1 | filename1] [directoryname2 | filename2]\n"
01161                 "REN [drive:][path][directoryname1 | filename1] [directoryname2 | filename2]\n\n"
01162                 "Note that you can not specify a new drive or path for your destination.\n");
01163         MSG_Add("SHELL_CMD_DELETE_HELP","Removes one or more files.\n");
01164         MSG_Add("SHELL_CMD_DELETE_HELP_LONG","DEL [/P] [/F] [/Q] names\n"
01165                    "ERASE [/P] [/F] [/Q] names\n\n"
01166                    "  names\t\tSpecifies a list of one or more files or directories.\n"
01167                    "\t\tWildcards may be used to delete multiple files. If a\n"
01168                    "\t\tdirectory is specified, all files within the directory\n"
01169                    "\t\twill be deleted.\n"
01170                    "  /P\t\tPrompts for confirmation before deleting one or more files.\n"
01171                    "  /F\t\tForce deleting of read-only files.\n"
01172                    "  /Q\t\tQuiet mode, do not ask if ok to delete on global wildcard\n");
01173         MSG_Add("SHELL_CMD_COPY_HELP","Copies one or more files.\n");
01174         MSG_Add("SHELL_CMD_COPY_HELP_LONG","COPY [/Y | /-Y] source [+source [+ ...]] [destination]\n\n"
01175                    "  source\tSpecifies the file or files to be copied.\n"
01176                    "  destination\tSpecifies the directory and/or filename for the new file(s).\n"
01177                    "  /Y\t\tSuppresses prompting to confirm you want to overwrite an\n\t\texisting destination file.\n"
01178                    "  /-Y\t\tCauses prompting to confirm you want to overwrite an\n\t\texisting destination file.\n\n"
01179                    "The switch /Y may be preset in the COPYCMD environment variable.\n"
01180                    "This may be overridden with /-Y on the command line.\n\n"
01181                    "To append files, specify a single file for destination, but multiple files\n"
01182                    "for source (using wildcards or file1+file2+file3 format).\n");
01183         MSG_Add("SHELL_CMD_CALL_HELP","Starts a batch file from within another batch file.\n");
01184         MSG_Add("SHELL_CMD_CALL_HELP_LONG","CALL [drive:][path]filename [batch-parameters]\n\n"
01185                    "batch-parameters   Specifies any command-line information required by\n"
01186                    "                   the batch program.\n");
01187         MSG_Add("SHELL_CMD_SUBST_HELP","Assigns an internal directory to a drive.\n");
01188         MSG_Add("SHELL_CMD_SUBST_HELP_LONG","SUBST [drive1: [drive2:]path]\nSUBST drive1: /D\n\n"
01189                    "  drive1:\tSpecifies a drive to which you want to assign a path.\n"
01190                    "  [drive2:]path\tSpecifies a mounted local drive and path you want to assign to.\n"
01191                    "  /D\t\tDeletes a mounted or substituted drive.\n\n"
01192                    "Type SUBST with no parameters to display a list of mounted local drives.\n");
01193         MSG_Add("SHELL_CMD_LOADHIGH_HELP","Loads a program into upper memory (requires XMS and UMB memory).\n");
01194         MSG_Add("SHELL_CMD_LOADHIGH_HELP_LONG","LH\t\t[drive1:][path]filename [parameters]\nLOADHIGH\t[drive1:][path]filename [parameters]\n");
01195         MSG_Add("SHELL_CMD_LS_HELP", "Lists directory contents.\n");
01196         MSG_Add("SHELL_CMD_LS_HELP_LONG", "LS [drive:][path][filename] [/A] [/L] [/P] [/Z]\n\n"
01197                 "  /A\tLists hidden and system files also.\n"
01198                 "  /L\tLists names one per line.\n"
01199                     "  /P\tPauses after each screenful of information.\n"
01200                         "  /Z\tDisplays short names even if LFN support is available.\n");
01201         MSG_Add("SHELL_CMD_CHOICE_HELP","Waits for a keypress and sets ERRORLEVEL.\n");
01202         MSG_Add("SHELL_CMD_CHOICE_HELP_LONG","CHOICE [/C:choices] [/N] [/S] text\n"
01203                 "  /C[:]choices  -  Specifies allowable keys.  Default is: yn.\n"
01204                 "  /N  -  Do not display the choices at end of prompt.\n"
01205                 "  /S  -  Enables case-sensitive choices to be selected.\n"
01206                 "  text  -  The text to display as a prompt.\n");
01207         MSG_Add("SHELL_CMD_ATTRIB_HELP","Displays or changes file attributes.\n");
01208         MSG_Add("SHELL_CMD_ATTRIB_HELP_LONG","ATTRIB [+R | -R] [+A | -A] [+S | -S] [+H | -H] [drive:][path][filename] [/S]\n\n"
01209                         "  +    Sets an attribute.\n"
01210                         "  -    Clears an attribute.\n"
01211                         "  R    Read-only file attribute.\n"
01212                         "  A    Archive file attribute.\n"
01213                         "  S    System file attribute.\n"
01214                         "  H    Hidden file attribute.\n"
01215                         "  [drive:][path][filename] Specifies file(s) for ATTRIB to process.\n"
01216                         "  /S Processes files in all directories in the specified path.\n");
01217         MSG_Add("SHELL_CMD_PATH_HELP","Displays or sets a search path for executable files.\n");
01218         MSG_Add("SHELL_CMD_PATH_HELP_LONG","PATH [[drive:]path[;...][;%PATH%]\n"
01219                    "PATH ;\n\n"
01220                    "Type PATH ; to clear all search path settings.\n"
01221                    "Type PATH without parameters to display the current path.\n");
01222         MSG_Add("SHELL_CMD_VERIFY_HELP","Controls whether to verify files are written correctly to a disk.\n");
01223         MSG_Add("SHELL_CMD_VERIFY_HELP_LONG","VERIFY [ON | OFF]\n\nType VERIFY without a parameter to display the current VERIFY setting.\n");
01224         MSG_Add("SHELL_CMD_VER_HELP","Displays or sets DOSBox-X's reported DOS version.\n");
01225         MSG_Add("SHELL_CMD_VER_HELP_LONG","VER [/R]\n" 
01226                    "VER SET [major.minor] or VER SET [major minor]\n\n" 
01227                    "  [major.minor] or [major minor]  Set the reported DOS version.\n\n"
01228                    "  Example: \"VER SET 6.0\" or \"VER SET 7.1\" for DOS 6.0 or 7.1 respectively.\n"
01229                    "  The command \"VER SET 7 1\" however sets the reported DOS version as 7.01.\n\n" 
01230                    "Type VER without parameters to display DOSBox-X and the reported DOS version.\n");
01231         MSG_Add("SHELL_CMD_VER_VER","DOSBox-X version %s (%s). Reported DOS version %d.%02d.\n");
01232         MSG_Add("SHELL_CMD_ADDKEY_HELP","Generates artificial keypresses.\n");
01233         MSG_Add("SHELL_CMD_ADDKEY_HELP_LONG","ADDKEY [key]\n");
01234         MSG_Add("SHELL_CMD_VOL_HELP","Displays the disk volume label and serial number, if they exist.\n");
01235         MSG_Add("SHELL_CMD_VOL_HELP_LONG","VOL [drive]\n");
01236         MSG_Add("SHELL_CMD_PROMPT_HELP","Changes the command prompt.\n");
01237         MSG_Add("SHELL_CMD_PROMPT_HELP_LONG","PROMPT [text]\n"
01238                    "  text    Specifies a new command prompt.\n\n"
01239                    "Prompt can be made up of normal characters and the following special codes:\n"
01240                    "  $A   & (Ampersand)\n"
01241                    "  $B   | (pipe)\n"
01242                    "  $C   ( (Left parenthesis)\n"
01243                    "  $D   Current date\n"
01244                    "  $E   Escape code (ASCII code 27)\n"
01245                    "  $F   ) (Right parenthesis)\n"
01246                    "  $G   > (greater-than sign)\n"
01247                    "  $H   Backspace (erases previous character)\n"
01248                    "  $L   < (less-than sign)\n"
01249                    "  $N   Current drive\n"
01250                    "  $P   Current drive and path\n"
01251                    "  $Q   = (equal sign)\n"
01252                    "  $S     (space)\n"
01253                    "  $T   Current time\n"
01254                    "  $V   DOS version number\n"
01255                    "  $_   Carriage return and linefeed\n"
01256                    "  $$   $ (dollar sign)\n");
01257     MSG_Add("SHELL_CMD_ALIAS_HELP", "Defines or displays aliases.\n");
01258     MSG_Add("SHELL_CMD_ALIAS_HELP_LONG", "ALIAS [name[=value] ... ]\n\nType ALIAS without parameters to display the list of aliases in the form:\n`ALIAS NAME = VALUE'\n");
01259         MSG_Add("SHELL_CMD_COUNTRY_HELP", "Displays or changes the current country.\n");
01260         MSG_Add("SHELL_CMD_COUNTRY_HELP_LONG", "COUNTRY [nnn] \n\n  nnn\tSpecifies a country code.\n");
01261     MSG_Add("SHELL_CMD_CTTY_HELP","Changes the terminal device used to control the system.\n");
01262         MSG_Add("SHELL_CMD_CTTY_HELP_LONG","CTTY device\n  device\tThe terminal device to use, such as CON.\n");
01263         MSG_Add("SHELL_CMD_MORE_HELP","Displays output one screen at a time.\n");
01264         MSG_Add("SHELL_CMD_MORE_HELP_LONG","MORE [drive:][path][filename]\nMORE < [drive:][path]filename\ncommand-name | MORE [drive:][path][filename]\n");
01265         MSG_Add("SHELL_CMD_TRUENAME_HELP","Finds the fully-expanded name for a file.\n");
01266         MSG_Add("SHELL_CMD_TRUENAME_HELP_LONG","TRUENAME file\n");
01267         MSG_Add("SHELL_CMD_DXCAPTURE_HELP","Runs program with video or audio capture.\n");
01268         MSG_Add("SHELL_CMD_DXCAPTURE_HELP_LONG","DX-CAPTURE [/V|/-V] [/A|/-A] [/M|/-M] [command] [options]\n\nIt will start video or audio capture, run program, and then automatically stop capture when the program exits.\n");
01269 #if C_DEBUG
01270         MSG_Add("SHELL_CMD_DEBUGBOX_HELP","Runs program and breaks into debugger at entry point.\n");
01271         MSG_Add("SHELL_CMD_DEBUGBOX_HELP_LONG","DEBUGBOX [command] [options]\n");
01272         MSG_Add("SHELL_CMD_INT2FDBG_HELP","Hooks INT 2Fh for debugging purposes.\n");
01273         MSG_Add("SHELL_CMD_INT2FDBG_HELP_LONG","INT2FDBG [option]\n  /I      Installs hook\n\nIt will hook INT 2Fh at the top of the call chain for debugging information.\n");
01274 #endif
01275         MSG_Add("SHELL_CMD_COMMAND_HELP","Starts the DOSBox-X command shell.\n\nThe following options are accepted:\n\n  /C\tExecutes the specified command and returns.\n  /K\tExecutes the specified command and continues running.\n  /INIT\tInitializes the command shell.\n");
01276 
01277         /* Regular startup */
01278         call_shellstop=CALLBACK_Allocate();
01279         /* Setup the startup CS:IP to kill the last running machine when exitted */
01280         RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
01281         SegSet16(cs,RealSeg(newcsip));
01282         reg_ip=RealOff(newcsip);
01283 
01284         CALLBACK_Setup(call_shellstop,shellstop_handler,CB_IRET,"shell stop");
01285         PROGRAMS_MakeFile("COMMAND.COM",SHELL_ProgramStart);
01286 
01287     /* NTS: Some DOS programs behave badly if run from a command interpreter
01288      *      who's PSP segment is too low in memory and does not appear in
01289      *      the MCB chain (SimCity 2000). So allocate shell memory normally
01290      *      as any DOS application would do.
01291      *
01292      *      That includes allocating COMMAND.COM stack NORMALLY (not up in
01293      *      the UMB as DOSBox SVN would do) */
01294 
01295         /* Now call up the shell for the first time */
01296         Bit16u psp_seg;//=DOS_FIRST_SHELL;
01297         Bit16u env_seg;//=DOS_FIRST_SHELL+19; //DOS_GetMemory(1+(4096/16))+1;
01298         Bit16u stack_seg;//=DOS_GetMemory(2048/16,"COMMAND.COM stack");
01299     Bit16u tmp,total_sz;
01300 
01301     // decide shell env size
01302     if (dosbox_shell_env_size == 0)
01303         dosbox_shell_env_size = (0x158u - (0x118u + 19u)) << 4u; /* equivalent to mainline DOSBox */
01304     else
01305         dosbox_shell_env_size = (dosbox_shell_env_size+15u)&(~15u); /* round up to paragraph */
01306 
01307     LOG_MSG("COMMAND.COM env size:             %u bytes",dosbox_shell_env_size);
01308 
01309     // According to some sources, 0x0008 is a special PSP segment value used by DOS before the first
01310     // program is used. We need the current PSP segment to be nonzero so that DOS_AllocateMemory()
01311     // can properly allocate memory.
01312     dos.psp(8);
01313 
01314     // COMMAND.COM environment block
01315     tmp = dosbox_shell_env_size>>4;
01316         if (!DOS_AllocateMemory(&env_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate environment block segment");
01317     LOG_MSG("COMMAND.COM environment block:    0x%04x sz=0x%04x",env_seg,tmp);
01318 
01319     // COMMAND.COM main binary (including PSP and stack)
01320     tmp = 0x1A + (2048/16);
01321     total_sz = tmp;
01322         if (!DOS_AllocateMemory(&psp_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate main body + PSP segment");
01323     LOG_MSG("COMMAND.COM main body (PSP):      0x%04x sz=0x%04x",psp_seg,tmp);
01324 
01325     // now COMMAND.COM has a main body and PSP segment, reflect it
01326     dos.psp(psp_seg);
01327     shell_psp = psp_seg;
01328 
01329     {
01330         DOS_MCB mcb((Bit16u)(env_seg-1));
01331         mcb.SetPSPSeg(psp_seg);
01332         mcb.SetFileName("COMMAND");
01333     }
01334 
01335     {
01336         DOS_MCB mcb((Bit16u)(psp_seg-1));
01337         mcb.SetPSPSeg(psp_seg);
01338         mcb.SetFileName("COMMAND");
01339     }
01340 
01341     // set the stack at 0x1A
01342     stack_seg = psp_seg + 0x1A;
01343     LOG_MSG("COMMAND.COM stack:                0x%04x",stack_seg);
01344 
01345     // set the stack pointer
01346         SegSet16(ss,stack_seg);
01347         reg_sp=2046;
01348 
01349         /* Set up int 24 and psp (Telarium games) */
01350         real_writeb(psp_seg+16+1,0,0xea);               /* far jmp */
01351         real_writed(psp_seg+16+1,1,real_readd(0,0x24*4));
01352         real_writed(0,0x24*4,((Bit32u)psp_seg<<16) | ((16+1)<<4));
01353 
01354         /* Set up int 23 to "int 20" in the psp. Fixes what.exe */
01355         real_writed(0,0x23*4,((Bit32u)psp_seg<<16));
01356 
01357         /* Set up int 2e handler */
01358     if (call_int2e == 0)
01359         call_int2e = CALLBACK_Allocate();
01360 
01361     //  RealPt addr_int2e=RealMake(psp_seg+16+1,8);
01362     // NTS: It's apparently common practice to enumerate MCBs by reading the segment value of INT 2Eh and then
01363     //      scanning forward from there. The assumption seems to be that COMMAND.COM writes INT 2Eh there using
01364     //      it's PSP segment and an offset like that of a COM executable even though COMMAND.COM is often an EXE file.
01365         RealPt addr_int2e=RealMake(psp_seg,8+((16+1)*16));
01366 
01367     CALLBACK_Setup(call_int2e,&INT2E_Handler,CB_IRET_STI,Real2Phys(addr_int2e),"Shell Int 2e");
01368         RealSetVec(0x2e,addr_int2e);
01369 
01370         /* Setup environment */
01371         PhysPt env_write=PhysMake(env_seg,0);
01372         MEM_BlockWrite(env_write,path_string,(Bitu)(strlen(path_string)+1));
01373         env_write += (PhysPt)(strlen(path_string)+1);
01374         MEM_BlockWrite(env_write,comspec_string,(Bitu)(strlen(comspec_string)+1));
01375         env_write += (PhysPt)(strlen(comspec_string)+1);
01376         MEM_BlockWrite(env_write,prompt_string,(Bitu)(strlen(prompt_string)+1));
01377         env_write +=(PhysPt)(strlen(prompt_string)+1);
01378         mem_writeb(env_write++,0);
01379         mem_writew(env_write,1);
01380         env_write+=2;
01381         MEM_BlockWrite(env_write,full_name,(Bitu)(strlen(full_name)+1));
01382 
01383 //    extern bool Mouse_Vertical;
01384         extern bool Mouse_Drv;
01385         Mouse_Drv = true;
01386 
01387         VFILE_RegisterBuiltinFileBlob(bfb_DEBUG_EXE);
01388         VFILE_RegisterBuiltinFileBlob(bfb_MOVE_EXE);
01389         VFILE_RegisterBuiltinFileBlob(bfb_FIND_EXE);
01390         VFILE_RegisterBuiltinFileBlob(bfb_LASTDRIV_COM);
01391         VFILE_RegisterBuiltinFileBlob(bfb_FCBS_COM);
01392         VFILE_RegisterBuiltinFileBlob(bfb_XCOPY_EXE);
01393         VFILE_RegisterBuiltinFileBlob(bfb_APPEND_EXE);
01394         VFILE_RegisterBuiltinFileBlob(bfb_DEVICE_COM);
01395         VFILE_RegisterBuiltinFileBlob(bfb_BUFFERS_COM);
01396 
01397         /* These are IBM PC/XT/AT ONLY. They will not work in PC-98 mode. */
01398         if (!IS_PC98_ARCH) {
01399                 VFILE_RegisterBuiltinFileBlob(bfb_HEXMEM16_EXE);
01400                 VFILE_RegisterBuiltinFileBlob(bfb_HEXMEM32_EXE);
01401                 VFILE_RegisterBuiltinFileBlob(bfb_DOSIDLE_EXE);
01402                 VFILE_RegisterBuiltinFileBlob(bfb_CWSDPMI_EXE);
01403                 VFILE_RegisterBuiltinFileBlob(bfb_DOS32A_EXE);
01404                 VFILE_RegisterBuiltinFileBlob(bfb_DOS4GW_EXE);
01405                 VFILE_RegisterBuiltinFileBlob(bfb_EDIT_COM);
01406                 VFILE_RegisterBuiltinFileBlob(bfb_TREE_EXE);
01407 
01408                 if (IS_VGA_ARCH)
01409                         VFILE_RegisterBuiltinFileBlob(bfb_25_COM);
01410                 else if (IS_EGA_ARCH)
01411                         VFILE_RegisterBuiltinFileBlob(bfb_25_COM_ega);
01412                 else
01413                         VFILE_RegisterBuiltinFileBlob(bfb_25_COM_other);
01414         }
01415 
01416         /* don't register 28.com unless EGA/VGA */
01417         if (IS_VGA_ARCH)
01418                 VFILE_RegisterBuiltinFileBlob(bfb_28_COM);
01419         else if (IS_EGA_ARCH)
01420                 VFILE_RegisterBuiltinFileBlob(bfb_28_COM_ega);
01421 
01422         /* don't register 50 unless VGA */
01423         if (IS_VGA_ARCH) VFILE_RegisterBuiltinFileBlob(bfb_50_COM);
01424 
01425         /* MEM.COM is not compatible with PC-98 and/or 8086 emulation */
01426         if (!IS_PC98_ARCH && CPU_ArchitectureType >= CPU_ARCHTYPE_80186)
01427                 VFILE_RegisterBuiltinFileBlob(bfb_MEM_COM);
01428 
01429         /* DSXMENU.EXE */
01430         if (IS_PC98_ARCH)
01431                 VFILE_RegisterBuiltinFileBlob(bfb_DSXMENU_EXE_PC98);
01432         else
01433                 VFILE_RegisterBuiltinFileBlob(bfb_DSXMENU_EXE_PC);
01434 
01435         DOS_PSP psp(psp_seg);
01436         psp.MakeNew(0);
01437         dos.psp(psp_seg);
01438    
01439         /* The start of the filetable in the psp must look like this:
01440          * 01 01 01 00 02
01441          * In order to achieve this: First open 2 files. Close the first and
01442          * duplicate the second (so the entries get 01) */
01443         Bit16u dummy=0;
01444         DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDIN  */
01445         DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDOUT */
01446         DOS_CloseFile(0);                                                       /* Close STDIN */
01447         DOS_ForceDuplicateEntry(1,0);                           /* "new" STDIN */
01448         DOS_ForceDuplicateEntry(1,2);                           /* STDERR */
01449         DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDAUX */
01450         if (!DOS_OpenFile("PRN",OPEN_READWRITE,&dummy)) DOS_OpenFile("CON",OPEN_READWRITE,&dummy);      /* STDPRN */
01451 
01452     psp.SetSize(psp_seg + total_sz);
01453     psp.SetStack(((unsigned int)stack_seg << 16u) + (unsigned int)reg_sp);
01454         psp.SetParent(psp_seg);
01455         /* Set the environment */
01456         psp.SetEnvironment(env_seg);
01457         /* Set the command line for the shell start up */
01458         CommandTail tail;
01459         tail.count=(Bit8u)strlen(init_line);
01460         memset(&tail.buffer, 0, CTBUF);
01461         strncpy(tail.buffer,init_line,CTBUF);
01462         MEM_BlockWrite(PhysMake(psp_seg,CTBUF+1),&tail,CTBUF+1);
01463         
01464         /* Setup internal DOS Variables */
01465         dos.dta(RealMake(psp_seg,CTBUF+1));
01466         dos.psp(psp_seg);
01467 
01468     /* settings */
01469     {
01470         const Section_prop * section=static_cast<Section_prop *>(control->GetSection("dos"));
01471         enable_config_as_shell_commands = section->Get_bool("shell configuration as commands");
01472     }
01473 }
01474 
01475 /* Pfff... starting and running the shell from a configuration section INIT
01476  * What the hell were you guys thinking? --J.C. */
01477 void SHELL_Run() {
01478         dos_shell_running_program = false;
01479 #if defined(WIN32) && !defined(C_SDL2)
01480         int Reflect_Menu(void);
01481         Reflect_Menu();
01482 #endif
01483 
01484         LOG(LOG_MISC,LOG_DEBUG)("Running DOS shell now");
01485 
01486         if (first_shell != NULL) E_Exit("Attempt to start shell when shell already running");
01487         SHELL_ProgramStart_First_shell(&first_shell);
01488 
01489         try {
01490                 first_shell->Run();
01491                 delete first_shell;
01492                 first_shell = 0;//Make clear that it shouldn't be used anymore
01493                 dos_shell_running_program = false;
01494 #if defined(WIN32) && !defined(C_SDL2)
01495                 int Reflect_Menu(void);
01496                 Reflect_Menu();
01497 #endif
01498         }
01499         catch (...) {
01500                 delete first_shell;
01501                 first_shell = 0;//Make clear that it shouldn't be used anymore
01502                 dos_shell_running_program = false;
01503 #if defined(WIN32) && !defined(C_SDL2)
01504                 int Reflect_Menu(void);
01505                 Reflect_Menu();
01506 #endif
01507                 throw;
01508         }
01509 }