Reverse engineering Street Fighter II - Rainbow Edition

Discuss previously released ROM Hacks here. Total Conversions, Kaizo Hacks, Vandalized monstrosities, anything goes.
Post Reply
User avatar
Posts: 25
Joined: Fri Feb 14, 2020 2:51 pm

Reverse engineering Street Fighter II - Rainbow Edition

Post by Gnawtor »

*this post contained wrong information due to a dumb mistake I made and a corrective post is forthcoming*

User avatar
Posts: 25
Joined: Fri Feb 14, 2020 2:51 pm

Re: Reverse engineering Street Fighter II - Rainbow Edition

Post by Gnawtor »

Alright, sorry about that bullshit before. What happened was I was dumb and was comparing the wrong version of unmodified SFII Champion Edition to Rainbow Edition (henceforth abbreviated as RB). This research will be based off a hex comparison of the program roms from romsets sf2ceja and sf2rb. I am using sf2ceja as a control as it shares the same assembly date with sf2rb.

Before going any further, I would like to do my part to help dispel some widespread misinformation about this hack. Many supposedly reliable sources out there attribute this hack to a group called "Hung Hsi Enterprise" due to an ASCII string found in the hack's program that reads "DISTRIBUTED BY HUNG HSI ENTERPRISE IN TAIWAN". This text actually appears in the rom of most if not all official versions of SFII:CE that I have looked at. This was most likely just the name of the Taiwanese company that secured the distribution rights to SFII:CE in that country. This text appears on the title screen in one known official romset, sf2cet.

Anyway, let's move on.

The first change in RB appears fairly early on in the ROM. The hackers have hijacked some code that controls a frame skip feature in the original game, which can be seen when pressing a button while the region warning text is drawing to make it draw faster, or during a victory pose to speed up the background and points additions.

Code: Select all

000C76  clr.w   (-$75d0,A5)
000C7A  tst.b   ($2dd,A5)		; Test if speed-up button pushed
000C7E  bne     $c8e			; branch to turbo if speed-up pushed
000C80  tst.b   ($2d3,A5)		; Test if program is in Test Mode
000C84  beq     $cbe			; No turbo if not Test Mode
000C86  btst     #$0, ($88, A5)   ; Test dip switch C1
000C8C  beq     $cbe			; Branch to no turbo
This is a snippet of the unmodified code from the original. This performs a series of tests to see if the frame skip code should run.

Here is the same snippet of code in RB:

Code: Select all

000C76  clr.w   (-$75d0,A5)
000C7A  tst.b   ($2dd,A5)		; Test if speed-up button pushed
000C7E  bne     $c8e
000C80  tst.b   ($2d3,A5)		; Test if program is in Test Mode
000C84  bne     $c8e
000C86  jsr     $e544a.l		; Jump to new Rainbow Edition code
000C8C  bne     $cbe			; branch to no turbo
The same checks take place (with a different branch condition code for the Test Mode check) but then the hackers have inserted a jump that takes us to some new code added to an empty part of the ROM. Also, the branch instruction at $C8C has been changed from a BEQ to a BNE. More on that later.

Code: Select all

0E544A  movem.l D0-D4/A1, -(A7)
0E544E  clr.w   D2
0E5450  clr.w   D0
0E5452  move.b  $201201.l, D0
0E5458  move.b  $281201.l, D2		; Shitty bootleg protection
0E545E  sub.b   D0, D2
0E5460  subi.b  #$30, D2		; Result: 0xE
0E5464  jmp     ($4,PC,D2.w)		; Jump to $E5478

First it pushes some register values to the stack. Then it reads two values from a memory region that falls outside of the normal jurisdiction of the CPS1 ($201201 and $281201). This is where my knowledge on RB becomes murky, because I always understood that this hack was sold as an EPROM upgrade kit for legit CE boards, but this suggests some sort of on board protection. At any rate, MAME's driver forces the expected values into these addresses (#$2 and #$40 respectively). Then the first value is subtracted from the second, and then the program does an immediate subtraction of #$30 to get the final value #$E for the jump offset. The jump offset never changes. The address used for the indirect jump instruction is actually in the middle of the next instruction, perhaps so if the protection fails it will run garbage and crash the game.

The jump takes us here:

Code: Select all

0E5478  clr.w   D4
0E547A  move.w  ($9c0,A5), D0		; Matches Cleared counter
0E547E  move.w  D0, D3				; Preserve Matches Cleared counter to d3
0E5480  move.b  ($64c,A5), D1		; 1P human (0) or CPU (1)
0E5484  or.b    ($94c,A5), D1		; 2P human (0) or CPU (1)
0E5488  beq     rb_turbo_noturbo	; $e54dc	; branch out of turbo if this is a PVP game
0E548C  move.b  ($87,A5), D4		; load DIP switch bank B
0E5490  lsr.b   #4, D4				; Shift switches 5 through 8 right.
									; Bit mask according to switch numbers:
									; XXXX 8765
									; These switches are documented as
									; unused in SFII:CE manual.
0E5492  andi.w  #$f, D4
0E5496  btst    #$3, D4				; Test highest bit of DIP bank B (switch 8)
0E549A  beq     rb_turbo_fixed			; $e554c ; Branch to Fixed Turbo mode if switch 8 is off
0E549E  bclr    #$3, D4				; Switch 8 is on, and we are in Variable Turbo mode
									; Clear switch 8 from our dip read
0E54A2  move.w  (-$6f2e,A5), D1		; Load preserved round clear counter
0E54A6  cmpi.b  #$7, D4				; A comparison with no action taken after.
0E54AA  move.w  #$0, D1
0E54AE  sub.b   D1, D0				; A weird way of checking if rounds cleared counter is zero.
0E54B0  bls     rb_turbo_noturbo				; Branch out of turbo if this is the first match
0E54B4  mulu.w  #$c, D4				; Multiply dip B 5,6,7 values to create turbo table base offset
0E54B8  add.w   D4, D0				; Add to rounds cleared counter to create final offset
0E54BA  move.b  ($30,PC,D0.w), D1	; Load turbo counter max value
0E54BE  move.b  (-$6f30,A5), D0		; Load turbo counter current value
0E54C2  cmp.b   D1, D0				; Compare current turbo value to max
0E54C4  bhi     rb_turbo_clearcounter ;$e54d2 ; Branch if turbo counter max exceeded

0E54C8  addq.b  #1, (-$6f30,A5)		; Increment turbo counter
0E54CC  movem.l (A7)+, D0-D4/A1		; Pull register values from stack and return
0E54D0  rts

0E54D2  clr.b   (-$6f30,A5)			; reset turbo counter (returns with Z flag set, frame skip will run)
0E54D6  movem.l (A7)+, D0-D4/A1
0E54DA  rts

0E54DC  move.w  D3, (-$6f2e,A5)		; Store matches cleared counter
0E54E0  move.b  #$1, (-$6f30,A5)	; Store 1 at turbo counter
0E54E6  movem.l (A7)+, D0-D4/A1		; Pull register values from stack and return
0E54EA  rts

0E554C  tst.b   D4					; Test D4 to see if any of switches 5,6,7 are set
0E554E  beq     $e54dc				; Branch out of turbo if no switches set.
0E5550  move.b  ($10,PC,D4.w), D1	; This turbo mode doesn't use the matches cleared counter.
									; Dip B 5,6,7 control the turbo counter max values.
0E5554  move.b  (-$6f30,A5), D0
0E5558  cmp.b   D1, D0
0E555A  bhi     rb_turbo_clearcounter ;$e54d2 ; Clear turbo counter
0E555E  bra     rb_turbo_incrementcounter ;$e54c8 ; Increment turbo counter
Allow me to translate into English what's happening here:

First thing this function does is check if both players are human controlled. If so, it will return out of turbo mode and not speed up the game. The hackers wanted their turbo feature to only work during CPU battles.

RB has utilized four dip switches that were unused in the original SFII:CE. Dip bank B, switches 5, 6, 7, and 8 now control RB's turbo mode. In fact, RB has added two different turbo modes for operators to choose between. Turbo mode will not run at all if Dip B SW 5,6,7,8 are all off. SW 8 controls which of the two turbo modes will run. Since nobody else has given these a name, I will call these modes Fixed Turbo and Variable Turbo.

Fixed Turbo is enabled by setting DIP B SW 8 to OFF and any of DIP B SW 5,6,7 (let's call these the turbo intensity switches) to ON. The turbo intensity switches is used as an offset to a table of values that control how fast the game runs. The full list of turbo values for Fixed Turbo mode are:

Code: Select all

80 50 40 30 28 18 10 08 04 02
However, since Fixed Turbo mode can only run if any of the turbo intensity switches are set, the first value #$80 goes entirely unused. Likewise, the highest value we can get from that table with the 3 switches is #$08, and the last 2 values can never be read.

Variable Turbo makes the game go faster based on a combination of which Turbo Intensity switches are set, and how many matches the player has cleared. The player has to clear at least one match (flag crossed out on world map) for Variable Turbo to run. The higher the turbo intensity switches, and the more matches have been cleared, the faster the game will run. After a certain point it becomes virtually unplayable.

Now, how does this actually affect the game itself?

RB's turbo code controls a counter that the hackers set aside in an unused section of RAM. Whenever either of RB's turbo modes runs, it will check this counter against the turbo value, and if the counter has exceeded the turbo value, the counter will be reset. Otherwise, the counter is incremented. What's happening is when this new subroutine returns back to SFII:CE's existing turbo code, it uses the zero flag to determine whether it will skip a frame or not using the original game's turbo mode where this hijack was inserted into. If the turbo counter was reset, then it returns with the zero flag set, and the frame gets skipped. The lower the turbo value, the more often the code will return zero and a frame will get skipped. Sort of like a controlled trickle of the game's existing turbo function.

So, to summarize the rules of Street Fighter II: Rainbow Edition's turbo modes:

- Only works during a fight against a CPU opponent.
- DIP bank B switch 8 selects between Fixed Turbo (OFF) and Variable Turbo (ON)
- DIP bank B switches 5, 6 and 7 control the intensity of the turbo modes.
- Variable Turbo only works after you have cleared at least one match off the world map.
- Variable Turbo becomes faster the more matches you clear.
- Fixed Turbo's speed is controlled solely by the turbo intensity switches and works even if you haven't cleared any matches yet.

DIP switch map for RB's turbo mode:

Code: Select all

  5    6    7    8
 OFF  OFF  OFF  OFF  |  Turbo Disabled
 ON   OFF  OFF  OFF  |  Fixed Turbo 1
 OFF  ON   OFF  OFF  |  Fixed Turbo 2
 ON   ON   OFF  OFF  |  Fixed Turbo 3
 OFF  OFF  ON   OFF  |  Fixed Turbo 4
 ON   OFF  ON   OFF  |  Fixed Turbo 5
 OFF  ON   ON   OFF  |  Fixed Turbo 6
 ON   ON   ON   OFF  |  Fixed Turbo 7
 OFF  OFF  OFF  ON   |  Variable Turbo 1
 ON   OFF  OFF  ON   |  Variable Turbo 2
 OFF  ON   OFF  ON   |  Variable Turbo 3
 ON   ON   OFF  ON   |  Variable Turbo 4
 OFF  OFF  ON   ON   |  Variable Turbo 5
 ON   OFF  ON   ON   |  Variable Turbo 6
 OFF  ON   ON   ON   |  Variable Turbo 7
 ON   ON   ON   ON   |  Variable Turbo 8
Next we will most likely be looking at how this hack has changed how projectiles work.

Post Reply