Speedlock 1


This is the one with the 'clicking' leader tone. The earlier ones had normal colpurs, later ones used red, blue and black. It makes no odds though - they're all virtually identical. I'll be doing Army Moves because it was on and old Covertape.


The Small Basic Bit
Firstly, *Load and *List as usual.

"army 1" LINE 0 LEN 205
0 PAPER 0: INK 0: BORDER 0: CLS: PRINT USR 23829.1: LOAD ""
All this does is a CLEAR 65535 in machine code. Indeed, the earlier ones were just CLEAR 65535: LOAD "".


The Big Basic Bit
*Load and *List the second bit of basic.

"army 1" LINE 0 LEN 1770
0 REM
0 BORDER 0: PAPER 0: INK 0: BRIGHT 0: CLS: POKE 23624,0
0 POKE (PEEK 23641.1+256*PEEK 23642.3),PEEK 23649.2: POKE
(PEEK 23641.1+256*PEEK 23642.3)+1,PEEK 23650.2
0 POKE (PEEK 23613.1+256*PEEK 23614.2),PEEK 23627.3: POKE
(PEEK 23613.1+256*PEEK 23614.2)+1,PEEK 23628.3
0 POKE 23662.1,PEEK 23618.1: POKE 23663.2,PEEK 23619.1: POKE
23664.1,PEEK 23621.1
40079FINT EXP CAT FN INKEY$ZHG-2.2786987E+35t INPUT
@DINT EXP DATA ...
Obviously, line 40079 is a load of crap which can't possibly be used as basic. Except it is. Take a look at the fourth line 0 (the one with PEEK 23613 in it) and refer to the list os System Variables at the back of the Spectrum manual. You'll see that 23613 is called ERRSP, which stands for ERRor Stack Pointer. I discussed stacks in issue 59, so have a look and come back.
Whenever an error occurs (such as the Nonsense in Basic error at line 40079), the ROM sets the Stack Pointer (SP) to PEEK 23613+256*PEEK 23614 and RETs. In fact, you could write an 'on error goto' routine in your own basic programs by directing it to a bit of code of your own. That fourth line 0 sets the address it RETs to, to PEEK 23627+256*PEEK 23628. PRINT this value, and that's the start of the machine code. Before you start hacking it though, put a breakpoint right at the start, return to basic and GOTO 0. Why? Because the machine code assumes certain values in certain registers, which are set by the execution of all those POKEs.


PO and PE
A JP PO,address will JP if one of the following is true...
1) After a LD A,I; LD I,A; LD A,R; LD R,A if interrupts are disabled
2) After a CPI; CPD; LDI; LDD; CPIR; CPDR; LDIR; LDDR if BC=0
3) After an AND; OR; XOR if there are an even number of set bits (ie 1's)
4) After an ADD; SUB; INC; DEC if result is above 127 or below -128

Similarly, a JP PE,address will JP if one of the above is not true. You will need to know these when hacking Speedlock.


What a load of crap!
The start of the code for Army Moves is 6212 hex. It looks like a load of garbage, but that's the idea - you're supposed to think you're at the wrong address. The first bit of meaningful code occurs at 62A9, but watch out for the following along the way...

RET PO If your disassembler disables interrupts, POKE this with 0 or it'll crash.

LD R,A You need to keep track of the value in R from now on (take a look at last month's column if you're unsure).

LDIR This one blanks out all memory (HL=63B8, DE=63E0, BC=9B63). To overcome it, simply make BC=63 (ie the first two digits, the value of B, become 0) then add 6300 to HL and DE afterwards because the values are needed. R will remain intact, so don't worry about that.

IM 2 If your disassembler enables interrupts, POKE this with 0 or it'll crash. Note that you need to POKE both the ED and the 5E. Make sure though, that your disassembler keeps track of R (DevPac doesn't, so you'll have to calculate the value yourself if you're using it).

RET PE Again, POKE with 0 if your disassembler enables interrupts (rule one above).


Changes, changes
Now we see the first decryptor (in fact, the only bit of code that actually looks like code)...
62A9 CALL PO,3008
62AC LD   A,R
62AE XOR  (HL)
62AF LD   (HL),A
62B0 LDI
62B2 RET PO
62B3 DEC SP
62B4 DEC SP
62B5 RET PE
62AC-62AF is a standard R register decryptor. LDI is similar to LDIR, but doesn't repeat for each byte. In other words, the byte at address HL is POKEd into address DE, HL and DE are incremented and BC is decremented. If BC=0, it has finished decrypting and the RET PO will ret, to FCA3 (from the PUH HL at 62A7). Otherwise, the stack pointer is decremented twice (so it points to the return address for the routine at 3008, ie 62AC) and the RET PE is executed.
Note that at 62A9, you should have the following values (the rest are unimportant): HL=5EFD, DE=FCA3, BC=0315, R=C5.
Control comes out of the decryptor to FCA3, where the decrypted code is placed (by repeating the LDI instruction. Doing LDI 2000 times is the same as doing a LDIR with BC=2000). The code at FCA3 is another load of garbage, followed by exactly the same decryptor as the one at 62A9, but now with the following values - HL=FCD1, DE=FCD1, BC=02E7, R=B8. The RETurn address for the RET PO is set at FCAC, with LD IY,FCD1 followe by EX (SP).HL. Once you've decrypted that, the final decryptor is seen.
FCD1 LD   BC,(FFB7)
FCD5 LD   B,89
FCD7 LD   DE,FCA3
FCDA PUSH BC
FCDB LD   A,(DE)
FCDC PUSH DE
FCDD LD   DE,038C
FCE0 SUB  C
FCE1 LD   HL,FD2C
FCE4 XOR  (HL)
FCE5 LD   (HL),A
FCE6 INC  HL
FCE7 DEC  E
FCE8 JP   NZ,FCE4
FCEB DEC  D
FCEC JP   NZ,FCE4
FCEF POP  DE
FCF0 INC  DE
FCF1 POP  BC
FCF2 LD   C,A
FCF3 DEC  B
FCF4 JP   NZ,FCDA
FCF7 LD   HL,0000
FCFA LD   DE,FF37
FCFD LD   B,81
FCFF PUSH BC
FD00 LD   A,(DE)
FD01 INC  DE
FD02 LD   B,00
FD04 LD   C,A
FD05 ADD  HL,BC
FD06 POP  BC
FD07 DEC  B
FD08 JP   NZ,FCFF
FD0B LD   DE,319C
FD0E AND  A
FD0F SBC  HL,DE
FD11 EX   AF,AF'
FD12 LD   HL,FCD1
FD15 LD   B,3D
FD17 LD   (HL),C9
FD19 INC  HL
FD1A DJNZ FD17
FD1C EX   AF,AF'
FD1D JP   Z,FF37
FD20 LD   IY,0000
FD24 LD   (IY+75),00
FD28 INC  IY
FD2A JR   FD24
Firstly, consider the loop FCE1-FCEC; it is a very easy decryptor (R is no longer needed), decrypting 038C bytes from FD2C. But! Now consider the larger loop, FCD7-FCF6. The initial value of A for the decryptor at FCE4 is taken from the byte at address DE, starting at FCA3, for 89 bytes. Surprise, surprise, the exact length of the decryptor. There are two ways around this. Firstly, we could copy the decryptor somewhere, patch all those JP NZ's to JP to the equivalent address of the copied decryptor, or alternatively copy the decryptor somewhere, patch in the new address as the start value of DE (at FCD8 anf FCD9) then run the decryptor at FCD1. The second approach is quicker to get going (and will make the final hack smaller) so I'll go for that. The breakpoint you want to put in is at FB1E - the JP Z address is the start of the turboloader.
If you are hacking a Speedlock with normal loading colours, patch the JP to the game as normal and start it loading (ie JP FF37). If you're doing the one with the red, blue and black border, the patch is totally different, so listen up.


Setting the table
The main game file loads as a series of headerless blocks, with tiny leader tones (similar to Powerload in issue 59). The values of IX and DE are stored in a table, and taken out one by one to be loaded. To find the table, search the code for FD 21 (ie LD IY,address). The third one LD's IY with the address of the table. To find out where it loads to...
10 FOR f=address TO 1e9 STEP 5
20 PRINT PEEK f+256*PEEK (f+1);",";PEEK (f+2)+256*PEEK (f+3)
30 IF PEEK (f+4) THEN NEXT f
Incidently, PEEK (f+4) holds the 128K page number.
Find a safe place to put your POKEs (probably about 5D00ish) and put them there. Now search the code for ED 53 which is code for LD (address),DE. The instruction directly above it is a LD DE,address. Change the address to the address of your pokes, and you can now load the game. The JP to the game must be to the address you overwrote (ie the value of DE). If you are suing HL to put your pokes in, you MUST firstly PUSH HL then POP HL before the JP, because the value of HL is checked to see whether the game has loaded properly or not.


The Army Moves hack
I've made this hack as general as possible. All you should need to change is the value of VARS. Make sure you CLEAR 6e4 before you run it (RANDOMIZE USR 3e4), or the stack will get overwritten by the loading system.
ORG   30000

VARS  EQU  #6212        ;THIS IS THE ADDRESS OF THE START
                        ; OF THE CODE IN THE BASIC

LOAD  LD   IX,#5CCB
      LD   DE,1770
      LD   A,255
      SCF
      CALL #556
      JR   NC,LOAD      ;LOOP BACK IF BASIC HASN'T LOADED
                        ; PROPERLY
      DI                ;TO PRESERVE R
      LD   HL,#5EFD     ;THE INITIAL VALUES FOR THE FIRST
                        ; DECRYPTOR ARE PUT IN MANUALLY
      PUSH HL           ;KEEP THIS VALUE TEMPORARILY
      LD   DE,#FCA3
      LD   BC,VARS-#5EFD
      LD   A,#C4        ;R WILL BE INCREMENTED ONCE BY THE
                        ; CALL TO THE DECRYPTOR
      LD   R,A
      CALL VARS+151     ;DO THE FIRST DECRYPTOR AND RET PO
                        ; BACK
      LD   HL,#FCD1     ;PUT THE INITIAL VALUES FOR THE
                        ; SECOND DECRYPTOR
      LD   DE,#5F2B     ;THIS WAS #FCD1 BUT DOING THIS
                        ; WILL MAKE ANOTHER COPY OF THE CODE
      LD   BC,#2E7
      LD   A,R
      ADD  A,21         ;COMPENSATE FOR THE EXTRA CODE
                        ; WE'VE EXECUTED AND FOR NOT
                        ; EXECUTING FCA3-FCC3
      SET  7,A          ;R ALWAYS PRESERVES BIT 7, AND SO
                        ; MUST WE. THIS COULD BE AN "OR 128"
                        ; INSTRUCTION
      LD   R,A          ;PUT THE NEW VALUE BACK INTO R
      CALL #FCC4        ;DO THE SECOND DECRYPTOR AND RET PO
                        ; BACK
      POP  HL           ;TAKE THAT 5EFD WE PUSHED ONTO THE
                        ; STACK EARLIER
      LD   (#FCD8),HL   ;THE CODE AT 5EFD IS NOW AN EXACT
                        ; COPY OF THE CODE AT FCA3
      DEC  (HL)         ;THE JP Z NOW READS RET (CA IS THE
                        ; CODE FOR JP Z AND C9 IS CODE FOR
                        ; RET)
      CALL #FCD1        ;DO THE FINAL DECRYPTOR
      LD   HL,POKES     ;COPY THE POKES TO A SAFE PLACE
      LD   DE,#FCD1     ;SUCH AS FCD1
      LD   BC,END-POKES ;BC=LENGTH OF THE POKES CODE
      LD   (#FE9F),DE   ;FE9F IS THE ADDRESS OF THE NUMBER
                        ; IN THE LD DE,GAMEJP COMMAND
      LDIR              ;MOVE THE POKES
      JP   #FF37        ;AND NOW LOAD THE GAME
POKES EQU  $            ;PUT YOUR INFI LIVES POKES HERE BUT
                        ; REMEMBER TO PRESERVE HL
      JP   #FF7A        ;THIS IS THE ORIGINAL ADDRESS FOR
                        ; THE LD DE,NUMBER
END   EQU  $