10 REM > AFSFiler/src v1.31b J.G.Harston
   20 REM 19-Nov-2010 v0.14 Initial version based on SJFiler
   30 REM 05-Aug-2012 v0.15 L3FS disks done, sequential and interleaved
   40 REM 28-May-2017 v0.16 Checks JesMap for file allocation blocks, updated os%, FNfn_todos
   50 REM 30-May-2018 v0.17 (C)onfirm option, OPT command, checks for outside image file
   60 REM 08-Jun-2018 v0.18 Reads file lengths, buffers allocation map
   70 REM 05-Apr-2018 v1.30 Bring all Filers to same version number
   80 REM 19-Mar-2020 v1.30a Probe for FSOp before writing long info
   90 REM 20-Mar-2020 v1.30b Multiple JesMaps, COPY $ works inside a subdirectory
  100 REM 22-Mar-2020 v1.30c Forces ADFS for FNscsi disk access
  110 REM 24-Mar-2020 v1.30d Follow multiple JesMaps to find file length
  120 REM 25-Mar-2020 v1.30e SIN chaining JesMaps is 24-bit
  130 REM 28-Mar-2020 v1.30f Testing and tweeking, COPY sets top dir metadata
  140 REM 30-Mar-2020 v1.30g Tweeks and optimisations
  150 REM 31-Mar-2020 v1.31  Updated version number
  160 REM 27-Apr-2020 v1.31a Uses deeper REPEAT/UNTIL instead of FOR/NEXT in directory copy
  170 REM 28-Apr-2020 v1.31b Fix for FileLen2 where sector is full but no link
  180 :
  190 os%=FNfx(0,1)AND&FF:VDU10,8:A%=POS:VDU13:IFA%<50:MODE&87:IFHIMEM>&7C00:MODE&83:IFHIMEM>&4000:MODE&80
  200 IFHIMEM<&FFFF:HIMEM=FNfx(132,0)
  210 PROCinit:PRINT"AFSFiler v"ver$" by J.G.Harston"':ON ERROR IF FNerr:END
  220 REPEAT:fs%=FNfs:X%=ctrl%:Y%=X%DIV256:IF POS:PRINT
  230   VDU 8:wdt%=POS+1:PRINT
  240   IFdrv$="":PRINTCHR$(D%+48)"> "ELSE PRINT"["drv$"] ";
  250   csd%=home%:INPUTLINE""A$:PROCdo(FNs(A$))
  260 UNTIL0
  270 :
  280 DEFPROCinit:ver$="1.31b"
  290 DIM ctrl% 127,name% 255,data% 255,thisdir% &19FF,alloc% 511:X%=ctrl%:Y%=X%DIV256:diskrec%=0:IF os%=6:DIM diskrec% 255
  300 D%=0:drv$="":path$="":title$="":curr%=-1:csd%=0:home%=0:inmem%=0:valid%=&30534641:map$="JesMap"
  310 den%=1:opt%=0:fullinf%=TRUE:d$=".":s$="/":IFos%AND-24:d$="/":s$=".":IFos%AND-32:d$="\"
  320 cmd$=":MOUNT:DIR:CAT:EX:INFO:TYPE:DUMP:COPY:BLOCK:INF:HELP:QUIT:"
  330 hlp$=":<drive>|<image>:<dir>|&<blocknum>:::(<file>):<file> [CTRLS]:<file> [7BIT]:<source> <dest> (<opts>):<blocknum>:FULL|SHORT:::<opts>:"
  340 ENDPROC
  350 :
  360 DEFFNerr:OSCLI"FX229":IF POS:PRINT
  370 REPORT:IFFNfs<>fs%:OSCLI"FX143,18,"+STR$fs%
  380 PROCCloseAll:A%=ERR<>17 AND ERR<>28:PRINTLEFT$(" at line "+STR$ERL,ERR<128 AND A%):=INKEY-1 AND A%
  390 DEFPROCCloseAll:REM in%=in%:IFin%:A%=in%:in%=0:CLOSE#A%
  400 out%=out%:IFout%:A%=out%:out%=0:CLOSE#A%
  410 dsk%=dsk%:IFdsk%:A%=dsk%:dsk%=0:CLOSE#A%
  420 ENDPROC
  430 :
  440 DEFPROCdo(A$):IFA$="?":A$="HELP"
  450 IF LEFT$(A$,1)=";" OR A$="":ENDPROC
  460 IF LEFT$(A$,1)="*":OSCLIMID$(A$,2):ENDPROC
  470 IF LEFT$(A$,1)=".":A$="CAT "+MID$(A$,2)
  480 A%=INSTR(A$+" "," "):B$=FNuc(LEFT$(A$,A%-1)):A$=FNs(MID$(A$,A%+1))
  490 IF LENB$=1 AND INSTR("01234567",B$):drv$="":D%=VALB$AND(((os%<6)OR3)AND7):home%=0:ENDPROC
  500 A%=INSTR(cmd$,":"+B$+":"):IF A%=0:PRINT"Bad command":ENDPROC
  510 A%=EVAL("FN_"+B$+"(A$)"):ENDPROC
  520 :
  530 DEFFNsyn(S$):IF A$="":PRINT "Syntax: "B$" "S$:=TRUE ELSE =FALSE
  540 :
  550 REM Commands
  560 REM ========
  570 ; FN_INFO, FN_COPY, FN_QUIT, FN_INF, FN_MOUNT, FN_OPT :REM Prevent crunching
  580 ; FN_HELP, FN_TYPE, FN_DUMP, FN_CAT, FN_BLOCK, FN_DIR, FN_EX
  590 :
  600 DEFFN_QUIT(A$):PRINT"Quit"
  610 ON ERROR END
  620 IF os%>5:*QUIT
  630 END
  640 :
  650 DEFFN_HELP(A$):p%=2:q%=2:REPEAT
  660   A%=INSTR(cmd$,":",p%):PRINT SPC2MID$(cmd$,p%,A%-p%);:p%=A%+1
  670   A%=INSTR(hlp$,":",q%):PRINT TAB(8)MID$(hlp$,q%,A%-q%):q%=A%+1
  680 UNTIL p%>LENcmd$:=0
  690 :
  700 DEFFN_OPT(A$):IF A$="":PRINT"OPT=";opt%'"1:"'"2:"'"4:"'"8:":=0
  710 opt%=VAL(A$):=0
  720 :
  730 DEFFN_INF(A$):IFA$="":PRINT"Output "LEFT$("FULL",fullinf%);LEFT$("SHORT",NOTfullinf%)" .inf info":=0
  740 fullinf%=FNuc(A$)="S":=0
  750 :
  760 DEFFN_MOUNT(A$):home%=0:IF LENA$<2:PROCdo(A$):=0 ELSE drv$=FNs(A$):D%=-1:=0
  770 DEFFN_CAT(A$):PROCLstDir(0):=0
  780 DEFFN_EX(A$):PROCLstDir(1):=0
  790 :
  800 DEFFN_DIR(A$):curr%=-1:IF FNsyn("<dir>|&<blocknum>"):=TRUE
  810 IF LEFT$(A$,1)="&":A%=FNMount:home%=EVAL(FNuc(A$)):path$="&"+FNh0(home%,6):=0
  820 IF INSTR(A$,".")+INSTR(A$,"^"):PRINT"DIR <Unsupported>":=TRUE
  830 IF A$="$":A%=FNMount:home%=root%:path$="$":=0
  840 IF FNlook:=TRUE
  850 IF(fptr%?&14 AND &20)=0:PRINT"'"src$"' not a directory":=TRUE
  860 A%=fptr%?&C:fptr%?&C=13:path$=path$+"."+FNs($(fptr%+2)):fptr%?&C=A%
  870 home%=fptr%!&17 AND &FFFFFF
  880 =0
  890 :
  900 DEFFN_BLOCK(A$):IF FNsyn("<blocknum>"):=TRUE
  910 blk%=EVAL("&"+FNuc(A$))AND&FFFFFF:PROCRdBlocks(data%,blk%,D%,1):S%=blk%*256:O%=data%:ln%=256:PROCdump
  920 =0
  930 :
  940 DEFFN_DUMP(A$):IF FNsyn("<fsp>"):=TRUE
  950 IF FNlook:=TRUE
  960 PROCFileInfo(fptr%,1):S%=0:REPEAT:PROCReadData:PROCdump:UNTILeof%
  970 =0
  980 :
  990 DEFPROCdump:cols%=16:IF wdt%<80:cols%=8
 1000 FOR P%=0 TO ln%-1 STEP cols%:B$=""
 1010   PRINT FNh0(P%+S%,8)" ";:FOR Q%=P% TO P%+cols%-1
 1020     IF Q%<ln%:PRINT FNh0(?O%,2)" "ELSE ?O%=32:PRINT SPC3;
 1030     A$=CHR$(?O%AND&7F):IF A$>=" " AND A$<="~" B$=B$+A$ ELSE B$=B$+"."
 1040 O%=O%+1:NEXT:PRINT B$:NEXT:S%=S%+ln%:ENDPROC
 1050 :
 1060 DEFFN_TYPE(A$):IF FNsyn("<fsp>"):=TRUE
 1070 msk%=INSTR(A$," [")<>0:msk%=(msk%AND&80)OR&7F:A$=LEFT$(A$,INSTR(A$+" "," ")-1)
 1080 IF FNlook:=TRUE
 1090 PROCFileInfo(fptr%,1):P%=0:S%=0:last%=0:REPEAT:PROCReadData
 1100   FOR P%=0 TO ln%-1:Q%=?O%ANDmsk%:IFmsk%=255:VDUQ% ELSE IFQ%=10 OR Q%=13 OR Q%>31 VDUQ%
 1110     IF(Q%=10 OR Q%=13) AND Q%<>last%:VDU23-Q%
 1120     IFQ%=9:PRINTSPC(8-(POS MOD 8));
 1130   last%=?O%:O%=O%+1:NEXT:S%=S%+ln%
 1140 UNTIL eof%:IFPOS:PRINT
 1150 =0
 1160 :
 1170 DEFFN_INFO(A$)
 1180 IF A$<>"":IF FNlook:=TRUE
 1190 IF A$<>"":x%=0:PROCListFile(1):=0
 1200 IF FNMount:=TRUE
 1210 PRINT"Disk: L";den%-(den%<>3);"FS::"title$;SPC(18-LENtitle$);MID$("SEQSEQINT",den%*3-2,3)" DEN=";den%
 1220 PRINT"Root: &"FNh0(root%,6)"  CrDate: "FNdate(idate%)
 1230 PRINT"MapA: &"FNh0(mapa%,6);MID$("* ",(mapa%=map%)+2,1)" ";
 1240 PRINT"MapB: &"FNh0(mapb%,6);MID$("* ",(mapb%=map%)+2,1)" ";
 1250 PRINT"MapSz:  ";mapsz%'"Size: &"FNh0(dsize%,6)"  ";dsize%DIV4;"K  ";(dsize%AND-512)/4096;"M"
 1260 =0
 1270 :
 1280 DEFFN_COPY(A$):IF FNsyn("<afs0 source> (inf:)<host dest> (<C>onfirm)"):=TRUE
 1290 REM copy * path
 1300 REM copy * inf:path
 1310 A%=INSTR(A$+" "," "):src$=LEFT$(A$,A%-1):dst$=FNs(MID$(A$,A%+1))
 1320 cnf%=FNuc(RIGHT$(dst$,2))=" C":IF cnf%:dst$=FNs(LEFT$(dst$,LENdst$-2))
 1330 inf%=FNuc(LEFT$(dst$,4))="INF:":IFinf%:dst$=MID$(dst$,5)
 1340 IFdst$="":PRINT"<dest> missing":=TRUE
 1350 IFos%<6:IFINSTR(dst$,"::")ORLEFT$(dst$,1)="-":PRINT"FS prefix unsupported":=TRUE
 1360 A$=src$:IF A$<>"$":IF FNlook:=TRUE
 1370 IFsrc$="$":A%=FNMount:fptr%=data%:fptr%?&14=&20:fptr%!&17=root%:curr%=-1:IF A%<0:=TRUE
 1380 IF((fptr%?&14)AND&20)=0:leaf$=src$:PROCCopyOneFile(src$,dst$):=0:REM copy single file
 1390 :
 1400 REM src=directory, copy all recursively
 1410 A%=FNfile(dst$,8):dst$=dst$+d$:REM Create target directory
 1420 oldcsd%=csd%:oldfptr%=fptr%:curr%=-1:csd%=fptr%!&17 AND &FFFFFF:PROCCopyDirectory(dst$)
 1430 REM Set info on top directory
 1440 A%=LENdst$:REPEATA%=A%-1:UNTIL MID$(dst$,A%,1)=d$ OR A%<1 OR src$="$":dst$=LEFT$(dst$,A%)
 1450 IFsrc$="$":load%=0:exec%=0:length%=&200:attr%=8:mdate%=idate%:PROCSetInfo(dst$):=0
 1460 PROCSetDir:=0
 1470 :
 1480 :
 1490 REM File copying code
 1500 REM =================
 1510 :
 1520 DEFPROCCopyDirectory(dst$):LOCAL entry%,leaf$
 1530 PROCRdDir:fptr%=thisdir%:IF thisdir%?15=0:ENDPROC
 1540 REM FOR entry%=1 TO thisdir%?15
 1550 REM fptr%=thisdir%+(!fptr% AND &FFFF):PROCCopyObject
 1560 REM NEXT entry%
 1570 entry%=1:REPEAT:fptr%=thisdir%+(!fptr% AND &FFFF):PROCCopyObject
 1580 entry%=entry%+1:UNTIL entry%>thisdir%?15
 1590 ENDPROC
 1600 :
 1610 DEFPROCCopyObject:LOCAL oldcsd%,oldfptr%
 1620 PROCFileInfo(fptr%,1):IF fname$="":ENDPROC :REM Broken entry
 1630 IFcnf%:PRINT"Copy "fname$;:cnf%=FNyna(cnf%):IFcnf%>0:PRINT:ENDPROC ELSE VDU13
 1640 leaf$=fname$:fname$=FNfn_todos(fname$)
 1650 IF((fptr%?&14)AND&20)=0:PROCCopyOneFile(fname$,dst$+fname$):ENDPROC
 1660 :
 1670 REM Descend into directory
 1680 PRINT"Copying "fname$;SPC(10-LENfname$)" to "dst$+fname$
 1690 oldcsd%=csd%:oldfptr%=fptr%           :REM Save current directory
 1700 curr%=-1:csd%=fptr%!&17 AND &FFFFFF   :REM Select new directory
 1710 A%=FNfile(dst$+fname$,8)              :REM Create matching destination directory
 1720 PROCCopyDirectory(dst$+fname$+d$)     :REM Copy directory at cblk%
 1730 PROCSetDir:ENDPROC
 1740 :
 1750 DEFPROCSetDir
 1760 curr%=-1:csd%=oldcsd%:PROCRdDir:fptr%=oldfptr% :REM Restore previous
 1770 PROCFileInfo(fptr%,1):PROCSetInfo(dst$+fname$) :REM Set directory's metadata
 1780 ENDPROC
 1790 :
 1800 DEFPROCCopyOneFile(src$,dst$)
 1810 PRINT"Copying "src$;SPC(10-LENsrc$)" to "dst$;SPC4;
 1820 PROCFileInfo(fptr%,1):IFFNfile(dst$,5):X%!14=&33:A%=FNfile(dst$,4)
 1830 X%!2=load%:X%!6=exec%:X%!10=0:X%!14=&4000:A%=FNfile(dst$,7)
 1840 IFlength%:PROCCopyData:REM Copy if length<>0
 1850 PROCSetInfo(dst$):PRINTSTRING$(3,CHR$127)
 1860 ENDPROC
 1870 :
 1880 DEFPROCCopyData
 1890 out%=FNf_openout(dst$):S%=0:REPEAT:PROCReadData:IF POS=0:PRINTSPC4;
 1900   IF(S%AND1023)=0:PRINT STRING$(3,CHR$8)FNd0(100*S%DIVlength%,2)"%";
 1910   IFln%:PROCgbpb(2,out%,data%,ln%,0):S%=S%+ln%
 1920 UNTIL eof%:CLOSE#out%:out%=0:length%=S%
 1930 ENDPROC
 1940 :
 1950 DEFPROCSetInfo(dst$):X%!2=load%:X%!6=exec%:X%!10=length%:X%!14=attr%:A%=FNfile(dst$,1)
 1960 A$=leaf$+STRING$(11-LENleaf$," ")+FNh0(load%,8)+" "+FNh0(exec%,8)+" "+FNh0(length%,8)
 1970 IFfullinf%:A$=A$+" "+FNh0(attr%AND255,2)+" "+FNh0(mdate%,4)
 1980 A$=A$+CHR$13+CHR$10
 1990 IFinf%:out%=OPENOUT(dst$+s$+"inf"):FOR p%=1 TO LEN A$:BPUT#out%,ASCMID$(A$,p%,1):NEXT:CLOSE#out%:out%=0
 2000 IFfs%<>5:ENDPROC
 2010 A%=FNNetFS_Op(19,CHR$4+CHR$access%+dst$) :REM Write access
 2020 X%!8=mdate%:A%=FNNetFS_OpN(19,5,10,dst$) :REM Write create date from mdate
 2030 IF FNNetFS_Op(18,CHR$64+dst$):ENDPROC    :REM create&mod date&time don't exist
 2040 X%!8=mdate%:X%!10=0:X%!13=mdate%:X%!15=0
 2050 A%=FNNetFS_OpN(19,64,18,dst$)            :REM Write create&mod date&time
 2060 ENDPROC
 2070 :
 2080 :
 2090 REM Object display routines
 2100 REM =======================
 2110 :
 2120 DEFPROCLstDir(cflg%):x%=0:IF FNMount:PRINT"Not an AFS0 disk":ENDPROC
 2130 REM cflg%=0 - CAT, cflg%=1 - EX
 2140 PRINT "Path: L";den%-(den%<>3);"FS::"title$"."path$'
 2150 PROCRdDir:fptr%=thisdir%:IF thisdir%?15=0:ENDPROC
 2160 FOR entry%=1 TO thisdir%?15
 2170   fptr%=thisdir%+(!fptr% AND &FFFF):PROCListFile(cflg%)
 2180 NEXT entry%:IF POS:PRINT:REM IF(x%AND3):IF(cflg%AND1)=0:PRINT
 2190 ENDPROC
 2200 :
 2210 DEFPROCListFile(cflg%)
 2220 PROCFileInfo(fptr%,cflg%):PRINTfname$;SPC(11-LENfname$);
 2230 IF(cflg%AND1):PRINTFNh0(load%,8)" "FNh0(exec%,8)" "FNh0(length%,6)" ";
 2240 PRINTFNattr(access%);
 2250 IF(cflg%AND1):PRINTFNdate(mdate%)" "FNh0(sin%,6):ENDPROC
 2260 x%=x%+1:IF x%<(wdt%+1) DIV 20:PRINT " "ELSE x%=0:PRINT
 2270 ENDPROC
 2280 :
 2290 DEFFNattr(A%):A$=""
 2300 IF(A%AND1):A$="r"+A$
 2310 IF(A%AND2):A$="w"+A$
 2320 A$="/"+A$
 2330 IF(A%AND4):A$="R"+A$
 2340 IF(A%AND8):A$="W"+A$
 2350 IF(A%AND16):A$="L"+A$
 2360 IF(A%AND32):A$="D"+A$
 2370 IF(A%AND64):A$="P"+A$
 2380 IF(A%AND128):A$="M"+A$
 2390 =A$+STRING$(8-LENA$," ")
 2400 :
 2410 DEFFNdate(A%)=FNd0(A%AND31,2)+"/"+FNd0((A%DIV256)AND15,2)+"/"+FNd0(1981+(A%DIV4096)+((A%AND&E0)/2),4)
 2420 :
 2430 DEFFNtime(A%)=FNd0(A%AND255,2)+":"+FNd0(A%DIV256,2)
 2440 :
 2450 :
 2460 REM Catalog manipulation routines
 2470 REM =============================
 2480 :
 2490 DEFFNlook:src$=A$:fptr%=FNfind(src$):IFfptr%:=FALSE
 2500 PRINT"'"src$"' not found":=TRUE
 2510 :
 2520 DEFFNfind(A$):match$=FNuc(LEFT$(A$,10)):match%=0:IFFNMount:=0
 2530 PROCRdDir:fptr%=thisdir%:entry%=1:IFthisdir%?15=0:=0
 2540 REPEAT:fptr%=thisdir%+(!fptr% AND &FFFF)
 2550   A%=fptr%?&C:fptr%?&C=13:fname$=FNs($(fptr%+2)):fptr%?&C=A%
 2560   match%=(FNuc(fname$)=match$):entry%=entry%+1
 2570 UNTIL entry%>thisdir%?15 OR match%
 2580 IF match%:=fptr% ELSE =0
 2590 :
 2600 DEFPROCFileInfo(f%,i%)
 2610 A%=f%?&C:f%?&C=13:fname$=FNs($(f%+2)):f%?&C=A%
 2620 FOR A%=1 TO LEN fname$:IF MID$(fname$,A%,1)<"!" OR MID$(fname$,A%,1)>"~":fname$=""
 2630 NEXT A%
 2640 load%=f%!&0C:exec%=f%!&10:access%=f%?&14:mdate%=f%!&15 AND &FFFF:sin%=f%!&17 AND &FFFFFF
 2650 length%=0:attr%=(access%AND3)*16+(access%AND&C)/4+(access%AND16)/2+mdate%*256
 2660 IF (i%AND1)=0 OR sin%=0:ENDPROC
 2670 IF den%=1:PROCFileLen1:ENDPROC ELSE PROCFileLen2:ENDPROC
 2680 :
 2690 DEFPROCFileLen1
 2700 offset%=sin%:REPEAT
 2710   offset%=(offset%AND&FFF)*2+5             :REM Offset to allocation map entry
 2720   PROCRdAlloc(map%+offset%DIV256,2)        :REM Read sectors of allocation map allowing for overlap
 2730   offset%=alloc%!(offset%AND255) AND &FFFF :REM Get next allocation map entry
 2740 length%=length%+256:UNTIL offset% AND &4000
 2750 A%=offset% AND 255:IF A%:length%=length%+A%-256:REM Low byte of length
 2760 ENDPROC
 2770 :
 2780 DEFPROCFileLen2
 2790 PROCRdAlloc(sin%,1):IFFNChkMap(alloc%):PRINT"Bad map":ENDPROC
 2800 B%=alloc%?8:IF B%:length%=B%-256         :REM Low byte of length
 2810 REPEAT
 2820   A%=12:REPEAT:B%=alloc%!A% AND &FFFF00:length%=length%+B%:A%=A%+5:UNTIL B%=0 OR A%>&F9
 2830   IF A%>&F9:B%=alloc%!250 AND &FFFFFF:IF B%:PROCRdAlloc(B%,1)
 2840 UNTIL B%=0
 2850 ENDPROC
 2860 :
 2870 DEFPROCRdDir:IFcurr%=csd%:ENDPROC
 2880 IFden%=1:PROCRdDir1 ELSE PROCRdDir2
 2890 curr%=csd%:ENDPROC
 2900 :
 2910 DEFPROCRdDir1
 2920 addr%=thisdir%:fptr%=csd%:REPEAT
 2930   PROCRdBlocks(addr%,fptr% AND &FFF,D%,1)      :REM Read a directory sector
 2940   offset%=(fptr%AND&FFF)*2+5                   :REM Offset to allocation map entry
 2950   PROCRdAlloc(map%+offset%DIV256,2)            :REM Read sectors of allocation map
 2960   fptr%=alloc%!(offset%AND255) AND &FFFF       :REM Get next allocation map entry
 2970 addr%=addr%+256:UNTIL (fptr% AND &4000)
 2980 ENDPROC
 2990 :
 3000 DEFPROCRdDir2
 3010 PROCRdAlloc(csd%,1):IF FNChkMap(alloc%):PRINT"Bad map":thisdir%?15=0:ENDPROC
 3020 addr%=thisdir%:fptr%=alloc%+10:REPEAT
 3030   PROCRdBlocks(addr%,!fptr% AND &FFFFFF,D%,fptr%!3 AND &FFFF)
 3040   addr%=addr%+256*(fptr%!3 AND &FFFF):fptr%=fptr%+5
 3050 UNTIL (!fptr% AND &FFFFFF)=0
 3060 ENDPROC
 3070 :
 3080 :
 3090 REM Disk access routines
 3100 REM ====================
 3110 REM Internal disk errors are:
 3120 REM  -1 Unsupported on this hardware
 3130 REM  -2 Disk/image not present
 3140 REM  -3 Past end of image
 3150 REM  -4
 3160 REM  -5 Not a recognised disk
 3170 REM  -6 Can only do subset of disk types
 3180 :
 3190 DEFFNMount:IFcsd%:=0
 3200 curr%=-1:home%=0:inmem%=0:bps%=256:den%=4:REPEAT:den%=den%-1:IFden%=1:spt%=10 ELSE spt%=16
 3210   PROCfdcInit(diskrec%,8,spt%,2,den%+(den%=3),80,0):!data%=0:PROCRdBlocks(data%,0,D%,1)
 3220   IF !data%=valid%:den%=1
 3230   IF den%>1:!data%=0:PROCRdBlocks(data%,data%!&F6 AND &FFFFFF,D%,1):REM Look for DiskInfo to work out SEQ/INT
 3240 UNTIL den%=1 OR !data%=valid%
 3250 REM 1=L2FS SEQ (image or physical disk)
 3260 REM 2=L3FS SEQ (SEQ image or physical disk)
 3270 REM 3=L3FS INT (INT image)
 3280 IF!data%=0:IFdrv$<>"":err%=-2:REM Disk image not found
 3290 IFerr%=-2:PRINT "Disk error: image file not found":=-2
 3300 IF!data%<>valid%:=-5         :REM Not an AFS0 disk
 3310 path$="$":A%=data%?20:data%?20=13:title$=FNs($(data%+4)):data%?20=A%
 3320 :
 3330 REM Level3FS:
 3340 freec%=data%!36 AND &FFFF:idate%=data%!34 AND &FFFF:root%=data%!31 AND &FFFFFF:dnxt%=data%?29
 3350 mapsz%=data%?28:nsec%=data%!26 AND &FFFF:ndsks%=data%?25:dsize%=data%!22 AND &FFFFFF
 3360 ntrk%=data%!20 AND &FFFF:mapb%=0:mapa%=0:map%=0:home%=root%:csd%=home%
 3370 IF den%>1:=0
 3380 :
 3390 REM Level2FS:
 3400 mapsz%=data%?33          :mapb%=data%!30 AND &FFFFFF:mapa% =data%!27 AND &FFFFFF
 3410 idate%=data%!25 AND &FFFF:root%=data%!22 AND &FFFFFF:dsize%=(data%!20 AND &FFFF)*2
 3420 map%=mapa%:home%=root%:csd%=home%
 3430 PROCRdBlocks(data%,mapa%,D%,1):f%=?data%
 3440 PROCRdBlocks(data%,mapb%,D%,1):IF((f%-?data%)AND255)=255:map%=mapb%
 3450 =0
 3460 :
 3470 DEFPROCReadData
 3480 IFsin%=0:ln%=0:eof%=TRUE:ENDPROC           :REM length%=0 or past end of file
 3490 IFden%=1:PROCReadData1 ELSE PROCReadData2
 3500 O%=data%:ENDPROC
 3510 :
 3520 DEFPROCReadData1
 3530 PROCRdBlocks(data%,sin% AND &FFF,D%,1)     :REM Read a file sector
 3540 offset%=(sin%AND&FFF)*2+5                  :REM Offset to allocation map entry
 3550 PROCRdAlloc(map%+offset%DIV256,2)          :REM Read sectors of allocation map
 3560 sin%=alloc%!(offset%AND255) AND &FFFF      :REM Get next allocation map entry
 3570 eof%=sin%AND&4000:ln%=256
 3580 IFeof%:ln%=sin%AND255:IFln%=0:ln%=256      :REM Last sector
 3590 ENDPROC
 3600 :
 3610 DEFPROCReadData2
 3620 eof%=0:IFS%=0:aptr%=0
 3630 IF aptr%=0:PROCRdAlloc(sin%,1):IF FNChkMap(alloc%):PRINT"Bad map":eof%=TRUE:ln%=0:ENDPROC
 3640 ln%=alloc%?8:REPEAT:IF aptr%<10:aptr%=10
 3650   sec%=alloc%!aptr% AND &FFFFFF                         :REM Get sector start
 3660   num%=alloc%!(aptr%+3) AND &FFFF                       :REM Get number of sectors
 3670   IF aptr%=250:PROCRdAlloc(sec%,1):alloc%?8=ln%:aptr%=0 :REM Chain to next allocation map
 3680 UNTIL aptr%:PROCRdBlocks(data%,sec%,D%,1)             :REM Read a file sector
 3690 sec%=sec%+1:num%=num%-1:alloc%!aptr%=sec%
 3700 alloc%?(aptr%+3)=num%:alloc%?(aptr%+4)=num%DIV256
 3710 ln%=256:IFnum%=0:aptr%=aptr%+5:IFalloc%!aptr%=0:eof%=TRUE:ln%=alloc%?8:IFln%=0:ln%=256
 3720 ENDPROC
 3730 :
 3740 DEFPROCRdAlloc(sin%,num%):IF sin%=inmem%:ENDPROC
 3750 PROCRdBlocks(alloc%,sin%,D%,num%):inmem%=sin%:ENDPROC
 3760 :
 3770 DEFFNChkMap(B%):A%=B%?6:B%?6=13:IF $B%=map$:B%?6=A%:=B%?6<>B%?255 ELSE B%?6=A%:=TRUE
 3780 :
 3790 DEFPROCRdBlocks(addr%,block%,drive%,number%):LOCAL p%
 3800 err%=0:FOR p%=0 TO number%-1:PROCfdcRd(addr%+p%*256,block%+p%,drive%,1,den%)
 3810 NEXT:ENDPROC
 3820 :
 3830 :
 3840 REM FDC routines
 3850 REM ============
 3860 :
 3870 DEFPROCfdcInit(dskrec%,bps%,spt%,hds%,den%,trks%,sec0%)
 3880 LOCAL i%:IFos%<>6:ENDPROC
 3890 dskrec%?0=bps%:dskrec%?1=spt%:dskrec%?2=hds%:dskrec%?3=den%:IFden%=1:dskrec%?2=1
 3900 FOR i%=4 TO 59 STEP 4:dskrec%!i%=0:NEXT:dskrec%!60=&20000000:dskrec%!64=&20000000
 3910 dskrec%?8=sec0%:dskrec%!16=trks%*spt%*(2^bps%)*hds%
 3920 ENDPROC
 3930 :
 3940 DEFPROCfdcRd(addr%,sec%,drv%,num%,den%)
 3950 FORA%=0TO255STEP4:addr%!A%=0:NEXT:IFdrv%>7:err%=-1:ENDPROC
 3960 IFden%=3:IFsec%<&500:sec%=(sec%AND15)+((sec%AND&FFF0)*2) ELSE IFden%=3:sec%=(sec%AND15)+(((sec%AND&FFF0)-&500)*2)+&10
 3970 IFdrv$="":REPEAT:PROCfdcOp(1):addr%=addr%+256:sec%=sec%+1:num%=num%-1:UNTILnum%<1:ENDPROC
 3980 dsk%=FNf_openin(drv$):IFdsk%=0:err%=-2:ENDPROC
 3990 IFsec%*256>EXT#dsk%:CLOSE#dsk%:dsk%=0:err%=-3:PRINT"Disk error: past end of image":ENDPROC
 4000 PROCgbpb(3,dsk%,addr%,num%*256,sec%*256):CLOSE#dsk%:dsk%=0
 4010 ENDPROC
 4020 :
 4030 DEFPROCfdcOp(op%)
 4040 IFos%=6:CASEATN"XADFS_DiscOp",0,op%+64+(diskrec%<<6),sec%*bps%+((drv%AND3)<<29),addr%,bps% TO err%
 4050 IFos%<6:IFden%=1:IFop%=1:err%=FNdisk(addr%,&53,(drv%AND3)+2*(sec%DIV800),(sec%DIV10)MOD80,sec%MOD10,1,den%)
 4060 IFos%<6:IFden%>1:IFop%=1:err%=FNscsi(addr%,&08,drv%,sec%,1)
 4070 IFcsd%=0:ENDPROC
 4080 IFerr%:IFPOS:PRINT
 4090 IFerr%=-1:PRINT"Unsupported":ENDPROC
 4100 IFerr%:PRINT"Disk error &"FNh0(err%,2)" at ";drv%":"FNh0(sec%,6)
 4110 ENDPROC
 4120 :
 4130 :
 4140 REM Translate leafname if saving to DOS
 4150 REM ===================================
 4160 REM / ? < > + = ; \ = become . # $ ^ & @ % ~
 4170 DEFFNfn_todos(A$):LOCAL B%:IF(os%AND-32)=0:=A$
 4180 FORA%=1TOLENA$:B%=INSTR("/?<>+=;\",MID$(A$,A%,1)):IFB%:A$=LEFT$(A$,A%-1)+MID$(".#$^&@%~",B%,1)+MID$(A$,A%+1)
 4190 NEXT:=A$
 4200 :
 4210 DEFFNyna(A%):IFA%=0:=0
 4220 PRINT"? (Y/N/A)";:REPEAT:A%=INSTR("YAN",CHR$(GETAND&DF)):UNTILA%
 4230 PRINTSTRING$(7,CHR$127);MID$("YesAllNo ",A%*3-2,3);:=A%-2
 4240 :