Puyo Puyo Tsu is the sequal to Puyo Puyo, both entries in a series of great puzzle games. Puyo Puyo Tsu is one of the most important in the series and is still a preferred game with fans of the series today.
I did this reverse engineering project for a Computer Science House weekend hackathon. It was done just for fun.
The original version of the game is the Japanese arcade release. This version runs on Sega System C2 hardware, which is architecturally based on the Sega Mega Drive / Genesis. As the C2 was designed as a low-end arcade hardware option, there are several games that utilize it. Puyo Puyo Tsu is elusive, but other System C2 games are easier to get a hold of, like the first Puyo Puyo game. Other than ROM chips storing game code and data, and a specialized CPLD, all C2 games run on the same hardware.
This picture is of an actual Sega System C2 board, with Puyo Puyo Tsu on it.
Not every C2 is created equal. Each C2 game has a CPLD on board, responsible for processing some video configuration - common to all games - but it also has a stateful lookup table that returns values unique for each game. This LSFR-indexed table is used for copy protection, so game software can verify it hasn't been burned to another PCB. Sega did not want arcade operators to be able to simply burn a new game rather than buy another board. The one in this picture is the one matched to Puyo Puyo Tsu: 315-0228.
With copy protection specific to each game, you might expect that a conversion simply wouldn't boot and would do nothing when powered on. However, protection is not enforced in the hardware itself; not interacting with the CPLD's protection registers doesn't halt the board or keep the CPU in reset, or anything like that. Any copy protection enforcement is implemented in software exclusively. As a result, how the game works or doesn't work when protection fails is up to the game. One game, Bloxeed, does no protection checks, and can be run on any System C board.
So, Puyo Puyo Tsu will boot on any System C2 PCB - it doesn't crash, or show a warning screen. In fact, there's no obvious indication that anything is wrong, and the game will appear to run fine. The developers were sneaky! A failed copy protection intentionally tweaks one crucial gameplay feature - ethe garbage system, and the related sousai (countering) mechanic. By disabling the garbage system, the game effectively is reduced to a single player practice mode since the opposing player can no longer be attacked! Although you can win the first level by the fluke, the game is for all intents and purposes not playable as intended.
I did actually manage to complete a round of the game in this neutered state! However, I was unable to beat the second stage, as the opponent was smart enough to not incur a loss on itself. Winning the first round was more or less a matter of chance, and is an exercise of patience!
As I mentioned before, other Systme C2 games are easier to find than this one. I had a Puyo Puyo PCB, the first game. I wanted to play the Tsu without sourcing and programming a replacement CPLD.
First I looked to see if someone had done this already. I found one person who claimed to have already done it, but after inquiring I was informed that he wanted 60 Euros for the fix. Absolutely not!! Time to get down and dirty and do it myself.
For development of the solution to be easier, an emulator is a good idea. If you run MAME and emulate Puyo Puyo Tsu (puyopuy2) the game will run as expected. However, we want to simulate what happens when we run the game on the wrong hardware. I simply copied the Puyo Puyo Tsu roms to the folder for another game (ichir) and allowed it to boot up the Puyu Puyo Tsu code in the context of another game. This tripped the protection check as expected. With a test environment set up, the actual work can begin.
Research is naturally the first step. Charles McDonald's documentation tells me that the CPLD's protection registers are mapped to the address $800001. A quick disassembly of the game later, I searched for reads from $800001. I wasn't yet familiar with Mame's debugger, so I did not think ot set a watchpoint for this address in an emulator, but that would have brought me to the same place.
I found just one spot where register d2 was being loaded with the contents at $800001. I was actually very lucky to have found it so easily, as all other reads and writes from the CPLD were obfuscated with an odd offset address. a2 was loaded with $7FA9F1, and is accessed with an absolute indexed addressing scheme (with $5810 as the offset).
Let's quickly review how the CPLD works: The CPLD contains a table of 256 entries, each four bits wide. When reading from it, the index into this table is 8 bits. The upper four bits are the second-to-last thing written to the protection registers ($800001) and the lower four bits are the last thing the CPLD returned. In this way it has some internal state.
So, that landed me into the copy protection check routine. In short, it compares what it reads from hte CPLD with a table, which interestingly does NOT match what the MAME driver provides. Here is the protection routine, disassembled and annotated:
If this routine finds a discrepency, it marks a flag at $FFA026 indicating that the protection check failed, and that flag is checked every time the player pops a Puyo group in the playfield. When it's non-zero, the garbage system is effectively disabled.
So, looking at this code we can think of a few potential ways to beat the copy protection:
-simply return at the start of the protection check, skipping it all
-replace the flag setting with a no-op, so there is no consequence to the failed check
-change the cmp.b d2, d1 to a useless cmp.b d2, d2 or d1, d1 so the comparision is always "correct"
I tried all of these potential solutions. They work in that I was able to restore the garbage system. I celebrated only briefly, because after playing one round of the game I discovered a new problem. After I won, the game locked up! I could not get to the next stage. It wasn't entirely frozen, as the animations on-screen continued to run and the game still played sounds when I inserted coins. The player sprites are supposed to appear at the bottom and say a voice clip, but they never appeared. The game just sat here forever:
Puyo Puyo Tsu has a complicated object system, through which everything is implemented. Objects have callbacks to functions that should be run on every time interval (which in this case is synchronized to the display's vertical retrace). The intro sequence, menus, actual gameplay - everything runs through objects wiht callbacks. Armed with this knowledge, let's return to the problem at hand.
The winning player sprite should appear in the winner's playfield. The losing sprite appears in the loser's playfield, but also says a voice sample and then falls off screen. After the fall, the screen fades out and the game proceeds to the next level. Well, if everything in this game is run via object callbacks, I'd expect that the missing losing player sprite would have been responsible for arbitrating the progression to the next part of the game! So, something is preventing the two player sprites from being spawned.
I spent a while looking at the small pool of memory the game uses for the object list, and started figuring out the format a little. What I thought were object ID types were actually just pointers to their routine that should be fun. That explained why objects would flip between different "types" every so often - the callbacks were being changed based on object state. I looked at what the correctly-running game should do, and tried to figure out which object was doing what to let the game proceed, and what was keeping it from spawning!
I even went as far as to check against the working, original game, and painstakingly set the exit conditions with some hand-assembled code, which would set the registers to exactly what they should be in case the game was checking those after returning from the function. No dice.
All that object system analysis was a waste of time, though. I took a break from it, and when I came back I thought "How can my disabling of the copy protection possibly trigger something like this?" I had very carefully ensured there were no side effects to the copy protection routine; the only write to main memory was the flag I had NOP'd out, and the memory-mapped hardware makes no external modifications. It then occured to me to set a memory watchpoint on the routine, not for execution but for reading. I did that, and it got triggered right as a round of the game began!
It turns out the game not only has copy protection, but also copy protection protection. Before the start of the round, a second copy protection routine is run which checksums the first copy protection routine. If it fails, it sets another flag. If that flag is set, it prevents the sprites from spawning.
The solution turned out to be quite simple, then - I NOP'd out the check for the flag that prevents the sprites from spawning. It was 5:00 AM, and I was tired of it and not interested in reversing another protection routine. No, I opted for the cheaper solution, which was to just remove the consequences of a failed check. Let it fail, see if I care! move.w #$FFFF, d1 got replaced with a few NOPs.
The end result is Puyo Puyo Tsu for C2, on any C2 board. I've played through the entire game multiple times, and also tested the versus mode quite a bit. An IPS patch is available here. Please do not attempt to profit off of this work. I can accept no responsibility for improper modification to your Sega System C2 board if you should choose to make your own Puyo Puyo Tsu conversion.
Here is the modified game, for free.
Back to main index