Conversation between Víctor Morilla and Miguel Angel Rodríguez Jódar, revealing the secrets of the anti-hack protection used in Thor for ZX Spectrum and discovering a previously undocumented cheat code.
Victor Morilla
Do you remember the techniques in basic loaders that were used to avoid hacks in games? It occurred to me to look at what I did in his day with Thor (my platformer video game distributed by Proein). 33 years later and I am unable to understand what I did! Randomize USR to a non-existent variable? Anyone has this more recent? Can you figure out what I did
Miguel Angel Rodríguez Jódar
v can exist perfectly even if you haven’t defined it in your code with a LET. Just do a PRINT and see what you get.
Victor Morilla
PRINT v does not show anything: it gives a not found variable. In other loaders, it was played with modifying the value of the ERR SP system variable so that when an error occurred, the execution of the machine code was launched. But it doesn’t seem to be the case …
César Hernández Bañó
Yes. As Miguel Angel Rodríguez Jódar tells you, that variable v can be saved in the program. ZX spectrum basic programs record the variables that have been defined in memory. If you load said program and do neither a RUN nor a CLEAR, the variable will be in memory
Miguel Angel Rodríguez Jódar
Okay, I think I already know where the shots go:
In the list, the pokes «say» that they go to 23624 and 23659, but that is not true. They go to 23613 and 23614: the variable ERR_SP. The values that are poked are also not zeros, but are, respectively, 30 and 93, that is, 23838. ERR_SP is where SP points in case of error. That is, if an error occurs, the SP record will be loaded with 23838.
The 16-bit value in that address will be the error return address, which in this case is 24094.
When the program loads and runs, RANDOMIZE USE v generates an error message. SP loads with the value 23838 and does a RET. That RET loads the value at that address, 24094, into the program counter, which points to a machine code that was loaded close to BASIC.
That code copies a chunk of memory from the BASIC zone to near the end of RAM (the loader), and then runs it.
Miguel Angel Rodríguez Jódar
The loader, already the real one, asks to load a gigantic block, which occupies the entire ROM, and even $ FFF5. The stack is saved by little. It is clear that during loading, the loader is overwritten and what you see behind the CALL may not be what is executed afterwards …
Victor Morilla
Yes! A silly mistake! Made the loader unnecessarily long.
Miguel Angel Rodríguez Jódar
I didn’t see it as a bug. Precisely one of the anticopy protections used is to load very very large blocks, in order to prevent a copycat from swallowing it.
Victor Morilla
That makes more sense. It is all too obvious that the screen takes a long time to start charging. Thank you very much for all the help. It seems to me that it is not the first loader you look at 😅
Miguel Angel Rodríguez Jódar
No … it’s not the first one I look at, no 😉
Miguel Angel Rodríguez Jódar
Indeed, after loading, the JP NC, $ 0000 has been transformed into a JP C, $ C7F3, which jumps to what seems to be the beginning of the program itself.
Miguel Angel Rodríguez Jódar
In that beginning there is a very curious thing: as soon as the program starts, and before anything else, it is checked if the C and V keys are pressed, and if both are, then a certain value is saved in $ A23E (the value $ 18). An easter egg to give infinite lives or invulnerability perhaps?
Miguel Angel Rodríguez Jódar
In $ A23E there is this routine, which jumps to a certain place if a certain variable in $ C00F is not 0. When the value it has is changed to $ 18, that «jump if it is not 0» becomes an «always jump». I bet what you want that variable is the life counter or the energy counter. I’m going to put a breakpoint to see when that runs.
Miguel Angel Rodríguez Jódar
And of course, I «trick» the program into executing that part, as if I had pressed C and V.
Miguel Angel Rodríguez Jódar
The breakpoint above is executed once on each lap of the main game loop. I’ll give it a few times until someone touches me, see what happens…
Miguel Angel Rodríguez Jódar
AHA! The variable is the life counter! And when that instruction is changed, it is never decremented. They take away my energy, but when it reaches 0 it recharges again, and the lives continue to be 5. Is this easter egg documented?
Victor Morilla
Genius! Miguel Angel. Cool. I knew about the easter egg, but I don’t think it’s documented. What I still don’t understand is how some pokes appeared in the list and different ones were being used.
Miguel Angel Rodríguez Jódar
Ah! That is because in the BASIC of the ZX Spectrum, the numerical values are represented in two different ways: one, by means of ASCII code, which is what you see when you list the program (the 23624, 23659 and the two 0s), and another is the internal representation that is hidden in the listing, but in memory, follows the ASCII representation. Check out this memory dump from the BASIC loader:
In it, I have marked a byte, 244. That is the POKE code. Right after it is the value in ASCII 23624. And just after that, byte 14 appears, which is the «number» mark. The next 5 bytes define the binary value of the number. Normally, the ASCII representation matches that internal value, but it doesn’t have to be, and the interpreter actually ignores the ASCII version and sticks with the internal version.
This internal representation of 5 bytes is very simple, for integers between -65535 and +65535: the first byte (after 14) is a 0, the second is the sign (0 = positive, 255 = negative), the third and fourth, the value in 16 bits, little endian, and the fifth is 0.
In this specific case, the values that interest us after 23624 are 61 and 92. That gives: 61 + 256 * 92 = 23613, which is the value that will actually be used in the POKE.
And so, all the other values that you see in the list. Not one of them matches what runs later.
Victor Morilla
Thank you very much for the explanation.
Miguel Angel Rodríguez Jódar
I understand that being the author, you knew him, of course, but … did you put it on your own initiative or was it «suggested» to you by Proein? Or was it some issue to debug the game more easily and you «forgot» to remove it? Wasn’t this cheat code published in the specialized press in its day? Have we brought it out NOW?
Victor Morilla
The funniest thing is that possibly, up to this point, no one tried to hack this code. When I wrote it when I was 15 years old, I did not expect that the first one to try would be my future self … and that I would fail if not for Miguel Angel.