10 REM > TubeHost
   20 REM Serial Tube Host System
   30 REM v0.26  - Added client/host filename conversion
   40 REM v0.27  - Configurable .inf usage
   50 REM v0.27a - "" is a wildcard filename
   60 REM v0.27b - host_LOAD, host_SAVE convert filename
   70 REM v0.28  - reads file header, correct filename for *command, */command, *RENAME done
   80 REM v0.29  - *RUN looks for file.bin, data transfer in blocks, implemented *EX
   90 REM v0.29a - COM configurable
  100 REM v0.29b - Uses KBD translation, RDCH sends Escape change
  110 REM v0.29c - hnd% used for handles that don't need to be closed on error
  120 :
  130 REM Note, os% test differences:
  140 REM   os%=6         - RISC OS
  150 REM   os%=%000x0xxx - Any dir.file/ext filing system
  160 REM   os%=8         - Unix/Linux/etc
  170 REM   os%=%000x1xxx - Any dir/file.ext filing system
  180 REM   os%=32        - BBC BASIC for Windows
  190 REM   os%>31        - Any dir\file.ext filing system
  200 :
  210 A%=0:X%=1:os%=((USR&FFF4)AND&FF00)DIV256
  220 _BAUD$="19200" :REM Default baud
  230 _DEBUG$="0"    :REM Default debug
  240 _PFX$="&9B"    :REM Default escape prefix
  250 _COM$="1"      :REM Default serial port
  260 _RST$="0"      :REM Send reset sequence on startup
  270 _MTU$="32"     :REM Maximum transaction size
  280 _INF$="1"      :REM Use .inf files
  290 :
  300 DIM ctrl0% 131,string% 255,escflg% 0:ctrl%=ctrl0%+2:ch%=0:in%=0:out%=0:ser%=0:serout%=0:serin%=0:fn$="":ver$="0.29c"
  310 client%=0:host%=2*((os%>31)AND32)+4*(os%AND8):ON ERROR REPORT:PROCclose:PRINT:PROCexit(ERR):QUIT
  320 PROCChoices_Init("J.G.Harston","TubeHost"):baud%=VAL_BAUD$:dbg%=VAL_DEBUG$
  330 PFX%=EVAL_PFX$:INF%=VAL_INF$:inf%=INF%:max%=VAL_MTU$*1024:IF max%<1024:max%=&7FFFFFFF
  340 PRINT"Tube Host v";ver$;" at ";baud%:tmp$="<Wimp$ScrapDir>."
  350 PROCf_init:IFos%=32:SYS "SetWindowText",@hwnd%,"Tube Host v"+ver$:tmp$=@tmp$
  360 ON ERROR PROCreport:PROC_ERROR:PROCclose:IF FNerr:PROCexit(ERR):QUIT
  370 quit%=FALSE:IFFNopen:PRINT"Couldn't open serial port":A%=INKEY(100):PROCexit(-1):END
  380 IFVAL_RST$:PROC_RESET:_RST$="0":PROCSerFlush
  390 REM REPEAT:PRINTFNc(FNRdByte);:UNTIL quit%
  400 REPEAT:VDU FNRdByte:UNTIL quit%
  410 PROCclose:PROCexit(0)
  420 QUIT
  430 :
  440 :
  450 DEFFNopen
  460 IFos%=32:serin%=OPENUP("COM"+_COM$+": baud="+STR$baud%+" parity=N data=8 stop=1 octs=on"):serout%=serin%:=serin%=0
  470 A%=baud%:IFA%>63:A%=LN(A%DIV75)/LN2:IFA%<4:A%=A%+1
  480 OSCLI"FX7,"+STR$A%:OSCLI"FX8,"+STR$A%:*FX2,2
  490 IFos%=6:serin%=OPENIN"Serial:":serout%=OPENOUT"Serial:":=(serin%=0)OR(serout%=0)
  500 =0
  510 :
  520 DEFPROCclose
  530 IFch% :A%=ch% :ch%=0 :CLOSE#A%
  540 IFin% :A%=in% :in%=0 :CLOSE#A%
  550 IFout%:A%=out%:out%=0:CLOSE#A%
  560 IFserout%=serin%:serout%=0
  570 IFserout%:A%=serout%:serout%=0:CLOSE#A%:ENDPROC
  580 IFserin% :A%=serin% :serin%=0 :CLOSE#A%:ENDPROC
  590 *FX2
  600 ENDPROC
  610 :
  620 DEFFNerr
  630 =INKEY-1 OR (ERR<128 AND ERR<>17 AND ERR<>28 AND INKEY-1)
  640 :
  650 DEFPROCreport:IFdbg%=0:ENDPROC
  660 IFos%=32:*OUTPUT 0
  670 REPORT:PRINTLEFT$(" at line "+STR$ERL,ERR<128 AND ERR<>17 AND ERR<>28)
  680 ENDPROC
  690 :
  700 DEFPROCexit(A%)
  710 IF os%=32:QUIT A% ELSE OSCLI"FX1,"+STR$(A%AND255):QUIT
  720 ENDPROC
  730 :
  740 DEFFNd0(A%,N%)=RIGHT$("00000000"+STR$A%,N%)
  750 DEFFNh0(A%,N%)=RIGHT$("0000000"+STR$~A%,N%)
  760 DEFFNc(A%):A%=A%AND127:IFA%<32 OR A%=127:="." ELSE =CHR$A%
  770 :
  780 :
  790 REM Inward
  800 REM   x                 VDU x
  810 REM   esc,esc           VDU esc
  820 REM   esc,n             MOS function, control block follows
  830 REM
  840 :
  850 :
  860 REM OS_RDCH
  870 REM =======
  880 REM Tube data  &00 -- Carry Char
  890 DEFFN_0                   :IFdbg%AND2:PRINT"OS_RDCH ";
  900 PROCEscOff:IFos%=32:A%=FNkbd_GET ELSE A%=GET
  910 PROCEscOn:IFA%=27:PROCWrCmd(&81):REM Send EscapeChange
  920 PROCWrByte((A%=27)AND&80) :IFdbg%AND2:PRINT"-- "FNh0(&00,2)"  ";
  930 PROCWrByte(A%)            :IFdbg%AND2:PRINTFNh0(A%,2)
  940 =0
  950 :
  960 :
  970 REM OS_CLI
  980 REM ======
  990 REM Tube data  &02 string &0D -- &7F or &80
 1000 DEFFN_2                 :IFdbg%:PRINT"OS_CLI  ";
 1010 PROCRdString            :IFdbg%:PRINT$string%" ";
 1020 Y%=string%:A%=FNmos_cli
 1030 IF A%>=0:PROCWrByte(A%) :IFdbg%:PRINT"-- "FNh0(A%,2)
 1040 =0
 1050 :
 1060 :
 1070 REM SHORT_BYTE
 1080 REM ==========
 1090 REM Tube data  &04 X A -- X
 1100 DEFFN_4          :IFdbg%:PRINT"OS_BYTE ";
 1110 X%=FNRdByte      :IFdbg%:PRINTFNh0(X%,2)" ";
 1120 A%=FNRdByte      :IFdbg%:PRINTFNh0(A%,2)" ";
 1130 A%=FNmos_byte
 1140 IF A%>&7B:IF A%<&7F:PROCWrCmd(&80-(A%=125)):REM Send EscapeChange
 1150 PROCWrByte(X%)   :IFdbg%:PRINT"-- "FNh0(X%,2)
 1160 =0
 1170 :
 1180 REM LONG_BYTE
 1190 REM =========
 1200 REM Tube data  &06 X Y A -- Cy Y X
 1210 DEFFN_6          :IFdbg%:PRINT"OS_BYTE ";
 1220 X%=FNRdByte      :IFdbg%:PRINTFNh0(X%,2)" ";
 1230 Y%=FNRdByte      :IFdbg%:PRINTFNh0(Y%,2)" ";
 1240 A%=FNRdByte      :IFdbg%:PRINTFNh0(A%,2)" ";
 1250 A%=FNmos_byte
 1260 IF A%<0:=0       :REM Reply already sent, eg *FX142
 1270 IF A%=&81:IF X%=27:PROCWrCmd(&81):REM Send EscapeChange
 1280 PROCWrByte((A%AND&100)DIV2) :IFdbg%:PRINT"-- "FNh0(A%DIV2,2)" ";
 1290 PROCWrByte(Y%)              :IFdbg%:PRINTFNh0(Y%,2)" ";
 1300 PROCWrByte(X%)              :IFdbg%:PRINTFNh0(X%,2)
 1310 =0
 1320 :
 1330 :
 1340 REM OS_WORD
 1350 REM =======
 1360 REM Tube data  &08 A in_len block out_len -- block
 1370 DEFFN_8               :IFdbg%:PRINT"OS_WORD ";
 1380 A%=FNRdByte           :IFdbg%:PRINTFNh0(A%,2)" ";
 1390 inlen%=FNRdByte       :IFdbg%:PRINTFNh0(inlen%,2)" ";
 1400 PROCRdBlock(inlen%)   :IFdbg%:IFinlen%:FORD%=0TOinlen%-1:PRINTFNh0(ctrl%?D%,2);:NEXT
 1410 outlen%=FNRdByte      :IFdbg%:PRINTFNh0(outlen%,2)" ";
 1420 X%=ctrl%:Y%=X%DIV256:A%=FNmos_word
 1430 PROCWrBlock(outlen%)  :IFdbg%:PRINT"-- ";:IFoutlen%:FORD%=0TOoutlen%-1:PRINTFNh0(ctrl%?D%,2);:NEXT
 1440 :                     :IFdbg%:PRINT
 1450 =0
 1460 :
 1470 :
 1480 REM OS_RDLINE
 1490 REM =========
 1500 REM Tube data  &0A block -- &FF or &7F string &0D
 1510 DEFFN_A               :IFdbg%AND2:PRINT"OS_RDLN ";
 1520 PROCRdBlock(5)        :IFdbg%AND2:PRINTFNh0(ctrl%!0,4)" "FNh0(ctrl%?2,2)" "FNh0(ctrl%?3,2)" "FNh0(ctrl%?4,2)" ";
 1530 X%=ctrl%:Y%=string%:A%=0:A%=FNmos_word
 1540 IF(A%AND&100)         :IFdbg%AND2:PRINT"-- "FNh0(&80,2)
 1550 IF(A%AND&100):PROCWrByte(&80):=0
 1560 PROCWrByte(&7F)       :IFdbg%AND2:PRINT"-- "FNh0(&7F,2)" ";
 1570 PROCWrString          :IFdbg%AND2:PRINT$string%
 1580 =0
 1590 :
 1600 :
 1610 REM OS_ARGS
 1620 REM =======
 1630 REM Tube data  &0C handle block function -- result block
 1640 DEFFN_C               :IFdbg%:PRINT"OS_ARGS ";
 1650 X%=ctrl%:Y%=FNRdByte  :IFdbg%:PRINTFNh0(Y%,2)" ";
 1660 PROCRdBlock(4)        :IFdbg%:PRINTFNh0(!X%,8);" ";
 1670 A%=FNRdByte           :IFdbg%:PRINTFNh0(A%,2)" ";
 1680 Y%=Y%AND&7F:A%=FNmos_args
 1690 PROCWrByte(A%)        :IFdbg%:PRINT"-- "FNh0(A%,2)" ";
 1700 PROCWrBlock(4)        :IFdbg%:PRINTFNh0(!X%,8)
 1710 =0
 1720 :
 1730 :
 1740 REM OS_BGET
 1750 REM =======
 1760 REM Tube data  &0E handle -- Carry byte
 1770 DEFFN_E               :IFdbg%AND1:PRINT"OS_BGET ";
 1780 Y%=FNRdByte           :IFdbg%AND1:PRINTFNh0(Y%,2)" ";
 1790 Y%=Y%AND&7F:A%=FNmos_bget
 1800 PROCWrByte((A%AND&100)DIV2):IFdbg%AND1:PRINT"-- "FNh0((A%AND&100)DIV2,2)" ";
 1810 PROCWrByte(A%)        :IFdbg%AND1:PRINTFNh0(A%,2)
 1820 =0
 1830 :
 1840 :
 1850 REM OS_BPUT
 1860 REM =======
 1870 REM Tube data  &10 handle byte -- &7F
 1880 DEFFN_10              :IFdbg%AND1:PRINT"OS_BPUT ";
 1890 Y%=FNRdByte           :IFdbg%AND1:PRINTFNh0(Y%,2)" ";
 1900 A%=FNRdByte           :IFdbg%AND1:PRINTFNh0(A%,2)" ";
 1910 Y%=Y%AND&7F:A%=FNmos_bput
 1920 PROCWrByte(&7F)       :IFdbg%AND1:PRINT"-- "FNh0(&7F,2)
 1930 =0
 1940 :
 1950 :
 1960 REM OS_FIND
 1970 REM =======
 1980 REM Tube data  &12 function string &0D -- handle
 1990 REM            &12 &00 handle -- &7F
 2000 DEFFN_12              :IFdbg%:PRINT"OS_FIND ";
 2010 A%=FNRdByte           :IFdbg%:PRINTFNh0(A%,2)" ";
 2020 IF A%=0 THEN
 2030   Y%=FNRdByte         :IFdbg%:PRINTFNh0(Y%,2)" ";
 2040   Y%=Y%AND&7F:A%=FNmos_find:IF Y%=0 OR Y%=serin%:A%=FNopen:REM Reopen serial port
 2050   PROCWrByte(&7F)     :IFdbg%:PRINT"-- "FNh0(&7F,2)
 2060   =0
 2070 ENDIF
 2080 PROCRdString          :IFdbg%:PRINT$string%" ";
 2090 Y%=string%-1:REPEATY%=Y%+1:UNTIL?Y%<33:?Y%=13:Y%=string%
 2100 IFFNwildcardsNull:=0  :REM BB4W throws up a dialogue box with wildcards and ""
 2110 $Y%=FNfn_convert($Y%,client%,host%)
 2120 Y%=FNmos_find:IFY%:Y%=Y% OR &80
 2130 PROCWrByte(Y%)        :IFdbg%:PRINT"-- "FNh0(Y%,2)
 2140 =0
 2150 :
 2160 :
 2170 REM OS_FILE
 2180 REM =======
 2190 REM Tube data  &14 block string <cr> function -- result block
 2200 DEFFN_14              :IFdbg%:PRINT"OS_FILE ";
 2210 PROCRdBlock(16)       :IFdbg%:FOR D%=0 TO 12 STEP 4:PRINTFNh0(ctrl%!D%,8)" ";:NEXT
 2220 PROCRdString          :IFdbg%:PRINT$string%;" ";
 2230 A%=FNRdByte           :IFdbg%:PRINTFNh0(A%,2)" ";
 2240 Y%=string%-1:REPEATY%=Y%+1:UNTIL?Y%<33:?Y%=13:Y%=string%
 2250 IFFNwildcardsNull:=0  :REM BB4W throws up a dialogue box with wildcards and ""
 2260 fn$=$Y%:$Y%=FNfn_convert($Y%,client%,host%)
 2270 X%=ctrl%-2:A%=FNmos_file
 2280 PROCWrByte(A%)        :IFdbg%:PRINT"-- "FNh0(A%,2);
 2290 PROCWrBlock(16)       :IFdbg%:FOR D%=0 TO 12 STEP 4:PRINT" "FNh0(ctrl%!D%,8);:NEXT:PRINT
 2300 =0
 2310 :
 2320 :
 2330 REM OS_GBPB
 2340 REM =======
 2350 REM Tube data  &16 block function -- block Carry result
 2360 DEFFN_16              :IFdbg%:PRINT"OS_GBPB ";
 2370 PROCRdBlock(13)       :IFdbg%:PRINTFNh0(?ctrl%,2)" ";:FOR D%=1 TO 9 STEP 4:PRINTFNh0(ctrl%!D%,8)" ";:NEXT
 2380 A%=FNRdByte           :IFdbg%:PRINTFNh0(A%,2)" ";
 2390 X%=ctrl%:?X%=?X%AND&7F
 2400 A%=FNmos_gbpb:?X%=?X%OR&80
 2410 PROCWrBlock(13)            :IFdbg%:PRINT"-- "FNh0(?ctrl%,2)" ";:FOR D%=1 TO 9 STEP 4:PRINTFNh0(ctrl%!D%,8)" ";:NEXT
 2420 PROCWrByte((A%AND&100)DIV2):IFdbg%:PRINTFNh0((A%AND&100)DIV2,2)" ";
 2430 PROCWrByte(A%)             :IFdbg%:PRINTFNh0(A%,2)
 2440 =0
 2450 :
 2460 :
 2470 REM OS_FSC
 2480 REM ======
 2490 REM Tube data  &18 X Y A -- &FF Y X
 2500 REM                      -- &7F  then  string &0D -- &7F/&40/&80
 2510 DEFFN_18               :IFdbg%:PRINT"OS_FSC  ";
 2520 X%=FNRdByte            :IFdbg%:PRINTFNh0(X%,2)" ";
 2530 Y%=FNRdByte            :IFdbg%:PRINTFNh0(Y%,2)" ";
 2540 A%=FNRdByte            :IFdbg%:PRINTFNh0(A%,2)" ";
 2550 IFdbg%:PRINTMID$("OPTEOF*/ CMDRUNCATNEWHNDWRNEX INFRUNREN",A%*3+1,3)" ";
 2560 :
 2570 IF A%>127 OR A%<2 OR A%=6 OR A%=7 OR A%=8 THEN
 2580   IFA%=1:Y%=Y%AND127  :REM =EOF
 2590   A%=FNmos_fsc
 2600   PROCWrByte(&FF)      :IFdbg%:PRINT"-- "FNh0(&FF,2)" ";
 2610   PROCWrByte(Y%)       :IFdbg%:PRINTFNh0(Y%,2)" ";
 2620   PROCWrByte(X%)       :IFdbg%:PRINTFNh0(X%,2)
 2630   =0
 2640 ENDIF
 2650 PROCWrByte(&7F)        :IFdbg%:PRINT"-- "FNh0(&7F,2)" ";
 2660 PROCRdString           :IFdbg%:PRINT$string%" ";
 2670 Y%=string%:A%=FNmos_fsc
 2680 IF A%>=0:PROCWrByte(A%):IFdbg%:PRINTFNh0(A%,2)
 2690 =0
 2700 :
 2710 :
 2720 REM Null routines
 2730 REM =============
 2740 DEFFN_1A=0
 2750 DEFFN_1C=0
 2760 :
 2770 REM OS_RESYNC - Called within error handler to allow Host to resynchronise
 2780 REM =========
 2790 REM Tube data  &1E -- &00
 2800 DEFFN_1E               :IFdbg%:PRINT"OS_SYNC ";
 2810 PROCWrByte(&00)        :IFdbg%:PRINT"-- "FNh0(&00,2)
 2820 =0
 2830 :
 2840 :
 2850 DEFPROC_ERROR
 2860 $string%=LEFT$("HostFS error: ",ERR<128 AND ERR<>17 AND ERR<>28)+REPORT$+LEFT$(" ("+STR$ERL+")",ERR<128 AND ERR<>17 AND ERR<>28)+CHR$0
 2870 PROCWrCmd(&00)         :IFdbg%:PRINT"ERROR   "FNh0(PFX%,2)" "FNh0(&00,2)" ";
 2880 A%=ERR:IF ERR=28:A%=253:$string%="Bad address"+CHR$0
 2890 PROCWrByte(A%)        :IFdbg%:PRINTFNh0(A%,2)" ";
 2900 B%=0
 2910 REPEAT
 2920   A%=string%?B%
 2930   PROCWrByte(A%)      :IFdbg%:PRINTFNc(A%);
 2940   B%=B%+1
 2950 UNTILA%=0             :IFdbg%:PRINT
 2960 ENDPROC
 2970 :
 2980 :
 2990 DEFPROC_RESET
 3000 PROCWrCmd(&00)        :IFdbg%:PRINT"RESET   "FNh0(PFX%,2)" "FNh0(&00,2)" ";
 3010 PROCWrByte(&00)       :IFdbg%:PRINTFNh0(&00,2)" ";
 3020 PROCWrByte(&00)       :IFdbg%:PRINTFNh0(&00,2)
 3030 REM REPEAT:A%=FNRdByte:IFA%:VDUA%  :REM Startup string
 3040 REM UNTILA%=0:PROCWrByte(&00)      :REM Acknowledge startup
 3050 ENDPROC
 3060 :
 3070 :
 3080 DEFFNwildcardsNull:IFos%<>32:=FALSE
 3090 IF?Y%=13 OR $Y%="""":ERROR 204,"Bad filename"
 3100 DEFFNwildcards:IFos%<>32:=FALSE
 3110 IF(client%AND96)=0:IFINSTR($Y%,"*")+INSTR($Y%,"#")=0:=FALSE
 3120 IF(client%AND96)  :IFINSTR($Y%,"*")+INSTR($Y%,"?")=0:=FALSE
 3130 ERROR 253,"Wildcards"
 3140 =TRUE
 3150 :
 3160 :
 3170 DEFPROCEscOff:IFos%=32:OSCLI"ESC OFF":ENDPROC
 3180 OSCLI"FX229":ENDPROC
 3190 DEFPROCEscOn:IFos%=32:OSCLI"ESC OFF":ENDPROC
 3200 OSCLI"FX229,1":ENDPROC
 3210 :
 3220 :
 3230 REM I/O routines
 3240 REM ============
 3250 :
 3260 DEFFNRdByte
 3270 REPEAT
 3280   B%=FNSerIn:IFB%=PFX%:B%=FNSerIn:IFB%<>PFX%:IF(B%AND&81)=0:client%=B%AND&60:A%=EVAL("FN_"+STR$~(B%AND&1E)):B%=-1
 3290 UNTIL B%>=0
 3300 =B%
 3310 :
 3320 DEFPROCWrByte(A%)
 3330 PROCSerOut(A%):IF A%=PFX%:PROCSerOut(A%)
 3340 ENDPROC
 3350 :
 3360 DEFPROCWrCmd(A%)
 3370 PROCSerOut(PFX%):PROCSerOut(A%):ENDPROC
 3380 :
 3390 DEFPROCRdBlock(N%):IF N%=0:ENDPROC
 3400 FOR D%=N%-1 TO 0 STEP -1:ctrl%?D%=FNRdByte:NEXT:ENDPROC
 3410 :
 3420 DEFPROCWrBlock(N%):IF N%=0:ENDPROC
 3430 FOR D%=N%-1 TO 0 STEP -1:PROCWrByte(ctrl%?D%):NEXT:ENDPROC
 3440 :
 3450 DEFPROCRdString
 3460 D%=0:REPEAT:B%=FNRdByte:string%?D%=B%:D%=D%+1:UNTILB%=13:ENDPROC
 3470 :
 3480 DEFPROCWrString
 3490 D%=0:REPEAT:B%=string%?D%:PROCWrByte(B%):D%=D%+1:UNTILB%=13:ENDPROC
 3500 :
 3510 :
 3520 REM Low level routines
 3530 REM ==================
 3540 :
 3550 DEFFNSerIn:IFserin%:=BGET#serin%
 3560 =0
 3570 :
 3580 DEFPROCSerOut(A%):IFserout%:BPUT#serout%,A%:ENDPROC
 3590 ENDPROC
 3600 :
 3610 DEFPROCSerFlush:IFserin%:WHILE EXT#serin%:B%=BGET#serin%:ENDWHILE:ENDPROC
 3620 ENDPROC
 3630 :
 3640 :
 3650 DEFFNargs(A%,Y%,X%)
 3660 PRINT"@ARGS "FNh0(A%,2)" "FNh0(Y%,2)" "FNh0(X%,8)" ";
 3670 =0
 3680 :
 3690 :
 3700 REM Choices System
 3710 REM ==============
 3720 DEFPROCChoices_Init(Pub$,App$):LOCAL A$,A%
 3730 IF os%=32 THEN
 3740   PROCChoices_Rd(@dir$+"\"+App$+".ini")
 3750   A$=FNReg_Rd("Software\"+Pub$+"\"+App$+"\Settings")
 3760   IFA$<>"":PROCChoices_Rd(A$)
 3770 ENDIF
 3780 IF os%=6 THEN
 3790   SYS "OS_GetEnv" TO A$:A$=MID$(A$,INSTR(A$,"""")+1):A$=LEFT$(A$,INSTR(A$,"""")-1)
 3800   REPEAT A%=INSTR(Pub$,"."):IFA%:Pub$=LEFT$(Pub$,A%-1)+MID$(Pub$,A%+1)
 3810     IFA%=0:A%=INSTR(Pub$," "):IFA%:Pub$=LEFT$(Pub$,A%-1)+MID$(Pub$,A%+1)
 3820   UNTILA%=0:PROCChoices_Rd(A$+".^.Choices")
 3830   PROCChoices_Rd("Choices:"+Pub$+"."+App$+".Choices")
 3840 ENDIF
 3850 ENDPROC
 3860 :
 3870 DEFPROCChoices_Rd(file$):LOCAL A$,A%
 3880 in%=OPENIN(file$)
 3890 IFin% THEN
 3900   REPEAT
 3910     A$=GET$#in%:IF LEFT$(A$,1)="#":A$=""
 3920     A%=INSTR(A$,":"):IF A% THEN
 3930       A%=A%-1:REPEAT:A%=A%+1:UNTIL MID$(A$,A%,1)<>" "
 3940       PROCVar_Assign("_"+LEFT$(A$,A%-1)+"$",""""+MID$(A$,A%+1)+"""")
 3950     ENDIF
 3960   UNTIL EOF#in%:CLOSE#in%:in%=0
 3970 ENDIF
 3980 ENDPROC
 3990 
   10 REM > BBCMOS
   20 REM BBC MOS Interface
   30 REM Within this library, all filenames are locally formatted
   40 ;
   50 DEFFNmos_args
   60 IF Y%=0:IF A%=0:=FNfs           :REM =FS
   70 IF Y%=-1 OR Y%=&7F THEN
   80   CASE A% OF
   90     WHEN 0:!X%=0:=0                          :REM =PTR#-1
  100     WHEN 2:!X%=((os%=32)AND255)-ADVAL(-1):=0 :REM =EXT#-1, number in keyboard buffer
  110     WHEN 5:!X%=((os%=32)AND255)=ADVAL(-1):=0 :REM =EOF#-1, keyboard buffer empty?
  120   ENDCASE
  130 ENDIF
  140 IF Y% THEN
  150   CASE A% OF
  160     WHEN 0:!X%=PTR#Y%:=0        :REM =PTR
  170     WHEN 1:PTR#Y%=!X%:=0        :REM PTR=
  180     WHEN 2:!X%=EXT#Y%:=0        :REM =EXT
  190     WHEN 3:EXT#Y%=!X%:=0        :REM EXT=
  200     WHEN 4:!X%=EXT#Y%:=0        :REM =Alloc
  210     WHEN 5:!X%=EOF#Y%:=0        :REM =EOF
  220     WHEN 6:EXT#Y%=!X%:=0        :REM Alloc=
  230   ENDCASE
  240 ENDIF
  250 IF os%=6:IF HIMEM>&FFFF:SYS "OS_Args",A%,Y%,!X% TO A%,Y%,!X%:=A%
  260 !X%=FNargs(A%,Y%,!X%)
  270 =A%                             :REM Unchanged=unsupported
  280 :
  290 DEFFNmos_bget
  300 IF Y%=0 THEN
  310   IF os%=32:A%=FNkbd_GET ELSE A%=GET       :REM =BGET#0
  320   =((A%=27)AND&100)+A%
  330 ENDIF
  340 IF os%=32:=((PTR#Y%=EXT#Y%)AND256)+BGET#Y% :REM =BGET#Y
  350 A%=USR&FFD7
  360 =((A%AND&1000000)DIV&10000)OR(A%AND&FF)
  370 :
  380 DEFFNmos_bput
  390 IF Y%=0:VDU A% ELSE BPUT#Y%,A%
  400 =A%
  410 :
  420 DEFFNmos_find
  430 IF A%=0:CLOSE#Y%:=A%
  440 IF (A%AND&C0)=&40:=FNf_openin($Y%)
  450 IF (A%AND&C0)=&80:=FNf_openout($Y%)
  460 IF (A%AND&C0)=&C0:=FNf_openup($Y%)
  470 =0
  480 :
  490 DEFFNmos_file
  500 IF A%=6:LOCAL ERROR:ON ERROR LOCAL:=0
  510 A%=A%AND&FF
  520 IF A%<5 OR A%=&FF:=FNhost_file
  530 IF os%=6:SYS "OS_File",A%,Y%,X%!2,X%!6,X%!10,X%!14 TO A%,,X%!2,X%!6,X%!10,X%!14:=A%
  540 IF A%=5:X%!2=0:X%!6=0:X%!10=0:X%!14=0:A%=Y%:Y%=X%DIV256:=FNfile_info($A%)
  550 IF A%=6:PROCf_delete($Y%):IF inf%<>0 AND os%=32:PROCf_delete($Y%+s$+"inf"):=1:REM Should return filetype
  560 IF A%=6:=1:REM Should return filetype
  570 IF A%=7:OSCLI"SAVE """+FNf_name($Y%)+""" "+STR$~PAGE+"+"+STR$~(X%!14-X%!10):=1
  580 IF A%=8:PROCf_cdir($Y%):=2
  590 =A%
  600 :
  610 DEFFNmos_gbpb:=FNhost_gbpb
  620 :
  630 DEFFNmos_cli
  640 Y%=Y%-1:REPEAT:Y%=Y%+1:UNTIL?Y%<>32 AND ?Y%<>ASC"*"     :REM Skip '*'s and ' 's
  650 A%=&103:IF ?Y%=ASC"/":Y%=Y%+1:A%=&102                   :REM Fall through into FSC
  660 :
  670 DEFFNmos_fsc
  680 osc%=A%AND256:A%=A%AND255                               :REM b8=called from OSCLI
  690 CASE A% OF
  700   WHEN 0:                                               :REM *OPT
  710   CASE X% OF
  720     WHEN 0:              :REM *OPT 0 - reset options
  730     WHEN 1:              :REM *OPT 1 - message levels (needs Client support)
  740     WHEN 2:              :REM *OPT 2 - error handling level
  750     WHEN 3:              :REM *OPT 3 - (CFS: interblock gap) (MTU?)
  760     WHEN 4:              :REM *OPT 4 - boot option
  770     WHEN 5:dbg%=Y%:=0    :REM *OPT 5 - debug level
  780     WHEN 6:              :REM *OPT 6 - transmission delay (HADFS: internal drive, ANFS: claim workspace)
  790     WHEN 7:              :REM *OPT 7 -                    (HADFS: external drive, ANFS: user options)
  800   ENDCASE
  810   =0
  820   WHEN 1:A%=5:X%=ctrl%:A%=FNmos_args:X%=?X%:Y%=X%:=A%   :REM =EOF
  830   WHEN 7:X%=&80:Y%=&9F:=0                               :REM Handles
  840   WHEN 255:=0                                           :REM Boot filing system
  850   REM X%=Break type, Y%=0 Boot, Y<>0 NoBoot (Y%=0 Shift Break, Y%=8 Break, Y%=&FF CoPro startup)
  860   REM CoPro checks returned X.b7 for startup action
  870   REM Filing system checks returned Y.b1-b0 for boot action
  880   :
  890   WHEN 3:                                               :REM *command
  900   IF ?Y%=ASC"|":=0                                    :REM *| comment
  910   IF ?Y%=ASC"\":Y%=Y%+1                               :REM *\command -> *command
  920   IF ?Y%=13:=0                                        :REM Null string
  930   A%=INSTR($Y%+" "," "):B%=INSTR($Y%,"."):IFB%:IFA%>B%+1:A%=B%+1
  940   A$=":"+FNuc(LEFT$($Y%,A%-1)):Y%=Y%+A%-1             :REM A$=command
  950   WHILE ?Y%=32:Y%=Y%+1:ENDWHILE                       :REM Skip spaces
  960   IF RIGHT$(A$,1)=".":A$=LEFT$(A$,LENA$-1) ELSE A$=A$+":"
  970   A%=INSTR(":CAT::::RUN::::CAT::::DELETE:CREATE:CDIR:::EX:::::INFO::::::::::RENAME:",A$)
  980   IF A%=0:A%=3ORosc%:Y%=string%:=FNhost_fsc
  990   A%=3+A%DIV7:IF A%=3:A%=5
 1000   IF A%>5 AND A%<9 THEN
 1010     X%=Y%-1:REPEATX%=X%+1:UNTIL?X%<33:?X%=13          :REM Terminate string
 1020     IF A%=7 THEN
 1030       X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<>32             :REM *CREATE filename length
 1040       ctrl%!14=EVAL("&"+$X%)
 1050       ctrl%!2=0:ctrl%!6=0:ctrl%!10=0
 1060     ENDIF
 1070     $Y%=FNfn_convert($Y%,client%,host%):X%=ctrl%:!X%=Y%:A%=A%OR&100:=FNmos_file
 1080   ENDIF
 1090   A%=A%ORosc%
 1100 ENDCASE
 1110 =FNhost_fsc
 1120 :
 1130 DEFFNmos_word
 1140 CASE A% OF
 1150   WHEN 0:INPUT LINE""A$:$Y%=A$:X%=Y%:Y%=LENA$:=0
 1160   WHEN 1:!X%=TIME:X%?4=0:=0
 1170   WHEN 2:TIME=!X%:=0
 1180   WHEN 5:X%?4=?!X%:=0
 1190   WHEN 6:?!X%=X%?4:=0
 1200   WHEN 7:SOUND !X%,X%!2,X%!4,X%!6:=0
 1210   WHEN 8:ENVELOPE X%?0,X%?1,X%?2,X%?3,X%?4,X%?5,X%?6,X%?7,X%?8,X%?9,X%?10,X%?11,X%?12,X%?13:=0
 1220   WHEN 9:X%?4=POINT(!X%AND&FFFF,X%!2AND&FFFF):=0
 1230   WHEN 14:$X%=TIME$:=0
 1240   WHEN 15:TIME$=$X%:=0
 1250 OTHERWISE
 1260   Y%=X%DIV256:A%=(USR&FFF1)AND&FF
 1270 ENDCASE
 1280 =A%
 1290 :
 1300 DEFFNmos_byte
 1310 IF A%=0 THEN
 1320   X%=(os%AND&D7)OR((client%AND64)DIV2)OR((client%AND32)DIV4)
 1330   IF X%<>os%:X%=X%OR&80
 1340   =0                                                    :REM =HOST
 1350 ENDIF
 1360 IF A%>123:IFA%<127:?escflg%=128*(A%AND1):OSCLI"FX15"    :REM Clear/Set/Ack Escape
 1370 IF A%=&7F:A%=5:X%=ctrl%:A%=FNmos_args:X%=?X%:Y%=X%:=&7F :REM =EOF
 1380 IF A%=&81:IFos%=32:X%=FNkbd_INKEY((X%AND255)+256*Y%):Y%=X%DIV256:=A%-256*(X%=-1) :REM =INKEY
 1390 IF A%=&81:X%=INKEY((X%AND255)+256*Y%):Y%=X%DIV256:=A%-256*(X%=-1)                :REM =INKEY
 1400 IF A%=&82:X%=membot%DIV65536:Y%=X%DIV256:=A%            :REM high order address
 1410 IF A%=&83:X%=membot%:Y%=X%DIV256:=A%                    :REM bottom of memory
 1420 IF A%=&84:X%=memtop%:Y%=X%DIV256:=A%                    :REM top of memory
 1430 IF A%=&86:X%=POS:Y%=VPOS:=A%                            :REM =POS, =VPOS
 1440 IF os%=32 THEN
 1450   IF A%=&80:IFX%=7:MOUSE X%,A%,A%:Y%=X%DIV256:=&80      :REM =ADVAL(7)
 1460   IF A%=&80:IFX%=8:MOUSE A%,X%,A%:Y%=X%DIV256:=&80      :REM =ADVAL(8)
 1470   IF A%=&80:IFX%=9:MOUSE A%,A%,X%:Y%=X%DIV256:=&80      :REM =ADVAL(9)
 1480   IF A%=&87:X%=GET(POS,VPOS):Y%=MODE:=A%                :REM =MODE
 1490   IF A%=&A0 THEN
 1500     CASE X% OF
 1510       WHEN  0,128:SYS "GetRgnBox",@vdu%!60,ctrl%:X%=ctrl%!0
 1520       WHEN  1    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=ctrl%!1
 1530       WHEN  2,129:SYS "GetRgnBox",@vdu%!60,ctrl%:X%=ctrl%!4
 1540       WHEN  3    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=ctrl%!5
 1550       WHEN  4,130:SYS "GetRgnBox",@vdu%!60,ctrl%:X%=ctrl%!8
 1560       WHEN  5    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=(ctrl%!8-1)DIV256
 1570       WHEN  6,131:SYS "GetRgnBox",@vdu%!60,ctrl%:X%=ctrl%!12
 1580       WHEN  7    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=(ctrl%!12/2-1)DIV256
 1590       WHEN  8,132:X%=@vdu%!24 DIV @vdu%!216
 1600       WHEN  9,133:X%=@vdu%!36 DIV @vdu%!220-1
 1610       WHEN 10,134:X%=@vdu%!28 DIV @vdu%!216-1
 1620       WHEN 11,135:X%=@vdu%!32 DIV @vdu%!220
 1630       WHEN 12,136:X%=2*@vdu%!0
 1640       WHEN 13    :X%=2*@vdu%!0 DIV 256
 1650       WHEN 14,137:X%=2*@vdu%!4
 1660       WHEN 15    :X%=2*@vdu%!4 DIV 256
 1670       WHEN 16,138:X%=2*@vdu%!8
 1680       WHEN 17    :X%=2*@vdu%!8 DIV 256
 1690       WHEN 18,139:SYS "GetRgnBox",@vdu%!60,ctrl%:X%=2*(ctrl%!12-@vdu%!12-1)
 1700       WHEN 19    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=2*(ctrl%!12-@vdu%!12-1) DIV 256
 1710       WHEN 20,142:X%=@vdu%!16
 1720       WHEN 21    :X%=@vdu%!16 DIV 256
 1730       WHEN 22,143:SYS "GetRgnBox",@vdu%!60,ctrl%:X%=(ctrl%!12-@vdu%!20-1)
 1740       WHEN 23    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=(ctrl%!12-@vdu%!20-1) DIV 256
 1750       WHEN 24    :X%=POS
 1760       WHEN 25    :X%=VPOS
 1770       WHEN 36    :X%=@vdu%!8
 1780       WHEN 37    :X%=@vdu%!8 DIV 256
 1790       WHEN 38    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=(ctrl%!12-@vdu%!12-1)
 1800       WHEN 39    :SYS "GetRgnBox",@vdu%!60,ctrl%:X%=(ctrl%!12-@vdu%!12-1) DIV 256
 1810       WHEN 85    :X%=MODE
 1820       WHEN 87,155:X%=@vdu%?70
 1830       WHEN 88,156:X%=@vdu%?71
 1840       WHEN 89,153:X%=@vdu%?65
 1850       WHEN 90,154:X%=@vdu%?67
 1860       WHEN 91,151:X%=3-(@vdu%?64-7)DIV2:IFX%<0:X%=1
 1870       WHEN 92,152:X%=3-(@vdu%?66-7)DIV2:IFX%<0:X%=1
 1880       WHEN 96,3  :X%=@vdu%?73
 1890       WHEN 98    :REM Left colour mask
 1900       WHEN 99    :REM Right colour mask
 1910       WHEN 109   :X%=@vdu%?65
 1920       WHEN 110   :X%=@vdu%?67
 1930       WHEN 155   :X%=@vdu%?70
 1940       WHEN 156   :X%=@vdu%?71
 1950       WHEN 153   :X%=@vdu%?65
 1960       WHEN 154   :X%=@vdu%?67
 1970     OTHERWISE
 1980       X%=0
 1990     ENDCASE
 2000     Y%=X%DIV256:=&A0
 2010   ENDIF
 2020 ENDIF
 2030 REM Note: Client program should do ((os%=32)AND255)-ADVAL for -1/-2
 2040 IF A%=&80:X%=ADVAL((X%AND255)OR((X%>127)AND-256)):Y%=X%DIV256:=A% :REM =ADVAL
 2050 IF A%=&A0 THEN
 2060   IF X%<16:IF X%<>3:=A%                               :REM =VDU(n)
 2070   IF X%=3:X%=96
 2080   CASE r%(1) OF
 2090     WHEN 16:A%=138
 2100     WHEN 18:A%=139
 2110     WHEN 20:A%=142
 2120     WHEN 22:A%=143
 2130     WHEN 36:A%=144
 2140     WHEN 38:A%=145
 2150     WHEN 77:A%=148
 2160     WHEN 80:A%=149
 2170     WHEN 83:A%=150
 2180     WHEN 87:A%=155
 2190     WHEN 88:A%=156
 2200     WHEN 89:A%=153
 2210     WHEN 90:A%=154
 2220     WHEN 91:A%=151
 2230     WHEN 92:A%=152
 2240     WHEN 96:A%=3
 2250   OTHERWISE
 2260     A%=X%:IF A%<128:X%=0:Y%=0:=&A0
 2270   ENDCASE
 2280   !ctrl%=A%:ctrl%!4=-1:SYS "OS_ReadVduVariables",ctrl%,ctrl%+8
 2290   X%=ctrl%!8:Y%=X%DIV256:=&A0
 2300 ENDIF
 2310 A%=USR&FFF4:X%=(A%AND&FFFF00)DIV256:Y%=(A%AND&FF0000)DIV65536
 2320 =(A%AND&1000000)DIV&10000+(A%AND&FF)
 2330 :
   10 REM > HostTx
   20 REM Assumes a reliable (all bytes get through) error-free (no bytes are corrupted) link channel
   30 REM Within this library, all filenames are passed as internal filenames with external name in fn$
   40 REM v0.27 OSFILE 1-4
   50 REM v0.28 Reads file header, correct filename for *command, */command, *RENAME done, *file checks for .bin
   60 REM v0.29 Data transfers done in blocks, added *EX.
   70 REM Changed name to HostTx as now almost completely generalised.
   80 REM v0.29c hnd% used for handles that don't need to be closed on error
   90 ;
  100 :
  110 REM File load/save routines
  120 REM =======================
  130 REM fn$ set by external caller to originally passed filename
  140 DEFFNhost_file
  150 L%=X%!2:E%=X%!6:T%=X%!14:IFA%:?X%=FNfile_info($Y%):S%=X%!10
  160 IFA%>0 AND A%<5 THEN
  170   REM L%=X%!2:E%=X%!6:T%=X%!14
  180   REM ?X%=FNfile_info($Y%)
  190   IF inf%=0:=?X%
  200   IF A%=1:X%!2=L%:X%!6=E%:X%!14=T%
  210   IF A%=2:X%!2=L%
  220   IF A%=3:X%!6=E%
  230   IF A%=4:X%!14=T%
  240   Y%=string%:A$=$Y%:$Y%=FNfn_leaf(A$)+" "+FNh0(X%!2,8)+" "+FNh0(X%!6,8)+" "+FNh0(X%!10,8):REM +" "+FNh0(X%!14,2)
  250   OSCLI"SAVE "+A$+s$+"inf "+STR$~Y%+"+"+STR$~(LEN$Y%+1):=?X%
  260   REM OSCLI"SAVE "+FNfn_noext(A$)+s$+"inf "+STR$~Y%+"+"+STR$~(LEN$Y%+1):=?X%
  270 ENDIF
  280 IFA%=&FF:T%=X%!2:IF(E%AND255)=0:X%!2=L%
  290 IFA%=&FF:IF?X%:IFX%!2=-1:ERROR 252,"Bad address"
  300 IFA%=&FF:A%=&40 ELSE A%=&80
  310 ch%=FNmos_find
  320 IFA%=&40:IFch%=0:ERROR 214,"File '"+fn$+"' not found"
  330 IFA%=&80:IFch%=0:ERROR 192,"Can't save '"+fn$+"'"
  340 IFA%=&40 THEN
  350   Y%=FNhost_send(X%+2,ch%,EXT#ch%):X%!2=T%                            :REM Send the file
  360 ELSE
  370   Y%=X%!14-X%!10:T%=FNhost_read(X%+10,ch%,Y%):X%!10=Y%:X%!14=&33      :REM Fetch the file
  380 ENDIF
  390 CLOSE#ch%:ch%=0
  400 IF A%=&80 AND os%>31 AND inf%<>0 THEN
  410   Y%=string%:?X%=FNfile_info($Y%):IFX%!2=L%:IFX%!6=E%:IFX%!10=S%:=?X% :REM No change, no new .inf file
  420   A$=$Y%:$Y%=FNfn_leaf(A$)+" "+FNh0(L%,8)+" "+FNh0(E%,8)+" "+FNh0(X%!10,8)
  430   OSCLI"SAVE "+A$+s$+"inf "+STR$~Y%+"+"+STR$~(LEN$Y%+1)
  440   REM OSCLI"SAVE "+FNfn_noext(A$)+s$+"inf "+STR$~Y%+"+"+STR$~(LEN$Y%+1)
  450   X%!2=L%:X%!6=E%
  460 ENDIF
  470 =1                                                                    :REM Return 'File'
  480 :
  490 :
  500 REM File read/write routines
  510 REM ========================
  520 DEFFNhost_gbpb
  530 REM X%=>control block
  540 IFA%=0 OR A%>4:=FNhost_info       :REM Read filing system information
  550 hnd%=?X%:IF(A%AND1):PTR#hnd%=X%!9 :REM Set PTR
  560 IFA%>2:X%!5=FNhost_send(X%+1,hnd%,X%!5) ELSE X%!5=FNhost_read(X%+1,hnd%,X%!5)
  570 X%!9=PTR#hnd%                     :REM Return updated PTR
  580 =(EOF#hnd%)AND&100                :REM Return Carry in b8, 0 in b0-b7 for 'supported'
  590 :
  600 :
  610 REM File filing system information
  620 REM ==============================
  630 DEFFNhost_info
  640 IF os%<>32:=A%
  650 LOCAL sh%,dir%,res%,ptr%:DIM dir% LOCAL 317
  660 CASE A% OF
  670   WHEN 6:
  680   SYS "GetCurrentDirectory",255,string%:A$=$$string%
  690   B$=LEFT$(A$,1):A$=FNfn_leaf(A$):IF A$="":A$="\"
  700   A$=FNfn_convert(A$,host%,client%)
  710   PROCWrCmd(&E0)           :REM Load data
  720   PROCWrByte(X%?4)         :REM Send address
  730   PROCWrByte(X%?3)
  740   PROCWrByte(X%?2)
  750   PROCWrByte(X%?1)
  760   PROCWrByte(1)
  770   PROCWrByte(ASCB$)
  780   PROCWrByte(LENA$)
  790   $string%=A$+CHR$0
  800   PROCWrString
  810   PROCWrCmd(&B0)           :REM End transfer
  820   X%!1=X%!1+LEN$string%
  830   =0
  840   WHEN 8
  850   ptr%=X%!9
  860   SYS "FindFirstFile",".\*.*",dir% TO sh%   :REM Initialise search
  870   IF sh%<>-1 THEN
  880     REPEAT
  890       A$=$$(dir%+44)                   :REM Get object name
  900       IF A$="." OR A$=".." OR FNuc(RIGHT$(A$,4))=".INF":ptr%=ptr%+1:REM Step past special objects
  910       IF ptr%=0 THEN
  920         SYS "FindClose",sh%
  930         A$=FNfn_convert(A$,host%,client%)
  940         PROCWrCmd(&E0)           :REM Load data
  950         PROCWrByte(X%?4)         :REM Send address
  960         PROCWrByte(X%?3)
  970         PROCWrByte(X%?2)
  980         PROCWrByte(X%?1)
  990         PROCWrByte(LENA$)        :REM name length
 1000         $string%=A$
 1010         PROCWrString             :REM Name string
 1020         PROCWrCmd(&B0)           :REM End transfer
 1030         X%!1=X%!1+1+LENA$        :REM Update data address
 1040         X%!5=X%!5-1              :REM Count=Count-number transfered
 1050         X%!9=X%!9+1              :REM Increment returned pointer
 1060         =0
 1070       ENDIF
 1080       ptr%=ptr%-1                :REM Decrement requested pointer
 1090       SYS "FindNextFile",sh%,dir% TO res%  :REM Step to next object
 1100     UNTIL res%=0                           :REM Loop until no more found
 1110     SYS "FindClose",sh%
 1120   ENDIF
 1130   =0                             :REM Return at end of directory
 1140 ENDCASE
 1150 =A%
 1160 :
 1170 :
 1180 REM Filing control routines
 1190 REM =======================
 1200 DEFFNhost_fsc
 1210 REM   0 - *OPT - done by BBCMOS
 1220 REM   1 - EOF  - done by BBCMOS
 1230 REM   2 - */
 1240 REM   3 - *command
 1250 REM   4 - *RUN
 1260 REM   5 - *CAT
 1270 REM   6 - New filing system  - done by BBCMOS
 1280 REM   7 - Handles            - done by BBCMOS
 1290 REM   8 - OSCLI warning      - done by BBCMOS
 1300 REM   9 - *EX
 1310 REM  10 - *INFO
 1320 REM  11 - LIBRUN
 1330 REM  12 - *RENAME
 1340 REM 255 - Boot filing system - done by BBCMOS (should this be here?)
 1350 REM String has spaces skipped
 1360 LOCAL sh%,dir%,res%,ptr%:DIM dir% LOCAL 317
 1370 osc%=A% AND 256:A%=A% AND 255:IF A%=9:IF os%<>32:A%=5 :REM *EX not implemented for non-Windows
 1380 CASE A% OF
 1390   WHEN 5:                                             :REM *CAT
 1400   IF?Y%<>13:$Y%=$Y%+"."
 1410   IFFNwildcards:=0    :REM BB4W throws up a dialogue box with wildcards
 1420   out%=OPENOUT(tmp$+"!")
 1430   IF os%=32:OSCLI "OUTPUT "+STR$out%:OSCLI "DIR "+FNfn_convert($Y%,client%,host%)+"*.*":*OUTPUT 0
 1440   CLOSE#out%:out%=0
 1450   IF osc%=0:PROCWrByte(&40)     :IFdbg%:PRINTFNh0(&40,2)" ";
 1460   in%=OPENIN(tmp$+"!"):REPEAT:A%=BGET#in%
 1470     IF(host%AND64)=64 AND (client%AND96)=0 THEN
 1480       IF A%=46:A%=47 ELSE IF A%=47:A%=92 ELSE IF A%=92:A%=46
 1490     ENDIF
 1500     IF(host%AND96)=0 AND (client%AND64)=64 THEN
 1510       IF A%=46:A%=92 ELSE IF A%=47:A%=46 ELSE IF A%=92:A%=47
 1520     ENDIF
 1530     IF((host%AND96)<>0 AND (client%AND96)=0) OR ((host%AND96)=0 AND (client%AND96)<>0) THEN
 1540       IF A%=35 OR A%=63:A%=A%EOR28  :REM # ?
 1550       IF A%=36 OR A%=60:A%=A%EOR24  :REM < $
 1560       IF A%=61 OR A%=64:A%=A%EOR125 :REM = @
 1570       IF A%=62 OR A%=94:A%=A%EOR96  :REM > ^
 1580     ENDIF
 1590     IF((host%AND64)=64 AND (client%AND96)=32) OR ((host%AND96)=32 AND (client%AND64)=64) THEN
 1600       IF A%=47:A%=92 ELSE IF A%=92:A%=47
 1610     ENDIF
 1620     IF osc%:VDU A% ELSE PROCWrByte(A%)
 1630   UNTILEOF#in%:CLOSE#in%:in%=0
 1640   REM in%=OPENIN(tmp$+"!"):REPEAT
 1650   REM PROCPrText(FNfn_convert(GET$#in%,host%,client%)):IF osc%=0:PROCWrByte(10)
 1660   REM UNTILEOF#in%:CLOSE#in%:in%=0:PROCPrText(CHR$10)
 1670   =0
 1680   WHEN 9,10:                                          :REM *EX, *INFO
 1690   IFFNwildcards:=0    :REM BB4W throws up a dialogue box with wildcards
 1700   ex%=A%=9:res%=0
 1710   X%=Y%-1:REPEATX%=X%+1:UNTIL?X%<33:?X%=13          :REM Terminate at space or <cr>
 1720   fn$=$Y%:$Y%=FNfn_convert($Y%,client%,host%):X%=ctrl%
 1730   IF osc%=0:PROCWrByte(&40)     :IFdbg%:PRINTFNh0(&40,2);" ";
 1740   IF ex%:fn$=$Y%:IF fn$="":fn$="."
 1750   IF ex%:SYS "FindFirstFile",fn$+"\*.*",dir% TO sh% :REM Initialise search
 1760   REPEAT
 1770     IF ex%:IF sh%<>-1:$Y%=$$(dir%+44):IF $Y%="." OR $Y%=".." OR FNuc(RIGHT$($Y%,4))=".INF":$Y%=""
 1780     IF ex%:IF $Y%<>"":$Y%=fn$+"\"+$Y%
 1790     IF $Y%<>"":A%=FNfile_info($Y%):IF A%=0:ERROR 214,"File '"+fn$+"' not found"
 1800     A$=LEFT$(FNfn_convert(FNfn_leaf($Y%),host%,client%),12)
 1810     A$=A$+STRING$(13-LENA$," ")+FNh0(X%!2,8)+" "+FNh0(X%!6,8)
 1820     A$=A$+" "+FNh0(X%!10,8)+" "+FNf_access(X%?14,A%)+" "+FNf_date(X%!15)+CHR$10
 1830     IF $Y%<>"":PROCPrText(A$)
 1840     IF ex%:SYS "FindNextFile",sh%,dir% TO res%
 1850   UNTIL res%=0
 1860   IF ex%:SYS "FindClose",sh%
 1870   =0
 1880   WHEN 12                                             :REM *RENAME
 1890   IFFNwildcards:=0    :REM Can't do wildcarded rename
 1900   X%=Y%-1:REPEATX%=X%+1:UNTIL?X%<33:A%=?X%:?X%=13:fn1$=$Y% :REM fn1$ terminate at space or <cr>
 1910   ?X%=A%:X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<>32               :REM Skip spaces to fn2
 1920   A%=X%:X%=X%-1:REPEATX%=X%+1:UNTIL?X%<33:?X%=13:fn2$=$A%  :REM fn2$ terminate at space or <cr>
 1930   $Y%=FNf_name(FNfn_convert(fn1$,client%,host%)):X%=ctrl%
 1940   IF FNfile_info($Y%)=0:ERROR 214,"File '"+fn1$+"' not found"
 1950   A%=Y%+LEN$Y%+1:$A%=FNf_name(FNfn_convert(fn2$,client%,host%))
 1960   IF fn1$<>fn2$:IF FNfile_info($A%):ERROR 196,"File '"+fn2$+"' exists"
 1970   fn1$=$Y%:fn2$=$A%:A%?-1=32:OSCLI"Rename "+$Y%
 1980   fn1$=fn1$+s$+"inf"
 1990   fn2$=fn2$+s$+"inf"
 2000   REM fn1$=FNfn_noext(fn1$)+s$+"inf"
 2010   REM fn2$=FNfn_noext(fn2$)+s$+"inf"
 2020   LOCAL ERROR:ON ERROR LOCAL:=0
 2030   OSCLI "Rename "+fn1$+" "+fn2$                            :REM Rename any .inf file
 2040   =0
 2050   WHEN 3:                                               :REM *command
 2060   Y%=Y%-1:REPEAT:Y%=Y%+1:UNTIL?Y%<>32 AND ?Y%<>ASC"*" :REM Skip '*'s and ' 's
 2070   X%=Y%:IF?Y%=ASC"\":Y%=Y%+1                          :REM *\command -> *command
 2080   A%=INSTR($Y%+" "," "):B%=INSTR($Y%,"."):IFB%:IFA%>B%+1:A%=B%
 2090   A$=":"+FNuc(LEFT$($Y%,A%-1)):Y%=Y%+A%-1             :REM A$=command
 2100   WHILE ?Y%=32:Y%=Y%+1:ENDWHILE                       :REM Skip spaces
 2110   IFRIGHT$(A$,1)=".":A$=LEFT$(A$,LENA$-1) ELSE A$=A$+":"
 2120   C$=":ACCESS:DIR:FREE:GO:HELP:LOAD:LIB:SAVE:":A%=INSTR(C$,A$)
 2130   IF A%:=EVAL("FNhost_"+MID$(C$,A%+1,INSTR(C$,":",A%+1)-A%-1))
 2140   IF A%=0:IF?X%=ASC"\":ERROR 254,"Bad command"
 2150   Y%=X%:A%=3                                          :REM Fall through to */filename
 2160 ENDCASE
 2170 IF A%=2 OR A%=3 OR A%=4 THEN
 2180   :                                                   :REM */, *command, *RUN
 2190   IF ?Y%=13:=0                                        :REM Null string
 2200   X%=Y%:REPEAT:X%=X%+1:UNTIL?X%<33:?X%=13
 2210   fn$=$Y%:$Y%=FNfn_convert($Y%,client%,host%)
 2220   X%=ctrl%:X%!2=0:X%!6=0:?X%=FNfile_info($Y%)
 2230   IF ?X%=0:IF RIGHT$($Y%,4)<>s$+"bin":$Y%=$Y%+s$+"bin":?X%=FNfile_info($Y%)
 2240   IF ?X%=0:IFA%=3:ERROR 254,"Bad command"             :REM *command
 2250   IF ?X%=0:ERROR 214,"File '"+fn$+"' not found"       :REM */, *RUN
 2260   IF (X%!2 AND X%!6)=-1:ERROR 252,"Bad address"       :REM No load or exec address
 2270   A%=&40:ch%=FNmos_find
 2280   IF ch%=0:ERROR 214,"Couldn't open '"+fn$+"'"
 2290   Y%=FNhost_send(X%+2,ch%,EXT#ch%)                    :REM Send file
 2300   CLOSE#ch%:ch%=0
 2310   PROCWrCmd(&C0)                                                      :REM Send 'address' command
 2320   PROCWrByte(X%?9):PROCWrByte(X%?8):PROCWrByte(X%?7):PROCWrByte(X%?6) :REM Send start address
 2330   =&80                                                                :REM Return 'execute'
 2340 ENDIF
 2350 =0
 2360 :
 2370 DEFFNhost_send(addr%,hnd%,num%):LOCAL blk%,len%
 2380 REPEAT:len%=num%:IF len%>max%:len%=max%
 2390   blk%=len%:PROCWrCmd(&E0)                                                        :REM Send 'load' command
 2400   PROCWrByte(addr%?3):PROCWrByte(addr%?2):PROCWrByte(addr%?1):PROCWrByte(addr%?0) :REM Send address
 2410   IF blk%:IF NOT EOF#hnd%:REPEAT:PROCWrByte(BGET#hnd%):blk%=blk%-1:UNTIL blk%=0 OR EOF#hnd% :REM Send data
 2420   len%=len%-blk%:!addr%=!addr%+len%:num%=num%-len%                                :REM Update address, count
 2430   PROCWrCmd(&B0)                                                                  :REM End transfer
 2440   PROCSerFlush:blk%=INKEY(10):PROCSerFlush
 2450 UNTIL num%=0 OR EOF#hnd%
 2460 =num%
 2470 :
 2480 DEFFNhost_read(addr%,hnd%,num%):LOCAL blk%,len%
 2490 REPEAT:len%=num%:IF len%>max%:len%=max%
 2500   blk%=len%:PROCWrCmd(&F0)                                                        :REM Send 'save' command
 2510   PROCWrByte(addr%?3):PROCWrByte(addr%?2):PROCWrByte(addr%?1):PROCWrByte(addr%?0) :REM Send address
 2520   IF blk%:REPEAT:BPUT#hnd%,FNRdByte:blk%=blk%-1:UNTIL blk%=0                       :REM Read data
 2530   len%=len%-blk%:!addr%=!addr%+len%:num%=num%-len%                                :REM Update address, count
 2540   PROCWrCmd(&B0)                                                                  :REM End transfer
 2550   PROCSerFlush:blk%=INKEY(10):PROCSerFlush
 2560 UNTIL num%=0
 2570 =num%
 2580 :
 2590 :
 2600 REM Support *commands
 2610 REM =================
 2620 DEFFNhost_HELP
 2630 A%=FNuc(LEFT$($Y%,4))="TUBE" OR FNuc(LEFT$($Y%,4))="HOSTFS" OR ?Y%=ASC"."
 2640 IF osc%=0:PROCWrByte(&40)     :IFdbg%:PRINTFNh0(&40,2)
 2650 PROCPrText(CHR$10)
 2660 PROCPrText("TubeHost "+ver$+CHR$10)
 2670 IF A%=0:=0
 2680 REM PROCPrText("  ACCESS <afsp> <access>"+CHR$10)
 2690 PROCPrText("  CAT (<path>)"+CHR$10)
 2700 PROCPrText("  CREATE <fsp> <len>"+CHR$10)
 2710 PROCPrText("  CDIR <dir>"+CHR$10)
 2720 PROCPrText("  DELETE <fsp>"+CHR$10)
 2730 PROCPrText("  DIR <dir>"+CHR$10)
 2740 PROCPrText("  EX (<path>)"+CHR$10)
 2750 REM PROCPrText("  FREE"+CHR$10)
 2760 PROCPrText("  GO <addr>"+CHR$10)
 2770 PROCPrText("  HELP (<words>)"+CHR$10)
 2780 PROCPrText("  INFO <fsp>"+CHR$10)
 2790 PROCPrText("  LOAD <fsp> (<addr>)"+CHR$10)
 2800 PROCPrText("  OPT <x>,<y>"+CHR$10)
 2810 REM PROCPrText("  LIB <dir>"+CHR$10)
 2820 PROCPrText("  RUN <filename>"+CHR$10)
 2830 PROCPrText("  RENAME <oldfsp> <newfsp>"+CHR$10)
 2840 PROCPrText("  SAVE <fsp> <start> <end>|+<len> (<load> (<exec>))"+CHR$10)
 2850 =0
 2860 :
 2870 DEFFNhost_ACCESS:=0
 2880 DEFFNhost_FREE:=0
 2890 DEFFNhost_LIB:=0
 2900 :
 2910 DEFFNhost_DIR:IF?Y%=13:$Y%=FNfn_convert("&",0,client%)
 2920 fn$=$Y%:IFFNwildcards:=0
 2930 PROCf_dir(FNfn_convert($Y%,client%,host%)):INF%=inf%:=0
 2940 :
 2950 DEFFNhost_GO
 2960 IF dbg%:IF ?Y%=13:PRINT
 2970 IF ?Y%=13:PROC_RESET:=-1
 2980 !X%=EVAL("&"+$Y%)
 2990 PROCWrCmd(&C0)       :REM Set Address
 3000 PROCWrByte(X%?3)     :REM Send address
 3010 PROCWrByte(X%?2)
 3020 PROCWrByte(X%?1)
 3030 PROCWrByte(X%?0)
 3040 =&80
 3050 :
 3060 DEFFNhost_LOAD
 3070 X%=Y%:REPEAT:X%=X%+1:UNTIL?X%<33
 3080 A%=X%-1:REPEAT:A%=A%+1:UNTIL?A%<>32
 3090 ?X%=13:IF?A%=13:A%=-1 ELSE A%=EVAL("&"+FNuc($A%))
 3100 X%=ctrl%:X%!2=A%:X%?6=A%=-1
 3110 fn$=$Y%:$Y%=FNfn_convert($Y%,client%,host%)
 3120 A%=&FF:A%=FNhost_file:=0
 3130 :
 3140 DEFFNhost_SAVE
 3150 X%=Y%
 3160 X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<33:A%=X%:$A%=FNuc($A%) :REM A%=end of filename
 3170 X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<>32:S%=X%:?A%=13       :REM S%=start of START
 3180 X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<ASC"0":A%=X%           :REM A%=end of START
 3190 X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%=ASC"+" OR ?X%>ASC"/" OR ?X%=13
 3200 P%=?X%=ASC"+":IFP%:REPEAT:X%=X%+1:UNTIL?X%<>32
 3210 E%=X%:L%=S%:R%=S%                                      :REM E%=start of END/LEN
 3220 X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<ASC"0":?A%=13:A%=X%
 3230 X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<>32
 3240 IF?X%<>13:R%=X%:X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<ASC"0":?A%=13:A%=X%:X%=X%-1:REPEAT:X%=X%+1:UNTIL?X%<>32:IF?X%<>13:L%=X%
 3250 ?A%=13:X%=ctrl%:X%!2=EVAL("&"+$L%):X%!6=EVAL("&"+$R%)
 3260 X%!10=EVAL("&"+$S%):IFP%:X%!14=EVAL("&"+$E%)+X%!10 ELSE X%!14=EVAL("&"+$E%)
 3270 fn$=$Y%:$Y%=FNfn_convert($Y%,client%,host%)
 3280 A%=&00:A%=FNhost_file:=0
 3290 :
 3300 :
 3310 DEFPROCPrText(A$)
 3320 IFosc%:PRINTA$;CHR$13;:ENDPROC
 3330 $string%=A$:PROCWrString
 3340 ENDPROC
 3350 :
 3360 :
 3370 DEFFNfile_info(A$):LOCAL A%,L%,E%:L%=-1:E%=-1
 3380 :
 3390 REM Check for file header
 3400 in%=FNf_openin(A$):IF in% THEN
 3410   FOR A%=0 TO 127:IF EOF#in%:X%?A%=0 ELSE X%?A%=BGET#in%
 3420   NEXT A%:CLOSE#in%:in%=0    :REM Read header
 3430   IF X%!(X%?7)=&29432800 THEN
 3440     A%=X%?6                  :REM Code type
 3450     L%=&FFFF8000             :REM Sideways ROM
 3460     IF A%AND&40:L%=&8000     :REM Language ROM
 3470     IF A%AND&20 THEN
 3480       A%=X%?7:REPEATA%=A%+1:UNTILX%?A%=0 OR A%>123
 3490       IF A%<124:L%=X%!(A%+1) :REM Relocation address
 3500     ENDIF
 3510     E%=L%
 3520   ENDIF
 3530 ENDIF
 3540 :
 3550 REM Check for .inf file
 3560 I$=A$+s$+"inf":REM IF FNf_info(I$)=0:I$=FNfn_noext(A$)+s$+"inf"
 3570 LOCAL ERROR:ON ERROR LOCAL:=FNf_info(A$)
 3580 IF FNf_info(I$)=1 AND X%!10<120 THEN
 3590   A%=X%!10:OSCLI"LOAD "+I$+" "+STR$~X%:X%!A%=&D20:A%=X%
 3600   REPEAT:A%=A%+1:UNTIL?A%=32:REPEATA%=A%+1:UNTIL?A%<>32:L%=EVAL("&"+$A%)
 3610   REPEAT:A%=A%+1:UNTIL?A%=32:REPEATA%=A%+1:UNTIL?A%<>32:E%=EVAL("&"+$A%)
 3620 ENDIF
 3630 X%!2=L%:X%!6=E%
 3640 =FNf_info(A$):REM Fills in Size and Attr, on RISCOS also fills in Load and Exec
 3650 :
 3660 :
 3670 REM Convert filenames
 3680 REM =================
 3690 DEFFNfn_convert(A$,from%,to%)
 3700 inf%=INF%
 3710 IFFNuc(LEFT$(A$,5))=":INF:":inf%=TRUE:A$=MID$(A$,6)
 3720 IFFNuc(LEFT$(A$,7))=":NOINF:":inf%=FALSE:A$=MID$(A$,8)
 3730 IFfrom%=to% OR A$="":=A$
 3740 REM x00xxxxx - BBC
 3750 REM x01xxxxx - Unix
 3760 REM x1xxxxxx - DOS
 3770 IF(from%AND96)=0 :IF(to%AND64)=64:=FNfn_bbctodos(A$)
 3780 IF(from%AND96)=0 :IF(to%AND96)=32:=FNfn_bbctounix(A$)
 3790 IF(from%AND64)=64:IF(to%AND96)=0 :=FNfn_dostobbc(A$)
 3800 IF(from%AND64)=64:IF(to%AND96)=32:=FNfn_dostounix(A$)
 3810 IF(from%AND96)=32:IF(to%AND96)=0 :=FNfn_unixtobbc(A$)
 3820 IF(from%AND96)=32:IF(to%AND64)=64:=FNfn_unixtodos(A$)
 3830 =A$
 3840 :
 3850 REM REM Support functions from BLib.File, BLib.FInfo
 3860 REM REM ============================================
 3870 :
 3880 DEFFNf_access(A%,T%):LOCALA$:IF(A%AND128):A$="P"
 3890 IF(T%AND2):A$=A$+"D"
 3900 IF(A%AND8):A$=A$+"L"
 3910 IF(A%AND2):A$=A$+"W"
 3920 IF(A%AND1):A$=A$+"R"
 3930 IF(A%AND5)=4:A$=A$+"E"
 3940 A$=A$+"/":IF(A%AND32):A$=A$+"w"
 3950 IF(A%AND16):A$=A$+"r"
 3960 IF(A%AND80)=64:A$=A$+"e"
 3970 =LEFT$(A$+"        ",8)
 3980 :
 3990 DEFFNf_date(A%)=FNd0(A%AND31,2)+"/"+FNd0((A%AND&F00)DIV256,2)+"/"+FNd0((A%AND&F000)DIV4096+(A%AND&E0)/2+1981,4)
   10 REM > BLib.Filename 1.01 25-Jan-2011
   20 REM Convert between BBC, DOS/Windows and UNIX/ZIP/URL filenames
   30 REM!Keep FNfn_frombbc(), FNfn_tobbc(), FNfn_unixtodos(), FNfn_dostounix()
   50 :
   60 :
   70 DEFFNfn_bbctodos(A$):LOCALA%,B%,H%:H%=32:DEF:FNfn_frombbc()
   80 DEFFNfn_bbctounix(A$):LOCALA%,B%,H%:H%=8:DEF:FNfn_frombbc()
   90 DEFFNfn_frombbc(A$):LOCALA%,B%,H%:H%=os%
  100 IFA$=""OR(H%AND-24)=0:=A$
  110 IFLEFT$(A$,1)=":":A%=INSTR(A$+".","."):A$=MID$(A$,2,A%-2)+":"+MID$(A$,A%-(MID$(A$,A%+1,1)="$"))
  120 A%=0:REPEAT:A%=A%+1:B%=ASCMID$(A$,A%,1)
  130   IFB%=35ORB%=63:A$=LEFT$(A$,A%-1)+CHR$(B%EOR28)+MID$(A$,A%+1) :REM # ?
  140   IFB%=61ORB%=64:A$=LEFT$(A$,A%-1)+CHR$(B%EOR125)+MID$(A$,A%+1):REM = @
  150   IFB%=36ORB%=60:A$=LEFT$(A$,A%-1)+CHR$(B%EOR24)+MID$(A$,A%+1) :REM < $
  160   IFB%=62ORB%=94:A$=LEFT$(A$,A%-1)+CHR$(B%EOR96)+MID$(A$,A%+1) :REM > ^
  170   IFB%=ASC"$":IF(H%AND-24)=8:A$=LEFT$(A$,A%-1)+"/"+MID$(A$,A%+1-(A%<>LENA$))
  180   IFB%=ASC"$":IF(H%AND-32):A$=LEFT$(A$,A%-1)+"\"+MID$(A$,A%+1-(A%<>LENA$))
  190   IFB%=ASC"@":A$=LEFT$(A$,A%-1)+"."+MID$(A$,A%+1)
  200   IFB%=ASC"^":A$=LEFT$(A$,A%-1)+".."+MID$(A$,A%+1):A%=A%+1
  210   IFB%=ASC"/":A$=LEFT$(A$,A%-1)+"."+MID$(A$,A%+1)
  220   IFB%=ASC".":IF(H%AND-24)=8:A$=LEFT$(A$,A%-1)+"/"+MID$(A$,A%+1)
  230   IFB%=ASC".":IF(H%AND-32):A$=LEFT$(A$,A%-1)+"\"+MID$(A$,A%+1)
  240   IFB%=ASC"\":IF(H%AND-32):A$=LEFT$(A$,A%-1)+"/"+MID$(A$,A%+1)
  250   REM IFB%=ASC"&":A$=LEFT$(A$,A%-1)+"%HOME%"+MID$(A$,A%+1)
  260 UNTILA%>LENA$:=A$
  270 :
  280 DEFFNfn_fromunix(A$):IF(os%AND-24)=8:=A$ ELSE IF(os%AND-24)=0:=FNfn_unixtobbc(A$):DEF:FNfn_dostounix()
  290 DEFFNfn_fromdos(A$):IF(os%AND-32):=A$ ELSE IF(os%AND-24)=0:=FNfn_dostobbc(A$):DEF:FNfn_dostounix()
  300 DEFFNfn_tounix(A$):IF(os%AND-24)=8:=A$ ELSE IF(os%AND-24)=0:=FNfn_bbctounix(A$):DEF:FNfn_dostounix()
  310 DEFFNfn_todos(A$):IF(os%AND-32):=A$ ELSE IF(os%AND-24)=0:=FNfn_bbctodos(A$):DEF:FNfn_dostounix()
  320 DEFFNfn_unixtodos(A$):DEF:FNfn_dostounix()
  330 DEFFNfn_dostounix(A$)
  340 IFINSTR(A$,"/")+INSTR(A$,"\")=0:=A$
  350 LOCAL A%,B%:A%=0:REPEAT:A%=A%+1:B%=ASCMID$(A$,A%,1):IFB%=ASC"/"ORB%=ASC"\":A$=LEFT$(A$,A%-1)+CHR$(B%EOR115)+MID$(A$,A%+1)
  360 UNTILA%>=LENA$:=A$
  370 :
  380 DEFFNfn_dostobbc(A$):LOCALA%,B%,H%:H%=32:DEF:FNfn_tobbc()
  390 DEFFNfn_unixtobbc(A$):LOCALA%,B%,H%:H%=8:DEF:FNfn_tobbc()
  400 DEFFNfn_tobbc(A$):LOCALA%,B%,H%:H%=os%
  410 IFA$=""OR(H%AND-24)=0:=A$
  420 IFA$="/":IF(H%AND-24)=8:="$"
  430 IFA$="\":IF(H%AND-32):="$"
  440 A%=0:REPEAT:A%=A%+1:B%=ASCMID$(A$,A%,1)
  450   IFB%=35ORB%=63:A$=LEFT$(A$,A%-1)+CHR$(B%EOR28)+MID$(A$,A%+1):REM # ?
  460   IFMID$(A$,A%,2)="..":A$=LEFT$(A$,A%-1)+"^"+MID$(A$,A%+2):A%=A%-1:B%=0
  470   IFMID$(A$,A%)=".":A$=LEFT$(A$,A%-1)+"@":B%=0
  480   IFMID$(A$,A%,2)="./":IF(H%AND-24)=8:A$=LEFT$(A$,A%-1)+"@"+MID$(A$,A%+1):B%=0
  490   IFMID$(A$,A%,2)=".\":IF(H%AND-32):A$=LEFT$(A$,A%-1)+"@"+MID$(A$,A%+1):B%=0
  500   REM IFMID$(A$,A%,6)="%HOME%":A$=LEFT$(A$,A%-1)+"&"+MID$(A$,A%+6):B%=0
  510   IFB%=ASC".":A$=LEFT$(A$,A%-1)+"/"+MID$(A$,A%+1)
  520   IFB%=ASC"\":IF(H%AND-32):A$=LEFT$(A$,A%-1)+"."+MID$(A$,A%+1)
  530   IFB%=ASC"/":IF(H%AND-32):A$=LEFT$(A$,A%-1)+"\"+MID$(A$,A%+1)
  540   IFB%=ASC"/":IF(H%AND-24)=8:A$=LEFT$(A$,A%-1)+"."+MID$(A$,A%+1)
  550   IFB%=ASC":":A$=":"+LEFT$(A$,A%-1)+".$"+MID$(A$,A%+1):A%=A%+2
  560 UNTILA%>LENA$:=A$
  570 :
   10 REM > BLib.File 1.02 J.G.Harston
   20 REM Platform-independant filing functions
   30 :
   40 :
   50 REM PROCf_init - initialise filing-system variables
   60 REM -----------------------------------------------
   70 DEFPROCf_init
   80 d$=".":s$="/":IF(os%AND-24):d$="/":s$=".":IF(os%AND-32):d$="\"
   90 ENDPROC
  100 :
  110 :
  120 REM FNfn_root(path$) - return root of path
  130 REM --------------------------------------
  140 DEFFNfn_root(A$):LOCALA%,B%:IFLEFT$(A$,2)=d$+d$:=LEFT$(A$,INSTR(A$+d$,d$,3)-1)
  150 A%=INSTR(A$,":",2):B%=INSTR(A$,d$,A%)AND(A%<>0):IFB%:A%=B%-1
  160 IFA%:=LEFT$(A$,A%)
  170 IFINSTR(":$%&^@\-",LEFT$(A$,1)):=LEFT$(A$,INSTR(A$+d$,d$)-1)
  180 =""
  190 :
  200 :
  210 REM FNfn_leaf(path$) - returns leafname
  220 REM -----------------------------------
  230 DEFFNfn_leaf(A$):LOCALA%,B%:A%=INSTR(A$,":",2)
  240 REPEATB%=A%:A%=INSTR(A$,d$,A%+1):UNTILA%=0:=MID$(A$,B%+1)
  250 :
  260 :
  270 REM FNfn_path(path$) - returns pathname
  280 REM -----------------------------------
  290 DEFFNfn_path(A$):LOCALA%,B%:A%=INSTR(A$,":",2)
  300 REPEATB%=A%:A%=INSTR(A$,d$,A%+1):UNTILA%=0:=LEFT$(A$,B%)
  310 :
  320 :
  330 REM FNfn_ext(path$) - returns leafname's extension
  340 REM ----------------------------------------------
  350 DEFFNfn_ext(A$):IFINSTR(A$,s$)=0:=""
  360 LOCAL A%:A%=LENA$+1:REPEATA%=A%-1:UNTILINSTR(":"+d$+s$,MID$(A$,A%,1))
  370 IFMID$(A$,A%,1)=s$:=MID$(A$,A%) ELSE =""
  380 :
  390 :
  400 REM FNfn_noext(path$) - returns path with no extension
  410 REM --------------------------------------------------
  420 DEFFNfn_noext(A$):IFINSTR(A$,s$)=0:=A$
  430 LOCAL A%:A%=LENA$+1:REPEATA%=A%-1:UNTILINSTR(":"+s$+d$,MID$(A$,A%,1))
  440 IFMID$(A$,A%,1)=s$:=LEFT$(A$,A%-1) ELSE =A$
  450 :
  460 :
  470 REM FNfn_fullpath(path$, file$) - return full absolute path
  480 REM -------------------------------------------------------
  490 DEFFNfn_fullpath(P$,A$)
  500 IFINSTR("$%&^\-",LEFT$(A$,1)):IF(os%AND-24)=0:=A$
  510 IFINSTR(A$,":")ORLEFT$(A$,1)=d$:=A$
  520 =P$+LEFT$(d$,RIGHT$(P$,1)<>d$)+A$
  530 :
  540 :
  550 REM PROCf_gbpb() - multiple get-bytes or put-bytes call
  560 REM ---------------------------------------------------
  570 DEFPROCf_gbpb(A%,chn%,addr%,num%,ptr%)
  580 ?X%=chn%:X%!1=addr%:X%!5=num%:X%!9=ptr%:IFPAGE<&FFFF:CALL&FFD1:ENDPROC
  590 IFA%=1ORA%=3:PTR#?X%=X%!9
  600 REPEAT:IFA%=1ORA%=2:BPUT#?X%,?X%!1 ELSE IFA%=3ORA%=4:?X%!1=BGET#?X%
  610 X%!1=X%!1+1:X%!5=X%!5-1:UNTIL(EOF#?X% AND A%>2)OR X%!5<1:ENDPROC
  620 :
  630 :
  640 REM FNfs - get current filing system number
  650 REM ---------------------------------------
  660 DEFFNfs:IFPAGE<&FFFF:LOCALA%,Y%,E%:=(USR&FFDA)AND&FF
  670 =29
  680 :
  690 :
  700 REM PROCf_dir(dir$) - select directory
  710 REM PROCf_delete(object$) - delete object
  720 REM -------------------------------------
  730 DEFPROCf_dir(A$):IF(os%AND-24):A$="cd "+A$ ELSE A$="dir "+FNf_name(A$)
  740 DEFPROCf_delete(A$):IF(os%AND-24)=8:A$="rm "+A$ ELSE A$="delete "+FNf_name(A$)
  750 OSCLIA$:ENDPROC
  760 :
  770 :
  780 REM PROCf_cdir(dir$) - create a directory only if it doesn't exist
  790 REM --------------------------------------------------------------
  800 DEFPROCf_cdir(A$):IF(os%AND-24):A$="mkdir "+A$ ELSE A$="cdir "+A$
  810 IF FALSE THEN
  820   OSCLIA$:ENDPROC
  830 ENDIF
  840 LOCAL ERROR:ON ERROR LOCAL:ENDPROC
  850 OSCLIA$:ENDPROC
  860 :
  870 :
  880 REM FNf_name(path$) - ensure no extension if no extension specified
  890 REM ---------------------------------------------------------------
  900 DEFFNf_name(A$)
  910 IFos%>31:LOCALA%,B%:REPEATB%=A%:A%=INSTR(A$,"\",A%+1):UNTILA%=0:IFINSTR(A$,".",B%)=0:A$=A$+"."
  920 =A$
  930 :
  940 :
  950 REM FNf_openin(file$) - open an input file ensuring no extension
  960 REM ------------------------------------------------------------
  970 DEFFNf_openin(A$)=OPENIN(FNf_name(A$))
  980 :
  990 :
 1000 REM FNf_openup(file$) - open an update file ensuring no extension
 1010 REM -------------------------------------------------------------
 1020 DEFFNf_openup(A$)=OPENUP(FNf_name(A$))
 1030 :
 1040 :
 1050 REM FNf_openout(file$) - open an output file ensuring no extension
 1060 REM --------------------------------------------------------------
 1070 DEFFNf_openout(A$)=OPENOUT(FNf_name(A$))
 1080 :
 1090 :
 1100 REM FNf_info(object$) - returns file type, file info in X%!...
 1110 REM ----------------------------------------------------------
 1120 DEFFNf_info(A$):IFPAGE<&FFFF:$name%=A$:?X%=name%:X%?1=name%DIV256:A%=5:=(USR&FFDD)AND&FF
 1130 IFos%<>32:=0
 1140 LOCALdir%,sh%:DIMdir%LOCAL319
 1150 SYS"FindFirstFile",FNf_name(A$),dir%TOsh%
 1160 IFsh%=-1:SYS"GetFileAttributes",FNf_name(A$)TOsh%:=((sh%AND16)DIV16+1)AND(sh%<>-1)
      REM Eh? Shouldn't exit there
 1170 SYS"FindClose",sh%:X%!10=dir%!32:IFdir%!28:X%!10=-1
 1180 X%?14=(?dir%AND1)*8+(&33 AND ((?dir%AND16)=0))
 1190 SYS"FileTimeToSystemTime",dir%+20,X%+16
 1200 A%=(X%!16AND&FFFF)-1981:X%?15=X%?22+(A%AND&70)*2:X%?16=X%?18+A%*16
 1210 X%?17=X%?24:X%?18=X%?26:X%?19=X%?28:=(?dir%AND16)DIV16+1
 1220 :
 1230 :
 1240 REM PROCf_settype(object%, type%) - set file type
 1250 REM ---------------------------------------------
 1260 DEFPROCf_settype(A$,A%):IFos%=32:ENDPROC
 1270 IFPAGE<&FFFF:IFFNf_info(A$):X%?3=A%:X%?4=(A%DIV256)OR&F0:X%?5=&FF:A%=1:CALL&FFDD
 1280 ENDPROC
 1290 :
 1300 :
   10 REM > BLib.Win.BBCKbd 0.11
   20 REM Returns BBC-style keypress values
   30 :
   40 DEFFNkbd_GET:LOCAL A%:REPEATA%=FNkbd_INKEY(100):UNTILA%<>-1:=A%
   50 :
   60 DEFFNkbd_INKEY(T%):LOCAL A%,S%:IFT%<0:=INKEYT%
   70 A%=INKEY(T%):S%=(INKEY-1AND16)+(INKEY-2AND32)+(INKEY-3AND48)
   80 IFA%=8ORA%=&87:A%=&7F
   90 IFA%<&80:=A%
  100 A%=A%-&10:IFA%>&7F:IF(A%AND15)>9:A%=A%EOR&40:IFS%=0:=A%
  110 IFS%:IFA%>&CA:IFA%<&D0:=EVAL("&"+MID$("09E8ABAFAE",A%*2-405,2))
  120 IFA%<&80:=EVAL("&"+MID$("8C8DC88B9F9ECD7F8C8D8E8FCECFCECF",A%*2-223,2))EORS%
  130 =A%
   10 REM > BLib.String 1.00 09Aug1998
   20 :
   30 REM String Manipulation Functions
   40 REM =============================
   50 :
   60 REM FNs() - strip spaces from start and end of string
   70 REM -------------------------------------------------
   80 DEFFNs(A$):IFLEFT$(A$,1)=" ":REPEATA$=MID$(A$,2):UNTILLEFT$(A$,1)<>" "
   90 IFRIGHT$(A$,1)=" ":REPEATA$=LEFT$(A$,LENA$-1):UNTILRIGHT$(A$,1)<>" "
  100 =A$
  110 :
  120 REM FNuc() - convert string to upper case
  130 REM -------------------------------------
  140 DEFFNuc(A$):LOCAL B$:IFA$="":=""
  150 REPEATB$=B$+CHR$(ASCA$AND((A$<"@")OR&DF)):A$=MID$(A$,2):UNTILA$="":=B$
  160 :
  170 REM FNlc() - convert string to lower case
  180 REM -------------------------------------
  190 DEFFNlc(A$):LOCAL B$:IFA$="":=""
  200 REPEATB$=B$+CHR$(ASCA$OR((A$<"_")AND&20)):A$=MID$(A$,2):UNTILA$="":=B$
  210 :
   10 REM > BLib.Variable
   20 REM Variable assignment functions
   30 REM =============================
   40 REM!Keep FNVar_S(), FNVar_I(), FNVar_B(), FNVar_F(), FNVar_R()
   50 |!Keep FNVar_S(); FNVar_I(); FNVar_B(); FNVar_F(); FNVar_R()
   60 :
   70 REM Var_Assign(Var$,Val$)
   80 REM Sets the variable in 'Var$' to the value in 'Val$'
   90 REM --------------------------------------------------
  100 :
  110 DEFPROCVar_Assign(Var$,Val$)
  120 IF RIGHT$(Var$,1)="$":A%=EVAL("FNVar_S("+Var$+","+Val$+")"):ENDPROC
  130 IF RIGHT$(Var$,1)="%":A%=EVAL("FNVar_I("+Var$+","+Val$+")"):ENDPROC
  140 IF RIGHT$(Var$,1)="&":A%=EVAL("FNVar_B("+Var$+","+Val$+")"):ENDPROC
  150 IF RIGHT$(Var$,1)="#":A%=EVAL("FNVar_F("+Var$+","+Val$+")"):ENDPROC
  160 A%=EVAL("FNVar_R("+Var$+","+Val$+")"):ENDPROC
  170 ENDPROC
  180 :
  190 DEFFNVar_S(RETURN a$,b$):a$=b$:=0
  200 DEFFNVar_I(RETURN a%,b%):a%=b%:=0
  210 DEFFNVar_B(RETURN a&,b&):a&=b&:=0
  220 DEFFNVar_F(RETURN a#,b#):a#=b#:=0
  230 DEFFNVar_R(RETURN a, b ):a =b :=0
  240 :
   10 REM > BLib.Win.Reg v1.04 20Sep2010 J.G.Harston
   20 REM v1.00 18May2006 Initial version
   30 REM v1.01 09Feb2007 Finds required buffer size
   40 REM v1.02 13Aug2007 B% localised
   50 REM v1.03 25Apr2009 Michael Hutton: uses $$B%, added PROCReg_Del()
   60 REM v1.04 20Sep2010 Shortened variable names
   70 :
   80 REM Simple Windows registry access functions
   90 REM ========================================
  100 :
  110 REM Reg_Rd(Key$) - Read a user registry entry
  120 REM -----------------------------------------
  130 :
  140 DEFFNReg_Rd(K$):LOCAL K%,R%,L%,T%,I$,V$,B%
  150 I$=K$:REPEAT:K%=INSTR(I$,"\"):IFK%:I$=MID$(I$,K%+1)
  160 UNTILK%=0:K$=LEFT$(K$,LENK$-LENI$-1)
  170 SYS "RegOpenKeyEx",&80000001,K$,0,&20001,^K% TO R%
  180 IF R%=0 THEN
  190   SYS "RegQueryValueEx",K%,I$,0,^T%,0,^L%:DIM B% LOCAL L%
  200   SYS "RegQueryValueEx",K%,I$,0,^T%,B%,^L% TO R%
  210   IF R%=0:B%?(L%-1)=0:V$=$$B%
  220   SYS "RegCloseKey",K%
  230 ENDIF
  240 =V$
  250 :
  260 :
  270 REM Reg_Wr(Key$,Value$) - Write a user registry entry
  280 REM -------------------------------------------------
  290 :
  300 DEFPROCReg_Wr(K$,V$):LOCAL K%,D%,R%,I$
  310 I$=K$:REPEAT:K%=INSTR(I$,"\"):IFK%:I$=MID$(I$,K%+1)
  320 UNTILK%=0:K$=LEFT$(K$,LENK$-LENI$-1)
  330 SYS "RegCreateKeyEx",&80000001,K$,0,"",0,&F003F,0,^K%,^D% TO R%
  340 IF R%=0 THEN
  350   SYS "RegSetValueEx",K%,I$,0,1,V$,LENV$+1
  360   SYS "RegCloseKey",K%
  370 ENDIF
  380 ENDPROC
  390 :
  400 :
  410 REM Reg_Del(Key$) - delete a user registry entry
  420 REM --------------------------------------------
  430 :
  440 DEFPROCReg_Del(K$):LOCAL K%,R%
  450 SYS "RegCreateKeyEx",&80000001,K$,0,"",0,&F003F,0,^K%,0 TO R%
  460 IF R%=0 THEN
  470   SYS "RegDeleteTree",K%,0 TO R%
  480   SYS "RegCloseKey",K%
  490 ENDIF
  500 ENDPROC