h3 #target TAP or TAPE tap pre ; ZX Spectrum tape file: #target tap #code , ,, #code , ,,flag= #code , ,,FLAG= ... pre ; Jupiter Ace tape file: #target tap #code , ,,flag=none #code , ,,none p This target defines a ZX Spectrum or a Jupiter Ace tape file. This file may and should contain several code segments. Normally each code segment defines one block on the tape. The blocks are written to the file in the uncompressed TAP format. p The tape file format represents programs and data saved to a music compact cassette by the original ZX Spectrum tape saving routines. h5 ZX Spectrum vs. Jupiter Ace tap format p Unluckily the tape file formats for the ZX Spectrum and for the Jupiter Ace are only similar, not identical. zasm handles this difference for you, but it has to use a little heuristics to decide in which format to save. p Normally a ZX Spectrum file is written. Except if the following conditions are met, then a Jupiter Ace tape file is written: ul li The first segment is 25 bytes long (the Jupiter Ace header size) li Block flags start with 0x00 for the first header block and then alternate between 0xFF and 0x00 for all following blocks. This is required for the Jupiter Ace tape file, because they do not store the flag byte. p Since version 4.2.0 zasm uses a different method to select Jupiter Ace .tap format: ul li If you set the flag byte in the #code directive to none, then a Jupiter Ace-style tape block is written. This block does not store the flag byte (therefore 'none') but instead emulators will implicitely assume $00 and $FF for the flag byte in alternating order. p.b Layout of a block in a ZX Spectrum tape file: pre defw defb defm defb p.b Layout of a block in a Jupiter Ace tape file: pre defw defm defb p The flag argument defines the type of block and is set from the #code segment's flag byte. p Then the raw data follows which is taken from the #code segment's data. p Finally a checksum follows which is calculated and appended by the assembler. p A tape file is a simple sequence of any number of these blocks. p Any kind of data is typically saved in two blocks: a header block, containing information about the following data, and a data block, containing data as described by the preceding header. p A complete game for the ZX Spectrum is typically saved in two parts: a basic loader, which consists of a header and a data block and the machine code part, which consists of a header and data block too. The basic part is typically saved with an auto start address which loads the following parts and starts the game. p A game for the Jupiter Ace may consist of only a single part (which consists of a header and data block). Machine code was not such a requirement because Forth is already pretty fast. The Jupiter Ace did not support auto starting and therefore the user had to type commands to load following blocks (if any) and to start and even restart a game. p The flag argument defines the type of block. On both the ZX Spectrum and the Jupiter Ace this is typically $00 for a header block and $FF for the following data block. p For an example see the .tap template file: template_tap.asm p It is possible to spread a tape block across multiple #code segments. This is required if you want to include c source files. The first #code segment must define an address and the block flag and the following #code segments must have exactly matching addresses (you may put in '*' for the address) and no flag. All #code segments which meet this requirement will be united with the first segment to one tape block. p New since version 4.0.24: Each code segment which has a flag byte starts a new tape block. All following segments which have no flag byte are appended to this tape block. A segment address defined for a following block does not create a 'gap' on the tape. The segment will be loaded where it is loaded and the program is assumed to move it to the defined location befor it is used. Actually this is true for the first block too: The tape loader defines where the block will be loaded, not the segment address stated in the #code directive. This can be used if you want to move the entire program to a lower location in ram when it starts up. h5 Basic layout of a ZX Spectrum tape pre #target tap ; headerflag: equ 0 dataflag: equ 0xff ; ; use printer buffer for variables: ; #data VARIABLES, 0x5B00, 0x100 ; ; Basic loader, header: ; #code PROG_HEADER,0,17,headerflag defb 0 ; Indicates a Basic program defb "mloader " ; the block name, 10 bytes long defw variables_end-0 ; length of block = length of basic program plus variables defw 10 ; line number for auto-start, 0x8000 if none defw program_end-0 ; length of the basic program without variables ; ; Tokenized Basic program: ; #code PROG_DATA,0,*,dataflag ; ; 10 CLEAR 23999 defb 0,10 ; line number defb end10-($+1) ; line length defb 0 ; statement number defb tCLEAR ; token CLEAR defm "23999",$0e0000bf5d00 ; number 23999, ascii & internal format end10: defb $0d ; line end marker ; ; 20 LOAD "" CODE 24000 defb 0,20 ; line number defb end20-($+1) ; line length defb 0 ; statement number defb tLOAD,'"','"',tCODE ; token LOAD, 2 quotes, token CODE defm "24000",$0e0000c05d00 ; number 24000, ascii & internal format end20: defb $0d ; line end marker ; ; 30 RANDOMIZE USR 24000 defb 0,30 ; line number defb end30-($+1) ; line length defb 0 ; statement number defb tRANDOMIZE,tUSR ; token RANDOMIZE, token USR defm "24000",$0e0000c05d00 ; number 24000, ascii & internal format end30: defb $0d ; line end marker ; program_end: ; ; <-- Basic variables --> ; variables_end: ; ; Machine code block, header: ; #code CODE_HEADER,0,17,headerflag defb 3 ; Indicates binary data defb "mcode " ; the block name, 10 bytes long defw code_end-code_start ; length of data block which follows defw code_start ; default location for the data defw 0 ; unused ; ; Machine code block, data: ; #code CODE_DATA, code_start,*,dataflag ; ; <-- Z80 assembler code and data --> ; code_end: