By this point, you should be familiar with headerless loaders, which take up the bulk of most protection systems these days. However, there is one other important aspect of protection that you need to know about if you are to crack the more complex protection systems. It's encryption.
Encryption works like a secret code. You start off with somthing unintelligable, then you use the "secret rules" to change it into something that makes sense. So, IBDLFST doesn't make much sense, but if you take the previous letter in the alphabet each time, you get HACKERS, which makes perfect sense. Encryption works in roughly the same way. We've already seen that a loading system occupies a small area of memory. An encrypted loading system will appear as a block of code which makes absolutely no sense whatsoever. There will also be a short program which changes all this nonsense into workable code so the loading system can be run. Some really tough loading systems, have more than one decrypter, such as the Alkatraz loading system which has 250 of the damn things; in practice I've got through about 25 before going mad and hacking something else (don't worry, there's another way of hacking them which I'll tell you about later.)
To make it easier for you to understand decryption, its best to have a look at a real loading system. As an example, I've chosen Impossaball, which was on the YS#75 covertape. Load up STK at address 50000 (it's a safe address), and see what the BASIC has to say for itself by BLOADing it......
10 CLEAR 64530: LOAD "" CODE:RANDOMIZE USR 64531
20 LOAD "" CODE 16384
21 FOR i=1 TO 40 STEP - RANDOMIZE USR 50000
Fair enough - so enter CLEAR 64530:LOAD "" CODE and start the tape. The first code block loads in, and then - it crashes! What's going on? Don't worry, you have just fallen victim to the first type of encryption - BASIC encryption.
In BASIC, any numerical constant (technotwaddle for "number") between 0 and 65,535 is stored in the memory as follows. First, there is the number in it's ASCII form, which takes between 1 and 5 bytes. So, the number 1234 will appear as #30,#31#,#32,#33 here. Then, come the numbers #0D,#00 and 00 (this is always the case). Then, there is the number stored in it's two byte form (as in machine code). What happens it that the computer prints the ASCII form (which is what you get when you LIST a program), but uses the two-byte form in calculations and expressions. The upshot of all this is that what you see isn't always what you get! As an example, type in this:
1 PRINT 1
Now type POKE 23760,50 (which changes the ASCII value of the number 1 from "1" to "2"). Now if you LIST the program, you will see 1 PRINT 2, but if you RUN the program, the computer will print the number 1 instead!
Needless to say, protection systems use the same idea. Sometimes, the numbers listed are obviously fake (if you list a program and you get something like 0 CLEAR 0:RANDOMIZE USR 0 it's obviously got encrypted BASIC), but some programs, like the Impossaball are not obvious at all until you try executing what you see.
There was a program called *List printed ages ago in YS, but if you haven't got that, I've reprinted it here. So type it in, SAVE it to tape, RUN it and reload the Impossaball BASIC loader.
loading LINE 10 LEN 81
10 CLEAR 25599: LOAD "" CODE: RANDOMIZE USR 64512
20 LOAD "" CODE 16384
21 FOR i=1 TO 40 STEP ????....
Now that's what you really get! Type CLEAR 25599:LOAD "" CODE and load in the first block of code. When that's done, load up your disassembler, and disassemble address 64512 (FC00 hex) to have a look at the decrypter.
FC00 01 99 02 LD BC,#0299 FC03 21 13 FC LD HL,#FC13 FC06 11 14 FC LD DE,#FC14
This part of the program sets up all the initial values for the decrypters in some of the registers.
FC09 1A LD A,(DE)
We've come across brackets before, but briefly what happens here is that the contents of the address with the value of the DE register (which starts of as #FC14) are put into the A register. So now the A register could contain any byte.
FC0A AE XOR (HL)
We haven't seen XOR before, so I'll explain what it does. It is in technical terms a Boolean Operation. You may have seen XOR gates if you studied (or are studying!) Physics, Electronics or Computer Science at school. An XOR gate has two inputs, which can each either be 0 or 1, and one output, which can be either 0 or 1 as well. If the two inputs are the same (0 and 0, or 1 and 1), the output is 0, otherwise it is 1. In machine code, you XOR the A register with a number or contents of a register, and what happens is that each bit in the A register is XORed with the same bit in the number or register contents, and the result is stored in the A register. If you're confused, look at the example below.
Contents of the A register | Number to be XORed with | Result |
00100101 | 01010011 | 01110110 |
(Notice that all the numbers are in binary - see your Spectrum manual for more information about this).
If you still don't understand, just remember that an XOR will change the contents of the A register. That's all you need to remember for now.
Continuing the disassembly...
FC0B 77 LD (HL),A
This puts the value of A (which has just been changed) into the bytes at the address with the value of the HL register (which starts off as #FC13).
So, the routine has basically taken a byte out of a memory location, changed it a bit, and put the altered value back again. This is decryption in its most obvious form - the changed values make up a working machine code program.
FC0C 23 INC HL FC0D 13 INC DE FC0E 0B DEC BC
I don't think I've mentioned INC before, but it's basically the opposite of DEC in that it increases the value in whatever register. So the values in the HL and DE registers are incremented, and the value in the BC register is decremented.
FC0F 78 LD A,B FC10 B1 OR C FC11 20 F6 JR NZ,#FC09
This is a standard piece of code, and it essentially means "If BC isn't 0, then jump to #FC09". In other words, another byte will be decrypted until the value of BC is 0 (which happens when everything has been decrypted), and the decrypter ends.
Continuing the disassembly...
FC13 5C LD E,H FC14 9F SBC A,A FC15 A5 AND L FC16 5B LD E,E FC17 13 INC DE FC18 5B LD E,E ....
Hang on - this code doesn't make sense! It has no relation to the code above, and the instruction LD E,E is pointless anyway. Well, you'll remember that the initial value of decryption is #FC13, which means that all the code from there onwards has to be decrypted. So we'll have to crack the decrypter to go any further.
Sometimes, it is possible to put an EI/RET instruction directly after the decrypter, but this is not possible here, as you will see. So instead, we'll have to move the decrypter somewhere else in memory, and put the EI/RET on the end (in actual fact we don't need the EI because there is no DI command in the decrypter). This is easily done by using the LDIR command. Type in the following program:
1 FOR N=23296 TO 23310:INPUT A:POKE N,A:NEXT N
Now RUN it and enter the following numbers:
33,0,252,17,128,91,1,1,18,0,237,176,54,201,201
The program you have just typed in is this:
LD HL,FC00
LD DE,5B80
LD BC,0012
LDIR
LD (HL),C9
RET
Now RANDOMIZE USR 23296 and the decrypter will be copied to 5B80 and a RET will be stuck on the end. Just RANDOMIZE USR 23424 (5B80 in decimal) to run the decrypter. When the OK message comes up, restart the disassembler and look at FC13 again:
FC13 C3 3A FE JP #FE3A
This jumps to #FE3A, to start the loading.
FE3A F3 DI FE3B 21 00 58 LD HL,#5800 FE3E 11 01 58 LD DE,#5801 FE41 01 FF 02 LD BC,#02FF FE44 36 00 LDIR
As you already know, this makes the screen black.
FE48 CD 81 FE CALL #FE81 FE4B CD 00 80 CALL #8000
If you look at the code at #FE81, you'll see it's a headerless loader. The routine at #8000 prints the loading screen.
FE4E CD 81 FE CALL #FE81 FE51 CD 00 64 CALL #6400
This loads another block (the main game), and prints another screen (the border for the game).
FE54 F3 DI FE55 31 FF FF LD SP,#FFFF
This disables interrupts and changes the stack pointer, so we'll have to change that in the final hack.
FE58 21 00 6D LD HL,#6000 FE5B 11 00 5B LD DE,#5B00 FE5E 01 00 8F LD BC,#8F00 FE61 ED BO LDIR
This is another LDIR command, but if you know how the Spectrum's memory is organised, you will see that the BASIC system variables are overwritten, which means we can't return to BASIC when the game has loaded. Fortunately, there's a short routine to get round this, which I'll explain in a mo.
FE63 21 71 FE LD HL,#FE71 FE66 11 00 F0 LD DE,#F000 FE69 01 10 00 LD BC,#0010 FE6C ED B0 LDIR FE6E C3 00 F0 JP F000
This moves the code from FE71 to F000, and jumps to F000. Obviously, then, the code at FE71 is important.
FE71 21 00 FC LD HL,#FC00 FE74 11 01 FC LD DE,#FC01 FE77 01 FF 01 LD BC,#01FF FE7A 36 00 LD (HL),0 FE7C ED B0 LDIR FE7E C3 00 80 JP #8000
This routine wipes out all the memory from #FC00 to #FC01, but in actual fact you don't need to do this. Then it jumps to #8000, which is the start of the game. So, we can put a machine code routine to put a POKE in, and then jump to #8000.
However, we've got to find the POKE first, and there's the problem that we can't use BASIC because it is overwritten. Luckily, there is a short routine you can use which will cause a NEW to a certain address. Put it into the above program by typing in the following:
1 FOR N=65137 TO 65144:INPUT A:POKE N,A:NEXT N
Then RUN it, and enter these numbers in turn:
243,175,17,0,95,195,203,17
The program you've just typed in is the following:
DI
XOR A
LD DE,#5F00
JP #11CB
DI disables interrupts, XOR A loads A with 0 (think about what would happen if you XORed the value of the A register with itself, LD DE,#5F00 means we want to NEW up to #5F00 (this value can be changed from about #5D00 to #FFFF), and JP #11CB starts the NEW.
Now RANDOMIZE USR 65082 (#FE3A in decimal), and restart the game tape. When the computer resets, you can load STK into address 24320 to find POKEs.
Phew! And that's about all of that protection system cleared up. If you can get your way through that lot, I think you're probably ready to have a go at a "commercial" protection system. First, though, we'll write a complete hack for the game.
10 CLEAR 25599:LOAD "" CODE
This is from the BASIC loader, and it loads in the first machine code block
20 FOR N=23296 TO 23310:READ A:POKE N,A:NEXT N
This line POKEs in the machine code program to move the decrypter.
30 DATA 33,0,252,17,128,91,1,19,0,237,176,54,201,201
And here's the actual machine code itself
40 RANDOMIZE USR 23296
This line calls the decrypter and returns to BASIC
40 FOR N=65137 TO 65143: READ A:POKE N,A:NEXT N
This line POKEs in our hacking program
50 DATA 175,50,??,??,195,0,128
And here's the hacking program, which loads ???? with 0 (which is the infinite lives POKE), and jumps to #8000.
60 RANDOMIZE USR 65082
This starts the whole loading system off, with the POKE firmly in place.
And that's about it. A bit of a long piece of work, but it was worth it!