/*
 *  Copyright (C) 2002-2021  The DOSBox Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <assert.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <gtest/gtest.h>

#include "dosbox.h"
#include "logging.h"
#include "regs.h"
#include "control.h"
#include "shell.h"
#include "menu.h"
#include "cpu.h"
#include "callback.h"
#include "dos_inc.h"
#include "support.h"
#include "mapper.h"
#include "render.h"
#include "jfont.h"
#include "../dos/drives.h"
#include "../ints/int10.h"
#include <unistd.h>
#include <time.h>
#include <string>
#include <sstream>
#include <vector>
#if defined(WIN32)
#include <windows.h>
#else
#include <dirent.h>
#endif
#include "build_timestamp.h"
#include "version_string.h"

/* if any step requires bringing up the command prompt for the CONFIG shell, set to true */
bool config_shell_run = false;
bool config_shell_prompt = false;
bool config_shell_prompt_start = false; // at start before running device drivers
bool config_shell_prompt_end = false; // at end after running device drivers

bool shown_welcome = false;

extern bool shell_keyboard_flush;
extern bool dos_kernel_shutdown_mcb;
extern bool dos_shell_running_program, mountwarning, winautorun;
extern bool startcmd, startwait, startquiet, internal_program;
extern bool addovl, addipx, addne2k, enableime, showdbcs;
extern bool halfwidthkana, force_conversion, gbk, uselangcp, chinasea;
extern const char* RunningProgram;
extern int enablelfn, msgcodepage, lastmsgcp;
extern uint16_t countryNo;
extern unsigned int dosbox_shell_env_size;
extern bool is_ttfswitched_on;
bool outcon = true, usecon = true, pipetmpdev = true;
bool shellrun = false, prepared = false, testerr = false;

char char_yes = 'y', char_no = 'n'; // YES NO CHARS in lower case

uint16_t shell_psp = 0;
Bitu call_int2e = 0;
Bitu call_int23 = 0;

std::string GetDOSBoxXPath(bool withexe=false);
const char* DOS_GetLoadedLayout(void);
Bitu DOS_ChangeKeyboardLayout(const char* layoutname, int32_t codepage), DOS_SwitchKeyboardLayout(const char* new_layout, int32_t& tried_cp);
Bitu DOS_LoadKeyboardLayout(const char* layoutname, int32_t codepage, const char* codepagefile);
int Reflect_Menu(void);
void SetIMPosition(void);
void SetKEYBCP();
void initRand();
#if !defined(OSFREE)
void initcodepagefont(void);
void get_yesno_chars(void);
#endif
void runMount(const char *str);
void ResolvePath(std::string& in);
void DOS_SetCountry(uint16_t countryNo);
bool SwitchLanguage(int oldcp, int newcp, bool confirm);
void CALLBACK_DeAllocate(Bitu in), DOSBox_ConsolePauseWait();
void GFX_SetTitle(int32_t cycles, int frameskip, Bits timing, bool paused);
bool isDBCSCP(), InitCodePage(), isKanji1(uint8_t chr), shiftjis_lead_byte(int c), sdl_wait_on_error(), CheckDBCSCP(int32_t codepage), TTF_using(void);
void makestdcp950table(), makeseacp951table();

#if defined(USE_TTF)
void ttf_switch_on(bool ss = true), ttf_switch_off(bool ss = true);
void ttf_setlines(int cols, int lins);
void ttf_reset();
#endif
extern VideoModeBlock* CurMode;

Bitu call_shellstop = 0;
/* Larger scope so shell_del autoexec can use it to
 * remove things from the environment */
DOS_Shell * first_shell = nullptr;

static Bitu shellstop_handler(void) {
	return CBRET_STOP;
}

void get_yesno_chars(void) {
    const char* yesno = MSG_Get("INT21_6523_YESNO_CHARS");
    char_yes = 'y', char_no = 'n';
    if(yesno && yesno[0] && yesno[1]) {
        unsigned char y = static_cast<unsigned char>(yesno[0]);
        unsigned char n = static_cast<unsigned char>(yesno[1]);

        if(y != n && isprint(static_cast<unsigned char>(y))
            && isprint(static_cast<unsigned char>(n)) &&
            y < 0x80 && n < 0x80) {
            if(y >= 'A' && y <= 'Z') y = y - 'A' + 'a';
            if(n >= 'A' && n <= 'Z') n = n - 'A' + 'a';
            char_yes = static_cast<char>(y);
            char_no = static_cast<char>(n);
        }
        else {
            char_yes = 'y';
            char_no = 'n';
        }
    }
}

void SHELL_ProgramStart(Program * * make) {
	*make = new DOS_Shell;
}
//Repeat it with the correct type, could do it in the function below, but this way it should be
//clear that if the above function is changed, this function might need a change as well.
static void SHELL_ProgramStart_First_shell(DOS_Shell * * make) {
	*make = new DOS_Shell;
}
#if !defined(OSFREE)
static void SHELL_ProgramStart_Config_shell(DOS_Shell * * make) {
	*make = new DOS_ConfigShell;
}
#endif

bool i4dos=false;
char i4dos_data[CONFIG_SIZE] = { 0 };
char config_data[CONFIG_SIZE] = { 0 };
char autoexec_data[AUTOEXEC_SIZE] = { 0 };
static std::list<std::string> autoexec_strings;
typedef std::list<std::string>::iterator auto_it;

void VFILE_Remove(const char *name,const char *dir="");
void runRescan(const char *str), DOSBox_SetSysMenu(void);
#if !defined(OSFREE)
int toSetCodePage(DOS_Shell *shell, int newCP, int opt);
#endif

#if defined(WIN32)
void MountAllDrives(bool quiet) {
    char str[100];
    uint16_t n = 0;
    uint32_t drives = GetLogicalDrives();
    char name[4]="A:\\";
    for (int i=0; i<25; i++) {
        if ((drives & (1<<i)) && !Drives[i])
        {
            name[0]='A'+i;
            int type=GetDriveType(name);
            if (type!=DRIVE_NO_ROOT_DIR) {
                if (!quiet) {
                    sprintf(str, "Mounting %c: => %s..\r\n", name[0], name);
                    n = (uint16_t)strlen(str);
                    DOS_WriteFile(STDOUT,(uint8_t *)str,&n);
                }
                char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
                name[2]=' ';
                strcpy(mountstring,name);
                name[2]='\\';
                strcat(mountstring,name);
                strcat(mountstring," -Q");
                runMount(mountstring);
                if (!Drives[i] && !quiet) {
                    sprintf(str, "Drive %c: failed to mount.\r\n",name[0]);
                    n = (uint16_t)strlen(str);
                    DOS_WriteFile(STDOUT,(uint8_t *)str,&n);
                } else if(mountwarning && !quiet && type==DRIVE_FIXED && (strcasecmp(name,"C:\\")==0)) {
                    strcpy(str, MSG_Get("PROGRAM_MOUNT_WARNING_WIN"));
                    if (strlen(str)>2&&str[strlen(str)-1]=='\n'&&str[strlen(str)-2]!='\r') {
                        str[strlen(str)-1]=0;
                        strcat(str, "\r\n");
                    }
                    n = (uint16_t)strlen(str);
                    DOS_WriteFile(STDOUT,(uint8_t *)str,&n);
                }
            }
        }
    }
}
#endif

void AutoexecObject::Install(const std::string &in) {
	if(GCC_UNLIKELY(installed)) E_Exit("autoexec: already created %s",buf.c_str());
	installed = true;
	buf = in;
	autoexec_strings.push_back(buf);
	this->CreateAutoexec();

	//autoexec.bat is normally created AUTOEXEC_Init.
	//But if we are already running (first_shell)
	//we have to update the environment to display changes

	if(first_shell)	{
		//create a copy as the string will be modified
		std::string::size_type n = buf.size();
		char* buf2 = new char[n + 1];
		safe_strncpy(buf2, buf.c_str(), n + 1);
		if((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
			char* after_set = buf2 + 4;//move to variable that is being set
			char* test2 = strpbrk(after_set,"=");
			if(!test2) {first_shell->SetEnv(after_set,"");return;}
			*test2++ = 0;
			//If the shell is running/exists update the environment
			first_shell->SetEnv(after_set,test2);
		}
		delete [] buf2;
	}
}

void AutoexecObject::InstallBefore(const std::string &in) {
	if(GCC_UNLIKELY(installed)) E_Exit("autoexec: already created %s",buf.c_str());
	installed = true;
	buf = in;
	autoexec_strings.push_front(buf);
	this->CreateAutoexec();
}

void AutoexecObject::CreateAutoexec(void) {
	/* Remove old autoexec.bat if the shell exists */
	if(first_shell)	VFILE_Remove("AUTOEXEC.BAT");

	//Create a new autoexec.bat
	autoexec_data[0] = 0;
	size_t auto_len;
	for(auto_it it = autoexec_strings.begin(); it != autoexec_strings.end(); ++it) {

		std::string linecopy = *it;
		std::string::size_type offset = 0;
		//Lets have \r\n as line ends in autoexec.bat.
		while(offset < linecopy.length()) {
			std::string::size_type  n = linecopy.find("\n",offset);
			if ( n == std::string::npos ) break;
			std::string::size_type rn = linecopy.find("\r\n",offset);
			if ( rn != std::string::npos && rn + 1 == n) {offset = n + 1; continue;}
			// \n found without matching \r
			linecopy.replace(n,1,"\r\n");
			offset = n + 2;
		}

		auto_len = strlen(autoexec_data);
		if ((auto_len+linecopy.length() + 3) > AUTOEXEC_SIZE) {
			E_Exit("SYSTEM:Autoexec.bat file overflow");
		}
		sprintf((autoexec_data + auto_len),"%s\r\n",linecopy.c_str());
	}
	if (first_shell) {
        internal_program = true;
        VFILE_Register("AUTOEXEC.BAT",(uint8_t *)autoexec_data,(uint32_t)strlen(autoexec_data));
        internal_program = false;
    }
}

void AutoexecObject::Uninstall() {
	if(!installed) return;

	// Remove the line from the autoexecbuffer and update environment
	for(auto_it it = autoexec_strings.begin(); it != autoexec_strings.end(); ) {
		if ((*it) == buf) {
			std::string::size_type n = buf.size();
			char* buf2 = new char[n + 1];
			safe_strncpy(buf2, buf.c_str(), n + 1);
			bool stringset = false;
			// If it's an environment variable remove it from there as well
			if ((strncasecmp(buf2,"set ",4) == 0) && (strlen(buf2) > 4)){
				char* after_set = buf2 + 4;//move to variable that is being set
				char* test2 = strpbrk(after_set,"=");
				if (!test2) {
					it++;
					delete [] buf2;
					continue;
				}
				*test2 = 0;
				stringset = true;
				//If the shell is running/exists update the environment
				if (first_shell) first_shell->SetEnv(after_set,"");
			}
			delete [] buf2;
			if (stringset && first_shell && first_shell->bf && first_shell->bf->filename.find("AUTOEXEC.BAT") != std::string::npos) {
				//Replace entry with spaces if it is a set and from autoexec.bat, as else the location counter will be off.
				*it = buf.assign(buf.size(),' ');
				++it;
			} else {
				it = autoexec_strings.erase(it);
			}
		} else ++it;
	}
	installed=false;
	this->CreateAutoexec();
}

AutoexecObject::~AutoexecObject(){
	Uninstall();
}

DOS_Shell::~DOS_Shell() {
	if (bf != NULL) delete bf; /* free batch file */

	/* shell termination is not handled like a normal program.
	 * memory allocated by the shell is not automatically freed on termination.
	 * files are not automatically closed */
	if (psp->GetSegment()) {
		/* BOOT will set the first MCB chain to zero to signal that low memory has been overwritten
		 * by the guest OS boot code */
		if (!dos_kernel_shutdown_mcb) {
			DOS_FreeProcessMemory(psp->GetSegment());

			/* NTS: DOS_PSP would ideally allow JFT handle operations regardless of whatever the
			 *      current PSP segment is, but that's not how the code is written */
			const uint16_t o_psp = dos.psp();
			dos.psp(psp->GetSegment());
			psp->CloseFiles();
			dos.psp(o_psp);
		}
	}

	if (psp->GetSegment() == shell_psp)
		shell_psp = 0;
}

DOS_Shell::DOS_Shell():Program(){
	input_handle=STDIN;
	echo=true;
	exit=false;
	perm = false;
	bf = nullptr;
	call=false;
	exec=false;
	lfnfor = uselfn;
	input_eof=false;
	completion_index = 0;
}

Bitu DOS_Shell::GetRedirection(char *s, char **ifn, char **ofn, char **toc,bool * append) {

	char * lr=s;
	char * lw=s;
	char ch;
	Bitu num=0;
	bool quote = false;
	bool lead1 = false, lead2 = false;
	char* t;
	int q;

	while ( (ch=*lr++) ) {
		if(quote && ch != '"') { /* don't parse redirection within quotes. Not perfect yet. Escaped quotes will mess the count up */
			*lw++ = ch;
			continue;
		}
        if (lead1) {
            lead1=false;
            if (ch=='|') {
                *lw++=ch;
                continue;
            }
        } else if ((IS_PC98_ARCH && shiftjis_lead_byte(ch)) || (isDBCSCP() && !((dos.loaded_codepage == 936 || IS_PDOSV) && !gbk) && isKanji1(ch))) lead1 = true;
		switch (ch) {
		case '"':
			quote = !quote;
			break;
		case '>':
			*append = ((*lr) == '>');
			if (*append)
				lr++;
			lr = ltrim(lr);
			if (*ofn)
				free(*ofn);
			*ofn = lr;
			q = 0;
			lead2 = false;
			while (*lr && (q/2*2!=q || *lr != ' ') && *lr != '<' && !(!lead2 && *lr == '|')) {
                if (lead2)
                    lead2 = false;
                else if ((IS_PC98_ARCH && shiftjis_lead_byte(*lr&0xff)) || (isDBCSCP() && !((dos.loaded_codepage == 936 || IS_PDOSV) && !gbk) && isKanji1(*lr&0xff)))
                    lead2 = true;
				if (*lr=='"')
					q++;
				lr++;
			}
			// if it ends on a : => remove it.
			if ((*ofn != lr) && (lr[-1] == ':'))
				lr[-1] = 0;
			t = (char*)malloc(lr-*ofn+1);
			safe_strncpy(t, *ofn, lr-*ofn+1);
			*ofn = t;
			continue;
		case '<':
			if (*ifn)
				free(*ifn);
			lr = ltrim(lr);
			*ifn = lr;
			q = 0;
			lead2 = false;
			while (*lr && (q/2*2!=q || *lr != ' ') && *lr != '>' && !(!lead2 && *lr == '|')) {
                if (lead2)
                    lead2 = false;
                else if ((IS_PC98_ARCH && shiftjis_lead_byte(*lr&0xff)) || (isDBCSCP() && !((dos.loaded_codepage == 936 || IS_PDOSV) && !gbk) && isKanji1(*lr&0xff)))
                    lead2 = true;
				if (*lr=='"')
					q++;
				lr++;
			}
			if ((*ifn != lr) && (lr[-1] == ':'))
				lr[-1] = 0;
			t = (char*)malloc(lr-*ifn+1);
			safe_strncpy(t, *ifn, lr-*ifn+1);
			*ifn = t;
			continue;
		case '|':
			num++;
			if (*toc)
				free(*toc);
			lr = ltrim(lr);
			*toc = lr;
			while (*lr)
				lr++;
			t = (char*)malloc(lr-*toc+1);
			safe_strncpy(t, *toc, lr-*toc+1);
			*toc = t;
			continue;
		}
		*lw++=ch;
	}
	*lw=0;
	return num;
}

class device_TMP : public DOS_Device {
public:
    std::string str = "";
    unsigned long long curpos = 0;
	device_TMP(char *name) { SetName(name); };
	bool Read(uint8_t * data,uint16_t * size) override {
        int i;
        for (i=0; i<*size; i++) {
            if (curpos+i>=str.size()) break;
            *data++ = str.substr(curpos++, 1)[0];
        }
		*size = i;
		return true;
	}
	bool Write(const uint8_t * data,uint16_t * size) override {
        for (int i=0; i<*size; i++) str += std::string(1, data[i]);
		return true;
	}
	bool Seek(uint32_t * pos,uint32_t type) override {
		switch (type) {
            case 0:
                curpos = *pos;
                break;
            case 1:
                curpos = curpos+*pos;
                break;
            case 2:
                curpos = str.size()+*pos;
                break;
            default:
                DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
                return false;
		}
		if (curpos > str.size()) curpos = str.size();
		return true;
	}
	bool Close() override { return true; }
	uint16_t GetInformation(void) override { return (strcmp(RunningProgram, "WCLIP") ? DeviceInfoFlags::Device : 0) | DeviceInfoFlags::EofOnInput; }
	bool ReadFromControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) override { (void)bufptr; (void)size; (void)retcode; return false; }
	bool WriteToControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) override { (void)bufptr; (void)size; (void)retcode; return false; }
};

void DOS_Shell::ParseLine(char * line) {
	LOG(LOG_EXEC,LOG_DEBUG)("Parsing command line: %s",line);
	/* Check for a leading @ */
	if (line[0] == '@') line[0] = ' ';
	line = trim(line);

	/* Do redirection and pipe checks */

	char * in  = nullptr;
	char * out = nullptr;
	char * toc = nullptr;

	uint16_t dummy,dummy2;
	uint32_t bigdummy = 0;
	bool append;
	bool normalstdin  = false;	/* whether stdin/out are open on start. */
	bool normalstdout = false;	/* Bug: Assumed is they are "con"      */

	GetRedirection(line, &in, &out, &toc, &append);

	if(toc && *trim(toc) == '\0') {
		SyntaxError(); /* No command to pass output */
		if(in) free(in);
		if(out) free(out);
		if(toc) free(toc);
		return;
	}

	if (in || out || toc) {
		normalstdin  = (psp->GetFileHandle(0) != 0xff);
		normalstdout = (psp->GetFileHandle(1) != 0xff);
	}
	if (in) {
		if(DOS_OpenFile(in,OPEN_READ,&dummy)) {	//Test if file exists
			DOS_CloseFile(dummy);
			LOG_MSG("SHELL:Redirect input from %s",in);
			if(normalstdin) DOS_CloseFile(0);	//Close stdin
			DOS_OpenFile(in,OPEN_READ,&dummy);	//Open new stdin
		} else {
			WriteOut(!*in?"File open error\n":(dos.errorcode==DOSERR_ACCESS_DENIED?MSG_Get("SHELL_CMD_FILE_ACCESS_DENIED"):"File open error - %s\n"), in);
			if(in) free(in);
			if(out) free(out);
			if(toc) free(toc);
			return;
		}
	}
	bool fail=false;
	char pipetmp[270];
	uint16_t fattr;
	if(toc) {
		// Initialize random number generator
		initRand();

		// Try to get TEMP or TMP environment variable
		std::string tempPath;
		if(!GetEnvStr("TEMP", tempPath) && !GetEnvStr("TMP", tempPath)) {
			// Fallback: use current drive root as fallback directory (e.g., "C:\")
			char currentDrive = DOS_GetDefaultDrive() + 'A';
			tempPath = std::string(1, currentDrive) + ":\\";
		}
		else {
			// Extract directory from environmental variable
			std::string::size_type idx = tempPath.find('=');
			if(idx != std::string::npos)
				tempPath = tempPath.substr(idx + 1);
		}

		// Ensure the path ends with backslash
		if(!tempPath.empty() && tempPath.back() != '\\') {
			tempPath += '\\';
		}

		// Check if directory is valid
		uint16_t fattr;
		if(!(DOS_GetFileAttr(tempPath.c_str(), &fattr) && (fattr & DOS_ATTR_DIRECTORY))) {
			// Fallback to current drive root again if directory is invalid
			char currentDrive = DOS_GetDefaultDrive() + 'A';
			tempPath = std::string(1, currentDrive) + ":\\";
		}

		// Assign to tempEnv after fixing the path
		const char* tempEnv = tempPath.c_str();

		// Generate unique pipe file path
		int pipeid = rand() % 10000;
		snprintf(pipetmp, sizeof(pipetmp), "%spipe%d.tmp", tempEnv, pipeid);
	}

	DOS_Device *tmpdev = NULL;
	if (out||toc) {
		if (out&&toc)
			WriteOut(!*out?"Duplicate redirection\n":"Duplicate redirection - %s\n", out);
		LOG_MSG("SHELL:Redirect output to %s",toc?pipetmp:out);
		if(normalstdout) DOS_CloseFile(1);
		if(!normalstdin && !in) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
		bool status = true;
		/* Create if not exist. Open if exist. Both in read/write mode */
		if(!toc&&append) {
			if (DOS_GetFileAttr(out, &fattr) && fattr&DOS_ATTR_READ_ONLY) {
				DOS_SetError(DOSERR_ACCESS_DENIED);
				status = false;
			} else if( (status = DOS_OpenFile(out,OPEN_READWRITE,&dummy)) ) {
				DOS_SeekFile(1,&bigdummy,DOS_SEEK_END);
			} else {
				status = DOS_CreateFile(out,DOS_ATTR_ARCHIVE,&dummy);	//Create if not exists.
			}
		} else if (!toc&&DOS_GetFileAttr(out, &fattr) && fattr&DOS_ATTR_READ_ONLY) {
			DOS_SetError(DOSERR_ACCESS_DENIED);
			status = false;
		} else {
			bool device=DOS_FindDevice(pipetmp)!=DOS_DEVICES;
			if (toc&&!device&&DOS_FindFirst(pipetmp, ~DOS_ATTR_VOLUME)&&!DOS_UnlinkFile(pipetmp))
				fail=true;
			status = device?false:DOS_OpenFileExtended(toc&&!fail?pipetmp:out,OPEN_READWRITE,DOS_ATTR_ARCHIVE,0x12,&dummy,&dummy2);
			bool pipetmp_is_zdrive = strncasecmp(pipetmp, "Z:\\", 3) == 0;
			if (toc&&(fail||!status)&&(!strchr(pipetmp,'\\')|| pipetmp_is_zdrive)) {
				Overlay_Drive *da = Drives[0] ? (Overlay_Drive *)Drives[0] : NULL, *dc = Drives[2] ? (Overlay_Drive *)Drives[2] : NULL;
				if (!pipetmp_is_zdrive && ((Drives[0]&&!Drives[0]->readonly&&!(da&&da->ovlreadonly))||(Drives[2]&&!Drives[2]->readonly&&!(dc&&dc->ovlreadonly)))) {
					int len = (int)strlen(pipetmp);
					if(len > 266) {
						len = 266;
						pipetmp[len] = 0;
					}
					for(int i = len; i >= 0; i--)
						pipetmp[i + 3] = pipetmp[i];
					pipetmp[0] = Drives[2] ? 'c' : 'a';
					pipetmp[1] = ':';
					pipetmp[2] = '\\';
					fail = false;
				} else if (!tmpdev && pipetmpdev) {
					char* filename_only = strrchr(pipetmp, '\\');
					if(!filename_only) filename_only = pipetmp;
					else filename_only++;
					char tmpname[270];
					strncpy(tmpname, filename_only, sizeof(tmpname));
					tmpname[sizeof(tmpname) - 1] = 0;

					char* p = strchr(tmpname, '.');
					if(p) *p = 0;

					tmpdev = new device_TMP(tmpname);
					if(p) *p = '.';

					if(tmpdev) {
						DOS_AddDevice(tmpdev);
						fail = false;
					}
				} else
					fail=true;
				if (!tmpdev && DOS_FindFirst(pipetmp, ~DOS_ATTR_VOLUME) && !DOS_UnlinkFile(pipetmp))
					fail=true;
				else
					status = DOS_OpenFileExtended(pipetmp, OPEN_READWRITE, DOS_ATTR_ARCHIVE, 0x12, &dummy, &dummy2);
			}
		}
		if(!status && normalstdout) {
			DOS_OpenFile("con", OPEN_READWRITE, &dummy);							// Read only file, open con again
			if (!toc) {
				WriteOut(!*out?"File creation error\n":(dos.errorcode==DOSERR_ACCESS_DENIED?MSG_Get("SHELL_CMD_FILE_ACCESS_DENIED"):"File creation error - %s\n"), out);
				DOS_CloseFile(1);
				DOS_OpenFile("nul", OPEN_READWRITE, &dummy);
			}
		}
		if(!normalstdin && !in) DOS_CloseFile(0);
	}
	/* Run the actual command */

	if (this == first_shell) dos_shell_running_program = true;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
	Reflect_Menu();
#endif
	if (toc||(!toc&&((out&&DOS_FindDevice(out)!=DOS_FindDevice("con"))))) outcon=false;
	if (toc||(!toc&&((out&&DOS_FindDevice(out)!=DOS_FindDevice("con"))||(in&&DOS_FindDevice(in)!=DOS_FindDevice("con"))))) usecon=false;

	DoCommand(line);

	if (this == first_shell) dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
	Reflect_Menu();
#endif

	/* Restore handles */
	if(in) {
		DOS_CloseFile(0);
		if(normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
		free(in);
	}
	if(out||toc) {
		DOS_CloseFile(1);
		if(!normalstdin) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
		if(normalstdout) DOS_OpenFile("con",OPEN_READWRITE,&dummy);
		if(!normalstdin) DOS_CloseFile(0);
		if (out) free(out);
	}
	if (toc) {
		if(tmpdev != nullptr) {
			std::string path(pipetmp);
			size_t lastSlash = path.find_last_of("\\/");
			if(lastSlash != std::string::npos)
				path = path.substr(lastSlash + 1);
			size_t dot = path.find_last_of('.');
			if(dot != std::string::npos)
				path = path.substr(0, dot);
			strncpy(pipetmp, path.c_str(), sizeof(pipetmp) - 1);
			pipetmp[sizeof(pipetmp) - 1] = '\0';
		}
		if (!fail&&DOS_OpenFile(pipetmp, OPEN_READ, &dummy))					// Test if file can be opened for reading
		{
			DOS_CloseFile(dummy);
			if (normalstdin)
				DOS_CloseFile(0);												// Close stdin
			DOS_OpenFile(pipetmp, OPEN_READ, &dummy);							// Open new stdin
			ParseLine(toc);
			DOS_CloseFile(0);
			if (normalstdin)
				DOS_OpenFile("con", OPEN_READWRITE, &dummy);
		}
		else
			WriteOut("\nFailed to create/open a temporary file for piping. Check the %%TEMP%% variable.\n");
		free(toc);
		if (tmpdev) {
			DOS_DelDevice(tmpdev);
			tmpdev = NULL;
		} else if (DOS_FindFirst(pipetmp, ~DOS_ATTR_VOLUME)) DOS_UnlinkFile(pipetmp);
	}
	outcon=usecon=true;
}

void DOS_Shell::RunInternal(void) {
	char input_line[CMD_MAXLINE] = {0};
	while (bf) {
		if (bf->ReadLine(input_line)) {
			if (echo) {
				if (input_line[0] != '@') {
					ShowPrompt();
					WriteOut_NoParsing(input_line);
					WriteOut_NoParsing("\n");
				}
			}
			ParseLine(input_line);
			if (echo) WriteOut_NoParsing("\n");
		}
	}
}

bool ansiinstalled = true, ANSI_SYS_installed();
extern void ReadCharAttr(uint16_t col,uint16_t row,uint8_t page,uint16_t * result);
bool is_ANSI_installed(Program *shell) {
    if (ANSI_SYS_installed()) return true;
    uint16_t oldax=reg_ax;
    if (CurMode->type == M_TEXT) {
        shell->WriteOut("-\033[2J=+");
        uint8_t page = real_readb(BIOSMEM_SEG, BIOSMEM_CURRENT_PAGE);
        uint16_t result1, result2;
        ReadCharAttr(0,0,page,&result1);
        ReadCharAttr(1,0,page,&result2);
        bool installed = (uint8_t)result1=='=' && (uint8_t)result2=='+';
        if (installed) {
            shell->WriteOut("\033[2J");
            return true;
        }
        reg_ax = (uint16_t)CurMode->mode;
        CALLBACK_RunRealInt(0x10);
        reg_ax=oldax;
    }
    reg_ax=0x1a00;
    CALLBACK_RunRealInt(0x2F);
    if (reg_al!=0xff) {reg_ax=oldax;return false;}
    reg_ax=oldax;
    return true;
}

std::string GetPlatform(bool save);
char *str_replace(const char *orig, const char *rep, const char *with);
const char *ParseMsg(const char *msg) {
    char str[13];
    strncpy(str, UPDATED_STR, 12);
    str[12]=0;
    if (machine != MCH_PC98) {
        if (!ansiinstalled || J3_IsJapanese()) {
            msg = str_replace(str_replace(msg, "\033[0m", ""), "\033[1m", "");
            for (int i=1; i<8; i++) {
                sprintf(str, "\033[3%dm", i);
                msg = str_replace(msg, str, "");
                sprintf(str, "\033[4%d;1m", i);
                msg = str_replace(msg, str, "");
            }
        }
        Section_prop *section = static_cast<Section_prop *>(control->GetSection("dosbox"));
        std::string theme = section->Get_string("bannercolortheme");
        if (theme == "black")
            msg = str_replace(msg, "\033[44;1m", "\033[40;1m");
        else if (theme == "red")
            msg = str_replace(str_replace(msg, "\033[31m", "\033[34m"), "\033[44;1m", "\033[41;1m");
        else if (theme == "green")
            msg = str_replace(str_replace(str_replace(msg, "\033[36m", "\033[34m"), "\033[32m", "\033[36m"), "\033[44;1m", "\033[42;1m");
        else if (theme == "yellow")
            msg = str_replace(str_replace(msg, "\033[31m", "\033[34m"), "\033[44;1m", "\033[43;1m");
        else if (theme == "blue")
            msg = str_replace(msg, "\033[44;1m", "\033[44;1m");
        else if (theme == "magenta")
            msg = str_replace(str_replace(msg, "\033[31m", "\033[34m"), "\033[44;1m", "\033[45;1m");
        else if (theme == "cyan")
            msg = str_replace(str_replace(msg, "\033[36m", "\033[34m"), "\033[44;1m", "\033[46;1m");
        else if (theme == "white")
            msg = str_replace(str_replace(msg, "\033[36m", "\033[34m"), "\033[44;1m", "\033[47;1m");
    }
    if (machine == MCH_PC98)
        return msg;
    else {
        if (real_readw(BIOSMEM_SEG,BIOSMEM_NB_COLS)>80)
            msg = str_replace(str_replace(str_replace(msg, "\xBA\033[0m", "\xBA\033[0m\n"), "\xBB\033[0m", "\xBB\033[0m\n"), "\xBC\033[0m", "\xBC\033[0m\n");
        bool uselowbox = false;
        force_conversion = true;
        int cp=dos.loaded_codepage;
        if ((showdbcs
#if defined(USE_TTF)
        || ttf.inUse
#endif
        )
#if defined(USE_TTF)
        && halfwidthkana
#endif
        && dos.loaded_codepage==932) uselowbox = true;
        force_conversion = false;
        dos.loaded_codepage=cp;
        if (uselowbox || IS_JEGA_ARCH || IS_JDOSV) {
            std::string m=msg;
            if (strstr(msg, "\xCD\xCD\xCD\xCD") != NULL) {
                if (IS_JEGA_ARCH) {
                    msg = str_replace(msg, "\xC9", std::string(1, 0x15).c_str());
                    msg = str_replace(msg, "\xBB", std::string(1, 0x16).c_str());
                    msg = str_replace(msg, "\xC8", std::string(1, 0x18).c_str());
                    msg = str_replace(msg, "\xBC", std::string(1, 0x17).c_str());
                    msg = str_replace(msg, "\xCD", std::string(1, 0x13).c_str());
                } else if(J3_IsJapanese()) {
                    msg = str_replace(msg, "\xC9", "+");
                    msg = str_replace(msg, "\xBB", "+");
                    msg = str_replace(msg, "\xC8", "+");
                    msg = str_replace(msg, "\xBC", "+");
                    msg = str_replace(msg, "\xCD", "-");
                } else {
                    msg = str_replace(msg, "\xC9", std::string(1, 1).c_str());
                    msg = str_replace(msg, "\xBB", std::string(1, 2).c_str());
                    msg = str_replace(msg, "\xC8", std::string(1, 3).c_str());
                    msg = str_replace(msg, "\xBC", std::string(1, 4).c_str());
                    msg = str_replace(msg, "\xCD", std::string(1, 6).c_str());
                }
            } else {
                if (IS_JEGA_ARCH) {
                    msg = str_replace(msg, "\xBA ", (std::string(1, 0x14)+" ").c_str());
                    msg = str_replace(msg, " \xBA", (" "+std::string(1, 0x14)).c_str());
                } else if(J3_IsJapanese()) {
                    msg = str_replace(msg, "\xBA ", "| ");
                    msg = str_replace(msg, " \xBA", " |");
                } else {
                    msg = str_replace(msg, "\xBA ", (std::string(1, 5)+" ").c_str());
                    msg = str_replace(msg, " \xBA", (" "+std::string(1, 5)).c_str());
                }
            }
        }
        return msg;
    }
}

static char const * const path_string="PATH=Z:\\;Z:\\SYSTEM;Z:\\BIN;Z:\\DOS;Z:\\4DOS;Z:\\DEBUG;Z:\\TEXTUTIL;Z:\\PATCHING";
static char const * const comspec_string="COMSPEC=Z:\\COMMAND.COM";
static char const * const prompt_string_config="PROMPT=CONFIG:$P$G";
static char const * const prompt_string="PROMPT=$P$G";
static char const * const full_name="Z:\\COMMAND.COM";
static char const * const init_line="/INIT AUTOEXEC.BAT";
extern uint8_t ZDRIVE_NUM;

char *str_replace(const char *orig, const char *rep, const char *with);
void GetExpandedPath(std::string &path) {
    std::string udrive = std::string(1,ZDRIVE_NUM+'A'), ldrive = std::string(1,ZDRIVE_NUM+'a');
    char pathstr[100];
    strcpy(pathstr, path_string+5);
    if (path==udrive+":\\"||path==ldrive+":\\")
        path=ZDRIVE_NUM==25?pathstr:str_replace(pathstr, "Z:\\", (udrive+":\\").c_str());
    else if (path.size()>3&&(path.substr(0, 4)==udrive+":\\;"||path.substr(0, 4)==ldrive+":\\;")&&path.substr(4).find(udrive+":\\")==std::string::npos&&path.substr(4).find(ldrive+":\\")==std::string::npos)
        path=std::string(ZDRIVE_NUM==25?pathstr:str_replace(pathstr, "Z:\\", (udrive+":\\").c_str()))+path.substr(3);
    else if (path.size()>3) {
        size_t pos = path.find(";"+udrive+":\\");
        if (pos == std::string::npos) pos = path.find(";"+ldrive+":\\");
        if (pos != std::string::npos && (!path.substr(pos+4).size() || (path[pos+4]==';'&&path.substr(pos+4).find(udrive+":\\")==std::string::npos&&path.substr(pos+4).find(ldrive+":\\")==std::string::npos)))
            path=path.substr(0, pos+1)+std::string(ZDRIVE_NUM==25?pathstr:str_replace(pathstr, "Z:\\", (udrive+":\\").c_str()))+path.substr(pos+4);
    }
}

void showWelcome(Program *shell) {
    /* Start a normal shell and check for a first command init */
    ansiinstalled = is_ANSI_installed(shell);
    std::string verstr = "v"+std::string(VERSION)+", "+OS_PLATFORM_LONG+" "+SDL_STRING+" " + OS_BIT + "-bit";
    if (machine == MCH_PC98) {
        shell->WriteOut(ParseMsg("\x86\x52\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x56\n"));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 \033[32m")+(MSG_Get("SHELL_STARTUP_TITLE")+std::string("      ")).substr(0,22)+std::string("  \033[33m%*s\033[37m \x86\x46\n")).c_str()),42,verstr.c_str());
        shell->WriteOut(ParseMsg("\x86\x46                                                                    \x86\x46\n"));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 ")+MSG_Get("SHELL_STARTUP_HEAD1_PC98")+std::string(" \x86\x46\n")).c_str()));
        shell->WriteOut(ParseMsg("\x86\x46                                                                    \x86\x46\n"));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 ")+str_replace(MSG_Get("SHELL_STARTUP_TEXT1_PC98"), "\n", " \x86\x46\n\x86\x46 ")+std::string(" \x86\x46\n")).c_str()));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 ")+MSG_Get("SHELL_STARTUP_EXAMPLE_PC98")+std::string(" \x86\x46\n")).c_str()));
        shell->WriteOut(ParseMsg("\x86\x46                                                                    \x86\x46\n"));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 ")+str_replace(MSG_Get("SHELL_STARTUP_TEXT2_PC98"), "\n", " \x86\x46\n\x86\x46 ")+std::string(" \x86\x46\n")).c_str()));
        shell->WriteOut(ParseMsg("\x86\x46                                                                    \x86\x46\n"));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 ")+str_replace(MSG_Get("SHELL_STARTUP_INFO_PC98"), "\n", " \x86\x46\n\x86\x46 ")+std::string(" \x86\x46\n")).c_str()));
        shell->WriteOut(ParseMsg("\x86\x46                                                                    \x86\x46\n"));
        shell->WriteOut(ParseMsg((std::string("\x86\x46 ")+str_replace(MSG_Get("SHELL_STARTUP_TEXT3_PC98"), "\n", " \x86\x46\n\x86\x46 ")+std::string(" \x86\x46\n")).c_str()));
        shell->WriteOut(ParseMsg("\x86\x5A\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44"
            "\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x44\x86\x5E\033[0m\n"));
        shell->WriteOut(ParseMsg((std::string("\033[1m\033[32m")+MSG_Get("SHELL_STARTUP_LAST")+"\033[0m\n").c_str()));
    } else {
        shell->WriteOut(ParseMsg("\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\xCD"
            "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
            "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBB\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA \033[32m")+(MSG_Get("SHELL_STARTUP_TITLE")+std::string("             ")).substr(0,30)+std::string(" \033[33m%*s\033[37m \xBA\033[0m")).c_str()),45,verstr.c_str());
        shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+MSG_Get("SHELL_STARTUP_HEAD1")+std::string(" \xBA\033[0m")).c_str()));
        shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+str_replace(MSG_Get("SHELL_STARTUP_TEXT1"), "\n", " \xBA\033[0m\033[44;1m\xBA ")+std::string(" \xBA\033[0m")).c_str()));
        if (IS_VGA_ARCH) shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+MSG_Get("SHELL_STARTUP_EXAMPLE")+std::string(" \xBA\033[0m")).c_str()));
        shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+MSG_Get("SHELL_STARTUP_HEAD2")+std::string(" \xBA\033[0m")).c_str()));
        shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+str_replace(MSG_Get("SHELL_STARTUP_TEXT2"), "\n", " \xBA\033[0m\033[44;1m\xBA ")+std::string(" \xBA\033[0m")).c_str()));
        shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        if (IS_DOSV) {
            shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+str_replace(MSG_Get("SHELL_STARTUP_DOSV"), "\n", " \xBA\033[0m\033[44;1m\xBA ")+std::string(" \xBA\033[0m")).c_str()));
            shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        } else if (machine == MCH_CGA || machine == MCH_PCJR || machine == MCH_AMSTRAD) {
            shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+str_replace(MSG_Get(mono_cga?"SHELL_STARTUP_CGA_MONO":"SHELL_STARTUP_CGA"), "\n", " \xBA\033[0m\033[44;1m\xBA ")+std::string(" \xBA\033[0m")).c_str()));
            shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        } else if (machine == MCH_HERC || machine == MCH_MDA) {
            shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+str_replace(MSG_Get("SHELL_STARTUP_HERC"), "\n", " \xBA\033[0m\033[44;1m\xBA ")+std::string(" \xBA\033[0m")).c_str()));
            shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        }
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+MSG_Get("SHELL_STARTUP_HEAD3")+std::string(" \xBA\033[0m")).c_str()));
        shell->WriteOut(ParseMsg("\033[44;1m\xBA                                                                              \xBA\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[44;1m\xBA ")+str_replace(MSG_Get("SHELL_STARTUP_TEXT3"), "\n", " \xBA\033[0m\033[44;1m\xBA ")+std::string(" \xBA\033[0m")).c_str()));
        shell->WriteOut(ParseMsg("\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\xCD"
            "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD"
            "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC\033[0m"));
        shell->WriteOut(ParseMsg((std::string("\033[32m")+(MSG_Get("SHELL_STARTUP_LAST")+std::string("                                                       ")).substr(0,79)+std::string("\033[0m\n")).c_str()));
    }
}

bool finish_prepare = false;
void change_output(int type);
bool setColors(const char* colorArray, int n);
extern bool switch_to_d3d11_on_startup;
extern bool switch_to_metal_on_startup;

void DOS_Shell::Prepare(void) {
	if (this == first_shell) {
#if C_DIRECT3D && C_SDL2
		if(switch_to_d3d11_on_startup){
			switch_to_d3d11_on_startup = false;
			change_output(13);
		}
#endif
#if defined(MACOSX) && defined(C_SDL2) && C_METAL
		if(switch_to_metal_on_startup){
			switch_to_metal_on_startup = false;
			change_output(14);
		}
#endif
#if defined(USE_TTF)
		if(CurMode->type == M_TEXT || (IS_PC98_ARCH && is_ttfswitched_on)) ttf_switch_on(true); // Initialization completed, M_TEXT modes can switch to TTF mode from now on.
		if(ttf.inUse) {
			int cols = static_cast<Section_prop*>(control->GetSection("ttf"))->Get_int("cols");
			int lins = static_cast<Section_prop*>(control->GetSection("ttf"))->Get_int("lins");
			if(cols || lins) ttf_setlines(cols, lins);
			if(is_ttfswitched_on){
				const char* colors = static_cast<Section_prop*>(control->GetSection("ttf"))->Get_string("colors");
				if(*colors && !setColors(colors, -1)) {
					LOG_MSG("Incorrect color scheme: %s", colors);
				}
				is_ttfswitched_on = false;
			}
		}
#endif
#if !defined(OSFREE)
		get_yesno_chars();

		const char* layoutname = DOS_GetLoadedLayout();
		if(layoutname == NULL && !IS_PC98_ARCH) {/*Keyboard layouts and CPI/CPX files have no meaning in PC-98 mode*/
			int32_t cp = dos.loaded_codepage;
			DOS_LoadKeyboardLayout("us", 437, "auto");
			toSetCodePage(NULL, cp, -1);
		}
#endif
		Section_prop *section = static_cast<Section_prop *>(control->GetSection("dosbox"));
		bool startbanner = section->Get_bool("startbanner");
		first_shell->perm = section->Get_bool("shell permanent");
#if !defined(OSFREE)
		if (!countryNo) {
# if defined(WIN32)
			char buffer[128];
# endif
			if (IS_PC98_ARCH || IS_JEGA_ARCH)
				countryNo = 81;
			else if (IS_DOSV)
				countryNo = IS_PDOSV?86:(IS_TDOSV?886:(IS_KDOSV?82:81));
# if defined(WIN32)
			else if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICOUNTRY, buffer, 128)) {
				countryNo = uint16_t(atoi(buffer));
				DOS_SetCountry(countryNo);
			}
# endif
			else {
				const char *layout = DOS_GetLoadedLayout();
				if (layout == NULL)
					countryNo = COUNTRYNO::United_States;
				else if (country_code_map.find(layout) != country_code_map.end())
					countryNo = country_code_map.find(layout)->second;
				else
					countryNo = COUNTRYNO::United_States;
				DOS_SetCountry(countryNo);
			}
		}
#endif
		section = static_cast<Section_prop *>(control->GetSection("dos"));
		bool zdirpath = section->Get_bool("drive z expand path");
		std::string layout = section->Get_string("keyboardlayout");
		strcpy(config_data, "");
		section = static_cast<Section_prop *>(control->GetSection("config"));
#if !defined(OSFREE)
		if ((section!=NULL&&!control->opt_noconfig)||control->opt_langcp) {
			char *countrystr = (char *)section->Get_string("country"), *r=strchr(countrystr, ',');
			int country = 0;
			int32_t newCP = dos.loaded_codepage;
			if((control->opt_langcp && msgcodepage > 0) || CheckDBCSCP(msgcodepage) || msgcodepage == dos.loaded_codepage) newCP = msgcodepage;
			if ((r==NULL || !*(r+1)) && !control->opt_langcp)
				country = atoi(trim(countrystr));
			else if(!msgcodepage){
				if (r!=NULL) *r=0;
				country = atoi(trim(countrystr));
				newCP = r==NULL||IS_PC98_ARCH||IS_JEGA_ARCH||IS_DOSV?dos.loaded_codepage:atoi(trim(r+1));
				if (r!=NULL) *r=',';
			}
			if (newCP != dos.loaded_codepage && (!TTF_using() || (TTF_using() && isSupportedCP(newCP)))) {
				toSetCodePage(this, newCP, -1);
			}
			if (country>0&&!control->opt_noconfig) {
				countryNo = country;
				DOS_SetCountry(countryNo);
			}
			if(!chinasea)makestdcp950table();
			if(chinasea) makeseacp951table();
			InitCodePage();
			if(startbanner && !control->opt_fastlaunch) {
				if (!shown_welcome) {
					//showWelcome(this);
					DoCommand((char *)std::string("z:\\system\\intro welcome").c_str());
					shown_welcome = true;
				}
			}
			else if((CurMode->type == M_TEXT || IS_PC98_ARCH) && ANSI_SYS_installed()) {
				WriteOut("\033[2J");
			}
			const char * extra = section->data.c_str();
			if (extra&&!control->opt_securemode&&!control->SecureMode()&&!control->opt_noconfig) {
				std::string vstr;
				std::istringstream in(extra);
				char linestr[CROSS_LEN+1], cmdstr[CROSS_LEN], valstr[CROSS_LEN], tmpstr[CROSS_LEN];
				char *cmd=cmdstr, *val=valstr, *tmp=tmpstr, *p;
				if (in)	for (std::string line; std::getline(in, line); ) {
					if (line.length()>CROSS_LEN) {
						strncpy(linestr, line.c_str(), CROSS_LEN);
						linestr[CROSS_LEN]=0;
					} else
						strcpy(linestr, line.c_str());
					p=strchr(linestr, '=');
					if (p!=NULL) {
						*p=0;
						strcpy(cmd, linestr);
						strcpy(val, p+1);
						cmd=trim(cmd);
						val=trim(val);
						if (strlen(config_data)+strlen(cmd)+strlen(val)+3<CONFIG_SIZE) {
							strcat(config_data, cmd);
							strcat(config_data, "=");
							strcat(config_data, val);
							strcat(config_data, "\r\n");
						}
						if (!strncasecmp(cmd, "set ", 4)) {
							vstr=std::string(val);
							ResolvePath(vstr);
							if (zdirpath && !strcmp(cmd, "set path")) GetExpandedPath(vstr);
							DoCommand((char *)(std::string(cmd)+"="+vstr).c_str());
						} else if (!strcasecmp(cmd, "install")||!strcasecmp(cmd, "installhigh")||!strcasecmp(cmd, "device")||!strcasecmp(cmd, "devicehigh")) {
							vstr=std::string(val);
							ResolvePath(vstr);
							strcpy(tmp, vstr.c_str());
							char *name=StripArg(tmp);
							if (!*name) continue;
							if (!DOS_FileExists(name)&&!DOS_FileExists((std::string("Z:\\SYSTEM\\")+name).c_str())&&!DOS_FileExists((std::string("Z:\\BIN\\")+name).c_str())&&!DOS_FileExists((std::string("Z:\\DOS\\")+name).c_str())&&!DOS_FileExists((std::string("Z:\\4DOS\\")+name).c_str())&&!DOS_FileExists((std::string("Z:\\DEBUG\\")+name).c_str())&&!DOS_FileExists((std::string("Z:\\TEXTUTIL\\")+name).c_str())) {
								WriteOut(MSG_Get("SHELL_MISSING_FILE"), name);
								continue;
							}
							if (!strcasecmp(cmd, "install"))
								DoCommand((char *)vstr.c_str());
							else if (!strcasecmp(cmd, "installhigh"))
								DoCommand((char *)("lh "+vstr).c_str());
							else if (!strcasecmp(cmd, "device"))
								DoCommand((char *)("device "+vstr).c_str());
							else if (!strcasecmp(cmd, "devicehigh"))
								DoCommand((char *)("lh device "+vstr).c_str());
						}
					} else if (!strncasecmp(line.c_str(), "rem ", 4)) {
						strcat(config_data, line.c_str());
						strcat(config_data, "\r\n");
					}
				}
			}
		}
#else
		WriteOut("\x1B[2J"); /* erase screen */
		WriteOut("\x1B[H"); /* home cursor */
#endif
		std::string line;
		GetEnvStr("PATH",line);
		if (!strlen(config_data)) {
			strcat(config_data, "rem=");
			strcat(config_data, section->Get_string("rem"));
			strcat(config_data, "\r\n");
		}
#if !defined(OSFREE)
		if(dos.loaded_codepage == 932) toSetCodePage(this, 932, -1); // Workaround for corrupted box-drawing characters
#endif
		runRescan("-A -Q");
		internal_program = true;
		VFILE_Register("AUTOEXEC.BAT",(uint8_t *)autoexec_data,(uint32_t)strlen(autoexec_data));
#if !defined(OSFREE)
		VFILE_Register("CONFIG.SYS",(uint8_t *)config_data,(uint32_t)strlen(config_data));
#endif
		internal_program = false;
#if defined(WIN32)
		if (!control->opt_securemode&&!control->SecureMode())
		{
			const Section_prop* sec = static_cast<Section_prop*>(control->GetSection("dos"));
			const char *automountstr = sec->Get_string("automountall");
			if (strcmp(automountstr, "0") && strcmp(automountstr, "false")) MountAllDrives(!strcmp(automountstr, "quiet")||control->opt_fastlaunch);
		}
#endif
		strcpy(i4dos_data, "");
		section = static_cast<Section_prop *>(control->GetSection("4dos"));
		if (section!=NULL) {
			const char * extra = section->data.c_str();
			if (extra) {
				std::istringstream in(extra);
				if (in)	for (std::string line; std::getline(in, line); ) {
					if (strncasecmp(line.c_str(), "rem=", 4)&&strncasecmp(line.c_str(), "rem ", 4)) {
						strcat(i4dos_data, line.c_str());
						strcat(i4dos_data, "\r\n");
					}
				}
			}
		}
		internal_program = true;
		VFILE_Register("4DOS.INI",(uint8_t *)i4dos_data,(uint32_t)strlen(i4dos_data), "/4DOS/");
		internal_program = false;
		//unsigned int cp=dos.loaded_codepage;
		//if (!dos.loaded_codepage) InitCodePage();
		//initcodepagefont();
		//dos.loaded_codepage=cp;
		finish_prepare = true;
	}
#if (defined(WIN32) && !defined(HX_DOS) || defined(LINUX) && C_X11 || defined(MACOSX)) && (defined(C_SDL2) || defined(SDL_DOSBOX_X_SPECIAL))
	if (enableime) SetIMPosition();
#endif
}

#if defined(OSFREE)
bool DOS_Shell::OSFreeOperatingSystemNotFound(void) {
	uint16_t n;
	uint8_t c;

	WriteOut("\n");
	WriteOut("Operating System Not Found.\n\n");
	WriteOut("Press ENTER for more information, ESC to exit.\n");

	do {
		n=1;c=0;
		if (!DOS_ReadFile(0/*STDIN*/,&c,&n)) break;

		if (c == 0x04/*CTRL+D secret drop to command line--though you're not going to find much!*/) return true;
		if (c == 27/*ESC*/) return false;
		if (c == 13/*ENTER*/) break;
	} while(1);

	WriteOut("\n");
	WriteOut("This version was built without MS-DOS emulation.\n");
	WriteOut("\n");
	WriteOut("The full version may be unavailable for your use for legal reasons including\n");
	WriteOut("but not limited to OS level age verification requirements in your local\n");
	WriteOut("state, province, or general jurisdiction.\n");
	WriteOut("\n");
	WriteOut("To use this emulator, you will need to obtain and use a working MS-DOS boot\n");
	WriteOut("disk or bootable hard disk image. Then, modify your dosbox.conf to IMGMOUNT\n");
	WriteOut("and/or BOOT the image to run your DOS application. Please read documentation\n");
	WriteOut("for more information on how to boot disk images.\n");
	WriteOut("\n");
	WriteOut("Hit ENTER or ESC to close the emulator.\n");

	do {
		n=1;c=0;
		if (!DOS_ReadFile(0/*STDIN*/,&c,&n)) break;

		if (c == 0x04/*CTRL+D secret drop to command line--though you're not going to find much!*/) return true;
		if (c == 13/*ENTER*/ || c == 27/*ESC*/) break;
	} while(1);

	return false;
}
#endif

#if defined(OSFREE)
static bool once_block = false;
#endif

void DOS_Shell::Run(void) {
	shellrun=true;
	char input_line[CMD_MAXLINE] = {0};
	std::string line;
	bool optP=cmd->FindStringRemain("/P",line), optC=cmd->FindStringRemainBegin("/C",line), optK=false;
	if (!optC) optK=cmd->FindStringRemainBegin("/K",line);
	if (optP) perm=true;
	if (optC||optK) {
		input_line[CMD_MAXLINE-1u] = 0;
		strncpy(input_line,line.c_str(),CMD_MAXLINE-1u);
		char* sep = strpbrk(input_line,"\r\n"); //GTA installer
		if (sep) *sep = 0;
		DOS_Shell temp;
		temp.echo = echo;
		temp.exec=true;
		temp.ParseLine(input_line);		//for *.exe *.com  |*.bat creates the bf needed by runinternal;
		temp.RunInternal();				// exits when no bf is found.
		temp.exec=false;
		if (!optK||(!perm&&temp.exit)) {
			shellrun=false;
			return;
		}
	} else if (cmd->FindStringRemain("/?",line)) {
		WriteOut(MSG_Get("SHELL_CMD_COMMAND_HELP"));
		shellrun=false;
		return;
	}

	bool optInit=cmd->FindString("/INIT",line,true);
	if (this != first_shell && !optInit)
		WriteOut(optK?"\n":"DOSBox-X command shell [Version %s %s]\nCopyright DOSBox-X Team. All rights reserved.\n\n",VERSION,SDL_STRING);

	if(optInit) {
		input_line[CMD_MAXLINE - 1u] = 0;
		strncpy(input_line, line.c_str(), CMD_MAXLINE - 1u);
		line.erase();
		ParseLine(input_line);
	}
	if (!exit) {
		RunningProgram = "COMMAND";
		GFX_SetTitle(-1,-1,-1,false);
	}
	do {
		/* Get command once a line */
		if (bf) {
			if (bf->ReadLine(input_line)) {
				if (echo) {
					if (input_line[0]!='@') {
						ShowPrompt();
						WriteOut_NoParsing(input_line);
						WriteOut_NoParsing("\n");
					}
				}
			} else input_line[0]='\0';
		} else {
			if (optInit && control->opt_exit) break;
#if defined(OSFREE)
			if (!once_block) {
				once_block = true;
				LOG_MSG("DOS shell not available in OSFREE mode");
				if (!OSFreeOperatingSystemNotFound()) break;
			}
#endif

			if (echo) ShowPrompt();
			InputCommand(input_line);
			if (echo && !input_eof) WriteOut("\n");

			/* Bugfix: CTTY NUL will return immediately, the shell input will return
			 *         immediately, and if we don't consume CPU cycles to compensate,
			 *         will leave DOSBox-X running in an endless loop, hung. */
			if (input_eof) CALLBACK_Idle();
		}

		/* do it */
		if(strlen(input_line)!=0) {
			if (bf == NULL/*not running a batch file*/ && shell_keyboard_flush) DOS_FlushSTDIN();
			ParseLine(input_line);
			if (echo && !bf) WriteOut_NoParsing("\n");
			if (bf == NULL/*not running a batch file*/ && shell_keyboard_flush) DOS_FlushSTDIN();
		}
	} while (perm||!exit);
	shellrun=false;
}

void DOS_Shell::SyntaxError(void) {
	WriteOut(MSG_Get("SHELL_SYNTAXERROR"));
}

bool filename_not_8x3(const char *n), isDBCSCP(), isKanji1(uint8_t chr), shiftjis_lead_byte(int c);
class AUTOEXEC:public Module_base {
private:
	AutoexecObject autoexec[17];
	AutoexecObject autoexec_echo;
    AutoexecObject autoexec_auto_bat;
public:
    void RunAdditional() {
        force_conversion = true;
        int cp=dos.loaded_codepage;
        InitCodePage();
        force_conversion = false;

        /* The user may have given .BAT files to run on the command line */
        if (!control->auto_bat_additional.empty()) {
            std::string cmd = "@echo off\n";

            decltype(control->auto_bat_additional)::size_type ind = 0;
            for (auto & str : control->auto_bat_additional) {
                if (!control->opt_prerun) cmd += "\n";
                if (!strncmp(str.c_str(), "@mount c: ", 10)) {
                    cmd += str+"\n";
                    cmd += "@config -get lastmount>nul\n";
                    cmd += "@if not '%CONFIG%'=='' %CONFIG%";
                } else {
                    std::string batname;
                    //LOG_MSG("auto_bat_additional %s\n", str.c_str());

                    std::replace(str.begin(),str.end(),'/','\\');
                    size_t pos = std::string::npos;
                    bool lead = false;
                    for (unsigned int j=0; j<str.size(); j++) {
                        if (lead) lead = false;
                        else if ((IS_PC98_ARCH && shiftjis_lead_byte(str[j])) || (isDBCSCP() && isKanji1(str[j]))) lead = true;
                        else if (str[j]=='\\') pos = j;
                    }
                    if(pos == std::string::npos) {  //Only a filename, mount current directory
                        batname = str;
                        cmd += "@mount c: . -nl -q\n";
                    } else { //Parse the path of .BAT file
                        std::string batpath = str.substr(0,pos+1);
                        if (batpath==".\\") batpath=".";
                        else if (batpath=="..\\") batpath="..";
                        batname = str.substr(pos+1);
                        cmd += "@mount c: \"" + batpath + "\" -nl -q\n";
                    }
                    std::string opt = control->opt_o.size() > ind && control->opt_o[ind].size() ? " "+control->opt_o[ind] : "";
                    ind++;
                    bool templfn=!uselfn&&filename_not_8x3(batname.c_str())&&(enablelfn==-1||enablelfn==-2);
                    cmd += "@config -get lastmount>nul\n";
                    cmd += "@set LASTMOUNT=%CONFIG%\n";
                    cmd += "@if not '%LASTMOUNT%'=='' %LASTMOUNT%\n";
                    cmd += "@cd \\\n";
                    if (templfn) cmd += "@config -set lfn=true\n";
#if defined(WIN32) && !defined(HX_DOS)
                    if (!winautorun) cmd += "@config -set startcmd=true\n";
#endif
                    cmd += "@CALL \"";
                    cmd += batname;
                    cmd += "\"" + opt + "\n";
                    if (templfn) cmd += "@config -set lfn=" + std::string(enablelfn==-1?"auto":"autostart") + "\n";
#if defined(WIN32) && !defined(HX_DOS)
                    if (!winautorun) cmd += "@config -set startcmd=false\n";
#endif
                    cmd += "@if not '%LASTMOUNT%'=='' mount %LASTMOUNT% -q -u\n";
                    cmd += "@set LASTMOUNT=";
                }
                if (control->opt_prerun) cmd += "\n";
            }
            autoexec_auto_bat.Install(cmd);
        }
        dos.loaded_codepage = cp;
    }
	AUTOEXEC(Section* configuration):Module_base(configuration) {
		/* Register a virtual AUTOEXEC.BAT file */
		const Section_line * section=static_cast<Section_line *>(configuration);

		/* Check -securemode switch to disable mount/imgmount/boot after running autoexec.bat */
		bool secure = control->opt_securemode;

		if (control->opt_prerun) RunAdditional();

		/* add stuff from the configfile unless -noautoexec or -securemode is specified. */
		const char * extra = section->data.c_str();
		if (extra && !secure && !control->opt_noautoexec) {
			/* detect if "echo off" is the first line */
			size_t firstline_length = strcspn(extra,"\r\n");
			bool echo_off  = !strncasecmp(extra,"echo off",8);
			if (echo_off && firstline_length == 8) extra += 8;
			else {
				echo_off = !strncasecmp(extra,"@echo off",9);
				if (echo_off && firstline_length == 9) extra += 9;
				else echo_off = false;
			}

			/* if "echo off" move it to the front of autoexec.bat */
			if (echo_off)  {
				autoexec_echo.InstallBefore("@echo off");
				if (*extra == '\r') extra++; //It can point to \0
				if (*extra == '\n') extra++; //same
			}

			/* Install the stuff from the configfile if anything left after moving echo off */

			if (*extra) autoexec[0].Install(std::string(extra));
		}

		/* Check to see for extra command line options to be added (before the command specified on commandline) */
		/* Maximum of extra commands: 10 */
		Bitu i = 1;
		for (auto it=control->opt_c.begin();i <= 11 && it!=control->opt_c.end();it++) /* -c switches */
			autoexec[i++].Install(*it);

		/* Check for the -exit switch which causes dosbox to when the command on the commandline has finished */
		bool addexit = control->opt_exit;

		if (!control->opt_prerun) RunAdditional();

#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. */
		/* Check for first command being a directory or file */
		char buffer[CROSS_LEN+1];
		char orig[CROSS_LEN+1];
		char cross_filesplit[2] = {CROSS_FILESPLIT , 0};

		Bitu dummy = 1;
		bool command_found = false;
		while (control->cmdline->FindCommand(dummy++,line) && !command_found) {
			struct stat test;
			if (line.length() > CROSS_LEN) continue;
			strcpy(buffer,line.c_str());
			if (stat(buffer,&test)) {
				if (getcwd(buffer,CROSS_LEN) == NULL) continue;
				if (strlen(buffer) + line.length() + 1 > CROSS_LEN) continue;
				strcat(buffer,cross_filesplit);
				strcat(buffer,line.c_str());
				if (stat(buffer,&test)) continue;
			}
			if (test.st_mode & S_IFDIR) {
				autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
				autoexec[13].Install("C:");
				if(secure) autoexec[14].Install("z:\\system\\config.com -securemode");
				command_found = true;
			} else {
				char* name = strrchr(buffer,CROSS_FILESPLIT);
				if (!name) { //Only a filename
					line = buffer;
					if (getcwd(buffer,CROSS_LEN) == NULL) continue;
					if (strlen(buffer) + line.length() + 1 > CROSS_LEN) continue;
					strcat(buffer,cross_filesplit);
					strcat(buffer,line.c_str());
					if(stat(buffer,&test)) continue;
					name = strrchr(buffer,CROSS_FILESPLIT);
					if(!name) continue;
				}
				*name++ = 0;
				if (access(buffer,F_OK)) continue;
				autoexec[12].Install(std::string("MOUNT C \"") + buffer + "\"");
				autoexec[13].Install("C:");
				/* Save the non-modified filename (so boot and imgmount can use it (long filenames, case sensitive)) */
				strcpy(orig,name);
				upcase(name);
				if(strstr(name,".BAT") != 0) {
					if(secure) autoexec[14].Install("z:\\system\\config.com -securemode");
					/* BATch files are called else exit will not work */
					autoexec[15].Install(std::string("CALL ") + name);
					if(addexit) autoexec[16].Install("exit");
				} else if((strstr(name,".IMG") != 0) || (strstr(name,".IMA") !=0 )) {
					//No secure mode here as boot is destructive and enabling securemode disables boot
					/* Boot image files */
					autoexec[15].Install(std::string("BOOT ") + orig);
				} else if((strstr(name,".ISO") != 0) || (strstr(name,".CUE") !=0 )) {
					/* imgmount CD image files */
					/* securemode gets a different number from the previous branches! */
					autoexec[14].Install(std::string("IMGMOUNT D \"") + orig + std::string("\" -t iso"));
					//autoexec[16].Install("D:");
					if(secure) autoexec[15].Install("z:\\system\\config.com -securemode");
					/* Makes no sense to exit here */
				} else {
					if(secure) autoexec[14].Install("z:\\system\\config.com -securemode");
					autoexec[15].Install(name);
					if(addexit) autoexec[16].Install("exit");
				}
				command_found = true;
			}
		}

		/* Combining -securemode, noautoexec and no parameters leaves you with a lovely Z:\. */
		if ( !command_found ) {
			if ( secure ) autoexec[12].Install("z:\\system\\config.com -securemode");
		}
#else
		if (secure) autoexec[i++].Install("z:\\system\\config.com -securemode");
#endif
#if 0
//#if defined(WIN32) && defined(USE_TTF) /* Workaround for TTF screen initialization */
        if(static_cast<Section_prop*>(control->GetSection("sdl"))->Get_string("output")=="ttf") {
            autoexec[i++].Install("@config -set output=surface");
            autoexec[i++].Install("@config -set output=ttf");
        }
//#endif /* (WIN32) && (USE_TTF) */
#endif // 0
        if (addexit) autoexec[i++].Install("exit");

		assert(i <= 17); /* FIXME: autoexec[] should not be fixed size */

        internal_program = true;
		VFILE_Register("AUTOEXEC.BAT",(uint8_t *)autoexec_data,(uint32_t)strlen(autoexec_data));
        internal_program = true;

	}
};

static AUTOEXEC* test = NULL;

static void AUTOEXEC_ShutDown(Section * sec) {
	(void)sec;//UNUSED
	if (test != NULL) {
		delete test;
		test = NULL;
	}
	if (first_shell != NULL) {
		delete first_shell;
		first_shell = nullptr;//Make clear that it shouldn't be used anymore
	}
	if (call_shellstop != 0) {
		CALLBACK_DeAllocate(call_shellstop);
		call_shellstop = 0;
	}
	if (call_int2e != 0) {
		CALLBACK_DeAllocate(call_int2e);
		call_int2e = 0;
	}
	if (call_int23 != 0) {
		CALLBACK_DeAllocate(call_int23);
		call_int23 = 0;
	}
	shown_welcome = false;
}

void AUTOEXEC_Startup(Section *sec) {
	(void)sec;//UNUSED
	if (test == NULL) {
		LOG(LOG_MISC,LOG_DEBUG)("Allocating AUTOEXEC.BAT emulation");
		test = new AUTOEXEC(control->GetSection("autoexec"));
	}
}

void AUTOEXEC_Init() {
	LOG(LOG_MISC,LOG_DEBUG)("Initializing AUTOEXEC.BAT emulation");

	AddExitFunction(AddExitFunctionFuncPair(AUTOEXEC_ShutDown));
	AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
	AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
	AddVMEventFunction(VM_EVENT_DOS_EXIT_REBOOT_BEGIN,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
	AddVMEventFunction(VM_EVENT_DOS_SURPRISE_REBOOT,AddVMEventFunctionFuncPair(AUTOEXEC_ShutDown));
}

static Bitu INT23_Handler(void) {
	LOG(LOG_MISC,LOG_DEBUG)("DOS shell received CTRL+C signal");
	SETFLAGBIT(CF,true);
	return CBRET_NONE;
}

static Bitu INT2E_Handler(void) {
	/* Save return address and current process */
	RealPt save_ret=real_readd(SegValue(ss),reg_sp);
	uint16_t save_psp=dos.psp();

	/* Set first shell as process and copy command */
	dos.psp(shell_psp);//DOS_FIRST_SHELL);
	DOS_PSP psp(shell_psp);//DOS_FIRST_SHELL);
	psp.SetCommandTail(RealMakeSeg(ds,reg_si));
	SegSet16(ss,RealSeg(psp.GetStack()));
	reg_sp=2046;

	/* Read and fix up command string */
	CommandTail tail;
	MEM_BlockRead(PhysMake(dos.psp(),CTBUF+1),&tail,CTBUF+1);
	if (tail.count<CTBUF) tail.buffer[tail.count]=0;
	else tail.buffer[CTBUF-1]=0;
	char* crlf=strpbrk(tail.buffer,"\r\n");
	if (crlf) *crlf=0;

	/* Execute command */
	if (strlen(tail.buffer)) {
		DOS_Shell temp;
		temp.ParseLine(tail.buffer);
		temp.RunInternal();
	}

	/* Restore process and "return" to caller */
	dos.psp(save_psp);
	SegSet16(cs,RealSeg(save_ret));
	reg_ip=RealOff(save_ret);
	reg_ax=0;
	return CBRET_NONE;
}

void SHELL_MessagesInit() {
	/* Add messages */
	MSG_Add("SHELL_CMD_TREE_ERROR", "No subdirectories exist\n");
	MSG_Add("SHELL_CMD_VOL_TREE", "Directory PATH listing for Volume %s\n");
	MSG_Add("SHELL_CMD_VOL_DRIVE","\n Volume in drive %c ");
	MSG_Add("SHELL_CMD_VOL_SERIAL"," Volume Serial Number is ");
	MSG_Add("SHELL_CMD_VOL_SERIAL_NOLABEL","has no label\n");
	MSG_Add("SHELL_CMD_VOL_SERIAL_LABEL","is %s\n");
	MSG_Add("SHELL_ILLEGAL_PATH","Path not found\n");
	MSG_Add("SHELL_ILLEGAL_DRIVE","Invalid drive specification\n");
	MSG_Add("SHELL_CMD_HELP","If you want a list of all supported internal commands type \033[33;1mHELP /ALL\033[0m.\nYou can also find external commands on the Z: drive as programs.\nA short list of the most often used commands:\n");
	MSG_Add("SHELL_CMD_HELP_END1","External commands such as \033[33;1mMOUNT\033[0m and \033[33;1mIMGMOUNT\033[0m can be found on the Z: drive.\n");
	MSG_Add("SHELL_CMD_HELP_END2","Type \033[33;1mHELP command\033[0m or \033[33;1mcommand /?\033[0m for help information for the specified command.\n");
	MSG_Add("SHELL_CMD_ECHO_ON","ECHO is on.\n");
	MSG_Add("SHELL_CMD_ECHO_OFF","ECHO is off.\n");
	MSG_Add("SHELL_ILLEGAL_SWITCH","Invalid switch - %s\n");
	MSG_Add("SHELL_INVALID_PARAMETER","Invalid parameter - %s\n");
	MSG_Add("SHELL_MISSING_PARAMETER","Required parameter missing.\n");
	MSG_Add("SHELL_MISSING_FILE","The following file is missing or corrupted: %s\n");
	MSG_Add("SHELL_CMD_CHDIR_ERROR","Invalid directory - %s\n");
	MSG_Add("SHELL_CMD_CHDIR_HINT","Hint: To change to different drive type \033[31m%c:\033[0m\n");
	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");
	MSG_Add("SHELL_CMD_CHDIR_HINT_3","You are still on drive Z:, and the specified directory cannot be found.\nFor accessing a mounted drive, change to the drive with a syntax like \033[31mC:\033[0m.\n");
	MSG_Add("SHELL_CMD_DATE_HELP","Displays or changes the internal date.\n");
	MSG_Add("SHELL_CMD_DATE_ERROR","The specified date is not correct.\n");
	MSG_Add("SHELL_CMD_DATE_DAYS","3SunMonTueWedThuFriSat"); // "2SoMoDiMiDoFrSa"
	MSG_Add("SHELL_CMD_DATE_NOW","Current date: ");
	MSG_Add("SHELL_CMD_DATE_SETHLP","Type 'date %s' to change.\n");
	MSG_Add("SHELL_CMD_DATE_HELP_LONG","DATE [[/T] [/H] [/S] | date]\n"\
			"  date:       New date to set\n"\
			"  /S:         Permanently use host time and date as DOS time\n"\
			"  /F:         Switch back to DOSBox-X internal time (opposite of /S)\n"\
			"  /T:         Only display date\n"\
			"  /H:         Synchronize with host\n");
	MSG_Add("SHELL_CMD_TIME_HELP","Displays or changes the internal time.\n");
	MSG_Add("SHELL_CMD_TIME_ERROR","The specified time is not correct.\n");
	MSG_Add("SHELL_CMD_TIME_NOW","Current time: ");
	MSG_Add("SHELL_CMD_TIME_SETHLP","Type 'time %s' to change.\n");
	MSG_Add("SHELL_CMD_TIME_HELP_LONG","TIME [[/T] [/H] | time]\n"\
			"  time:       New time to set\n"\
			"  /T:         Display simple time\n"\
			"  /H:         Synchronize with host\n");
	MSG_Add("SHELL_CMD_MKDIR_EXIST","Directory already exists - %s\n");
	MSG_Add("SHELL_CMD_MKDIR_ERROR","Unable to create directory - %s\n");
	MSG_Add("SHELL_CMD_RMDIR_ERROR","Invalid path, not directory, or directory not empty - %s\n");
	MSG_Add("SHELL_CMD_RMDIR_FULLTREE_CONFIRM", "Delete directory \"%s\" and all its subdirectories? (Y/N)?");
	MSG_Add("SHELL_CMD_RMDIR_SINGLE_CONFIRM", "Delete file \"%s\" (Y/N)?");
	MSG_Add("SHELL_CMD_RENAME_ERROR","Unable to rename - %s\n");
	MSG_Add("SHELL_CMD_ATTRIB_GET_ERROR","Unable to get attributes: %s\n");
	MSG_Add("SHELL_CMD_ATTRIB_SET_ERROR","Unable to set attributes: %s\n");
	MSG_Add("SHELL_CMD_DEL_ERROR","Unable to delete - %s\n");
	MSG_Add("SHELL_CMD_DEL_CONFIRM", "Delete %s (Y/N)?");
	MSG_Add("SHELL_CMD_DEL_SURE","All files in directory will be deleted!\nAre you sure [Y/N]?");
	MSG_Add("SHELL_SYNTAXERROR","Syntax error\n");
	MSG_Add("SHELL_CMD_SET_NOT_SET","Environment variable %s not defined.\n");
	MSG_Add("SHELL_CMD_SET_OUT_OF_SPACE","Not enough environment space left.\n");
	MSG_Add("SHELL_CMD_IF_EXIST_MISSING_FILENAME","IF EXIST: Missing filename.\n");
	MSG_Add("SHELL_CMD_IF_ERRORLEVEL_MISSING_NUMBER","IF ERRORLEVEL: Missing number.\n");
	MSG_Add("SHELL_CMD_IF_ERRORLEVEL_INVALID_NUMBER","IF ERRORLEVEL: Invalid number.\n");
	MSG_Add("SHELL_CMD_GOTO_MISSING_LABEL","No label supplied to GOTO command.\n");
	MSG_Add("SHELL_CMD_GOTO_LABEL_NOT_FOUND","GOTO: Label %s not found.\n");
	MSG_Add("SHELL_CMD_FILE_ACCESS_DENIED","Access denied - %s\n");
	MSG_Add("SHELL_CMD_FILE_NOT_FOUND","File not found - %s\n");
	MSG_Add("SHELL_CMD_FILE_EXISTS","File %s already exists.\n");
	MSG_Add("SHELL_CMD_DIR_INTRO"," Directory of %s\n\n");
	MSG_Add("SHELL_CMD_DIR_BYTES_USED","%5d File(s) %17s Bytes\n");
	MSG_Add("SHELL_CMD_DIR_BYTES_FREE","%5d Dir(s)  %17s Bytes free\n");
	MSG_Add("SHELL_CMD_DIR_FILES_LISTED","Total files listed:\n");
	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");
	MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_CDROM","Do you want to give DOSBox-X access to your real CD-ROM drive %c [Y/N]?");
	MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY","Do you want to give DOSBox-X access to your real floppy drive %c [Y/N]?");
	MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_REMOVABLE","Do you want to give DOSBox-X access to your real removable drive %c [Y/N]?");
	MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_NETWORK","Do you want to give DOSBox-X access to your real network drive %c [Y/N]?");
	MSG_Add("SHELL_EXECUTE_DRIVE_ACCESS_FIXED","Do you really want to give DOSBox-X access to your real hard drive %c [Y/N]?");
	MSG_Add("SHELL_EXECUTE_ILLEGAL_COMMAND","Bad command or filename - \"%s\"\n");
	MSG_Add("SHELL_CMD_PAUSE","Press any key to continue . . .\n");
	MSG_Add("SHELL_CMD_PAUSE_HELP","Waits for one keystroke to continue.\n");
	MSG_Add("SHELL_CMD_PAUSE_HELP_LONG","PAUSE\n");
	MSG_Add("SHELL_CMD_COPY_FAILURE","Copy failure - %s\n");
	MSG_Add("SHELL_CMD_COPY_SUCCESS","   %d File(s) copied.\n");
	MSG_Add("SHELL_CMD_COPY_CONFIRM","Overwrite %s (Yes/No/All)?");
	MSG_Add("SHELL_CMD_COPY_NOSPACE","Insufficient disk space - %s\n");
	MSG_Add("SHELL_CMD_COPY_ERROR","Copy error - %s\n");
	MSG_Add("SHELL_CMD_COPY_NOSELF", "File cannot be copied onto itself\r\n");
	MSG_Add("SHELL_CMD_SUBST_DRIVE_LIST","The currently mounted local drives are:\n");
	MSG_Add("SHELL_CMD_SUBST_NO_REMOVE","Unable to remove, drive not in use.\n");
	MSG_Add("SHELL_CMD_SUBST_IN_USE","Target drive is already in use.\n");
	MSG_Add("SHELL_CMD_SUBST_NOT_LOCAL","It is only possible to use SUBST on local drives.\n");
	MSG_Add("SHELL_CMD_SUBST_INVALID_PATH","The specified drive or path is invalid.\n");
	MSG_Add("SHELL_CMD_SUBST_FAILURE","SUBST: There is an error in your command line.\n");
	MSG_Add("SHELL_CMD_VTEXT_ON","DOS/V V-text is currently enabled.\n");
	MSG_Add("SHELL_CMD_VTEXT_OFF","DOS/V V-text is currently disabled.\n");
	MSG_Add("SHELL_ALLFILES_CHAR", "a");

	std::string mapper_keybind = mapper_event_keybind_string("host");
	if (mapper_keybind.empty()) mapper_keybind = "unbound";

	/* Capitalize the binding */
	if (mapper_keybind.size() > 0)
		mapper_keybind[0] = toupper(mapper_keybind[0]);

	std::string default_host =
#if defined(WIN32) && !defined(HX_DOS)
		"F11"
#else
		"F12"
#endif
		;

	/* Punctuation is important too. */
	//mapper_keybind += ".";

	/* NTS: MSG_Add() takes the string as const char * but it does make a copy of the string when entering into the message map,
	 *      so there is no problem here of causing use-after-free crashes when we exit. */
	//std::string host_key_help; // SHELL_STARTUP_BEGIN2 <- UNUSED

	if (machine == MCH_PC98) {
		// "\x86\x46 To activate the keymapper \033[31mhost+M\033[37m. Host key is F12.                 \x86\x46\n"
	}
	else {
		// "\xBA To activate the keymapper \033[31mhost+M\033[37m. Host key is F12.                 \xBA\n"
	}

	MSG_Add("SHELL_STARTUP_TITLE", "Welcome to DOSBox-X !");
	MSG_Add("SHELL_STARTUP_HEAD1_PC98", "\033[36mGetting Started with DOSBox-X:\033[37m                                    ");
	MSG_Add("SHELL_STARTUP_TEXT1_PC98", "Type \033[32mHELP\033[37m for shell commands, and \033[32mINTRO\033[37m for a short introduction. \nYou could also complete various tasks through the \033[33mdrop-down menus\033[37m.");
	MSG_Add("SHELL_STARTUP_EXAMPLE_PC98", "\033[32mExample\033[37m: Try select \033[33mTrueType font\033[37m or \033[33mOpenGL perfect\033[37m output option.");
	MSG_Add("SHELL_STARTUP_TEXT2_PC98", (std::string("To launch the \033[33mConfiguration Tool\033[37m, use \033[31mhost+C\033[37m. Host key is \033[32m") + (mapper_keybind + "\033[37m.                       ").substr(0,13) + std::string("\nTo activate the \033[33mMapper Editor\033[37m for key assignments, use \033[31mhost+M\033[37m.    \nTo switch between windowed and full-screen mode, use \033[31mhost+F\033[37m.      \nTo adjust the emulated CPU speed, use \033[31mhost+Plus\033[37m and \033[31mhost+Minus\033[37m.   ")).c_str());
	MSG_Add("SHELL_STARTUP_INFO_PC98","\033[36mDOSBox-X is now running in \033[32mJapanese NEC PC-98\033[36m emulation mode.\033[37m     ");
	MSG_Add("SHELL_STARTUP_TEXT3_PC98", "\033[32mDOSBox-X project \033[33mhttps://dosbox-x.com/     \033[36mComplete DOS emulations\033[37m\n\033[32mDOSBox-X guide   \033[33mhttps://dosbox-x.com/wiki\033[37m \033[36mDOS, Windows 3.x and 9x\033[37m\n\033[32mDOSBox-X support \033[33mhttps://github.com/joncampbell123/dosbox-x/issues\033[37m");
	MSG_Add("SHELL_STARTUP_HEAD1", "\033[36mGetting started with DOSBox-X:                                              \033[37m");
	MSG_Add("SHELL_STARTUP_TEXT1", "Type \033[32mHELP\033[37m to see the list of shell commands, \033[32mINTRO\033[37m for a brief introduction.\nYou can also complete various tasks in DOSBox-X through the \033[33mdrop-down menus\033[37m.");
	MSG_Add("SHELL_STARTUP_EXAMPLE", "\033[32mExample\033[37m: Try select the \033[33mTrueType font\033[37m or \033[33mOpenGL pixel-perfect\033[37m output option.");
	MSG_Add("SHELL_STARTUP_HEAD2", "\033[36mUseful default shortcuts:                                                   \033[37m");
	MSG_Add("SHELL_STARTUP_TEXT2", (std::string("- switch between windowed and full-screen mode with key combination \033[31m")+(default_host+" \033[37m+ \033[31mF\033[37m                        ").substr(0,23)+std::string("\033[37m\n") +
				std::string("- launch \033[33mConfiguration Tool\033[37m using \033[31m")+(default_host+" \033[37m+ \033[31mC\033[37m                      ").substr(0,22)+std::string("\033[37m, and \033[33mMapper Editor\033[37m using \033[31m")+(default_host+" \033[37m+ \033[31mM\033[37m                     ").substr(0,24)+std::string("\033[37m\n") +
				std::string("- increase or decrease the emulation speed with \033[31m")+(default_host+" \033[37m+ \033[31mPlus\033[37m      ").substr(0,25)+std::string("\033[37m or \033[31m") +
				(default_host+" \033[37m+ \033[31mMinus\033[37m       ").substr(0,29)+std::string("\033[37m")).c_str());
	MSG_Add("SHELL_STARTUP_DOSV","\033[32mDOS/V mode\033[37m is now active. Try also \033[32mTTF CJK mode\033[37m for a general DOS emulation.");
	MSG_Add("SHELL_STARTUP_CGA", "Composite CGA mode is supported. Use \033[31mCtrl+F8\033[37m to set composite output ON/OFF.\nUse \033[31mCtrl+Shift+[F7/F8]\033[37m to change hue; \033[31mCtrl+F7\033[37m selects early/late CGA model. ");
	MSG_Add("SHELL_STARTUP_CGA_MONO","Use \033[31mCtrl+F7\033[37m to cycle through green, amber, and white monochrome color,      \nand \033[31mCtrl+F8\033[37m to change contrast/brightness settings.                         ");
	MSG_Add("SHELL_STARTUP_HERC","Use \033[31mCtrl+F7\033[37m to cycle through white, amber, and green monochrome color.      \nUse \033[31mCtrl+F8\033[37m to toggle horizontal blending (only in graphics mode).          ");
	MSG_Add("SHELL_STARTUP_HEAD3", "\033[36mDOSBox-X project on the web:                                                \033[37m");
	MSG_Add("SHELL_STARTUP_TEXT3", "\033[32mHomepage of project\033[37m: \033[33mhttps://dosbox-x.com/           \033[36mComplete DOS emulations\033[37m\n\033[32mUser guides on Wiki\033[37m: \033[33mhttps://dosbox-x.com/wiki\033[32m       \033[36mDOS, Windows 3.x and 9x\033[37m\n\033[32mIssue or suggestion\033[37m: \033[33mhttps://github.com/joncampbell123/dosbox-x/issues      \033[37m");
	MSG_Add("SHELL_STARTUP_LAST", "HAVE FUN WITH DOSBox-X !");

	MSG_Add("SHELL_CMD_BREAK_HELP","Sets or clears extended CTRL+C checking.\n");
	MSG_Add("SHELL_CMD_BREAK_HELP_LONG","BREAK [ON | OFF]\n\nType BREAK without a parameter to display the current BREAK setting.\n");
	MSG_Add("SHELL_CMD_CHDIR_HELP","Displays or changes the current directory.\n");
	MSG_Add("SHELL_CMD_CHDIR_HELP_LONG","CHDIR [drive:][path]\n"
			"CHDIR [..]\n"
			"CD [drive:][path]\n"
			"CD [..]\n\n"
			"  ..   Specifies that you want to change to the parent directory.\n\n"
			"Type CD drive: to display the current directory in the specified drive.\n"
			"Type CD without parameters to display the current drive and directory.\n");
	MSG_Add("SHELL_CMD_CLS_HELP","Clears screen.\n");
	MSG_Add("SHELL_CMD_CLS_HELP_LONG","CLS\n");
	MSG_Add("SHELL_CMD_DIR_HELP","Displays a list of files and subdirectories in a directory.\n");
	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"
			"  [drive:][path][filename]\n"
			"              Specifies drive, directory, and/or files to list.\n"
			"  /W          Uses wide list format.\n"
			"  /B          Uses bare format (no heading information or summary).\n"
			"  /S          Displays files in specified directory and all subdirectories.\n"
			"  /P          Pauses after each screenful of information.\n"
			"  /A          Displays files with specified attributes.\n"
			"  attributes   D  Directories                R  Read-only files\n"
			"               H  Hidden files               A  Files ready for archiving\n"
			"               S  System files               -  Prefix meaning not\n"
			"  /O          List by files in sorted order.\n"
			"  sortorder    N  By name (alphabetic)       S  By size (smallest first)\n"
			"               E  By extension (alphabetic)  D  By date & time (earliest first)\n"
			"               G  Group directories first    -  Prefix to reverse order\n\n"
			"Switches may be preset in the DIRCMD environment variable.  Override\n"
			"preset switches by prefixing any switch with - (hyphen)--for example, /-W.\n"
	       );
	MSG_Add("SHELL_CMD_ECHO_HELP","Displays messages, or turns command-echoing on or off.\n");
	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");
	MSG_Add("SHELL_CMD_EXIT_HELP","Exits from the command shell.\n");
	MSG_Add("SHELL_CMD_EXIT_HELP_LONG","EXIT\n");
	MSG_Add("SHELL_CMD_HELP_HELP","Shows DOSBox-X command help.\n");
	MSG_Add("SHELL_CMD_HELP_HELP_LONG","HELP [/A or /ALL]\nHELP [command]\n\n"
			"   /A or /ALL   Lists all supported internal commands.\n"
			"   [command]    Shows help for the specified command.\n\n"
			"\033[0mE.g., \033[37;1mHELP COPY\033[0m or \033[37;1mCOPY /?\033[0m shows help information for COPY command.\n\n"
			"Note: External commands like \033[33;1mMOUNT\033[0m and \033[33;1mIMGMOUNT\033[0m are not listed by HELP [/A].\n"
			"      These commands can be found on the Z: drive as programs (e.g. MOUNT.COM).\n"
			"      Type \033[33;1mcommand /?\033[0m or \033[33;1mHELP command\033[0m for help information for that command.\n");
	MSG_Add("SHELL_CMD_LS_HELP","Lists directory contents.\n");
	MSG_Add("SHELL_CMD_LS_HELP_LONG","LS [drive:][path][filename] [/A] [/L] [/P] [/Z]\n\n"
			"  /A     Lists hidden and system files also.\n"
			"  /L     Lists names one per line.\n"
			"  /P     Pauses after each screenful of information.\n"
			"  /Z     Displays short names even if LFN support is available.\n");
	MSG_Add("SHELL_CMD_MKDIR_HELP","Creates a directory.\n");
	MSG_Add("SHELL_CMD_MKDIR_HELP_LONG","MKDIR [drive:][path]\n"
			"MD [drive:][path]\n");
	MSG_Add("SHELL_CMD_RMDIR_HELP","Removes a directory.\n");
	MSG_Add("SHELL_CMD_RMDIR_HELP_LONG","RMDIR [drive:][path]\n"
			"RD [drive:][path]\n");
	MSG_Add("SHELL_CMD_SET_HELP","Displays or changes environment variables.\n");
	MSG_Add("SHELL_CMD_SET_HELP_LONG","SET [variable=[string]]\n\n"
			"   variable     Specifies the environment-variable name.\n"
			"   string       Specifies a series of characters to assign to the variable.\n\n"
			"* If no string is specified, the variable is removed from the environment.\n\n"
			"Type SET without parameters to display the current environment variables.\n");
	MSG_Add("SHELL_CMD_IF_HELP","Performs conditional processing in batch programs.\n");
	MSG_Add("SHELL_CMD_IF_HELP_LONG","IF [NOT] ERRORLEVEL number command\n"
			"IF [NOT] string1==string2 command\n"
			"IF [NOT] EXIST filename command\n\n"
			"  NOT               Specifies that DOS should carry out\n"
			"                    the command only if the condition is false.\n\n"
			"  ERRORLEVEL number Specifies a true condition if the last program run\n"
			"                    returned an exit code equal to or greater than the number\n"
			"                    specified.\n\n"
			"  string1==string2  Specifies a true condition if the specified text strings\n"
			"                    match.\n\n"
			"  EXIST filename    Specifies a true condition if the specified filename\n"
			"                    exists.\n\n"
			"  command           Specifies the command to carry out if the condition is\n"
			"                    met.  Command can be followed by ELSE command which\n"
			"                    will execute the command after the ELSE keyword if the\n"
			"                    specified condition is FALSE\n");
	MSG_Add("SHELL_CMD_GOTO_HELP","Jumps to a labeled line in a batch program.\n");
	MSG_Add("SHELL_CMD_GOTO_HELP_LONG","GOTO label\n\n"
			"   label   Specifies a text string used in the batch program as a label.\n\n"
			"You type a label on a line by itself, beginning with a colon.\n");
	MSG_Add("SHELL_CMD_HISTORY_HELP","Displays or clears the command history list.\n");
	MSG_Add("SHELL_CMD_HISTORY_HELP_LONG","HISTORY [/C]\n\n  /C  Clears the command history list.\n");
	MSG_Add("SHELL_CMD_SHIFT_HELP","Changes the position of replaceable parameters in a batch file.\n");
	MSG_Add("SHELL_CMD_SHIFT_HELP_LONG","SHIFT\n");
	MSG_Add("SHELL_CMD_FOR_HELP","Runs a specified command for each file in a set of files.\n");
	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");
	MSG_Add("SHELL_CMD_LFNFOR_HELP","Enables or disables long filenames when processing FOR wildcards.\n");
	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");
	MSG_Add("SHELL_CMD_TYPE_HELP","Displays the contents of a text file.\n");
	MSG_Add("SHELL_CMD_TYPE_HELP_LONG","TYPE [drive:][path][filename]\n");
	MSG_Add("SHELL_CMD_REM_HELP","Adds comments in a batch file.\n");
	MSG_Add("SHELL_CMD_REM_HELP_LONG","REM [comment]\n");
	MSG_Add("SHELL_CMD_RENAME_HELP","Renames a file/directory or files.\n");
	MSG_Add("SHELL_CMD_RENAME_HELP_LONG","RENAME [drive:][path][directoryname1 | filename1] [directoryname2 | filename2]\n"
			"REN [drive:][path][directoryname1 | filename1] [directoryname2 | filename2]\n\n"
			"Note that you can not specify a new drive or path for your destination.\n\n"
			"Wildcards (* and ?) are supported for files. For example, the following command\n"
			"renames all text files: \033[37;1mREN *.TXT *.BAK\033[0m\n");
	MSG_Add("SHELL_CMD_DELETE_HELP","Removes one or more files.\n");
	MSG_Add("SHELL_CMD_DELETE_HELP_LONG","DEL [/P] [/F] [/Q] names\n"
			"ERASE [/P] [/F] [/Q] names\n\n"
			"  names         Specifies a list of one or more files or directories.\n"
			"                Wildcards may be used to delete multiple files. If a\n"
			"                directory is specified, all files within the directory\n"
			"                will be deleted.\n"
			"  /P            Prompts for confirmation before deleting one or more files.\n"
			"  /F            Force deleting of read-only files.\n"
			"  /Q            Quiet mode, do not ask if ok to delete on global wildcard.\n");
	MSG_Add("SHELL_CMD_COPY_HELP","Copies one or more files.\n");
	MSG_Add("SHELL_CMD_COPY_HELP_LONG","COPY [/Y | /-Y] source [+source [+ ...]] [destination]\n\n"
			"  source        Specifies the file or files to be copied.\n"
			"  destination   Specifies the directory and/or filename for the new file(s).\n"
			"  /Y            Suppresses prompting to confirm you want to overwrite an\n"
			"                existing destination file.\n"
			"  /-Y           Causes prompting to confirm you want to overwrite an\n"
			"                existing destination file.\n\n"
			"The switch /Y may be preset in the COPYCMD environment variable.\n"
			"This may be overridden with /-Y on the command line.\n\n"
			"To append files, specify a single file for destination, but multiple files\n"
			"for source (using wildcards or file1+file2+file3 format).\n");
	MSG_Add("SHELL_CMD_CALL_HELP","Starts a batch file from within another batch file.\n");
	MSG_Add("SHELL_CMD_CALL_HELP_LONG","CALL [drive:][path]filename [batch-parameters]\n\n"
			"batch-parameters   Specifies any command-line information required by\n"
			"                   the batch program.\n");
	MSG_Add("SHELL_CMD_SUBST_HELP","Assigns an internal directory to a drive.\n");
	MSG_Add("SHELL_CMD_SUBST_HELP_LONG","SUBST [drive1: [drive2:]path]\nSUBST drive1: /D\n\n"
			"  drive1:       Specifies a drive to which you want to assign a path.\n"
			"  [drive2:]path Specifies a mounted local drive and path you want to assign to.\n"
			"  /D            Deletes a mounted or substituted drive.\n\n"
			"Type SUBST with no parameters to display a list of mounted local drives.\n");
	MSG_Add("SHELL_CMD_LOADHIGH_HELP","Loads a program into upper memory (requires XMS and UMB memory).\n");
	MSG_Add("SHELL_CMD_LOADHIGH_HELP_LONG","LH              [drive:][path]filename [parameters]\n"
			"LOADHIGH        [drive:][path]filename [parameters]\n");
	MSG_Add("SHELL_CMD_CHOICE_HELP","Waits for a user keypress to choose one of a set of choices.\n");
	MSG_Add("SHELL_CMD_CHOICE_HELP_LONG","CHOICE [/C:choices] [/N] [/S] /T[:]c,nn text\n\n"
			"  /C[:]choices Specifies allowable keys.  Default is: yn.\n"
			"  /N           Do not display the choices at end of prompt.\n"
			"  /S           Enables case-sensitive choices to be selected.\n"
			"  /T[:]c,nn    Default choice to c after nn seconds.\n"
			"  text         The text to display as a prompt.\n\n"
			"ERRORLEVEL is set to offset of key user presses in choices.\n");
	MSG_Add("SHELL_CMD_ATTRIB_HELP","Displays or changes file attributes.\n");
	MSG_Add("SHELL_CMD_ATTRIB_HELP_LONG","ATTRIB [+R | -R] [+A | -A] [+S | -S] [+H | -H] [drive:][path][filename] [/S]\n\n"
			"  +   Sets an attribute.\n"
			"  -   Clears an attribute.\n"
			"  R   Read-only file attribute.\n"
			"  A   Archive file attribute.\n"
			"  S   System file attribute.\n"
			"  H   Hidden file attribute.\n"
			"  [drive:][path][filename]\n"
			"      Specifies file(s) or directory for ATTRIB to process.\n"
			"  /S  Processes files in all directories in the specified path.\n");
	MSG_Add("SHELL_CMD_PATH_HELP","Displays or sets a search path for executable files.\n");
	MSG_Add("SHELL_CMD_PATH_HELP_LONG","PATH [[drive:]path[;...][;%PATH%]\n"
			"PATH ;\n\n"
			"Type PATH ; to clear all search path settings.\n"
			"Type PATH without parameters to display the current path.\n");
	MSG_Add("SHELL_CMD_PUSHD_HELP","Stores the current directory for use by the POPD command, then\nchanges to the specified directory.\n");
	MSG_Add("SHELL_CMD_PUSHD_HELP_LONG","PUSHD [path]\n\n"
			"path        Specifies the directory to make the current directory.\n\n"
			"Type PUSHD with no parameters to display currently stored directories.\n");
	MSG_Add("SHELL_CMD_POPD_HELP","Changes to the directory stored by the PUSHD command.\n");
	MSG_Add("SHELL_CMD_POPD_HELP_LONG","POPD\n");
	MSG_Add("SHELL_CMD_VERIFY_HELP","Controls whether to verify files are written correctly to a disk.\n");
	MSG_Add("SHELL_CMD_VERIFY_HELP_LONG","VERIFY [ON | OFF]\n\nType VERIFY without a parameter to display the current VERIFY setting.\n");
	MSG_Add("SHELL_CMD_VER_HELP","Displays or sets DOSBox-X's reported DOS version.\n");
	MSG_Add("SHELL_CMD_VER_HELP_LONG","VER [/R]\n"
			"VER [SET] number or VER SET [major minor]\n\n"
			"  /R                 Display DOSBox-X's Git commit version and build date.\n"
			"  [SET] number       Set the specified number as the reported DOS version.\n"
			"  SET [major minor]  Set the reported DOS version in major and minor format.\n\n"
			"  \033[0mE.g., \033[37;1mVER 6.0\033[0m or \033[37;1mVER 7.1\033[0m sets the DOS version to 6.0 and 7.1, respectively.\n"
			"  On the other hand, \033[37;1mVER SET 7 1\033[0m sets the DOS version to 7.01 instead of 7.1.\n\n"
			"Type VER without parameters to display DOSBox-X and the reported DOS version.\n");
	MSG_Add("SHELL_CMD_VER_VER","DOSBox-X version %s (%s). Reported DOS version %d.%02d.\n");
	MSG_Add("SHELL_CMD_VER_INVALID","The specified DOS version is not correct.\n");
	MSG_Add("SHELL_CMD_VOL_HELP","Displays the disk volume label and serial number, if they exist.\n");
	MSG_Add("SHELL_CMD_VOL_HELP_LONG","VOL [drive]\n");
	MSG_Add("SHELL_CMD_PROMPT_HELP","Changes the command prompt.\n");
	MSG_Add("SHELL_CMD_PROMPT_HELP_LONG","PROMPT [text]\n"
			"  text    Specifies a new command prompt.\n\n"
			"Prompt can be made up of normal characters and the following special codes:\n"
			"  $A   & (Ampersand)\n"
			"  $B   | (pipe)\n"
			"  $C   ( (Left parenthesis)\n"
			"  $D   Current date\n"
			"  $E   Escape code (ASCII code 27)\n"
			"  $F   ) (Right parenthesis)\n"
			"  $G   > (greater-than sign)\n"
			"  $H   Backspace (erases previous character)\n"
			"  $L   < (less-than sign)\n"
			"  $N   Current drive\n"
			"  $P   Current drive and path\n"
			"  $Q   = (equal sign)\n"
			"  $S     (space)\n"
			"  $T   Current time\n"
			"  $V   DOS version number\n"
			"  $_   Carriage return and linefeed\n"
			"  $$   $ (dollar sign)\n");
	MSG_Add("SHELL_CMD_ALIAS_HELP", "Defines or displays aliases.\n");
	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");
	MSG_Add("SHELL_CMD_ASSOC_HELP", "Displays or changes file extension associations.\n");
	MSG_Add("SHELL_CMD_ASSOC_HELP_LONG", "ASSOC [.ext[=command] ... ]\n\nType ASSOC without parameters to display the current file associations.\nFile extensions must start with a dot (.); wildcards (* and ?) are allowed.\n");
	MSG_Add("SHELL_CMD_CHCP_HELP", "Displays or changes the current DOS code page.\n");
	MSG_Add("SHELL_CMD_CHCP_HELP_LONG", "CHCP [nnn [file]]\nCHCP nnn[:[language]]\n\n  nnn      Specifies a code page number.\n  file     Specifies a code page file.\n  language Specifies a corresponding language.\n\nSupported code pages for changing in the TrueType font output:\n\n437,737,775,808,850,852,853,855-869,872,874,1250-1258,3021\n\nAlso double-byte code pages including 932, 936, 949, and 950/951.\n\nCustomized code pages are supported by providing code page files.\n");
	MSG_Add("SHELL_CMD_CHCP_ACTIVE", "Active code page: %d\n");
	MSG_Add("SHELL_CMD_CHCP_MISSING", "ASCII characters not defined in TTF font: %d\n");
	MSG_Add("SHELL_CMD_CHCP_INVALID", "Invalid code page number - %s\n");
	MSG_Add("SHELL_CMD_COUNTRY_HELP", "Displays or changes the current country.\n");
	MSG_Add("SHELL_CMD_COUNTRY_HELP_LONG", "COUNTRY [nnn] \n\n  nnn   Specifies a country code.\n\nCountry-specific information such as date and time formats will be affected.\n");
	MSG_Add("SHELL_CMD_CTTY_HELP","Changes the terminal device used to control the system.\n");
	MSG_Add("SHELL_CMD_CTTY_HELP_LONG","CTTY device\n  device        The terminal device to use, such as CON.\n");
	MSG_Add("SHELL_CMD_MORE_HELP","Displays output one screen at a time.\n");
	MSG_Add("SHELL_CMD_MORE_HELP_LONG","MORE [drive:][path][filename]\nMORE < [drive:][path]filename\ncommand-name | MORE [drive:][path][filename]\n");
	MSG_Add("SHELL_CMD_TRUENAME_HELP","Finds the fully-expanded name for a file.\n");
	MSG_Add("SHELL_CMD_TRUENAME_HELP_LONG","TRUENAME [/H] file\n");
	MSG_Add("SHELL_CMD_DXCAPTURE_HELP","Runs program with video or audio capture.\n");
	MSG_Add("SHELL_CMD_DXCAPTURE_HELP_LONG","DX-CAPTURE [/V|/-V] [/A|/-A] [/M|/-M] [/O|/-O] [/D|/-D] [command] [options]\n\nIt will start video or audio capture, run program, and then automatically stop capture when the program exits.\n /V for video, /A for audio, /M multi-track audio, /O for OPL FM (DROv2 format) and /-D for disabling the post-exit delay.");
#if C_DEBUG
	MSG_Add("SHELL_CMD_DEBUGBOX_HELP","Runs program and breaks into debugger at entry point.\n");
	MSG_Add("SHELL_CMD_DEBUGBOX_HELP_LONG","DEBUGBOX [command] [options]\n\nType DEBUGBOX without a parameter to start the debugger.\n");
#endif
	MSG_Add("SHELL_CMD_COMMAND_HELP","Starts the DOSBox-X command shell.\n\nThe following options are accepted:\n\n  /C    Executes the specified command and returns.\n  /K    Executes the specified command and continues running.\n  /P    Loads a permanent copy of the command shell.\n  /INIT Initializes the command shell.\n");

}

void DOS_ApplyMinMCBAndDummyDCB(void);

void SHELL_Init() {
	LOG(LOG_MISC,LOG_DEBUG)("Initializing DOS shell");

#if !defined(OSFREE)
	/* now that CONFIG shell has had a chance to load drivers, apply minimum mcb segment / minimum free segment and dummy DCB */
	DOS_ApplyMinMCBAndDummyDCB();
#endif

	/* Regular startup */
	if (call_shellstop == 0) call_shellstop = CALLBACK_Allocate();

	/* Setup the startup CS:IP to kill the last running machine when exited */
	RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
	SegSet16(cs,RealSeg(newcsip));
	reg_ip=RealOff(newcsip);

	CALLBACK_Setup(call_shellstop,shellstop_handler,CB_IRET,"shell stop");

	/* NTS: Some DOS programs behave badly if run from a command interpreter
	 *      who's PSP segment is too low in memory and does not appear in
	 *      the MCB chain (SimCity 2000). So allocate shell memory normally
	 *      as any DOS application would do.
	 *
	 *      That includes allocating COMMAND.COM stack NORMALLY (not up in
	 *      the UMB as DOSBox SVN would do) */

	/* Now call up the shell for the first time */
	uint16_t psp_seg;//=DOS_FIRST_SHELL;
	uint16_t env_seg;//=DOS_FIRST_SHELL+19; //DOS_GetMemory(1+(4096/16))+1;
	uint16_t stack_seg;//=DOS_GetMemory(2048/16,"COMMAND.COM stack");
	uint16_t tmp,total_sz;
	bool tiny_memory_mode = false;

	// below a certain memory size, alter memory arrangement and allocation to minimize memory
	if (MEM_ConventionalPages() < 0x8) tiny_memory_mode = true;

	// decide shell env size
	if (dosbox_shell_env_size == 0) {
		if (MEM_ConventionalPages() >= 0x10/*64KB or more*/)
			dosbox_shell_env_size = (0x158u - (0x118u + 19u)) << 4u; /* equivalent to DOSBox SVN */
		else if (MEM_ConventionalPages() >= 0x8/*32KB or more*/)
			dosbox_shell_env_size = 384;
		else if (MEM_ConventionalPages() >= 0x4/*16KB or more*/)
			dosbox_shell_env_size = 256;
		else
			dosbox_shell_env_size = 144;
	}
	else {
		dosbox_shell_env_size = (dosbox_shell_env_size+15u)&(~15u); /* round up to paragraph */
	}

	LOG_MSG("COMMAND.COM env size:             %u bytes",dosbox_shell_env_size);

	// According to some sources, 0x0008 is a special PSP segment value used by DOS before the first
	// program is used. We need the current PSP segment to be nonzero so that DOS_AllocateMemory()
	// can properly allocate memory.
	dos.psp(8);

	auto savedMemAllocStrategy = DOS_GetMemAllocStrategy();
	auto shellHigh = std::string(static_cast<Section_prop*>(control->GetSection("dos"))->Get_string("shellhigh"));
	if (shellHigh=="true" || shellHigh=="1" ||
			(shellHigh=="auto" && dos.version.major >= 7))
	{
		DOS_SetMemAllocStrategy(savedMemAllocStrategy | 0x80);
	}

	// COMMAND.COM environment block
	tmp = dosbox_shell_env_size>>4;
	if (!DOS_AllocateMemory(&env_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate environment block segment");
	LOG_MSG("COMMAND.COM environment block:    0x%04x sz=0x%04x",env_seg,tmp);

	// COMMAND.COM main binary (including PSP and stack)
	if (tiny_memory_mode)
		tmp = 0x13 + (1536/16);
	else
		tmp = 0x1A + (2048/16);
	total_sz = tmp;

	// Use normal MCB allocation unless memsize is 4KB
	if (MEM_ConventionalPages() > 1) {
		if (!DOS_AllocateMemory(&psp_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate main body + PSP segment");
	}
	else {
		psp_seg = DOS_GetMemory(tmp,"COMMAND.COM main body and PSP");
	}

	LOG_MSG("COMMAND.COM main body (PSP):      0x%04x sz=0x%04x",psp_seg,tmp);

	DOS_SetMemAllocStrategy(savedMemAllocStrategy);

	// now COMMAND.COM has a main body and PSP segment, reflect it
	dos.psp(psp_seg);
	shell_psp = psp_seg;

	{
		DOS_MCB mcb((uint16_t)(env_seg-1));
		mcb.SetPSPSeg(psp_seg);
		mcb.SetFileName("COMMAND");
	}

	{
		DOS_MCB mcb((uint16_t)(psp_seg-1));
		mcb.SetPSPSeg(psp_seg);
		mcb.SetFileName("COMMAND");
	}

	// set the stack at 0x1A
	if (tiny_memory_mode)
		stack_seg = psp_seg + 0x13;
	else
		stack_seg = psp_seg + 0x1A;
	LOG_MSG("COMMAND.COM stack:                0x%04x",stack_seg);

	// set the stack pointer
	SegSet16(ss,stack_seg);

	if (tiny_memory_mode)
		reg_sp=1534;
	else
		reg_sp=2046;

	LOG(LOG_MISC,LOG_DEBUG)("Shell init SS:SP %04x:%04x",(unsigned int)stack_seg,(unsigned int)reg_sp);

	/* Set up int 24 and psp (Telarium games) */
	real_writeb(psp_seg+16+1,0,0xea);		/* far jmp */
	real_writed(psp_seg+16+1,1,real_readd(0,0x24*4));
	real_writed(0,0x24*4,((uint32_t)psp_seg<<16) | ((16+1)<<4));

	/* Old comment: Set up int 23 to "int 20" in the psp. Fixes what.exe */
	/* 2023/09/28: Point INT 23h at a vector that calls our callback and then calls INT 21h AH=4Ch. Real COMMAND.COM does this too. */
	if (call_int23 == 0) call_int23 = CALLBACK_Allocate();

	RealPt addr_int23=RealMake(psp_seg,8+((16+2)*16));

	CALLBACK_Setup(call_int23,&INT23_Handler,CB_RETF,Real2Phys(addr_int23),"Shell Int 23 CTRL+C");
	RealSetVec(0x23,addr_int23);

	/* Set up int 2e handler */
	if (call_int2e == 0) call_int2e = CALLBACK_Allocate();

	//	RealPt addr_int2e=RealMake(psp_seg+16+1,8);
	// NTS: It's apparently common practice to enumerate MCBs by reading the segment value of INT 2Eh and then
	//      scanning forward from there. The assumption seems to be that COMMAND.COM writes INT 2Eh there using
	//      it's PSP segment and an offset like that of a COM executable even though COMMAND.COM is often an EXE file.
	RealPt addr_int2e=RealMake(psp_seg,8+((16+1)*16));

	CALLBACK_Setup(call_int2e,&INT2E_Handler,CB_IRET_STI,Real2Phys(addr_int2e),"Shell Int 2e");
	RealSetVec(0x2e,addr_int2e);

	/* Setup environment */
	PhysPt env_write=PhysMake(env_seg,0);
	MEM_BlockWrite(env_write,path_string,(Bitu)(strlen(path_string)+1));
	env_write += (PhysPt)(strlen(path_string)+1);
	MEM_BlockWrite(env_write,comspec_string,(Bitu)(strlen(comspec_string)+1));
	env_write += (PhysPt)(strlen(comspec_string)+1);
	MEM_BlockWrite(env_write,prompt_string,(Bitu)(strlen(prompt_string)+1));
	env_write +=(PhysPt)(strlen(prompt_string)+1);
	mem_writeb(env_write++,0);
	mem_writew(env_write,1);
	env_write+=2;
	MEM_BlockWrite(env_write,full_name,(Bitu)(strlen(full_name)+1));

//    extern bool Mouse_Vertical;
	extern bool Mouse_Drv;
	Mouse_Drv = true;

	DOS_PSP psp(psp_seg);
	psp.MakeNew(0);
	dos.psp(psp_seg);

	/* The start of the filetable in the psp must look like this:
	 * 01 01 01 00 02
	 * In order to achieve this: First open 2 files. Close the first and
	 * duplicate the second (so the entries get 01) */
	DOS_OpenExistingSFTEntry(0,1);
	DOS_OpenExistingSFTEntry(1,1);
	DOS_OpenExistingSFTEntry(2,1);
	DOS_OpenExistingSFTEntry(3,0);
	DOS_OpenExistingSFTEntry(4,2);

	psp.SetSize(psp_seg + total_sz);
	psp.SetStack(((unsigned int)stack_seg << 16u) + (unsigned int)reg_sp);

	/* Create appearance of handle inheritance by first shell */
	for (uint16_t i=0;i<5;i++) {
		uint8_t handle=psp.GetFileHandle(i);
		if (Files[handle]) Files[handle]->AddRef();
	}

	psp.SetParent(psp_seg);
	/* Set the environment */
	psp.SetEnvironment(env_seg);
	/* Set the command line for the shell start up */
	CommandTail tail;
	tail.count=(uint8_t)strlen(init_line);
	memset(&tail.buffer, 0, CTBUF);
	strncpy(tail.buffer,init_line,CTBUF);
	MEM_BlockWrite(PhysMake(psp_seg,CTBUF+1),&tail,CTBUF+1);

	/* Setup internal DOS Variables */
	dos.dta(RealMake(psp_seg,CTBUF+1));
	dos.psp(psp_seg);
}

/* Pfff... starting and running the shell from a configuration section INIT
 * What the hell were you guys thinking over there at DOSBox SVN? --J.C. */
void SHELL_Run() {
	dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
	Reflect_Menu();
#endif

	LOG(LOG_MISC,LOG_DEBUG)("Running DOS shell now");

	if (first_shell != NULL) E_Exit("Attempt to start shell when shell already running");
	Section_prop *section = static_cast<Section_prop *>(control->GetSection("config"));
	bool altshell=false;
	char namestr[CROSS_LEN], tmpstr[CROSS_LEN], *name=namestr, *tmp=tmpstr;
	SHELL_ProgramStart_First_shell(&first_shell);
	first_shell->Prepare();
	prepared = true;
	if (section!=NULL&&!control->opt_noconfig&&!control->opt_securemode&&!control->SecureMode()) {
		char *shell = (char *)section->Get_string("shell");
		if (strlen(shell)) {
			tmp=trim(shell);
			name=StripArg(tmp);
			upcase(name);
			if (*name&&(DOS_FileExists(name)||DOS_FileExists((std::string("Z:\\SYSTEM\\")+name).c_str())||DOS_FileExists((std::string("Z:\\BIN\\")+name).c_str())||DOS_FileExists((std::string("Z:\\DOS\\")+name).c_str())||DOS_FileExists((std::string("Z:\\4DOS\\")+name).c_str())||DOS_FileExists((std::string("Z:\\DEBUG\\")+name).c_str())||DOS_FileExists((std::string("Z:\\TEXTUTIL\\")+name).c_str()))) {
				strreplace(name,'/','\\');
				altshell=true;
			} else if (*name)
				first_shell->WriteOut(MSG_Get("SHELL_MISSING_FILE"), name);
		}
	}
	if (control->opt_test) {
#if C_DEBUG
		testerr = RUN_ALL_TESTS();
		control->opt_nolog = false;
		LOG_MSG("Unit test completed: %s\n", testerr?"failure":"success");
		control->opt_nolog = true;
#else
		testerr = true;
		printf("Unit tests are only available in debug builds\n\n");
#endif
#if defined(WIN32)
		if (sdl_wait_on_error()) DOSBox_ConsolePauseWait();
#endif
		return;
	}
	i4dos=false;
	if (altshell) {
		if (strstr(name, "4DOS.COM")) i4dos=true;
		first_shell->SetEnv("COMSPEC",name);
		if (!strlen(tmp)) {
			char *p=strrchr(name, '\\');
			if (!strcasecmp(p==NULL?name:p+1, "COMMAND.COM") || !strcasecmp(name, "Z:COMMAND.COM")) {strcpy(tmpstr, init_line);tmp=tmpstr;}
			else if (!strcasecmp(p==NULL?name:p+1, "4DOS.COM") || !strcasecmp(name, "Z:4DOS.COM")) {strcpy(tmpstr, "AUTOEXEC.BAT");tmp=tmpstr;}
		}
		first_shell->Execute(name, tmp);
		return;
	}
	try {
		first_shell->Run();
		delete first_shell;
		first_shell = nullptr;//Make clear that it shouldn't be used anymore
		prepared = false;
		dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
		Reflect_Menu();
#endif
	}
	catch (...) {
		delete first_shell;
		first_shell = nullptr;//Make clear that it shouldn't be used anymore
		prepared = false;
		dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
		Reflect_Menu();
#endif
		throw;
	}
}

#if !defined(OSFREE)
struct ConfigShell_Entry {
	bool		debugbreak = false; // break into debugger before running program or loading device driver
	bool		echo = false; // echo program or device driver path/command on console

	enum {
		NONE=0,
		RUN,
		DEVICE,
		PAUSE,
		NEXTDRIVE,
	};

	uint8_t		type = NONE;
	uint8_t		drive = 0;
	std::string	path;
	std::string	args;
	std::string	cmd;
};

extern std::string config_run_var_device;
extern std::string config_run_var_devparm;
extern uint8_t device_nextdrive;

void DOS_ConfigShell::Run(void) {
	if (config_shell_prompt && config_shell_prompt_start)
		DOS_Shell::Run();

	{
		Section_prop *section = static_cast<Section_prop *>(control->GetSection("dosbox"));
		bool startbanner = section->Get_bool("startbanner");

		if(startbanner && !control->opt_fastlaunch) {
			if (!shown_welcome) {
				//showWelcome(this);
				DoCommand((char *)std::string("z:\\system\\intro welcome").c_str());
				shown_welcome = true;
			}
		}
		else if((CurMode->type == M_TEXT || IS_PC98_ARCH) && ANSI_SYS_installed()) {
			WriteOut("\033[2J");
		}
	}

	const Section_line * section=static_cast<Section_line *>(control->GetSection("devices"));
	const char *cfgstr = section->data.c_str();

	std::vector<ConfigShell_Entry> entries;
	ConfigShell_Entry entry_template;

	if (IS_PC98_ARCH)
		device_nextdrive = 0;/*A:*/
	else
		device_nextdrive = 2;/*C:*/

	while (*cfgstr) {
		/* every line has the format NAME=VALUE */
		/* DEVICE=C:\DOS\EMM386.EXE /X /Y /A /X */
		std::string name,value;

		{
			const char *b = cfgstr;
			while (*cfgstr && *cfgstr != '=' && *cfgstr != '\n' && *cfgstr != '\r') cfgstr++;
			const char *e = cfgstr;
			while (e > b && *(e-1) == ' ') e--;
			name = std::string(b,size_t(e-b));
		}

		if (*cfgstr == '=') {
			cfgstr++;
			while (*cfgstr == ' ' || *cfgstr == '\t') cfgstr++;
			const char *b = cfgstr;
			while (*cfgstr && *cfgstr != '\n' && *cfgstr != '\r') cfgstr++;
			const char *e = cfgstr;
			while (e > b && *(e-1) == ' ') e--;
			value = std::string(b,size_t(e-b));
		}

		while (*cfgstr && *cfgstr != '\n' && *cfgstr != '\r') cfgstr++;
		if (*cfgstr == '\r') cfgstr++;
		if (*cfgstr == '\n') cfgstr++;

		if (name == "ECHO") {
			if (value == "ON" || value == "1")
				entry_template.echo = true;
			else if (value == "OFF" || value == "0")
				entry_template.echo = false;
		}
		else if (name == "DEBUGBREAK") {
			if (value == "ON" || value == "1")
				entry_template.debugbreak = true;
			else if (value == "OFF" || value == "0")
				entry_template.debugbreak = false;
		}
		else if (name == "PAUSE") {
			entries.push_back(entry_template);
			ConfigShell_Entry &ent = entries[entries.size()-1u];
			ent.type = ConfigShell_Entry::PAUSE;
		}
		else if (name == "RUN") {
			entries.push_back(entry_template);
			ConfigShell_Entry &ent = entries[entries.size()-1u];
			ent.type = ConfigShell_Entry::RUN;
			ent.cmd = value;
		}
		else if (name == "DEVICE") {
			entries.push_back(entry_template);
			ConfigShell_Entry &ent = entries[entries.size()-1u];
			ent.type = ConfigShell_Entry::DEVICE;

			size_t i = value.find_first_of(' ');
			if (i == std::string::npos) i = value.length();
			ent.path = value.substr(0,i);
			while (i < value.length() && value[i] == ' ') i++;
			ent.args = value.substr(i);
		}
		else if (name == "NEXTDRIVE") {
			entries.push_back(entry_template);
			ConfigShell_Entry &ent = entries[entries.size()-1u];
			ent.type = ConfigShell_Entry::NEXTDRIVE;

			const char *c = value.c_str();
			if (isalpha(*c)) {
				ent.drive = toupper(*c) - 'A';
			}
			else if (isdigit(*c)) {
				ent.drive = strtoul(c,NULL,10);
				if (ent.drive > 25) ent.drive = 25;
			}
		}
	}

	if (false/*DEBUG*/) {
		LOG_MSG("CONFIG.SYS devices parsing result");
		for (const auto &e : entries) {
			if (e.type == ConfigShell_Entry::RUN) {
				LOG_MSG(" - dbgbrk=%u echo=%u RUN '%s'",e.debugbreak,e.echo,e.cmd.c_str());
			}
			else if (e.type == ConfigShell_Entry::DEVICE) {
				LOG_MSG(" - dbgbrk=%u echo=%u DEVICE '%s' '%s'",e.debugbreak,e.echo,e.path.c_str(),e.args.c_str());
			}
			else {
				LOG_MSG(" - ????");
			}
		}
	}

	shellrun=true;
	char tmp[512];
	for (auto &ent : entries) {
		if (ent.type == ConfigShell_Entry::RUN) {
			if (ent.echo) WriteOut("RUNNING: RUN=%s",ent.cmd.c_str());
			if (ent.debugbreak) snprintf(tmp,sizeof(tmp),"DEBUGBOX %s",ent.cmd.c_str());
			else snprintf(tmp,sizeof(tmp),"%s",ent.cmd.c_str());
			ParseLine(tmp);
		}
		else if (ent.type == ConfigShell_Entry::DEVICE) {
			if (ent.echo) WriteOut("RUNNING: DEVICE=%s %s",ent.path.c_str(),ent.args.c_str());

			config_run_var_device = ent.path;
			config_run_var_devparm = ent.args;

			if (ent.debugbreak) snprintf(tmp,sizeof(tmp),"DEBUGBOX CONFIG \xff\xaa\xff");
			else snprintf(tmp,sizeof(tmp),"CONFIG \xff\xaa\xff");
			ParseLine(tmp);

			config_run_var_device.clear();
			config_run_var_devparm.clear();
		}
		else if (ent.type == ConfigShell_Entry::PAUSE) {
			if (ent.echo) WriteOut("RUNNING: PAUSE");
			/* FIXME: Our own internal pause function? */
			strcpy(tmp,"PAUSE");
			ParseLine(tmp);
		}
		else if (ent.type == ConfigShell_Entry::NEXTDRIVE) {
			if (ent.echo) WriteOut("RUNNING: NEXTDRIVE=%c",ent.drive+'A');
			device_nextdrive=ent.drive;
		}

	}
	shellrun=false;

	if (config_shell_prompt && config_shell_prompt_end)
		DOS_Shell::Run();
}

DOS_ConfigShell::~DOS_ConfigShell() {
}

DOS_ConfigShell::DOS_ConfigShell():DOS_Shell(){
	config_shell = true;
}
#endif

void CONFIGSHELL_Init() {
#if !defined(OSFREE)
	config_shell_run = true;

	// if there is nothing there, don't even run this code
	const Section_line * section=static_cast<Section_line *>(control->GetSection("devices"));
	const char *cfgstr = section->data.c_str();
	while (*cfgstr == ' ' || *cfgstr == '\t') cfgstr++;
	if (*cfgstr == 0) config_shell_run = false;

	if (!config_shell_run) return;

	LOG(LOG_MISC,LOG_DEBUG)("Initializing CONFIG shell");

	/* Regular startup */
	if (call_shellstop == 0) call_shellstop = CALLBACK_Allocate();

	/* Setup the startup CS:IP to kill the last running machine when exited */
	RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
	SegSet16(cs,RealSeg(newcsip));
	reg_ip=RealOff(newcsip);

	CALLBACK_Setup(call_shellstop,shellstop_handler,CB_IRET,"shell stop");

	/* This version is for loading device drivers, which by their nature,
	 * are loaded low in memory alongside all the other parts of MS-DOS.
	 * Device drivers that cannot load low without crashing are not worth supporting.
	 *
	 * Any DOS application run in this phase is by default loaded into the
	 * top of available memory (lastfit memory strategy), not from the bottom,
	 * and therefore should never crash because it was loaded to low.
	 *
	 * Only device drivers are allocated from the bottom up in memory. */

	/* Now call up the shell for the first time */
	uint16_t psp_seg;//=DOS_FIRST_SHELL;
	uint16_t env_seg;//=DOS_FIRST_SHELL+19; //DOS_GetMemory(1+(4096/16))+1;
	uint16_t stack_seg;//=DOS_GetMemory(2048/16,"COMMAND.COM stack");
	uint16_t tmp,total_sz;
	bool tiny_memory_mode = false;

	// below a certain memory size, alter memory arrangement and allocation to minimize memory
	if (MEM_ConventionalPages() < 0x8) tiny_memory_mode = true;

	// decide shell env size---CONFIG size is more restricted than main shell
	if (MEM_ConventionalPages() >= 0x10/*64KB or more*/)
		dosbox_shell_env_size = (0x158u - (0x118u + 19u)) << 4u; /* equivalent to DOSBox SVN */
	else if (MEM_ConventionalPages() >= 0x8/*32KB or more*/)
		dosbox_shell_env_size = 384;
	else if (MEM_ConventionalPages() >= 0x4/*16KB or more*/)
		dosbox_shell_env_size = 256;
	else
		dosbox_shell_env_size = 144;

	LOG_MSG("COMMAND.COM env size:             %u bytes",dosbox_shell_env_size);

	// According to some sources, 0x0008 is a special PSP segment value used by DOS before the first
	// program is used. We need the current PSP segment to be nonzero so that DOS_AllocateMemory()
	// can properly allocate memory.
	dos.psp(8);

	auto savedMemAllocStrategy = DOS_GetMemAllocStrategy();
	DOS_SetMemAllocStrategy(2/*last fit*/);

	// COMMAND.COM environment block
	tmp = dosbox_shell_env_size>>4;
	if (!DOS_AllocateMemory(&env_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate environment block segment");
	LOG_MSG("COMMAND.COM environment block:    0x%04x sz=0x%04x",env_seg,tmp);

	// COMMAND.COM main binary (including PSP and stack)
	if (tiny_memory_mode)
		tmp = 0x13 + (1536/16);
	else
		tmp = 0x1A + (2048/16);
	total_sz = tmp;

	// Use normal MCB allocation unless memsize is 4KB
	if (MEM_ConventionalPages() > 1) {
		if (!DOS_AllocateMemory(&psp_seg,&tmp)) E_Exit("COMMAND.COM failed to allocate main body + PSP segment");
	}
	else {
		psp_seg = DOS_GetMemory(tmp,"COMMAND.COM main body and PSP");
	}

	LOG_MSG("COMMAND.COM main body (PSP):      0x%04x sz=0x%04x",psp_seg,tmp);

	DOS_SetMemAllocStrategy(savedMemAllocStrategy);

	// now COMMAND.COM has a main body and PSP segment, reflect it
	dos.psp(psp_seg);
	shell_psp = psp_seg;

	{
		DOS_MCB mcb((uint16_t)(env_seg-1));
		mcb.SetPSPSeg(psp_seg);
		mcb.SetFileName("CONFIG");
	}

	{
		DOS_MCB mcb((uint16_t)(psp_seg-1));
		mcb.SetPSPSeg(psp_seg);
		mcb.SetFileName("CONFIG");
	}

	// set the stack at 0x1A
	if (tiny_memory_mode)
		stack_seg = psp_seg + 0x13;
	else
		stack_seg = psp_seg + 0x1A;
	LOG_MSG("COMMAND.COM stack:                0x%04x",stack_seg);

	// set the stack pointer
	SegSet16(ss,stack_seg);

	if (tiny_memory_mode)
		reg_sp=1534;
	else
		reg_sp=2046;

	LOG(LOG_MISC,LOG_DEBUG)("Shell init SS:SP %04x:%04x",(unsigned int)stack_seg,(unsigned int)reg_sp);

	/* Set up int 24 and psp (Telarium games) */
	real_writeb(psp_seg+16+1,0,0xea);		/* far jmp */
	real_writed(psp_seg+16+1,1,real_readd(0,0x24*4));
	real_writed(0,0x24*4,((uint32_t)psp_seg<<16) | ((16+1)<<4));

	/* Old comment: Set up int 23 to "int 20" in the psp. Fixes what.exe */
	/* 2023/09/28: Point INT 23h at a vector that calls our callback and then calls INT 21h AH=4Ch. Real COMMAND.COM does this too. */
	if (call_int23 == 0) call_int23 = CALLBACK_Allocate();

	RealPt addr_int23=RealMake(psp_seg,8+((16+2)*16));

	CALLBACK_Setup(call_int23,&INT23_Handler,CB_RETF,Real2Phys(addr_int23),"Shell Int 23 CTRL+C");
	RealSetVec(0x23,addr_int23);

	/* Set up int 2e handler */
	if (call_int2e == 0) call_int2e = CALLBACK_Allocate();

	//	RealPt addr_int2e=RealMake(psp_seg+16+1,8);
	// NTS: It's apparently common practice to enumerate MCBs by reading the segment value of INT 2Eh and then
	//      scanning forward from there. The assumption seems to be that COMMAND.COM writes INT 2Eh there using
	//      it's PSP segment and an offset like that of a COM executable even though COMMAND.COM is often an EXE file.
	RealPt addr_int2e=RealMake(psp_seg,8+((16+1)*16));

	CALLBACK_Setup(call_int2e,&INT2E_Handler,CB_IRET_STI,Real2Phys(addr_int2e),"Shell Int 2e");
	RealSetVec(0x2e,addr_int2e);

	if (config_shell_prompt) {
		/* Setup environment */
		PhysPt env_write=PhysMake(env_seg,0);
		MEM_BlockWrite(env_write,path_string,(Bitu)(strlen(path_string)+1));
		env_write += (PhysPt)(strlen(path_string)+1);
		MEM_BlockWrite(env_write,comspec_string,(Bitu)(strlen(comspec_string)+1));
		env_write += (PhysPt)(strlen(comspec_string)+1);
		MEM_BlockWrite(env_write,prompt_string_config,(Bitu)(strlen(prompt_string_config)+1));
		env_write +=(PhysPt)(strlen(prompt_string_config)+1);
		mem_writeb(env_write++,0);
		mem_writew(env_write,1);
		env_write+=2;
		MEM_BlockWrite(env_write,full_name,(Bitu)(strlen(full_name)+1));
	}
	else {
		/* Setup environment */
		PhysPt env_write=PhysMake(env_seg,0);
		MEM_BlockWrite(env_write,path_string,(Bitu)(strlen(path_string)+1));
		env_write += (PhysPt)(strlen(path_string)+1);
		// Do not write COMSPEC or PROMPT, this is not a general purpose shell
		mem_writeb(env_write++,0);
		mem_writew(env_write,1);
		env_write+=2;
		mem_writeb(env_write++,0);// No, we're not writing the full name either
	}

	DOS_PSP psp(psp_seg);
	psp.MakeNew(0);
	dos.psp(psp_seg);

	/* The start of the filetable in the psp must look like this:
	 * 01 01 01 00 02
	 * In order to achieve this: First open 2 files. Close the first and
	 * duplicate the second (so the entries get 01) */
	DOS_OpenExistingSFTEntry(0,1);
	DOS_OpenExistingSFTEntry(1,1);
	DOS_OpenExistingSFTEntry(2,1);
	DOS_OpenExistingSFTEntry(3,0);
	DOS_OpenExistingSFTEntry(4,2);

	psp.SetSize(psp_seg + total_sz);
	psp.SetStack(((unsigned int)stack_seg << 16u) + (unsigned int)reg_sp);

	/* Create appearance of handle inheritance by first shell */
	for (uint16_t i=0;i<5;i++) {
		uint8_t handle=psp.GetFileHandle(i);
		if (Files[handle]) Files[handle]->AddRef();
	}

	psp.SetParent(psp_seg);
	/* Set the environment */
	psp.SetEnvironment(env_seg);
	/* Set the command line for the shell start up */
	CommandTail tail;
	tail.count=(uint8_t)0;
	memset(&tail.buffer, 0, CTBUF);
	MEM_BlockWrite(PhysMake(psp_seg,CTBUF+1),&tail,CTBUF+1);

	/* Setup internal DOS Variables */
	dos.dta(RealMake(psp_seg,CTBUF+1));
	dos.psp(psp_seg);
#endif
}

void CONFIGSHELL_Run() {
#if !defined(OSFREE)
	if (!config_shell_run) return;

	dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
	Reflect_Menu();
#endif

	LOG(LOG_MISC,LOG_DEBUG)("Running CONFIG shell now");

	if (first_shell != NULL) E_Exit("Attempt to start shell when shell already running");
	SHELL_ProgramStart_Config_shell(&first_shell);
	prepared = true;
	i4dos=false;
	try {
		first_shell->Run();
		delete first_shell;
		first_shell = nullptr;//Make clear that it shouldn't be used anymore
		prepared = false;
		dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
		Reflect_Menu();
#endif
	}
	catch (...) {
		delete first_shell;
		first_shell = nullptr;//Make clear that it shouldn't be used anymore
		prepared = false;
		dos_shell_running_program = false;
#if DOSBOXMENU_TYPE == DOSBOXMENU_HMENU
		Reflect_Menu();
#endif
		throw;
	}
#endif
}

