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.
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.
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:
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:
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.
The next way is to load the program using
MERGE "" instead of
LOAD "" . Unlike
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.
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)
$ 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.
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.
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
$ 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:
Pac-Man.z80computer memory using
tap2sna.pyor emulator features.
Pac-Man.ctlfile with an initial set of instructions for disassembling:
i 16384 Ignore for now c $ 5D26 Loader
sna2skool.py -H -c Pac-Man.ctl Pac-Man.z80 & gt; Pac-Man.skool.
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 DI IM 1 ; Bootloader decryption LD D, IYh; LD E, IYl; LD B, $ 25; Encrypted bootloader length EX DE, HL; 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 INC HL; 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) ADD HL, DE; PUSH HL; We save the address of the loader on the stack XOR (HL); Loader decryption cycle LD (HL), A; INC HL; 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
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
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.
It remains to figure out how the program is called after loading. Instead of the usual
CALL LD-BYTES and
LD SP, XXXX and
JP LD-BYTES are used here. The first (usual) version works as follows:
CALLpushes the current value of the program counter (
PC) on the stack.
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.
As a result of studying the bootloader, we found out the following:
$ 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.