ZXSTLC - ZX Spectrum Tape (& Disk) Loader Creator

Instead of my normal target of an unexpanded 48k machine my new game project needed to utilise the additional RAM available on the 128k models. Last thing on my mind when I started was how to create a tape loader but as the project progressed, and I started using the additional memory banks, it became clear that I needed to pause and figure out how I was going to actually load it outside of an emulator. I'd done a bit of work with loading all the 128k memory banks for my Z80onDSK utility and know that when you want to use all the available memory it becomes more difficult due to clashing with buffers, especially with page 7. My new game pushes the memory of the Spectrum to the limit so use of Page 7 was a must and that kind of ruled out creating a simple basic loader with OUT commands. However, I still wanted to use the ROM tape loading routines as moving to a custom or fast loader would break compatibility with the newer SD card machines.

A bit of research on the WoS & Spectrum Computing Forums pointed me at using a small machine code routine at the beginning of the basic loader which switched to USR 0 mode first, which I did consider, but where is the fun in using somebody else's code :-)

USR 0 - As a long time Spectrum user I never fully understood why USR 0 was needed as surely just using the 128k loader would work? Anyway now I get it. As I mentioned one of the issues I faced with loading from tape was the need to use the page 7 memory bank which has some of the 128k buffers located in it. Having a simple basic loader kept overwriting data held in page 7 and in order to get around this I quickly realised you needed to stop the 128k ROM being used or simply put you need to be in USR 0 mode. Instead of using basic and USR 0 I just created a machine code loader that pages in the 48k ROM and has the rest of the loader in the machine code so it never goes back to the 128k ROM.

The end result was a simple machine code loader which still used the 48k ROM (LD_BYTES) tape routines for best compatibility with modern SD loaders but allows for bank switching during the load and the full use of page 7. It also loaded the data in headerless blocks.

I could have left it at that but the process of creating a tape was still very manual and it also didn't solve disk loading which I also wanted for my Gotek drive. I therefore put a bit more time in to create a full utility which completely automates the process.

Backwards Compression - One of the downsides of a 128k project only is the amount of time it takes to load, especially if you use all the memory banks. Even though a lot of users now have instant SD Card loaders I did also want to investigate compression to reduce the time taken for those using real tapes (or a TZXDuino). This brought up another issue which is where do you put the compressed data to decompress, in particular for the last part to be loaded where there isn't a spare memory page. The solution was to use a backwards compression technique and in-place decompression and I adapted my modified LZF compressor to create this. To get it working you load the compressed data at the beginning of the memory area say 0xC000 and you decompress from the top of the memory area say 0xFFFF. As you are working top downwards in theory the decompression and the compression pointers will not overlap till right at the end. This does mean you need to lower the start of the compressed data to slightly below the start and experimenting gave me a delta of 3 meaning the compressed data needed to be located from 0xBFFD for data starting at 0xC000. The utility will check if this gap works or a larger delta is required. It is worth noting that if the utility cannot compress the data it simply stores it instead.

+3 Disk Version - With the tape version working well I moved to creating a disk version. As for my Z80onDSK utility I opted for using a straight off disk decompressor rather than the backwards one implemented in the tape version. The main reason for this is page 7 memory is even more of a pain with the 3DOS buffer having to be used till the disk usage has stopped and ROM paged out. This means if page 7 is required the last part from memory location 0xDB00 to 0xE800 has to be copied in from another memory location after the disk has been disabled. The only place left for this is the screen. Therefore if page 7 is required the screen is blanked for the last disk load of 3328bytes. This is then copied in place and the program launched.

Page 7 Memory Layout

0xc000-0xdb00 - shadow screen (not used in my loader so can be overwritten)
0xdb00-0xe800 - 3dos used (have to preserve this till after disk usage)
0xe800-0xec00 - unused
0xec00-0xfe00 - 128k buffers (not used in my loader so can be overwritten)  
0xfe00-0xffff - bootselector (not used in my loader so can be overwritten)

Loading Schema

Tape Loading                       Disk Loading                          
Basic                              Basic
Loader + Screen (compressed)       Loader + Screen (compressed)
  Located 0x5EFD and below           Located 0x5EFD and below
File 1 (hl) - Page 7 (full)        File 1
File 2 (hl) - Page 6 (full)          Page 7 0xC000-0xDAFF (shadow screen)
File 3 (hl) - Page 4 (full)          Page 7 0xE800-0xFFFF 
File 4 (hl) - Page 3 (full)          Page 6 (full)
File 5 (hl) - Page 1 (full)          Page 4 (full) 
File 6 (hl)                          Page 3 (full) 
  Page 5 0x6000-0x7FFF             File 2  
  Page 2 (full)                      Page 1 (full)
  Page 0 (full)                      Page 5 0x6000-0x7FFF
Memory Page Set (default 5,2,0)      Page 2 (full)
Enable/Disable Interrupts            Page 0 (full)
Code Launched                        Page 7 0xDB00-0xE7FF to 0x4000
                                   Disable Disk & 3DOS ROM
                                   Copy 3328bytes from 0x4000 to 0xDB00 
                                   Memory Page Set (default 5,2,0)
                                   Enable/Disable Interrupts								   
                                   Code Launched

I tested the utility on a few of my existing games and it reduced the overall size and loading time by around 30% with the added advantage of creating a disk version as well.

You can test it out using the link below and full instructions on how to use are below or can be found by typing zxstlc on a command line.

Anyway now back to my 128k project!

ZXSTLC v1.2b

Windows & Linux (x86)

Version History

v1 initial realease to WoS & Spectrum Computing
v1.1 fixed a bug with the tape delta adjustment
v1.2 Disk only change. Added ability to change the blank screen used to mask the Page 7 load at the end of the disk load. You can now specify a different colour, a simple rainbow effect or add your own. This is only shown very briefly at the end of the load but looks better than just a black screen.
v1.2b New backwards parser with improved and faster compression

Worked Example

Quick example to show how to create a tape and disk loader for my game The Order of Mazes

First compile the code into a binary file. For this project I put memory banks 5,2 & 0 together as this is the standard 48k layout. I therefore compile into a single binary as follows.

pasmo --bin toom.asm toom.bin

This utility can work with this or you can break out page 0 separately using the -0 option. Now that I have the main binary file I need to add details of where I want to LOAD to code and also the START location (same RANDOMIZE USR xxx). For this game the LOAD position is 28621 and the START 32768. To create the load I simply type:

zxstlc -b toom.bin 28621 32768

This will create both toom.tap and toom.dsk files using the standard settings with the following output:

| ZXSTLC (ZX Spectrum Tape (& Disk) Loader Creator) v1.1 (c) 2020 Tom Dalby
| Both Tape & Disk Creation
| Main binary size=29703bytes compossed of:
| - Page 5 size=4147bytes
| - Page 2 size=16384bytes
| - Page 0 size=9172bytes
| Default Loading Screen Used
| Tape Creation:
| - M(520) Compressed Backwards to 21867bytes from 29703bytes (26.4%)
| - Basic & Loader Tape Size (inc. Loading Screen) 387+246=633bytes
|   - Checksums: bh0x62,bd0xe5,ch0xb3,cd0x69
| - Writing TAP: BLM(520) 22500bytes from 30336bytes (25.8%)
| Disk Creation:
| - M(520) Compressed to 21889bytes from 29703bytes (26.3%)
| - Basic & Loader Disk Size (inc. Loading Screen) 713+246=959bytes
| - Writing DSK: BL1(520) 22848bytes from 30662bytes (25.5%)

I could leave it at that, however I want to add a custom loading screen, change the file name and also the Spectrum Program name on the tape and disk to MAZE. To do this the command line is:

zxstlc -b toom.bin 28621 32768 -N " MAZE" -f MAZE -l toom.scr -m MAZE

In addition to the default options I now have -N " MAZE" which makes the tape basic Program name MAZE but as I've specified capital N it will do the special trick so it overwrites the Program: part. It is in quotes as I want a leading space. The -m MAZE does the same for the disk names and -f MAZE for the saved files which will now be MAZE.tap & MAZE.dsk. Finally -l toom.scr adds a custom loading screen to the loader. The output is now:

| ZXSTLC (ZX Spectrum Tape (& Disk) Loader Creator) v1.1 (c) 2020 Tom Dalby
| Both Tape & Disk Creation
| New Tape Loader Name:  MAZE
| New Filename: MAZE
| New Loading Screen: toom.scr
| New Disk Loader Name: MAZE
| Main binary size=29703bytes compossed of:
| - Page 5 size=4147bytes
| - Page 2 size=16384bytes
| - Page 0 size=9172bytes
| Loading Screen Compressed to 1473bytes from 6912bytes (78.7%)
| Tape Creation:
| - M(520) Compressed Backwards to 21867bytes from 29703bytes (26.4%)
| - Basic & Loader Tape Size (inc. Loading Screen) 387+1473=1860bytes
|   - Checksums: bh0x8,bd0xe5,ch0x68,cd0x5e
| - Writing TAP: BLM(520) 23727bytes from 37002bytes (35.9%)
| Disk Creation:
| - M(520) Compressed to 21889bytes from 29703bytes (26.3%)
| - Basic & Loader Disk Size (inc. Loading Screen) 713+1473=2186bytes
| - Writing DSK: BL1(520) 24075bytes from 37328bytes (35.5%)

In order to add the additional 128k pages you need to simply add the appropriate page argument and binary file to the end of the command line so for example to add pages 1, 3 & 4 you would put:

zxstlc -b toom.bin 28621 32768 -1 page1.bin -3 page3.bin -4 page4.bin

Each page can be any length however they must be compiled from ORG 0xC000 even if the code used is later in the memory. This is done to make the process simpler and as the pages are compressed unused space is shrunk to no more than a few bytes. To accomplish this you can just use multiple ORG statements in your assembler listing, the first one being ORG 0xC000. There are a few more options to tweak the loader, see below:

Command Line Arguments

| ZXSTLC (ZX Spectrum Tape (& Disk) Loader Creator) v1.2 (c) 2020 Tom Dalby
| Usage: ZXSTLC <-c> <-s> <-b or t,d)>
| -s create a single headerless data file within a .TAP wrapper, no compression
| -c create a single headerless data file within a .TAP wrapper, zxsc compression
|    both -s & -c print size of the headerless block to STDOUT
| -t create a full tape including loader and all data files within a .TAP wrapper
| -d create a full disk including loader and all data files within a .DSK wrapper
| -b create both tape & disk as above
|    a basic command line will be:
|      where MAIN.BIN is a binary SPECTRUM file to load - normally composed
|        of memory banks 5, 2 and 0 (standard 48k memory layout)
|      LOAD is where to load the binary file in decimal i.e. 24576
|        equivalent to CODE xxx (note has to >=24576)
|      START is where to run the code in decimal i.e. 32768
|        equivalent to RANDOMIZE USR xxx (note has to >=24576)
|    This will build a tape/disk loader for either a 48k or 128k program 
|    containing only the standard memory pages (5, 2 & 0). In addition to the
|    loader & code block it will add in a standard loading screen (which you can
|    change, see later). For 128k programs with additional memory pages you
|    need to specify each separately as follows:
|    This will include memory pages 1 & 3 from the respective .BIN files.
|    Please note only pages 0, 1, 3, 4, 6 & 7 can be added this way with 2 & 5
|    always via the MAIN.BIN file. Page 0 can either be added into MAIN.BIN
|    or separately. If separate the utility will added it to pages 2 & 5 with
|    padding to create a single code block for easier loading.
|    Note when compiling each of the additional memory pages they need to start
|    @ memory position 0xC000 (49152), even if the code is after this address. 
|    Each page is compressed so the blank space will not increase the size of
|    the loader by more than a few bytes. Pages can end at any point <= 0xffff
|    (max 16384bytes). With PASMO and similar compilers this is easy to do with
|    ORG additional. To compile use the PASMO --bin option.
|    Optional arguments:
|    -f FILENAME change output filename, default taken from MAIN.BIN
|    -l SCREEN.SCR add a custom loading screen. Screen must be in .scr format
|       and exactly 6912bytes long. Default is use a generic screen
|    -L suppress loading screen i.e. just get a blank screen while loading
|    -i COL change BASIC loader PAPER, INK & BORDER colour, default black (0)
|    -e switch enable interrupts (ei) to disable interrupts (di) before launch
|    -p PAGE set final memory page before launch, default 16 (page 0, 48k ROM)
|       memory page is defined by the following byte xxDRSPPP where:
|         D is used disable/enable paging - 1 paging off, 0 paging on
|         R determines which ROM - 1 48k ROM, 0 128k ROM
|         S determines which screen - 1 Shadow Screen, 0 Normal Screen
|         PPP determines which RAM page (0-7)
|       for 48k ROM, page 0, normal screen & paging enabled -> 00010000 or 16
|    Tape only:
|    -n NAME change SPECTRUM tape filename (max 10chars) default is LOADING... 
|    -N NAME same as -n but uses trick to make Program: vanish (max 5chars)
|    -a change backwards compressor delta from the default 3 (>=3)
|       the utility will error if the delta is not high enough to achieve
|       successful decompression.
|    Disk only:
|    -m NAME change SPECTRUM disk filenames (max 8chars & alphanumeric)
|       default is LOADCODE, name will be made uppercase if lower specified
|    -A ATTR change masking screen for page 7 load (defult black)
|       ATTR can either be a number between 1 & 15 or a file name for a binary
|       file containing an ATTR layout. Note this needs to be 768bytes long and
|       the ink & paper must be exactly the same in order to hide the load
|       Specifying a number 1-15 just changes the colour of the screen, with 1
|       being dark blue, 2 dark red... 7 dark white, 8 is bright blue etc... up
|       to 14. 15 is a special rainbow layout just for fun.