DonHodges.com Logo

 

For more excellent, in-depth information on Pac-Man, please visit:

 

 

Splitting Apart the Split Screen

The Journey to Find, Analyze, and Fix Pac Man’s Split Screen

By Don Hodges
Started 10/28/2007
Posted: 12/8/2007
Last Update:  07/17/2013


  |


4/21/2008:  This article is featured on Kotaku's home page http://kotaku.com/

12/10/2007:  This article is featured on MAME World's home page http://www.mameworld.net/

 


In the previous article on Donkey Kong, we saw that the game designers built in a mechanism to prevent the player from ever passing level 99. After level 99 is reached, it repeats forever, never advancing to 100. However, this is not widely known because a timer bug prevents anyone from ever passing level 22. If only the designers of Pac-Man had done something similar.

But they didn’t, and Pac-Man suffers from the well known split screen when level 256 is reached. We will examine how and why this occurs, and create a patch to fix the program’s bug.

Pac Man's Split Screen Level 256

 

The Problem

The first step is to search the Internet to see if this answer has already been found. We find a lot of good work has been done at Mark Longridge's web site [ http://cubeman.org/arcade-source/pacman.asm  ] where Pac Man’s code is disassembled and some comments are added. There is also a more complete disassembly of Ms. Pac Man at Scott Lawrence’s web site http://umlautllama.com/projects/pacdocs/mspac/mspac.asm. Ms. Pac Man shares a great deal of code with the original Pac Man, so some of these comments are useful as well.

Now we need to understand the nature of the split screen bug. It occurs on board 256, presumably because the game is trying to draw 256 fruits. Immediately we see from the source code on Mark Longridge's site a comment which mentions drawing the fruit.

2C03 CD8F2B 	CALL #2B8F 	; Draw fruit

Using this we discover the subroutine which draws the fruit. It follows here with my comments.  You will probably need some knowledge of assembly language to follow this.

2BF0 3A134E 	LD A,(#4E13) 	; Load A with level number
2BF3 3C 	INC A 		; Increase by one
2BF4 FE08 	CP #08 		; Is this level < 8 ?
2BF6 D22E2C 	JP NC,#2C2E 	; No, jump to compute different start for fruit table
2BF9 11083B 	LD DE,#3B08 	; Yes, load DE with address of cherry in fruit table
2BFC 47 	LD B,A 		; For B = 1 to level number
2BFD 0E07 	LD C,#07 	; C is 7 = the total number of locations to draw
2BFF 210440 	LD HL,#4004 	; Load HL with the start of video memory
2C02 1A 	LD A,(DE) 	; Load A with value from fruit table
2C03 CD8F2B 	CALL #2B8F 	; Draw fruit subroutine
2C06 3E04 	LD A,#04 	; 
2C08 84 	ADD A,H 	; Add 400 to HL
2C09 67 	LD H,A 		; HL now points to color memory
2C0A 13 	INC DE 		; DE now points to color code in fruit table
2C0B 1A 	LD A,(DE) 	; Load A with color code from fruit table
2C0C CD802B 	CALL #2B80 	; Draw color subroutine
2C0F 3EFC 	LD A,#FC 	; 
2C11 84 	ADD A,H 	; Subtract 4 from H
2C12 67 	LD H,A 		; HL now points back to video memory
2C13 13 	INC DE 		; Increase pointer to next fruit in table
2C14 23 	INC HL 		; 
2C15 23 	INC HL 		; Next starting point is 2 bytes higher
2C16 0D 	DEC C 		; Count down how many clears to draw
2C17 10E9 	DJNZ #2C02 	; Next B – loop back and draw next fruit
2C19 0D 	DEC C 		; Count down C. Did C just turn negative?
2C1A F8 	RET M 		; Yes, return to game, we are done
2C1B CD7E2B 	CALL #2B7E 	; No, call subroutine to draw a clear
2C1E 3E04 	LD A,#04 	; 
2C20 84 	ADD A, H 	;
2C21 67 	LD H,A 		; Increase HL by 400 for color value to be cleared
2C22 AF 	XOR A 		; Load A with 0, the code for black color
2C23 CD802B 	CALL #2B80 	; Draw color subroutine – draws black color
2C26 3EFC 	LD A,#FC 	; 
2C28 84 	ADD A,H 	; Subtract 4 from H
2C29 67 	LD H,A 		; HL now points back to video memory
2C2A 23 	INC HL 		;
2C2B 23 	INC HL 		; Set next starting point to be 2 bytes more
2C2C 18EB 	JR #2C19 	; Jump back and draw next clear

; Arrive here when the level is 8 or higher
2C2E FE13 	CP #13 		; Is the level > #13 (19 decimal, 7th key) ?
2C30 3802 	JR C,#2C34 	; If not, skip next step
2C32 3E13 	LD A,#13 	; If yes, treat all levels 19 and up as if they are level 19.
2C34 D607 	SUB #07 	; Subtract 7 to point to first value to be drawn
2C36 4F 	LD C,A 		; Copy result to C
2C37 0600 	LD B,#00 	; Load B with Zero
2C39 21083B 	LD HL,#3B08 	; Load HL with pointer to start of fruit table
2C3C 09 	ADD HL,BC 	; 
2C3D 09 	ADD HL,BC 	; Adjust fruit table pointer, based on current level
2C3E EB 	EX DE,HL 	; Load DE to point to fruit in table
2C3F 0607 	LD B,#07 	; Load B counter to draw 7 fruit
2C41 C3FD2B 	JP #2BFD 	; Jump back up to fruit drawing section

The Fruit Table:

Memory Location

Fruit Code

Color Code

Fruit

3B08

90

14

cherry

3B0A

94

0F

strawberry

3B0C

98

15

1st peach

3B0E

98

15

2nd peach

3B10

A0

14

1st apple

3B12

A0

14

2nd apple

3B14

A4

17

1st grape

3B16

A4

17

2nd grape

3B18

A8

09

1st galaxian

3B1A

A8

09

2nd galaxian

3B1C

9C

16

1st bell

3B1E

9C

16

2nd bell

3B20

AC

16

1st key

3B22

AC

16

2nd key

3B24

AC

16

3rd key

3B26

AC

16

4th key

3B28

AC

16

5th key

3B2A

AC

16

6th key

3B2C

AC

16

7th key

3B2E

AC

16

8th key

Here are the main points of fruit drawing:

Load level number and increase its count by 1
If the level is 7 or less, set the pointer to the starting fruit to the cherry
If the level is 8 or more, set the fruit pointer to the starting fruit for the current level.
If the level is 19 or above, set the fruit pointer to the 1st key.
Set up two counters. One is for drawing fruit, the other is for possibly drawing blank spaces in cases where there are less than 7 fruit to draw.
Draw the fruit, color the fruit.
Increase the pointers to the next fruit in the table and the next location on screen.
Loop back up to 7 times to draw the rest of the fruit.
Check to see if any blank spaces are needed. If so, then draw the proper number of blank spaces and color them all black.
Return to the program.

We need to know that levels on Pac Man are stored with a starting value of zero. In other words, the first level that we play (cherry) is treated as level zero by the game. The second level (strawberry) is counted as level 1 by the game, and so on. The programmers know this and so when the subroutine is called to draw the fruit, it loads the level counter and then increases it by one. Then it figures out how many and which fruit to draw.

When level 256 is reached, it is counted by the game as level 255 (this number is #FF in hexadecimal). The subroutine is called to draw the fruit and the value for the level wraps around to zero when it is incremented. No check is done to see if the Carry flag is set, which would have been one way for the programmers to realize the game has reached this point. So with the counter at zero, this causes the subroutine which draws fruit to think that it is a level less than 7 with only 1 to 7 fruit to draw, in addition to some possible blank spaces.

The program starts drawing fruit with the counter in register B set to zero, instead of the expected number from 1 to 7. At the end of the loop, B is decreased by one and checked for zero. If it is not zero then the loop runs again, if it is zero then the subroutine ends. On level 256, B is zero to begin with. It is decremented at the end of the loop, which makes it roll back down to 255, causing the loop to run a total of 256 times, which is why the split screen gets drawn on this level.

The result is that memory locations which are up to 512 bytes past the start of the fruit table, which are used for some other totally different part of the program, are drawn on the screen at increasing locations in video memory. The subroutine which draws the fruit is expecting only eight distinct values for the various fruit, and is expecting only seven memory locations where the fruit are drawn at the bottom of the screen. The memories which end up getting fed into this subroutine have different values than the ones the subroutine is expecting.

There are two main parts to drawing a fruit. The first part draws the fruit, and the second part draws the color. It turns out that each fruit is actually made up of four separate pieces of graphics, all painted with a single color code. Let’s examine the subroutines which draw the fruit and the colors.

The following subroutine draws the four parts of a fruit onscreen. It requires that A is loaded with the code for the first part of the fruit and HL is loaded with the memory address of the first position on screen where it is to be drawn.

2B8F E5 	PUSH HL 	; Save HL
2B90 D5 	PUSH DE 	; Save DE
2B91 111F00 	LD DE,#001F 	; this offset is added later for third part of fruit 
2B94 77 	LD (HL),A 	; Draw first part of fruit code into screen memory
2B95 3C 	INC A 		; Point to second part of fruit
2B96 23 	INC HL 		; Increment screen memory for second part of fruit
2B97 77 	LD (HL),A 	; Draw second part of fruit code into screen memory
2B98 3C 	INC A 		; Point to third part of fruit
2B99 19 	ADD HL,DE 	; Add offset for third part of fruit
2B9A 77 	LD (HL),A 	; Draw third part of fruit code into screen memory
2B9B 3C 	INC A 		; Point to fourth part of fruit
2B9C 23 	INC HL 		; Increment screen memory for fourth part of fruit
2B9D 77 	LD (HL),A 	; Draw fourth part of fruit code into screen memory
2B9E D1 	POP DE 		; Restore DE
2B9F E1 	POP HL 		; Restore HL
2BA0 C9 	RET 		; Return

The following subroutine draws colors onscreen for a 2x2 grid. It requires that A is loaded with the code for the color and HL is loaded with the memory address of the position on screen where the first color is to be drawn. If a clear value is to be drawn, the first address is called (#2B7E). If A is preloaded with a color, then the second address is called (#2B80).

2B7E 3E40 	LD A,#40 	; Used to draw clear value
2B80 E5 	PUSH HL 	; Save HL
2B81 D5 	PUSH DE 	; Save DE
2B82 77 	LD (HL),A 	; Draw color into first part
2B83 23 	INC HL 		; Set location to second part of fruit
2B84 77 	LD (HL),A 	; Draw color into second part
2B85 111F00 	LD DE,#001F 	; Offset is used for third part
2B88 19 	ADD HL,DE 	; Set location to third part of fruit
2B89 77 	LD (HL),A 	; Draw color into third part
2B8A 23 	INC HL 		; Set location to fourth part of fruit
2B8B 77 	LD (HL),A 	; Draw color into fourth part
2B8C D1 	POP DE 		; Restore DE
2B8D E1 	POP HL 		; Restore HL
2B8E C9 	RET 		; Return

The next part of understanding the split screen is to examine the memory locations of the screen elements. Luckily this has already been done by Bart Grantham. http://www.bartgrantham.com/projects/mspacman/sprite-RAM.html  The memory grid has been overlayed with the screen to show this:

Pac Man's Video Memory Map

The color memory map is the same except all address are #0400 higher. The extreme four corners exist in memory but are not visible on the screen. This is important because some of the fruits end up getting drawn into these invisible memories.

The screen memory starts in the lower right corner, and goes right-to-left for the bottom two rows. After the bottom two rows, the screen memory continues near the upper right corner in location #4040 and then goes top-to-bottom down each strip of the playing field, after each strip the next one starts one column to the left of the previous. After the last position on the playing field, near the bottom left corner, the memories then jump to the top right corner and two rows run across the top, right-to-left, much like the bottom two rows.

Using the cherry as an example, observe how the fruit drawing program works. The subroutine draws the first part of the fruit, then increments both pointers which indicate which graphic to draw and where to draw it. The graphic pointer is incremented but in order to draw the third part of the fruit, the location pointer needs to go down one row and then back one, which is accomplished in the subroutine by adding #1F hexadecimal. Then the third part of the fruit is drawn. The fourth part of the fruit is drawn after incrementing both pointers by one.

Examine for the example of drawing a cherry:

Pac Man Cherry Part 1 of 4
Location 4004. Code drawn is #90 = top right of cherry

Pac Man Cherry Part 2 of 4
Location 4004+1 = 4005. Code drawn is #91 = top left of cherry

Pac Man Cherry Part 3 of 4
Location 4004+1+1E = 4024. Code drawn is #92 = bottom right of cherry

Pac Man Cherry Part 4 of 4
Location 4004+1+1E+1 = 4025. Code drawn is #93 = bottom left of cherry

After the cherry is drawn, the program calls the subroutine which draws in the color, which is basically the same except it writes to the color memory instead of character memory, and it writes the same color to all four positions instead of increasing the value each time.

That’s it. Now the process is repeated as necessary, and depending on the level, up to seven fruit are drawn at the bottom of the screen.
 

The Bug in Action


On level 256, due to the bug in the algorithm, the program attempts to draw 256 fruit instead of a regular amount. The routine continues on and starts grabbing data that is past the fruit table in memory and starts drawing the characters in those memories as if they were fruit, into increasing locations in memory with starting values incremented by two each time.

The routine keeps incrementing the location to draw by two. The first thirteen fruit are drawn along the bottom. The thirteenth fruit, (the first key) is the last drawn on the screen without any corruption.

Pac Man 13th Fruit

The fourteenth fruit, (the 2nd key), is drawn into the very bottom-left corner of video memory which is not visible on the screen (starting with address #401E).

Pac Man 14th Fruit

Then the next fruit drawn, (the third key), starts at address #4020 which is the bottom right corner, not visible on screen. But the fruit-drawing routine draws four locations: #4020, #4021, #4040 and #4041. So, the top half of this fruit is not seen onscreen.

Pac Man 15th Fruit 1st half

The bottom half is drawn into the top-right location of the playing field, going top-to-bottom instead of going right-to-left.

Pac Man 15th Fruit 2nd half part 1

Pac Man 15th Fruit 2nd half part 2

The next fruit that is drawn, (the fourth key), is visible onscreen, split between the bottom right corner for its top half, and the top right side of the playing field for the bottom half. This is repeated for the entire bottom row, including into the very bottom left corner where the top half memories are not visible onscreen (#403E and #403F), but the bottom half is drawn to the bottom two places in the first column of the playfield (#405E and #045F).

Also during this bottom row, after the 8th key is drawn, (the 5th visible being split at the bottom), the fruit table ends and the program starts drawing fruit using values from the memories that come after the fruit table. These values cause the subroutine to draw seemingly random fruit and colors. For example, the values of the two memory locations which follow final key in the fruit table are #73 and #20. So the program tries to draw fruit #73 with color #20 into the next memory locations. The subroutine draws character values #73-#77, which are not visible characters, so it just draws blanks on the screen. The next two values in memory are #00 and #0C. The subroutine draws a “fruit” with these values by drawing the numbers 0, 1, 2, and 3 into these memories onscreen. However, the color subroutine draws color #0C which is black, so these values are erased from view almost instantly after they are drawn.

Looking at the last fruit to be split between the bottom of the screen and the right side, we observe the behavior of the 9th fruit after the end of the fruit table. The first value that is drawn is #06 which draws the number 6, 7, 8, and 9 into memory, as seen. Then the color subroutine blanks them all out with a black color.

Pac Man Split Screen - 9th location after Fruit Table

Pac Man Split Screen - 9th location after Fruit Table Color

Looking at the next fruit, we see it is split between invisible memories in the bottom left of the screen and the right side. The memory contains #04 which makes the subroutine draws the number 4, 5, 6, and 7 into memory, as seen. After they are drawn, the color subroutine blanks them all out with a black color.

After the end of the bottom row, the starting addresses of the fruit shift from #403E, to #4040 which is the top right corner of the playfield.  The 11th location after the fruit table is drawn into the top right corner of the playfield, and we can see that since the memories here are arranged differently than at the bottom, the fruits are drawn going down 2 columns instead of across 2 rows.

 

Pac Man Split Screen - 11th location after Fruit Table

Then the program draws the corrupted fruit values down each column of memory, overwriting the values that it had written on the previous pass for the bottom half of the fruit, with new values for the top half of each fruit as well as its own bottom half in the next column over. These bottom half values keep getting replaced by the next column of fruits, except in the final column when the subroutine ends.

It turns out that some of the memories contain codes that will draw dots onto the screen that Pac Man can eat. If the memory location has a value of #0F or #10, an edible dot will be drawn to the screen. There are several locations in this range that meet this condition, and so several of the characters that are drawn in the split screen are edible dots. If #13 or #14 had existed in any of these memories, an energizer would have been drawn, but these values do not appear in this memory range.

The last memory location drawn is at location #4202, which is #200 more than the location of the first cherry (#4002). It is #200 because the program loops #100 (256 decimal) times, incremented by two each time.

In addition, after the all of these corrupted fruit values are drawn, the program thinks that it has drawn no fruit and proceeds to draw seven blank spaces. This is because the counter that starts at seven and is counted down each time a fruit is drawn, wraps around itself and returns to seven after the 256th fruit is drawn. So after the last fruit, the program draws seven blank spaces and colors them black. This occurs for memory locations #4204 through #4210, the last of which clear off the left side of the center portion of the ghost’s home box.

Pac Man Split Screen Flash Level 256

We can see more clearly the data written to the screen by taking a screen shot of the level being cleared by the rack skip dip switch setting. This un-hides most of the data written to the screen that might otherwise be hidden by being colored black.

Finally the subroutine ends and the program continues “normally”.

The game then draws the player’s remaining Pac Men in the lower left of the screen. This process overwrites the top half of the bells, galaxians, and one of the limes that were drawn in those locations, as well as any graphics that were drawn into their bottom halves.

Then the game returns, and Pac Man is doomed because this level is un-finishable. There are not enough dots on the screen to eat to finish the level, even though the game re-draws the split screen each time the player dies, which means the dots in the right hand side reappear and can be eaten again. If the memories contained enough values of #0F or #10, which draw dots to the screen, it is conceivable that this level could have been passable.

 

THE FIX

The next step is to fix the code so that this behavior does not occur on level 256.

A close look at the disassembly of Ms. Pac Man at Scott Lawrence’s web site( http://umlautllama.com/projects/pacdocs/mspac/mspac.asm ) shows a “fix” that has been written by Mark Spaeth:

HACK8: Mark Spaeth's "20 byte" level 255 Pac-Man rom fix
; level 255 pac fix ; HACK8
;0A90 C3800F 	JP #0F88 
;0A93 00 	NOP
…
;0F88 3A134E 	LD A,(#4E13) 	; board number
;0F8B 3C 	INC A
;0F8C FEFF 	CP #FF
;0F8E 2803 	JR Z,#0F93 	; don't store level if == 255
;0F90 32134E 	LD (#4E13),A 	; store new board number
;0F93 C3940A 	JP #0A94 	; jump back

What does this fix do? It overwrites the program that increases the game’s level with a jump command (called a ‘hook’) that takes it out to a new subroutine that has been written and placed in some unused memory. This subroutine really has nothing directly to do with the fruit drawing. It checks the value of the level right after it has been increased after a level is completed. If an overflow occurs, the result is not written back to memory. Then it jumps back to the original program. The result is the game stays forever on game level #FE (the 255th screen) and never goes any higher. Another hack which bypasses the self-test must also be employed with this one, or else the checksum routines that are run when the game powers on will detect a problem and refuse to run the game.

Not being satisfied with Mark Spaeth’s “fix”, we devise one that actually corrects the flawed logic in the fruit drawing subroutine. To be elegant, we will fix the code within the confines of the original code space. Here is the fix. There may also be other ways to implement this.

Here are the first few lines of the original fruit drawing subroutine

2BF0 3A134E 	LD A,(#4E13) 	; Load A with level number
2BF3 3C 	INC A 		; Increase by one
2BF4 FE08 	CP #08 		; Is this level < 8 ?
2BF6 D22E2C 	JP NC,#2C2E 	; No, jump to compute different start for fruit table

Part of the fix will change the code to read this way:

2BF0 3A134E 	LD A,(#4E13) 	; Load A with level number
2BF3 FE07 	CP #07 		; Is this level < 7 ?
2BF5 D22E2C 	JP NC,#2C2E 	; No, jump to compute different start for fruit table
2BF8 3C 	INC A 		; Increase by one

So instead of increasing the value by 1 and then checking against 8, we check against 7 and increase only if the original value is less than 7. Now we have to modify the code that starts at #2C2E if the level is greater than 7.

The original code reads

2C2E FE13 CP #13 	; Is the level > #13 (19 decimal, 7th key) ?
2C30 3802 JR C,#2C34 	; No, skip next step
2C32 3E13 LD A,#13 	; Yes, load A with #13
2C34 D607 SUB #07 	; Subtract 7 to point to first value to be drawn

We modify this to read:

2C2E FE12 CP #12 	; Is the level > #12 (18 decimal, 7th key) ?
2C30 3802 JR C,#2C34 	; No, skip next step
2C32 3E12 LD A,#12 	; Yes, load A with #12
2C34 D606 SUB #06 	; Subtract 6 to point to first value to be drawn

We arrive at this location with A being one less than the program was expecting, because we did not increment it before coming here. So we check against #12 instead of #13, force to #12 instead of #13, and subtract 6 instead of 7 to make up for it. Everything else can remain the same and the bug is fixed.

The last thing that needs to be done is to fix the power-on checksum routine so that this change can be made without causing a memory error. Instead of creating or using another hack to bypass the self-tests, we will add offsets to the end of this memory bank to make the checksums add up properly.

The checksum routines in this game first add up all the even bytes in a memory bank and check against #00, and then repeat this for the odd numbered bytes in the memory bank. We do some quick math and discover that the new program’s even bytes add up to #18B less checksum bytes than the program it replaces, and the new program’s odd bytes add up to #187 more checksum bytes than the program it replaces. To fool the program’s checksum we therefore add #8B to an unused even memory location and #79 to an unused odd memory location.

Before:
2FFC: 00
2FFD: 00

After:
2FFC: 8B
2FFD: 79

The total patch is made by changing just nine bytes of the original code, plus two bytes for the checksum fix. This patch can be written into Puckman romset file named [namcopac.6h] into three areas. For continuity, this is padded with four bytes of the original code, making it 15 bytes instead of the expected 11 bytes.

0BF3: FE 07 D2 2E 2C 3C
0C2F: 12 38 02 3E 12 D6 06
0FFC: 8B 79

MAME will complain that the ROM set is bad, because it uses more complicated hashing algorithms to detect errors, but it will still work.

Alternately we can use a MAME cheat code and run it when needed:

:puckman:20700001:2BF3:FE07D22E:FFFFFFFF:No Split Screen
:puckman:20510001:2BF7:00002C3C:0000FFFF:No Split Screen (2/5)
:puckman:20010001:2C2F:00000012:000000FF:No Split Screen (3/5)
:puckman:20610001:2C33:0012D606:00FFFFFF:No Split Screen (4/5)
:puckman:20510001:2FFC:00008B79:0000FFFF:No Split Screen (5/5)

Learn more about cheat codes in MAME at http://cheat.retrogames.com

This cheat will also work for all of the Pac Man ROM sets in MAME, as they all suffer from the split-screen bug. The exception is the bootleg that runs on Galaxian hardware (pacmanbl) which does not suffer from this bug to begin with.

After being patched, the game no longer tries to draw 256 fruit when reaching level 256. It draws seven keys and the level can be finished, at which point the game wraps around back to level 0 (cherry), and will show its intermissions again at the same places. However, the game does not become easy again but stays on the same difficulty level where the energizers have no effect. The player may continue to use the previous 9th key patterns for the rest of the game.

Thoughts & Comments

It is probably a good thing that Pac Man has this bug in its program. If it didn’t, expert players could conceivably be able to play the game indefinitely, because whenever they get tired they can just park Pac Man in the hiding places and leave the game to go eat, sleep, or whatever, and then return to the game and continue playing. The only limiting factor would have been the length of time that the game could run without a power outage or suffering from some other hardware failure. Experts would have been able to play the game for weeks, months, even years, or more.
 

 

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.

 

All content Copyright © 2008 Don Hodges
Various logos are trademarks of their respective companies.
Send Email to Don Hodges

Weblog Commenting and Trackback by HaloScan.com