By C.J.Richardson. An article in the September issue of
8-Bit by M.T.Farnworth 15A, prompted
me to start considering how I actually
find cheats for games myself. The tools
that I find indispensable for this are:
DISCDOCTOR, my printer and a list of
the opcodes.
Before anything else, may I suggest
that you refresh yourself by reading
M.T.Farnworth's article again, it is
imperative that you use a backup and
rename the original file something
like: CODEOR so that it is obvious
which is the original unbutchered
version.
I will mention a couple of machine code
instructions in the following text.
JSR - Jump Sub Routine similar to gosub
in BASIC.
RTS Return from Sub Routine.
NOP No OPeration. Handy blank out.
RTS and NOP take up only one memory
location. JSR takes up 3.
There are a couple of methods mentioned
here which can be applied without
reading the code of a game at all.
However, a disassembler is a real must.
If you haven't got one, there is a
simple one of mine included on this
issue disc.
Firstly, play the game and decide if it
is worth attempting to find a cheat
for it. Then decide on the things that
you would like to have:
Infinite lives?
Immortality?
Walk through walls?
Disable the nasties?
Infinite weapons or shields?
See the passwords?
Read the text?
The list can grow endlessly depending
on the type of game. For instance, in
an adventure game you might want to
see all the game locations. This can be
done in a number of ways. Sometimes the
number of the present location is
stored in zero page, it is a matter of
changeing this number and calling the
game repeatedly. To carry this out you
have to find out where the main loop of
the game is and slip an RTS (&60) into
the loop. Change the memory location,
then call the game loop again.
Sometimes you can spot the main loop as
a large number of JSR instructions
followed by a JMP instruction pointing
back to the start of all the JSRs. This
is a dead giveaway. You can clap 3 NOPs
over the JSR instruction, or an RTS at
the address the JSR instruction points
to. At this particular point in the
game either method will have the same
end effect, i.e. the routine will be
avoided. So if it is a collision detect
routine, then a collision will not be
detected!
However, if you put the RTS at the
address pointed to rather than just
removing the instruction to jump there,
then the routine will be totally
disabled, if it is called from other
parts of the program, the same thing
will happen, a collision will not be
detected. If you think about this, the
RTS method on occasions will not have
the desired effect, because if the same
routine is used to detect if a nasty
has been hit, then it will not die
either. So you must have an idea of
what you want to do before deciding on
the method to use. An idea is to write
a short loop such as this with the game
in memory, a direct command so that you
don't overwrite anything that may be
there:
FOR L%=&1000TO&5000:IF?L%=&20 PRINT"At
&";÷L%;" JSR&";÷?(L%+2);÷?(L%+1):NEXT
ELSE NEXT
This line will print out all the
occurrences of JSR instructions in an
area of memory from &1000 to &5000. It
may also print out some rogue ones, as
the number &20 could occur for other
reasons. The line gives the memory
location of the instruction along with
the address of the subroutine. So you
can put 3 NOP instructions starting at
the first number, or an RTS at the
address pointed to.
A line like that can produce quite a
lot of output. This is where the
printer comes in. Type the above line
then, before pressing return press CTRL
+B to switch the printer on. I use a
till roll! Remember to type CTRL+C to
switch the printer off when you have
finished. If you haven't a clue where
to start, try just working your way
from one end of memory to the other in
stages of say &300 bytes changeing all
occurences of JSR to NOPs. This can
have quite alarming effects sometimes!
Using that method you can put a cheat
in without reading the program. Using
trial and error, you can gradually find
the one routine you need to cut out.
Something like:
FORL%=&1000TO&1100:IF?L%=&20 ?L%=&EA:
?(L%+1)=&EA:?(L%+2)=&EA:NEXT ELSE NEXT
Then call the start of the program.
Change the value of the FOR NEXT loop
when the program crashes to:
FORL%=&1100 TO &1200 etc and try again.
Instead of putting the RTS instruction
at the address pointed to by the JSR
instruction, you could put it a bit
further on, so that a part of the
routine is used.
At the start of the game, there will
generally be an area which repeatedly
saves values to zero page locations,
this could be the initialisation of
scores, shields, weapons, lives, etc.
Try changeing the value saved. The
program may look something like this:
LDA#0
STA&76
STA&77
STA&78
LDA#3
STA&79
Something like this looks really
promising.The LDA#3 instruction could
be changed to LDA#&FF, so instead of
starting with 3 lives, you may start
with 255. Sometimes that may cause
display problems though, as the program
tries to print 255 elves at the top of
the screen instead of the 3 you should
have had. Sometimes it is best not to
be too greedy. Try altering the LDA#0
as well, and see what happens, one of
those locations may refer to the start
position for instance. If you change
the 0 to 4, you may find yourself on
level 5 of the game.
At this point you may now have
discovered where the number of lives
are stored, let us assume that location
&79 holds the number of lives. You have
proved this by altering the number 3 to
255 and getting loads of lives. So now
you can find out where the memory
location is altered. For instance if
somewhere in memory is the instruction
DEC&79 (&C6 &79) this is where your
lives are depleted.
You could find it like this:
FORL%=&1100TO&3000:IF?L%=&C6:IF?(L%+1)=
&79 PRINT"Change ?&";÷L%;" and ?&";÷L%+
1;" to &EA":NEXT ELSE NEXT
It's then a simple
matter of replacing these 2 bytes with
NOPs. Then lo and behold, you will
still get killed, but you have infinite
lives. Taking this a stage further, you
may now have found the area that
detects a kill, so read backwards until
you find a JMP or RTS instruction (it
could be others), and put an RTS just
after that.
That brings me to another heavy handed
method. Use the previously described
method of finding JSRs to find RTSs
instead:
FOR L%=&1900TO&5000:IF?L%=&60 P."RTS at
&";÷L%:N. ELSE N.
Then try putting an RTS just after
that. The principle behind this (albeit
based on shaky ground) is that
sometimes an RTS represents the end of
a routine, therefore it follows that
where one finishes another must start.
This will therefore sometimes disable
an important routine such as collision
detection.
An extension of this idea is another
really easy method to attempt without
even reading the coding:
*LOAD in the game
Type as a direct command:
FORL%=&1A00TO&1900STEP-1:IF?L%=&60?(L%+
1)=&60 :NEXT ELSE NEXT
Then CALL the start address. When that
crashes, repeat the operation but
increase the FOR NEXT loop values by
another &100. Or shorten the loop to
home in on the instruction that is
doing the trick.
Here are some assembler opcodes to look
for. "n" and "nn" represent the number
of bytes that follow the instruction.
...........HEX DEC. OPCODE.............
...........&10 16.. BPL.&n.............
...........&20 32.. JSR.&nn............
...........&30 48.. BMI.&n.............
...........&4C 76.. JMP.&nn............
...........&50 80.. BVC &n.............
...........&60 96.. RTS................
...........&61 97.. ADC (&n),X.........
...........&65 101. ADC.&n.............
...........&69 105. ADC.#&n............
...........&6C 108. JMP(&nn)...........
...........&6D 109. ADC.&nn............
...........&71 113. ADC.(&n),Y.........
...........&74 116. STZ.&n,X...........
...........&76 118. ADC.&n,X...........
...........&79 121. ADC.&nn,Y..........
...........&81 129. STA.(&n,X).........
...........&84 132. STY.&n.............
...........&85 133. STA.&n.............
...........&86 134. STX.&n.............
...........&88 136. DEY................
...........&8C 140. STY.&nn............
...........&8D 141. STA.&nn............
...........&8E 142. STX.&nn............
...........&90 144. BCC.&n.............
...........&9D 157. STA.&nn,X..........
...........&A0 170. LDY.#&n............
...........&A2 162. LDX.#&n............
...........&A4 164. LDY.&n.............
...........&A5 165. LDA.&n.............
...........&A6 166. LDX.&n.............
...........&AC 172. LDY.&nn............
...........&AD 173. LDA.&nn............
...........&AE 174. LDX.&nn............
...........&B0 176. BCS.&n.............
...........&C5 197. CMP.&n.............
...........&C6 198. DEC.&n.............
...........&C8 200. INY................
...........&CA 202. DEX................
...........&D6 214. DEC.&n,X...........
...........&DE 222. DEC.&nn,X..........
...........&E4 228. CPX.&n.............
...........&E5 229. SBC.&n.............
...........&E6 230. INC.&n.............
...........&E9 233. SBC.#&n............
If you want a full list of the opcodes,
see the program "DISM".
Dism is a basic program which reads
machine code in memory or on disc and
converts it to something a little more
readable. The machine code MNEMONICS.
First, get the machine code you want to
read into memory or decide on the file
you want to read from disc. If you are
reading from memory, *LOAD the machine
code into memory. Then load DISM, be
careful not to load DISM in at a place
that will overwrite the part you want
to read. Change page to above or below
the code. If you can't do this, use the
read from disc option mentioned below.
Run the program. It will ask these
questions:
List opcodes? Answering "Y" to this
will send a list of the mnemonics to
the printer.
Read from Disc or memory?
Printout. Answer "Y" and all output
goes to the printer as well as screen.
Jump Single or Relative? If you answer
"S" to this, memory read will be in
jumps of one location at a time. Use
this option to find your way around a
program. The option you will normally
choose is "R" for relative jumps. If an
instruction takes up 3 bytes, the next
instruction disassembled will be taken
from 3 bytes further on.
Mode 7 or 0. Choose the mode.
If you are reading from disc the
program will now ask for the name of
the file you are going to read.
Read from where? The program is now
asking for the position in memory or on
disc that you want to start reading
from. You can enter the address in
decimal, or precede the number with "&"
for hexadecimal. If when using the disc
option you enter a number less than 1
or larger than the file, the program
will stop.
Instructions are read in blocks of ten,
press any key but shift to read more.
Press "J" to Jump to somewhere else in
memory. Enter the memory location in
either decimal, or precede the number
with "&" to enter it in hexadecimal.
The program will convert itself to 6502
by following the instructions on the
title page. Type SA.FNS to save the
converted file. All that the conversion
does is remove the extra instructions
available on the 65C02. You do not need
to convert the program unless using the
data to write a program.
Here is an idea to get you going:
Chain Dism. Press N M N R 0.
Then type &900 and press RETURN.
You should now be reading one of the
mag routines.