Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!tut.cis.ohio-state.edu!ucbvax!hplabs!hpfcdc!hpislx!hpmtlx!reid From: reid@hpmtlx.HP.COM ($Reid Trimble) Newsgroups: comp.lang.pascal Subject: Re: xmodem help needed Message-ID: <5430003@hpmtlx.HP.COM> Date: 23 Feb 89 21:11:10 GMT References: <401@advdev.Cambridge.NCR.COM> Organization: HP Manufacturing Test Division - Loveland, CO Lines: 519 Here's an old turbo pascal (3.0?) program that implements xmodem. Have fun deciphering it... --------------------------------- cut ---------------------------------------- {.HEFile: Modem.Pas Listed: 4/20/87 Page: #} {.PL66} {$C-} {no user interrupts} {$U-} {$K-} {no stack checking - program works} program Modem; { Written by Jack M. Wierda Chicago Illinois Modified by Steve Freeman LANGUAGE: TURBO Pascal This program is in the public domain. This program is basically a re-write in PASCAL of Ward Christensen's Modem Program which was distributed in CP/M User's Group Volume 25. Identical and compatible options are provided to allow this program to work directly with XMODEM running under CP/M. } const Version = '12-Nov-84'; FredsPhone = '7-5038'; SignOnLine = 'ACGM10,RLIP,PSSWD'; MaxPhoneNums = 26; COMport = 1; NUL = 00; SOH = #$01; EOT = #$04; ACK = #$06; TAB = 09; LF = #$0A; CR = #$0D; NAK = #$15; Space = ' '; DELete = $7F; lastbyte = 127; timeout = 256; errormax = 5; retrymax = 5; loopspersec = 6500; Intseg: integer = 0; {filled with interrupt segment address} type maxstr = string[255]; PhoneEntry = string[32]; PhoneStr = string[20]; BytePointer = ^byte; var COMbase: integer; {this will point to the Communications base} WorkFile: file; PhoneFile: text; PhoneList: array[1..MaxPhoneNums] of PhoneEntry; option, hangup, return, mode, baudrate : char; sector : array[0..lastbyte] of byte; base, N_Phones: integer; { interrupt vectors and pointers to them } newvec, oldvec: BytePointer; INT3: BytePointer absolute $0000:$002C; {for COM2:} INT4: BytePointer absolute $0000:$0030; {for COM1:} rcvbuf: array[0..127] of byte; inptr, outptr: integer; datardy: boolean; {.pa} type hexstr = string[4]; function hex(num: integer): hexstr; var i, j: integer; h: string[16]; str: hexstr; begin str := '0000'; h := '0123456789ABCDEF'; j := num; for i:=4 downto 1 do begin str[i] := h[(j and 15)+1]; j := j shr 4; end; hex := str; end; {.cp10} function GetYN: char; var c: char; begin repeat read(kbd,c); c := upcase(c); until c in ['Y','N']; writeln(c); GetYN := c end; {.cp4} procedure SetDTR; begin port[base+4] := $09; {DTR on and INT enabled} end; {.cp4} procedure HangUpPhone; {hang up by terminating the line} begin port[base+4] := 0; end; {.cp7} function status: integer; var st: integer; begin st := port[base+5]; st := st shl 8 + port[base+6]; status := st; end; {.cp6} procedure send(ch: char); var s: byte; begin repeat s := port[base+5] and $20 until (s=$20); port[base] := ord(ch); end; {.cp6} function get_rcv_char: char; begin get_rcv_char := chr(rcvbuf[outptr]); outptr := (outptr + 1) and $7F; if inptr=outptr then datardy := false; end; {.cp5} function receive: char; begin repeat until datardy; receive := get_rcv_char; end; {.cp9} function ReadLine(seconds:integer): integer; var j : integer; begin j := loopspersec * seconds; repeat j := j-1 until datardy or (j = 0); if j = 0 then readline := timeout else readline := ord(get_rcv_char); end; {.cp8} procedure PurgeLine; {purge the receive register} var c: char; begin repeat if datardy then c := get_rcv_char; delay(35); { 300 baud time period for received char } until not(datardy) end; {.cp42} procedure Set_RS232_Vector; procedure Int_Handler; { This routine buffers all incoming received data } begin inline($50/$52/$57/$1E/ {save registers} $2E/ {CS:} $8E/$1E/Intseg/ {MOV DS,[Intseg]} {get data segment pointer} $BA/$FD/$03/ {MOV DX,$3FD} {is character ready?} $EC/ {IN AL,DX} $24/$01/ {AND AL,01} $74/$19/ {JZ here} { no, skip entry} $BA/$F8/$03/ {MOV DX,$3F8} { yes, get pointer} $A1/inptr/ {MOV AX,[inptr]} {get index to buffer} $97/ {XCHG DI,AX} $EC/ {IN AL,DX} {get data from receiver} $88/$85/rcvbuf/ {MOV [DI+rcvbuf],AL} {put data into buffer} $97/ {XCHG DI,AX} {increment pointer} $40/ {INC AX} $24/$7F/ {AND AL,$7F} $A3/inptr/ {MOV [inptr],AX} $B8/$01/$00/ {MOV AX,1} {show data is ready} $A2/datardy/ {MOV [datardy],AX} {here} $B0/$64/ {MOV AL,64} {EOI, level 4 on 8259} $E6/$20/ {OUT 20,AL} $1F/$5F/$5A/$58/$CF); {restore and return} end; begin Intseg := Dseg; COMbase := $0400 + 2 * (COMport - 1); oldvec := INT4; newvec := ptr(cseg,ofs(Int_Handler)+7+5); INT4 := newvec; inline($BA/$3F8/ {MOV DX,BASE} $EC/$EC/$EC/$EC/ {IN AL,DX} $BA/$3FD/$EC/ {MOV DX,BASE+5 ! IN AL,DX} $BA/$3FE/$EC); {MOV DX,BASE+6 ! IN AL,DX} datardy := false; inptr := 0; outptr := inptr; inline($E4/$21/$24/$EF/$E6/$21); {turn off IRQ mask bit - enabled} end; {.cp16} procedure Setup(md, brc: char); var al: integer; begin base := memw[0:COMbase]; port[base+3] := $83; {access baud rate divisor and sets 8 data, no parity, 1 stop} if md='O' then mode:=' ' else mode:='R'; baudrate := brc; if baudrate='1' then portw[base] := $0060 {1200 baud} else portw[base] := $0180; { 300 baud} port[base+3] := $03; {set access for xmt/rcv} port[base+1] := $01; {enable receiver interrupts} SetDTR; {put station on-line} return := 'N'; end; {.cp16} procedure Initialize; var mode, baudrate: char; begin repeat write('Mode : A(nswer), O(riginate) ? '); read(kbd,mode); mode := upcase(mode); until mode in ['A','O']; writeln(mode); repeat write('Baud rate : 3(00), 1(200) ? '); read(kbd,baudrate); until baudrate in ['1','3']; writeln(baudrate); Setup(mode,baudrate); end; {.cp19} procedure terminal; var s, t: byte; c: char; begin {$I-} {no I/O checking here} writeln('Use ctrl-E to exit terminal mode.'); repeat s := port[base+5]; {get status} if datardy then begin t := ord(get_rcv_char); t := t and $7F; if t<>$7F then write(chr(t)); end; if keypressed and ((s and $20) = $20) then begin read(kbd,c); port[base] := ord(c); end; until (c = ^E); end; {$I+} {.cp5} procedure sendtext(str: maxstr); var i: integer; begin for i:=1 to length(str) do send(str[i]); end; {.cp20} function Dial(PhoneNumber: PhoneStr): char; var c, kc: char; t: integer; begin HangUpPhone; write(cr,lf,'Dialing: ',PhoneNumber); delay(250); SetDTR; delay(250); sendtext(cr); delay(1000); sendtext('AT '+mode+'M1V0DT'+PhoneNumber+cr); delay(2000); c := receive; c := chr(0); repeat c := get_rcv_char until (c=cr); write(', Waiting for carrier ...'); t := 60 * loopspersec; repeat t := t - 1; if datardy then c := get_rcv_char; if keypressed then read(kbd,kc); until (c in ['0'..'5']) or (t=0) or (kc=^E); if c='1' then writeln(' connected.') else if (t=0) or (kc=^E) then c := '9'; Dial := c end; {.cp15} procedure SignOn; var i: integer; c: char; begin write('Signing on ... '); delay(2000); for i:=1 to 7 do begin send('8'); delay(333); end; sendtext('('+cr); delay(2500); sendtext(SignOnLine+cr); writeln('all set !'); end; {.pa} procedure SendFile; var j, sectornum, counter, checksum : integer; filename : string[20]; c: char; procedure SendIt; begin sectornum := 1; repeat counter := 0; blockread(WorkFile,sector,1); repeat write(cr,'Sending sector ', sectornum); send(SOH); send(chr(sectornum)); send(chr(-sectornum-1)); checksum := 0; for j:=0 to lastbyte do begin send(chr(sector[j])); checksum := (checksum + sector[j]) mod 256 end; send(chr(checksum)); purgeline; counter := counter + 1; until (readline(10) = ord(ack)) or (counter = retrymax); sectornum := sectornum + 1 until (eof(WorkFile)) or (counter = retrymax); if counter = retrymax then writeln(cr,lf,'No ACK on sector') else begin counter := 0; repeat send(EOT); counter := counter + 1 until (readline(10)=ord(ack)) or (counter=retrymax); if counter = retrymax then writeln(cr,lf,'No ACK on EOT') else writeln(cr,lf,'Transfer complete'); end; end; begin write('Filename.Ext ? '); readln(filename); if length(filename)>0 then begin assign(WorkFile,filename); reset(WorkFile); SendIt; close(WorkFile) end; end; {.pa} procedure readfile; var j, firstchar, sectornum,sectorcurrent, sectorcomp, errors, checksum : integer; errorflag : boolean; filename : string[20]; procedure ReceiveIt; begin sectornum := 0; errors := 0; send(nak); send(nak); { send ready characters } repeat errorflag := false; repeat firstchar := readline(20) until firstchar in [ord(SOH),ord(EOT),timeout]; if firstchar = timeout then writeln(cr,lf,'Error - No starting SOH'); if firstchar = ord(SOH) then begin sectorcurrent := readline(1); {real sector number} sectorcomp := readline(1); {+ inverse of above} if (sectorcurrent+sectorcomp)=255 {<-- becomes this #} then begin if (sectorcurrent=sectornum+1) then begin checksum := 0; for j := 0 to lastbyte do begin sector[j] := readline(1); checksum := (checksum+sector[j]) and $00FF end; if checksum=readline(1) then begin blockwrite(WorkFile,sector,1); errors := 0; sectornum := sectorcurrent; write(cr,'Received sector ',sectorcurrent); send(ack) end else begin writeln(cr,lf,'Checksum error'); errorflag := true end end else if (sectorcurrent=sectornum) then begin repeat until readline(1)=timeout; writeln(cr,lf,'Received duplicate sector ', sectorcurrent); send(ack) end else begin writeln(cr,lf,'Synchronization error'); errorflag := true end end else begin writeln(cr,lf,'Sector number error'); errorflag := true end end; if errorflag then begin errors := errors+1; repeat until readline(1)=timeout; send(nak) end; until (firstchar in [ord(EOT),timeout]) or (errors = errormax); if (firstchar=ord(EOT)) and (errors0 then begin assign(WorkFile,filename); rewrite(WorkFile); ReceiveIt; close(WorkFile); end; end; {.cp17} function ReadPhoneList: integer; var index: integer; begin assign(PhoneFile,'MODEM.PHN'); index := 0; {$I-} reset(PhoneFile); {$I+} if IOresult=0 then begin while (not eof(PhoneFile)) and (index<26) do begin index := index + 1; readln(PhoneFile,PhoneList[index]); end; close(PhoneFile); end; ReadPhoneList := index; end; {.cp41} procedure Call; var rc: char; selection, i, j, k: integer; PhoneNo: PhoneStr; begin if N_Phones>0 then begin clrscr; writeln; for i:=1 to N_Phones do begin if (i mod 2)=0 then write(' ') else writeln; write(chr(i+64),' - ',PhoneList[i]); end; writeln; writeln; write('Enter selection letter: '); repeat repeat until keypressed; read(kbd,rc); rc := upcase(rc); selection := ord(rc) - ord('@'); until (selection in [1..N_Phones]); writeln(rc); mode := PhoneList[selection][31]; baudrate := PhoneList[selection][32]; Setup(mode,baudrate); j := 30; PhoneNo := ''; while PhoneList[selection][j]<>'.' do j:=j-1; for k:=j+1 to 30 do PhoneNo := PhoneNo + PhoneList[selection][k]; rc := Dial(PhoneNo); end else rc := Dial(FredsPhone); if rc='1' then begin if N_Phones=0 then SignOn else if selection=1 then Signon; terminal; end else HangUpPhone; end; {.cp22} procedure GetOption; begin clrscr; writeln('Modem, ',Version); gotoxy(7,4); writeln('Options:'); writeln; writeln(' R - receive a file'); writeln(' S - send a file'); writeln(' T - terminal mode'); writeln; writeln(' C - place a call'); writeln(' H - hang up the phone'); writeln(' O - option configuration'); writeln(' X - exit to system'); writeln; write('which ? '); repeat read(kbd,option); option := upcase(option); until option IN ['O','C','R','S','T','H','X']; writeln(option); end; {.cp16} begin {Modem} Set_RS232_Vector; N_Phones := ReadPhoneList; Setup('O','1'); { default of Originate/1200 baud } repeat GetOption; case option of 'T': Terminal; 'R': ReadFile; 'S': SendFile; 'O': Initialize; 'C': Call; 'H': HangUpPhone; 'X': return := 'Y'; end; until return='Y'; inline($E4/$21/$0C/$10/$E6/$21); {turn on IRQ mask bit - disabled} (* INT4 := oldvec; {restore the old RS232 vector} *) end.