Indoor Sports (Hacking Tips #9)

By Mr. Spock

Originally published in EUG #71

Indoor Sports - After the Basic LOADER program, a file called SCREEN at &A00 decrypts and runs itself using a fancy EOR method, which is discussed in detail.

1. SCREEN Disassembly

0A00 LDA #15
0A02 LDX #0
0A04 JSR OSbyte	\*FX15,0 clear keyboard
0A07 LDX #0
0A09 SEI	\disable interrupts

Location &A0B contains the low byte of the data after the LDA instruction (i.e. offset into page &B) and is INCremented at &A49.

0A0A LDA &B00	\Load encrypted byte from page &B.
0A0D CLC

Location &A0F initially contains 33, being the data after the ADC instruction at &A0E, and is DECremented at &A46.

0A0E ADC #33
0A10 EOR &0A00,X

Location &A14 initially contains 78, being the data after the EOR instruction at &A13, and is INCremented at &A43.

0A13 EOR #78
0A15 SEC
0A16 SBC &0A14
0A19 EOR &0A00,X
0A1C EOR #43
0A1E CLC
0A1F ADC #33
0A21 EOR #102
0A23 SEC
0A24 SBC #13
0A26 EOR &0A00,X
0A29 INX
0A2A BNE &0A0D

Location &A2D contains the low byte of the data after the STA instruction, i.e. offset into page &B.

0A2C STA &0B00	\Store decrypted byte in page &B

This section puts the instruction JMP &0A5B at locations &287-9. When the BREAK key is pressed, if location &287 contains a JMP instruction (opcode &4C) the computer jumps to that address, which is &0A5B in this case.

0A2F LDA #1
0A31 STA &0258
0A34 LDA #&4C	\JMP
0A36 STA &0287
0A39 LDA #&5B
0A3B STA &0288
0A3E LDA #&0A	\&0A5B
0A40 STA &0289

The next section increments the low bytes of the page of memory (page &B) being decrypted.

0A43 INC &0A14	\Increment EOR value
0A46 DEC &0A0F	\Decrement ADC value
0A49 INC &0A0B	\Increment load address low byte
0A4C INC &0A2D	\Increment store address high byte
0A4F BNE &0A0A	\Loop till done.

A checksum is performed and if the byte at &0BFF is not 69 then the code branches to &A5B, otherwise execution continues at &B00.

0A51 LDA &0BFF
0A54 CMP #69
0A56 BNE &0A5B
0A58 JMP &0B00

If the checksum fails and/or the Break key is pressed, the computer goes to &A5B which is a memory-clearing lock-up routine. The RAM &0B00 to &7FFF is cleared, then cleared again ad infinitum.

0A5B LDA #0
0A5D STA &70
0A5F LDA #&0B
0A61 STA &71	\Set address to &B00
0A63 LDY #0
0A65 LDA #0
0A67 STA (&70),Y	\Store zero in memory
0A69 INY
0A6A BNE &0A65	\until done one page
0A6C INC &71
0A6E LDA &71	\increment page number
0A70 CMP #&80	\until at end of RAM	
0A78 BNE &0A65
0A74 BEQ &0A5B	\Loop forever

2. Zapping the tape protection

It is not possible to poke an RTS into the code to return to Basic once the decryption is done, that is, we can't do ?&A58=96:CALL&A 00. This is because this would modify a byte whose value is essential to the decryption. The solution is to make a copy of the relevant piece of code and add some instructions to enable it run at its new location, and execute the copy. (Compare with hacking note on Boulderdash.)

   10 *L.SCREEN A00
   30 REM Make copy of decryption code
   40 FOR X%=0 TO &42:X%?&1100=X%?&A00:NEXT
   50 P%=&1143:[INC&A14:INC&1114
   60 DEC&A0F:DEC&110F
   70 INC&A0B:INC&110B
   80 INC&A2D:INC&112D
   90 BNE&110A:RTS
   90 ]CALL&1100

You can now inspect the decrypted page &B in a disassembler, or save it to disc for future reference:

	*SAVE SCREENZ A00+200

Note that some further checks are made at &B00:

0B00 LDA &0258	\Check if Escape key has been disabled
0B03 CMP #1	\by examining location &258
0B05 BEQ &0B0A	\If it has, continue
0B07 JMP &0A5B	\else jump to memory clearing lock-up routine

The next section switches off all foreign ROMs in case the user has installed their own one, perhaps containing a utility to take a snapshot of the memory.

0B0A LDX #0
0B0C LDA #0
0B0E STA &02A1,X
0B11 INX
0B12 CPX #15
0B14 BNE &0B0C

Now we see a commonly-used piece of code to restore the default vectors, in case the user has fiddled with any of them and thus potentially enabling them to gain access to the game code. The address of the default vector table - the table that holds the default ve ctor addresses - is at &FFB7/8 in the operating system ROM, and location &FFB6 contains the number of default vectors.

\Restore default vectors.
0B16 LDY &FFB6
0B19 LDA &FFB7	\Default vector table lo
0B1C STA &70
0B1E LDA &FFB8	\Default vector table hi
0B21 STA &71
0B23 LDA (&70),Y	\Load vector addresses
0B25 STA &200,Y	\Store in page 2
0B28 DEY
0B29 BNE &8023	\loop until done all of them.

Finally we reach the actual program which goes into Mode 4, turns foreground colour green, and prints some messages. CHR$6 marks the end of the VDU sequence.

0B2B LDX #0
0B2D LDA &0B7F,X	\Get VDU code from &B7F
0B30 JSR OSwrch	\print it
0B33 INX
0B34 CMP #6	\Was ASCII code 6?
0B36 BNE &0B2D	\No so continue with next VDU code.

(&B38-7E is discussed below)

0B7F EQUB 22,4	\MODE 4
0B81 EQUB 19,1,2,0,0,0	\Foreground colour green
0B87 EQUB 31,0,2
0B8A EQUS"  TYNESOFT SOFTWARE 1988 PRESENTS ..."
0BB0 EQUB 31,0,7
0BB3 EQUS "      ...=== INDOOR SPORTS ===..."
0BD4 EQUB 31,0,12
0BD7 EQUS "         ELECTRON TAPE VERSION"
0BF5 EQUB 31,0,17
0BF8 EQUB "     THE GAME WILL FOLLOW SHORTLY"
0C19 EQUB 28,5,31,18,20	\Text window
0C1E EQUB 6	\End of sequence marker

The file -TyneSoft- is *LOADed to &1100, decrypted and executed by JuMPing to &1208.

0B38 LDX #&45
0B3A LDY #&0B	\CLI at &B45
0B3C JSR OScli
0B3F JSR &0B57	\Decrypt and
0B42 JMP &1208	\Execute it
\Filename terminated by Carriage Return
0B45 EQUS "L.-TyneSoft- 1100"
0B56 EQUB &0D

Here is the decryption routine, simple this time as it just EORs every byte of the code the two bytes of a 16-bit seed, initially &3 398 and decremented by 2 for each byte decoded.

0B57 LDA #&00
0B59 STA &70
0B5B TAY
0B5C LDA #&11	\Start address=&1100
0B5E STA &71
0B60 LDA (&70),Y
0B62 EOR #&33	\Decryption seed hi
0B64 EOR #&98	\Decryption seed lo
0B66 STA (&70),Y
0B68 DEC &0B65
0B6E DEC &0B65	\
0B6E BNE &0B73	\
0B70 DEC &0B63	\Subtract 2 from decryption seed
0B73 INY
0B74 BNE &0B60
0B76 INC &71
0B78 LDA &71
0B7A CMP #&27	\Have we reached &2700?
0B7C BNE &0B60	\no, carry on.
0B7E RTS	\Yes, return.

3. Zapping the Other Loader Files

The loader for Bowling is a simple unencrypted file and will not be discussed here. However, the loaders for the other three games - Darts, Tennis, and Airball - employ the same method described in section 1 and the code is identical except (A) it runs at &E00 and (B) JMP &287 instead of JMP &A5B is poked into locations &287-9, i.e. a jump instruction to the address of itself. Here is a disassembly for completeness:

0E00 LDA #15
0E02 LDX #0
0E04 JSR OSbyte	\*FX15,0 clear keyboard
0E07 LDX #0
0E09 SEI	\disable interrupts
0E0A LDA &F00	\Load encrypted byte from page &B.
0E0D CLC
0E0E ADC #33
0E10 EOR &0E00,X
0E13 EOR #78
0E15 SEC
0E16 SBC &0E14
0E19 EOR &0E00,X
0E1C EOR #43
0E1E CLC
0E1F ADC #33
0E21 EOR #102
0E23 SEC
0E24 SBC #13
0E26 EOR &0E00,X
0E29 INX
0E2A BNE &0E0D
0E2C STA &0F00
0E2F LDA #1
0E31 STA &0258	\Disable Escape
0E34 LDA #&4C	\JMP opcode
0E36 STA &0287
0E39 LDA #&87	\to &287
0E3B STA &0288
0E3E LDA #&02
0E40 STA &0289
0E43 INC &0E14	\Increment EOR value
0E46 DEC &0E0F	\Decrement ADC value
0E49 INC &0E0B	\Increment load address low byte
0E4C INC &0E2D	\Increment store address high byte
0E4F BNE &0E0A	\Loop till done.
0E51 JMP &0F00

The code at &F00 varies according to the game being loaded. An example for Darts is given below.

0F00 LDA &0258
0F03 CMP #1
0F05 BEQ &0F0A	\Check that Escape has been disabled
0F07 JMP &0E54	\If not, jump to loop-forever routine.
\Switch out any foreign ROMs (as discussed previously)
0F0A LDX #0
0F0C LDA #0
0F0E STA &02A1,X
0F11 INX 
0F12 CPX #15
0F14 BNE &0F0C
\Restore default vectors (as discussed previously)
0F16 LDY &FFB6
0F19 LDA &FFB7
0F1C STA &70
0F1E LDA &FFB8
0F21 STA &71
0F23 LDA (&70),Y
0F25 STA &0200,Y
0F28 DEY 
0F29 BNE &0F23
\Load in the game files
0F2B LDX #&49
0F2D LDY #&0F	\string at &F49
0F2F JSR OScli	\*L.Dart1 800 
0F32 LDX #&55
0F34 LDY #&0F
0F36 JSR OScli	\*L.Dart2 5630 
0F39 JSR &0B71
0F3C LDX #&62
0F3E LDY #&0F	\*L.Dart3 1100
0F40 JSR OScli 
0F43 JSR &0F70	\Decrypt
0F46 JMP &3F1C	\and finally execute

0F49 EQUS"L.Dart1 800"+CHR$&0D
0F55 EQUS"L.Dart2 5630"+CHR$&0D
0F62 EQUS"L.Dart3 1100"+CHR$&0D

\Routine to decrypt code at &1100-&4F00
0F70 LDA #&00
0F72 STA &70
0F74 TAY 
0F75 LDA #&11	\Start address &1100.
0F77 STA &71
0F79 LDA (&70),Y
0F7B EOR #&04	\16-bit decryption seed
0F7D EOR #&75	\with initial value of &475.
0F7F STA (&70),Y
0F81 DEC &0F7E
0F84 DEC &0F7E
0F87 BNE &0F8C
0F89 DEC &0F7C
0F8C INY 
0F8D BNE &0F79
0F8F INC &71
0F91 LDA &71
0F93 CMP #&4F	\Have we reached &4F00?
0F95 BNE &0F79	\No, continue.
0F97 RTS 	\Yes, return.

For disc systems where PAGE is higher than &E00, it is advisable to load in DART3 from tape, decrypt and re-save the DART3 file. Assuming the 'zapped' DARTS file is still in memory:

	*L.DART3 1100
	CALL&F70
	*DISC
	*SAVE DART3 1100 4F00 3F1C

The game can now be run by typing:

	*L.Dart1 800
	*L.Dart2 5630
	*/Dart3

Or a disc loader could be written (in machine code) to perform the above tasks automatically.

Mr Spock 7 Feb 2005