PAGE ,132 TITLE File Chain Program, Version 2.16, 24-Dec-1986 ; Bug Fix Version 2.17, 28 May 1989 by David Gwillim ; Subdirectories were reported as using 0 clusters with 100% waste ; Also corrected typo for pgm lines storing file size to SIZEMSB ; Changed/Added lines marked with a comment starting with ;!! ; ; Written By Steven Georgiades ; ; File Chain Program ; Will respond with a list of the disk clusters that make up the file chain ; for the requested file. ; ; If you are using this program and find it of value, your ; contribution in any amount ($5.00 suggested) will be greatly ; appreciated. Makes checks payable to Steven M. Georgiades. ; Thank you. ; ; If you have any questions or comments about this or any other ; SMG program, call or write: ; ; Steven M. Georgiades ; 701-H South Hayward Street ; Anaheim, CA 92804 ; (714) 826-9549 ; CODE SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:CODE,DS:CODE,ES:CODE,SS:CODE ORG 5CH FCB LABEL BYTE ORG 80H PARAM LABEL BYTE ORG 100H CHAIN: JMP BEGIN CHAINMSG1 DB "The Chain for $" CHAINMSG2 DB " is:",13,10,10,"$" CHAINMSG3 DB 13,10,10 CHAINMSG4 DB "Total Clusters in File = XXXXX",13,10,10 CHAINMSG5 DB "Physical File Length = XXXXXXXX",13,10,"$" ;!! CHAINMSG6 DB "Logical File Length = XXXXXXXX (" PERCENT DB " 0.00% Waste)",13,10,"$" ISDIR DB 'File is a SUBDIRECTORY',13,10,"$" ;!! LOGSZMSG DW OFFSET CHAINMSG6 CLSTSTR DB "XXXXX$" PRCNT100 DB "100.0" ANDSTR DB " , $" THRUSTR DB " to$" SPECERR DB "Invalid File or Path Specification",7,": $" CRLF DB 13,10,"$" SIGNON DB "File Chain Program, Version 2.17",13,10 ;!! DB "SMG Software",13,10 DB "(C) Copyright 1986, 1989 Steven M. Georgiades" ;!! DB 13,10,13,10,"$" ;!! USAGE DB 13,10,"Usage:",13,10,13,10 ;!! DB " CHAIN [d:][path]filename[.ext]",13,10,13,10 ;!! DB " Will respond with a list of the disk clusters that make up the specified",13,10 DB " file. Note that wildcards are NOT allowed.",13,10,"$" CLSTSEC DW ? DIR_LEN DW ? DIRBUF DW ? DIRSEC DW ? DRIVE DB ? EOF DW 0FF8H FATSEC DW ? FATSIZE DB 3 FILENAME DB 13 DUP(0) PREV DW ? RANGE DB 0 SECSIZE DW ? SIZLSB DW ? SIZMSB DW ? STARTSEC DW ? FILESPEC DB "$",79 DUP(0) BEGIN: MOV AH,9 ; Output Sign-On Message MOV DX,OFFSET SIGNON INT 21H MOV AH,19H ; Get Default Drive INT 21H MOV DRIVE,AL ; and Save MOV SI,OFFSET PARAM ; Set up Pointer to Parameter LODSB ; Read Parameter Length CBW MOV BX,AX MOV BYTE PTR [SI][BX],0 ; Terminate Parameter String STRIP: LODSB ; Strip Off Leading Whitespace CMP AL,' ' JE STRIP CMP AL,9 JE STRIP OR AL,AL ; If End-of-Parameter, Error JNZ NO_ERR1 MOV DX,OFFSET USAGE JMP ERROUT NO_ERR1: DEC SI ; ReUse Last Character MOV DI,OFFSET FILESPEC ; Point to FileSpec Buffer CMP BYTE PTR [SI+1],':' ; If 2nd Character is Colon, JNE GET_PATH AND AL,0DFH SUB AL,'A' ; Get Drive Number MOV DRIVE,AL ADD SI,2 ; and Scan Past GET_PATH: MOV AL,DRIVE ; Get Drive Number ADD AL,'A' ; Convert to Drive Number STOSB ; in FileSpec MOV AL,':' ; Add Colon to FileSpec STOSB CMP BYTE PTR [SI],'\' ; If Next Character is not '\', JE COPYPATH MOV AL,'\' STOSB PUSH SI ; Get Current Path to FileSpec MOV SI,DI MOV AH,47H MOV DL,DRIVE INC DL INT 21H POP SI MOV AL,0 ; Find End-of-FileSpec MOV CX,64 REPNE SCASB JE NO_ERR2 JMP ERROR NO_ERR2: DEC DI CMP BYTE PTR [DI-1],'\' ; If Root DIR, Skip JE COPYPATH MOV AL,'\' ; End-of-FileSpec = '\' STOSB COPYPATH: LODSB ; Copy Rest of Parameter OR AL,AL ; to FileSpec JZ COPYDONE STOSB JMP SHORT COPYPATH COPYDONE: MOV AL,'$' ; Set End-of-FileSpec STOSB MOV DX,OFFSET FILESPEC ; Convert to Upper Case CALL UPPER MOV AL,DRIVE ; Read Boot Record MOV CX,1 MOV DX,0 MOV BX,OFFSET FATBUF INT 25H POPF MOV AX,FATBUF[11] ; Read Sector Size MOV SECSIZE,AX ; and Save MOV AL,BYTE PTR FATBUF[13] ; Read Sectors per Cluster XOR AH,AH MOV CLSTSEC,AX ; and Save MOV CX,FATBUF[14] ; Read # of Reserved Sectors MOV AL,BYTE PTR FATBUF[16] ; Read # of FAT's XOR AH,AH ; Convert to Word MOV BX,FATBUF[22] ; Read Sectors per FAT MOV FATSEC,BX MUL BX ; Calculate Total FAT Sectors ADD CX,AX ; Add to Reserved Sectors MOV AX,FATBUF[17] ; Read Number of DIR Entries MOV DIR_LEN,AX PUSH CX ; Calculate DIR Sectors MOV CL,5 SHL AX,CL POP CX MOV BX,SECSIZE XOR DX,DX DIV BX OR DX,DX ; Adjust for Partial Sector JZ NO_ADD INC AX NO_ADD: MOV DIRSEC,AX ; Save DIR Sectors ADD CX,AX ; Add DIR Sectors to Reserved MOV STARTSEC,CX ; Save in STARTSEC MOV AX,FATSEC ; Calculate FAT Buffer Size MOV BX,SECSIZE MUL BX MOV BX,AX LEA AX,FATBUF[BX] ; Get DIR Buffer Pointer MOV DIRBUF,AX MOV AX,FATBUF[19] ; Read Total Sectors on Media SUB AX,CX ; Calculate Total Data Clusters MOV BX,CLSTSEC XOR DX,DX DIV BX CMP AX,4079 ; If Necessary, Adjust FAT Size JLE FAT_OK MOV FATSIZE,4 MOV EOF,0FFF8H FAT_OK: MOV AL,DRIVE ; Read FAT MOV CX,FATSEC MOV DX,1 MOV BX,OFFSET FATBUF INT 25H POPF MOV AL,DRIVE ; Read Root Directory MOV CX,DIRSEC MOV DX,STARTSEC SUB DX,CX MOV BX,DIRBUF INT 25H POPF MOV SI,OFFSET FILESPEC[3] ; Point to First Element of Path NEXT_FLD: MOV DI,OFFSET FILENAME ; Point to FileName Buffer NEXTCHAR: LODSB ; Copy Path Element to FileName CMP AL,'\' ; until '\' or '$' JE GOT_DIR CMP AL,'$' JE GOT_FNM STOSB JMP SHORT NEXTCHAR GOT_DIR: MOV AL,0 ; If '\', Read Subdirectory STOSB CALL READDIR JNC NEXT_FLD ; If No Error, Get Next Dir JMP ERROR ; Else Flag Error GOT_FNM: MOV AL,0 ; If '$', STOSB CALL SRCHFILE ; Read File DIR Info JNC NO_ERR3 JMP ERROR ; If Error, Say So NO_ERR3: MOV AX,[BX+28] ; Read File Length MOV SIZLSB,AX MOV DX,[BX+30] MOV SIZMSB,DX ;!! was mov sizmsb,ax MOV DI,OFFSET CHAINMSG6[31] ; Convert to ASCII CALL DEC8OUT ; Strip Off Leading Zeroes CALL STRIP0 MOV AH,9 ; Output FileSpec Message MOV DX,OFFSET CHAINMSG1 INT 21H MOV AH,9 MOV DX,OFFSET FILESPEC INT 21H MOV AH,9 MOV DX,OFFSET CHAINMSG2 INT 21H MOV BX,[BX+26] ; Read Starting Cluster Number XOR CX,CX ; Cluster Count = 0 OR BX,BX ; If Start Cluster = 0, Done JZ LASTCLST MOV AX,BX ; Output First Cluster Number CALL OUT_CLST MOV PREV,BX ; Previous Cluster = First INC CX ; Cluster Count = 1 CALL NEXTCLST ; Get Next Cluster Number CMP BX,EOF ; If Last, Skip JNB LASTCLST CLSTLOOP: INC CX ; Increment Cluster Count MOV AX,PREV ; If Cluster is Previous + 1, INC AX CMP AX,BX JNE CLSTOUT MOV RANGE,-1 ; Set Range Flag JMP SHORT NEXTONE CLSTOUT: CMP RANGE,0 ; Else JE NORANGE ; If Range Flag is Set, MOV AH,9 ; Output " - " MOV DX,OFFSET THRUSTR INT 21H MOV AX,PREV ; Output Last Clstr in Range CALL OUT_CLST MOV RANGE,0 ; Reset Range Flag NORANGE: MOV AH,9 ; Output ", " MOV DX,OFFSET ANDSTR INT 21H MOV AX,BX ; Output Cluster Number CALL OUT_CLST NEXTONE: MOV PREV,BX ; Previous = Current CALL NEXTCLST ; Get Next Cluster Number CMP BX,EOF ; If Not EOF, Repeat JB CLSTLOOP CMP RANGE,0 ; If Range Flag is Set, JE LASTCLST MOV AH,2 ; Output " - " MOV DL,'-' INT 21H MOV AX,PREV ; Output Last Cluster of Range CALL OUT_CLST LASTCLST: MOV AX,SIZMSB ; If Size = 0, 100% Waste OR AX,SIZLSB JNZ NOT_ZERO JCXZ LC1 ;!! allow for files MOV LOGSZMSG,OFFSET ISDIR ;!! say this is a subdirectory JMP SHORT CLSTCNT ;!! LC1: MOV SI,OFFSET PRCNT100 MOV DI,OFFSET PERCENT MOV CX,5 REP MOVSB JMP SHORT CLSTCNT NOT_ZERO: MOV AX,SECSIZE ; Calculate Percent Waste MOV BX,CLSTSEC MUL BX MOV BX,AX MUL CX SUB AX,SIZLSB MOV DX,100 MUL DX DIV BX PUSH DX XOR DX,DX DIV CX PUSH DX MOV DI,OFFSET PERCENT[2] ; Convert Percent to ASCII CALL DEC2OUT CALL STRIP0 POP AX MUL BX POP DI ADD AX,DI ADC DX,0 DIV CX MOV DX,100 MUL DX DIV BX MOV DI,OFFSET PERCENT[5] CALL DEC2OUT CLSTCNT: MOV AX,CX ; Convert Cluster Count to ASCII MOV DI,OFFSET CHAINMSG4[31] CALL DEC5OUT CALL STRIP0 ; Strip Off Leading Zeroes MOV AX,CLSTSEC ; Calculate Physical File Size MUL CX MOV CX,SECSIZE MUL CX MOV DI,OFFSET CHAINMSG5[31] ; Convert to ASCII CALL DEC8OUT CALL STRIP0 ; Strip Off Leading Zeroes MOV AH,9 ; Output Trailer Message MOV DX,OFFSET CHAINMSG3 INT 21H MOV AH,9 ;!! Output Logical Size message MOV DX,LOGSZMSG ;!! INT 21H ;!! MOV AX,4C00H ; Exit to DOS INT 21H ERROR: MOV AH,9 ; Output Error Message MOV DX,OFFSET SPECERR INT 21H MOV AH,9 MOV DX,OFFSET FILESPEC ; Output FileSpec INT 21H MOV DX,OFFSET CRLF ERROUT: MOV AH,9 INT 21H MOV AX,4C01H ; Exit to DOS INT 21H SRCHFILE: PUSH AX ; Save Register PUSH CX PUSH DI PUSH SI CMP FILENAME,'.' ; If Filename Starts with '.', JNE SRCHFIL2 MOV SI,OFFSET FILENAME ; Copy Filename to FCB MOV DI,OFFSET FCB[1] MOV CX,11 SRCHFIL1: LODSB STOSB OR AL,AL LOOPNZ SRCHFIL1 INC CX ; and Pad With Spaces DEC DI MOV AL,' ' REP STOSB JMP SHORT SRCHFIL3 SRCHFIL2: MOV AX,2900H ; Else Parse Filename MOV SI,OFFSET FILENAME MOV DI,OFFSET FCB INT 21H SRCHFIL3: MOV DI,OFFSET FCB[1] ; Point to Filename in FCB MOV SI,DIRBUF ; Point to Start of DIRBUF MOV CX,DIR_LEN ; Load DIR Length JCXZ SRCHFIL5 ; If Zero, Not Found SRCHFIL4: PUSH CX ; Save Registers PUSH DI PUSH SI MOV CX,11 ; Compare Filename REPE CMPSB POP SI ; Restore Registers POP DI POP CX JE SRCHFIL6 ; If Match, Found ADD SI,32 ; Else Next DIR Entry LOOP SRCHFIL4 ; and Repeat SRCHFIL5: STC ; Set Error Flag JMP SHORT SRCHFIL7 ; Done SRCHFIL6: MOV BX,SI ; Set Pointer to DIR Entry CLC ; Clear Error Flag SRCHFIL7: POP SI ; Restore Registers POP DI POP CX POP AX RET ; Done READDIR: PUSH CX ; Save Registers PUSH DI PUSH SI CALL SRCHFILE ; Search for DIR JC READDIR2 ; If Not Found, Error MOV BX,[BX+26] ; Get Starting Cluster Number MOV DIR_LEN,0 ; DIR Length = 0 OR BX,BX ; If Start Cluster = 0, Error JZ READDIR2 MOV AX,SECSIZE ; Calculate DIR Entries/Cluster MOV CX,CLSTSEC MUL CX MOV CL,5 SHR AX,CL MOV CX,AX MOV DI,DIRBUF ; Point to DIR Buffer READDIR1: CALL READCLST ; Read Cluster ADD DIR_LEN,CX ; Increment DIR Length CMP BX,EOF ; If Not Last Cluster, JB READDIR1 ; Get Next Cluster JMP SHORT READDIR3 ; Done Reading DIR READDIR2: STC ; Set Error Flag READDIR3: POP SI ; Restore Registers POP DI POP CX RET ; Done READCLST: PUSH AX ; Save Registers PUSH CX PUSH DX MOV AX,BX ; Calculate Absolute Sector # SUB AX,2 MOV CX,CLSTSEC MUL CX ADD AX,STARTSEC MOV DX,AX MOV AL,DRIVE PUSH BX ; Save Registers PUSH DI MOV BX,DI ; Read Cluster INT 25H POPF POP DI ; Restore Registers POP BX CALL NEXTCLST ; Get Next Cluster Number MOV AX,CLSTSEC ; Increment Buffer Pointer MOV CX,SECSIZE MUL CX ADD DI,AX POP DX ; Restore Registers POP CX POP AX RET ; Done NEXTCLST: CMP FATSIZE,3 ; If FAT Size = 16 Bits, JE NEXTCLS1 SHL BX,1 ; Simply Read Next Cluster # MOV BX,FATBUF[BX] RET ; Done NEXTCLS1: PUSH AX ; Save Registers PUSH CX MOV AX,BX ; Word # = Cluster # * 1.5 SHL AX,1 ADD BX,AX SHR BX,1 MOV BX,FATBUF[BX] JNC NEXTCLS2 ; If Odd, Use 12 MSB's MOV CL,4 SHR BX,CL NEXTCLS2: AND BX,0FFFH ; Else Use 12 LSB's POP CX ; Restore Registers POP AX RET ; Done UPPER: PUSH AX ; Save Registers PUSH DX PUSH DI PUSH SI MOV SI,DX ; Set Up Source Index MOV DI,DX ; Set Up Destination Index UPPER1: LODSB ; Read From Source OR AL,AL ; If EOS, Done JZ UPPER3 CMP AL,'a' ; If Lower Case, JL UPPER2 CMP AL,'z' JG UPPER2 SUB AL,'a'-'A' ; Convert to Upper Case UPPER2: STOSB ; Save in Destination JMP SHORT UPPER1 ; Repeat UPPER3: POP SI ; Restore Registers POP DI POP DX POP AX RET ; Done OUT_CLST: PUSH AX ; Save Registers PUSH DX PUSH DI MOV DI,OFFSET CLSTSTR[5] ; Convert Cluster # to Decimal CALL DEC5OUT CALL STRIP0 ; Strip Off Leading Zeroes MOV AH,9 ; Output Cluster Number MOV DX,OFFSET CLSTSTR INT 21H POP DI ; Restore Registers POP DX POP AX RET ; Done STRIP0: CMP BYTE PTR [DI],'0' ; If Character != '0', Done JNE STRIP1 CMP BYTE PTR [DI+1],'0' ; If Next Character != Digit, JL STRIP1 ; Done CMP BYTE PTR [DI+1],'9' JG STRIP1 MOV BYTE PTR [DI],' ' ; Change '0' to ' ' INC DI ; Point to Next Character JMP SHORT STRIP0 ; Repeat STRIP1: RET ; Done DEC2OUT: PUSH AX ; Save Registers PUSH BX XOR AH,AH ; Clear AH MOV BL,10 ; AH=AX%10,AL=AX/10 DIV BL ADD AX,'00' ; Convert to ASCII SUB DI,2 MOV [DI],AX ; Store in String POP BX ; Restore Registers POP AX RET ; Done DEC4OUT: PUSH AX ; Save Registers PUSH BX MOV BL,100 ; AH=AX%100,AL=AX/100 DIV BL XCHG AH,AL ; Convert 2 LSD's CALL DEC2OUT XCHG AH,AL ; Convert 2 MSD's CALL DEC2OUT POP BX ; Restore Registers POP AX RET ; Done DEC5OUT: PUSH AX ; Save Registers PUSH BX PUSH DX ; DX=AX%10000,AX=AX/10000 MOV BX,10000 XOR DX,DX DIV BX XCHG DX,AX ; Convert 4 LSD's CALL DEC4OUT XCHG DX,AX ; Convert MSD ADD AL,'0' SUB DI,1 MOV [DI],AL POP DX ; Restore Registers POP BX POP AX RET ; Done DEC8OUT: PUSH AX ; Save Registers PUSH BX PUSH DX MOV BX,10000 ; DX=DX:AX%10000,AX=DX:AX/10000 DIV BX XCHG DX,AX ; Convert 4 LSD's CALL DEC4OUT XCHG DX,AX ; Convert 4 MSD's CALL DEC4OUT POP DX ; Restore Registers POP BX POP AX RET ; Done FATBUF LABEL WORD CODE ENDS END CHAIN