/* * Copyright (C) 2002-2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "dosbox.h" #if C_MODEM #include #include #include #include "SDL_net.h" #include "inout.h" #include "mixer.h" #include "pic.h" #include "setup.h" #include "programs.h" #include "debug.h" #include "timer.h" #include "callback.h" #include "math.h" #include "regs.h" #include "serialport.h" #define MODEMSPD 57600 #define SREGS 100 static Bit8u tmpbuf[QUEUE_SIZE]; struct ModemHd { char cmdbuf[QUEUE_SIZE]; bool commandmode; bool answermode; bool echo,response,numericresponse; bool telnetmode; Bitu cmdpause; Bits ringtimer; Bits ringcount; Bitu plusinc; Bitu cmdpos; Bit8u reg[SREGS]; TCPsocket incomingsocket; TCPsocket socket; TCPsocket listensocket; SDLNet_SocketSet socketset; IPaddress openip; Bitu comport; Bitu listenport; char remotestr[4096]; bool dialing; double f1, f2; Bitu diallen; Bitu dialpos; char dialstr[256]; // TODO: Re-enable dialtons //MIXER_Channel * chan; }; enum ResTypes { ResNONE, ResOK,ResERROR, ResCONNECT,ResRING, ResBUSY,ResNODIALTONE,ResNOCARRIER, }; #define TEL_CLIENT 0 #define TEL_SERVER 1 struct telnetClient { bool binary[2]; bool echo[2]; bool supressGA[2]; bool timingMark[2]; bool inIAC; bool recCommand; Bit8u command; }; static Bitu call_int14; #if 1 static void toUpcase(char *buffer) { Bitu i=0; while (buffer[i] != 0) { buffer[i] = toupper(buffer[i]); i++; } } class CSerialModem : public CSerial { public: ModemHd mhd; CSerialModem(Bit16u baseAddr, Bit8u initIrq, Bit32u initBps, const char *remotestr = NULL, Bit16u lport = 27) : CSerial(baseAddr, initIrq, initBps) { mhd.cmdpos = 0; mhd.commandmode = true; mhd.plusinc = 0; mhd.incomingsocket = 0; mhd.answermode = false; memset(&mhd.reg,0,sizeof(mhd.reg)); mhd.cmdpause = 0; mhd.echo = true; mhd.response = true; mhd.numericresponse = false; /* Default to direct null modem connection. Telnet mode interprets IAC codes */ mhd.telnetmode = false; /* Bind the modem to the correct serial port */ //strcpy(mhd.remotestr, remotestr); /* Initialize the sockets and setup the listening port */ mhd.socketset = SDLNet_AllocSocketSet(1); if (!mhd.socketset) { LOG_MSG("MODEM:Can't open socketset:%s",SDLNet_GetError()); //TODO Should probably just exit return; } mhd.socket=0; mhd.listenport=lport; if (mhd.listenport) { IPaddress listen_ip; SDLNet_ResolveHost(&listen_ip, NULL, mhd.listenport); mhd.listensocket=SDLNet_TCP_Open(&listen_ip); if (!mhd.listensocket) LOG_MSG("MODEM:Can't open listen port:%s",SDLNet_GetError()); } else mhd.listensocket=0; // TODO: Fix dialtones if requested //mhd.chan=MIXER_AddChannel((MIXER_MixHandler)this->MODEM_CallBack,8000,"MODEM"); //MIXER_Enable(mhd.chan,false); //MIXER_SetMode(mhd.chan,MIXER_16MONO); } void SendLine(const char *line) { rqueue->addb(0xd); rqueue->addb(0xa); rqueue->adds((Bit8u *)line,strlen(line)); rqueue->addb(0xd); rqueue->addb(0xa); } void SendRes(ResTypes response) { char * string;char * code; switch (response) { case ResNONE: return; case ResOK:string="OK";code="0";break; case ResERROR:string="ERROR";code="4";break; case ResRING:string="RING";code="2"; case ResNODIALTONE:string="NO DIALTONE";code="6";break; case ResNOCARRIER:string="NO CARRIER";code="3";break; case ResCONNECT:string="CONNECT 57600";code="1";break; } rqueue->addb(0xd);rqueue->addb(0xa); rqueue->adds((Bit8u *)string,strlen(string)); rqueue->addb(0xd);rqueue->addb(0xa); } void Send(Bit8u val) { tqueue->addb(val); } Bit8u Recv(Bit8u val) { return rqueue->getb(); } void openConnection(void) { if (mhd.socket) { LOG_MSG("Huh? already connected"); SDLNet_TCP_DelSocket(mhd.socketset,mhd.socket); SDLNet_TCP_Close(mhd.socket); } mhd.socket = SDLNet_TCP_Open(&openip); if (mhd.socket) { SDLNet_TCP_AddSocket(mhd.socketset,mhd.socket); SendRes(ResCONNECT); mhd.commandmode = false; memset(&telClient, 0, sizeof(telClient)); updatemstatus(); } else { SendRes(ResNODIALTONE); } } void updatemstatus(void) { Bit8u ms=0; //Check for data carrier, a connection that is if (mhd.incomingsocket) ms|=MS_RI; if (mhd.socket) ms|=MS_DCD; if (!mhd.commandmode) ms|=MS_DSR; //Check for DTR reply with DSR // if (cport->mctrl & MC_DTR) ms|=MS_DSR; //Check for RTS reply with CTS if (mctrl & MC_RTS) ms|=MS_CTS; SetModemStatus(ms); } bool Dial(char * host) { /* Scan host for port */ Bit16u port; char * hasport=strrchr(host,':'); if (hasport) { *hasport++=0; port=(Bit16u)atoi(hasport); } else port=23; /* Resolve host we're gonna dial */ LOG_MSG("host %s port %x",host,port); if (!SDLNet_ResolveHost(&openip,host,port)) { openConnection(); return true; } else { LOG_MSG("Failed to resolve host %s:%s",host,SDLNet_GetError()); SendRes(ResNOCARRIER); return false; } } void AcceptIncomingCall(void) { assert(!mhd.socket); mhd.socket=mhd.incomingsocket; SDLNet_TCP_AddSocket(mhd.socketset,mhd.socket); SendRes(ResCONNECT); LOG_MSG("Connected!\n"); mhd.incomingsocket = 0; mhd.commandmode = false; } Bitu ScanNumber(char * & scan) { Bitu ret=0; while (char c=*scan) { if (c>='0' && c<='9') { ret*=10; ret+=c-'0'; scan++; } else break; } return ret; } void HangUp(void) { SendRes(ResNOCARRIER); SDLNet_TCP_DelSocket(mhd.socketset,mhd.socket); SDLNet_TCP_Close(mhd.socket); mhd.socket=0; mhd.commandmode = true; updatemstatus(); } void DoCommand() { mhd.cmdbuf[mhd.cmdpos] = 0; mhd.cmdpos = 0; //Reset for next command toUpcase(mhd.cmdbuf); LOG_MSG("Modem Sent Command: %s\n", mhd.cmdbuf); /* Check for empty line, stops dialing and autoanswer */ if (!mhd.cmdbuf[0]) { if(!mhd.dialing) { mhd.answermode = false; goto ret_none; } else { //MIXER_Enable(mhd.chan,false); mhd.dialing = false; SendRes(ResNOCARRIER); goto ret_none; } } /* AT command set interpretation */ if ((mhd.cmdbuf[0] != 'A') || (mhd.cmdbuf[1] != 'T')) goto ret_error; /* Check for dial command */ if(strncmp(mhd.cmdbuf,"ATD3",3)==0) { char * foundstr=&mhd.cmdbuf[3]; if (*foundstr=='T' || *foundstr=='P') foundstr++; /* Small protection against empty line */ if (!foundstr[0]) goto ret_error; if (strlen(foundstr) >= 12){ // Check if supplied parameter only consists of digits bool isNum = true; for (Bitu i=0; i '9') isNum = false; if (isNum) { // Parameter is a number with at least 12 digits => this cannot be a valid IP/name // Transform by adding dots char buffer[128]; Bitu j = 0; for (Bitu i=0; i12) buffer[j++] = ':'; } buffer[j] = 0; foundstr = buffer; } } Dial(foundstr); goto ret_none; } char * scanbuf; scanbuf=&mhd.cmdbuf[2];char chr;Bitu num; while (chr=*scanbuf++) { switch (chr) { case 'I': //Some strings about firmware switch (num=ScanNumber(scanbuf)) { case 3:SendLine("DosBox Emulated Modem Firmware V1.00");break; case 4:SendLine("Modem compiled for DosBox version " VERSION);break; };break; case 'E': //Echo on/off switch (num=ScanNumber(scanbuf)) { case 0:mhd.echo = false;break; case 1:mhd.echo = true;break; };break; case 'V': switch (num=ScanNumber(scanbuf)) { case 0:mhd.numericresponse = true;break; case 1:mhd.numericresponse = false;break; };break; case 'H': //Hang up switch (num=ScanNumber(scanbuf)) { case 0: if (mhd.socket) { HangUp(); goto ret_none; } //Else return ok };break; case 'O': //Return to data mode switch (num=ScanNumber(scanbuf)) { case 0: if (mhd.socket) { mhd.commandmode = false; goto ret_none; } else { goto ret_error; } };break; case 'T': //Tone Dial case 'P': //Pulse Dial break; case 'M': //Monitor case 'L': //Volume ScanNumber(scanbuf); break; case 'A': //Answer call if (mhd.incomingsocket) { AcceptIncomingCall(); } else { mhd.answermode = true; } goto ret_none; case 'Z': //Reset and load profiles num=ScanNumber(scanbuf); break; case ' ': //Space just skip break; case 'S': //Registers { Bitu index=ScanNumber(scanbuf); bool hasequal=(*scanbuf == '='); if (hasequal) scanbuf++; Bitu val=ScanNumber(scanbuf); if (index>=SREGS) goto ret_error; if (hasequal) mhd.reg[index]=val; else LOG_MSG("print reg %d with %d",index,mhd.reg[index]); };break; default: LOG_MSG("Unhandled cmd %c%d",chr,ScanNumber(scanbuf)); } } #if 0 if (strstr(mhd.cmdbuf,"S0=1")) { mhd.autoanswer = true; } if (strstr(mhd.cmdbuf,"S0=0")) { mhd.autoanswer = false; } if (strstr(mhd.cmdbuf,"NET0")) { mhd.telnetmode = false; } if (strstr(mhd.cmdbuf,"NET1")) { mhd.telnetmode = true; } #endif LOG_MSG("Sending OK"); SendRes(ResOK); return; ret_error: LOG_MSG("Sending ERROR"); SendRes(ResERROR); ret_none: return; } void MC_Changed(Bitu new_mc) { LOG_MSG("New MC %x",new_mc ); if (!(new_mc & 1) && mhd.socket) HangUp(); updatemstatus(); } void TelnetEmulation(Bit8u * data, Bitu size) { Bitu i; Bit8u c; for(i=0;i250) { /* Reject anything we don't recognize */ tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(c); /* We won't do crap! */ } } switch(telClient.command) { case 251: /* Will */ if(c == 0) telClient.binary[TEL_SERVER] = true; if(c == 1) telClient.echo[TEL_SERVER] = true; if(c == 3) telClient.supressGA[TEL_SERVER] = true; break; case 252: /* Won't */ if(c == 0) telClient.binary[TEL_SERVER] = false; if(c == 1) telClient.echo[TEL_SERVER] = false; if(c == 3) telClient.supressGA[TEL_SERVER] = false; break; case 253: /* Do */ if(c == 0) { telClient.binary[TEL_CLIENT] = true; tqueue->addb(0xff); tqueue->addb(251); tqueue->addb(0); /* Will do binary transfer */ } if(c == 1) { telClient.echo[TEL_CLIENT] = false; tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(1); /* Won't echo (too lazy) */ } if(c == 3) { telClient.supressGA[TEL_CLIENT] = true; tqueue->addb(0xff); tqueue->addb(251); tqueue->addb(3); /* Will Suppress GA */ } break; case 254: /* Don't */ if(c == 0) { telClient.binary[TEL_CLIENT] = false; tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(0); /* Won't do binary transfer */ } if(c == 1) { telClient.echo[TEL_CLIENT] = false; tqueue->addb(0xff); tqueue->addb(252); tqueue->addb(1); /* Won't echo (fine by me) */ } if(c == 3) { telClient.supressGA[TEL_CLIENT] = true; tqueue->addb(0xff); tqueue->addb(251); tqueue->addb(3); /* Will Suppress GA (too lazy) */ } break; default: LOG_MSG("MODEM: Telnet client sent IAC %d", telClient.command); break; } telClient.inIAC = false; telClient.recCommand = false; continue; } else { if(c==249) { /* Go Ahead received */ telClient.inIAC = false; continue; } telClient.command = c; telClient.recCommand = true; if((telClient.binary[TEL_SERVER]) && (c == 0xff)) { /* Binary data with value of 255 */ telClient.inIAC = false; telClient.recCommand = false; rqueue->addb(0xff); continue; } } } else { if(c == 0xff) { telClient.inIAC = true; continue; } rqueue->addb(c); } } } void Timer(void) { int result =0; unsigned long args = 1; bool sendbyte = true; Bitu usesize; Bit8u txval; /* Check for eventual break command */ if (!mhd.commandmode) mhd.cmdpause++; /* Handle incoming data from serial port, read as much as available */ Bitu tx_size=tqueue->inuse(); while (tx_size--) { txval = tqueue->getb(); if (mhd.commandmode) { if (mhd.echo) rqueue->addb(txval); if (txval==0xa) continue; //Real modem doesn't seem to skip this? else if (txval==0x8 && (mhd.cmdpos > 0)) --mhd.cmdpos; else if (txval==0xd) DoCommand(); else if (txval != '+') { if(mhd.cmdpos 1000) { if(txval == '+') { mhd.plusinc++; if(mhd.plusinc>=3) { LOG_MSG("Entering command mode"); mhd.commandmode = true; SendRes(ResOK); mhd.plusinc = 0; } sendbyte=false; } else { mhd.plusinc=0; } //If not a special pause command, should go for bigger blocks to send } tmpbuf[0] = txval; tmpbuf[1] = 0x0; if (mhd.socket && sendbyte) { SDLNet_TCP_Send(mhd.socket, tmpbuf,1); //TODO error testing } } } SDLNet_CheckSockets(mhd.socketset,0); /* Handle incoming to the serial port */ if(!mhd.commandmode && mhd.socket) { if(rqueue->left() && SDLNet_SocketReady(mhd.socket) && (mctrl & MC_RTS)) { usesize = rqueue->left(); if (usesize>16) usesize=16; result = SDLNet_TCP_Recv(mhd.socket, tmpbuf, usesize); if (result>0) { if(mhd.telnetmode) { /* Filter telnet commands */ TelnetEmulation(tmpbuf, result); } else { rqueue->adds(tmpbuf,result); } mhd.cmdpause = 0; } else HangUp(); } } /* Check for incoming calls */ if (!mhd.socket && !mhd.incomingsocket && mhd.listensocket) { mhd.incomingsocket = SDLNet_TCP_Accept(mhd.listensocket); if (mhd.incomingsocket) { mhd.diallen = 12000; mhd.dialpos = 0; SendRes(ResRING); //MIXER_Enable(mhd.chan,true); mhd.ringtimer = 3000; mhd.reg[1] = 0; //Reset ring counter reg } } if (mhd.incomingsocket) { if (mhd.ringtimer <= 0) { mhd.reg[1]++; if (mhd.answermode || (mhd.reg[0]==mhd.reg[1])) { AcceptIncomingCall(); return; } SendRes(ResRING); mhd.diallen = 12000; mhd.dialpos = 0; //MIXER_Enable(mhd.chan,true); mhd.ringtimer = 3000; } --mhd.ringtimer; } updatemstatus(); } bool CanSend(void) { return true; } bool CanRecv(void) { if(rqueue->inuse()) { return true; } else { return false; } } protected: char cmdbuf[QUEUE_SIZE]; bool commandmode; bool answermode; bool echo; bool telnetmode; Bitu cmdpause; Bits ringtimer; Bits ringcount; Bitu plusinc; Bitu cmdpos; Bit8u reg[SREGS]; IPaddress openip; TCPsocket incomingsocket; TCPsocket socket; TCPsocket listensocket; SDLNet_SocketSet socketset; struct { bool binary[2]; bool echo[2]; bool supressGA[2]; bool timingMark[2]; bool inIAC; bool recCommand; Bit8u command; } telClient; struct { bool active; double f1, f2; Bitu len,pos; char str[256]; } dial; }; #endif CSerialModem *csm; static Bitu INT14_FOSSIL(void) { switch (reg_ah) { case 0x01: // Serial - Write character to port csm->tqueue->addb(reg_al); reg_ah = csm->read_reg(0xd) & 0x7f; break; case 0x02: // FOSSIL - Receive character with wait //LOG_MSG("FOSSIL: Calling get to receive character", reg_ah); csm->mctrl |= MC_RTS; while(!csm->rqueue->inuse()) { // Wait for byte. Yes, I realize that this locks up DosBox, but // it would an oldskool system as well. csm->Timer(); } reg_al = csm->rqueue->getb(); reg_ah = 0; break; case 0x03: //LOG_MSG("FOSSIL: Calling get port status", reg_ah); // Serial - Get port status csm->mctrl |= MC_RTS; reg_ah = csm->read_reg(0xd) ; // Line status if(csm->rqueue->inuse()) reg_ah |= 0x1; reg_al = csm->read_reg(0xe); // Modem status break; default: LOG_MSG("FOSSIL: Func 0x%x not handled", reg_ah); break; } return CBRET_NONE; } void MODEM_Init(Section* sec) { unsigned long args = 1; Section_prop * section=static_cast(sec); if(!section->Get_bool("modem")) return; if(SDLNet_Init()==-1) { LOG_MSG("SDLNet_Init failed: %s\n", SDLNet_GetError()); return; } if(!SDLNetInited) { if(SDLNet_Init()==-1) { LOG_MSG("SDLNet_Init failed: %s\n", SDLNet_GetError()); return; } SDLNetInited = true; } Bit16u comport = section->Get_int("comport"); csm = NULL; switch (comport) { case 1: csm = new CSerialModem(0x3f0, 4, 57600, section->Get_string("remote"), section->Get_int("listenport")); break; case 2: csm = new CSerialModem(0x2f0, 3, 57600, section->Get_string("remote"), section->Get_int("listenport")); break; case 3: csm = new CSerialModem(0x3e0, 4, 57600, section->Get_string("remote"), section->Get_int("listenport")); break; case 4: csm = new CSerialModem(0x2e0, 3, 57600, section->Get_string("remote"), section->Get_int("listenport")); break; default: // Default to COM2 csm = new CSerialModem(0x2f0, 3, 57600, section->Get_string("remote"), section->Get_int("listenport")); break; } if(csm != NULL) seriallist.push_back(csm); //Enable FOSSIL support (GTERM, etc) call_int14=CALLBACK_Allocate(); CALLBACK_Setup(call_int14,&INT14_FOSSIL,CB_IRET); RealSetVec(0x14,CALLBACK_RealPointer(call_int14)); } #endif