Monday, May 28, 2018

Punch Out Arcade Free Play Enhancement

("Too Long Didn't Read", skip to the end!)

So I recently acquired a Punch-Out arcade game (one of the ones I remember playing as a kid, could never defeat Bald Bull!).  It came with a helpful freeplay mod installed (which I am assuming came from here ) which is a really nice improvement from the stock Nintendo freeplay mode.  The stock freeplay mode basically coins up the game on power-up and sits there waiting for a button press, which puts the game's CRT monitors at risk of burn-in.  The freeplay mod I linked is a big improvement but it still has some room for improvement.  Specifically, when 'coining up' by pressing 'uppercut', the player is taken to a screen where they must manually press the left punch button in order to start the game.  As far as I can tell, this screen never goes away.  Why is this bad?  Well, I have kids and I have a day job.  If the kids power on my Punch-Out while I'm gone and leave it in this aforementioned state, my CRT is at risk of burn-in.  Believe me, I do _NOT_ want to find a replacement for these CRTs!  I've got much better things to do with my time!

So I decided to see if I could improve the freeplay mod I mentioned before to never leave the screen stuck waiting for a button press.

I used MAME's source code to get memory map and I/O map of the game, MAME's excellent built-in debugger to set breakpoints and watchpoints, and my trusty copy of IDA (Interactive Disassembler) to make understanding the code easier.  I also compared the aforementioned freeplay mod with the unmodified ROM to take a shortcut to understanding where the game looks for coin insertions.

Here is what the previous mod changes:

ROM:039A                 in      a, (3)          ; query dip switches
ROM:039C                 and     0Fh             ; isolate free play
ROM:039E                 cp      0Fh             ; are we in free play mode?
ROM:03A0                 jr      nz, NotFreePlay ; branch if not in free play mode

The mod changes 39C to AND with 0 instead of 0Fh and to compare with 1 instead of comparing with 0Fh.  This has the result of always taking the branch of 3A0 which pops us into the normal attract mode on power-up instead of Nintendo's crappy original freeplay behavior.

Next mod is at 61d:

ROM:061D                 in      a, (1)          ; query joystick and coins
ROM:061F                 ld      e, a
ROM:0620                 ld      b, 80h          ; isolate coin1
ROM:0622                 call    ActIfCoinInserted
ROM:0625                 ld      l, 0D6h
ROM:0627                 ld      a, e
ROM:0628                 ld      b, 40h ; '@'    ; isolate coin2
ROM:062A                 call    ActIfCoinInserted

the mod moves this code to an unused section of the ROM at $2AC0 and adds a check to see if the upper-cut button (aka button 3) is pressed.  If it is, it does some other simple checks and eventually ensures that 1 credit is now available which normally puts the game into the mode where it prompts the player to press left punch to start a new game.

I had no idea where this loop took place that repeatedly polled the state of the left punch button.  Fortunately, MAME's built-in debugger is quite powerful and allows setting of I/O watch points.  I referred to MAME's source code for Punch-Out and discovered that the buttons are read by reading I/O port 0.  So I simply told MAME to break any time something somewhere in the code read this port.

In addition to MAME breaking at the modified code to check to see if the uppercut button was being pressed, it rewarded me by breaking here:

ROM:12BA CheckLeftPunchNewGame:                  ; CODE XREF: ROM:12DA↓j
ROM:12BA                 in      a, (0)
ROM:12BC                 and     1               ; check to see if button 1 is pressed
ROM:12BE                 jr      nz, loc_12B5    ; branch if it's pressed
ROM:12C0 LeftNotPressed:                         ; CODE XREF: ROM:12F0↓j
ROM:12C0                 call    sub_2937
ROM:12C3                 jr      loc_1289

This was clearly the code I was looking for to see if left punch was getting pressed.
The only danger was that I didn't know if this code only got called after a coin was inserted and while waiting to start a new game or if it was a generic 'read the button' routine and was used during the game itself.  If the former, I could just change the behavior of the branch to always assume that the button was being pressed.  If the latter, I would have to dig deeper to avoid altering the behavior of the gameplay itself.

I disabled the breakpoint, then started a game.  As soon as Glass Joe appeared, I re-enabled the breakpoint at $12BA and to my delight, the breakpoint was not getting hit.  This meant that the code was only used when waiting for a new game.  Woohoo!

Now I just had to find the code that checked to see if the player wanted to do a 'rematch' after losing the game.  I knew that I had to change the behavior from the way the original game worked to prevent potential burn-in, so I had to make a decision about whether to always start a new game if a player 'coins up' during the fast timer, or always do a rematch if the player 'coins up' during the fast timer.  I decided that if the player 'coins up' after losing the game, this means that they always want to do a rematch (never a new game).  If the player wants to do a new game, they'll just have to let the fast timer expire.

I had noticed some other code near $12BA which also read from I/O port 0 but checked for button 2 (aka right punch).  The code was here:

ROM:12E6 CheckBothButtonsGameOverCreditIsNonZero:
ROM:12E6                                         ; CODE XREF: ROM:12E2↑j
ROM:12E6                 in      a, (0)
ROM:12E8                 and     4
ROM:12EA                 jr      nz, loc_12B5    ; branch if right is pressed
ROM:12EC                 in      a, (0)
ROM:12EE                 and     1
ROM:12F0                 jr      z, LeftNotPressed

So I set a breakpoint at $12E6, then destroyed Glass Joe and allowed Piston Hurricane to defeat me.

After my game was over, I saw the 'fast timer' urging me to insert a coin.  I hit 'uppercut' to credit up, and was gratified to have the debugger break at $12E6.  Woohoo!

From this point, future modifications were easy:

I simply had to change the "jr nz, 12b5" at $12BE to "jr 12b5" (always jump instead of jump if non-zero) and do a similar change at $12EA from "jr nz, 12b5" to "jr 12b5".

This mod was fairly easy for me to perform but I stood on the shoulders of giants who did all of the hard work.  The tools that made it easy:
- MAME source code that had the I/O ports and memory map
- MAME debugger (super nice for a free project)
- The pre-existing freeplay mod that allowed me to take some major shortcuts


- Apply the Punch-Out freeplay mod from here .
- Using a hex editor, make these additional changes inside of the EPROM at 8L (MAME calls it chp1-c.8l),
change $12BE from 20 F5 to 18 F5
change $12EA from 20 C9 to 18 C9

Hopefully this makes it so you never have to worry about CRT burn-in even if your kids are playing your game while you are gone.

No comments:

Post a Comment