In a previous edition we made Tails playable in Sonic Jam. And in another previous edition we made Sonic team up with Tails (and/or Knuckles) in Sonic 3D Blast. Can we implement the buddy system in Sonic Jam, too? Yes we can!
Here’s a video of the hack in action:
We want both characters to respond to controller inputs (Sonic Jam only cares about player 1’s, alas). So the first thing we’ll need to do is have the game load two copies of Sonic instead of one.
The function at 06050A6E
is responsible for loading all of the important objects in Sonic World. It hooks up the camera, Sonic, Tails, and several other things. This logic is implemented by various other functions, which are referenced below the loading function body:
If we replace the Tails helper function with a copy of the Sonic helper function, that will give us two copies of Sonic and zero of Tails. The patch (which needs to be applied before Sonic World loads) is:
06050C3C 0604A59C
We have two copies of Sonic now, but only one is visible! We need to tell the game to render the second one.
The function at 0604EB98
is responsible for rendering Sonic. Notice that it reads from a fixed location - 0x001E0
bytes past the global base register (which is 060FFC00
for all of the Saturn Sonic Team games except Christmas NiGHTS):
The value at that location is what I call the “perspective pointer,” something all of the Saturn Sonic Team games use. It tells the game, e.g., where the camera should focus. That’s on Sonic for Sonic Jam, of course.
Our problem is that this only points to one of the Sonics. We need to somehow get the rendering function to execute twice, once for each copy of Sonic. Fortunately, we’ve already solved half the problem: adding a second Sonic already gave us another active instance of the rendering function.
We can take advantage of the Sonic Team “function chain” idiom (more on this in future editions) to solve the other half of the problem. Every function in the chain is passed the address of the byte following its definition in register R4. Consider this item:
That corresponds to something like sonic_renderer_0604eb98(r4=0x060D00D0)
in pseudo-Python. What we can do, then, is manually put a pointer to one of our Sonics in 060D00D0
:
We then repeat this for the second function chain item, but we use the address of the second instance of Sonic (mine was 060D0190
).
Now we need to change the “read from fixed location 0x01E0+GBR” instruction to “read from @R4.” The patch is:
0604EB9E 5040
That gives us two visible Sonics! However, they’re hard to distinguish because they’re on top of each other. We can separate them by manually changing one’s X position: the address to manipulate is 3C
bytes past the value we used for R4 above (in my case: 060D0190 + 3C
) - adjust it to taste.
Now we have two Sonics running around, responding to the same inputs. Neat! This edition described how we can turn one of them into Tails We’ll use two patches from that article:
0604A726 A020 0009
0604CADA E0FF
Because we want our Tails to respond to inputs like Sonic does, we’ll tell the game we’re using Sonic in the places where it checks. The patches are:
0604CA6C E000
0604D4FA E000
0604DDF0 E000
This almost works when we switch one of the Sonic to Tails, but the game crashes after a bit of running around. From what I can tell, it doesn’t like applying Sonic’s shader to both character models at the same time. So we’ll skip it and deal with a blocky-looking Tails:
0604A71E 0009
Now all we need to do is change the character index for one of the Sonics:
That’s it! Sonic and Tails, running around together. You’ll notice that Tails can run through walls and objects - this is a consequence of the game using that fixed address from above in other places. So this is not a terribly playable hack. But it’s cool to mess around with, and it helped me learn about the “perspective pointer,” which will be used in future editions.
E-mail me your Saturn reverse engineering questions! We’ll do some reader Q&A in a future editions.