HOW TO HACK

By Jon North

Sinclair ZX Spectrum
ISSUE 59
This month, I'll be finishing off last month's Bleepload explanation with the hack that you should have got for Beach Buggy. Once I've done that I'll be covering Powerload, and cracking Red LED.
The Bleepload Crack
This is the routine you should have worked out for yourselves. Basically, it takes the patches I explained last month and does them all, in order, so that the JP to the game is overwritten with the infinite lives pokes. To use it, CLEAR 25500: LOAD ""CODE (from the basic loader) then RANDOMIZE USR 32768.
Listing 1 ;Bleepload crack by Jon North
This one's for Beach Buggy

  ORG #8000 ;Start from #8000=32768
  LD A,#CD ;Patch in a CALL
  LD (#CD54),A  
  LD HL,CHECK ;The CALL is to "CHECK"
  LD (#CD55),HL  
  JP #CD00 ;Start the loader
CHECK LD (#FF15),A ;Execute the command overwritten by our patch
  INC HL ;Go past the "JP" instruction
  LD A,(HL) ;A=LSB of JP address
  DEC HL ;Restore HL to its original value
  CP #1B ;Does A=#1B?
  RET Z ;Return if so
  LD A,#32 ;Otherwise remove the patch
  LD (#CD54),A  
  LD HL,#FF15  
  LD (#CD55),HL  
  LD A,#C3 ;C3 is code for JP
  LD (#5D3A),A ;Patch in a JP
  LD HL,BACK ;The JP is to "BACK"
  LD (#5D3B),HL  
  JP #5D00 ;Resume loading and wait for control
BACK LD (#5C6B),A ;Execute the command overwritten by our patch
  LD HL,POKES ;Move the pokes to overwrite the JP to the game
  LD DE,#5D98 ;The JP is at #5D98
  LD BC,END-POKES ;BC=length of the pokes
  LDIR   ;Move the code down
  JP #5D3D ;Resume loading
POKES XOR A ;A=0
  LD (#B336),A ;Infinite fuel
  LD (#C779),A ;Infinite time
  LD A,#C3  
  LD (#CA44),A ;Infinite time
  JP #B1FB ;Start the game
END EQU $  
Note that this routine was written for the Devpac assembler, yours may use different notation.
The Stack
In machine code, you can store values on what is called a "stack" ng. It in fact does a JP HL, so ignore the brackets. HL was previously set to 5E68, so this is where the JP is to.

5E68 LD A,12
5E6A LD (5E93),A
5E6D POP HL
5E6E PUSH HL
6E6F POP DE
6E70 RET
The value at the top of the stack is 5E76, so this is where the RET will go to.

5E76 POP BC
5E77 LD A,(HL)
5E78 NEG
5E79 LD (HL),A
5E7A INC HL
5E7B DJNZ 5E77
5E7D POP HL
5E7E LD (5E78),HL
5E81 POP BC
5E82 LD A,C9
5E84 LD (5E7E),A
5E87 LD A,0
5E89 LD (5E7A),A
5E8C PUSH DE
5E8D POP HL
5E8E RET

Firstly, there is a decrypter from 5E77 to 5E7C, so put a breakpoint at 5E7D before you go any further. You may not recognise the DJNZ 5E77 command at 5E7B. DJNZ stands for Decrement B, and JP if Not Zero, in other words, B=B-1 and then if B doesn't equal 0, JP to 5E77. It is used exactly as JP NZ, JR NZ, CALL NZ or RET NZ would be used. The RET at 5E8E is to 5E77. You may think this strange, seeing as we've just cracked that code, but look closely and you'll see it has been modified. It now reads:

5E77 LD A,(HL)
5E78 RRD
5E7A NOP
5E7B INC HL
5E7C DJNZ
5E77
5E7E RET

If you think that this doesn't decrypt, because there is no LD (HL),A instruction, you should realise that RRD (and RLD) in fact change both A and (HL), so this instruction is like a decrypting instruction (ADD or SUB, for example) and a LD (HL),A all rolled into one. The RET at 5E7E is to 5E12.
5E12 LD HL,5FB4
5E15 LD DE,5FB5
5E18 LD BC,88B8
5E1B LDIR
5E1D POP HL
5E1E LD D,H
5E1F LD E,L
5E20 INC E
5E21 POP BC
5E22 LDIR
5E24 LD B,1E
5E26 POP HL
5E27 LD A,(HL)
5E28 XOR A3
5E2A LD (HL),A
5E2B INC HL
5E2C DJNZ
5E27
You should POKE 5E1B, 5E1C, 5E22 and 5E23 with 0 (otherwise the disassembler will be overwritten). Put a breakpoint at 5E27 to find out where the decrypter changes data- it is at 5E2E, the address immediately after the decrypter. To overcome this, move the block of code 5E27-5E2D to somewhere convenient, put a breakpoint after it and run it from there.
5E2E POP HL
5E2F LD (5E02),HL
5E32 POP HL
5E33 LD (5E05),HL
5E36 SCF
5E37 LD A,7
5E39 CALL 5E00

The routine at 5E00 is a standard headerless loader. IX=9C40, DE=0190, A=7, and the code at 5E07 is the start of the ROM loading routine, followed by a JP to it. It is effectively the same as CALL 0556.
5E3C JP NC,1
5E3D LD HL,9C40
5E40 LD B,FF
5E42 CALL 5E77
5E45 LD B,FF
5E47 CALL 5E77
5E4A DI
5E4B RET
The decrypter at 5E77 is unchanged. The RET at 5E4B is to 9C40. We can do away with the basic loader altogether in the final hack, by loading the second block of code as a standard headerless file, then decrypting it ourselves (as long as the decrypter in the hack is sufficiently different to the decrypter in the basic).
9C40 LD HL,9C52
9C43 LD BC,190
9C46 LD D,A5
9C48 LD A,(HL)
9C49 XOR D
9C4A LD (HL),A
9C4B INC HL
9C4C DEC BC
9C4D LD A,B
9C4E OR C
9C4F JP NZ,9C48
To crack this, change the JP NZ at 9C4F to a JR NZ: RET then CALL the decrypter (put a breakpoint after the call). JR NZ uses only two bytes, whereas JP NZ uses three, leaving us a spare byte for the RET.
9C52 LD HL,9C63
9C55 LD DE,FE52
9C58 LD BC,190
9C5B LDIR
9C5D LD SP,FD80
9C60 JP FE52
FE52 LD A,84
FE54 LD DE,1800
FE57 LD IX,4000
FE5B CALL FEB8
FE5E LD DE,400
FE61 LD IX,5BFF
FE65 CALL FF37
FE68 LD DE,1D4
FE6B LD IX,FE2C
FE6F CALL FF07
The big headerless block that follows, as you can see, is treated as a series of shorter headerless blocks without leader tones. We do not know the code at FE72 yet, because the last headerless file overloads itself. To get around this, put a patch in the equivilent code from 9C63 (which is still there), and load the first part of the game from there. What this means is put a breakpoint at 9C83 and JP to 9C63. Once loaded, the following code is seen:
FE72 LD DE,8440
FE75 LD IX,E1FF
FE79 CALL FF37
FE7C LD DE,12E4
FE7F LD IX,FFFF
FE83 JP FF7D
FF7D LD A,0
FF7F OUT (FE),A
FF81 CALL FE2C
FE2C LD HL,4000
FE2F LD BC,1B00
FE32 XOR A
FE33 XOR (HL)
FE34 LD D,A
FE35 INC HL
FE36 DEC BC
FE37 LD A,B
FE38 OR C
FE39 LD A,D
FE3A JP NZ,FE33
FE3D RET
The routine FE2C-FE3D checks the screen, and comes out with a value in D. It is not a decrypter, because there is no LD (HL),A instruction. The RET at FE3D is to FF84.

FF84 LD HL,FE50
FF87 CP (HL)
FF88 JP Z,FF96
FF8B LD HL,EE48
FF8E LD BC,FFFF
FF91 LD DE,EE49
FF94 LDIR
The way to overcome this is similar to what we did with the decrypter on that first headerless file. Change the JP Z at FF88 to LD D,(HL): JR FF96. It must do that JR Z, because the routine at FF8B blanks out all memory, from EE48-FF94.
FF96 LD HL,FFB0
FF99 LD BC,50
FF9C LD A,D
FF9D XOR (HL)
FF9E LD (HL),A
FF9F INC HL
FFA0 DEC BC
FFA1 LD A,B
FFA2 OR C
FFA3 JP NZ,FF9C
FFA6 CALL FE3E
The routine at FE3E is similar to the one at FE2C. It checks the loaded game and returns a value in E.
FFA9 LD HL,FE51
FFAC CP (HL)
FFAD JP NZ,FF8B
You should POKE FFAD, FFAE and FFAF with 0 (to remove the JP NZ). The value is never used so you don't need to put it in yourself.
FFB0 LD HL,5DC0
FFB3 LD BC,80E8
FFB6 CALL FFEC
FFEC LD A,(HL)
FFED RRD
FFEF INC HL
FFF0 DEC BC
FFF1 LD A,B
FFF2 OR C
FFF3 JR NZ,FFEC
FFF5 RET
This decrypts from 5DC0, then RETs to FFB9.
FFB9 LD HL,5DC0
FFBC LD BC,80E8
FFBF CALL FFF6
FFF6 LD A,D
FFF7 XOR (HL)
FFF8 LD (HL),A
FFF9 INC HL
FFFA DEC BC
FFFB LD A,B
FFFC OR C
FFFD JR NZ,FFF6
FFFF RET
This also decrypts from 5DC0, then RETs to FFC2.
FFC2 LD HL,C700
FFC5 LD DE,4000
FFC8 LD BC,1B00
FFCB LDIR
FFCD LD HL,C6FF
FFD0 LD DE,DCFF
FFD3 LD BC,6700
FFD6 LDDR
FFD8 LD HL,A710
FFDB LD (5C36),HL
FFDE LD BC,3110
FFE1 XOR A
FFE2 SBC HL,BC
FFE4 LD SP,5DBF
FFE7 IM 1
FFE9 JP 6F
006F JP (HL)
We can change the 6F at FFEA to somewhere convenient to put pokes in, then JP (HL) at the end. Make sure, though, that you don't corrupt HL in your pokes, otherwise the JP (HL) will JP to the wrong place.
The Red LED Hack
  ORG E200  
LOAD LD IX,#9C40  
  LD DE,#190  
  LD A,7  
  SCF    
  CALL #556  
  JR NC,LOAD ;This makes it loop back if it didn't load the block properly
  LD HL,#9C40 ;Start byte to decrypt
  LD BC,#1FE ;Length from the basic: FF+FF=1FE
DCRPT LD A,(HL)  
  RRD    
  INC HL  
  DEC BC  
  LD A,B  
  OR C  
  JR NZ,DCRPT  
  LD IX,#9C4F  
  LD (IX),#20 ;20 is code for JR NZ
  LD (IX+1),#F7 ;F7 is offset byte for -8 which in this case is 9C48
  LD (IX+2),#C9 ;C9 is code for RET
  CALL #9C40 ;Do the decrypter
  LD HL,BACK  
  LD (#9C61),HL ;JP to "BACK"
  JP #9C52  
BACK LD HL,LDPCH  
  LD DE,#9C83  
  LD BC,9  
  LDIR   ;Patch in 9 bytes to #9C83 (where we had a breakpoint before)
  JP #9C63 ;Start loading the game
LDPCH LD HL,DCPCH  
  LD (#FE84),HL ;JP to "DCPCH", not FF7D
  JP #FE72 ;Resume loading
DCPCH LD IX,#FE88  
  LD (IX),#56 ;56 is code for LD D,(HL)
  LD (IX+1),#18 ;18 is code for JR
  LD (IX+2),#B ;0B is offset for +11, in this case FF96
  LD (IX+#25),#C9 ;Do a RET instead of three 0'S
  CALL #FF7D ;Start decrypting the next bit
  LD HL,POKES  
  LD (#FFEA),HL ;Patch the JP 006F to come back
  JP #FFB0 ;Start decrypting the game
POKES XOR A ;Infinite time and energy pokes
  LD (#7F72),A  
  LD (#7CB8),A  
  LD (#A3E9),A  
  LD A,#C9  
  LD (#7FEA),A  
  JP (HL)  
Wasn't that good? I'm not sure what I'll be doing next month, it'll probably be Alkatrazz. I'm saving Speedlock for a massive Speedlock Special in the Christmas issue, covering all 7 of them! In the meantime, drop me a line if you have any ideas or any questions (sae if you want a reply)- Jon North, How-2-Hack (Should've Been Called Hacking Away), YS, 30 Monmouth Street, Bath, Avon BA1 2AP. Byee...
Sinclair ZX Spectrum

  Previous Page Back Next Page