mirror of
https://github.com/joncampbell123/dosbox-x.git
synced 2025-05-10 04:08:57 +08:00
1301 lines
49 KiB
C++
1301 lines
49 KiB
C++
/*
|
||
* 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 <string.h>
|
||
#include <algorithm> //std::copy
|
||
#include <iterator> //std::front_inserter
|
||
#include <regex>
|
||
|
||
#include "logging.h"
|
||
#include "shell.h"
|
||
#include "timer.h"
|
||
#include "bios.h"
|
||
#include "control.h"
|
||
#include "regs.h"
|
||
#include "callback.h"
|
||
#include "support.h"
|
||
#include "inout.h"
|
||
#include "../ints/int10.h"
|
||
#include "../dos/drives.h"
|
||
|
||
#ifdef _MSC_VER
|
||
# if !defined(C_SDL2)
|
||
# include "process.h"
|
||
# endif
|
||
# define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||
# define MAX(a,b) ((a) > (b) ? (a) : (b))
|
||
#else
|
||
# define MIN(a,b) std::min(a,b)
|
||
# define MAX(a,b) std::max(a,b)
|
||
#endif
|
||
|
||
bool clearline=false, inshell=false;
|
||
int autofixwarn=3;
|
||
extern int lfn_filefind_handle;
|
||
extern bool ctrlbrk, gbk;
|
||
extern bool DOS_BreakFlag;
|
||
extern bool DOS_BreakConioFlag;
|
||
|
||
void DOS_Shell::ShowPrompt(void) {
|
||
char dir[DOS_PATHLENGTH];
|
||
dir[0] = 0; //DOS_GetCurrentDir doesn't always return something. (if drive is messed up)
|
||
DOS_GetCurrentDir(0,dir,uselfn);
|
||
std::string line;
|
||
const char * promptstr = "\0";
|
||
inshell = true;
|
||
|
||
if(GetEnvStr("PROMPT",line)) {
|
||
std::string::size_type idx = line.find('=');
|
||
std::string value=line.substr(idx +1 , std::string::npos);
|
||
line = std::string(promptstr) + value;
|
||
promptstr = line.c_str();
|
||
}
|
||
|
||
while (*promptstr) {
|
||
if (!strcasecmp(promptstr,"$"))
|
||
WriteOut("\0");
|
||
else if(*promptstr != '$')
|
||
WriteOut("%c",*promptstr);
|
||
else switch (toupper(*++promptstr)) {
|
||
case 'A': WriteOut("&"); break;
|
||
case 'B': WriteOut("|"); break;
|
||
case 'C': WriteOut("("); break;
|
||
case 'D': WriteOut("%02d-%02d-%04d",dos.date.day,dos.date.month,dos.date.year); break;
|
||
case 'E': WriteOut("%c",27); break;
|
||
case 'F': WriteOut(")"); break;
|
||
case 'G': WriteOut(">"); break;
|
||
case 'H': WriteOut("\b"); break;
|
||
case 'L': WriteOut("<"); break;
|
||
case 'N': WriteOut("%c",DOS_GetDefaultDrive()+'A'); break;
|
||
case 'P': WriteOut("%c:\\%s",DOS_GetDefaultDrive()+'A',dir); break;
|
||
case 'Q': WriteOut("="); break;
|
||
case 'S': WriteOut(" "); break;
|
||
case 'T': {
|
||
Bitu ticks=(Bitu)(((65536.0 * 100.0)/(double)PIT_TICK_RATE)* mem_readd(BIOS_TIMER));
|
||
reg_dl=(uint8_t)((Bitu)ticks % 100);
|
||
ticks/=100;
|
||
reg_dh=(uint8_t)((Bitu)ticks % 60);
|
||
ticks/=60;
|
||
reg_cl=(uint8_t)((Bitu)ticks % 60);
|
||
ticks/=60;
|
||
reg_ch=(uint8_t)((Bitu)ticks % 24);
|
||
WriteOut("%d:%02d:%02d.%02d",reg_ch,reg_cl,reg_dh,reg_dl);
|
||
break;
|
||
}
|
||
case 'V': WriteOut("DOSBox-X version %s. Reported DOS version %d.%d.",VERSION,dos.version.major,dos.version.minor); break;
|
||
case '$': WriteOut("$"); break;
|
||
case '_': WriteOut("\n"); break;
|
||
case 'M': break;
|
||
case '+': break;
|
||
}
|
||
promptstr++;
|
||
}
|
||
inshell = false;
|
||
}
|
||
|
||
static void outc(uint8_t c) {
|
||
uint16_t n=1;
|
||
DOS_WriteFile(STDOUT,&c,&n);
|
||
}
|
||
|
||
static void backone() {
|
||
BIOS_NCOLS;
|
||
uint8_t page=real_readb(BIOSMEM_SEG,BIOSMEM_CURRENT_PAGE);
|
||
if (CURSOR_POS_COL(page)>0)
|
||
outc(8);
|
||
else if (CURSOR_POS_ROW(page)>0)
|
||
INT10_SetCursorPos(CURSOR_POS_ROW(page)-1, ncols-1, page);
|
||
}
|
||
|
||
//! \brief Moves the caret to prev row/last column when column is 0 (video mode 0).
|
||
void MoveCaretBackwards()
|
||
{
|
||
uint8_t col, row;
|
||
const uint8_t page(0);
|
||
INT10_GetCursorPos(&row, &col, page);
|
||
|
||
if (col != 0)
|
||
return;
|
||
|
||
uint16_t cols;
|
||
INT10_GetScreenColumns(&cols);
|
||
INT10_SetCursorPos(row - 1, static_cast<uint8_t>(cols), page);
|
||
}
|
||
|
||
bool DOS_Shell::BuildCompletions(char * line, uint16_t str_len) {
|
||
// build new completion list
|
||
// Lines starting with CD/MD/RD will only get directories in the list
|
||
bool dir_only = (strncasecmp(ltrim(line),"CD ",3)==0)||(strncasecmp(ltrim(line),"MD ",3)==0)||(strncasecmp(ltrim(line),"RD ",3)==0)||
|
||
(strncasecmp(ltrim(line),"CHDIR ",6)==0)||(strncasecmp(ltrim(line),"MKDIR ",3)==0)||(strncasecmp(ltrim(line),"RMDIR ",6)==0);
|
||
int q=0, r=0, k=0;
|
||
|
||
// get completion mask
|
||
const char *p_completion_start = strrchr(line, ' ');
|
||
while (p_completion_start) {
|
||
q=0;
|
||
char *i;
|
||
for (i=line;i<p_completion_start;i++)
|
||
if (*i=='\"') q++;
|
||
if (q/2*2==q) break;
|
||
*i=0;
|
||
p_completion_start = strrchr(line, ' ');
|
||
*i=' ';
|
||
}
|
||
char c[]={'<','>','|'};
|
||
for (unsigned int j=0; j<sizeof(c); j++) {
|
||
const char *sp = strrchr_dbcs(line, c[j]);
|
||
while (sp) {
|
||
q=0;
|
||
char *i;
|
||
for (i=line;i<sp;i++)
|
||
if (*i=='\"') q++;
|
||
if (q/2*2==q) break;
|
||
*i=0;
|
||
sp = strrchr_dbcs(line, c[j]);
|
||
*i=c[j];
|
||
}
|
||
if (!p_completion_start || p_completion_start<sp)
|
||
p_completion_start = sp;
|
||
}
|
||
|
||
if (p_completion_start) {
|
||
p_completion_start ++;
|
||
completion_index = (uint16_t)(str_len - strlen(p_completion_start));
|
||
} else {
|
||
p_completion_start = line;
|
||
completion_index = 0;
|
||
}
|
||
k=completion_index;
|
||
|
||
const char *path;
|
||
if ((path = strrchr(line+completion_index,':'))) completion_index = (uint16_t)(path-line+1);
|
||
if ((path = strrchr_dbcs(line+completion_index,'\\'))) completion_index = (uint16_t)(path-line+1);
|
||
if ((path = strrchr(line+completion_index,'/'))) completion_index = (uint16_t)(path-line+1);
|
||
|
||
// build the completion list
|
||
char mask[DOS_PATHLENGTH+2] = {0}, smask[DOS_PATHLENGTH] = {0};
|
||
if (p_completion_start && strlen(p_completion_start) + 3 >= DOS_PATHLENGTH) {
|
||
// TODO: This really should be done in the CON driver so that this code can just print ASCII code 7 instead
|
||
if (IS_PC98_ARCH) {
|
||
// TODO: BEEP. I/O PORTS ARE DIFFERENT AS IS THE PIT CLOCK RATE
|
||
}
|
||
else {
|
||
// IBM PC/XT/AT
|
||
IO_Write(0x43,0xb6);
|
||
IO_Write(0x42,1750&0xff);
|
||
IO_Write(0x42,1750>>8);
|
||
IO_Write(0x61,IO_Read(0x61)|0x3);
|
||
for(Bitu i=0; i < 333; i++) CALLBACK_Idle();
|
||
IO_Write(0x61,IO_Read(0x61)&~0x3);
|
||
}
|
||
return false;
|
||
}
|
||
if (p_completion_start) {
|
||
safe_strncpy(mask, p_completion_start,DOS_PATHLENGTH);
|
||
const char* dot_pos = strrchr(mask, '.');
|
||
const char* bs_pos = strrchr_dbcs(mask, '\\');
|
||
const char* fs_pos = strrchr(mask, '/');
|
||
const char* cl_pos = strrchr(mask, ':');
|
||
// not perfect when line already contains wildcards, but works
|
||
if ((dot_pos-bs_pos>0) && (dot_pos-fs_pos>0) && (dot_pos-cl_pos>0))
|
||
strncat(mask, "*",DOS_PATHLENGTH - 1);
|
||
else strncat(mask, "*.*",DOS_PATHLENGTH - 1);
|
||
} else {
|
||
strcpy(mask, "*.*");
|
||
}
|
||
|
||
RealPt save_dta=dos.dta();
|
||
dos.dta(dos.tables.tempdta);
|
||
|
||
bool res = false;
|
||
if (DOS_GetSFNPath(mask,smask,false)) {
|
||
sprintf(mask,"\"%s\"",smask);
|
||
int fbak=lfn_filefind_handle;
|
||
lfn_filefind_handle=uselfn?LFN_FILEFIND_INTERNAL:LFN_FILEFIND_NONE;
|
||
res = DOS_FindFirst(mask, 0xffff & ~DOS_ATTR_VOLUME);
|
||
lfn_filefind_handle=fbak;
|
||
}
|
||
if (!res) {
|
||
dos.dta(save_dta);
|
||
// TODO: This really should be done in the CON driver so that this code can just print ASCII code 7 instead
|
||
if (IS_PC98_ARCH) {
|
||
// TODO: BEEP. I/O PORTS ARE DIFFERENT AS IS THE PIT CLOCK RATE
|
||
}
|
||
else {
|
||
// IBM PC/XT/AT
|
||
IO_Write(0x43,0xb6);
|
||
IO_Write(0x42,1750&0xff);
|
||
IO_Write(0x42,1750>>8);
|
||
IO_Write(0x61,IO_Read(0x61)|0x3);
|
||
for(Bitu i=0; i < 300; i++) CALLBACK_Idle();
|
||
IO_Write(0x61,IO_Read(0x61)&~0x3);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
DOS_DTA dta(dos.dta());
|
||
char name[DOS_NAMELENGTH_ASCII], lname[LFN_NAMELENGTH], qlname[LFN_NAMELENGTH+2];
|
||
uint32_t sz;uint16_t date;uint16_t time;uint8_t att;
|
||
|
||
std::list<std::string> executable;
|
||
q=0;r=0;
|
||
while (*p_completion_start) {
|
||
k++;
|
||
if (*p_completion_start++=='\"') {
|
||
if (k<=completion_index)
|
||
q++;
|
||
else
|
||
r++;
|
||
}
|
||
}
|
||
int fbak=lfn_filefind_handle;
|
||
lfn_filefind_handle=uselfn?LFN_FILEFIND_INTERNAL:LFN_FILEFIND_NONE;
|
||
while (res) {
|
||
dta.GetResult(name,lname,sz,date,time,att);
|
||
if ((strchr(uselfn?lname:name,' ')!=NULL&&q/2*2==q)||r)
|
||
sprintf(qlname,q/2*2!=q?"%s\"":"\"%s\"",uselfn?lname:name);
|
||
else
|
||
strcpy(qlname,uselfn?lname:name);
|
||
// add result to completion list
|
||
|
||
if (strcmp(name, ".") && strcmp(name, "..")) {
|
||
if (dir_only) { //Handle the dir only case different (line starts with cd)
|
||
if(att & DOS_ATTR_DIRECTORY) l_completion.emplace_back(qlname);
|
||
} else {
|
||
const char *ext = strrchr(name, '.'); // file extension
|
||
if (ext && (strcmp(ext, ".BAT") == 0 || strcmp(ext, ".COM") == 0 || strcmp(ext, ".EXE") == 0))
|
||
// we add executables to a separate list and place that list in front of the normal files
|
||
executable.emplace_front(qlname);
|
||
else
|
||
l_completion.emplace_back(qlname);
|
||
}
|
||
}
|
||
res=DOS_FindNext();
|
||
}
|
||
lfn_filefind_handle=fbak;
|
||
/* Add executable list to front of completion list. */
|
||
std::copy(executable.begin(),executable.end(),std::front_inserter(l_completion));
|
||
dos.dta(save_dta);
|
||
return true;
|
||
}
|
||
|
||
extern bool isDBCSCP();
|
||
/* NTS: buffer pointed to by "line" must be at least CMD_MAXLINE+1 large */
|
||
void DOS_Shell::InputCommand(char * line) {
|
||
Bitu size=CMD_MAXLINE-2; //lastcharacter+0
|
||
uint8_t c;uint16_t n=1;
|
||
uint16_t str_len=0;uint16_t str_index=0;
|
||
uint16_t len=0;
|
||
bool current_hist=false; // current command stored in history?
|
||
uint16_t cr;
|
||
|
||
inshell = true;
|
||
input_eof = false;
|
||
line[0] = '\0';
|
||
|
||
std::list<std::string>::iterator it_history = l_history.begin(), it_completion = l_completion.begin();
|
||
|
||
while (size) {
|
||
dos.echo=false;
|
||
if (!DOS_ReadFile(input_handle,&c,&n)) {
|
||
LOG(LOG_MISC,LOG_ERROR)("SHELL: Lost the input handle, dropping shell input loop");
|
||
n = 0;
|
||
}
|
||
if (!n) {
|
||
input_eof = true;
|
||
size=0; //Kill the while loop
|
||
continue;
|
||
}
|
||
if (clearline) {
|
||
clearline = false;
|
||
*line=0;
|
||
if (l_completion.size()) l_completion.clear(); //reset the completion list.
|
||
str_index = 0;
|
||
str_len = 0;
|
||
}
|
||
|
||
if (input_handle != STDIN) { /* FIXME: Need DOS_IsATTY() or somesuch */
|
||
cr = (uint16_t)c; /* we're not reading from the console */
|
||
}
|
||
else if (IS_PC98_ARCH) {
|
||
extern uint16_t last_int16_code;
|
||
|
||
/* shift state is needed for some key combinations not directly supported by CON driver.
|
||
* bit 4 = CTRL
|
||
* bit 3 = GRPH/ALT
|
||
* bit 2 = kana
|
||
* bit 1 = caps
|
||
* bit 0 = SHIFT */
|
||
uint8_t shiftstate = mem_readb(0x52A + 0x0E);
|
||
|
||
/* NTS: PC-98 keyboards lack the US layout HOME / END keys, therefore there is no mapping here */
|
||
|
||
/* NTS: Since left arrow and backspace map to the same byte value, PC-98 treats it the same at the DOS prompt.
|
||
* However the PC-98 version of DOSKEY seems to be able to differentiate the two anyway and let the left
|
||
* arrow move the cursor back (perhaps it's calling INT 18h directly then?) */
|
||
if (c == 0x0B)
|
||
cr = 0x4800; /* IBM extended code up arrow */
|
||
else if (c == 0x0A)
|
||
cr = 0x5000; /* IBM extended code down arrow */
|
||
else if (c == 0x0C) {
|
||
if (shiftstate & 0x10/*CTRL*/)
|
||
cr = 0x7400; /* IBM extended code CTRL + right arrow */
|
||
else
|
||
cr = 0x4D00; /* IBM extended code right arrow */
|
||
}
|
||
else if (c == 0x08) {
|
||
/* IBM extended code left arrow OR backspace. use last scancode to tell which as DOSKEY apparently can. */
|
||
if (last_int16_code == 0x3B00) {
|
||
if (shiftstate & 0x10/*CTRL*/)
|
||
cr = 0x7300; /* CTRL + left arrow */
|
||
else
|
||
cr = 0x4B00; /* left arrow */
|
||
}
|
||
else {
|
||
cr = 0x08; /* backspace */
|
||
}
|
||
}
|
||
else if (c == 0x1B) { /* escape */
|
||
/* Either it really IS the ESC key, or an ANSI code */
|
||
if (last_int16_code != 0x001B) {
|
||
DOS_ReadFile(input_handle,&c,&n);
|
||
if (c == 0x44) // DEL
|
||
cr = 0x5300;
|
||
else if (c == 0x50) // INS
|
||
cr = 0x5200;
|
||
else if (c == 0x53) // F1
|
||
cr = 0x3B00;
|
||
else if (c == 0x54) // F2
|
||
cr = 0x3C00;
|
||
else if (c == 0x55) // F3
|
||
cr = 0x3D00;
|
||
else if (c == 0x56) // F4
|
||
cr = 0x3E00;
|
||
else if (c == 0x57) // F5
|
||
cr = 0x3F00;
|
||
else if (c == 0x45) // F6
|
||
cr = 0x4000;
|
||
else if (c == 0x4A) // F7
|
||
cr = 0x4100;
|
||
else if (c == 0x51) // F9
|
||
cr = 0x4300;
|
||
else if (c == 0x5A) // F10
|
||
cr = 0x4400;
|
||
else
|
||
cr = 0;
|
||
}
|
||
else {
|
||
cr = (uint16_t)c;
|
||
}
|
||
}
|
||
else {
|
||
cr = (uint16_t)c;
|
||
}
|
||
}
|
||
else {
|
||
if (c == 0) {
|
||
DOS_ReadFile(input_handle,&c,&n);
|
||
cr = (uint16_t)c << (uint16_t)8;
|
||
}
|
||
else {
|
||
cr = (uint16_t)c;
|
||
}
|
||
}
|
||
|
||
switch (cr) {
|
||
case 0x3d00: /* F3 */
|
||
if (!l_history.size()) break;
|
||
it_history = l_history.begin();
|
||
if (it_history != l_history.end() && it_history->length() > str_len) {
|
||
const char *reader = &(it_history->c_str())[str_len];
|
||
while ((c = (uint8_t)(*reader++))) {
|
||
line[str_index ++] = (char)c;
|
||
DOS_WriteFile(STDOUT,&c,&n);
|
||
}
|
||
str_len = str_index = (uint16_t)it_history->length();
|
||
size = (unsigned int)CMD_MAXLINE - str_index - 2u;
|
||
line[str_len] = 0;
|
||
}
|
||
break;
|
||
|
||
case 0x4B00: /* LEFT */
|
||
if ((IS_PC98_ARCH||isDBCSCP())&&str_index>1&&(line[str_index-1]<0||(IS_PC98_ARCH||dos.loaded_codepage==932||(dos.loaded_codepage==936&&gbk)||dos.loaded_codepage==950)&&line[str_index-1]>=0x40)&&line[str_index-2]<0) {
|
||
backone();
|
||
str_index --;
|
||
MoveCaretBackwards();
|
||
}
|
||
if (str_index) {
|
||
backone();
|
||
str_index --;
|
||
MoveCaretBackwards();
|
||
}
|
||
break;
|
||
|
||
case 0x7400: /*CTRL + RIGHT : cmd.exe-like next word*/
|
||
{
|
||
auto pos = line + str_index;
|
||
auto spc = *pos == ' ';
|
||
const auto end = line + str_len;
|
||
|
||
while (pos < end) {
|
||
if (spc && *pos != ' ')
|
||
break;
|
||
if (*pos == ' ')
|
||
spc = true;
|
||
pos++;
|
||
}
|
||
|
||
const auto lgt = MIN(pos, end) - (line + str_index);
|
||
|
||
for (auto i = 0; i < lgt; i++)
|
||
outc(static_cast<uint8_t>(line[str_index++]));
|
||
}
|
||
break;
|
||
case 0x7300: /*CTRL + LEFT : cmd.exe-like previous word*/
|
||
{
|
||
auto pos = line + str_index - 1;
|
||
const auto beg = line;
|
||
const auto spc = *pos == ' ';
|
||
|
||
if (spc) {
|
||
while(*pos == ' ') pos--;
|
||
while(*pos != ' ') pos--;
|
||
pos++;
|
||
}
|
||
else {
|
||
while(*pos != ' ') pos--;
|
||
pos++;
|
||
}
|
||
|
||
const auto lgt = std::abs(MAX(pos, beg) - (line + str_index));
|
||
|
||
for (auto i = 0; i < lgt; i++) {
|
||
backone();
|
||
str_index--;
|
||
MoveCaretBackwards();
|
||
}
|
||
}
|
||
break;
|
||
case 0x4D00: /* RIGHT */
|
||
if ((IS_PC98_ARCH||isDBCSCP())&&str_index<str_len-1&&line[str_index]<0&&(line[str_index+1]<0||(IS_PC98_ARCH||dos.loaded_codepage==932||(dos.loaded_codepage==936&&gbk)||dos.loaded_codepage==950)&&line[str_index+1]>=0x40)) {
|
||
outc((uint8_t)line[str_index++]);
|
||
}
|
||
if (str_index < str_len) {
|
||
outc((uint8_t)line[str_index++]);
|
||
}
|
||
break;
|
||
|
||
case 0x4700: /* HOME */
|
||
while (str_index) {
|
||
backone();
|
||
str_index--;
|
||
}
|
||
break;
|
||
|
||
case 0x5200: /* INS */
|
||
if (IS_PC98_ARCH) { // INS state handled by IBM PC/AT BIOS, faked for PC-98 mode
|
||
extern bool pc98_doskey_insertmode;
|
||
|
||
// NTS: No visible change to the cursor, just like DOSKEY on PC-98 MS-DOS
|
||
pc98_doskey_insertmode = !pc98_doskey_insertmode;
|
||
}
|
||
break;
|
||
|
||
case 0x4F00: /* END */
|
||
while (str_index < str_len) {
|
||
outc((uint8_t)line[str_index++]);
|
||
}
|
||
break;
|
||
|
||
case 0x4800: /* UP */
|
||
if (l_history.empty() || it_history == l_history.end()) break;
|
||
|
||
// store current command in history if we are at beginning
|
||
if (it_history == l_history.begin() && !current_hist) {
|
||
current_hist=true;
|
||
l_history.emplace_front(line);
|
||
}
|
||
|
||
// ensure we're at end to handle all cases
|
||
while (str_index < str_len) {
|
||
outc((uint8_t)line[str_index++]);
|
||
}
|
||
|
||
for (;str_index>0; str_index--) {
|
||
// removes all characters
|
||
backone(); outc(' '); backone();
|
||
}
|
||
strcpy(line, it_history->c_str());
|
||
len = (uint16_t)it_history->length();
|
||
str_len = str_index = len;
|
||
size = (unsigned int)CMD_MAXLINE - str_index - 2u;
|
||
DOS_WriteFile(STDOUT, (uint8_t *)line, &len);
|
||
++it_history;
|
||
break;
|
||
|
||
case 0x5000: /* DOWN */
|
||
if (l_history.empty() || it_history == l_history.begin()) break;
|
||
|
||
// not very nice but works ..
|
||
--it_history;
|
||
if (it_history == l_history.begin()) {
|
||
// no previous commands in history
|
||
++it_history;
|
||
|
||
// remove current command from history
|
||
if (current_hist) {
|
||
current_hist=false;
|
||
l_history.pop_front();
|
||
}
|
||
break;
|
||
} else --it_history;
|
||
|
||
// ensure we're at end to handle all cases
|
||
while (str_index < str_len) {
|
||
outc((uint8_t)line[str_index++]);
|
||
}
|
||
|
||
for (;str_index>0; str_index--) {
|
||
// removes all characters
|
||
backone(); outc(' '); backone();
|
||
}
|
||
strcpy(line, it_history->c_str());
|
||
len = (uint16_t)it_history->length();
|
||
str_len = str_index = len;
|
||
size = (unsigned int)CMD_MAXLINE - str_index - 2u;
|
||
DOS_WriteFile(STDOUT, (uint8_t *)line, &len);
|
||
++it_history;
|
||
|
||
break;
|
||
case 0x5300:/* DELETE */
|
||
{
|
||
if(str_index>=str_len) break;
|
||
int k=1;
|
||
if ((IS_PC98_ARCH||isDBCSCP())&&str_index<str_len-1&&line[str_index]<0&&(line[str_index+1]<0||(IS_PC98_ARCH||dos.loaded_codepage==932||(dos.loaded_codepage==936&&gbk)||dos.loaded_codepage==950)&&line[str_index+1]>=0x40))
|
||
k=2;
|
||
for (int i=0; i<k; i++) {
|
||
uint16_t a=str_len-str_index-1;
|
||
uint8_t* text=reinterpret_cast<uint8_t*>(&line[str_index+1]);
|
||
DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
|
||
outc(' ');backone();
|
||
for(Bitu i=str_index;i<(str_len-1u);i++) {
|
||
line[i]=line[i+1u];
|
||
backone();
|
||
}
|
||
line[--str_len]=0;
|
||
size++;
|
||
}
|
||
}
|
||
break;
|
||
case 0x0F00: /* Shift-Tab */
|
||
if (!l_completion.size()) {
|
||
if (BuildCompletions(line, str_len))
|
||
it_completion = l_completion.end();
|
||
else
|
||
break;
|
||
}
|
||
if (l_completion.size()) {
|
||
if (it_completion == l_completion.begin()) it_completion = l_completion.end ();
|
||
--it_completion;
|
||
|
||
if (it_completion->length()) {
|
||
for (;str_index > completion_index; str_index--) {
|
||
// removes all characters
|
||
backone(); outc(' '); backone();
|
||
}
|
||
|
||
strcpy(&line[completion_index], it_completion->c_str());
|
||
len = (uint16_t)it_completion->length();
|
||
str_len = str_index = (Bitu)(completion_index + len);
|
||
size = (unsigned int)CMD_MAXLINE - str_index - 2u;
|
||
DOS_WriteFile(STDOUT, (uint8_t *)it_completion->c_str(), &len);
|
||
}
|
||
}
|
||
break;
|
||
case 0x08: /* BackSpace */
|
||
{
|
||
int k=1;
|
||
if ((IS_PC98_ARCH||isDBCSCP())&&str_index>1&&(line[str_index-1]<0||(IS_PC98_ARCH||dos.loaded_codepage==932||(dos.loaded_codepage==936&&gbk)||dos.loaded_codepage==950)&&line[str_index-1]>=0x40)&&line[str_index-2]<0)
|
||
k=2;
|
||
for (int i=0; i<k; i++)
|
||
if (str_index) {
|
||
backone();
|
||
uint32_t str_remain=(uint32_t)(str_len - str_index);
|
||
size++;
|
||
if (str_remain) {
|
||
memmove(&line[str_index-1],&line[str_index],str_remain);
|
||
line[--str_len]=0;
|
||
str_index --;
|
||
/* Go back to redraw */
|
||
for (uint16_t i=str_index; i < str_len; i++)
|
||
outc((uint8_t)line[i]);
|
||
} else {
|
||
line[--str_index] = '\0';
|
||
str_len--;
|
||
}
|
||
outc(' '); backone();
|
||
// moves the cursor left
|
||
while (str_remain--) backone();
|
||
}
|
||
}
|
||
if (l_completion.size()) l_completion.clear();
|
||
break;
|
||
case 0x0a: /* Give a new Line */
|
||
outc('\n');
|
||
break;
|
||
case '': // FAKE CTRL-C
|
||
*line = 0; // reset the line.
|
||
if (l_completion.size()) l_completion.clear(); //reset the completion list.
|
||
size = 0; // stop the next loop
|
||
str_len = 0; // prevent multiple adds of the same line
|
||
DOS_BreakFlag = false; // clear break flag so the next program doesn't get hit with it
|
||
DOS_BreakConioFlag = false;
|
||
break;
|
||
case 0x0d: /* Don't care, and return */
|
||
if(!echo) { outc('\r'); outc('\n'); }
|
||
size=0; //Kill the while loop
|
||
break;
|
||
case 0x9400: /* Ctrl-Tab */
|
||
{
|
||
if (!l_completion.size()) {
|
||
if (BuildCompletions(line, str_len))
|
||
it_completion = l_completion.begin();
|
||
else
|
||
break;
|
||
}
|
||
size_t w_count, p_count, col;
|
||
unsigned int max[15], total, tcols=IS_PC98_ARCH?80:real_readw(BIOSMEM_SEG,BIOSMEM_NB_COLS);
|
||
if (!tcols) tcols=80;
|
||
int mrow=tcols>80?15:10;
|
||
for (col=mrow; col>0; col--) {
|
||
for (int i=0; i<mrow; i++) max[i]=2;
|
||
if (col==1) break;
|
||
w_count=0;
|
||
for (std::list<std::string>::iterator source = l_completion.begin(); source != l_completion.end(); ++source) {
|
||
std::string name = source->c_str();
|
||
if (name.size()+2>max[w_count%col]) max[w_count%col]=(unsigned int)(name.size()+2);
|
||
++w_count;
|
||
}
|
||
total=0;
|
||
for (size_t i=0; i<col; i++) total+=max[i];
|
||
if (total<tcols) break;
|
||
}
|
||
w_count = p_count = 0;
|
||
bool lastcr=false;
|
||
if (l_completion.size()) {WriteOut_NoParsing("\n");lastcr=true;}
|
||
for (std::list<std::string>::iterator source = l_completion.begin(); source != l_completion.end(); ++source) {
|
||
std::string name = source->c_str();
|
||
if (col==1) {
|
||
WriteOut("%s\n", name.c_str());
|
||
lastcr=true;
|
||
p_count++;
|
||
} else {
|
||
WriteOut("%s%-*s", name.c_str(), max[w_count % col]-name.size(), "");
|
||
lastcr=false;
|
||
}
|
||
if (col>1) {
|
||
++w_count;
|
||
if (w_count % col == 0) {p_count++;WriteOut_NoParsing("\n");lastcr=true;}
|
||
}
|
||
size_t GetPauseCount();
|
||
if (p_count>GetPauseCount()) {
|
||
WriteOut(MSG_Get("SHELL_CMD_PAUSE"));
|
||
lastcr=false;
|
||
w_count = p_count = 0;
|
||
uint8_t c;uint16_t n=1;
|
||
DOS_ReadFile(STDIN,&c,&n);
|
||
if (c==3) {ctrlbrk=false;WriteOut("^C\r\n");break;}
|
||
if (c==0) DOS_ReadFile(STDIN,&c,&n);
|
||
}
|
||
}
|
||
if (l_completion.size()) {
|
||
if (!lastcr) WriteOut_NoParsing("\n");
|
||
ShowPrompt();
|
||
WriteOut("%s", line);
|
||
}
|
||
break;
|
||
}
|
||
case'\t':
|
||
if (l_completion.size()) {
|
||
++it_completion;
|
||
if (it_completion == l_completion.end()) it_completion = l_completion.begin();
|
||
} else if (BuildCompletions(line, str_len))
|
||
it_completion = l_completion.begin();
|
||
else
|
||
break;
|
||
|
||
if (l_completion.size() && it_completion->length()) {
|
||
for (;str_index > completion_index; str_index--) {
|
||
// removes all characters
|
||
backone(); outc(' '); backone();
|
||
}
|
||
|
||
strcpy(&line[completion_index], it_completion->c_str());
|
||
len = (uint16_t)it_completion->length();
|
||
str_len = str_index = (Bitu)(completion_index + len);
|
||
size = (unsigned int)CMD_MAXLINE - str_index - 2u;
|
||
DOS_WriteFile(STDOUT, (uint8_t *)it_completion->c_str(), &len);
|
||
}
|
||
break;
|
||
case 0x1b: /* ESC */
|
||
// NTS: According to real PC-98 DOS:
|
||
// If DOSKEY is loaded, ESC clears the prompt
|
||
// If DOSKEY is NOT loaded, ESC does nothing. In fact, after ESC,
|
||
// the next character input is thrown away before resuming normal keyboard input.
|
||
//
|
||
// DOSBox / DOSBox-X have always acted as if DOSKEY is loaded in a fashion, so
|
||
// we'll emulate the PC-98 DOSKEY behavior here.
|
||
//
|
||
// DOSKEY on PC-98 is able to clear the whole prompt and even bring the cursor
|
||
// back up to the first line if the input crosses multiple lines.
|
||
|
||
// NTS: According to real IBM/Microsoft PC/AT DOS:
|
||
// If DOSKEY is loaded, ESC clears the prompt
|
||
// If DOSKEY is NOT loaded, ESC prints a backslash and goes to the next line.
|
||
// The Windows 95 version of DOSKEY puts the cursor at a horizontal position
|
||
// that matches the DOS prompt (not emulated here).
|
||
//
|
||
// DOSBox / DOSBox-X have always acted as if DOSKEY is loaded in a fashion, so
|
||
// we'll emulate DOSKEY behavior here.
|
||
while (str_index < str_len) {
|
||
outc(' ');
|
||
str_index++;
|
||
}
|
||
while (str_index > 0) {
|
||
backone();
|
||
outc(' ');
|
||
backone();
|
||
MoveCaretBackwards();
|
||
str_index--;
|
||
}
|
||
|
||
*line = 0; // reset the line.
|
||
if (l_completion.size()) l_completion.clear(); //reset the completion list.
|
||
str_index = 0;
|
||
str_len = 0;
|
||
break;
|
||
default:
|
||
if (cr >= 0x100) break;
|
||
if (l_completion.size()) l_completion.clear();
|
||
if(str_index < str_len && !INT10_GetInsertState()) { //mem_readb(BIOS_KEYBOARD_FLAGS1)&0x80) dev_con.h ?
|
||
outc(' ');//move cursor one to the right.
|
||
uint16_t a = str_len - str_index;
|
||
uint8_t* text=reinterpret_cast<uint8_t*>(&line[str_index]);
|
||
DOS_WriteFile(STDOUT,text,&a);//write buffer to screen
|
||
backone();//undo the cursor the right.
|
||
for(Bitu i=str_len;i>str_index;i--) {
|
||
line[i]=line[i-1]; //move internal buffer
|
||
backone(); //move cursor back (from write buffer to screen)
|
||
}
|
||
line[++str_len]=0;//new end (as the internal buffer moved one place to the right
|
||
size--;
|
||
}
|
||
|
||
line[str_index]=(char)(cr&0xFF);
|
||
str_index ++;
|
||
if (str_index > str_len){
|
||
line[str_index] = '\0';
|
||
str_len++;
|
||
size--;
|
||
}
|
||
DOS_WriteFile(STDOUT,&c,&n);
|
||
break;
|
||
}
|
||
}
|
||
|
||
inshell = false;
|
||
if (!str_len) return;
|
||
str_len++;
|
||
|
||
// remove current command from history if it's there
|
||
if (current_hist) {
|
||
// current_hist=false;
|
||
l_history.pop_front();
|
||
}
|
||
|
||
// add command line to history. Win95 behavior with DOSKey suggests
|
||
// that the original string is preserved, not the expanded string.
|
||
l_history.emplace_front(line); it_history = l_history.begin();
|
||
if (l_completion.size()) l_completion.clear();
|
||
|
||
/* DOS %variable% substitution */
|
||
ProcessCmdLineEnvVarStitution(line);
|
||
}
|
||
|
||
void XMS_DOS_LocalA20DisableIfNotEnabled(void);
|
||
|
||
/* Note: Buffer pointed to by "line" must be at least CMD_MAXLINE+1 bytes long! */
|
||
void DOS_Shell::ProcessCmdLineEnvVarStitution(char *line) {
|
||
// Wengier: Rewrote the code using regular expressions (a lot shorter)
|
||
// Tested by both myself and kcgen with various boundary cases
|
||
|
||
/* DOS7/Win95 testing:
|
||
*
|
||
* "%" = "%"
|
||
* "%%" = "%"
|
||
* "% %" = ""
|
||
* "% %" = ""
|
||
* "% % %" = " %"
|
||
*
|
||
* ^ WTF?
|
||
*
|
||
* So the below code has funny conditions to match Win95's weird rules on what
|
||
* consitutes valid or invalid %variable% names. */
|
||
|
||
/* continue scanning for the ending '%'. variable names are apparently meant to be
|
||
* alphanumeric, start with a letter, without spaces (if Windows 95 COMMAND.COM is
|
||
* any good example). If it doesn't end in '%' or is broken by space or starts with
|
||
* a number, substitution is not carried out. In the middle of the variable name
|
||
* it seems to be acceptable to use hyphens.
|
||
*
|
||
* since spaces break up a variable name to prevent substitution, these commands
|
||
* act differently from one another:
|
||
*
|
||
* C:\>echo %PATH%
|
||
* C:\DOS;C:\WINDOWS
|
||
*
|
||
* C:\>echo %PATH %
|
||
* %PATH % */
|
||
|
||
/* initial scan: is there anything to substitute? */
|
||
/* if not, then just return without modifying "line" */
|
||
/* not really needed but keep this code as is for now */
|
||
char *r=line;
|
||
while (*r != 0 && *r != '%') r++;
|
||
if (*r != '%') return;
|
||
|
||
/* if the incoming string is already too long, then that's a problem too! */
|
||
if (((size_t)(r+1-line)) >= CMD_MAXLINE) {
|
||
LOG_MSG("DOS_Shell::ProcessCmdLineEnvVarStitution WARNING incoming string to substitute is already too long!\n");
|
||
*line = 0; /* clear string (C-string chop with NUL) */
|
||
WriteOut("Command input error: string expansion overflow\n");
|
||
return;
|
||
}
|
||
|
||
// Begin the process of shell variable substitutions using regular expressions
|
||
// Variable names must start with non-digits. Space and special symbols like _ and ~ are apparently valid too (MS-DOS 7/Windows 9x).
|
||
constexpr char surrogate_percent = 8;
|
||
const static std::regex re("\\%([^%0-9][^%]*)?%");
|
||
std::string text = line;
|
||
std::smatch match;
|
||
/* Iterate over potential %var1%, %var2%, etc matches found in the text string */
|
||
while (std::regex_search(text, match, re)) {
|
||
std::string variable_name;
|
||
variable_name = match[1].str();
|
||
if (!variable_name.size()) {
|
||
/* Replace %% with the character "surrogate_percent", then (eventually) % */
|
||
text.replace(match[0].first, match[0].second, std::string(1, surrogate_percent));
|
||
continue;
|
||
}
|
||
/* Trim preceding spaces from the variable name */
|
||
variable_name.erase(0, variable_name.find_first_not_of(' '));
|
||
std::string variable_value;
|
||
if (variable_name.size() && GetEnvStr(variable_name.c_str(), variable_value)) {
|
||
const size_t equal_pos = variable_value.find_first_of('=');
|
||
/* Replace the original %var% with its corresponding value from the environment */
|
||
const std::string replacement = equal_pos != std::string::npos ? variable_value.substr(equal_pos + 1) : "";
|
||
text.replace(match[0].first, match[0].second, replacement);
|
||
} else
|
||
text.replace(match[0].first, match[0].second, "");
|
||
}
|
||
std::replace(text.begin(), text.end(), surrogate_percent, '%');
|
||
assert(text.size() <= CMD_MAXLINE);
|
||
strcpy(line, text.c_str());
|
||
}
|
||
|
||
int infix=-1;
|
||
std::string full_arguments = "";
|
||
bool dos_a20_disable_on_exec=false;
|
||
extern bool packerr, mountwarning, nowarn;
|
||
bool DOS_Shell::Execute(char* name, const char* args) {
|
||
/* return true => don't check for hardware changes in do_command
|
||
* return false => check for hardware changes in do_command */
|
||
char fullname[DOS_PATHLENGTH+4]; //stores results from Which
|
||
const char* p_fullname;
|
||
char line[CMD_MAXLINE];
|
||
if(strlen(args)!= 0){
|
||
if(*args != ' '){ //put a space in front
|
||
line[0]=' ';line[1]=0;
|
||
strncat(line,args,CMD_MAXLINE-2);
|
||
line[CMD_MAXLINE-1]=0;
|
||
}
|
||
else
|
||
{
|
||
safe_strncpy(line,args,CMD_MAXLINE);
|
||
}
|
||
}else{
|
||
line[0]=0;
|
||
}
|
||
|
||
const Section_prop* sec = static_cast<Section_prop*>(control->GetSection("dos"));
|
||
/* check for a drive change */
|
||
if (((strcmp(name + 1, ":") == 0) || (strcmp(name + 1, ":\\") == 0)) && isalpha(*name) && !control->SecureMode())
|
||
{
|
||
if (strrchr_dbcs(name,'\\')) { WriteOut(MSG_Get("SHELL_EXECUTE_ILLEGAL_COMMAND"),name); return true; }
|
||
if (!DOS_SetDrive(toupper(name[0])-'A')) {
|
||
#ifdef WIN32
|
||
if(!sec->Get_bool("automount")) { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
|
||
// automount: attempt direct letter to drive map.
|
||
int type=GetDriveType(name);
|
||
if(mountwarning && type==DRIVE_FIXED && (strcasecmp(name,"C:")==0)) WriteOut(MSG_Get("PROGRAM_MOUNT_WARNING_WIN"));
|
||
first_1:
|
||
if(type==DRIVE_CDROM) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_CDROM"),toupper(name[0]));
|
||
else if(type==DRIVE_REMOVABLE && (strcasecmp(name,"A:")==0||strcasecmp(name,"B:")==0)) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_FLOPPY"),toupper(name[0]));
|
||
else if(type==DRIVE_REMOVABLE) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_REMOVABLE"),toupper(name[0]));
|
||
else if(type==DRIVE_REMOTE) WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_ACCESS_NETWORK"),toupper(name[0]));
|
||
else if((type==DRIVE_FIXED)||(type==DRIVE_RAMDISK)) WriteOut(MSG_Get(mountwarning?"SHELL_EXECUTE_DRIVE_ACCESS_FIXED":"SHELL_EXECUTE_DRIVE_ACCESS_FIXED_LESS"),toupper(name[0]));
|
||
else { WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true; }
|
||
|
||
first_2:
|
||
uint8_t c;uint16_t n=1;
|
||
DOS_ReadFile (STDIN,&c,&n);
|
||
do switch (c) {
|
||
case 'n': case 'N':
|
||
{
|
||
DOS_WriteFile (STDOUT,&c, &n);
|
||
DOS_ReadFile (STDIN,&c,&n);
|
||
do switch (c) {
|
||
case 0xD: WriteOut("\n\n"); WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0])); return true;
|
||
case 0x3: return true;
|
||
case 0x8: WriteOut("\b \b"); goto first_2;
|
||
} while (DOS_ReadFile (STDIN,&c,&n));
|
||
}
|
||
case 'y': case 'Y':
|
||
{
|
||
DOS_WriteFile (STDOUT,&c, &n);
|
||
DOS_ReadFile (STDIN,&c,&n);
|
||
do switch (c) {
|
||
case 0xD: WriteOut("\n"); goto continue_1;
|
||
case 0x3: return true;
|
||
case 0x8: WriteOut("\b \b"); goto first_2;
|
||
} while (DOS_ReadFile (STDIN,&c,&n));
|
||
}
|
||
case 0x3: return true;
|
||
case 0xD: WriteOut("\n"); goto first_1;
|
||
case '\t': case 0x08: goto first_2;
|
||
default:
|
||
{
|
||
DOS_WriteFile (STDOUT,&c, &n);
|
||
DOS_ReadFile (STDIN,&c,&n);
|
||
do switch (c) {
|
||
case 0xD: WriteOut("\n");goto first_1;
|
||
case 0x3: return true;
|
||
case 0x8: WriteOut("\b \b"); goto first_2;
|
||
} while (DOS_ReadFile (STDIN,&c,&n));
|
||
goto first_2;
|
||
}
|
||
} while (DOS_ReadFile (STDIN,&c,&n));
|
||
|
||
continue_1:
|
||
|
||
char mountstring[DOS_PATHLENGTH+CROSS_LEN+20];
|
||
sprintf(mountstring,"MOUNT %s ",name);
|
||
|
||
if(type==DRIVE_CDROM) strcat(mountstring,"-t cdrom ");
|
||
else if(type==DRIVE_REMOVABLE && (strcasecmp(name,"A:")==0||strcasecmp(name,"B:")==0)) strcat(mountstring,"-t floppy ");
|
||
strcat(mountstring,name);
|
||
strcat(mountstring,"\\");
|
||
// if(GetDriveType(name)==5) strcat(mountstring," -ioctl");
|
||
nowarn=true;
|
||
this->ParseLine(mountstring);
|
||
nowarn=false;
|
||
//failed:
|
||
if (!DOS_SetDrive(toupper(name[0])-'A'))
|
||
#endif
|
||
WriteOut(MSG_Get("SHELL_EXECUTE_DRIVE_NOT_FOUND"),toupper(name[0]));
|
||
}
|
||
return true;
|
||
}
|
||
/* Check for a full name */
|
||
p_fullname = Which(name);
|
||
if (!p_fullname) return false;
|
||
strcpy(fullname,p_fullname);
|
||
const char* extension = strrchr(fullname,'.');
|
||
|
||
/*always disallow files without extension from being executed. */
|
||
/*only internal commands can be run this way and they never get in this handler */
|
||
if(extension == 0)
|
||
{
|
||
//Check if the result will fit in the parameters. Else abort
|
||
if(strlen(fullname) >( DOS_PATHLENGTH - 1) ) return false;
|
||
char temp_name[DOS_PATHLENGTH + 4];
|
||
const char* temp_fullname;
|
||
//try to add .com, .exe and .bat extensions to filename
|
||
|
||
strcpy(temp_name,fullname);
|
||
strcat(temp_name,".COM");
|
||
temp_fullname=Which(temp_name);
|
||
if (temp_fullname) { extension=".com";strcpy(fullname,temp_fullname); }
|
||
|
||
else
|
||
{
|
||
strcpy(temp_name,fullname);
|
||
strcat(temp_name,".EXE");
|
||
temp_fullname=Which(temp_name);
|
||
if (temp_fullname) { extension=".exe";strcpy(fullname,temp_fullname);}
|
||
|
||
else
|
||
{
|
||
strcpy(temp_name,fullname);
|
||
strcat(temp_name,".BAT");
|
||
temp_fullname=Which(temp_name);
|
||
if (temp_fullname) { extension=".bat";strcpy(fullname,temp_fullname);}
|
||
|
||
else
|
||
{
|
||
return false;
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
if (strcasecmp(extension, ".bat") == 0)
|
||
{ /* Run the .bat file */
|
||
/* delete old batch file if call is not active*/
|
||
bool temp_echo=echo; /*keep the current echostate (as delete bf might change it )*/
|
||
if(bf && !call) delete bf;
|
||
bf=new BatchFile(this,fullname,name,line);
|
||
echo=temp_echo; //restore it.
|
||
}
|
||
else
|
||
{ /* only .bat .exe .com extensions maybe be executed by the shell */
|
||
if(strcasecmp(extension, ".com") !=0)
|
||
{
|
||
if(strcasecmp(extension, ".exe") !=0) return false;
|
||
}
|
||
/* Run the .exe or .com file from the shell */
|
||
/* Allocate some stack space for tables in physical memory */
|
||
reg_sp-=0x200;
|
||
//Add Parameter block
|
||
DOS_ParamBlock block(SegPhys(ss)+reg_sp);
|
||
block.Clear();
|
||
//Add a filename
|
||
RealPt file_name=RealMakeSeg(ss,reg_sp+0x20);
|
||
MEM_BlockWrite(Real2Phys(file_name),fullname,(Bitu)(strlen(fullname)+1));
|
||
|
||
/* HACK: Store full commandline for mount and imgmount */
|
||
full_arguments.assign(line);
|
||
|
||
/* Fill the command line */
|
||
CommandTail cmdtail;
|
||
cmdtail.count = 0;
|
||
memset(&cmdtail.buffer,0,CTBUF); //Else some part of the string is unitialized (valgrind)
|
||
if (strlen(line)>=CTBUF) line[CTBUF-1]=0;
|
||
cmdtail.count=(uint8_t)strlen(line);
|
||
memcpy(cmdtail.buffer,line,strlen(line));
|
||
cmdtail.buffer[strlen(line)]=0xd;
|
||
/* Copy command line in stack block too */
|
||
MEM_BlockWrite(SegPhys(ss)+reg_sp+0x100,&cmdtail,CTBUF+1);
|
||
|
||
/* Split input line up into parameters, using a few special rules, most notable the one for /AAA => A\0AA
|
||
* Qbix: It is extremly messy, but this was the only way I could get things like /:aa and :/aa to work correctly */
|
||
|
||
//Prepare string first
|
||
char parseline[258] = { 0 };
|
||
for(char *pl = line,*q = parseline; *pl ;pl++,q++) {
|
||
if (*pl == '=' || *pl == ';' || *pl ==',' || *pl == '\t' || *pl == ' ') *q = 0; else *q = *pl; //Replace command seperators with 0.
|
||
} //No end of string \0 needed as parseline is larger than line
|
||
|
||
for(char* p = parseline; (p-parseline) < 250 ;p++) { //Stay relaxed within boundaries as we have plenty of room
|
||
if (*p == '/') { //Transform /Hello into H\0ello
|
||
*p = 0;
|
||
p++;
|
||
while ( *p == 0 && (p-parseline) < 250) p++; //Skip empty fields
|
||
if ((p-parseline) < 250) { //Found something. Lets get the first letter and break it up
|
||
p++;
|
||
memmove(static_cast<void*>(p + 1),static_cast<void*>(p),(250u-(unsigned int)(p-parseline)));
|
||
if ((p-parseline) < 250) *p = 0;
|
||
}
|
||
}
|
||
}
|
||
parseline[255] = parseline[256] = parseline[257] = 0; //Just to be safe.
|
||
|
||
/* Parse FCB (first two parameters) and put them into the current DOS_PSP */
|
||
uint8_t add;
|
||
uint16_t skip = 0;
|
||
//find first argument, we end up at parseline[256] if there is only one argument (similar for the second), which exists and is 0.
|
||
while(skip < 256 && parseline[skip] == 0) skip++;
|
||
FCB_Parsename(dos.psp(),0x5C,0x01,parseline + skip,&add);
|
||
skip += add;
|
||
|
||
//Move to next argument if it exists
|
||
while(parseline[skip] != 0) skip++; //This is safe as there is always a 0 in parseline at the end.
|
||
while(skip < 256 && parseline[skip] == 0) skip++; //Which is higher than 256
|
||
FCB_Parsename(dos.psp(),0x6C,0x01,parseline + skip,&add);
|
||
|
||
block.exec.fcb1=RealMake(dos.psp(),0x5C);
|
||
block.exec.fcb2=RealMake(dos.psp(),0x6C);
|
||
/* Set the command line in the block and save it */
|
||
block.exec.cmdtail=RealMakeSeg(ss,reg_sp+0x100);
|
||
block.SaveData();
|
||
#if 0
|
||
/* Save CS:IP to some point where i can return them from */
|
||
uint32_t oldeip=reg_eip;
|
||
uint16_t oldcs=SegValue(cs);
|
||
RealPt newcsip=CALLBACK_RealPointer(call_shellstop);
|
||
SegSet16(cs,RealSeg(newcsip));
|
||
reg_ip=RealOff(newcsip);
|
||
#endif
|
||
packerr=false;
|
||
/* Start up a dos execute interrupt */
|
||
reg_ax=0x4b00;
|
||
//Filename pointer
|
||
SegSet16(ds,SegValue(ss));
|
||
reg_dx=RealOff(file_name);
|
||
//Paramblock
|
||
SegSet16(es,SegValue(ss));
|
||
reg_bx=reg_sp;
|
||
SETFLAGBIT(IF,false);
|
||
CALLBACK_RunRealInt(0x21);
|
||
/* Restore CS:IP and the stack */
|
||
reg_sp+=0x200;
|
||
#if 0
|
||
reg_eip=oldeip;
|
||
SegSet16(cs,oldcs);
|
||
#endif
|
||
if (packerr&&infix<0&&sec->Get_bool("autoa20fix")) {
|
||
LOG(LOG_DOSMISC,LOG_DEBUG)("Attempting autoa20fix workaround for EXEPACK error");
|
||
if (autofixwarn==1||autofixwarn==3) WriteOut("\r\n\033[41;1m\033[1;37;1mDOSBox-X\033[0m Failed to load the executable\r\n\033[41;1m\033[37;1mDOSBox-X\033[0m Now try again with A20 fix...\r\n");
|
||
infix=0;
|
||
dos_a20_disable_on_exec=true;
|
||
Execute(name, args);
|
||
dos_a20_disable_on_exec=false;
|
||
infix=-1;
|
||
} else if (packerr&&infix<1&&sec->Get_bool("autoloadfix")) {
|
||
uint16_t segment;
|
||
uint16_t blocks = (uint16_t)(1); /* start with one paragraph, resize up later. see if it comes up below the 64KB mark */
|
||
if (DOS_AllocateMemory(&segment,&blocks)) {
|
||
DOS_MCB mcb((uint16_t)(segment-1));
|
||
if (segment < 0x1000) {
|
||
uint16_t needed = 0x1000 - segment;
|
||
DOS_ResizeMemory(segment,&needed);
|
||
}
|
||
mcb.SetPSPSeg(0x40); /* FIXME: Wouldn't 0x08, a magic value used to show ownership by MS-DOS, be more appropriate here? */
|
||
LOG(LOG_DOSMISC,LOG_DEBUG)("Attempting autoloadfix workaround for EXEPACK error");
|
||
if (autofixwarn==2||autofixwarn==3) WriteOut("\r\n\033[41;1m\033[1;37;1mDOSBox-X\033[0m Failed to load the executable\r\n\033[41;1m\033[37;1mDOSBox-X\033[0m Now try again with LOADFIX...\r\n");
|
||
infix=1;
|
||
Execute(name, args);
|
||
infix=-1;
|
||
DOS_FreeMemory(segment);
|
||
}
|
||
} else if (packerr&&infix<2&&!autofixwarn) {
|
||
WriteOut("Packed file is corrupt");
|
||
}
|
||
packerr=false;
|
||
}
|
||
return true; //Executable started
|
||
}
|
||
|
||
|
||
static const char * bat_ext=".BAT";
|
||
static const char * com_ext=".COM";
|
||
static const char * exe_ext=".EXE";
|
||
static char which_ret[DOS_PATHLENGTH+4], s_ret[DOS_PATHLENGTH+4];
|
||
|
||
char * DOS_Shell::Which(char * name) {
|
||
size_t name_len = strlen(name);
|
||
if(name_len >= DOS_PATHLENGTH) return 0;
|
||
|
||
/* Parse through the Path to find the correct entry */
|
||
/* Check if name is already ok but just misses an extension */
|
||
|
||
if (DOS_FileExists(name)) return name;
|
||
upcase(name);
|
||
if (DOS_FileExists(name)) return name;
|
||
/* try to find .com .exe .bat */
|
||
strcpy(which_ret,name);
|
||
strcat(which_ret,com_ext);
|
||
if (DOS_FileExists(which_ret)) return which_ret;
|
||
strcpy(which_ret,name);
|
||
strcat(which_ret,exe_ext);
|
||
if (DOS_FileExists(which_ret)) return which_ret;
|
||
strcpy(which_ret,name);
|
||
strcat(which_ret,bat_ext);
|
||
if (DOS_FileExists(which_ret)) return which_ret;
|
||
|
||
|
||
/* No Path in filename look through path environment string */
|
||
char path[DOS_PATHLENGTH];std::string temp;
|
||
if (!GetEnvStr("PATH",temp)) return 0;
|
||
const char * pathenv=temp.c_str();
|
||
if (!pathenv) return 0;
|
||
pathenv = strchr(pathenv,'=');
|
||
if (!pathenv) return 0;
|
||
pathenv++;
|
||
while (*pathenv) {
|
||
/* remove ; and ;; at the beginning. (and from the second entry etc) */
|
||
while(*pathenv == ';')
|
||
pathenv++;
|
||
|
||
/* get next entry */
|
||
Bitu i_path = 0; /* reset writer */
|
||
while(*pathenv && (*pathenv !=';') && (i_path < DOS_PATHLENGTH) )
|
||
path[i_path++] = *pathenv++;
|
||
|
||
if(i_path == DOS_PATHLENGTH) {
|
||
/* If max size. move till next ; and terminate path */
|
||
while(*pathenv && (*pathenv != ';'))
|
||
pathenv++;
|
||
path[DOS_PATHLENGTH - 1] = 0;
|
||
} else path[i_path] = 0;
|
||
|
||
int k=0;
|
||
for (int i=0;i<(int)strlen(path);i++)
|
||
if (path[i]!='\"')
|
||
path[k++]=path[i];
|
||
path[k]=0;
|
||
|
||
/* check entry */
|
||
if(size_t len = strlen(path)){
|
||
if(len >= (DOS_PATHLENGTH - 2)) continue;
|
||
|
||
if (uselfn&&len>3) {
|
||
if (path[len - 1]=='\\') path[len - 1]=0;
|
||
if (DOS_GetSFNPath(("\""+std::string(path)+"\"").c_str(), s_ret, false))
|
||
strcpy(path, s_ret);
|
||
len = strlen(path);
|
||
}
|
||
|
||
if(path[len - 1] != '\\') {
|
||
strcat(path,"\\");
|
||
len++;
|
||
}
|
||
|
||
//If name too long =>next
|
||
if((name_len + len + 1) >= DOS_PATHLENGTH) continue;
|
||
strcat(path,strchr(name, ' ')?("\""+std::string(name)+"\"").c_str():name);
|
||
|
||
strcpy(which_ret,path);
|
||
if (DOS_FileExists(which_ret)) return strchr(which_ret, '\"')&&DOS_GetSFNPath(which_ret, s_ret, false)?s_ret:which_ret;
|
||
strcpy(which_ret,path);
|
||
strcat(which_ret,com_ext);
|
||
if (DOS_FileExists(which_ret)) return strchr(which_ret, '\"')&&DOS_GetSFNPath(which_ret, s_ret, false)?s_ret:which_ret;
|
||
strcpy(which_ret,path);
|
||
strcat(which_ret,exe_ext);
|
||
if (DOS_FileExists(which_ret)) return strchr(which_ret, '\"')&&DOS_GetSFNPath(which_ret, s_ret, false)?s_ret:which_ret;
|
||
strcpy(which_ret,path);
|
||
strcat(which_ret,bat_ext);
|
||
if (DOS_FileExists(which_ret)) return strchr(which_ret, '\"')&&DOS_GetSFNPath(which_ret, s_ret, false)?s_ret:which_ret;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|