HOW TO HACK

By Jon North

Sinclair ZX Spectrum
ISSUE 64
As promised, this month I'm doing SoftLock. So go dig out an old Firebird game then come back. I'm doing Chimera as an example.
The Basic Bit
First up, *Load and *List as usual...
CHIMERA LINE 0 LEN 355 0 BORDER 0: INK 0: PAPER 0: CLS : PRINT AT 21,11;"*LOADING*": POKE 20107,255: RANDOMIZE USR (PEEK 23627+256*PEEK 23628) 1 SAVE "CHIMERA" LINE 0 ...so we see it runs from 23923, which is 5D73 hex.

5D73 LD IYL,A
5D75 DEC SP
5D76 DEC SP
5D77 POP BC
5D78 LD HL,0000
5D7B PUSH HL
5D7C POP IX
5D7E LD A,2E
5D80 LD IXH,40
5D83 SLA A
5D85 LD D,(IX+0)
5D88 LD E,(IX+1)
5D8B INC IX
5D8D INC IX
5D8F ADD HL,DE
5D90 CP IXH
5D92 JR NZ,5D85

This checks the screen (IXH=40 hex, which is the start of the screen area), so put a breakpoint at 5D94, return to Basic then GOTO 0 (because the screen is set up by the basic). When control returns to the disassembler, BC is 5D73 and DE is CA4E. These two values are used by the decrypter which follows.

5D94 EX DE,HL
5D95 LD HL,003D
5D98 ADD HL,BC
5D99 LD IXH,B
5D9B LD IXL,C
5D9D LD C,32
5D9F LD A,(HL)
5DA0 XOR E
5DA1 ADD A,C
5DA2 LD (HL),A
5DA3 LD C,A
5DA4 INC HL
5DA5 INC DE
5DA6 LD A,(HL)
5DA7 XOR D
5DA8 ADD A,C
5DA9 LD (HL),A
5DAA LD C,A
5DAB INC HL
5DAC INC DE
5DAD CP 48
5DAF LD A,A
5DB0 JR NZ,5D9F

This decrypts two bytes at a time, starting at 5DB0 (the JR NZ instruction). When it comes to cracking it in a routine, we'll move it to somewhere convenient, stick the JR NZ on the end and run it from there. As it is, firstly single-step through it, then move 5D9F-5DB1 to somewhere, stick a breakpoint on the end and run it from there.
When finished, you'll see the following code at 5DB2...

5DB2 LD SP,0000
5DB5 LD (5C3D),SP
5DB9 LD HL,0556
5DBC LD DE,FF00
5DBF LD B,H
5DC0 LD C,L
5DC1 LDIR
5DC3 LD H,FF
5DC5 LD DE,007E
5DC8 ADD IX,DE
5DCA PUSH IX
5DCC POP DE
5DCD LD B,05
5DCF LD A,(DE)
5DD0 LD L,A
5DD1 LD A,(HL)
5DD2 SRL (HL)
5DD4 SRL (HL)
5DD6 SUB (HL)
5DD7 LD (HL),A
5DD8 INC DE
5DD9 DJNZ
5DCF
5DDB LD B,17
5DDD LD A,(DE)
5DDE INC DE
5DDF LD L,A
5DE0 LD A,(DE)
5DE1 INC DE
5DE2 LD (HL),A
5DE3 DJNZ
5DDD
5DE5 XOR A
5DE6 LD L,A
5DE7 DEC A
5DE8 LD IX,4000
5DEC LD DE,1C00
5DEF SCF
5DF0 JP (HL)

Some of this code will be new to you, but what it does is to make a copy of the ROM loader (at 0556) at FF00, by the LDIR at the start. It then uses a table to change some of the timing constants so that it turboloads (which is what the rest of the code does). Finally, it sets IX and DE to load from 4000-5C00 (the screen and a bit of code) and off it goes.

FF00 INC D
FF01 EX AF,AF'
FF02 DEC D
FF03 DI
FF04 LD A,0F
FF06 OUT (FE),A
FF08 LD HL,5B00
FF0B PUSH HL

This is the start of the ROM loader, and how it works is unimportant, All you need to know is that the PUSH HL at FF0B PUSHes the return address for when loading finishes, which in this case is 5B00. To find the code at 5B00 (remember it hasn't been loaded yet), change the 5B00 at FF09 to something convenient, where you have placed a breakpoint. Once loaded, the code at 5B00 looks a bit like this...

5B00 DEC SP
5B01 DEC SP
5B02 CALL FF70
5B05 LD A,L
5B06 LD IXL,A
5B08 CALL FF70
5B0B LD A,L
5B0C LD IXH,A
5B0E PUSH IX
5B10 CALL FF70
5B13 LD A,L
5B14 LD IXL,A
5B16 CALL FF70
5B19 LD A,L
5B1A LD IXH,A
5B1C LD A,IXL
5B1E OR IXH
5B20 RET Z
5B21 POP DE
5B22 JP FF70

This code loads four bytes, and treats them as new values of IX and DE. These new values then get loaded as another headerless block (like Powerload). The DEC SP: DEC SP at the start ensures that this routine is always what control is returned to once the block has loaded, unless one of the following happens:

1. The code at 5B00 gets overloaded, in which case control is returned to the new code

2. FFFE and FFFF get overloaded. These two addresses hold the return address, and if overloaded, control will return to the address of the new values

3. The loaded value for IX is zero, in which case the loaded value for DE is RETed to. To find out which of these it is, we are going to write a simple routine which will load those values and store them somewhere, and which will load code at 5B00 but nowhere else.

FE00 LD IX,4000
FE04 LD DE,1C00
FE07 SCF
FE08 LD HL,FE11
FE0B LD (FF09),HL
FE0E JP FF00
FE11 LD A,(5B24)
FE14 CP FF
FE16 JR Z,FE1B
FE18
FE1B LD A,28
FE1D LD (5B23),A
FE20 LD A,FE
FE22 LD (5B24),A
FE25 JP 5B00
FE28 LD (FEF0),SP
FE2C LD SP,(FEF2)
FE30 PUSH IX
FE32 PUSH DE
FE33 LD (FEF2),SP
FE37 LD SP,(FEF0)
FE3B LD A,IXH
FE3D CP 5B
FE3F JR NZ,FE4E
FE41 LD A,DD
FE43 LD (FF58),A
FE46 LD A,75
FE48 LD (FF59),A
FE4B JP FF70
FE4E XOR A
FE4F LD (FF58),A
FE52 LD (FF59),A
FE55 JP FF70

Before using this routine, POKE 65266,254 so that you know where the stack is. To find out where the game loads to:

10 FOR F=65020 TO 0 STEP -4: IF PEEK F THEN PRINT PEEK (F+2)+256*PEEK (F+3);",";PEEK F+256*PEEK (F+1): NEXT F

The program will give you the following results:

56320,6232
61000,2000
64900,400
23296,100
65455,48
23324,2
39936,16384
23324,5
23296,256
63000,800
64000,1000
23552,16384
23324,2
23296,92

As you can see, 23296 is loaded over a few times, but loading continues. We can therefore assume that these blocks do not alter the code there in any way, or at least if they do, not sufficiently enough to worry about. Loading finished when that block of 92 bytes was loaded, so this must be different. One disassembly later....

5B00 XOR A
5B01 OUT (FE),A
5B03 LD HL,F870
5B06 LD DE,F870
5B09 LD BC,9470
5B0C LD IX,5AFF
5B10 LD A,FF
5B12 LD R,A
5B14 LD A,(HL)
5B15 SUB (IX+0)
5B18 XOR IYL
5B1A RLCA
5B1B XOR IYH
5B1D LD (DE),A
5B1E DEC HL
5B1F DEC DE
5B20 DEC BC
5B21 DEC IX
5B23 LD A,IXH
5B25 OR IXL
5B27 JR NZ,5B2C
5B29 LD IXH,5A
5B2C LD A,B
5B2D OR C
5B2E JR NZ,5B14
5B30 LD HL,F8D4
5B33 LD DE,5B01
5B36 LD BC,00FF
5B39 LD SP,5FB4
5B3C PUSH HL
5B3D LD HL,5B00
5B40 LD A,C9
5B42 LD (HL),A
5B43 LDIR

This routine firstly decrypts the game, then sets the stack pointer and PUSHes the return address for the game (the PUSH at 5B3C), then fills the printer buffer with RETs. To stick pokes in, simply move them down into 5B3D, then stick a RET at the end to start the game.
The Chimera Hack
This routine loads the basic, then moves the decrypter to a convenient address. Once there, the JR NZ at the end is put in manually, then the entry values are put in and it is CALLed. It then puts a RET at the end of the routine which creates the turboloader and CALLs it, and once in memory the return address is patched and it starts loading. After each short headerless and leaderless block is loaded, it checks a value in the printer buffer to check whether or not the game decrypter is there - if it is then loading must have finished and the infy lives pokes are stuck on the end of the decrypter. Otherwise, control is returned to 5B00 so that the next block can be loaded. The routine is ORGed to 63801, because this is a safe place which never gets loaded over (as can be seen by the table of load addresses). Note that before the game decrypter is run, the hacking routine is deleted, because the game is decrypted through it.
       
  ORG 63801  
LOAD LD IX,#5CCB  
  LD DE,355  
  LD A,#FF  
  SCF    
  CALL #556 ;Load basic with a standard headerless load
  JR NC,LOAD ;Go back if load unsuccessful
  LD HL,#5D99 ;Start of decrypter
  LD DE,#4600 ;Bung it in the screen because it will be safe
  LD BC,#17 ;Length of decrypter
  LDIR   ;Copy it down
  EX DE,HL ;HL is now the end of the copy
  LD (HL),#20 ;20 is code for JR NZ
  INC HL ;Point to next address
  LD (HL),#ED ;Offset for the JR NZ
  INC HL ;Point to next address
  LD (HL),#C9 ;Stick a RET on the end
  LD HL,#5DB0 ;Initial value of HL
  LD BC,#5D73 ;Initial value of BC
  LD DE,#CA4E ;Initial value of DE
  CALL #4600 ;Do the decrypter
  LD A,#C9 ;C9 is code for RET
  LD (#5DF0),A ;Stick a ret at the end of the turboload creator
  CALL #5DB9 ;Create the turboload
  LD HL,NEWRET ;Patch in a new return address
  LD (#FF09),HL ;The patch is at FF09
  LD SP,0 ;Initial value of SP
  JP #FF00 ;Start loading
NEWRET LD A,(#5B32) ;See if there's any code here
  CP #F8 ;Check if the byte at 5B32 is a F8
  JP NZ,#5B00 ;If not, load another block
  LD HL,POKES ;Otherwise copy the pokes down
  LD DE,#5B3D  
  LD BC,END-POKES  
  LDIR    
  JP #5B3D+DELETE-POKES ;Need to delete this routine before decrypting the game
POKES XOR A ;A=0
  LD H,A  
  LD L,A ;HL=A=0
  LD (#E6EE),A ;Infy time poke
  LD (#EE20),HL ;Infy food poke
  LD (#EDF1),A  
  LD (#EF9C),HL ;Infy water pokes
  RET   ;To the game
DELETE LD HL,63801 ;Start of this routine
  LD DE,63802 ;Next byte
  LD BC,END-63801 ;Length of routine
  LD (HL),0 ;Put a 0 in at the start
  LDIR   ;Delete the rest of it
  JP #5B00 ;You can now decrypt the game
END EQU $  
Well, that's another one down. Between now and next month, get hold of a copy of MoonStrike, because I'm going to go through the Movieload on it (be warned though, it's quite a tough nut to crack). Ideas, probs, offers of dates and unwanted +3's should be sent to Jon's Hacking Bit at the usual YS address. See you next month.
Sinclair ZX Spectrum

  Previous Page Back Next Page