Killing the
Killscreen by Donkeying with
Kong
The Journey to Find and Fix
Donkey Kong’s Logic Bomb
By Don Hodges
Started 10/28/2007 - Posted
11/4/2007
Last update
Sunday, February 19, 2017
Donkey Kong has
a “kill screen” if the player
ever makes it to level 22. On
this level, the timer runs out
very quickly and Jumpman [Mario]
always dies before he can get
past the 2nd girder.
So I wanted to find out: what
exactly is the bug in the
program that causes this
behavior and also, can it be
fixed?
First I looked on the Internet
to see if the answer has already
been found. Some good starting
information is located at
http://www.jeffsromhack.com/products/donkeykong_tech.htm.
There, Jeff Kulczycki breaks
down the math of the kill
screen, showing the formula that
is used to compute the bonus for
each level. The formula says the
level times 10, plus 40, gives
the number of hundreds in the
bonus timer. If the result is
too large it is forced back
down. The key to this is that
the level number is used in the
calculation. On level 22 an
overflow occurs, leaving the
player with just 400 points on
the timer because the
multiplication and addition
yields a number larger than 256.
So armed with this knowledge and
a MAME emulator complete with
some cheats to help out, we
start the journey. In MAME we
look closely at the set level
cheat and the cheat which gives
infinite time on the bonus
clock. Memory location #62B1
holds the hundreds place of the
timer, with the onscreen timer
in #638C, and the level of play
in #6229.
The next step is to disassemble
the program ROMs for Donkey
Kong. I found a good Z80
disassembler (dZ80 by Mark
Incley,
http://www.inkland.demon.co.uk
) and ran it on the program
ROMs, then searched for those
addresses listed above, looking
for clues.
After lots of looking we finally
arrive at a section of code
inside the c_5et_g.bin file. You
will probably need some
knowledge of assembly language
to follow the rest of this. The
reason this section stands out
is because the first instruction
references the level number
stored in #6229, and near the
end is a reference to #62B0,
which is very close to #62B1
which holds the timer. So let’s
look at the code with my
comments attached.
0F7A
3A2962 LD A,(#6229) ; Load A
with level number
0F7D 47 LD B,A ; Copy this number into B
0F7E A7 AND A ; Perform Bitwise AND of A with
A
0F7F 17 RLA ; Rotate Left the bits in A
(multiply by 2)
0F80 A7 AND A ; Perform Bitwise AND of A with
A
0F81 17 RLA ; Rotate Left the bits in A
(multiply by 2)
0F82 A7 AND A ; Perform Bitwise AND of A with
A
0F83 17 RLA ; Rotate Left the bits in A
(multiply by 2)
0F84 80 ADD A,B ; A = A + B
0F85 80 ADD A,B ; A = A + B
0F86 C628 ADD A,#28 ; A = A + #28 (40 decimal)
0F88 FE51 CP #51 ; Is A >= #51 (81 decimal) ?
0F8A 3802 JR C,#0F8E ; No, then skip ahead to #0F8E
0F8C 3E50 LD A,#50 ; Yes, then A = #50 (80 decimal)
0F8E 21B062 LD HL,#62B0 ;
Load HL Address to store the
result
0F91 0603 LD B,#03 ; For B = 1 to 3
0F93 77 LD (HL),A ; Stores A in #62B0, #62B1, then
#62B2
0F94 2C INC L ; L = L + 1
0F95 10FC DJNZ #0F93 ; Next B
The first half of this code
takes the level and multiplies
it by 10. First, it grabs
the number of the level and
stores it in A. Then it makes a
copy of A in B for later use.
The next step had me confused
for a while:
0F7E A7
AND A ; Perform Bitwise AND of A
with A
What could the benefit be of
ANDing a number to itself?
Wouldn’t it always just give the
same number back as a result?
The answer to this question is
yes, but it turns out the reason
for this is to clear the Carry
flag to zero, so that the next
instruction which multiplies the
level by 2 will not also bring
in the Carry bit into the least
significant bit. The next
instruction is
0F7F 17
RLA ; Rotate Left the bits in A
With the Carry flag cleared,
this will double the number in A
by shifting its bits left. This
process is repeated 2 more
times, which results in A
becoming 8 times what it started
as. To get to 10 times, we add
the original value stored in B
twice:
0F84 80
ADD A,B ; A = A + B
0F85 80 ADD A,B ; A = A + B
So now the value in A is 10
times the level. Next we add #28
(40 decimal) to the result:
0F86
C628 ADD A,#28 ; A = A + #28 (40
decimal)
Now the value in A is 10 times
the level, plus 40, just like it
is supposed to be. Next we have
a check that is run on the
answer.
0F88
FE51 CP #51
; Is A >= #51 (81 decimal) ?
0F8A 3802 JR C,#0F8E ; No, then
skip ahead to #0F8E
0F8C 3E50 LD A,#50 ;
Yes, then A = #50 (80 decimal)
0F8E …
The game programmers wanted to
make sure the timer never got
above 8000, so they coded in a
check to see if the hundreds
ever went higher than 80, and if
it did to force it back down to
80. Next, the result is stored.
0F8E
21B062 LD HL,#62B0 ; Load HL
Address to store the result
0F91 0603 LD B,#03
; For B = 1 to 3
0F93 77 LD (HL),A ; Stores A in #62B0, #62B1, then
#62B2
0F94 2C INC L ; L = L + 1
0F95 10FC DJNZ #0F93
; Next B
This final set of instructions
saves the answer computed for
the timer into memory by writing
the value into 3 locations:
#6280, #6281, and #6282.
The problem with this code is
apparent: There is no checking
for overflow when doing the
doubling, nor when doing the
addition. If the level number is
large enough, the final result
for the timer will lose the
Carry flag, making the result
mod 256 of the intended result.
As we have seen, the Carry flag
is intentionally cleared each
time before the number is
doubled. This all works together
to create the “kill screen” on
level 22: the timer is computed
as (22 * 10) + 40 = 260, which
is greater than 256 by 4. This
gives level 22 a starting bonus
of 400, which is a very short
time and makes it impossible to
finish.
Now, most of this information so
far was already known, at least
to Jeff Kulczycki at the web
page referenced in the
introduction. The next step is
to create a fix for this code.
To be elegant, we want to
implement the fix in the same or
less space that the original
program runs. If we make the
code shorter, that is OK because
we can fill the extra space with
NOP (No OPeration) commands. But
we cannot take more room unless
we jump out of the code here to
a new subroutine and then jump
back. This would require finding
unused memory in the program
space for the fix code.
It turns out that one solution
is possible; there may be
others. Here is what we do.
First, we get rid of the code
which checks if the result is
greater than 81. Instead, we
create a check right after
loading the level into A to see
if it is greater than 4. If A is
greater than 4, we force it back
to 4 and continue with the rest
of the code normally. This means
now that level 4 and above will
always compute to a bonus of 80
hundreds, giving the desired
result. The fixed code fits
exactly in the same space as the
original:
0F7A
3A2962 LD A,(#6229) ; Load A
with the level number
0F7D FE04 CP #04 ; Is the level >= 4 ?
0F7F 3802 JR C, #0F83 ; If not, jump ahead and compute
bonus normally
0F81 3E04 LD A, #04 ; If it is, then set A = 4
0F83 47 LD B,A
; Copy A into B
0F84 A7
AND A
; Clear the carry flag
0F85 17 RLA
; Rotate Left the bits in A
0F86 A7
AND A
; Clear the carry flag
0F87 17 RLA
; Rotate Left the bits in A
0F88 A7
AND A
; Clear the carry flag
0F89 17 RLA
; Rotate Left the bits in A
0F8A 80 ADD A,B
; A = A + B
0F8B 80 ADD A,B
; A = A + B
0F8C C628 ADD A,#28
; A = A + #28 (40 decimal)
0F8E 21B062 LD HL,#62B0 ;
Load HL Address to store the
result
Now we just need to get this
fixed code back into the ROM
named c_5et_g.bin. There are two
ways to do this. One is to use a
hex editor (I use Hex Workshop)
and overwrite the locations of
offset 0F7D through 0F8D with
the new values [FE 04 38 02 3E
04 47 A7 17 A7 17 A7 17 80 80 C6
28]. The problem with this
method is that MAME thinks the
ROM is bad with an incorrect
checksum. Luckily, Donkey Kong
does not have a self-test of its
ROMs, so it will still play
without problems.
Another way is to create a cheat
for MAME to use, and load it
when we need to use it.
The above text can be added to
your MAME cheat.dat file. Then
the fix can be enabled in MAME
as a cheat. All the information
you may need about MAME cheats
can be found at
http://cheat.retrogames.com
It helps to have some of the
already known MAME cheats for
Donkey Kong, like unlimited
lives and set level. Using the
set level cheat, this patch has
been tested and it does seem to
work; when we arrive at level
22, the timer behaves correctly,
and we can finish the level.
In addition to the bug which
computes the timer, there is
also a bug in the display of the
timer. Let’s have a quick look
at that:
063A
3AB062 LD A,(#62B0) ; Load A
with value from initial timer
063D 010A00 LD BC,#000A ;
Load B with 0, load C with #0A
(10 decimal)
0640 04 INC B
; B counts how many tens
0641 91 SUB C
; subtract 10 decimal from A
0642 C24006 JP NZ,#0640 ;
keep repeating until zero
0645 78 LD A,B
; Load A with the number of tens
in the counter
0646 07 RLCA
; rotate left four times…
0647 07 RLCA
0648 07 RLCA
0649 07 RLCA
064A 328C63 LD (#638C),A ; store
results to on screen timer.
The above segment of code will
properly convert the hex value
of the timer into its hex coded
decimal equivalent, provided the
input is in the expected range
(50, 60, 70, or 80 decimal). For
example, if the timer is #50,
the algorithm generates an
answer of #80 to display on the
screen (80 is decimal for #50).
However, if the input is outside
the expected range (not evenly
divisible by 10), the formula
generates garbage. This explains
why the onscreen timer on level
22 is incorrect. As we have seen
above, the hundreds timer for
level 22 gets computed as 4.
When fed into this code, the
answer that gets computed is A1,
which is not a decimal number
and the game doesn’t know how to
display it properly. This
explains why the timer on level
22 is initially shown as 100.
Finally, it turns out that there
is another section of code which
checks to see if the level is
greater than or equal to 100, and if it is,
to force it back to 99:
06E1
3A2962 LD A,(#6229) ; Load A
with level number
06E4 FE64 CP #64 ; Is A >= #64 (100 decimal) ?
06E6 3805 JR C,#06ED ; If not, jump to ahead to #06ED
06E8 3E63 LD A,#63
; If yes, then load A with #63
(99 decimal)
06EA 322962 LD (#6229),A ; Push
A back into level location
With the above code, it proves
the game designers considered
the possibility that level 100
could be reached and put in a
cap to keep the level, and the
level display, from overflowing.
This inclusion is very
interesting given the timer bug
prevents anyone from passing
level 22.
Comments and
Conclusions
I hope this
excursion was as fun for you to
read as it was for me to find
and write up. It is
interesting to go back to this
game, now 26 years old, and
patch the code which prevents
expert players from getting as
high as they can get.
Given that only a handful of
players have ever gotten that
high, why did I even bother to
do this? The answer lies
in the fact that just because a
program is old, doesn't mean it
can't someday be fixed,
especially in a game that asks
the player, "How High Can You
Get?".
Updated
1/28/2009: Fixed some
comparison comments based on the
suggestions from an email from a
reader,
and did some other cleanups as
necessary.
Updated 6/18/2009: Fixed a
typo in the last paragraph
(thanks to a commenter who
pointed it out).
In accordance with Title 17 U.S.C. Section 107,
some of the material on this site is distributed without profit to those who have expressed a prior interest in receiving the included information for research and educational purposes. For more information go to: http://www.law.cornell.edu/uscode/17/107.shtml. If you wish to use copyrighted material from this site for purposes of your own that go beyond 'fair use', you must obtain permission from the copyright owner.