PAGE 59, 132 TITLE MSKERM -- Main module for MS-Kermit and descendents ; Update 20 Jan 86 IF1 %OUT >> Starting pass 1 ELSE %OUT >> Starting pass 2 ENDIF PUBLIC Prompt, DosNum, CurDsk, SwChar, Kermit, KrmRet, OldStk PUBLIC SPath, PC_Type, PC_Subtype, Dos_Minor, TakRd, NoInt, OkInt PUBLIC Get_memory_block, Quick_Push, Exit2, In_menu_mode PUBLIC TakAdr, TakLev, Force_mono Code SEGMENT PARA PUBLIC Code ENDS DataS SEGMENT PARA PUBLIC 'datas' DataS ENDS Stack SEGMENT PARA STACK 'STACK' Stack ENDS INCLUDE MsDefs.H ; Based on ... ;******************** Version 2.26 ********************************** ; KERMIT, Celtic for "free" ; ; The name "Kermit" is a registered trade mark of Henson Associates, Inc., ; used by permission ; ; Kermit-MS Program Version 2.26, July 26, 1984 ; ; Based on the Columbia University KERMIT Protocol ; ; Copyright (C) 1982,1983,1984 Trustees of Columbia University ; ; Daphne Tzoar, Jeff Damens ; Columbia University Center for Computing Activities ; 612 West 115th Street ; New York, NY 10025 ; ; Special thanks to Frank da Cruz, Bill Catchings, Steve Jensen, Herm Fischer, ; Vace Kundakci, and Bernie Eiben for their help and contributions makseg EQU 26H deffcb EQU 5cH setblk EQU 4AH exec EQU 4BH env EQU 2CH ; environment address in psp cline EQU 80H ; offset in psp of command line namsiz EQU 64 ; Bytes for file name and size maxnam EQU 10 chmod EQU 43H ; chmod call (used to test for file existence) DataS SEGMENT PUBLIC 'datas' EXTRN DTA:byte, comand:byte, flags:byte, pack:byte, trans:byte EXTRN fcb:byte, cpfcb:byte, prmptr:word, inichk:byte Help_text db Cr,Lf Program_name DB ' is an enhanced version of Columbia ' db "University's MS-Kermit program.",Cr,Lf,'This version supports ' db 'IBM PCs and compatibles as well as the DEC Rainbow.',Cr,Lf,Cr,Lf Program_name db ' has two main functions: Terminal Emulation and File ' db 'Transfer. In',Cr,Lf,'Terminal Emulation Mode, ' Program_name DB ' permits ' db 'your microcomputer to act as a DEC',Cr,Lf,'VT100 terminal. ' db ' In File Transfer Mode, ' Program_name DB ' permits you to exchange files' db Cr,Lf,'with a remote computer running a program which ' db 'supports the Kermit file',Cr,Lf,'transfer protocol. ' db 'A third mode, Command Mode, is used to set parameters and ' db Cr,Lf,'control the program. Press ? while ' db 'typing a command for help, or press Esc',Cr,Lf db 'for command completion.',Cr,Lf,Cr,Lf db 'To use Terminal Emulation, you must first specify (or default) ' db 'all required',Cr,Lf,'parameters (PORT, BAUD rate, and PARITY),' db " then press the IBM PC's F5 key ",'("Do"',Cr,Lf,'on Rainbow). ' db 'To get back to Command Mode, press F5 or Do again. While in' db Cr,Lf,'Terminal Mode you can press F6 (PC only) ' db 'to get more help.',Cr,Lf,Cr,Lf db 'To use File Transfer:',Cr,Lf db ' 1) Establish a connection to a remote host computer;',Cr,Lf db ' 2) Run Kermit (or a Kermit-based program) on the host in ' db 'SERVER mode;',Cr,Lf db ' 3) Enter local Command Mode and type "SEND filename" to ' db 'send a file,',Cr,Lf db ' or "GET filename" to receive a file from the host.' db Cr,Lf,Cr,Lf,'$' versio DB cr, lf ; Leading CrLf verdef DB ' running $' On DB 'on $' IBMsg DB 'IBM PC$' RbMsg DB 'DEC Rainbow$' GrMsg DB 'GRiD GRiDCase$' Att6300Msg DB 'AT&T 6300$' QuadRamsg DB 'QuadRam DataVue$' MSDOS DB 'under MS-DOS$' UkMsg DB 'MS-DOS OEM #$' XT_str DB '/XT$' Portable_str DB ' Portable$' jr_str DB 'jr$' AT_str DB ' AT$' Compaq_str DB ' clone (Compaq)$' Clone_str DB ' clone (?)$' ; DB ' under DOS v' ;dosvers DB '9.99' EndVersio DB ' -- type ? or HELP for help',cr,lf,cr,lf,'$' tmp db ?,'$' crlf db cr,lf,'$' ermes1 db '? Unrecognized command',cr,lf,'$' ermes3 db '? Not confirmed',cr,lf,'$' erms30 db '? Passed maximum nesting level for TAKE command',cr,lf,'$' erms31 db '? File not found',cr,lf,'$' erms32 db '? File(s) not found',cr,lf,'$' erms33 db '? CHKDSK program not found on current disk',cr,lf,'$' erms34 db '? This program runs only under DOS 2.0 and above',cr,lf,'$' erms35 db '? Must specify program name',cr,lf,'$' erms36 db '? Could not free memory',cr,lf,'$' erms37 db '? Unable to execute program',cr,lf,'$' erms38 db '? Not enough TAKE stack space left to parse filename',cr,lf,'$' infms1 db 'Really erase *.*? $' infms8 db 'File(s) erased',cr,lf,'$' Echo_help DB ' Text to be echoed on screen$' tmsg5 db '[Closing log file]',cr,lf,'$' ; [jd] filhlp1 db ' Command file specification $' filhlp2 db ' File specification (possibly wild) $' filhlp3 db ' File spec (possibly wild) or confirm with carriage return$' filmsg db ' File specification with optional path name $' filwmsg db ' File specification (possibly wild) with optional path name $' chkfil db 0,'CHKDSK COM' chkflen EQU $-chkfil tophlp DB ' One of the following:' DB cr,lf,' ' DB 'BYE',tab,tab DB 'CLOSE',tab,tab DB 'CLS',tab,tab DB 'CONNECT' DB cr,lf,' ' DB 'DEFINE',tab,tab DB 'DELETE',tab,tab DB 'DIRECTORY',tab DB 'DO' DB cr,lf,' ' DB 'DO-SCRIPT',tab DB 'DROP-DTR',tab DB 'ECHO',tab,tab DB 'EXIT' DB cr,lf,' ' DB 'FINISH',tab,tab DB 'GET',tab,tab DB 'HELP',tab,tab DB 'LOCAL' DB cr,lf,' ' DB 'LOG',tab,tab DB 'LOGOUT',tab,tab DB 'MENU',tab,tab DB 'NPSEND' DB cr,lf,' ' DB 'PUSH',tab,tab DB 'QUIT',tab,tab DB 'RECEIVE',tab,tab DB 'REMOTE' DB cr,lf,' ' DB 'RUN',tab,tab DB 'SCRIPT-FILE',tab DB 'SEND',tab,tab DB 'SERVER' DB cr,lf,' ' DB 'SET',tab,tab DB 'SHOW',tab,tab DB 'SPACE',tab,tab DB 'STATUS' DB cr,lf,' ' DB 'TAKE',tab,tab DB 'XRECEIVE',tab DB 'XSEND' DB '$' lochlp DB cr,lf,' DELETE file' DB cr,lf,' DIRECTORY [filespec]' DB cr,lf,' PUSH to command interpreter' DB cr,lf,' RUN program' DB cr,lf,' SPACE remaining on current disk' DB '$' ; COMND tables yestab DB 2 mkeyw 'NO',0 mkeyw 'YES',1 comtab DB 36 mkeyw 'BYE',bye mkeyw 'C',telnet mkeyw 'CLOSE',clscpt mkeyw 'CLS',Clear_screen mkeyw 'CONNECT',telnet mkeyw 'DEFINE',dodef mkeyw 'DELETE',delete mkeyw 'DIRECTORY',direct mkeyw 'DO',docom mkeyw 'DO-SCRIPT', Do_Script mkeyw 'DROP-DTR',DoDrop mkeyw 'ECHO', Echo_command mkeyw 'EXIT',exit mkeyw 'FINISH',finish mkeyw 'GET',get mkeyw 'HELP',help mkeyw 'LOCAL',lclcmd mkeyw 'LOG',setcpt mkeyw 'LOGOUT',logout mkeyw 'MENU', Menu mkeyw 'NPSEND', Non_protocol_send mkeyw 'PUSH',dopush mkeyw 'QUIT',exit mkeyw 'RECEIVE',read mkeyw 'REMOTE',remote mkeyw 'RUN',run mkeyw 'SCRIPT-FILE', Script_File mkeyw 'SEND',send mkeyw 'SERVER',server mkeyw 'SET',setcom mkeyw 'SHOW',showcmd mkeyw 'SPACE',chkdsk mkeyw 'STATUS',status mkeyw 'TAKE',take mkeyw 'XRECEIVE', XReceive mkeyw 'XSEND', XSend loctab DB 5 mkeyw 'DELETE',delete mkeyw 'DIRECTORY',direct mkeyw 'PUSH',dopush mkeyw 'RUN',run mkeyw 'SPACE',chkdsk shotab DB 2 mkeyw 'KEY',shokey mkeyw 'MACRO',shomac ; Program storage Hold_di DW ? ; Place to hold di reg over call to COMND oldstk DW ? ; Storage for system stack oldsts DW ? ; System stack segment ssave DW ? ; Original SS when doing CHKDSK siz DW ? ; Memory size in3ad DD 0 ; Original break interrupt addresses. [25] curdsk DB 0 ; Current disk origd DB 0 ; Original disk fildat DB 0 ; Manipulate file data/time creation DB 0 taklev DB 0 ; Take levels. [25t] takadr DW takstr-(size takinfo) ; Pointer into structure. [25t] temp DW 0 temp1 DW ? ; Temporary storage temp2 DW ? temp3 DW ? temp4 DW ? psp DW ? divst DW 0 Max_count_of_blocks=10 ; Size of memory block table Count_of_allocated_blocks DW 0 ; Number of entries in memory block table Block_table DW Max_count_of_blocks DUP (0) ; Memory block table PC_Type DB 0 ; Zero for IBM ; 2 for Rainbow ; 4 for GRiD GRiDCase ; 6 for AT&T 6300 ; 8 for QuadRam DataVue ; 20 for unknown hardware (crash is likely) PC_Subtype DB 0 ; Zero for vanilla PC, ; 1 for PC/XT ; 2 for PCjr ; 3 for PC AT ; 4 for Portable PC ; 90 for Compaq ; 99 for PC clones takstr DB (size takinfo) * maxtak dup(?) ; Build a 12 char FCB style INI file name ininam DB 0 IniXXX DB ' INI' ; Start with 8 spaces followed by INI IniYYY LABEL BYTE ; A label following the base name ORG IniXXX ; Go back to where the 8 space are Program_name ; Overlay the start with our file name ORG IniYYY ; Go back to proper location ; Build a DOS 2.0 style INI file name IniNm2 LABEL BYTE Program_name DB '.INI',0 ; Name of INIT file IniNm2_len EQU $ - IniNm2 nambuf DB maxnam * namsiz dup (?) cmdnam DB namsiz dup (?) exefcb DB fcbsiz dup (?) exefcb2 DB fcbsiz dup (?) exearg DW ? ; segment addr of environment (filled in below) DD 0 ; ptr to cmd line (filled in below) DD exefcb ; default fcb DD exefcb2 ; second default fcb dircmd DB ' /c dir ' dirclen EQU $-dircmd dirnam DB 50h dup (?) chkdcmd DB 'chkdsk.com' chkdlen EQU $-chkdcmd dosnum DB ? ; dos version number Dos_Minor DB ? ; Dos minor version number Force_mono DB ? ; Use monochrome attributes on color displays pthnam DB 'PATH=' pthlen EQU $-pthnam pthbuf DB 160 dup (?) ; buffer for path definition defpth DB '\', 70 dup (?) ; buffer for default path cmspnam DB 'COMSPEC=' cmsplen EQU $-cmspnam cmspbuf DB '\command.com', 0 ; Default name DB 30 dup (?) ; Some additional space TFile DB 100 dup (?) ; Temp space for file names swchar DB '\' ; Default switch character In_menu_mode DB 0 ; If non-0, we are in menu (not command) mode NoInt_Count DB 0 ; Number of times we have been NoInt-ed LastChar DB 0 ; Last char we read in NPSEND Number DB 8 DUP (?) ; Place to hold a number DataS ENDS Code SEGMENT PUBLIC EXTRN cmblnk:near, locate:near, logout:near, Drop_DTR:NEAR EXTRN bye:near, telnet:near, finish:near, comnd:near, Telnet2:NEAR EXTRN read:near, remote:near, send:near, status:near, get:near EXTRN dodisk:near, serrst:near, setcom:near, OutChr:NEAR EXTRN clscpi:near, clscpt:near, getbaud:near, dodef:near, setcpt:near EXTRN docom:near, Set_up_script_processor:NEAR, server:near, lclini:near EXTRN shokey:near, shomac:near, XReceive:NEAR, XSend:NEAR EXTRN Script_File:NEAR, Do_Script:NEAR, Menu:NEAR, Quick_menu:NEAR ASSUME cs:Code, ds:Datas, ss:Stack Critical DD ? ; Address of original Critical Error Handler Error_handling_installed DB 0 ; Flag set if error handling has been installed Last_Critical_Error DW 00FFh ; Most recent critical error, if any Start PROC FAR push ds ; Save system data area sub ax,ax ; Get a zero push ax ; Put zero return addr on stack mov ax, SEG DataS ; Initialize DS mov ds,ax sub ax,ax mov oldstk, sp ; Save old stack pointer mov ax,es:[2] ; In program segment prefix mov siz,ax ; Pick up memory size mov psp,es mov ax, ds ; Copy ds mov es, ax ; to es ASSUME es:datas mov ah, 30h ; Code to get DOS version number int Dos ; Do it cmp al, 2 ; DOS 2.0 or higher? jae DOS_is_Ok ; Not a problem mov ah, PrStr ; Code to print a string mov dx, OFFSET ErMs34 ; Message complaining about lousy DOS int Dos ; Type it ret ; Return to DOS DOS_is_Ok: mov ax, (33H * 256) + 1 ; Code to set BREAK state sub dl, dl ; Force it off NOW! int Dos ; We use ^C heavily, we have to control it push es ; Save es register mov es, psp ; Point to psp again mov ax, es:[Env] ; Get environment ptr mov exearg, ax ; Put into argument block ; Give back memory which we don't need from our initial allocation ; (DOS gives us all available memory) mov bx, OFFSET Stk + 15 ; End of program mov cl, 4 shr bx, cl ; Compute # of paragraphs in last segment mov ax, SEG stack ; Last segment, either alphabetically or ; sequentially sub ax, Psp ; Minus beginning.. add bx, ax ; # of paragraphs occupied mov ah, SetBlk ; Try to change size of our memory block int Dos ; Success? jnc SHS_1 ; Nope.. pushf ; Save flags mov ah, PrStr mov dx, OFFSET ErMs36 ; Complain int Dos popf ; Restore flags SHS_1: pop es ; Unclobber register ; Before beginning, establish our own quick-o shortcut in case a drive ; on the PATH is without disk ... in that case, just bomb the call, don't ; do the usual "Abort, Retry or Ignore" call Set_up_error_handling ; Establish our routine as the one to use mov ah, prstr ; Print start of version header mov dx, OFFSET Versio int Dos ; Get the DOS version number and the "vendor number" for the vendor of this ; copy of DOS ... anytime we are running PC-DOS (on whatever machine) we think ; we are on "an IBM". If the user thinks PC-DOS runs on his machine, lets ; assume that the machine is a perfect enough clone for our purposes ... PUBLIC See_what_machine_we_are_on See_what_machine_we_are_on: mov ah, 30h ; Code to get DOS version & vendor numbers int Dos ; Do it mov dosnum, al ; Remember major Dos version mov Dos_Minor, ah ; Store minor version number also mov Force_mono, 0 ; Default to "use color on color display" mov ah, PrStr ; Set up to type strings PUBLIC Check_vendor Check_vendor: cmp bh, 0FFh ; Generic MS-DOS? jne Not_MSDOS ; No mov dx, OFFSET MSDOS ; "under MS-DOS" int Dos ; Type it jmp SHORT Finish_banner ; Go finish it up Not_MSDOS: mov dx, OFFSET On ; "on " int Dos ; Type it cmp bh, 0 ; Is the vendor IBM? jne Not_IBM ; No call Vendor_is_IBM ; Use special rtn to determine type of IBM jmp SHORT Finish_banner ; Go finish it up Not_IBM: cmp bh, 16h ; Is the vendor DEC? jne Not_DEC ; No mov PC_Type, 2 ; Type is DEC mov dx, OFFSET RbMsg ; DEC Rainbow int Dos jmp SHORT Finish_banner Not_DEC: cmp bh, 2Eh ; Is the vendor GRiD? jne Not_GRiD ; No mov PC_Type, 4 ; Type is GRiD mov dx, OFFSET GrMsg ; GRiD GRiDCase int Dos jmp SHORT Finish_banner Not_GRiD: cmp bh, 77h ; Is the vendor AT&T? jne Not_Att ; No mov PC_Type, 6 ; Type is AT&T 6300 mov dx, OFFSET Att6300Msg int Dos jmp SHORT Finish_banner PUBLIC No_body Not_Att: cmp bh, 60 ; Is the vendor QuadRam? jne No_body ; No mov PC_Type, 8 ; Type is QuadRam DataVue 25 mov dx, OFFSET QuadRamsg int Dos jmp SHORT Finish_banner PUBLIC No_body No_body: push bx ; Save reg mov PC_Type, 20 ; Type is unknown mov dx, OFFSET UkMsg ; Unknown hardware int Dos pop bx ; Get back reg cld ; Forwards mov al, bh ; Copy OEM # to al mov ah, 0 ; Zero high half mov di, OFFSET Number ; Point at Number call nout2 ; Do the number mov al, '$' ; Get a dollar sign stosb ; Put it down mov ah, PrStr ; Code to type to screen mov dx, OFFSET Number ; Point this at Number int Dos ; Do it Finish_banner: mov dx,offset EndVersio ; Close it out int dos mov ah,setdma ; Set disk transfer address mov dx,offset DTA int dos call getbaud ; Get the baud rate. [25] call dodisk ; See how many disk drives we have. [21a] call setint mov ah,gcurdsk ; Get current disk int dos inc al ; We want 1 == A (not zero) mov curdsk,al mov origd,al ; Remember original disk we started on mov es,psp mov ax,es:[env] ; pick up environment address push ax call getpath ; get the path from the environment pop ax ; get environment back call getcsp ; get comspec from environment as well mov Flags.ExtFlg, 0 ; Clear EXIT flag before LCLINI just in case call LclIni ; Do local initialization cmp Flags.ExtFlg, 0 ; Some LclIni routines flag fatal errors je KrmGo ; No, finish startup jmp KrmEnd ; If so jump to KRMEND KrmGo: call gcmdlin ; read command line call rdinit ; read kermit init file call packlen ; Packet length in case do server comand ; This is the main KERMIT loop. It prompts for and gets the users commands Kermit: mov ax,ds mov es,ax ; make sure this addresses data segment mov dx,prmptr ; get prompt call prompt ; Prompt the user mov pack.state,0 ; Clear the state mov flags.cxzflg,0 ; Reset each itme mov Flags.XFlg, 0 ; This one also [tm] mov ah, Flags.Real_RemFlg ; Pick this up mov Flags.RemFlg, ah ; Copy to here mov ah,inichk ; Original or set checksum length mov trans.chklen,ah ; Reset just in case mov dx,offset comtab mov bx,offset tophlp mov comand.cmcr,1 ; Allow bare CR's mov ah,cmkey call comnd jmp kermt2 mov comand.cmcr,0 ; Not anymore call bx ; Call the routine returned krmret: jmp kermt3 cmp flags.extflg,0 ; Check if the exit flag is set jne krmend ; If so jump to KRMEND jmp kermit ; Do it again kermt2: mov dx,offset ermes1 ; Give an error jmp short kermt4 kermt3: mov dx,offset ermes3 ; Give an error kermt4: cmp flags.cxzflg,'C' ; some sort of abort? je kermit ; yes, don't print error message mov ah,prstr int dos mov flags.droflg,0 ; Reset drive override flag mov flags.nmoflg,0 ; Reset filename override flag mov flags.getflg,0 ; May as well do this one mov flags.cmrflg,0 ; This one too jmp kermit krmend: call serrst ; Just in case the port wasn't reset. [21c] mov dl,origd ; Original disk drive dec dl ; Want A == 0 mov ah,seldsk ; Reset original disk just in case int dos call Return_all_memory ; Clean up after ourselves call Restore_normal_error_handling ; Return to normal situation mov sp, OldStk ; Fix up stack just in case mov ax, 4C00h ; Code to exit with no error int Dos ; Quit to DOS ret ; Should not return here! START ENDP ; Special routine to figure out what kind of IBM flavor PC we are running on Vendor_is_IBM: mov PC_Type, 0 ; Type is IBM mov dx, OFFSET IBMsg ; IBM PC int Dos mov bx, 0FFFFh ; Highest segment push es ; Save es mov es, bx ; Set up es mov al, es:[0Eh] ; "Machine-Sensitive Code", says IBM pop es ; Restore es cmp al, 0FFh ; Vanilla PC? jne IBM_0 ; No, keep working ret ; Yes, done here IBM_0: cmp al, 0FEh ; PC/XT? jne NotXT ; Its down to /XT versus Portable PC (same BIOS) mov bx, 040h ; DOS's data segment push es ; Save es mov es, bx ; Set up es mov al, es:[075h] ; Count of fixed disk drives pop es ; Restore es cmp al, 0 ; Are there any fixed disks on this machine? jne IsXT ; Yes there are, must be an XT mov PC_Subtype, 4 ; Type is Portable PC mov dx, OFFSET Portable_str mov Force_mono, 1 ; Portable has monochrome amber screen jmp SHORT Suffix IsXT: mov PC_Subtype, 1 ; Type is PC/XT mov dx, OFFSET XT_str jmp SHORT Suffix NotXT: cmp al, 0FDh ; PCjr? jne Notjr mov PC_Subtype, 2 ; Type is PCjr mov dx, OFFSET jr_str jmp SHORT Suffix Notjr: cmp al, 0FCh ; PC AT? jne NotAT mov PC_Subtype, 3 ; Type is PC AT mov dx, OFFSET AT_str jmp SHORT Suffix NotAT: cmp al, 02Dh ; Compaq? jne NotCompaq mov PC_Subtype, 90 ; Type is Compaq mov dx, OFFSET Compaq_str mov Force_mono, 1 ; Compaq has green monochrome screen jmp SHORT Suffix NotCompaq: mov PC_Subtype, 99 ; Type is PC clone mov dx, OFFSET Clone_str Suffix: int Dos ; Finish up DOS call ret ; Done here ; CLS command -- used to clear the screen Clear_screen PROC mov ah,cmcfm call comnd ; Get a confirm jmp r call CmBlnk ; Call routines to clear the screen call Locate ; then home the cursor ... jmp RSkp ; Get the prompt Clear_screen ENDP ; DROP-DTR command -- used to hang up the line DoDrop PROC mov ah, cmcfm call comnd ; Get a confirm jmp r call SerRst ; Reset serial port (uninstall interrupt rtn) call Drop_DTR ; Call routine to drop DTR jmp RSkp DoDrop ENDP ; ECHO command -- display some text for the user to see Echo_command PROC mov ah, CMTXT ; Read in a line of text mov bx, OFFSET TFile ; Temp area for text, max 100 chars mov dx, OFFSET Echo_help ; Simple message for help call comnd ; Do it jmp RSkp mov di, OFFSET TFile ; Point to start of message mov bl, ah ; Copy length to bl sub bh, bh ; Clear high half add di, bx ; Move to end of message mov al, Cr ; Return stosb ; Store it mov al, Lf ; Line feed stosb ; Store it mov al, '$' ; Dollar sign stosb ; Store it mov dx, OFFSET TFile ; Point to text again mov ah, PrStr ; Code to type a string int Dos ; Type it jmp rskp Echo_command ENDP ; This is the 'exit' command. It leaves KERMIT and returns to DOS EXIT PROC NEAR mov ah, cmcfm call comnd ; Get a confirm jmp r test flags.capflg,0FFH ; capturing? jz exit2 ; no, keep going mov dx,offset tmsg5 mov ah,prstr int Dos call clscpi nop ; this skip returns.. nop nop exit2: mov flags.extflg, 1 ; Set the exit flag jmp rskp ; Then return to system EXIT ENDP ; This is the 'help' command. It gives a very brief intro to the program HELP PROC NEAR mov ah,cmcfm call comnd ; Get a confirm jmp r mov ah,prstr ; Print a string to the console mov dx,offset Help_text ; The address of the help message int dos jmp rskp HELP ENDP lclcmd proc near mov ah,cmkey mov dx,offset loctab mov bx,offset lochlp call comnd jmp r call bx nop nop nop jmp rskp lclcmd endp PUBLIC SetInt ; Don't ignore ^C when in debug mode SETINT PROC NEAR push es ; Save reg mov ax, 3523h ; Want addr for int 23h int Dos ; Get address of this interrupt mov WORD PTR in3ad, bx ; Store offset (IP) mov ax, es ; Copy es to ax mov WORD PTR in3ad+2, ax ; Store the segment (CS) pop es ; Get back normal es push ds ; Save ds mov ax, cs ; Copy cs ... mov ds, ax ; ... to ds mov ax, 2523h ; Install new addr for int 23 mov dx, OFFSET IntBrk ; Addr of routine int Dos ; Store addr of our routine for this interrupt pop ds ; Restore ds ret SETINT ENDP ; NPSEND command -- Non-Protocol SEND of a file Non_protocol_send PROC cld ; Forwards cmp taklev, maxtak ; Hit our limit? jl NPS_1 ; Continue if still OK mov ah,prstr mov dx,offset erms38 ; Complain int dos jmp RSkp NPS_1: mov di,takadr add di,size takinfo mov Hold_di, di ; Save reg mov ah,cmtxt lea bx,[di].takbuf ; convenient place to parse name into mov dx,offset filmsg ; Help in case user types "?" call comnd ; Parse for a text string jmp RSkp ; Can't get text string (?), give up mov di, Hold_di ; Restore reg lea si,[di].takbuf ; get buffer back mov bl,ah ; length of thing parsed sub bh, bh ; Zero top half mov byte ptr [bx+si],0 ; make it asciz mov ax,si ; point to name again call spath ; is it around? jc NPS_2 ; no, go complain mov di, Hold_di ; Restore reg mov dx,ax ; point to name from spath mov ah,open2 ; 2.0 open call mov al,0 ; open for reading int dos jnc NPS_3 ; open ok, keep going NPS_2: mov ah,prstr mov dx,offset erms31 int dos jmp RSkp NPS_3: inc taklev mov takadr,di mov word ptr [di].takfcb+1,ax ; save file descriptor mov byte ptr [di].takfcb,0feh ; mark as 2.0 file descriptor mov bx, ax ; need descriptor here mov ax, (LSeek*256)+2 sub cx, cx mov dx, cx ; seek 0 bytes from end int dos mov [di].takcnt,ax mov [di].takcnt+2,dx ; store length mov ax, (LSeek*256)+0 sub cx, cx mov dx, cx ; now seek back to beginning int dos call takrd ; Get a buffer full of data mov ax, OFFSET cs:Our_entry_point ; Get address of our entry point call Set_up_script_processor ; Tell emulator to call us (and where) call Telnet2 ; Enter terminal emulator nop ; Ignore skip return nop nop jmp RSkp ; All done ; ***** Enter here on exit hooks from Terminal Emulator ***** Our_entry_point: cld ; Forwards or ah, ah ; Is the code 0 (keyboard)? jnz OEP_1 ; No jmp SHORT NPS_loop ; Yes, go do our stuff OEP_1: cmp ah, 1 ; Code 1 (host character)? je OEP_3 ; Yes cmp ah, 2 ; Code 2 (shutdown)? jne OEP_3 ; No jmp NPS_end ; Yes, close files and be ready to be gone OEP_3: ret ; Unknown, do nothing for now NPS_loop: call Get_a_char ; Get the next character jc NPS_end ; No more, go close the file mov ah, al ; Copy to ah call OutChr ; Send this character jmp NPS_end ; Failed, give up ret ; Return to terminal emulator for now NPS_end: sub ax, ax ; Make a zero call Set_up_script_processor ; Make emulator forget us mov bx, TakAdr ; Addr of TAKE frame mov bx, WORD PTR [bx].TakFcb+1 ; This is where file handle is stored mov ah, CLOSE2 ; Close file the file handle way int Dos dec TakLev ; Untake this file sub TakAdr, SIZE TakInfo ; Back off space on stack also ret ; Done here Get_a_char: mov bx, TakAdr ; Addr of our TAKE frame mov ax, [bx].TakCnt ; Get low half of doubleword char count or ax, [bx].TakCnt+2 ; Merge with high half of doubleword jz GAC_EOF ; No chars left, flag as end-of-file cmp [bx].TakChl, 0 ; Disk file, any chars left in buffer? jne GAC_2 ; Yes, don't need more yet call TakRd ; Reload buffer from disk file cmp [bx].TakChl, 0 ; Any chars now? je GAC_EOF ; No chars even after call to TakRd, EOF! GAC_2: dec [bx].TakChl ; Decrement for the char sub [bx].TakCnt, 1 ; DEC doesn't set carry!! sbb [bx].TakCnt+2, 0 mov si, [bx].TakPtr ; Pick up ptr into buffer lodsb ; Pick up next char mov [bx].TakPtr, si ; Store the updated ptr cmp al, Lf ; Is this a linefeed? jne GAC_3 ; No cmp LastChar, Cr ; Was the previous character a return? jne GAC_3 ; This Lf was not preceded by a Cr mov LastChar, al ; Update this jmp Get_a_char ; Char is Lf following Cr, so don't send it GAC_3: mov LastChar, al ; Update this for next time cmp al,CtlZ ; Maybe control-z? je GAC_EOF ; Yes, bomb out clc ; No error ret GAC_EOF: stc ; Error (EOF) ret Non_protocol_send ENDP ; take commands from a file, but allow a path name Take PROC cmp taklev,maxtak ; Hit our limit? jl Take1 ; Continue if still OK mov ah,prstr mov dx,offset erms30 ; Complain int dos jmp RSkp Take1: mov di,takadr add di,size takinfo mov ah,cmtxt lea bx,[di].takbuf ; convenient place to parse name into mov dx,offset filmsg ; Help in case user types "?" call comnd jmp RSkp call Setup_take_file ; Call common routine to do the work jnc TAK_Got_it ; Ok so far mov ah,prstr mov dx,offset erms31 int dos jmp RSkp TAK_Got_it: cmp flags.takflg,0 je Take4 mov ah,prstr mov dx,offset crlf int dos Take4: jmp rskp Take ENDP ; Setup_take_file -- routine to set up a new TAKE file in a new TAKE frame ; ; call with ... ; di/ New frame address ; ah/ Length of file name ; [di].TakBuf/ File name with optional path ... SPATH is used to fix up PUBLIC Setup_take_file Setup_take_file PROC mov Hold_di, di ; Save frame address lea si, [di].TakBuf ; Get buffer back mov bl, ah ; Length of thing parsed sub bh, bh ; Zero high half mov BYTE PTR [bx+si], 0 ; Make it ASCIZ mov ax, si ; Point to name again call SPath ; Is it around? jc Take2 ; No, go complain mov dx, ax ; Point to name from spath mov ax, (Open2*256) + 0 ; DOS 2.0 open call for read int Dos jnc Take3 ; Open ok, keep going Take2: ret ; Return with carry on ... Take3: inc TakLev ; Bump our nested TAKE count mov di, Hold_di ; Restore frame address mov TakAdr, di ; Advance to next TAKE frame mov BYTE PTR [di].TakFcb, 0FEh ; Mark as 2.0 file descriptor mov WORD PTR [di].TakFcb+1, ax ; Save file descriptor mov bx, ax ; Need descriptor here mov ax, (LSeek*256) + 2 ; Seek 0 bytes from end sub cx, cx sub dx, dx int Dos mov [di].TakCnt, ax ; Store length mov [di].TakCnt+2, dx ; (doubleword) mov ax, (LSeek*256) + 0 ; Seek back to the beginning sub cx, cx sub dx, dx int Dos call TakRd ; Get a buffer full of data clc ; Flag no carry (no error) ret ; Done here Setup_take_file ENDP TAKRD PROC NEAR push bx push cx push dx mov bx,takadr cmp byte ptr [bx].takfcb,0feh ; is it a 2.0 file handle? jne takrd1 ; no, handle differently push bx ; save frame address lea dx,[bx].takbuf ; buffer to read into mov cx,dmasiz ; # of bytes to read mov ah,readf2 ; 2.0 read call mov bx,word ptr [bx].takfcb+1 ; file handle is stored here int dos pop bx ; restore frame address jmp takrd2 ; rejoin common exit takrd1: mov ah,setdma lea dx,[bx].takbuf int dos mov ah,readf lea dx,[bx].takfcb int dos mov ah,setdma lea dx, DTA int dos takrd2: mov [bx].takchl,dmasiz lea ax,[bx].takbuf mov [bx].takptr,ax pop dx pop cx pop bx ret TAKRD ENDP ; copy the path into pthbuf ; enter with ax/ environment segment address ; works in 2.0 only getpath proc near push es mov bx,ds mov es,bx ; address data segment mov bx,offset pthnam ; thing to find mov cx,pthlen ; length of it mov dx,offset pthbuf ; place to put it mov byte ptr pthbuf,0 ; initialize to null.. call getenv ; get environment value pop es ret ; and return getpath endp ; copy the comspec into cmspbuf ; enter with ax/ environment segment address ; works in 2.0 only getcsp proc near push es mov bx,ds mov es,bx ; address data segment mov bx,offset cmspnam ; thing to find mov cx,cmsplen ; length of it mov dx,offset cmspbuf ; place to put it call getenv ; get environment value pop es ret ; and return getcsp endp ; find a path variable. Enter with ax/ environment segment, ; bx/ variable to find (incl =), cx/ length of variable name, ; dx/ address to store value at ; The buffer given in dx is unchanged if the variable isn't found getenv proc near push ds push es mov es,ax ; address segment mov di,0 ; offset in segment geten1: cmp es:byte ptr [di],0 ; end? je geten4 ; yes, forget it push cx ; save counter push di ; and offset mov si,bx repe cmpsb ; is it the one? pop di pop cx ; restore these je geten2 ; found it, break loop push cx ; preserve again mov cx,0ffffh ; bogus length mov al,0 ; marker to look for repne scasb ; search for it pop cx ; restore length jmp geten1 ; loop thru rest of environment geten2: add di,cx ; skip to definition mov si,di ; this is source mov di,dx ; destination as given mov ax,ds mov bx,es mov ds,bx mov es,ax ; exchange segment regs for copy geten3: lodsb ; get a byte stosb ; drop it off cmp al,0 ; end of string jne geten3 ; no, go on geten4: pop es pop ds ; restore registers ret ; and return getenv endp %OUT >> About half way through source file ; Put xxxxxxxx.INI onto Take stack if it exists. Just like ; the Take command, except it doesn't read a filename RdInit PROC mov bx, TakAdr ; Pick up "current" take address add bx, SIZE TakInfo ; Bump to next frame mov si, OFFSET IniNm2 ; Name to try lea di, [bx].TakBuf ; Ptr to where to put it mov cx, IniNm2_len ; Length of name cld ; Forwards rep movsb ; Copy the string mov di, bx ; Copy pointer to TAKE frame to di mov ah, IniNm2_len ; Length in ah call Setup_take_file ; Call common routine to do the work jnc RDI_Got_it ; Ok so far ret ; If no file, give up ! RDI_Got_it: jmp rskp RdInit ENDP ; get command line into a macro buffer gcmdlin proc near push ds push es cld mov es,psp ; address psp mov ch,0 mov cl,es:[cline] ; length of cmd line mov di,cline+1 ; point to actual line mov al,' ' jcxz gcmdl3 ; no command line, forget it repe scasb ; skip over spaces je gcmdl3 ; all spaces, forget it mov si,di ; this is first non-space dec si ; pre-incremented.. inc cx inc taklev ; bump take level add takadr,size takinfo ; address new take frame mov bx,takadr mov byte ptr [bx].takfcb,0ffh ; mark as a macro push cx ; save length push ds ; and segment lea di,[bx].takbuf ; into take buffer mov ax,ds mov ds,psp mov es,ax ; switch segments for copy gcmdl1: lodsb ; get a byte cmp al,',' ; comma? jne gcmdl2 ; no, keep going mov al,cr ; convert to cr gcmdl2: stosb ; deposit it loop gcmdl1 ; copy whole cmd pop ds ; restore segment pop cx ; restore len lea ax,[bx].takbuf mov [bx].takptr,ax ; init buffer ptr mov [bx].takchl,cl ; chars remaining mov [bx].takcnt,cx ; and all chars mov [bx].takcnt+2,0 ; clear high order gcmdl3: pop es pop ds ret gcmdlin endp ; This routine prints the prompt and specifies the reparse address PROMPT PROC NEAR mov comand.cmprmp,dx ; save the prompt pop bx ; Get the return address mov comand.cmrprs,bx ; Save as addr to go to on reparse mov comand.cmostp,sp ; Save for later restoral push bx ; Put it on the stack again mov bx,offset comand.cmdbuf mov comand.cmcptr,bx ; Initialize the command pointer mov comand.cmdptr,bx mov ah,0 mov comand.cmaflg,ah ; Zero the flags mov comand.cmccnt,ah mov comand.cmsflg,0FFH cmp flags.takflg,0 ; look at take flag jne promp1 ; supposed to echo, skip this check.. cmp taklev,0 ; inside a take file? je promp1 ; no, keep going ret ; yes, return promp1: mov ah,prstr ; Print the prompt mov dx,comand.cmprmp int dos ret PROMPT ENDP ; Erase specified file(s) DELETE PROC NEAR mov comand.cmcr,0 ; Filename must be specified mov ah,cmifi ; Parse an input filespec mov dx,offset fcb mov bx,offset filhlp2 ; Text of help message call comnd jmp r ; Bad filename mov ah,cmcfm ; Parse a confirm call comnd jmp r mov flags.nmoflg, 0 ; Function CMIFI leaves this set, clear it cld mov di,offset fcb+1 mov al,'?' mov cx,11 ; # of chars in a name repe scasb ; are they all ?'s? jne del1 ; no, skip message mov dx,offset infms1 call prompt mov ah,cmkey mov dx,offset yestab mov bx,0 call comnd jmp r push bx mov ah,cmcfm call comnd pop bx ret nop pop bx cmp bx,0 jne del1 jmp rskp del1: mov dx,offset fcb mov ah,sfirst ; See if any files match this specification int dos cmp al,0FFH ; No matches? jne del2 mov ah,prstr mov dx,offset erms32 int dos jmp rskp del2: mov dx,offset fcb mov ah,delf ; Erase the file(s) int dos mov dx,offset infms8 mov ah,prstr ; Say we did so int dos jmp rskp DELETE ENDP CHKDSK PROC mov ah,cmcfm call comnd jmp r mov ah, PrStr ; Code to type string mov dx, OFFSET CrLf ; A CrLf int Dos ; Type it mov si,offset chkdcmd ; point to cmd mov cx,chkdlen ; and length jmp crun ; and go execute it nicely CHKDSK ENDP DIRECT PROC mov ah,dosver ; See what level of DOS we're at int dos cmp al,0 ; Level 2.0 or above? jne dir4 ; Yes - get directory the easy way mov comand.cmcr,1 ; Allow plain CR (so DIR == DIR *.*) mov ah,cmifi ; Get input file spec mov dx,offset fcb ; Give the address for the FCB mov bx,offset filhlp3 call comnd jmp r mov ah,cmcfm ; Parse a confirm call comnd jmp r mov comand.cmcr,0 ; Reset this push es mov ax,ds mov es,ax mov temp1,0FFH mov di,offset nambuf dir0: call getfn ; Get a matching file name cmp al,0FFH ; Retcode -- are we done? je dir1 ; Yes, just leave call dumpit ; Print it or dump to buffer jmp dir0 dir1: pop es jmp rskp dir4: mov si,offset cmspbuf ; command processor mov di,offset dirnam dir5: lodsb ; get a byte or al,al jz dir6 ; stop on the null stosb ; otherwise copy it in jmp dir5 ; and keep going dir6: mov si,offset dircmd ; add directory command to it mov cx,dirclen rep movsb mov ah,cmtxt ; parse with cmtxt so we can have paths.. mov bx,di ; next available byte mov dx,offset filwmsg ; In case user wants help mov Hold_di, di call comnd jmp r mov di, Hold_di mov cl,ah mov ch,0 ; length of name sub di,offset dirnam ; compute # of bytes used add cx,di mov si,offset dirnam ; dir cmd jmp crun ; join run cmd from there DIRECT ENDP getfn: cmp temp1,0FFH jne gtfn1 mov ah,sfirst ; Any matches? mov dx,offset fcb int dos cmp al,0FFH ; Means no matches je gtfn5 call savfcb mov temp1,0 jmp gtfn4 gtfn1: cmp flags.wldflg,0FFH ; Wilcard seen? je gtfn2 ; Yes, get next file mov al,0FFH ; No, set retcode ret gtfn2: call rstfcb mov ah,snext mov dx,offset fcb int dos cmp al,0 ; Any more matches? je gtfn3 ; Yes keep going mov al,0FFH ; OK return code ret gtfn3: call savfcb gtfn4: push di mov si,offset DTA ; Data is here mov di,offset fcb ; Copy to here mov cx,37 repne movsb pop di call nicnam ; Make name nice for printing mov al,0 ret gtfn5: mov ah,prstr ; Don't print if a server mov dx,offset erms32 ; Say no matches int dos mov al,0FFH ; Failure return code ret savfcb: push di mov si,offset fcb ; Data is here mov di,offset cpfcb ; Copy to here mov cx,37 repne movsb pop di ret rstfcb: push di mov si,offset cpfcb ; Data is here mov di,offset fcb ; Copy to here mov cx,37 repne movsb pop di ret nicnam: mov al,CR ; Add CRLF before print names stosb mov al,LF stosb mov cx,8 mov si,offset fcb+1 repne movsb ; Get the file name mov al,' ' stosb mov cx,3 repne movsb mov al,tab stosb mov al,' ' stosb mov ah,openf mov dx,offset fcb int dos mov bx,offset fcb+18 ; Get hi order word of file size mov ax,[bx] mov dx,ax mov bx,offset fcb+16 ; Get lo order word mov ax,[bx] call nout2x ; Get it in decimal mov al,tab stosb mov al,' ' stosb mov ah,0 mov si,offset fcb+20 lodsb mov fildat+1,al lodsb mov fildat,al ; Date field of fcb mov cl,5 shr fildat+1,cl and fildat,1 mov cl,3 shl fildat,cl mov al,fildat or al,fildat+1 ; Get the month field cmp al,9 jg nic0 push ax mov al,' ' stosb pop ax nic0: call nout2 ; Make it decimal mov al,'-' stosb mov si,offset fcb+20 ; Get date field lodsb and al,1FH cmp al,10 ; Only one digit? jge nic0x push ax mov al,'0' ; Make it two digits stosb pop ax nic0x: call nout2 ; Make it decimal mov al,'-' stosb mov si,offset fcb+21 ; Get the year field lodsb shr al,1 add al,80 cmp al,100 ; At the year 2000 or above? js nic0y ; No, just go on sub al,100 ; Go back to two digits nic0y: cmp al,10 ; Only one digit? jge nic0z push ax mov al,'0' ; Make it two digits stosb pop ax nic0z: call nout2 ; Make it decimal mov al,tab stosb mov si,offset fcb+23 ; Get time field of fcb lodsb mov cl,3 ; Get the hour field shr al,cl mov tmp,'a' ; For AM cmp al,12 ; Before noon? jl nic1 mov tmp,'p' ; It's PM je nic1 ; Don't change "12" to "0" sub al,12 ; Use a 12 hr. clock nic1: cmp al,0 ; Just after midnight? jne nic1x add al,12 ; Make it "12" instead of "0" nic1x: cmp al,10 ; Pad with a space? jge nic2 push ax mov al,' ' stosb pop ax nic2: call nout2 ; Make it decimal mov al,':' ; Separate hours and minutes stosb mov si,offset fcb+23 ; Get the minutes field lodsb and al,07 mov cl,3 shl al,cl mov ah,al mov si,offset fcb+22 lodsb mov cl,5 shr al,cl or al,ah mov ah,0 cmp al,10 ; Would there be a leading zero jge nic3 push ax mov al,'0' stosb pop ax nic3: call nout2 ; Make it decimal mov al,tmp ; Add 'a' (AM) or 'p' (PM) stosb mov ah,closf mov dx,offset fcb int dos ret ; For now, just print it dumpit: mov al,'$' stosb mov ah,prstr mov dx,offset nambuf int dos mov di,offset nambuf ret ; Push to an inferior command parser DoPush PROC mov ah,cmcfm ; Confirm the command call comnd jmp r ; Entry to pre-confirmed Push command ... ; so we can Push from FT screen, for example ... Quick_Push: mov si,offset cmspbuf ; name of parser push si ; save beginning sub cx,cx ; initial length dopus2: lodsb inc cx ; count this or al,al ; at end? jnz dopus2 ; no, keep going pop si ; restore cmd dec cx ; this is incremented one over jmp short crun ; go run it dopush endp ; crun - run an arbitrary program. Enter with si/address of whole ; cmd, cx/length of cmd CRUN proc near push cx ; save length of cmd mov ax,ds mov es,ax ; address dest segment mov di,offset nambuf rep movsb ; copy command so we can mess with it pop cx mov si,offset nambuf ; point to command jmp short run3 ; and join run code CRUN ENDP RUN PROC NEAR mov ah,cmtxt ; Get program name mov bx,offset nambuf ; Convenient buffer mov dx,offset filmsg ; In case user wants help call comnd nop nop nop cmp ah,0 jne run2 mov ah,prstr mov dx,offset erms35 int dos jmp rskp run2: mov cl,ah mov ch,0 mov si,offset nambuf ; alternate entry if cmd is already known. Source cmd ptr in si ; is trashed run3: mov bx,cx mov byte ptr [si+bx],cr ; end string with a cr for dos mov di,offset cmdnam mov ax,ds mov es,ax run4: lodsb cmp al,' ' jne run5 dec si ; back up over space jmp short run6 ; and exit loop run5: stosb loop run4 run6: mov byte ptr [di],0 ; terminate string dec si ; point back a byte into argument mov [si],cl ; put length of argument here mov exearg+2,si ; pointer to argument string mov exearg+4,ds ; segment of same inc si ; pass length over mov al,1 ; scan leading separators mov di,offset exefcb ; parse into this fcb mov ah,prsfcb int dos ; go parse the fcb mov al,1 ; scan leading separators mov di,offset exefcb2 ; second fcb to fill mov ah,prsfcb int dos ; parse the fcb mov ax,ds mov es,ax ; put es segment back mov ax,offset cmdnam ; point to cmd name again call spath ; look for it jc run8 ; not found, go complain mov dx,ax ; point to command name call Restore_normal_error_handling ; Return to normal error handler ; At this point, we are about to run the program. But first, save the ; current settings of the three "user vectors", "Terminate", "Ctrl-Break Exit" ; and "Critical Error Handler". Seems DOS 2.0 and 2.1 don't restore them ; properly after this call. The program worked under DOS 3.0 and 3.1 ; without this code push es ; First, save the incoming es mov ax, 3522h ; Code to Get Vector, starting at 22h int Dos ; Get it push es ; Save his segment push bx ; and offset inc al ; 23h -- second vector to get int Dos ; Get it push es ; Save his segment push bx ; and offset inc al ; 24h -- third vector to get int Dos ; Get it push es ; Save his segment push bx ; and offset ; Run the program mov ax, ds ; Copy ds mov es, ax ; to es mov ax, (256*Exec)+0 ; Code to load and execute a program mov bx, OFFSET exearg ; Point to arguments mov ssave, sp ; Save stack ptr int Dos ; Go run the program ; Just came back from (maybe) running the program, have to restore some ; fundamental items first mov ax,seg datas mov ds, ax ; reset data segment mov ax,seg stack mov ss,ax ; and stack segment mov sp,ssave ; restore stack ptr ; Now restore the things DOS 2.0 and 2.1 let be clobbered mov ah, 25h ; Code to Set Interrupt Vector mov al, 24h ; Vector to restore pop dx ; Get back offset pop ds ; Get back segment int Dos ; Set it up mov al, 23h ; Vector to restore pop dx ; Get back offset pop ds ; Get back segment int Dos ; Set it up mov al, 22h ; Vector to restore pop dx ; Get back offset pop ds ; Get back segment int Dos ; Set it up pop es ; Get back saved es reg mov ax, SEG DataS ; DS just got clobbered mov ds, ax ; Fix it up again call Set_up_error_handling ; Establish our routine as the one to use pushf ; Save flags mov ah, SetDMA mov dx, OFFSET DTA int Dos ; Restore DTA address!! popf ; Recover flags jc Run8 ; Error mov ah, PrStr mov dx, OFFSET CrLf int Dos ; Type another blank line jmp rskp ; Return run8: mov ah,prstr mov dx,offset erms37 int dos jmp rskp RUN ENDP ; the show command showcmd proc near mov ah,cmkey mov dx,offset shotab xor bx,bx ; no canned help call comnd jmp r call bx ; call the handler jmp r jmp rskp ; and return showcmd endp PUBLIC IntBrk IntBrk PROC FAR push ax ; Interrupt routine, not allowed to push ds ; clobber registers pushf ; Save flags also mov ax, SEG Datas mov ds, ax cmp flags.debug, 1 ; Debug mode? je IntB2 ; Yes, then don't ignore the ^C mov flags.cxzflg, 'C' ; Say we saw a ^C mov pack.state, 'A' ; Set the state to abort popf ; Restore flags pop ds ; Restore regs pop ax cmp ah, 09h ; Were we typing a string? jne IntBl1 ; No add sp, 6 ; We want to return to AFTER the "int Dos" that ; started us typing ... otherwise this call ; retypes the string completely! IntBl1: iret ; That's it for this ^C IntB2: popf ; Restore flags pop ds ; Restore regs pop ax jmp in3ad ; Original break interrupt address IntBrk ENDP ; Set the maximum data packet size. [21b] PACKLEN PROC NEAR mov ah,trans.spsiz ; Maximum send packet size sub ah,4 ; Size minus control info sub ah,trans.chklen ; And minus checksum chars sub ah,2 ; Leave room at end: 2 for possible #X cmp trans.ebquot,'N' ; Doing 8-bit quoting? je pack0 ; Nope so we've got our size cmp trans.ebquot,'Y' je pack0 ; Not doing it in this case either sub ah,1 ; Another 1 for 8th-bit quoting pack0: mov trans.maxdat,ah ; Save max length for data field ret PACKLEN ENDP NOUT2 PROC NEAR push ax push dx mov temp,10 ; Divide quotient by 10 cwd ; Convert word to doubleword div temp ; AX <-- Quo, DX <-- Rem cmp ax,0 ; Are we done? jz nout0 ; Yes call nout2 ; If not, then recurse nout0: add dl,'0' ; Make it printable mov temp,ax mov al,dl stosb mov ax,temp pop dx pop ax ret ; We're done. [21c] NOUT2 ENDP NOUT2X PROC NEAR push ax push dx push cx mov temp,10 ; Divide quotient by 10 div temp ; AX <-- Quo, DX <-- Rem mov cx,dx ; Remember the remainder cmp ax,0 ; Are we done? jz nout0x ; Yes mov dx,0 call nout2 ; If not, then recurse nout0x: add cl,'0' ; Make it printable mov temp,ax mov al,cl stosb mov ax,temp pop cx pop dx pop ax ret ; We're done. [21c] NOUT2X ENDP SPATH PROC ; enter with ax/ ptr to file name. Searches path for given file, ; returns with ax/ ptr to whole name, or carry on if file isn't ; to be found ; First, see if file is in connected directory push ax call isfile ; See if file exists in current dir pop ax ; AX is clobbered jc spath_0 ; Not here, look down PATH ... jmp SPath_Exit ; That was easy ... found file spath_0: push es mov bx,ds mov es,bx ; address data segment mov bx,ax ; convenient place to keep this mov si,ax mov di,offset tfile ; place to copy to mov dl,0 ; no '\' seen yet mov ah,swchar ; get switch character spath1: lodsb stosb cmp al,ah ; contain path characters? jne spath2 ; no, keep going mov dl,1 ; remember we've seen them spath2: or al,al jnz spath1 ; copy name in or dl,dl ; look at flag jz spath3 ; no path embedded, keep going pop es mov ax,offset tfile ; else.. call isfile mov ax,offset tfile ; point to right thing.. jmp SPath_Exit ; let isfile decide and return ; no path, keep going spath3: mov si,offset pthbuf ; path definition cmp byte ptr [si],0 ; empty path? jne spath4 ; no, keep going mov ah,gcd ; get current dir mov dl,0 ; for default drive mov si,offset defpth+1 ; place to put it int dos mov si,offset defpth ; point to the path spath4: cmp byte ptr [si],0 ; null, exit loop je spath9 mov di,offset tfile ; place to put name spath5: lodsb ; get a byte cmp al,';' ; end of this part? je spath7 ; yes, break loop cmp al,0 ; maybe end of string? jne spath6 ; no, keep going dec si ; back up over it jmp short spath7 ; and break loop spath6: stosb ; else stick in dest string jmp spath5 ; and continue spath7: push si ; save this ptr mov si,bx ; this is user's file name mov al,swchar ; get switch character cmp byte ptr [di-1],al ; does it end with switch char? je spath8 ; yes, don't put one in stosb ; else add one spath8: lodsb stosb or al,al jnz spath8 ; copy rest of name pop si ; restore pos in path def mov ax,offset tfile call isfile ; is it a file? jc spath4 ; no, keep looking mov ax,offset tfile pop es jmp SPath_Exit ; return success (carry off) spath9: pop es ; restore this mov ax,bx ; not found yet, get original path call isfile ; does it exist? SPath_Exit: ret spath endp Set_up_error_handling PROC cmp Error_handling_installed, 0 ; Have this in already? jne SUE_Z ; Yes, quit mov Error_handling_installed, 1 ; Flag as already done pushf ; Save these regs push ax push bx push dx push es ; Save starting value of es mov ax, 3524h ; Code to get current interrupt vector 24h int Dos ; Get address of this interrupt mov WORD PTR cs:Critical, bx ; Store offset (IP) mov ax, es ; Copy es to ax mov WORD PTR cs:Critical+2, ax ; Store the segment (CS) pop es ; Get back normal es push ds ; Save ds mov ax, cs ; Copy cs ... mov ds, ax ; ... to ds mov ax, 2524h ; Install new addr for int 24 mov dx, OFFSET Handle_error ; Addr of routine int Dos ; Store addr of our routine for this interrupt pop ds ; Restore ds pop dx ; Restore regs pop bx pop ax popf SUE_Z: ret Set_up_error_handling ENDP Restore_normal_error_handling PROC cmp Error_handling_installed, 0 ; Anything to undo? je RNE_Z ; No, quit mov Error_handling_installed, 0 ; Flag as back to normal pushf ; Save these regs push ax push dx push ds mov dx, WORD PTR cs:Critical ; Set up the offset mov ax, WORD PTR cs:Critical+2 ; Set up the segment mov ds, ax ; in ds ... mov ax, 2524h ; Code to set interrupt vector 24h int Dos ; Store addr of original routine pop ds ; Restore regs pop dx pop ax popf RNE_Z: ret Restore_normal_error_handling ENDP PUBLIC Handle_error, Get_Error ; PUBLIC Set_up_error_handling ; PUBLIC Restore_normal_error_handling Handle_error PROC FAR push bx ; Save bx mov bx, di ; Copy di to bx sub bh, bh ; Clear the high half mov CS:Last_Critical_Error, bx ; Save for non-interrupt level rtns or ah, ah ; Test for disk errors js Not_disk ; It isn't disk cmp bl, 0 ; Compare with code for "Write protect" je Fail_It ; Yes, make the original DOS call fail cmp bl, 2 ; Compare with code for "Drive not ready" je Fail_It ; Yes, make the original DOS call fail jmp SHORT As_usual ; Treat it like any error Not_disk: cmp bl, 09h ; Compare with code for "Printer out of paper" je Fail_It ; Yes, make the original DOS call fail cmp bl, 0Ah ; Compare with code for "Write fault" je Fail_It ; Yes, make the original DOS call fail ; jmp SHORT As_Usual ; Fall thru to ... As_Usual: pop bx ; Restore bx jmp cs:Critical ; Go run original critical error handler Fail_It: pop bx ; Restore bx ; We must now set things up as if the original function call had failed ... add sp, 6 ; Flush DOS's 24h return regs ; IBM PC-DOS 2.0 manual (page D-7) says that you can "return to the program ; immediately after the INT 21 that experienced the error. Note that if ; this is done, DOS will be in an unstable state until a function call ; higher than 12 is issued." ; Based on this warning, lets do a harmless high numbered DOS call ; to "stabilize" DOS mov ah, 30h ; Code to get vendor number int Dos ; Ask Dos to stabilize itself ; Restore registers to what they were before the "failing" call pop ax pop bx pop cx pop dx pop si pop di mov bp, sp ; Copy stack ptr to bp or WORD PTR [bp+10], 1 ; Turn on carry bit also, to cover all cases pop bp ; Then restore real bp pop ds pop es mov al, 0FFh ; Set up al to look like a failure iret ; Return from interrupt to the original ; "failing" Dos call Handle_error ENDP Get_Error PROC mov ax, Last_Critical_Error ; Pick this up mov Last_Critical_Error, 0FFh ; Make this impossible ret ; Run with it Get_Error ENDP ; Returns with carry off if the file pointed to by ax exists isfile PROC mov dx,ax ; copy ptr mov ax, (chmod*100h)+0 ; chmod function, read file attribute int dos ret ; dos sets carry isfile endp ; Get_memory_block -- Get a single block of memory ; ; Call with ... ; bx/ number of paragraphs of memory requested ; ; Returns with ... ; carry flag set if failure ; ax/ high-order 16 bits of address of block if success Get_memory_block PROC mov ax, Count_of_allocated_blocks ; Get current index cmp ax, Max_count_of_blocks ; Too high? jae GMB_bomb ; Too many already mov ah, 48h ; Code to get a memory block int Dos ; Ask DOS for the memory jc GMB_bomb ; No such luck mov bx, Count_of_allocated_blocks ; Get current index shl bx, 1 ; Double for word offset mov Block_table[bx], ax ; Store ptr to the block inc Count_of_allocated_blocks ; Update index ret ; Done here GMB_bomb: stc ; Set error flag if not already set ret ; Return failure Get_memory_block ENDP ; Return_memory_block -- Return a single block of memory ; ; Call with ... ; bx/ high-order 16 bits of address of block to be returned Return_memory_block PROC ret ; Not sure we need this routine Return_memory_block ENDP ; Return_all_memory -- Return all allocated blocks of memory Return_all_memory PROC push es ; We modify es mov cx, Count_of_allocated_blocks ; The number of blocks we have jcxz RAM_done ; Done if no blocks sub bx, bx ; Start at index zero RAM_loop: mov ax, Block_table[bx] ; Get the next entry mov es, ax ; Copy to es mov ah, 49h ; Code to Free Allocated Memory int Dos ; Ask DOS to release this memory inc bx ; Move to word in table inc bx loop RAM_loop ; Go do the next one if any RAM_done: pop es ; Restore es mov Count_of_allocated_blocks, cx ; Zero the count of blocks ret ; Go home Return_all_memory ENDP ; Routines to disable and reenable interrupts NoInt PROC cli ; We want interrupts off inc NoInt_Count ; Bump our nested count ret ; That's it NoInt ENDP OkInt PROC dec NoInt_Count ; Unbump our nested count jnz OkInt_unchanged ; Still nested in a NOINT, just return sti ; Back to unnested state, turn ints back on OkInt_unchanged: ret ; Done here OkInt ENDP ; Jumping to this location is like retskp. It assumes the instruction ; after the call is a jmp addr RSKP PROC NEAR pop bp add bp,3 push bp ; ret RSKP ENDP ; Jumping here is the same as a ret R PROC NEAR ret R ENDP Code ENDS ; End of code section Stack SEGMENT PARA STACK 'STACK' DW 512 DUP(?) ; Our stack Stk EQU THIS WORD Stack ENDS END Start