I like projects!

Fantasy Zone II DX Free Play Hack

Written 7/24/17

Fantasy Zone II DX is a new title released for the 1986 Sega System 16 hardware developed by M2, and released in 2008. Despite being developed for ancient arcade hardware, it was released to the public on the PlayStation 2 SEGA AGES collection, with a bundled emulator. Later, a 3DS port was made as well. Public arcade release or not, it was developed to run on a real board and a few were built for a Sega promotional event for the game.

This article isn't about running the game on the actual hardware, nor about the game itself. This is about the fact that the game only accepts credits for play with no DIP switches or menu to change this behavior (though there is a test menu). Extra annoying point: If there are any credits in the machine, the intro story won't play at all! This means if you coin it up and leave it there, it'll just cycle the demo and title screen but won't play the story or intro music. A free play modification that plays on zero credits and shows the story is ideal.

This modification does this, and still supports the intro story.

In order to add Free Play to a game that doesn't support it, there are a few constraints:
* When Start is pressed for either player, the game should begin normally
* When the game is not in use, the normal attract sequence should run (as if no coins are inserted)

Games like Pac-Man and Galaga have Free Play, but they just sit on the static "PRESS START" screen. This is bad for operators as well as collectors, as the game no longer shows the demo to illustrate the game, and the screen will slowly burn in with this PRESS START text prominently in the center. This sort of Free Play is not the desired type.

I attacked this problem piecewise; I'll outline each section and describe how the solution was obtained.

Starting a game without coins
Starting out fresh, it seems overwhelming, as it's unreasonable to expect to pick through all of the game's code and RAM looking for something useful. System 16 games are too big for that approach.

The first thing I did was set a watch point on the address to which the coin and start button inputs are read, which is $C41001. Using the mame debugger, a read watchpoint was put on this address with a WORD size:

wpset 0xC41001,2,r

I found a routine that would read inputs, and diff them with those of the previous frame, and store them in RAM. Button presses were stored starting at $228946 as byte-sized bitfields. I set a read watchpoint on this address next.

This triggered breaks in a few places. The one that stood out to me was a small subroutine that checked for P1 and P2 start buttons. After checking a few times I saw that D1 contained the number of credits inserted at the entry point.

I found that this routine only fired during the gameplay demo. I traced backwards from demoStartChk, and found that D1 was being loaded with the contents of $23A690, which contains the number of inserted credits. After setting a watchpoint on $23A690, I found similar routine that reads the credit count at the title screen:

Changing this function was similar. The P1 start check was changed in the same way. The branch was made unconditional. For P2, the nonzero check's branch after checking the credit count was simply removed by replacing it with NOP instructions, so that it always falls through to the title2PStart branch. The subroutine then looked like this:

Following the same pattern as the title screen, the fix is simple. I was concerned that the game disabled the story mode when a game start was possible because there might be a problem, but it works fine from what I can tell.:

Now we can start the game at any time. The core functionality of the mod is complete. However, the game will still subtract one or two credits when the game begins, which makes the coin counter go a little nuts displaying fun characters like / and . instead of staying at zero.

Changing the game cost to zero

Finding this one was a lot simpler. I set a watchpoint on writes to $23A690 (the credit count). That triggered during initialization (where it was zeroed out) as well as when a game was started with this little function:

Now the game always shows "CREDIT 0" in the corner. That's great, and not untrue - but it should read "FREE PLAY" instead.

Changing credit text to Free Play"

Since the System 16B has a tilemap system, with 2 bytes per tile, it's up to the developer about how to store and display strings. It can either be a C-string (char[]) or it would be direct storage of tilemap data to be copied to the tilemap itself. Fortunately, the "CREDIT 0" string is stored as a C-string in ROM:

$03AAC2 CreditString:
dc.b 'CREDIT 0',0

This is a pretty obvious change:

$03AAC2 CreditString:
dc.b 'FREE PLAY',0

Sure enough, FREE PLAY was displayed. However, some naughty function was replacing the L with a ' ' and the Y with a '0'. It appears that the base string is drawn on the tilemap, and then a function checks the credit value and updates the plurality of credit by writing a space or 'S', as well as the single-digit credit count in the corner.

I found the text-printing function by setting a watchpoint on $03AAC2, checking for a 10-byte length to see who's reading this string. I found a function at $023312 that is used for general-purpose string printing on the tilemap. This little shred of it was actually responsible for modifying the tilemap:

Not only did this give me the tilemap base address, but with MAME's debugger I was able to proceed hit-by-hit and watch individual characters get drawn. By halting on the final character of "CREDIT 0" I was able to get a watchpoint for that exact tilemap location. Using that, I found who was writing '0' to the corner of the screen over my FREE PLAY text. Turns out it's part of that same credit update function from before:

This one I fixed by removing the writes to $410DFB and $410DFE. In retrospect, just having this function early return would have likely worked just fine, but I wasn't 100% sure that this didn't do anything else with some side effects, or might be entered midway at some other point, so the structure of it is retained along with any effects it might have. Who knows, maybe that return value is important. I'm not interested in optimizing this function that is only called during init.

That press start routine decides to show "1 Player Start" or "1 or 2 Player Start" by checking if the credit count (d0) is 1. This naive implementation is great, because if credits are zero, that means it shows "1 or 2 Player Start". We don't even have to touch the routine:

This one's a very simple hack, but on the weapon select screen the Heavy Bomb weapon has a typo (or a bad dump? heh) that makes it say "H.MOMB". This is an easy fix: change $03ACA0 to 0x42 ('B'). The typo is kind of cute though, so I recommend not actually fixing it and leaving it as-is.

Double Omake: Rom Combining / Splitting
The board this game runs on has 8-bit ROM sockets, so two ROMs at minimum are needed for the 68000 program data. One ROM provides even bytes and the other provides odd bytes. These are "interleaved in hardware" in that they share the same addressa and control signals (except high/low byte select with /OE) and satisfy different halves of the data bus. In order to work with the dumped ROM images, they must be combined.

Here is a little C tool I threw together for combining or splitting interleaved files: