Random number generator

The random number generator is pretty simple. A 16-bit seed is stored which for convenience in code is split into two 8-bit values. The routine is at 0xdd4 in the sf2ua ROM. Here’s the equivalent C code:

short sf2rand(void) {
    int x = (g.randSeed1 << 8) + g.randSeed2;
    x *= 3;
    x = x >> 8;
    g.randSeed2 += x;
    g.randSeed1  = x;
    return g.randSeed2;
}

As far as PRNGs go it’s pretty poor, but good enough for our purpose here. The output of it is often bitmasked to limit its values (e.g. masked with 0xf to get a number from 0-15, masked with 0xe to get an even number from 0-14, and so on), and also used as a bit index with the BTST instruction to get a 1-in-N chance.

For example, to get a 1-in-4 chance:

if( 0x88888888 & (1 << (sf2rand() & 0x1f)))

The RNG is seeded with exactly the same value every time the machine starts (there’s nothing else to seed it with, no RTC, nothing), so it always produces the same string of random numbers.

If you start the machine, wait for the player invite screen to start, insert a coin and play with Ryu, you will face Dhalsim on your first fight. If you insert your coin while the copyright warning is still printing, you’ll face Blanka instead.

One of these days I should analyse it to see when the seed returns to its initial value, i.e. when the sequence starts repeating.

Random number generator

Guile’s World Warrior bugs

One of the first things I went looking for was to see what the cause of Guile’s Invisible Throw bug (and others). This is why I started with the earliest, buggiest version, SF2UA, date 910206.

A ‘move’ in Street Fighter is encoded using three variables, one to signal whether the move a standing / crouching / jumping attack, another for punch / kick, and an ID. Another variable tracks whether they are standing, walking, attacking, or executing a power move. These are used in finite state machines which are called each frame to process the animations.

The bugs result from still checking for power move input after an ordinary attack has been selected (because players are allowed to charge back while still executing other moves), and a nasty side effect of the button decoder setting the Punch/Kick variable directly without even know the player’s context.

First, we’ll look at the ‘Stance’ bug. You get it by being close enough to your opponent to do the upside-down kick (within 70 pixels), charging back, pressing roundhouse to begin the kick, and then doing a sonic boom before the kick completes. You have to press fierce after the roundhouse, and within the same window that a Sonic Boom would be allowed. Guile executes his upside-down kick, but instead of returning to a standing position, he gets stuck on the last frame of the upside down kick animation. He can be hit out of this, but the AI players don’t usually do this as they think he’s still doing some sort of kick that seems to last forever. Vega gets very confused and does his slide attack repeatedly.

The upside-down kick is Standing, Kicking move #6.

What happens is the standing kick gets selected, and the animation begins normally. The Sonic and Blade state machines keep running throughout attacks; this is necessary to allow players to charge back/down while still executing a move. At this stage, because we’ve charged back for long enough, and are now pressing forward, the Sonic Boom state machine is now checking for a button press, and when it detects one it calls the button decoder.

The button decoder (you’ll find it at address 0x3296 in the SF2UA ROMs) is basically a chain of ‘if’ statements that test a bit for each button, and set the corresponding Punch/Kick and Strength, bailing out at the first match.  The last condition, a strong kick, isn’t even tested for, it’s returned by default is no other buttons are detected. It only makes sense to call the subroutine if it’s already known a button is down.

Punches are tested before kicks, and when we press our punch button the player Punch/Kick variable gets set to ‘Punch’, so our player’s state is now Standing, Punching move #6 which actually represents one of Guile’s throws. The animation doesn’t change, but the state machine for the throw runs instead. This is at 0x2f922.

Where the state machine for the kick simply waits for the animation to finish and then sets the player’s state and sprite to a normal standing pose, the state machine for the throw is different. It looks for a special flag in the animation that signals that the character being thrown should now be released, but this never comes so Guile gets stuck in this state.

All of Guile’s bugs centre around this setting of one of his kicks, which then get turned into punches with the same ID by this bug in the button decoder, and were fixed in the next release (sf2ub, sf2eb, and sf2jb).

Guile’s World Warrior bugs

Reverse engineering Street Fighter 2 – The World Warrior

Years ago (2005!) I started reverse engineering the ROMs for Street Fighter 2 – The World Warrior in my spare time, and rewriting the whole thing (yes, the whole game) in C.

Since I can’t likely share my source or binary without infringing Capcom’s copyright, I decided I should at least start a blog to share with the world what I’ve found from the code, for whatever reason you might be interested.

I’ll likely never be able to release my full code or binary, so I want to share as much of my findings as I (legally) can so if for some reason I can’t continue, not too much of this goes to waste.

I also wrote a graphics engine to emulate the CPS’s tile graphics hardware using OpenGL. Because this code is mine, I’ll be sharing parts of it with you.

Reverse engineering Street Fighter 2 – The World Warrior