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

OffsetBytesField
$008PLUS3DOS in ASCII
$081$1A
$091issue, $01
$0A1version, $00
$0B4the whole file’s length, header included, little-endian
$0F8the file sub-header; see File sub-header
$17104zero
$7F1checksum: the sum of bytes $00–$7E, modulo 256

File sub-header

The sub-header is the classic tape header’s last eight bytes.

OffsetBytesField, for a BASIC program
$0F1file type: 0 BASIC, 1 number array, 2 character array, 3 CODE
$102length of the BASIC data: the file length less 128
$122auto-start line; $8000 for none
$142offset of the variables: the program’s length, when no variables are saved
$161zero

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.

PartBytesEncoding
line number2big-endian: line 10 is 00 0A
length2little-endian: the bytes that follow, the terminator included
bodyntokens and characters
terminator1$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 negative

POKE 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.nex

is 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

← Reference