!       RECOVERFILES
!       V1.0A   UPPERCASES FILENAME "IF YOU WISH TO KEEP THIS"
!               CHECK VALIDITY OF FILENAME CHARACTERS TO PREVENT ILLEGAL
!               DIRECTORY.SYS ENTRIES
!       V1.0B   Ask where to start scan of disk.
!               Field read errors while scanning for header clusters.
!               Allow digits within file names.
!       V1.1B   When updating directory needed to un-writeprotect
!               Directory.SYS (RCW).  READB$ from clock fails under MT
!               but not under single user. Reported bug
!       V1.1C   fixed readb$ clock problem
!               when reading from clock didn't give eof
!               this allowed timeshare to retry to fill buffer
!               and get read buffer too short error when not a
!               multiple of six

        DIM CCSETFILEPROT$/:E,8,0,:11/,WBPROTECT$/:41/,NOPROTECT$/0/
        DIM DISK/1/, DIR/2/, CLOCK/3/, NULL$/0/

        DIM SECTOR$(4096),FILENAME$(17),TARGET$(20)
        DIM DIRENTRY$(32), DUMMY$/''/, CLUSTERNUMBER$(2), DEVICE$(20)
        DIM TEMP$(8),STARTLCN/0/
        DIM GETPARAMS$/:F,:E,:1,:5/, THRESHOLD/0/
        DIM READB$/:B,:E,:3/,RECOVERF/0/
        DIM DISKMAP/4/, MAP$(4096)
        DIM LETS$/"ABCDEFGHIJKLMNOPQRSTUVWXYZ$"/
        DIM LETSANDNUMS$/"ABCDEFGHIJKLMNOPQRSTUVWXYZ$.0123456789"/

        DEF MSB(X1) = INT(X1/256)
        DEF LSB(X2) = INT(X2-MSB(X2)*256)

        IF COL(0)<2 THEN
           PRINT 'Recover lost files v1.1c   Copyright (C) 1980 Software Dynamics'
           PRINT
           PRINT 'This programs exhaustively searches a disk for data'
           PRINT 'which appears to be a disk file (header clusters).'
           PRINT 'Invalid header clusters are automatically rejected.'
           PRINT
           INPUT 'Recover files from which disk drive? ' DEVICE$
        ELSE
           INPUT "" DEVICE$
           PRINT 'Recover lost files v1.1c   Copyright (C) 1980 Software Dynamics'
           PRINT
           PRINT 'This programs exhaustively searches a disk for data'
           PRINT 'which appears to be a disk file (header clusters).'
           PRINT 'Invalid header clusters are automatically rejected.'
           PRINT
        FI
        OPEN #DISK,DEVICE$
        INPUT 'Ignore files already in directory? ' TEMP$
        IF FIND('YES',UPPERCASE$(TEMP$))=1
        THEN CHECKDIRECTORY=TRUE ELSE CHECKDIRECTORY=FALSE
        INPUT 'Ignore files whose space is marked "owned" in DISKMAP.SYS? ' TEMP$
        IF FIND('YES',UPPERCASE$(TEMP$))=1
        THEN CHECKDISKMAP=TRUE ELSE CHECKDISKMAP=FALSE
        INPUT 'Reject object files? ' TEMP$
        IF FIND('YES',UPPERCASE$(TEMP$))=1
        THEN
                REJECTOBJECTFILES=TRUE
                INPUT 'Enter character string to check for in 1st sector of file: ' TARGET$
        ELSE
                REJECTOBJECTFILES=FALSE

                TARGET$=""
        FI
        ON ERROR GOTO 9050
        OPEN #DIR, DEVICE$ CAT 'DIRECTORY.SYS'
        ON ERROR GOTO 0
!       GET FILE PARAMETERS
        OPEN #CLOCK, 'CLOCK:'
        CALL SYSCALL(GETPARAMS$,DUMMY$,TEMP$)
        NBPS = TEMP$(1)*256 + TEMP$(2)
        NSPT = TEMP$(3)*256 + TEMP$(4)
        NTPC = TEMP$(5)*256 + TEMP$(6)
        NCYL = TEMP$(7)*256 + TEMP$(8)
        RESTORE #DISK, :11
        READ #DISK, TEMP$
        NSPC = TEMP$(1)
        NBPC = NSPC * NBPS
        NLCN = INT(NSPT*NTPC*NCYL/NSPC)
        MAXLCN = NLCN - 1
        LEN(SECTOR$)=NBPS
        IF CHECKDISKMAP
        THEN
                ON ERROR GOTO 9000
                OPEN #DISKMAP, DEVICE$ CAT 'DISKMAP.SYS'
                ON ERROR GOTO 0
                READ #DISKMAP, MAP$
                PRINT "DISKMAP.SYS dump:"
                FOR I = 0 TO INT((INT((NLCN+7)/8)+NBPS-1)/NBPS)*NBPS-1 STEP 16
                        PRINT HEX$(I)[3,3] ; '/ ';
                        FOR J = 1 TO 16
                                PRINT HEX$(MAP$(I+J))[4,2] ; ' ' ;
                        NEXT J
                        PRINT
                NEXT I
                PRINT
        FI
!
!       FIND A POTENTIAL HEADER CLUSTER
!
        INPUT "Shall I start the search with a cluster other than zero? " TEMP$
        IF UPPERCASE$(TEMP$)="YES" THEN
                INPUT "What cluster number should I start with? " STARTLCN
        FI

        ON ERROR GOTO 9100
        FOR LCN = STARTLCN TO MAXLCN
                RESTORE #DISK, LCN * NBPC
90              READ #DISK, CLUSTERNUMBER$
                CLUSTERNUMBER = CLUSTERNUMBER$(1)*256 + CLUSTERNUMBER$(2)
                IF CLUSTERNUMBER = LCN THEN GOSUB 100
                CYCLE LCN

9100            IF ELN<>90 OR (ERR<>1042 AND ERR<>1045) THEN ERROR
                PRINT "I got an error";ERR;" while trying to read cluster ";
                PRINT HEX$(LCN);"."
                PRINT "I will skip that cluster and look at the next."
        NEXT LCN
        PRINT 'File recovery completed.'
        IF RECOVERF > 0 Then
          PRINT 'SDOSDISKVALIDATE should now be performed on ';DEVICE$
        FI
        EXIT
!
!       FOUND SOMETHING THAT LOOKS LIKE (AT FIRST GLANCE) A HEADER CLUSTER
100     PRINT HEX$(LCN); " appears to be a header cluster";
!       SEE IF IT IS ALLOCATED IN THE MAP, IGNORE IT IF SO
        IF CHECKDISKMAP...
&       THEN
                MAPBYTEINDEX = INT(LCN/8)
                IF MAP$(MAPBYTEINDEX+1) & (1**(LCN-MAPBYTEINDEX*8)) <> 0
                THEN
                        PRINT "; rejected because marked as owned"
                        RETURN
                FI
        FI
        PRINT
!       SEE IF HEADER CLUSTER IS OWNED BY A FILE IN THE DIRECTORY
!       ALSO, LOCATE A FREE DIRECTORY SLOT
        CURRENTDIRPOS = -32
        FREEDIRECTORYSLOT = -32
        RESTORE #DIR, 0
200     CURRENTDIRPOS = CURRENTDIRPOS + 32
        READ #DIR, DIRENTRY$
        IF EOF(DIR) THEN 300 \ ! NOBODY OWNS THIS HEADER
        IF DIRENTRY$(19) = 0
        THEN
                ! FOUND A FREE DIRECTORY SLOT
                FREEDIRECTORYSLOT = CURRENTDIRPOS
                IF CHECKDIRECTORY THEN 200 ELSE 300
        ELSE
                IF CHECKDIRECTORY AND ( DIRENTRY$(17)*256 + DIRENTRY$(18) = LCN )
                THEN
                        PRINT "Rejected because owned by ";DIRENTRY$[1,16]
                        RETURN
                ELSE 200
        FI
!
!       HERE, WE WILL DO SOME THOROUGH CHECKING OF THE HEADER CLUSTER
!       READ IN EACH SECTOR, CHECKING EACH CLUSTER NUMBER
!       FOR VALIDITY. IF ANY ERRORS ARE FOUND, ALLOW
!       OPTION TO IGNORE THIS HEADER
!       WHILE CHECKING, WE WILL ACCUMULATE HCSIC, NCLUSTERS, AND FILESIZE
!
300     IF FREEDIRECTORYSLOT = -32 THEN FREEDIRECTORYSLOT = CURRENTDIRPOS
        NCLUSTERS = 0
        RESTORE #DISK, LCN * NBPC
        FOR HCSIC = 0 TO NSPC - 1
301     READ #DISK, SECTOR$(1,NBPS)
!
!       THIS IS A PRE-SCAN TO SEE IF ANY BAD CLUSTER NUMBERS IN THIS HEADER SECTOR
!
        FOR L = 1 TO NBPS STEP 2
                CLUSTERNUMBER  = SECTOR$(L)*256 + SECTOR$(L+1)
                IF CLUSTERNUMBER <> :FFFF AND CLUSTERNUMBER > MAXLCN
                THEN
                        IF HCSIC = 0
                        THEN
                                PRINT "Rejected because 1st sector of header contains invalid LCN = ";HEX$(CLUSTERNUMBER)
                                RETURN
                        ELSE 320
                FI
        NEXT L
!
!       IF WE GET HERE, THIS HEADER SECTOR CONTAINS ONLY GOOD LCNS
!
        IF HCSIC = 0 THEN PRINT "File owns clusters: ";
        FOR L = 1 TO NBPS STEP 2
                CLUSTERNUMBER = SECTOR$(L)*256 + SECTOR$(L+1)
                IF CLUSTERNUMBER = :FFFF THEN 310 \ ! THIS IS A GOOD CLUSTER NUMBER
305             PRINT HEX$(CLUSTERNUMBER);"  ";
                IF COL(0)>60 THEN PRINT
                IF CHECKDISKMAP
                THEN
                        MAPBYTEINDEX = INT(CLUSTERNUMBER/8)
                        IF MAP$(MAPBYTEINDEX+1) & (1**(CLUSTERNUMBER - MAPBYTEINDEX*8)) <> 0
                        THEN
                                IF COL(0)<>0 THEN PRINT
                                PRINT "Rejected because LCN is already owned"
                                RETURN
                        FI
                FI
308     NCLUSTERS = NCLUSTERS + 1
        FILESIZE = (HCSIC*NBPS/2 + (L+1)/2 - 1) * NBPC
310     NEXT L
        NEXT HCSIC
!
320     REM NOW DISPLAY THE CONTENTS OF THE FIRST SECTOR OF THE FILE TO THE USER
!       ASK WHAT FILENAME HE WISHES TO KEEP IT UNDER
        RESTORE #DISK,LCN*NBPC
        PRINT
        READ #DISK,SECTOR$[1,NBPS]
        LET CLUSTERNUMBER=SECTOR$[3]*256+SECTOR$[4]
        IF CLUSTERNUMBER=:FFFF
        THEN PRINT "Header cluster 1st sector map (1st data cluster not allocated):"
        ELSE
                RESTORE #DISK,CLUSTERNUMBER*NBPC
                READ #DISK,SECTOR$[1,NBPS]
                IF REJECTOBJECTFILES
                THEN
                        IF SECTOR$[1]=1
                        THEN
                                PRINT "Rejected because it appears to be object file"
                                RETURN
                        ELSE
                                IF FIND(SECTOR$,TARGET$)=0
                                THEN
                                        PRINT "Rejected because ";TARGET$;" not present"
                                        RETURN
                                FI
                        FI
                FI
                PRINT "Map of 1st sector of 1st data cluster:"
        FI
        FOR K=0 TO NBPS-1 STEP 16
                PRINT HEX$(K)[3,3];"/   ";
                FOR K1=K+1 TO K+16
                        PRINT HEX$(SECTOR$[K1])[4,2];" ";
                NEXT K1
                PRINT TAB(50);
                FOR K1=K+1 TO K+16
                        LET SECTOR$[K1]=SECTOR$[K1]&:7F
                        IF SECTOR$[K1]>=ASC(" ") AND SECTOR$[K1]<>:7F
                        THEN PRINT SECTOR$[K1,1]; ELSE PRINT ".";
                NEXT K1
                PRINT
        NEXT K
!       INVENT A FILE NAME AND
!       INSTALL THIS HEADER IN THE DIRECTORY
!

        PRINT "File size is not greater than";FILESIZE;"bytes"
BADNAME:        INPUT "Enter name of file if you wish to keep this: " FILENAME$
        IF FILENAME$="" THEN RETURN
        IF LEN(FILENAME$)>16 THEN BADNAME
        LET FILENAME$=UPPERCASE$(FILENAME$)
        IF FIND(LETS$,FILENAME$(1,1))=0 THEN BADNAME
        FOR K=2 TO LEN(FILENAME$)
                IF FIND(LETSANDNUMS$,FILENAME$(K,1))=0 THEN BADNAME
        NEXT K
!
!       PUT OUT THE DIRECTORY NAME
!
        RESTORE #DIR, FREEDIRECTORYSLOT

990        SYSCALL #DIR,CCSETFILEPROT$,NOPROTECT$
991        WRITE #DIR,FILENAME$
        FOR K= LEN(FILENAME$)+1 TO 16
992                WRITE #DIR, ' '
        NEXT K
!
!       PUT OUT THE HEADER CLUSTER NUMBER
!
        LEN(TEMP$) = 8
        TEMP$(1) = MSB(LCN) \ TEMP$(2) = LSB(LCN)
993        WRITE #DIR, TEMP$(1,2)
!
!       PUT OUT THE HCSIC
!
        TEMP$(1) = HCSIC
994     WRITE #DIR, TEMP$(1,1)
!
!       PUT OUT THE NCLUSTERS
!
        TEMP$(1) = MSB(NCLUSTERS) \ TEMP$(2) = LSB(NCLUSTERS)
995        WRITE #DIR, TEMP$(1,2)
!
!       PUT OUT THE FILESIZE
!
        TEMP$(1) = MSB(MSB(MSB(FILESIZE)))
        TEMP$(2) = LSB(MSB(MSB(FILESIZE)))
        TEMP$(3) = LSB(MSB(FILESIZE))
        TEMP$(4) = LSB(FILESIZE)
996        WRITE #DIR, TEMP$(1,4)
!
!       PUT OUT THE PROTECTION
!
997        WRITE #DIR, NULL$
!
!       PUT OUT THE CREATION DATE
!
998        CALL SYSCALL(READB$,DUMMY$,TEMP$(1,6))
987        WRITE #DIR, TEMP$(4,3)
!
!       FILL OUT THE RECORD WITH ZEROES
!
        FOR K = 1 TO 3
999                WRITE #DIR, NULL$
        NEXT K
989        SYSCALL #DIR,CCSETFILEPROT$,WBPROTECT$

        LET RECOVERF = RECOVERF + 1
        RETURN

9000    PRINT "No DISKMAP.SYS file present on device, bye!"
        EXIT

9050    PRINT "No DIRECTORY.SYS file present on device, bye!"
        EXIT

END
