In contrast to modern computers, on the spectra of the concept of the file system was not as such. 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 dump of the computer’s memory and made it possible to at least 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 will tell you how to perform such an adaptation on the example of the game Pac-Man , namely, the original image of Pac-Man.tzx .
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 file type), this means that the loader is written in machine code. It is necessary 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 easiest way is to start downloading the program, wait until the loader starts up, 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 a program using MERGE ""
instead of LOAD ""
. Unlike LOAD
, MERGE
ignores program autorun. In the case of Pac-Man, downloading 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 do not want to puzzle, 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 into 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 the breakpoint br 2053
, load the program, and when the download is finished and the code is interrupted, write 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 area of ​​BASIC, you can calculate its absolute address. In the case of the ZX Spectrum 48K without a loaded TR-DOS, the BASIC area starts at address 23755
( $5CCB
). Therefore, the bootloader will start at address 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 make br 23846
and start downloading the program. As soon as the bootloader starts executing, the emulator will stop:
In the case when the bootloader is quite simple, just look at the disassembled code in the middle panel and understand what is being loaded to. Usually, the code for downloading a headless file looks like this:
LD IX, $8000 ; LD DE, $4000 ; LD A, $FF ; CALL $0556 ; LD-BYTES JP $8000 ;
In a more complicated case with the execution of the code, you need to understand the steps and make notes. The SkoolKit toolkit is well suited 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.z80
computer memory using tap2sna.py
or emulator features.Pac-Man.ctl
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 > Pac-Man.skool
.As a result, after the first pass we get the following (my comments, addresses are omitted):
ORG $5D26 ; 23846, ; DI IM 1 ; LD D, IYh ; LD E, IYl ; LD B, $25 ; EX DE, HL ; LD DE, $0019 ; ADD HL, DE ; HL $5C53 ( PROG) LD E, (HL) ; PROG DE IX INC HL ; LD D, (HL) ; LD IXh, D ; LD IXl, E ; LD A, (IX+$7F) ; ( $7F- ; PROG) LD HL, $0035 ; ($35 PROG) ADD HL, DE ; PUSH HL ; XOR (HL) ; LD (HL), A ; INC HL ; DJNZ $5D43 ; AND (HL) ; RET NZ ; ; DEFB $77
All that really matters is that the decrypted bootloader is located at PROG + $35
. This means that if we set the breakpoint br 23808
, then by that time the decryption is already done, we will see the decrypted bootloader:
This program is much more similar to the typical case mentioned above. The registers IX
and DE
load the value $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 in 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 the download. 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:
CALL
pushes the current value of the software counter ( PC
) on the stack.RET
), the value is removed from the stack and the transition to the calling program occurs.Why is it done differently? 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 via JP
, at the time of the download completion, there would no longer be an address for the JP
or the instruction itself in 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, removes it from the stack using the new pointer and proceeds to the specified address.
The complete disassembly result can be viewed in the project repository on the github.
As a result of studying the bootloader, we found 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.
Source: https://habr.com/ru/post/451174/
All Articles