[WIN/1.0/1.1] MUGEN Techniques 7 - Null (Stecon) Overflow

Technique: Null/Stecon Overflow
Purpose: Using a very large number of state controllers in a single statedef (with a bug to exceed the limit of 512 controllers) to overwrite MUGEN's memory, allowing direct editing of data such as Alive flag, PalNo, and parent ID.
Japanese Search Term: ステコンオーバーフロー
(For Null Overflow to set the enemy's Alive flag specifically, search for 超即死/super-instant death)

For the next topic, I will cover Stecon Overflow. I figured this was a good topic to cover since we have already covered traditional custom states/methods to force the opponent into a custom state, and upcoming topics will be focused more on applications of Crosstalk than anything else.

Stecon Overflow is essentially a method to modify variables and flags that can't normally be modified within MUGEN's engine. For example, constants like PalNo and Alive, while usable in triggers, can't be directly modified to whatever values you want. Through Stecon Overflow, we can forcibly change the values of these flags (even to invalid values, such as negatives).

To understand Stecon Overflow, you need to first understand trigger persistency. Using 'persistent' on a controller causes it to run only every so many executions of the stecon. For example, 'persistent=5' causes the stecon to only run its code every 5 frames that the triggers are true. This is useful for, as an example, producing an Explod every n frames, or applying a HitDef at regular intervals.

This is easy enough to understand, but how exactly does this work? In MUGEN's backend, there is a portion of memory that stores the values of the persistent bytes for all controllers in a statedef (up to 512). When you change to a new state, the values of the persistent bytes in this portion of memory are all set to 0. Then, when a stecon's triggers evaluate to true, the game does the following:

1. If the persistent byte is 0, execute the stecon and set the persistent byte to the value of the persistent parameter.
2. If the persistent byte is non-zero, do not execute the stecon and just reduce the persistent byte by 1.

So knowing this, we know that on the first execution of a state all stecons are executed and the persistent values are loaded. Then on future executions, the persistent bytes are decremented until the next time they are 0. For example, if we define 'persistent=5', it will first be 0, then be set to 5->4->3->2->1->0 before executing the stecon again.

With this understanding of persistent we can start to understand Stecon Overflow. I mentioned there is a 512 byte long portion of MUGEN's memory dedicated to the persistent bytes, but this piece of memory is in a very important place. In fact, this piece of memory exists directly within the memory that defines various character variables (Alive, PalNo, StateType, HitPauseTime, etc) - all very close to the persistent bytes. The whole idea of Stecon Overflow is to create a StateDef with more than 512 state controllers, which can write persistent bytes to memory locations beyond the 512-byte block.

For example, the Alive flag is a 4-byte value (32-bit signed integer) located at the bytes 45 to 49 bytes past the persistent block. A Stecon Overflow with at least 557 state controllers can write persistent bytes to this location (since 512 = the persistent block, +45 = 557th persistent byte) and directly change the value of the Alive flag. Using this strategy on the opponent to set their Alive flag from 1 to 0 is known as super-instant death.

Here's a couple of specific restrictions on persistent which are relevant to Stecon Overflow: persistent on a non-ChangeState controller cannot be higher than 127 (even though a single unsigned byte can take values from 0 to 255); ChangeState is exempt from this and can have persistent up to 255. It can also take the special value 256, which is used to decrement a byte all the way to 0 without it being reset back to a new value if persistent = 0 and the state executes (really, really useful for when you need to reduce a byte to 0, such as Alive, but don't know its exact value beforehand).

So, if you're following so far and know about MUGEN's limitations, you might know that a StateDef can only have a maximum of 512 state controllers (Elecbyte was trying to prevent this exact overflow scenario from happening). If your character enters a state with 513 or more stecons, the game will crash, so we can't simply change to an overflow state and freely rewrite memory. However, there is an oversight related to hitpausetime: if a character has hitpausetime, only stecons with `ignorehitpause=1` will execute and count towards the 512-stecon limit. So by having hitpausetime and carefully placing `ignorehitpause=1` on crucial stecons, we can overwrite values past the 512-byte persistent block, without hitting the 512 stecon crash.

Be aware: The Alive flag is only located at the 557th Null stecon for the specific character executing those states, so if your own character (p1) enters the null states, the opponent (p2)'s Alive flag is untouched. Null Overflow is dependent on custom stating the enemy. To modify the p2's Alive flag without a custom state (i.e. from p1 entering the state), around 10k stecons are needed in one statedef which makes load times much longer; additionally the exact location can vary somewhat based on OS and MUGEN version. Therefore it is not considered stable or feasible to modify p2's data starting from p1.

Therefore, in order to directly change the enemy's Alive flag from 1 to 0, the following steps are needed:

1. Prepare a StateDef with at least 557 stecons (preferably 561). We normally use Null stecons for this (hence the technique name), since they occupy less space, and large Overflow statedefs tend to impact load times. It's also common to add a LifeSet to 0, or some Pause/SuperPause cancel stuff, to make the kill cleaner.
2. Add 'persistent=1' and 'ignorehitpause=1' to stecon 557. This will cover our bases for opponents with Alive equal to 1, but will not work for opponents that modify their Alive (you can handle these by looping through the Null states with a ChangeState with persistent=256, on all 4 bytes, continuing until Alive = 0).
3. Reverse the opponent to give them HitPauseTime via the pausetime attribute (pausetime=x,y gives you x hitpausetime and the opponent y hitpausetime), alongside a p2stateno of the Null Overflow statedef.
4. Make sure to have a SelfState out of the Null Overflow statedef, since if they are still in the statedef when HitPauseTime reaches 0, the game will crash.

A short example is provided below (not in full, just the core states). Also, I am giving a (very short) set of useful bytes that I know of. These are for 1.1 unless stated otherwise. A lot of these find their greatest use not used on the opponent, but used on yourself to disguise specific attributes (e.g. to set your Alive to a non-1 value).

Time: 513-516
StateType: 517-520
MoveType: 521-524
Physics: 525-528
Ctrl: 529-532
Guard state flag (block Target* sctrl): 533-536
Hitpausetime amount: 541-544
Alive (WINMUGEN): 553-556
Alive (1.0+): 557-560

PalNo (1.1 ONLY): 2153-2156

(fake) Parent ID (1.1): 2420-2423
(fake) Parent ID (WINMUGEN): 6689-6692

[Statedef 12345]
[State 1]
type=Null
trigger1=0
[State 2]
type=Null
trigger1=0
...
[State 557]
type=Null
trigger1=1
persistent=1
ignorehitpause=1
[State 558]
type=Null
trigger1=1
[State 559]
type=Null
trigger1=1
[State 560]
type=Null
trigger1=1
[State 561]
type=SelfState
trigger1=!Alive
trigger2=HitPauseTime<=2
value=0
ignorehitpause=1
persistent=256

Comments

Private comment