Metropoli BBS
VIEWER: undel.asm MODE: TEXT (ASCII)
        COMMENT*--------------------------------------------------------
              This program will undelete files. To use, type UNDEL FILE.EXT or
        UNDEL DRIVE:FILE.EXT where DRIVE is A or B. For ASCII files use UNDEL/A 
        FILE.EXT or UNDEL/A DRIVE:FILE.EXT; this prints out what is in each
        cluster before it adds it to the file and asks (Y/N) if it's correct. 
        Only for use with DOS 2.0 and 2.10 double sided disks.
        ---------------------------------------------------------------*
        FCB_LOC EQU     5CH     ;Location of FCB for deleted file in the PSP
        SWITCH_LOC EQU  81H     ;Location of text typed after 'UNDEL'
        BIG_A   EQU     412FH   ;Test chars for /A
        SMALL_A EQU     612FH   ;Test chars for /a
        CR      EQU     13      ;ASCII carriage return
        LF      EQU     10      ;ASCII line feed
CODE_SEG        SEGMENT
        ASSUME  CS:CODE_SEG,DS:CODE_SEG,ES:CODE_SEG
        ORG     100H            ;To make this a .com file
ENTRY:  JMP     FIRST           ;Skip over data area

        COPY_RIGHT      DB      '(C) 1984 S HOLZNER'
        I_O_FLAG        DW ?    ;Selects read or write of cluster
        START_CLUSTER   DW ?    ;Beginning cluster of deleted file
        FILE_SIZE       DW ?    ;File size in clusters (1 cluster=2 sectors)
        DISK_DRIVE      DB 0    ;Drive of deleted file
        NOT_FOUND_MSG   DB 13,'File not found deleted$'        ;Messages
        WRITTEN_OVER_MSG DB 13,'File already written over$'
        IS_IT_MSG       DB 13,10,'Is part of your file here? (Y/N) $'

        DATA    DB 1024 DUP(0)  ;Space for disk directory and FAT
        PROMPT_SECTOR  DB 512 DUP(0)    ;/A option uses for parts of file

FIRST:                          ;Start the process
UNDEL   PROC    NEAR
        MOV     AL,CS:FCB_LOC   ;Get the drive specified - 0 if none given
        SUB     AL,1            ;Was it a 1 (A:) or 2 (B:)?
        JNC     DRIVE_KNOWN     ;Yes, store drive number
        MOV     AH,19H          ;No, get default drive from INT 21H
        INT     21H
DRIVE_KNOWN:
        MOV     DISK_DRIVE,AL   ;Store drive
        MOV     DX,3            ;Start to search dir, starts at sector 5
LOOP:   ADD     DX,2            ;Add two to point at correct dir cluster
        CMP     DX,11           ;Past end of directory?
        JB      READ_DIR        ;If not, read dir into data area
        MOV     AH,9            ;Past end of dir & no match, exit with error
        LEA     DX,NOT_FOUND_MSG
        INT     21H
        JMP     OUT             ;Exit
READ_DIR:
        AND     I_O_FLAG,0      ;Select read
        CALL    CLUSTER_I_O     ;Get two sectors into data area
        LEA     DI,DATA         ;Prepare to search for deleted entry
        MOV     AL,0E5H         ;DOS set first char of deleted entry to E5H
        MOV     CX,400H         ;Counter=1024 to search this entire dir cluster
SEARCH:                         ;Look-for-next-deleted-entry loop
REPNE   SCASB
        JCXZ    LOOP            ;If no match (Counter=0), get next dir cluster
        MOV     SI,FCB_LOC+2    ;Possible match, point to 2nd char of file name
        MOV     BX,11           ;Compare all chars in name
CMPLOOP:DEC     BX              ;Decrement the name comparison loop counter
        CMPS    [DI],[SI]       ;Compare dir entry and deleted file name
        JZ      CMPLOOP         ;If match, check next char
        CMP     BX,0            ;Compare done, all chars matched perfectly?
        JNZ     SEARCH          ;No, keep checking through this dir cluster
        MOV     AX,CS:FCB_LOC+1         ;Yes, get file's first letter 
        MOV     [DI-12],AX      ;Move it into deleted entry (replace E5H)

        OR      I_O_FLAG,1      ;Select a write of 1 cluster
        CALL    CLUSTER_I_O     ;Write directory with undeleted name to disk
        MOV     AX,[DI+14]      ;Get starting cluster to use in FAT
        MOV     START_CLUSTER,AX        ;Store it
        MOV     AX,[DI+16]      ;Get low word of file size (in bytes)
        TEST    AX,1023         ;Find # of clusters - is MOD(size,1024)=0?
        JZ      EVEN_K          ;Yes, don't add 1 cluster before SHR
        ADD     AX,1024         ;No, need another cluster
EVEN_K: MOV     CL,10           ;Divide by 1024 (=cluster size)
        SHR     AX,CL
        MOV     DX,[DI+18]      ;High word of file size
        MOV     CL,6            ;Multiply by 2^16=65536, divide by 2^10=1024
        SHL     DX,CL
        ADD     AX,DX           ;Add high word clusters to low word clusters
        MOV     FILE_SIZE,AX    ;And store in FILE_SIZE
        MOV     DX,1            ;Read in File Allocation Table (FAT)
        AND     I_O_FLAG,0      ;Select a read of 1 cluster
        CALL    CLUSTER_I_O
        MOV     CX,FILE_SIZE    ;Counter to loop over # of clusters
        MOV     AX,START_CLUSTER        ;Check if written over already
        DEC     AX              ;Move back one cluster from beginning of file
        CALL    GET_NEXT_ZERO   ;Is next empty space in FAT the START_CLUSTER?
        CMP     DX,START_CLUSTER        
        JE      FILL            ;Yes, OK to start filling FAT
        MOV     AH,13H          ;No, file written over
        MOV     DX,FCB_LOC      ;Delete restored dir entry; point to file name 
        INT     21H             ;And delete it
        MOV     AH,9            ;Exit with error
        LEA     DX,WRITTEN_OVER_MSG
        INT     21H
        JMP     OUT             ;So long
FILL:   MOV     AX,DX           ;Set old empty space in FAT to one just found
        MOV     DX,0FFFH        ;Assume this is last entry (FFFH=End of file)
        CMP     CX,1            ;Is it the last entry? (CX=cluster counter)
        JZ      LAST            ;Yes, don't need to find next empty entry (zero)
        CALL    GET_NEXT_ZERO   ;Call with AX=old zero, returns DX=new zero
LAST:   CALL    PUT_FAT_ENTRY   ;Call with AX=old zero, DX=new zero, changes FAT
        LOOP    FILL            ;Work on next cluster
        MOV     DX,1            ;Prepare to write new FAT, 1st copy
        OR      I_O_FLAG,1      ;Select to write 1 cluster
        CALL    CLUSTER_I_O     ;Write the cluster
        MOV     DX,3            ;Prepare to write new FAT, 2nd copy
        CALL    CLUSTER_I_O
OUT:    INT     20H             ;And leave
UNDEL   ENDP                    

CLUSTER_I_O      PROC    NEAR    ;Reads specified cluster (dir, FAT etc.)
        COMMENT* Put start sector in DX, loads Cluster into 'DATA' area*
        PUSH    AX              ;Save the used registers
        PUSH    BX              ;INT 25H destroys all reg.s
        PUSH    CX
        PUSH    DX
        PUSH    DI
        MOV     AL,DISK_DRIVE   ;Get disk drive
        MOV     CX,2            ;Request 2 sectors (1 cluster) to be read
        LEA     BX,DATA         ;Point to DATA area
        TEST    I_O_FLAG,1
        JNZ     WRITE
        INT     25H             ;Read sector interrupt
        JMP     POPOUT
WRITE:  INT     26H
POPOUT: POPF                    ;Pop the extra push of flags
        POP     DI              ;Pop used (destroyed) registers
        POP     DX
        POP     CX
        POP     BX
        POP     AX
        RET                     ;Return
CLUSTER_I_O      ENDP

GET_NEXT_ZERO   PROC    NEAR    ;Unravel FAT and find next empty space in it
        COMMENT* FAT entry number (cluster #) in AX, returns next zero in DX*
        PUSH    AX              ;Push used reg.s
        PUSH    BX
        PUSH    CX
CHECK_NEXT:                     ;Entry by entry loop
        INC     AX              ;AX is entry pointer, start with next entry
        MOV     BX,AX           ;Get 3/2*AX for actual offset into FAT 
        SHL     BX,1            ; (Since each entry is 1.5 bytes)
        ADD     BX,AX
        SHR     BX,1            ;BX has FAT offset value for entry # in AX
        MOV     DX,WORD PTR DATA[BX]        ;DX now has FAT entry's value
        TEST    AX,1            ;Is the entry # even?
        JZ      EVEN_ENTRY      ;Yes, use bottom 12 bits
        MOV     CL,4            ;No, use top 12 bits
        SHR     DX,CL
EVEN_ENTRY:
        AND     DX,0FFFH        ;Get bottom 12 bits (OK now for even or odd)
        CMP     DX,0            ;Is the value for the given cluster # 0?
        JNE     CHECK_NEXT      ;No, look for next cluster
        MOV     DX,AX           ;Move entry value of zero into AX
        CMP     AX,START_CLUSTER   ;If this is the 1st one, skip /A option
        JE      POPS
        MOV     BX,SWITCH_LOC   ;Was /A specified? Check data area in PSP
        MOV     CX,[BX]         
        CMP     CX,SMALL_A      ;Check for /a
        JE      A_OPTION        ;Yes, do /A
        CMP     CX,BIG_A        ;No, maybe a /A?
        JNE     POPS            ;No, leave.
A_OPTION:                       ;/A was specified
        CALL    PRINT_OUT       ;Print out prompt sector
        JCXZ    CHECK_NEXT      ;If CX set to 0, sector wasn't right
POPS:   POP     CX              ;The pops before going
        POP     BX
        POP     AX
        RET
GET_NEXT_ZERO   ENDP

PRINT_OUT       PROC    NEAR    ;Print out a prompt sector
        COMMENT* Returns CX=1 if found the right sector, 0 otherwise *
        PUSH    DX              ;Push used reg
        ADD     DX,DX           ;Get 2*DX for sector number
        ADD     DX,8            ;Add 8 to skip boot, FATs and dir
        MOV     AL,DISK_DRIVE   ;Get disk drive
        MOV     CX,1            ;Ask for only 1 sector
        LEA     BX,PROMPT_SECTOR        ;Load into PROMPT_SECTOR area
        INT     25H             ;Read the sector
        POPF                    ;Pop extra flags put on by INT 25H
        MOV     AH,2            ;Prepare to print
        MOV     DL,CR           ;Send a carriage return
        INT     21H
        MOV     DL,LF           ;Send a line feed
        INT     21H
        MOV     BX,0            ;Initialize char printout counter
PRINT_LOOP:                     ;Like it says
        MOV     DL,PROMPT_SECTOR[BX]    ;Get char from read-in cluster
        INT     21H                     ;Print char
        INC     BX                      ;Go on to next char
        CMP     BX,160                  ;Done 160 Chars yet?
        JBE     PRINT_LOOP              ;No; go back for more
        MOV     AH,9                    ;Yes, print Prompt message
        LEA     DX,IS_IT_MSG            
        INT     21H
        MOV     AH,1                    ;Get a char from keyboard
        INT     21H
        MOV     CX,1            ;Assume found right sector (always optimistic)
        CMP     AL,'Y'          ;Was typed char a 'Y'?
        JE      FOUND           ;Yes, found right sector exit with CX still=0
        CMP     AL,'y'          ;No; was it a 'y'?
        JE      FOUND           ;Yes, leave with CX still = 0
        MOV     CX,0            ;Not found, exit with CX=1
FOUND:  POP     DX              ;Pop destroyed DX reg
        RET
PRINT_OUT       ENDP

PUT_FAT_ENTRY   PROC    NEAR    ;Writes new FAT entry into FAT in DATA area
        COMMENT* Pass FAT cluster number in AX and new entry in DX*
        PUSH    AX              ;The requisite Pushes
        PUSH    BX
        PUSH    CX
        PUSH    DX
        MOV     BX,AX           ;Get offset into FAT, 3*AX/2
        SHL     BX,1            ;Multiply by 2
        ADD     BX,AX           ;Add AX to get 3*AX
        SHR     BX,1            ;Divide by 2 -- BX has FAT offset value
        TEST    AX,1            ;Do we have an even entry number?
        JZ      P_EVEN_ENTRY    ;Yes, use bottom 12 bits
        MOV     CL,4            ;No, use top 12 bits
        SHL     DX,CL
P_EVEN_ENTRY:
        OR      WORD PTR DATA[BX],DX    ;Put Cluster # into FAT
        POP     DX              ;Do the Pops
        POP     CX
        POP     BX
        POP     AX
        RET                     ;Return and you're done
PUT_FAT_ENTRY   ENDP            

CODE_SEG        ENDS
        END     ENTRY           ;This sets the starting address to ENTRY










[ RETURN TO DIRECTORY ]