;============================================================================= ; LOG maintains a log of system usage in an ASCII file. Syntax is: ; LOG [filespec] [/U] ; where filespec = Name of and/or path to log file, /U = Uninstall ;============================================================================= CODE SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CODE ORG 100H BEGIN: JMP INITIALIZE PROGRAM DB "LOG 1.0 " COPYRIGHT DB "(c) 1988 Ziff Communications Co.",13,10 AUTHOR DB "PC Magazine ",254," Jeff Prosise",13,10,"$",26 HANDLE DW ? ;file handle DOSVERSION DW ? ;DOS version number INT21H DD ? ;interrupt 21h vector LEVEL DW 0 ;EXEC reentrancy level EXECFLAG DW 0 ;EXEC flag XERROR_AX DW ? ;extended error information XERROR_BX DW ? XERROR_CX DW ? XERROR_DX DW ? XERROR_SI DW ? XERROR_DI DW ? XERROR_DS DW ? XERROR_ES DW ? DW 3 DUP (0) ;============================================================================= ; DOSINT intercepts calls to interrupt 21h. ;============================================================================= DOSINT PROC FAR CMP AX,4B00H ;exit immediately if this JNE EXEC0 ; isn't a call to EXEC CMP CS:[LEVEL],9 ;exit if we've exceeded JB EXEC1 ; reentrancy limits EXEC0: JMP INT21H ;--Save registers passed to EXEC by the parent program. EXEC1: STI ;interrupts on PUSH AX ;save registers PUSH BX PUSH CX PUSH DX PUSH SI PUSH DI PUSH BP PUSH DS PUSH ES ;--Record the program start/end time and write an entry to the log. PUSH BX ;save registers set for PUSH DX ; EXEC call PUSH DS PUSH ES PUSH CS ;point DS to code segment POP DS ASSUME DS:CODE INC LEVEL ;increment reentrancy count CALL RECORD_TIME ;record the current time MOV DX,OFFSET FILESPEC CALL OPENFILE ;open the log file MOV SI,LEVEL ;point SI to start time and DEC SI ; DI to end time SHL SI,1 SHL SI,1 ADD SI,OFFSET TIMES MOV DI,SI ADD DI,4 MOV AL,BYTE PTR LEVEL ;put previous level number DEC AL ; in AL CALL WRITE_ENTRY ;write log entry CALL CLOSEFILE ;close the log file MOV EXECFLAG,1 ;set EXEC flag ASSUME DS:NOTHING POP ES ;restore EXEC register POP DS ; parameters POP DX POP BX ;--Record the name of the program just EXECed. PUSH ES ;save ES and BX for later PUSH BX PUSH DS ;set ES equal to DS POP ES MOV DI,DX ;scan for terminating zero XOR AL,AL ; in ASCIIZ filename MOV CX,128 CLD REPNE SCASB MOV BX,127 ;transfer string length SUB BX,CX ; to CX MOV CX,BX MOV SI,DI ;get ending address in SI SUB SI,2 ;set SI to last character STD ;set DF for reverse string ops EXEC2: LODSB ;scan backwards for first CMP AL,"\" ; character in filename JE EXEC3 CMP AL,":" JE EXEC3 LOOP EXEC2 DEC SI EXEC3: ADD SI,2 ;point SI to first character SUB BX,CX ;calculate length of filename MOV CX,BX ;transfer length to CX CLD ;clear DF again PUSH CS ;set ES to code segment POP ES MOV AL,13 ;calculate offset into table MOV DL,BYTE PTR CS:[LEVEL] ; of program names MUL DL MOV DI,AX ADD DI,OFFSET NAMES MOV AL,CL ;write filename character STOSB ; count into table EXEC5: LODSB ;convert lowercase characters CMP AL,"a" ; to upper and copy filename JB EXEC6 ; into table CMP AL,"z" JA EXEC6 AND AL,0DFH EXEC6: STOSB LOOP EXEC5 ;--Record the command line passed to the program just EXECed. POP BX ;restore ES and BX POP ES LDS SI,ES:[BX+2] ;load parm string address MOV CL,[SI] ;get length XOR CH,CH INC CX PUSH CS ;point ES:DI to command line POP ES ; buffer in code segment MOV DI,OFFSET COMLINE REP MOVSB ;copy command line ;--Restore register values originally passed by the parent program. POP ES POP DS POP BP POP DI POP SI POP DX POP CX POP BX POP AX ;--Save registers again since the DOS 2.X EXEC function destroys them. PUSH AX PUSH BX PUSH CX PUSH DX PUSH SI PUSH DI PUSH BP PUSH DS PUSH ES ;--Save the values of SS and SP, call EXEC, and restore the stack. MOV CS:[HANDLE],BX ;calculate an index into MOV BX,CS:[LEVEL] ; the STACKSEG and DEC BX ; STACKPTR tables SHL BX,1 CLI ;disable interrupts and MOV WORD PTR CS:[STACKSEG][BX],SS ; save the SS and SP MOV WORD PTR CS:[STACKPTR][BX],SP ; registers MOV BX,CS:[HANDLE] PUSHF ;PUSH the flags register and CALL INT21H ; call DOS EXEC LAHF ;store flags temporarily in AH MOV BX,CS:[LEVEL] ;recalculate table index DEC BX SHL BX,1 CLI ;disable interrupts and MOV SS,WORD PTR CS:[STACKSEG][BX] ; restore SS and SP MOV SP,WORD PTR CS:[STACKPTR][BX] STI SAHF ;restore flags ;--Restore the registers to their conditions before EXEC was called. POP ES POP DS POP BP POP DI POP SI POP DX POP CX POP BX POP AX ;--Save registers again while post-processing is performed. PUSHF ;then save it for exit PUSH AX ;save general registers PUSH BX PUSH CX PUSH DX PUSH SI PUSH DI PUSH BP PUSH DS PUSH ES ;--Save extended error information if DOS version is 3.10 or later. PUSH CS ;set DS to code segment POP DS ASSUME DS:CODE CMP DOSVERSION,030AH ;skip if not 3.10 or later JB EXEC7 PUSH DS ;save DS MOV AH,59H ;get extended error XOR BX,BX ; information INT 21H MOV CS:[XERROR_DS],DS ;save return value of DS POP DS ;set DS to code segment again MOV XERROR_AX,AX ;save remaining register MOV XERROR_BX,BX ; values in XERROR array MOV XERROR_CX,CX MOV XERROR_DX,DX MOV XERROR_SI,SI MOV XERROR_DI,DI MOV XERROR_ES,ES ;--Record the program start/end time and write an entry to the log. EXEC7: DEC LEVEL ;decrement reentrancy count CALL RECORD_TIME ;record the current time MOV DX,OFFSET FILESPEC CALL OPENFILE ;open the log file MOV SI,LEVEL ;point SI to start time and INC SI ; DI to end time SHL SI,1 SHL SI,1 ADD SI,OFFSET TIMES MOV DI,SI SUB DI,4 MOV AL,BYTE PTR LEVEL ;put previous level number INC AL ; in AL CALL WRITE_ENTRY ;write log entry CALL CLOSEFILE ;close the log file MOV EXECFLAG,0 ;clear EXEC flag ;--Restore extended error information if DOS version is 3.10 or later. CMP DOSVERSION,030AH ;skip if not 3.10 or later JB EXEC8 MOV AX,5D0AH ;restore information with MOV DX,OFFSET XERROR_AX ; undocumented function 5Dh INT 21H ;--Restore registers a final time and exit to the parent program. ASSUME DS:NOTHING EXEC8: POP ES ;restore registers POP DS POP BP POP DI POP SI POP DX POP CX POP BX POP AX POPF DOSEXIT: IRET DOSINT ENDP ;----------------------------------------------------------------------------- ; OPENFILE opens an existing log file or creates a new one. ; Entry: DS - code segment ; Exit: CF: clear-opened/created, set-file could not be opened/created ;----------------------------------------------------------------------------- ADDRSPEC DW ? CALLFLAG DB 0 OPENFILE PROC NEAR ASSUME DS:CODE MOV ADDRSPEC,DX ;save filespec address MOV AX,3D02H ;attempt to open an existing INT 21H ; log file JC OPEN3 ;branch if call failed MOV HANDLE,AX ;store file handle MOV BX,AX ;transfer it to BX MOV AX,4202H ;position file pointer at XOR CX,CX ; the end of the file XOR DX,DX INT 21H CMP CALLFLAG,0 ;write date to log file JNE OPEN1 ; if this is the first INC CALLFLAG ; call to OPENFILE OPEN0: CALL WRITE_DATE CALL WRITE_HEADER ;then write header line OPEN1: CLC ;clear CF for exit OPEN2: RET ;exit ;--Call to open an existing file failed. Create a new one and initialize it. OPEN3: MOV AH,3CH ;attempt to create a new XOR CX,CX ; file of the same name MOV DX,ADDRSPEC INT 21H JC OPEN2 ;exit on error MOV HANDLE,AX ;store file handle MOV BX,AX ;transfer it to BX MOV CALLFLAG,1 ;set entry flag MOV AH,40H ;write copyright text MOV CX,70 MOV DX,OFFSET PROGRAM INT 21H JMP OPEN0 ;exit OPENFILE ENDP ;----------------------------------------------------------------------------- ; CLOSEFILE closes the log file. ;----------------------------------------------------------------------------- CLOSEFILE PROC NEAR ASSUME DS:CODE MOV AH,3EH MOV BX,HANDLE INT 21H RET CLOSEFILE ENDP ;----------------------------------------------------------------------------- ; WRITE_ENTRY writes a one-line entry to the log file. ; Entry: DS:SI - start time ; DS:DI - end time ; AL - last reentrancy level ;----------------------------------------------------------------------------- LASTLEVEL DB ? WRITE_ENTRY PROC NEAR ASSUME DS:CODE MOV LASTLEVEL,AL ;store last level number CALL WRITE_TIME ;write start time MOV CX,4 CALL WRITE_SPACES PUSH SI ;save start time MOV SI,DI ;write end time CALL WRITE_TIME MOV CX,3 CALL WRITE_SPACES POP SI ;retrieve start time CALL WRITE_DIFF ;write elapsed time MOV CX,5 CALL WRITE_SPACES MOV AL,LASTLEVEL ;write reentrancy level MOV BL,1 CALL WRITE_NUM MOV CX,6 CALL WRITE_SPACES MOV AL,13 ;calculate offset into MUL LASTLEVEL ; program name table MOV DX,AX ADD DX,OFFSET NAMES MOV AH,40H ;write program name MOV BX,DX MOV CL,[BX] XOR CH,CH PUSH CX MOV BX,HANDLE INC DX INT 21H POP BX CMP EXECFLAG,0 ;exit now if this entry is JE NOPARMS ; a return from EXEC MOV CX,12 ;calculate number of spaces SUB CX,BX ; to skip ADD CX,5 CALL WRITE_SPACES MOV BX,OFFSET COMLINE ;write command line passed MOV CL,[BX] ; to the last program XOR CH,CH ; EXECed to the log file JCXZ NOPARMS MOV AH,40H MOV BX,HANDLE MOV DX,OFFSET COMLINE+1 INT 21H NOPARMS: CALL WRITE_CRLF ;end line with CR/LF RET WRITE_ENTRY ENDP ;----------------------------------------------------------------------------- ; WRITE_DATE writes the current date to the log file. ;----------------------------------------------------------------------------- MONTHS DB "JanFebMarAprMayJunJulAugSepOctNovDec" CENTURY DB "19" WRITE_DATE PROC NEAR ASSUME DS:CODE CALL WRITE_CRLF ;skip one line MOV AH,2AH ;get date from DOS INT 21H PUSH CX ;save it PUSH DX MOV AL,DL ;write day of the month to XOR BL,BL ; the log file CALL WRITE_NUM MOV CX,1 ;skip a space CALL WRITE_SPACES POP DX ;retrieve month from stack DEC DH ;calculate offset into MONTH MOV CL,DH ; table of text for the XOR CH,CH ; current month MOV DX,OFFSET MONTHS JCXZ WRDATE2 WRDATE1: ADD DX,3 LOOP WRDATE1 WRDATE2: MOV AH,40H ;write month to log file MOV BX,HANDLE MOV CX,3 INT 21H MOV CX,1 ;skip a space CALL WRITE_SPACES MOV AH,40H ;write "19" portion of year MOV CX,2 MOV DX,OFFSET CENTURY INT 21H POP CX ;retrieve year from stack SUB CX,1900 ;subtract century portion MOV AL,CL ;write year to the log file XOR BL,BL CALL WRITE_NUM CALL WRITE_CRLF ;finish with CRLF pairs CALL WRITE_CRLF RET WRITE_DATE ENDP ;----------------------------------------------------------------------------- ; WRITE_TIME writes the time to the log file. Entry: DS:SI - time ;----------------------------------------------------------------------------- COLON DB ":" WRITE_TIME PROC NEAR ASSUME DS:CODE MOV AL,[SI+1] ;write hours to log file MOV BL,1 CALL WRITE_NUM MOV AH,40H ;write ":" to separate MOV BX,HANDLE ; hours and minutes MOV CX,1 MOV DX,OFFSET COLON INT 21H MOV AL,[SI] ;write minutes to log file XOR BL,BL CALL WRITE_NUM RET WRITE_TIME ENDP ;----------------------------------------------------------------------------- ; WRITE_DIFF writes the elapsed time to the log file. ; Entry: DS:SI - start time, DS:DI - end time ;----------------------------------------------------------------------------- DELTA DB 3 DUP (?) WRITE_DIFF PROC NEAR ASSUME DS:CODE MOV AL,[DI] ;retrieve starting and ending MOV BL,[SI] ; seconds, minutes, and MOV CL,[DI+1] ; hours MOV DL,[SI+1] MOV AH,[DI+2] MOV BH,[SI+2] CMP AH,BH ;if ending seconds is less JAE WRDIFF1 ; than starting, add 60 to ADD AH,60 ; ending seconds and DEC AL ; decrement ending minutes WRDIFF1: CMP AL,BL ;if ending minutes is less JGE WRDIFF2 ; than starting, add 60 to ADD AL,60 ; ending minutes and DEC CL ; decrement ending hours WRDIFF2: CMP CL,DL ;if ending hours is less JGE WRDIFF3 ; than starting, add 24 ADD CL,24 ; to ending hours WRDIFF3: SUB AH,BH ;calculate seconds difference MOV DELTA+2,AH ;store it SUB AL,BL ;calculate minutes difference MOV DELTA,AL ;store it SUB CL,DL ;calculate hours difference MOV DELTA+1,CL ;store it MOV SI,OFFSET DELTA ;write hours and minutes to CALL WRITE_TIME ; the log file MOV AH,40H ;write ":" to log file MOV BX,HANDLE MOV CX,1 MOV DX,OFFSET COLON INT 21H MOV AL,[SI+2] ;write seconds to log file XOR BL,BL CALL WRITE_NUM RET WRITE_DIFF ENDP ;----------------------------------------------------------------------------- ; WRITE_NUM converts a binary byte value to ASCII and writes it to the log ; file. Value must be between 0 and 99, inclusive. ; Entry: AL - byte value, BL - 0 = include leading zeroes, 1 = don't include ;----------------------------------------------------------------------------- FILEWORD DW ? WRITE_NUM PROC NEAR ASSUME DS:CODE AAM ;convert to BCD in AX ADD AX,3030H ;convert to ASCII OR BL,BL ;convert leading zero to JZ WRNUM1 ; space if BL = 1 on CMP AH,30H ; entry JNE WRNUM1 MOV AH,20H WRNUM1: XCHG AH,AL ;swap bytes MOV FILEWORD,AX ;store them MOV AH,40H ;then write them to the MOV BX,HANDLE ; log file MOV CX,2 MOV DX,OFFSET FILEWORD INT 21H RET WRITE_NUM ENDP ;----------------------------------------------------------------------------- ; WRITE_HEADER writes the column titles to the log file. ;----------------------------------------------------------------------------- TITLES DB "START",5 DUP (32),"END",5 DUP (32) DB "ELAPSED",4 DUP (32),"LEVEL",4 DUP (32) DB "PROGRAM",10 DUP (32),"PARAMETERS",13,10 EQUALSIGN DB "=" WRITE_HEADER PROC NEAR ASSUME DS:CODE MOV AH,40H ;write column titles MOV BX,HANDLE MOV CX,67 MOV DX,OFFSET TITLES INT 21H MOV CX,79 ;write row of "=" symbols WRHEAD1: PUSH CX MOV AH,40H MOV CX,1 MOV DX,OFFSET EQUALSIGN INT 21H POP CX LOOP WRHEAD1 CALL WRITE_CRLF ;end it with a CR/LF pair RET WRITE_HEADER ENDP ;----------------------------------------------------------------------------- ; WRITE_CRLF writes a carriage return / line feed to the log file. ;----------------------------------------------------------------------------- CRLF DB 13,10 WRITE_CRLF PROC NEAR ASSUME DS:CODE MOV AH,40H MOV BX,HANDLE MOV CX,2 MOV DX,OFFSET CRLF INT 21H RET WRITE_CRLF ENDP ;----------------------------------------------------------------------------- ; WRITE_SPACES writes CX spaces to the log file. ;----------------------------------------------------------------------------- SPACE DB 32 WRITE_SPACES PROC NEAR ASSUME DS:CODE PUSH CX ;save counter MOV AH,40H ;write one space MOV BX,HANDLE MOV CX,1 MOV DX,OFFSET SPACE INT 21H POP CX ;retrieve count LOOP WRITE_SPACES ;loop until done RET WRITE_SPACES ENDP ;----------------------------------------------------------------------------- ; RECORD_TIME records the current time in the slot designated by LEVEL. ;----------------------------------------------------------------------------- RECORD_TIME PROC NEAR ASSUME DS:CODE MOV AH,2CH ;get current time INT 21H MOV BX,LEVEL ;calculate offset into table SHL BX,1 ; of start/end times SHL BX,1 MOV WORD PTR TIMES[BX],CX ;store current time MOV BYTE PTR TIMES[BX+2],DH RET RECORD_TIME ENDP ;============================================================================= ; 418-byte buffer area used after LOG becomes resident. ;============================================================================= PC = $ FILESPEC = PC ;filespec buffer PC = PC + 80 NAMES = PC ;storage array for program PC = PC + 130 ; names TIMES = PC ;storage array for program PC = PC + 40 ; start/end times STACKSEG = PC ;storage array for SS PC = PC + 20 ; register values STACKPTR = PC ;storage array for SP PC = PC + 20 ; register values COMLINE = PC ;command line parameter buffer PC = PC + 128 LASTBYTE = PC ;============================================================================= ; INITIALIZE installs or uninstalls the program. ;============================================================================= ERRMSG1 DB "Usage: LOG [filespec] [/U]$" ERRMSG2 DB "Not Installed$" ERRMSG3 DB "Cannot Uninstall$" ERRMSG4 DB "Already Installed$" ERRMSG5 DB "Invalid Filespec$" OUTTEXT DB "Uninstalled$" DEFNAME DB "\USAGE.LOG",13 IDLETEXT DB 6,"<idle>" INSTALLED DB 0 INITIALIZE PROC NEAR ASSUME CS:CODE, DS:CODE ;--See if a copy of LOG is already resident in memory. CLD ;clear DF for string ops MOV WORD PTR [BEGIN],0 ;initialize fingerprint XOR BX,BX ;zero BX for start MOV AX,CS ;keep CS value in AX INIT1: INC BX ;increment search segment value MOV ES,BX CMP AX,BX ;not installed if current JE PARSE1 ; segment is reached MOV SI,OFFSET BEGIN ;search this segment for ASCII MOV DI,SI ; fingerprint MOV CX,16 REPE CMPSB JNE INIT1 ;loop back if not found MOV INSTALLED,1 ;set installed flag ;--Parse the command line for entries. PARSE1: MOV SI,81H ;point SI to command line PARSE2: LODSB ;get a character CMP AL,20H ;skip it if it's a space JE PARSE2 CMP AL,0DH ;exit loop when a carriage JE NOSPEC ; return is encountered CMP AL,"/" ;check for forward slash JNE QUALIFY ;anything else is a filespec LODSB ;get the next character AND AL,0DFH ;capitalize it CMP AL,"U" ;branch to uninstall code if JE UNINSTALL ; character is a "U" ;--An error was encountered in parsing. Display error message and exit. MOV DX,OFFSET ERRMSG1 ;load message address ERROR_EXIT: MOV AH,9 ;display error message INT 21H MOV AX,4C01H ;exit with ERRORLEVEL = 1 INT 21H ;--Uninstall the program. UNINSTALL: MOV DX,OFFSET ERRMSG2 ;error if program isn't CMP INSTALLED,0 ; installed JE ERROR_EXIT CALL REMOVE ;call uninstall routine MOV DX,OFFSET ERRMSG3 ;error if uninstall failed JC ERROR_EXIT MOV DX,OFFSET OUTTEXT ;display "Uninstalled" MOV AH,9 ; message INT 21H MOV AX,4C00H ;exit with ERRORLEVEL = 0 INT 21H ;--Convert the filespec into a fully qualified filename. NOSPEC: MOV SI,OFFSET DEFNAME+1 ;Use default filespec QUALIFY: MOV DX,OFFSET ERRMSG4 ;error if program is already CMP INSTALLED,0 ; installed JNE ERROR_EXIT DEC SI ;point DS:SI to filespec PUSH CS ;point ES:DI to temporary POP ES ; filespec buffer MOV DI,OFFSET CWDIR+80 CALL GENSPEC ;generate complete filespec MOV DX,OFFSET ERRMSG5 ;exit if error flag is set JC ERROR_EXIT ; on return MOV DI,OFFSET CWDIR+80 ;find the terminating zero in XOR AL,AL ; the ASCIIZ string MOV CX,80 REPNE SCASB DEC DI CMP BYTE PTR [DI-1],"\" ;append default filename if JNE TESTSPEC ; last character is a MOV SI,OFFSET DEFNAME+1 ; backslash MOV CX,9 REP MOVSB XOR AL,AL ;write ASCIIZ terminator STOSB ;--Test the filespec and create the log file if it doesn't already exist. TESTSPEC: MOV DX,OFFSET CWDIR+80 CALL OPENFILE ;open/create log file MOV DX,OFFSET ERRMSG5 ;error if file could not JC ERROR_EXIT ; be opened CALL CLOSEFILE ;close log file CALL RECORD_TIME ;record starting time MOV AX,3000H ;record version of DOS INT 21H ; LOG is running under XCHG AH,AL MOV DOSVERSION,AX ;--Hook into interrupt 21h and deallocate the program's environment block. MOV AX,3521H ;save old interrupt vector INT 21H MOV WORD PTR INT21H,BX MOV WORD PTR INT21H[2],ES MOV AX,2521H ;then set the new vector MOV DX,OFFSET DOSINT INT 21H MOV AX,DS:[2CH] ;deallocate the program's MOV ES,AX ; environment block MOV AH,49H INT 21H ;--Initialize buffer areas. PUSH CS ;point ES to code segment POP ES MOV SI,OFFSET CWDIR+80 ;copy filepsec string to MOV DI,OFFSET FILESPEC ; filespec buffer COPYLOOP: LODSB STOSB OR AL,AL JNZ COPYLOOP MOV SI,OFFSET IDLETEXT ;copy "<idle>" string to MOV DI,OFFSET NAMES ; program names buffer MOV CX,7 REP MOVSB ;--Display copyright notice, then terminate and remain resident in memory. MOV AH,9 ;display copyright MOV DX,OFFSET PROGRAM INT 21H MOV AX,3100H MOV DX,(OFFSET LASTBYTE - OFFSET CODE + 15) SHR 4 INT 21H INITIALIZE ENDP ;----------------------------------------------------------------------------- ; REMOVE deallocates the memory block addressed by ES and restores the ; interrupt vector displaced on installation. ; Entry: ES - segment to release ; Exit: CF clear - program uninstalled, CF set - can't uninstall ;----------------------------------------------------------------------------- REMOVE PROC NEAR MOV CX,ES ;abort if the interrupt 21h MOV AX,3521H ; vector has been altered INT 21H ; since installation MOV AX,ES CMP AX,CX JNE REMOVE_ERROR MOV ES,CX MOV AH,49H ;free memory given to INT 21h ; original program block JC REMOVE_ERROR ;branch on error PUSH DS ;restore interrupt vector ASSUME DS:NOTHING MOV AX,2521H LDS DX,ES:[INT21H] INT 21H POP DS ASSUME DS:CODE NOT WORD PTR ES:[BEGIN] ;destroy ASCII fingerprint CLC ;clear CF for exit RET REMOVE_ERROR: STC ;set CF to indicate program RET ; couldn't be uninstalled REMOVE ENDP ;----------------------------------------------------------------------------- ; GENSPEC generates a fully qualified filename. ; Entry: DS:SI - filename, ES:DI - filespec buffer ; Exit: CF clear - drive/directory valid, CF set - drive/directory invalid ;----------------------------------------------------------------------------- ADDRIN DW ? ;input string address ADDROUT DW ? ;output string address DEFDRIVE DB ? ;default drive ADDRSLASH DW ? ;address of last backslash GENSPEC PROC NEAR MOV ADDROUT,DI ;save output buffer address ;--Save the current drive and directory. PUSH SI ;save SI MOV AH,19H ;get current drive and INT 21H ; save it MOV DEFDRIVE,AL MOV AH,47H ;get current directory and XOR DL,DL ; save it MOV SI,OFFSET CWDIR+1 INT 21H POP SI ;restore SI ;--Check for a drive designator and supply default drive code if needed. CMP BYTE PTR [SI+1],":" ;see if drive designator JNE GEN1 ; appears in filespec LODSW ; buffer and capitalize AND AL,0DFH ; drive letter if it does JMP SHORT GEN2 GEN1: MOV AL,DEFDRIVE ;get default drive letter ADD AL,"A" ;convert to ASCII MOV AH,":" ;append colon GEN2: STOSW ;and output drive spec ;--Scan the input filespec for the rightmost backslash character. MOV ADDRIN,SI ;save filespec address XOR CX,CX ;initialize counter GEN3: LODSB ;get next byte CMP AL,0DH ;scan finished if character JE GEN4 ; is a carriage return or CMP AL,20H ; space JE GEN4 CMP AL,"\" ;loop back if it's anything JNE GEN3 ; but a backslash MOV CX,SI ;save address of backslash DEC CX ; character and return to JMP GEN3 ; scan loop ;--Copy everything up to the last backslash to the output buffer. GEN4: MOV ADDRSLASH,CX ;save backslash address MOV SI,ADDRIN ;point SI back to filespec OR CX,CX ;terminate string if no JZ GEN6 ; backslashes found CMP SI,CX ;copy leading backslash if JE GEN5 ; if it was the only one SUB CX,SI ;otherwise copy everything DEC CX ; up to the last backslash JCXZ GEN5 REP MOVSB GEN5: MOVSB GEN6: XOR AL,AL ;append binary zero to form STOSB ; ASCIIZ string ;--Change to the drive and directory specified and get a full pathname. MOV AH,0EH ;switch current drive MOV DI,ADDROUT MOV DL,[DI] SUB DL,"A" INT 21H CMP BYTE PTR [DI+2],0 JE GEN7 MOV AH,3BH ;set current directory to MOV DX,ADDROUT ; the one just formulated ADD DX,2 ; and exit if the call fails INT 21H JC GEN_EXIT GEN7: MOV AH,47H ;now request a complete MOV SI,ADDROUT ; directory string from MOV DL,[SI] ; DOS SUB DL,40H ADD SI,3 MOV BYTE PTR [SI-1],"\" INT 21H JC GEN_EXIT ;exit on error ;--Reset the default drive and directory. MOV AH,0EH ;reset default drive MOV DL,DEFDRIVE INT 21H MOV AH,3BH ;finish up by setting the MOV DX,OFFSET CWDIR ; current directory to what INT 21H ; it was when we started JC GEN_EXIT ;--Append the filename to the directory specification if one was entered. MOV DI,ADDROUT ;find ASCIIZ terminator byte XOR AL,AL ; in output buffer MOV CX,80 REPNE SCASB DEC DI CMP BYTE PTR [DI-1],"\" ;insert a backslash if JE GEN8 ; there's not one at the MOV AL,"\" ; end of the string STOSB GEN8: MOV SI,ADDRSLASH ;point SI to the first OR SI,SI ; character in the JNZ GEN9 ; input filename MOV SI,ADDRIN JMP SHORT GEN10 GEN9: INC SI GEN10: LODSB ;copy characters until a CMP AL,0DH ; carriage return or space JE GEN11 ; delimiter is reached CMP AL,20H JE GEN11 STOSB JMP GEN10 GEN11: XOR AL,AL ;append terminating zero byte STOSB ; to string CLC ;clear CF and exit GEN_EXIT: RET GENSPEC ENDP CWDIR DB "\" ;directory string buffer CODE ENDS END BEGIN