9 HADFS programming examples

This chapter gives some examples of calling various HADFS functions, and provides some useful Basic functions and procedures for incorporating into programs. Some of the functions need some memory reserved and pointed to by ctrl% and data%. Where these are shown, these are recommended minimum values to use. In the listings given, no line numbers are shown, as none are needed. Where a line is too long to fit on the page, it is continued on the next line indented. Unless otherwise stated, these functions will work on all the following varieties of BBC BASIC:

    6502 BBCBASIC on I/O processor, Tube, Archimedes Host and Tube emulators
    32000 BBCBASIC on the 32016 second processor
    ARM BBCBASIC V on the Archimedes
    BBCBASIC(Z80) using BBC I/O on Z80 Tube or Archimedes Tube emulator 

Some will also work on BBCBASIC(86) for PCs and BBCBASIC(X) for UNIX. The examples are all supplied in the Extras directory on the System Startup disk. They are all in the file Extras.ProgLib, unless otherwise stated.

General functions
OSBYTE Calls
FNbyte returns the single byte in the X register for the named call. FNfx returns the two-byte value held in XY.

    DEFFNbyte(A%,X%,Y%)=(USR&FFF4 AND &FF00)DIV256
    DEFFNfx(A%,X%):LOCAL Y%:Y%=X%DIV256:=(USR&FFF4 AND &FFFF00)DIV256

As an example, FNfx(133,mode%) returns HIMEM for the specified mode, andFNbyte(135,0,0) returns the character under the cursor. FNfx(135,0)DIV256 returns the current screen mode number. 

OSFILE Calls
This call performs an OSFILE function, returning the object type from the A register.

    DIM ctrl% 20,name% 80

    DEFFNfile(A$,A%):LOCAL X%,Y%:X%=ctrl%:Y%=X%DIV256:$name%=A$
    ?X%=name%:X%?1=name%DIV256:A%=USR&FFDD AND &FF
    load%=X%!2:exec%=X%!6:len%=X%!10:attr%=X%!14:=A%

FNfile("FRED",5) would read the catalogue information for the file FRED. The catalogue information would be put in the variables load%, exec%, len% and attr%.

OSGBPB Calls
These procedures and functions call OSGBPB. PROCgbpb makes a general call. FNgbpb5 returns the name of the current disk. FNgbpb67 should be called with A% set to 6 or 7 to read the name of the current directory or library. FNgbpb5 and FNgbpb67 can be combined together as FNgbpbN. FNgbpb8 reads a filename from the current directory, returning a null string if at the end. The updated pointer is returned in the control block.

    DIM ctrl% 20,data% 20

    DEFPROCgbpb(A%,chn%,addr%,num%,ptr%):LOCAL X%,Y%:X%=ctrl%:Y%=X%DIV256
    ?X%=chn%:X%!1=addr%:X%!5=num%:X%!9=ptr%:CALL &FFD1:ENDPROC

    DEFFNgbpb5:LOCAL A%,X%,Y%:X%=ctrl%:Y%=X%DIV256:A%=5
    X%!1=data%:CALL &FFD1:X%=data%:X%?(1+?X%)=13:=$(X%+1)

    DEFFNgbpb67(A%):LOCAL X%,Y%:X%=ctrl%:Y%=X%DIV256
    X%!1=data%:CALL &FFD1:X%=data%+1+?data%:X%?(1+?X%)=13:=$(X%+1)

    DEFFNgbpbN(A%):LOCAL X%,Y%:X%=ctrl%:Y%=X%DIV256
    X%!1=data%:CALL &FFD1:X%=data%+((1+?data%)AND(A%>5))
    X%?(1+?X%)=13:=$(X%+1)

    DEFFNgbpb8(ptr%):LOCAL A%,X%,Y%:X%=ctrl%:Y%=X%DIV256:A%=8
    X%!1=data%:X%!5=1:X%!9=ptr%:CALL &FFD1:IFX%!5=1:=""
    X%=data%:X%?(1+?X%)=13:=$(X%+1)

After calling FNgbpb67() or FNgbpbN() with A%=6 or 7 using

	ctrl%?(1+?ctrl%):Drive$=$(ctrl%+1)

will set Drive$ to the ASCII drive identifier for that directory.

OSARGS Calls
These two functions allow you to make OSARGS calls. FNargs0 returns the value from the A register, ignoring the zero page data. FNargs sets and returns data passed in zero page. When reading a value, the !&70 parameter is ignored, so you can set it to zero on entry. When writing a value, some calls return a value, often the same as the value just written.

    DEFFNargs0(A%,Y%):LOCAL E%:E%=Y%:=USR &FFDA AND &FF

    DEFFNargs(A%,Y%,!&70):LOCAL X%,E%:IF?(TOP-3)=0:E%=Y%:Y%=0
    X%=&70:CALL &FFDA:=!X%

    used%=FNargs(4,0,0):REM Get disk space used
    free%=FNargs(5,0,0):REM Get disk free space
    size%=free%+used%:REM Find disk size
    dummy%=FNargs(1,chn%,num%):REM identical to PTR#chn%=num%

FNfs returns the current filing system number. A list of these is in Chapter 4. The LOCAL sets A%, Y% and E% to zero.

    DEFFNfs:LOCAL A%,Y%,E%:=USR&FFDA AND &FF

    REM Shorter version:
    DEFFNfs=FNargs0(0,0)

Time and Date
Basic IV (on the Master) and V (on the Archimedes) have a =TIME$ function to read the real-time clock (RTC). HADFS provides a RTC function, but Basic II (on the B/B+) does not have a function to read it. Also, the 6502 second processor only reads 24 bytes instead of 25, and so loses the terminator. The following function will read the RTC regardless of what machine it is running on, and will correct for years after 1996 and 1999, or return a null string if no time is available. The 'Acorn Era' is defined as starting in 1981, so the century range is 1981-2080.

    DIM ctrl% 30

    DEFFNtime:LOCALX%,Y%,A%:X%=ctrl%
    Y%=X%DIV256:?X%=0:A%=14:CALL&FFF1:IF?X%=0:=""
    X%?25=13:X%?15=13:A%=VALMID$($X%,5,2)DIV32
    $(X%+11)=STR$(VAL$(X%+11)+16*A%-100*(VAL$(X%+11)<1981))
    IFA%:X%?6=13:$(X%+4)=RIGHT$("0"+STR$(VAL$(X%+4)-32),2):X%?6=32
    X%?15=46:=$X%

The function FNDayOfWeek returns the day of the week for the given date, month and year. If the year if less than 100, it is assumed to be 1900 to 1999.

    DEFFNDayOfWeek(d%,m%,y%):IFy%<100:y%=y%+1900
    y%=y%MOD400:=(y%*365.25+m%*30+d%+VALMID$("120112234455",m%,1)+
       ((y%MOD4)=0)-((y%-1)DIV100)-(m%>2 AND((y%MOD4)=0 AND
       (y%MOD100)<>0 ORy%=0))+3)MOD7+1
    REM Returns 1..7 for Sun..Sat

The function FNdate returns the current date in the format &yyyymmdd. So, if it is called with T%=FNdate, then for example, (T%AND&FFFF0000)DIV65536 would give the current year.

    DEFFNdate:LOCALX%,Y%,A%
    X%=ctrl%:Y%=X%DIV256:A%=14:!X%=1:CALL&FFF1:IF!X%=1:=0
    ?X%=(VALSTR$~?X%+(((VALSTR$~X%?2)DIV2)AND&F0))MOD100
    =(VALSTR$~X%?2 AND31)+256*VALSTR$~X%?1+65536*(1900+?X%-100*(?X%<80))

PROCConvDate converts the given time and date to a centi-second count from 00:00:00 on 1st Jan 1900, as used for Archimedes time-stamps. Again, if the year is less than 100, it is assumed to be 1900 to 1999.

    DEFPROCConvDate(mem%,d%,m%,y%,hr%,mn%,sc%,cs%):IFy%<100:y%=y%+1900
    y%=y%MOD400:d%=y%*365.25+m%*30+d%+VALMID$("120112234455",m%,1)+
	((y%MOD4)=0)-((y%-1)DIV100)-(m%>2AND((y%MOD4)=0AND(y%MOD100)<>0 OR
	y%=0))+36493:IFd%>146096:d%=d%-146097
    d%=d%*&41EB:mem%!1=d%+d%:d%=((hr%*60+mn%)*60+sc%)*100+cs%
    ?mem%=d%:mem%!1=mem%!1+d%DIV256:ENDPROC

On entry: mem%->five bytes of memory
	   day, month, year of the date
	   hours, minutes, seconds, centiseconds of the time On exit:  mem% to mem%+4 containes five-byte
	   centisecond time since 00:00:00 on 1-Jan-1900.

The d%+d% is deliberate. It prevents a 'Too big' error happening when &7Fxxxxxxxx overflows to &80xxxxxxxx. The five-byte time range ends at 06:57:57.75, 04/06/2248. The five-byte time can be used to set the load and exec addresses on a file with OSFILE with

		PROCConvDate(ctrl%+6,.......)
		ctrl%!2=ctrl%?10 OR type%*&100 OR &FFF00000

  where ctrl% is an OSFILE control block, or:

		PROCConvDate(mem%,.......)
		exec%=!mem%:load%=mem%?4 OR type%*&100 OR &FFF00000

  to get load and exec addresses separately.

FNDaysSince returns the number of days since a past date. On entry td%, tm% and ty% should be set to today's date, month and year and pd%, pm% and py% should be set to the past date, month and year.

    DIM ctrl% 5

    DEFFNDaysSince(td%,tm%,ty%,pd%,pm%,py%):LOCAL past%
    PROCConvDate(ctrl%,pd%,pm%,py%,0,0,0,0):past%=ctrl%!1
    PROCConvDate(ctrl%,td%,tm%,ty%,0,0,0,0):=(ctrl%!1-past%)DIV&83D6

Number output
The function FNhex converts the number A% to a N%-digit hexadecimal string padded with zeros. FNdec does the same, returning a decimal string. FNDec returns a N%-digit decimal string, padded with spaces.

    DEFFNhex(A%,N%)=RIGHT$("0000000"+STR$~A%,N%)
    DEFFNdec(A%,N%)=RIGHT$("00000000"+STR$A%,N%)
    DEFFNDec(A%,N%)=RIGHT$("         "+STR$A%,N%)

FNIntToTxt converts the number A% to the English text string representing that number.

    DEFFNIntToTxt(A%):LOCAL A$,B$
    IF A%<0 AND A%<>-A%:="minus "+FNIntToTxt(-A%) ELSE IF A%<0:
	="minus two thousand "+FNIntToTxt(147483648)
    IF A%=0:="zero"
    IF A%>999999:A$=FNIntToTxt(A% DIV 1000000)+" million":
	A%=A% MOD 1000000
    IF A%>999 AND A$<>"":A$=A$+" "
    IF A%>999:A$=A$+FNIntToTxt(A% DIV 1000)+" thousand":A%=A% MOD 1000
    IF A%>99 AND A$<>"":A$=A$+" "
    IF A%>99:A$=A$+FNIntToTxt(A% DIV 100)+" hundred":A%=A% MOD 100
    IF A%=0:=A$ ELSE IF A$<>"":A$=A$+" and "
    IF A%>12 AND A%<20:
	B$=MID$("thir four fif  six  seveneigh nine",(A%-12)*5-4,5):
	=A$+LEFT$(B$,INSTR(B$+" "," ")-1)+"teen"
    IF A%>10 AND A%<13:=A$+MID$("eleventwelve",(A%-10)*6-5,6)
    B$=MID$("ten    twenty thirty fourty fifty  sixty  seventyeighty
	ninety",(A%DIV10)*7-6,7):B$=LEFT$(B$,INSTR(B$+" "," ")-1)
    A$=A$+B$:IF A%>20 AND A%MOD10<>0 AND A$<>"":A$=A$+"-"
    A%=A%MOD10:B$=MID$("one  two  threefour five six  seveneightnine",
	A%*5-4,5):=A$+LEFT$(B$,INSTR(B$+" "," ")-1)

Directory pathname
The function FNpath returns the pathname of the current directory, prefixed with the disk number. The function FNfullpath is almost identical, but it returns the disk name instead of the disk number.

    DIM ctrl% 20,data% 20

    REM Return pathname with drive number or name if no number
    DEFFNpath
    LOCAL A%,X%,Y%,n$,p$
    X%=ctrl%:Y%=X%DIV256:A%=6:REPEAT
    X%!1=data%:CALL &FFD1
    ?(data%+2+?data%+?(data%+?data%+1))=13
    n$=$(data%+2+?data%):*DIR ^
    n$=LEFT$(n$,INSTR(n$+" "," ")-1)
    p$=n$+"."+p$:UNTILn$="$"ORn$="&"
    p$=LEFT$(p$,LENp$-1)
    OSCLI"DIR "+p$
    X%!1=data%:CALL &FFD1
    ?(data%+1+?data%)=13:n$=$(data%+1)
    IFn$<>"":=":"+n$+"."+p$
    X%!1=data%:A%=5:CALL &FFD1
    ?(data%+1+?data%)=13:n$=$(data%+1)
    n$=LEFT$(n$,INSTR(n$+" "," ")-1):=":"+n$+"."+p$
    :
    REM Returns full pathname with diskname
    DEFFNfullpath
    LOCAL A%,X%,Y%,n$,p$
    X%=ctrl%:Y%=X%DIV256:A%=6:REPEAT
    X%!1=data%:CALL &FFD1
    ?(data%+2+?data%+?(data%+?data%+1))=13
    n$=$(data%+2+?data%):*DIR ^
    n$=LEFT$(n$,INSTR(n$+" "," ")-1)
    p$=n$+"."+p$:UNTILn$="$"ORn$="&"
    p$=LEFT$(p$,LENp$-1)
    OSCLI"DIR "+p$
    X%!1=data%:A%=5:CALL &FFD1
    ?(data%+1+?data%)=13:n$=$(data%+1)
    n$=LEFT$(n$,INSTR(n$+" "," ")-1):=":"+n$+"."+p$

The functions FNlibpath, FNfulllibpath, FNurdpath and FNfullurdpath use the FNpath and FNfullpath functions to give the pathnames of the library and user root directories.

    REM Return library pathname
    DEFFNlibpath
    LOCAL a$,b$:a$=FNpath:*DIR %
    b$=FNpath:OSCLI"DIR "+a$:=b$
    :
    REM Return full library pathname
    DEFFNfulllibpath
    LOCAL a$,b$:a$=FNpath:*DIR %
    b$=FNfullpath:OSCLI"DIR "+a$:=b$
    :
    REM Return URD pathname
    DEFFNurdpath
    LOCAL a$,b$:a$=FNpath:*DIR &
    b$=FNpath:OSCLI"DIR "+a$:=b$
    :

    REM Return full URD pathname
    DEFFNfullurdpath
    LOCAL a$,b$:a$=FNpath:*DIR &
    b$=FNfullpath:OSCLI"DIR "+a$:=b$

These functions are contained in Utils.FindPath. This is an example of what would be returned by the functions:

	FNpath		would return 	:0.$.Progs.Temp
	FNfullpath		would return	:ProgramDisk.$.Progs.Temp
	FNlibpath		would return	:1.$.Library
	FNfulllibpath	would return	:UtilsDisk.$.Library
	FNurdpath		would return	:0.$.User1
	FNfullurdpath	would return	:MyDisk.$.User1

These functions will work on any Acorn machine, including Archimedes, and on any filing system that recognises the ^ directory. The short name functions (ie FNpath, FNlibpath, FNurdpath) are suitable for using as a pathname, eg for *CAT or *DIR. The path returned by the full path functions that include the diskname can only be used for filing operations on filing systems that recognise named disks, ie NFS and Archimedes filing systems. 

Program Environment Functions
Command line tail
HADFS, the Archimedes and the *MCode command allow Basic programs to be run as *commands. This function allows the command tail to be retrieved, and also gets the operating system version and the command that started the program. So if a Basic program PROG was run with *PROG Hello, then FNOS_GetEnv would return "Hello" and run$ would be set to "*PROG" if MCoded or "PROG" if CHAINed or "" if Basic bootstrapped.

    DIM ctrl% 20,data% 80

    REM Works with Basic on I/O,T6502,Arc,TZ80bbc,PC
    DEFFNOS_GetEnv:LOCALA$:A%=0:X%=1:os%=((USR&FFF4)AND&FF00)DIV256
    IFos%=6ANDPAGE>&8000:LOADATN"OS_GetEnv"TOA$:A$=MID$(A$,1+INSTR(A$," "))
    IFos%=32:A$=$&100
    IFLENA$=0:IF?(TOP-3):A$=$&600 ELSE IFLENA$=0:A$=$&3800
    A%=INSTR(A$+" "," "):run$=LEFT$(A$,A%-1):IFrun$<>"":=MID$(A$,A%+1)
    X%=ctrl%:Y%=X%DIV256:A%=9:?X%=0:X%!1=data%:!data%=0:CALL&FFD1:
      IF!data%AND?data%+data%?2<>8:data%?(1+data%)=13:=$(data%+1)
    =""

On an Archimedes, it gets the command tail using the SYS call OS_GetEnv. On pre-Basic V, the SYS token is listed and can be entered as LOADATN with no spaces between LOAD and ATN. Next the command line passed on BBCBASIC(86), BBCBASIC(65) and BBCBASIC(80) is looked for; and if there is no run name, it finally checks to see if there is an HADFS or UNIX command tail available. If no command tail can be found anywhere, then a null string is returned. This function is used in ETree and TreeCopy.

The following function can be used to run a Basic program or *command, passing it a command line:

	DEFPROCos(A$):IFA$=""ORASCA$=42:OSCLIA$:ENDPROC ELSE CHAINA$

If the passed command starts with a '*', it is called as a command. If not, it is CHAINed as a Basic program.

HADFS functions
HADFS Version number
FNversion returns the HADFS binary version number. This is the version number*100 for versions before 0.52 and version number*100 for versions 5.20 onwards, ie HADFS 0.48 returns 48 and HADFS 5.20 returns 52.

    DEFFNversion:LOCAL A%,X%,Y%,E%
    A%=&FD:X%=&70:A%=USR&FFDA AND &FF:=((A%+3) AND &FF)-3

    REM Shorter version:
    DEFFNversion=((FNargs0(&FD,0)+3)AND &FF)-3

The +3 and -3 are so that early version of HADFS, or non-HADFS filing systems return a value of -3 so easy comparisons can be made, eg

    IF FNversion<40 THEN PRINT "Must be HADFS 0.40 or later":END

Number of channels
You can change the number of channels available on HADFS using *OPT5. This function will find out how many channels are available.

    DEFFNnum_of_chans:LOCAL A%,X%,Y%
    A%=90:X%=6:Y%=255:=2+(USR&FFF4 AND &3000)DIV&1000

    REM Shorter version:
    DEFFNnum_of_chans=2+(FNbyte(90,6,255)AND&30)DIV16

Drive numbers and characters
HADFS can support up to 32 drives, numbered from 0 to 9 and A to V. The two functions FNdrv and FNDrv convert a drive number to a character needed in a filename, and visa versa. 

    DEFFNdrv(A%)=CHR$(48+A%-7*(A%>9))
    DEFFNDrv(A$)=ASCA$-48+7*(A$>"9")AND31

Preserving directory context
OSARGS &FD can be used to read HADFS's Currently Selected Directory (CSD), so that it can be set to something else using *DIR, and then restored later. The LIB and URD can also be preserved and later restored in this way.

  csd%=FNargs(&FD,8,0):REM Read CSD
  *DIR somewhere
	:
	:
  Do something here
	:
	:
  csd%=FNargs(&FD,&88,csd%): Restore CSD

The *Disks command uses this method to switch between disks without losing where it was to start with.

Adding extra drives with OSWORD 90
Extra drives can be added by intercepting the OSWORD 90 call. The following is an example listing of some code to do just this by creating a ram-disk in JIM. JIM is the area of memory at &FD00 which is reserved for access to multiple pages of extra memory. A paging selector is accessed at address &FCFF in FRED. The ram-disk would have to be initialised with*INSTALL d $ Name DxxxK where xxx is the amount of memory available, and d is the drive number. Setting drive=27 gives drive R.

The file $.Extras.S/RamDisk contains the code for intercepting via WORDV. This efficiency of this listing is not what it could be, as it is programed for clarity rather than for speed. The code in S/Intern is highly optimised, but is thereby much harder to read.

Ram-based intercepting WORDV		Rom-based using service call 8

  addr=&A8:sect=addr+4:ptr=&AE		addr=&A8:sect=addr+4:ptr=&F0
  drive=27:REM drive R			drive=27:REM drive R

Redirect WORDV to point to OswordCall, .Service8:\ Unknown OSWORD
with old WORDV preserved in OldWord.	LDA &EF:CMP #90:BEQ Osword90

  .NotForMe				.NotForMe
  LDX ptr:LDY ptr+1:LDA #90		LDX &F4:LDA #8:LDY ptr+1
  .NotOsword90				RTS
  JMP (OldWord)
  \
  .OswordCall
  CMP #90:BNE NotOsword90
  \
  \ Routine common to ram or rom-based:
  .Osword90
  \ First check &0600 ID marker
  LDY #0:LDA (ptr),Y:BNE NotForMe
  INY:LDA (ptr),Y:CMP #6:BNE NotForMe

  \ Now check for drive number
  LDY #9:LDA (ptr),Y:CMP drive:BNE NotForMe
  \ Then check for low-level command
  \ bit7 commands are dealt with by HADFS
  LDY #11:LDA (ptr),Y:BMI NotForMe:BEQ NotForMe
  CMP #3:BCS NotForMe:\ This routine only does Read and Write
  \
  \ Control block now holds:
  \ 2..5=addr
  \ 6..8=sector
  \ 9   =drive (already checked)
  \ 10   =number of sectors
  \ 11  =type   1=write, 2=read
  \
  \ Preserve the memory about to be used
  LDX #7
  .SaveLoop
  LDA addr,X:PHA:DEX:BPL SaveLoop
  \ Get address & sector
  LDY #2
  .AddrLoop
  LDA (ptr),Y:STA addr-2,Y
  \ Ram-based routine:			\ Rom-based routine:
  INY:CPY #8:BNE AddrLoop:INY:INY	INY:CPY #10:BNE AddrLoop
  \
  \ Get number of sectors
  INY:LDA (ptr),Y:TAX
  \
  \ Get Read/Write
  INY:LDA (ptr),Y
  CMP #1:BEQ Write
  LDA &27A:BPL ReadLoop
  LDA addr+2:AND addr+3:CMP #&FF
  BNE TubeRead
  \
  \ Read to I/O memory
  .ReadLoop
  JSR SetSector
  .ReadLoop2
  LDA &FD00,Y:STA (addr),Y:INY
  BNE ReadLoop2
  JSR NextAddress
  BNE ReadLoop:BEQ IOEnd
  \
  \ Read to Tube memory
  .TubeRead
  TXA:PHA:\ Save number
  LDA #1:JSR ClaimTube
  PLA:TAX:\ Restore number
  .TubeReadLoop
  JSR SetSector
  .TubeReadLoop2
  LDA &FD00,Y:STA &FEE5:INY
  JSR TubeDelay:JSR TubeDelay
  BNE TubeReadLoop2
  JSR NextAddress
  BNE TubeReadLoop:BEQ TubeEnd
  \
  .Write
  LDA &27A:BPL WriteLoop
  LDA addr+2:AND addr+3:CMP #&FF
  BNE TubeWrite
  \
  \ Write from I/O memory
  .WriteLoop
  JSR SetSector
  .WriteLoop2
  LDA (addr),Y:STA &FD00,Y:INY
  BNE WriteLoop2
  JSR NextAddress
  BNE WriteLoop:BEQ IOEnd
  \
  \ Write from Tube memory
  .TubeWrite
  TXA:PHA:\ Save number
  LDA #0:JSR ClaimTube
  JSR TubeDelay:JSR TubeDelay:\ Write needs starting delay
  PLA:TAX:\ Restore number
  .TubeWriteLoop
  JSR SetSector
  .TubeWriteLoop2
  LDA &FEE5:STA &FD00,Y:INY
  JSR TubeDelay:JSR TubeDelay
  BNE TubeWriteLoop2
  JSR NextAddress
  BNE TubeWriteLoop
  .TubeEnd
  JSR TubeRelease
  .IOEnd
  \ Restore memory
  LDX #0
  .LoadLoop
  PLA:STA addr,X:INX:CPX #8
  BNE LoadLoop
  \ Claim call
  LDY #11:LDA #0:STA (ptr),Y
  RTS
  \
  \ Set the JIM page select latch
  .SetSector
  LDA sect+1:STA &FCFE:\ Select hi byte
  LDA sect:STA &FCFF:\ Select low byte
  LDY #0:RTS
  \
  \ Increment sector and address bytes 1/2/3
  .NextAddress
  INC sect:BNE NextAd2:INC Sect+1
  .NextAd2
  INC addr+1:BNE NextAd3
  INC addr+2:BNE NextAd3
  INC addr+3
  .NextAd3
  DEX
  RTS
  \
  \ Tube claim and release routines
  .ClaimTube
  PHA
  .ClaimLoop
  LDA #&C0+36:JSR &406:BCC ClaimLoop
  PLA
  LDX #addr AND 255:LDY #addr DIV 256
  JMP &406
  \ 
  .ReleaseTube
  LDA #&80+36:JMP &406
  \
  \ Delays by 12us
  .TubeDelay
  JSR TubeD2
  .TubeD2
  RTS

BBCBASIC on different machines
BBCBasic has been implemented on many machines, and it is often useful to find out exactly what hardware a program is running on. On all Acorn hardware, this is simple enough, as INKEY-256 and OSBYTE 0,1 give unique values for the machine you are using. However, some machines do not implement negative INKEYs, and so would just wait about 12 minutes for you to press a key, and some do not let you do an OSBYTE call. There are ways around this though. In this discussion, the following Basics will be refered to:

    BBCBasic(2/4)	6502 Basic, as supplied in the B and Master
    BBCBasic(32)	32000 Basic, as supplied with the 32016 co-pro
    BBCBasic(V)	ARM Basic, as supplied in the Archimedes
    BBCBasic(Z80)	Z80 Basic, for Acorn CPM, Torch CPN, Tatung Einstein, Cambridge Z88, Amstrad Notepad,
			or modified for Tube with BBC I/O
    BBCBasic(86)	80x86 Basic, for PC machines
    BBCBasic(X)	C-coded Basic, for UNIX platforms

BBCBasic(2/4) and BBCBasic(V) could also be called BBCBasic(65) and BBCBasic(ARM). Some of the above can also be run on the Archimedes under emulation.

Basic(65), Basic(32) and Basic(ARM) store the program in memory starting from PAGE with a CR, and ending with xx, CR, &FF where xx is the last character of the final line. Basic(Z80) and Basic(86) store the program ending with CR, 00, &FF, &FF. Basic(65), Basic(32) and Basic(ARM) always have access to the MOS and FILE calls and allow INKEY-256. This allows us to use ?(TOP-3)=0 to indicate Z80/86 or 65/32000/ARM, and to indicate whether we can make a call to &FFxx or do INKEY-256.

OSBYTE 0,1 will return 6 indicating an Archimedes when running on an emulator. BBCBasic(ARM) running native always has PAGE>&8000. This can be used to indicate native.

Basic(86) starts with HIMEM>&FFFF, and BBCBasic(Z80) always has HIMEM<&FFFF, so this check can be used to distinguish between Z80 and 80x86. BBCBasic(86) allows only three MOS calls, OSBYTE 0,1 which returns 32, OSBYTE 135 to read the character at the cursor position and OSWORD 10 to read character definitions

Basic(Z80) may run under CPM or not under CPM. All CPM calls go through a jump vector at address &0005. In some Basic(Z80) environments, the &0005 call returns zero to indicate non-CPM mode. In other environments, there isn't a jump vector there at all. If we first test to see if there is a jump vector there and if there is call it with C% set to 12 to ask for the CPM version number, we can find out if we are running under CPM. If the call returns zero, then we are running in a non-CPM environment that allows MOS and FILE calls. If we are running in a non-CPM environment we are probably running on a Cambridge Z88 or an NC100. The Z88 has its reset routine at location 0 starting with a &F3 byte. This distinguishes between Z88 and NC100, neither of which have any MOS or FILE calls.

    REM Works on I/O,T6502,Arc,TZ80bbc,PC,CPM,Unix,NC,Z88,Einstein
    DEFFNOS_GetEnv:LOCAL A%,X%,Y%,A$
    cpm%=0:dos%=0:bbc%=0:os%=-1:arc%=0:unix%=0:z88%=0:nc%=0
    bbc%=?(TOP-3)<>0:IFNOTbbc%:dos%=HIMEM>&FFFF:
      IFNOTdos%:cpm%=?5=&C3:IFcpm%:C%=12:IFUSR5=0:bbc%=TRUE:cpm%=0
    IFbbc%ORdos%:A%=0:X%=1:os%=(USR&FFF4 AND&FF)DIV256
    IFNOTbbc%ANDNOTdos%ANDNOTcpm%:z88%=?0=&F3:nc%=NOTz88%:A$=$&A000
    arc%=(os%=6 AND PAGE>&8000):unix%=os%=8:IFos%=32:A$=$&100
    IFarc%:LOADATN"OS_GetEnv"TOA$:A$=MID$(A$,1+INSTR(A$," "))
    IFLENA$=0:IFbbc%:A$=$&600 ELSE IFLENA$=0:A$=$&3800
    A%=INSTR(A$+" "," "):run$=LEFT$(A$,A%-1):IFrun$<>"":=MID$(A$,A%+1)
    X%=ctrl%:Y%=X%DIV256:A%=9:?X%=0:X%!1=data%:!data%=0:CALL&FFD1:
      IF!data%AND?data%+data%?2<>8:data%?(1+?data%)=13:=$(data%+1)
    =""

The above routine returns any command line parameters and sets the following global variables: run$ is set to the command used to start the program, os% contains the host operating system value (see below); cpm%, dos%, bbc%, unix%, arc%, z88% and nc100% are flags indicating what hardware is running. These can be used to indicate the following:

os%	Host os value (see below)
run$	Pathname used to start the program
bbc%	Calls to &FFxx and INKEY-256 ok, names are :d.$.dirname.leafname
cpm%	Calls to &0005 allowed, filenames are d:filename.xtn
dos%	filenames are d:\dirname\filename.xtn
unix%	filenames are /dirname/filename
arc%	extended Basic functions allowed
nc%	running on an Amstrad NC100/150/200
z88%	running on a Sinclair Z88

Host OS values returned in os% include the following:

	-1 : Unknown		 6 : Archimedes/Acorn
	 0 : Electron		 7 : Springboard
	 1 : BBC			 8 : Unix
	 2 : BBC B+
	 3 : Master 128		30 : Amstrad CPC
	 4 : Master ET		31 : Spectrum
	 5 : Master Compact		32 : IBM-type PC