Plus3DOS and NextBASIC Files
The file format NextZXOS saves a BASIC program in: a 128-byte +3DOS header, then the program as tokenised lines. With it, a program file can be built on the host and copied onto a card image, such as an autoexec.bas that starts a game when the Next boots.
The facts here are taken from the Next distribution’s own sources: the token table src/asm/dot_commands/bastoken.def and the TXT2BAS and BAS2TXT dot commands, in the tbblue repository.
+3DOS header
| Offset | Bytes | Field |
|---|---|---|
$00 | 8 | PLUS3DOS in ASCII |
$08 | 1 | $1A |
$09 | 1 | issue, $01 |
$0A | 1 | version, $00 |
$0B | 4 | the whole file’s length, header included, little-endian |
$0F | 8 | the file sub-header; see File sub-header |
$17 | 104 | zero |
$7F | 1 | checksum: the sum of bytes $00–$7E, modulo 256 |
File sub-header
The sub-header is the classic tape header’s last eight bytes.
| Offset | Bytes | Field, for a BASIC program |
|---|---|---|
$0F | 1 | file type: 0 BASIC, 1 number array, 2 character array, 3 CODE |
$10 | 2 | length of the BASIC data: the file length less 128 |
$12 | 2 | auto-start line; $8000 for none |
$14 | 2 | offset of the variables: the program’s length, when no variables are saved |
$16 | 1 | zero |
NextZXOS runs /nextzxos/autoexec.bas at boot whatever its auto-start line; the line matters to an explicit LOAD.
Program lines
The data after the header is a run of lines in ascending order, numbered 1 to 9999.
| Part | Bytes | Encoding |
|---|---|---|
| line number | 2 | big-endian: line 10 is 00 0A |
| length | 2 | little-endian: the bytes that follow, the terminator included |
| body | n | tokens and characters |
| terminator | 1 | $0D |
⚠ The line number is the only big-endian field in the format. Every other multi-byte value is little-endian.
Body
Printable ASCII, $20–$7E, is stored as itself: string contents, REM text and digits. A dot command line such as .nexload game.nex is stored entirely as text, since dot commands are not tokenised.
A keyword is one byte from $81 to $FF:
81 TIME 82 PRIVATE 83 IF 84 ENDIF 85 EXIT 86 REF
87 PEEK$ 88 REG 89 DPOKE 8A DPEEK 8B MOD 8C <<
8D >> 8E UNTIL 8F ERROR 90 ON 91 DEFPROC 92 ENDPROC
93 PROC 94 LOCAL 95 DRIVER 96 WHILE 97 REPEAT 98 ELSE
99 REMOUNT 9A BANK 9B TILE 9C LAYER 9D PALETTE 9E SPRITE
9F PWD A0 CD A1 MKDIR A2 RMDIR A3 SPECTRUM A4 PLAY
A5 RND A6 INKEY$ A7 PI A8 FN A9 POINT AA SCREEN$
AB ATTR AC AT AD TAB AE VAL$ AF CODE B0 VAL
B1 LEN B2 SIN B3 COS B4 TAN B5 ASN B6 ACS
B7 ATN B8 LN B9 EXP BA INT BB SQR BC SGN
BD ABS BE PEEK BF IN C0 USR C1 STR$ C2 CHR$
C3 NOT C4 BIN C5 OR C6 AND C7 <= C8 >=
C9 <> CA LINE CB THEN CC TO CD STEP CE DEF FN
CF CAT D0 FORMAT D1 MOVE D2 ERASE D3 OPEN # D4 CLOSE #
D5 MERGE D6 VERIFY D7 BEEP D8 CIRCLE D9 INK DA PAPER
DB FLASH DC BRIGHT DD INVERSE DE OVER DF OUT E0 LPRINT
E1 LLIST E2 STOP E3 READ E4 DATA E5 RESTORE E6 NEW
E7 BORDER E8 CONTINUE E9 DIM EA REM EB FOR EC GO TO
ED GO SUB EE INPUT EF LOAD F0 LIST F1 LET F2 PAUSE
F3 NEXT F4 POKE F5 PRINT F6 PLOT F7 RUN F8 SAVE
F9 RANDOMIZE FA IF FB CLS FC DRAW FD CLEAR FE RETURN
FF COPY$81–$A4 are the NextBASIC and 128K keywords and $A5–$FF the 48K ones. Inside a quoted string, $90–$A4 are the user-defined graphics A to U instead.
Numbers
A number in a line is stored as its digits, then $0E, then five bytes of the ROM’s floating-point form. For a whole number from −65535 to 65535 the five bytes are:
00 sign low high 00 sign is $00 for positive and $FF for negativePOKE 23739,111 stores 23739 as 32 33 37 33 39 0E 00 00 BB 5C 00. A number that is not a whole number stores an exponent plus $80 and a four-byte mantissa. The auto-start line in the header is a plain 16-bit word, not this form.
Example
A two-line autoexec.bas that moves into a game’s folder and loads it:
10 CD "\test\treasurehunters\treasurehunters"
20 .nexload treasurehunters.nexis 206 bytes. Its header holds a file length of CE 00 00 00, a sub-header of 00 4E 00 0A 00 4E 00 00, and checksum EC. Line 10 is 00 0A 29 00 A0 22 and the quoted path, 22 0D; line 20 is 00 14 1D 00 and the text .nexload treasurehunters.nex, then 0D.
This Python writes the same file:
import struct
def line(num, body):
return struct.pack('>H', num) + struct.pack('<H', len(body) + 1) + body + b'\x0d'
def plus3dos(basic, autostart=0x8000):
sub = bytes([0]) + struct.pack('<HHH', len(basic), autostart, len(basic)) + bytes([0])
hdr = b'PLUS3DOS' + bytes([0x1A, 1, 0]) + struct.pack('<I', 128 + len(basic)) + sub
hdr += bytes(127 - len(hdr))
return hdr + bytes([sum(hdr) & 0xFF]) + basic
prog = line(10, bytes([0xA0]) + b'"\\test\\treasurehunters\\treasurehunters"')
prog += line(20, b'.nexload treasurehunters.nex')
open('autoexec.bas', 'wb').write(plus3dos(prog, autostart=10))Copy it onto a card image with mtools, giving the offset of the card’s FAT partition, 32256 bytes on the distribution’s 2 GB image:
mcopy -o -i next.img@@32256 autoexec.bas ::/nextzxos/autoexec.bas