How High Can You Get? 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 MAME cheat looks like this:



:dkong:20000001:0F8D:00000028:000000FF:No Kill Screen

:dkong:20710001:0F89:178080C6:FFFFFFFF:No Kill Screen (2/5)

:dkong:20710001:0F85:17A717A7:FFFFFFFF:No Kill Screen (3/5)

:dkong:20710001:0F81:3E0447A7:FFFFFFFF:No Kill Screen (4/5)

:dkong:20710001:0F7D:FE043802:FFFFFFFF:No Kill Screen (5/5)



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?". Up next: Pac Man's split screen level analyzed and fixed. 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).