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 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
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
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
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).
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
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
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
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
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.
VARS EQU #6212 ;THIS IS THE ADDRESS OF THE START
; OF THE CODE IN THE BASIC
LOAD LD IX,#5CCB
JR NC,LOAD ;LOOP BACK IF BASIC HASN'T LOADED
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 A,#C4 ;R WILL BE INCREMENTED ONCE BY THE
; CALL TO THE DECRYPTOR
CALL VARS+151 ;DO THE FIRST DECRYPTOR AND RET PO
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
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"
LD R,A ;PUT THE NEW VALUE BACK INTO R
CALL #FCC4 ;DO THE SECOND DECRYPTOR AND RET PO
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
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 $