Adaptation programs for the ZX Spectrum to TR-DOS with modern tools. Part 1

Adaptation programs for the ZX Spectrum to TR-DOS with modern tools. Part 1

Unlike modern computers, there was no concept of a file system as such on the spectra. This means that loading from each type of media required a separate implementation and in most cases the program could not be simply copied from tape to diskette. In cases where the program loader was written in BASIC, it could be adapted to TR-DOS with a fairly simple refinement. However, the situation was complicated by the fact that in many games (both branded and hacked), the loaders were written in machine codes and sometimes contained copy protection.

5.25 "Floppy

Despite the presence of a “magic button” that simply made a complete computer memory dump and allowed to somehow save the program to a floppy disk, it was considered by experts to create disk versions of games while preserving the original boot image and other attributes.

In this article, I’ll explain how to make this adaptation using the example of Pac-Man , namely of the original image Pac-Man.tzx .

< a name = "habracut">


Despite the fact that in the old days all such work was done directly on the ZX Spectrum (for the lack of other options), I will adapt the game using the emulator and command line utilities. The main reason is that, especially at first, the adaptation process consists of a large amount of trial and error, and it is much less painful if it is automated. All the same can be done directly on the Spectrum.

In the first part we will use the following tools:

  1. The emulator Fuse for debugging and testing.
  2. SkoolKit to disassemble.

Disable autorun in bootloader

Since the image and data file is loaded without a header block (17 bytes with the name and type of the file), this means that the loader is written in machine code. You need to find where these codes are located and from which address they are launched.

There are several ways to look at the bootloader code:

  1. The simplest is to start downloading the program, wait for the bootloader to start, and stop it by pressing the Space key. In many cases it works, but in the case of Pacman, as in many others, it causes a reset.

  2. The next way is to load the program using MERGE "" instead of LOAD "" . Unlike LOAD , MERGE ignores autorun of the program. In the case of Pac-Man, loading via MERGE causes the computer to freeze with a characteristic screen shift to the left. This is due to the fact that instead of running the program line by line, MERGE tries to parse it entirely and merge it with the already loaded program. However, if the program has a block with machine codes that violates the syntax of the program, this leads to a failure.

  3. If you don’t want to break your head, you can convert the tape image from TZX to TAP and use the listbasic utility that comes with Fuse:

      $ tzx2tap Pac-Man.tzx
     $ listbasic Pac-Man.tap
      1 RANDOMIZE USR (PEEK 23635 + 256 * PEEK 23636 + 91)  


    Address 23635 ( $ 5C53 ) corresponds to the system variable PROG , which contains the starting address of the BASIC area.Thus, the entry point to the bootloader is shifted by 91 bytes relative to the BASIC area.

  4. Another way to look at the bootloader is described in the article Desativando a autoexecução de um programa BASIC . In the Fuse debugger you need to set a breakpoint br 2053 , load the program, and when the download is finished and the code execution stops, execute set 23619 128 . This will prevent the autorun of the program and allow you to go to BASIC.

Disassemble the bootloader

Knowing the offset of the entry point relative to the BASIC area, you can calculate its absolute address. In the case of the ZX Spectrum 48K without TR-DOS loaded, the BASIC area starts at 23755 ( $ 5CCB ). Therefore, the bootloader will start at 23755 + 91 = 23846 ( $ 5D26 ).

To get started, just put a breakpoint at the starting address and look at the machine codes. In Fuse, you can do br 23846 and start downloading the program. As soon as the bootloader starts executing, the emulator will stop:


In the case where the bootloader is quite simple, just look at the disassembled code in the middle panel and understand what it is loading. Usually, the code for downloading a headless file looks like this:

  LD IX, $ 8000;  boot start address
 LD DE, $ 4,000;  file length
 LD A, $ FF;  file body indicator
 CALL $ 0556;  LD-BYTES call
 JP $ 8,000;  switch to the program  

In a more difficult case, with the execution of the code, you need to understand the steps and make notes. The SkoolKit set of tools works well for this. If you set a goal, with its help, the game can be disassembled to the last screw (message, sprite, sound). How this is done is described in detail in the documentation .

In short, do the following:

  1. Snapshot Pac-Man.z80 computer memory using or emulator features.
  2. Create a control Pac-Man.ctl file with an initial set of instructions for disassembling:
      i 16384 Ignore for now
     c $ 5D26 Loader  
  3. Start disassembling: -H -c Pac-Man.ctl Pac-Man.z80 & gt; Pac-Man.skool .
  4. When learning code, add new instructions and comments to the control file.
  5. Repeat until complete enlightenment.

As a result, after the first pass, we get the following (my comments, addresses are omitted):

  ORG $ 5D26;  those same 23846, defined above

 ;  Disable interrupts
 IM 1

 ;  Bootloader decryption
 LD D, IYh;
 LD E, IYl;
 LD B, $ 25;  Encrypted bootloader length
 LD DE, $ 0019;
 ADD HL, DE;  At this stage, the HL contains $ 5C53 (the address of the variable PROG)
 LD E, (HL);  Load PROG value in DE and IX
 LD D, (HL);
 LD IXh, D;
 LD IXl, E;
 LD A, (IX + $ 7F);  Downloading the decryption key to the battery (located in the $ 7F byte
  ;  regarding PROG)
 LD HL, $ 0035;  Start of encrypted bootloader ($ 35 bytes relative to PROG)
 PUSH HL;  We save the address of the loader on the stack
 XOR (HL);  Loader decryption cycle
 LD (HL), A;
 DJNZ $ 5D43;  End of cycle
 AND (HL);
 RET NZ;  At the end of decryption go to the loader at the address on the stack

 ;  Decryption key
 DEFB $ 77  

Decryption of the bootloader

All that is really important is that the decrypted bootloader is located at PROG + $ 35 . This means that if we set a breakpoint br 23808 , then at this point the decryption is already done, we will see the decoded bootloader:


This program is much more similar to the typical case mentioned above. The IX and DE registers load $ 4000 ( 16384 ), do something else and transfer control to the ROM subroutine at $ 055A (this is a few bytes lower than the standard entry point at LD -BYTES ). It seems that this approach implements some kind of copy protection, because The standard procedure does not load this file and some copiers do not understand it.

Program Entry Point

It remains to figure out how the program is called after loading. Instead of the usual CALL LD-BYTES and JP , LD SP, XXXX and JP LD-BYTES are used here. The first (usual) version works as follows:

  1. CALL pushes the current value of the program counter ( PC ) on the stack.
  2. Control is passed to the called subroutine.
  3. When returning from a subroutine ( RET ), the value is removed from the stack and a transition to the calling program occurs.

Why is it done differently here? The fact is that Pac-Man is compatible with the ZX Spectrum 16K and occupies absolutely all RAM (see file size above). Thus, while loading, the program erases both the loader and the stack, wherever they are. If we wanted to go from the ROM to the bootloader using the stack and then call the loaded program through JP , at the time the download is finished, neither the address where JP is located, nor the instructions in there would be no memory.

Instead, the stack pointer moves to the memory area where after loading the address of the program entry point will appear, and the processor, not noticing the substitution, will remove it from the stack using the new pointer and go to the specified address.

The complete disassembly result can be viewed at project repositories on the githaba.


As a result of studying the bootloader, we found out the following:

  1. The 16384 byte header file is uploaded to 16384 (to the screen area, which is obvious in the boot process).
  2. When the download is complete, the stack pointer is located at $ 5D7C , where control is transferred.

In the following sections, I’ll tell you how to prepare files for writing to disk and write a monoblock file loader in assembler.

Related links:

  1. TRUT Spektrumist Profitsa .
  2. Reverse engineering ZX Spectrum (Z80) games .
  3. Adaptação de jogos de fita para Beta 48 .

Source text: Adaptation programs for the ZX Spectrum to TR-DOS with modern tools. Part 1