7 HADFS programming


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 for control block and data. Where a control block is used, it must be set by the main program before using the functions, and X% and Y% must be set to point to it with the following code:

DIM ctrl% 31:X%=ctrl%:Y%=X%DIV256

Where a function requires other memory, it is shown before the function definition, such as:

DIM name% 80

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 BBC BASIC on I/O processor, Tube, RISC OS 65Host and 65Tube emulators 32000 BBC BASIC on the 32016 second processor ARM BBC BASIC V on RISC OS and ARM second processor BBC BASIC(Z80) using BBC I/O on Z80 Tube or Archimedes Tube emulator

Most will also work on BBC BASIC(86) and BBC BASIC(Win) for PCs and BBC BASIC(X) for UNIX. These functions are all supplied in the Library.BLib directory on the System Startup disk.

General functions - BLib.MOS

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

DEFFNbyte(A%,X%,Y%)=(USR&FFF4 AND &FF00)DIV256

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

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

DIM name% 127


FNfile("FRED",5) would read the catalogue information for the file FRED. The catalogue information is put into memory with X%!2=load, X%!6=exec, X%!10=length and X%!14=attributes.

OSGBPB Calls - BLib.FileIO These procedures and functions call OSGBPB. PROCgbpb makes a general call. Calling FNgbpb with A% passed as 5, 6 or 7 returns the name of the current disk, the current directory or the current library respectively. 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 name% 31

?X%=chn%:X%!1=addr%:X%!5=num%:X%!9=ptr%:CALL &FFD1:ENDPROC

DEFFNgbpb(A%):X%!1=name%:CALL &FFD1:A%=name%+((1+?name%)AND((A%AND-2)=6))

A%=name%+1:A%!(A%?-1)=&D20:A%?(INSTR($A%," ")-1)=13:=$A%

After calling FNgbpb8(), X%!9 holds the ptr% to be used on the next call. After calling FNgbpb() with A%=6 or 7 using


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

OSARGS Calls - BLib.FileIO These two functions allow you to make OSARGS calls. FNargsA 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 zero page 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.

DEFFNargsA(A%):IFHIMEM<&10000:LOCAL X%,Y%,E%,!&70:X%=&70:=(USR&FFDA)AND&FF
SYS"OS_Args",A% TO A%:=A%

DEFFNargs(A%,Y%,ptr%):LOCAL X%,E%:IF?(TOP-3)=0:E%=Y%:Y%=0
IFHIMEM<&10000:LOCAL !&70:X%=&70:!X%=ptr%:CALL&FFDA:=!X%
SYS"OS_Args",A%,Y%,ptr% TO ,,ptr%:=ptr%

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.


Time and Date - BLib.Time 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 function FNtime 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.


The function FNTime checks for any onboard RTC and if none is found will also check for any fileserver clock.

?X%=?X%+((X%?2)AND&E0)DIV2:X%?2=X%?2 AND31:IF?X%<81:?X%=?X%+100
... DIV16)+((X%?4 AND&E0)DIV2):X%?1=X%?5 AND15:X%?2=X%?4 AND31:X%!4=X%!6
=FNDay(X%?3)+","+FNd0(X%?2,2)+" "+FNMon(X%?1)+" "+FNd0(1900+
... X%?0,4)+"."+FNd0(X%?4,2)+":"+FNd0(X%?5,2)+":"+FNd0(X%?6,2)

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

=(VALSTR$~X%?2 AND31)+256*VALSTR$~X%?1+65536*(6400+?X%-&700*(?X%<128))

Time and Date - BLib.Date The functions FNDay and FNMon return three-character string containing the specified day or month. FNDay and FNMon are also in the Time library.

... A%*3+1,3)

The function FNDate_DayOfWeek returns the day of the week for the given date, month and year. The day number is 1..7 for Sun..Sat and can be converted to the name of the day with FNDay. FNDate_DayOfWeek is also in the Time library.

DEFFNDate_DayOfWeek(d%,m%,y%):y%=y%MOD400 =(y%*365.25+m%*30+d%+VALMID$("120112234455",m%,1)+((y%MOD4)=0)- ... ((y%-1)DIV100)-(m%>2AND((y%MOD4)=0AND(y%MOD100)<>0ORy%=0))+3)MOD7+1

PROCDate_FromOrd converts the given time and date to a 5-byte centisecond count from 00:00:00 on 1st Jan 1900, as used for RISC OS time-stamps.

... ((y%-1)DIV100)-(m%>2AND((y%MOD4)=0AND(y%MOD100)<>0ORy%=0))+36493

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

X%!2=X%?10 OR type%*&100 OR &FFF00000

where X% is an OSFILE control block, or:

exec%=!mem%:load%=mem%?4 OR type%*&100 OR &FFF00000

to get load and execution addresses separately.

PROCDate_ToOrd converts a 5-byte centisecond count into a time and date.

DEFPROCDate_ToOrd(mem%):LOCAL A%,B%,C%,D%
IFmem%!1<0:ENDPROC:REM Problems with negatives ATM

On entry: mem%->five bytes of memory containing the centisecond count On exit: year%, month%, day% are set to the date hour%, minute%, second%, centisecond% are set to the time

FNDate_Since 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.

DEFFNDate_Since(td%,tm%,ty%,pd%,pm%,py%):LOCAL past%

Number output - BLib.Number The function FNh0 converts the number A% to a N%-digit hexadecimal string padded with zeros. FNd0 does the same, returning a decimal string. FNh and FNd return a N%-digit hexadecimal and decimal strings, padded with spaces.

DEFFNh(A%,N%)=RIGHT$(" "+STR$~A%,N%)
DEFFNd(A%,N%)=RIGHT$(" "+STR$A%,N%)

HADFS can support up to 32 drives, numbered from 0 to 9 and A to V. The function FNdrv gives the drive character "0" to "V" for the specified drive number 0 to 31. FNDrv converts the other way, giving the drive number from the given drive character.


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

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 - BLib.Pathname The function FNPath_Name returns the pathname of the current directory, prefixed with the disk number. The function FNPath_Full is almost the same, but it returns the disk name instead of the disk number.

DIM name% 31

LOCAL n$,p$:REPEAT:X%!1=name%:CALL &FFD1
n$=$(name%+2+?name%):*DIR ^
n$=LEFT$(n$,INSTR(n$+" "," ")-1)
p$=LEFT$(p$,LENp$-1):OSCLI"DIR "+p$
IFA%=6:X%!1=name%:CALL &FFD1:?(name%+1+?name%)=13:n$=$(name%+1):IF
... n$<>"":=":"+n$+"."+p$
X%!1=name%:A%=5:CALL &FFD1
n$=LEFT$(n$,INSTR(n$+" "," ")-1):=":"+n$+"."+p$

The functions FNPath_Lib, FNPath_LibFull, FNPath_URD and FNPath_URDFull use the FNPath_Name and FNPath_Full functions to give the pathnames of the library and user root directories.

LOCAL a$,b$:a$=FNPath_Name:*DIR %
b$=FNPath_Name:OSCLI"DIR "+a$:=b$
LOCAL a$,b$:a$=FNPath_Name:*DIR %
b$=FNPath_Full:OSCLI"DIR "+a$:=b$
LOCAL a$,b$:a$=FNPath_Name:*DIR &
b$=FNPath_Name:OSCLI"DIR "+a$:=b$
LOCAL a$,b$:a$=FNPath_Name:*DIR &
b$=FNPath_Full:OSCLI"DIR "+a$:=b$

This is an example of what would be returned by the functions:

FNPath_Name would return :0.$.Progs.Temp FNPath_Full would return :ProgramDisk.$.Progs.Temp FNPath_Lib would return :1.$.Library FNPath_LibFull would return :UtilsDisk.$.Library FNPath_URD would return :0.$.User1 FNPath_URDFull would return :MyDisk.$.User1

These functions will work on any Acorn machine, including Archimedes/RISC OS, and on any filing system that recognises the ^ directory. The short name functions (ie FNPath_Name, FNPath_Lib, FNPath_URD) 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 RISC OS filing systems.

Program Environment Functions

Command line tail HADFS, RISC OS 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 host type 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 "".

... A$=MID$(A$,1+INSTR(A$+" "," ",1+INSTR(A$," "))):IFLENA$=0:A$=run$
IFLENA$=0:IF?(TOP-3):A$=$&600 ELSE IFLENA$=0:A$=$(PAGE-&300)
A%=INSTR(A$+" "," "):run$=LEFT$(A$,A%-1):IFrun$<>"":=MID$(A$,A%+1)
... A%?(A%+1)=13:=$(A%+1)

This function works on the BBC/Master I/O processor, the 6502, Z80 and 32016 CoProcessors, Arthur, RISC OS, BBC BASIC(86) and BBC BASIC(Win). It needs 270 free bytes above the end of variable space for temporary workspace. As FNOS_GetEnv is usually called before most variables are set there should be sufficient space.

On an Arthur and RISC OS 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, any command line passed with BBC BASIC for Windows is looked for. Then the command line passed on BBC BASIC(86), BBC BASIC(65) and BBC BASIC(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 programs like ETree and TreeCopy.

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


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 FNhadfs_ver 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.

DEFFNhadfs_ver:LOCAL A%,X%,Y%,E%

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 FNhadfs_ver<40 THEN PRINT "Must be HADFS 0.40 or later":END

Preserving directory context OSARGS &FE 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 with OSRAG &FD. The LIB and URD can also be preserved and later restored in this way.

csd%=FNargs(&FE,25,0):REM Read CSD
*DIR somewhere
Do something here
csd%=FNargs(&FD,25,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.JimDisk/s 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 Intern/s 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)
CMP #90:BNE NotOsword90
\ Routine common to RAM or ROM-based:
LDY #0:LDA (ptr),Y:BNE NotForMe :\ First check &0600 ID marker
INY:LDA (ptr),Y:CMP #6:BNE NotForMe
LDY #9:LDA (ptr),Y:CMP drive:BNE NotForMe :\ Check for drive number
LDY #11:LDA (ptr),Y:BMI NotForMe:BEQ NotForMe :\ bit7 commands dealt by HADFS
CMP #3:BCS NotForMe :\ This 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
LDX #7 :\ Preserve the memory about to be used
LDA addr,X:PHA:DEX:BPL SaveLoop
LDY #2 :\ Get address & sector
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
INY:LDA (ptr),Y:TAX :\ Get number of sectors
INY:LDA (ptr),Y :\ Get Read/Write
CMP #1:BEQ Write
LDA &27A:BPL ReadLoop
LDA addr+2:AND addr+3:CMP #&FF
BNE TubeRead
.ReadLoop :\ Read to I/O memory
JSR SetSector
LDA &FD00,Y:STA (addr),Y:INY
BNE ReadLoop2
JSR NextAddress
BNE ReadLoop:BEQ IOEnd
.TubeRead :\ Read to Tube memory
TXA:PHA :\ Save number
LDA #1:JSR ClaimTube
PLA:TAX :\ Restore number
JSR SetSector
JSR TubeDelay:JSR TubeDelay
BNE TubeReadLoop2

JSR NextAddress
BNE TubeReadLoop:BEQ TubeEnd
LDA &27A:BPL WriteLoop
LDA addr+2:AND addr+3:CMP #&FF
BNE TubeWrite
.WriteLoop :\ Write from I/O memory
JSR SetSector
LDA (addr),Y:STA &FD00,Y:INY
BNE WriteLoop2
JSR NextAddress
BNE WriteLoop:BEQ IOEnd
.TubeWrite :\ Write from Tube memory
TXA:PHA :\ Save number
LDA #0:JSR ClaimTube
JSR TubeDelay:JSR TubeDelay :\ Write needs starting delay
PLA:TAX :\ Restore number
JSR SetSector
JSR TubeDelay:JSR TubeDelay
BNE TubeWriteLoop2
JSR NextAddress
BNE TubeWriteLoop
JSR TubeRelease
LDX #0 :\ Restore memory
BNE LoadLoop
LDY #11:LDA #0:STA (ptr),Y :\ Claim call
.SetSector :\ Set the JIM page select latch
LDA sect+1:STA &FCFE :\ Select hi byte
LDA sect:STA &FCFF :\ Select lo byte
.NextAddress :\ Increment sector and addr bytes 1/2/3
INC sect:BNE NextAd2:INC Sect+1
INC addr+1:BNE NextAd3
INC addr+2:BNE NextAd3
INC addr+3
.ClaimTube :\ Tube claim and release routines
LDA #&C0+36:JSR &406:BCC ClaimLoop
LDX #addr AND 255:LDY #addr DIV 256
JMP &406
LDA #&80+36:JMP &406

.TubeDelay :\ Delays by 12us
JSR TubeD2

BBC BASIC on different machines

BBC BASIC 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:

BBC BASIC(2/4) 6502 BASIC, as supplied in the B and Master BBC BASIC(32) 32000 BASIC, as supplied with the 32016 CoPro BBC BASIC(V) ARM BASIC, as supplied in the Archimedes or ARM CoProcessors BBC BASIC(Z80) Z80 BASIC, for Acorn CPM, Torch CPN, Tatung Einstein, Cambridge Z88, Amstrad Notepad, or modified for Tube with BBC I/O, Sinclair Spectrum or Amstrad CPC. BBC BASIC(86) 80x86 BASIC, for PC machines BBC BASIC(Win) 80x86 BASIC for PCs running Windows BBC BASIC(X) C-coded BASIC, for UNIX platforms Brandy BASIC Brandy BASIC

BBC BASIC(2/4) and BBC BASIC(V) could also be called BBC BASIC(65) and BBC BASIC(ARM). Some of the above can also be run on RISC OS 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), BASIC(86) and BASIC(Win) 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/Win 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 RISC OS/Archimedes when running on an emulator. BBC BASIC(ARM) running native always has PAGE>&8000. This can be used to indicate native.

BASIC(86) starts with HIMEM>&FFFF, and BBC BASIC(Z80) always has HIMEM<&FFFF, so this check can be used to distinguish between Z80 and 80x86. Additionally, Windows always has PAGE>&FFFF and DOS has PAGE<&10000. BBC BASIC(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
... IFNOTdos%:cpm%=?5=&C3:IFcpm%:C%=12:IFUSR5=0:bbc%=TRUE:cpm%=0
IFbbc%ORdos%:A%=0:X%=1:os%=(USR&FFF4 AND&FF00)DIV256
arc%=(os%=6 AND PAGE>&8000):unix%=os%=8
IFarc%:LOADATN"OS_GetEnv"TOA$:A$=MID$(A$,1+INSTR(A$," ",1+INSTR(A$," ")))
... X%?255=13:A$=LEFT$($X%,INSTR($X%,CHR$0)-1):=@cmd$ ELSE IFos%=32:A$=$&100
IFLENA$=0:IFbbc%:A$=$&600 ELSE IFLENA$=0:A$=$&3800
A%=INSTR(A$+" "," "):run$=LEFT$(A$,A%-1):IFrun$<>"":=MID$(A$,A%+1)
... IF!A%AND?A%+A%?2<>8:A%?(1+?A%)=13:=$(A%+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%, win%, 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 win% running on 32-bit Windows, long filenames and SYS available 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