X-Men vs. Street Fighter · CPS-2

XMvsSF 3v3 + DHC.

Arcade X-Men vs. Street Fighter is two on two. This makes it three on three: pick three characters, tag either reserve in, chain one Hyper into the next with a Delayed Hyper Combo, and snap the other team's point out.

It's a patch for your own ROM, the Europe 961004 set (xmvsf), with a RetroArch core included. Two players, or six: one controller per character, P1, P2 and P3 against P4, P5 and P6. The original screen, or a wide arena at 512×224. And there's a two-player build for Fightcade, still a preview.

3characters a sidetag either reserve, or cycle through all three
6playersone controller per character, in the included core
18coloursper character: nine inputs, each with or without Start
512pixels widean optional arena, 64 more columns a side

v1.1.1, preview. v1.1.1 fixes LK and MK pressed together in the two-player game while RetroArch's SOCD option is on, and the Mac launchers now bring RetroArch to the front; v1.1 added eighteen colours a fighter and HP+HK cycling through the whole team. It's still a preview: every run so far is scripted, with ordinary controller inputs, on the Linux core and its diagnostic build, apart from one Mac run of the two-player package. Not run yet: Windows PowerShell 5.1 on Windows, the other macOS runs, physical controllers, and the Fightcade client or online play. The third-tag fix for the reports on v1.0.0 hasn't been confirmed by the players who sent them, and the choppy snapback switch isn't changed yet. One refusal, a Team Hyper whose partner slot is still in use, hasn't come up in a run. The RetroArch packages need their included core: stock FinalBurn Neo, MAME and Fightcade can't run them, and there's no arcade-board version.

Before you play: Control card · What's been checked, and what's open · Where the colours come from · Package receipt · The core's source.

$NEW · start here

The three-member HUD: Gambit, Cyclops and Wolverine against Ryu, Ken and Dhalsim, three portraits and three life bars a side, with Gambit and Ryu on point
v1.0.0 · three a side

What's three on three?

Each side picks three characters. One fights, the point; the other two wait as reserve A and reserve B, in the order you picked them. All three count for the timer, and a team is out when all three are down.

A Delayed Hyper Combo: Ken's Hyper cut-in over the stage while the first Hyper's rays still fill the screen
v1.0.0 · Hyper into Hyper

What's a DHC?

A Delayed Hyper Combo. During your Hyper, enter the next member's Hyper motion and finish it with MP+MK or MP+HK: they come in with their own Hyper and stay in. Each Hyper costs its own meter.

A snapback lands: Ryu's kick sends Cyclops out, and Wolverine, the reserve Ryu named, is forced in next
v1.0.0 · snapback

What's a snapback?

QCF+MP+MK or QCF+MP+HK, for one level. A clean hit forces the other team's point out and drags the reserve you named in, so you pick who you fight next.

Watch the video.

Three a side, Delayed Hyper Combos, snapbacks, six controllers and the wide arena, in three minutes.

Watch the video.YouTube ↗

Three minutes, 1440p, with chapters. Every game shot is the v1.0.0 build in FinalBurn Neo with ordinary inputs, frame for frame at the board's 59.63 Hz; the numbers on the rails are the game's own RAM from each take. The board scenes are a drawing, with counts measured from the 68000 while the takes were recorded. The game's strobes are softened, and the video's description has the photosensitivity note. Music: Moment 37 (Less Power Mix) by morphus56k. Narration: an ElevenLabs voice. The video is a cut, not proof: the receipts are.

What it does.

Ten pieces. Every clip says which build it shows and how it was recorded.

v1.0.0 six-player build · FinalBurn Neo · ordinary inputs

01THREE A SIDE

Three picks, one on point.

Each side picks three in turn, and in six-player mode each player picks their own. No character twice in one team; the same character on both teams is fine.

v1.0.0 six-player build · FinalBurn Neo · ordinary inputs

02TAGS

Tag either reserve in.

MP+MK brings in reserve A and MP+HK reserve B. HP+HK, the game's own tag, brings in the next one still alive: first to second, second to third, third to first. A reserve that's down keeps its place, and a request never swaps in the other one. Reserve order follows the original picks excluding the current point. Release MP, MK and HK before the next direct request; after an ambiguous MP+MK+HK, release all three.

v1.0.0 two-player build · FinalBurn Neo · ordinary inputs

03THE THIRD

Whoever's left comes in.

When the point is knocked out, the partner the game keeps ready comes in if it's alive, as in the arcade game. If it isn't, the third member drops in instead.

v1.0.0 two-player build · FinalBurn Neo · ordinary inputs

04DELAYED HYPER COMBO

One Hyper into the next.

During your Hyper, enter the next member's Hyper motion and finish it with MP+MK or MP+HK. They come in with their own Hyper and stay in. Each Hyper costs its own meter, and each character joins a chain once. This is sequential; the separate QCF+HP+HK Team Hyper starts simultaneous attacks for two levels.

v1.0.0 six-player build · FinalBurn Neo · ordinary inputs

05SNAPBACK

Snap their point out.

QCF+MP+MK or QCF+MP+HK for one level, from neutral or cancelled from a ground normal that made contact. A clean hit forces the point out and the reserve you named in. Blocked or missed, the level is still spent.

v1.0.0 six-player build · FinalBurn Neo · ordinary inputs

06TEAM HYPER

The arcade game's team moves.

QCF+HP+HK with two levels for both Hypers at once, and the Variable Counter while blocking, as the arcade game has them. The partner is your first living reserve.

Magneto in all eighteen colours: the top row is the nine inputs alone, colours 1 to 9, and the bottom row is the same nine with Start held, colours 10 to 18v1.1 build · drawn from its colour table

0718 COLOURS

Eighteen colours each.

Confirm with a button for colours 1 to 6, or with a punch and its kick together for 7 to 9. Hold Start with any of those nine for 10 to 18. If the other team already has that character in that colour, you get the next free one. Chun-Li keeps Start for her other outfit, so she has nine colours in each.

v1.0.0 two-player build, wide arena · FinalBurn Neo · ordinary inputs

08ARCADE

CPU teams have three too.

Every CPU team up to Apocalypse has three members, and Apocalypse stays a solo boss. The partner battle after him stays as the game has it.

v1.0.0 six-player build, wide arena · FinalBurn Neo · ordinary inputs

09WIDE ARENA

See more of the stage.

The WIDE launcher opens a 512×224 screen with 64 more columns on each side. Stages keep their ends and fighters their limits: you see more of each stage, and fighters can stand further apart.

v1.0.0 six-player build · FinalBurn Neo · six controller ports

10SIX PLAYERS

One controller per character.

P1, P2 and P3 against P4, P5 and P6. The point's owner tags, starts DHCs and snapbacks; the incoming character is played by its own owner, and waiting players' buttons do nothing in combat.

Two players, six, or Fightcade.

Pick one, then the download for your computer. A RetroArch package holds the installer, the core and both screens; the Fightcade package holds an installer for Fightcade's own emulator.

2 players · v1.1.1 preview

Two players

RetroArch · Windows, macOS, Linux

P1 against P2, a team of three each, on one computer.

  • Tags, Delayed Hyper Combos, snapbacks, Team Hyper and eighteen colours
  • CPU teams of three and the whole arcade mode
  • The original screen and the wide arena, one launcher each
  • The installer, the core and the launchers in one download

Needs: RetroArch (free) and your own clean xmvsf.zip (Europe 961004) with its key file

Download · Windows x86-64 · XMvsSF-3v3-DHC-v1.1.1-2P-windows-x86_64.zip · 4.1 MB · SHA-256 c221126706fc…Download · macOS Apple silicon · XMvsSF-3v3-DHC-v1.1.1-2P-macos-arm64.zip · 4.1 MB · SHA-256 3c9155facef8…Download · Linux x86-64 · XMvsSF-3v3-DHC-v1.1.1-2P-linux-x86_64.zip · 4.3 MB · SHA-256 b8b3bfd99fe2…Install it ↓
6 players · v1.1.1 preview

Six players

RetroArch · Windows, macOS, Linux

Six controllers on one computer: P1, P2 and P3 against P4, P5 and P6.

  • Everything in the two-player version
  • Each player owns the character they pick, through tags, DHCs, snapbacks and KOs
  • Waiting players' buttons do nothing in combat
  • P1 and P4 run their team's menus

Needs: RetroArch (free), your own clean xmvsf.zip (Europe 961004) with its key file, and six controllers

Download · Windows x86-64 · XMvsSF-3v3-DHC-v1.1.1-6P-windows-x86_64.zip · 4.1 MB · SHA-256 b9106213b53c…Download · macOS Apple silicon · XMvsSF-3v3-DHC-v1.1.1-6P-macos-arm64.zip · 4.1 MB · SHA-256 9035019560d1…Download · Linux x86-64 · XMvsSF-3v3-DHC-v1.1.1-6P-linux-x86_64.zip · 4.3 MB · SHA-256 c951c666e279…Install it ↓
2 players · Fightcade · v1.1.1 preview

Fightcade

Fightcade · Windows, macOS, Linux

P1 against P2 on Fightcade's own FinalBurn Neo, with no extra core. A preview: not tried online yet.

  • Tags, Delayed Hyper Combos, snapbacks, Team Hyper and eighteen colours
  • CPU teams of three and the arcade mode
  • The original screen only: no six players, wide arena or miniature faces
  • One installer for Windows, macOS and Linux writes the whole xmvsf.zip

Needs: Fightcade, your own clean xmvsf.zip (Europe 961004) with its key file, and an opponent with the same build code

Download · Windows, macOS, Linux one package · XMvsSF-3v3-DHC-Fightcade-v1.1.1-2P.zip · 284 KB · SHA-256 e8572f465583…Install it for Fightcade ↓

Install it.

Unzip the package, give the installer your xmvsf.zip, then start a launcher. The installer writes new files in the package folder and never changes your ZIP.

What you need: RetroArch (free), and your own clean X-Men vs. Street Fighter ZIP, xmvsf.zip: the Europe 961004 parent set that MAME, FinalBurn Neo and Fightcade use, with xmvsf.key inside. The packages hold no game data. The launchers start RetroArch with the package's own core and settings and leave your RetroArch configuration alone.

Windows x86-64

  1. 01Install RetroArch.It's free: retroarch.com > Download > Windows > Installer. Keep the folder it suggests.
  2. 02Unzip the package.Right-click the ZIP > Extract All, and keep the folder together.
  3. 03Drop your ZIP on the installer.Drag your clean xmvsf.zip onto INSTALL (drop xmvsf.zip here).cmd. If Windows says it protected your PC: More info > Run anyway. It checks your ZIP and writes three files into the folder.
  4. 04Start a launcher.PLAY XMvsSF 3v3 2P.cmd for the original screen, PLAY XMvsSF 3v3 2P WIDE.cmd for the wide arena (6P in the six-player package). Start a new match from a fresh boot before loading a state; keep stock, v1.0.0, other core and standard/WIDE states separate.

macOS Apple silicon

  1. 01Install RetroArch.From retroarch.com, into Applications. The App Store version can't load a core from outside the store.
  2. 02Unzip the package.Double-click the ZIP, and copy your clean xmvsf.zip into the new folder.
  3. 03Open the installer.Right-click INSTALL.command > Open, then Open again: macOS asks the first time because the file was downloaded. It needs Python 3.8 or newer; accept the command line tools if macOS offers them.
  4. 04Start a launcher.Right-click PLAY XMvsSF 3v3 2P.command (or the WIDE one) > Open the first time. It finds RetroArch in Applications and brings it to the front (6P in the six-player package). Start a new match from a fresh boot before loading a state; keep stock, v1.0.0, other core and standard/WIDE states separate.

Linux x86-64

  1. 01Install RetroArch.Your distribution's package, the Flatpak or the Snap. Keep the package in your home folder for the Flatpak.
  2. 02Unzip the package.unzip the download anywhere, and keep the folder together.
  3. 03Run the installer../install.sh /path/to/xmvsf.zip (Python 3.8 or newer). It checks your ZIP and writes three files into the folder.
  4. 04Start a launcher../play-2p.sh for the original screen, ./play-2p-wide.sh for the wide arena (play-6p in the six-player package). Start a new match from a fresh boot before loading a state; keep stock, v1.0.0, other core and standard/WIDE states separate.

Two screens

OriginalPLAY XMvsSF 3v3 2P (or 6P) runs the original 384×224 screen, at 4:3.

WidePLAY XMvsSF 3v3 2P WIDE (or 6P WIDE) runs the 512×224 arena at 16:9, from its own patched files in the package's system folder. Save states work within one screen.

For Fightcade.

One package for Windows, macOS and Linux. It builds a whole xmvsf.zip for Fightcade's ROMs folder from yours, and both players compare the build code it prints.

What you need: Fightcade, your own clean xmvsf.zip (the Europe 961004 set, with xmvsf.key inside), and on macOS or Linux, Python 3.8 or newer. The package holds no game data. Your opponent needs the same build: its code is f98f-bfea-1816-8d9a.

  1. 01Unzip the package.Anywhere. It holds the installer and its instructions, no game data and no emulator.
  2. 02Build the game file.Windows: drag your clean xmvsf.zip onto INSTALL (drop xmvsf.zip here).cmd, then More info > Run anyway if Windows asks. macOS: copy xmvsf.zip into the folder and open INSTALL.command. Linux: ./install.sh /path/to/xmvsf.zip. It writes fightcade/xmvsf.zip and prints a build code.
  3. 03Swap it into Fightcade.Close Fightcade's emulator. In Fightcade/emulator/fbneo/ROMs, rename xmvsf.zip to xmvsf.zip.original, then copy the package's fightcade/xmvsf.zip in.
  4. 04Compare build codes.Both players need the same build, or the match desyncs. v1.1 and v1.1.1 share the code f98f-bfea-1816-8d9a. Give the installer the file in Fightcade's ROMs folder to see its code; it only reports it.
  5. 05Try Test Game first.Use a channel for the set xmvsf (Europe 961004). FinalBurn Neo warns that three files have the wrong CRC: they're the changed ones. Misc > Options > Hide ROM Warnings stops the message. v1.1 sets itself up even when Fightcade starts the match from a saved state of the stock game; that was tested on Fightcade's emulator code, not yet in the client. If only two fighters appear anyway, close the emulator and back up xmvsf_fbneo.fs and xmvsf_fbneo_ranked.fs from its savestates folder if present. Move those backups outside the folder and start Test Game again; both players should fresh-boot this build.

Going backIn Fightcade's ROMs folder, delete xmvsf.zip and rename xmvsf.zip.original back to xmvsf.zip. The US, Japan, Asia and Brazil versions share two of the changed files with xmvsf.zip and may fail their start-up check while this build is in place (not tested), so put the original back to play them. If your xmvsf.zip was a merged set holding those versions too, keep the original: the built file holds only the Europe 961004 set.

What's differentTwo players only, on the original 384×224 screen: the six-player mode, the wide arena and the miniature faces on the life bars need the RetroArch core. The rest is the same engine as the RetroArch builds: three a side, tags, KO hand-offs, Delayed Hyper Combos, snapbacks, Team Hyper, the Variable Counter, eighteen colours and CPU teams of three.

What was testedFightcade's emulator source (fightcade-fbneo c959501), run without its window, with scripted controller inputs and its states saved and loaded the way Fightcade's GGPO code does: every 61 frames the last 8 were loaded back and run again. On v1.0.0's build: 35 runs, 1,128,037 frames and 18,407 rollbacks, with no difference in the game state. Tags, KO hand-offs, Team Hyper, the Variable Counter, CPU third members, DHCs, snapbacks, colours, the HUD, continues and rematches pass there; an arcade run beat Apocalypse, but the test bot never won the partner battle after him. It boots and tags in MAME 0.264. On v1.1's build: five more runs with rollbacks on, for HP+HK cycling and every colour input, the Start ones included, and a match started from a state saved on the stock game, which the build now sets itself up from. Both times, the installer's PowerShell ran under PowerShell 7 on Linux and wrote the same file and build code as the Python installer.

Not tested yetThe Fightcade client itself, an online match between two machines, Fightcade's lobby with a changed xmvsf.zip, ranked matches, the Windows installer under Windows PowerShell 5.1 on Windows, the installer on a Mac, and physical controllers. Until those run, it's a preview.

If something's wrong

What you seeWhyWhat to do
NOT INSTALLED: ... not the Europe 961004 revisionThis build needs the parent set xmvsf (Europe 961004). The US, Japan, Asia and Brazil versions have different program chips.Use a clean xmvsf.zip of the Europe 961004 set, with xmvsf.key inside.
This ZIP is already patchedYou gave the installer the built file, the one now in Fightcade's ROMs folder.Give it your original, xmvsf.zip.original.
FinalBurn Neo warns about files with the wrong CRCThree files are changed, so their CRCs differ from the stock set's. It's expected.Misc > Options > Hide ROM Warnings in the emulator window.
The match desyncsThe two players don't have the same build, or one has the stock game.Compare build codes: both need f98f-bfea-1816-8d9a. Give the installer the file in Fightcade's ROMs folder to see its code.
The game plays two on twoFightcade loaded another xmvsf.zip, or the channel is for another version of the game. With v1.0.0, a stock per-game saved state could also skip the mod's startup; v1.1 sets itself up from one, which hasn't been tried in the Fightcade client yet.Check the file in Fightcade/emulator/fbneo/ROMs with the installer, and play in a channel for the set xmvsf. Back up xmvsf_fbneo.fs and xmvsf_fbneo_ranked.fs if present in savestates, move the backups outside, then try a fresh boot. Keep them to restore stock.
Windows protected your PCSmartScreen doesn't know the installer yet.More info > Run anyway. The installer only writes into its own folder.
Build codef98f-bfea-1816-8d9aChecksrelease receiptAll filesdownload page

Six players: the controllers.

RetroArch hands controllers to ports in the order it finds them. Each port is one player, and each player plays the character they pick.

  1. 01Plug in six controllers.Before you start the launcher. A keyboard works for P1 only.
  2. 02Check the order.Press F1 in RetroArch, then Settings > Input > Port 1 Controls > Device Index, and repeat for ports 2 to 6. Ports 1, 2 and 3 form the left team; ports 4, 5 and 6 the right. Six physical devices and six human participants remain unverified.
  3. 03Pick your own character.P1 picks the left team's first character, P2 the second and P3 the third; P4, P5 and P6 pick for the right team. The input you confirm with picks the colour, and the player who picks holds Start for colours 10 to 18.
  4. 04Menus.P1 and P4 insert coins, press Start and move through the menus for their team.

The buttons

RetroPadArcadeXbox pad
YLight punchX
XMedium punchY
LHeavy punchLB
BLight kickA
AMedium kickB
RHeavy kickRB
StartStartStart
SelectCoinBack

If something's wrong

What you seeWhyWhat to do
NOT INSTALLED: ... not the Europe 961004 revisionThis release needs the parent set xmvsf (Europe 961004). The US, Japan, Asia and Brazil clones have different program chips.Use a clean xmvsf.zip of the Europe 961004 set, the one MAME, FinalBurn Neo and Fightcade use.
xmvsf.key is missingCPS-2 games need their key file, and your ZIP doesn't have it.Use a current MAME or FinalBurn Neo set that includes xmvsf.key.
already exists and is not this release's fileA file from another version or a hand-made copy is where the installer writes.Move the file it names (in roms or system) out of the folder, then install again.
RetroArch starts without the gameThe launcher couldn't use a file or a setting, and it says which in its window.Run the launcher again and read its window. If RetroArch is somewhere unusual, put the full path of its program on the first line of retroarch-path.txt.
RetroArch says the ROM is unknown, or the game plays two on twoRetroArch's own FinalBurn Neo core, or the files were started by hand without the package's settings.Start the game with a PLAY launcher: it loads the included core and switches on the setting that reads the patched files. Don't rename or move them.
A punch and kick pair gives the first button's colourThe two buttons landed more than four frames apart, so the first one confirmed on its own.Press the punch and its kick of the same strength at the same moment. For colours 10 to 18, hold Start before you press, or press it within those four frames.
The faces on the HUD show the LK colourA core from v1.0.0 can't read v1.1's colour table. The fighters themselves still use the colour you picked.Start the game with the v1.1 package's PLAY launcher, which loads the v1.1 core.
The wrong controller drives a characterRetroArch hands controllers to ports in the order it finds them.F1 > Settings > Input > Port 1..6 Controls > Device Index.
macOS says the file can't be openedFiles from a browser are quarantined, and the .command files aren't signed.Right-click the file > Open, then Open again. The launcher clears the quarantine from its own folder so RetroArch can load the core.
Windows protected your PCSmartScreen doesn't know the installer yet.More info > Run anyway. The installer only writes into its own folder.
The fine printwhat runs it · what was checked · known limits

Only the included coreThe RetroArch packages need the core in the package: it adds the six-player input and the memory the three-member teams use. Stock FinalBurn Neo, MAME and Fightcade can't run those files, and there's no arcade board version. The Fightcade build is separate: it keeps the teams in memory FinalBurn Neo already has, for two players on the original screen.

By handThe installer is xmvsf3v3_patch.py (Python 3.8 or newer) on every system: python3 xmvsf3v3_patch.py /path/to/xmvsf.zip writes the same three files. The launchers start RetroArch with -L and the package's core, --appendconfig and the package's settings, which switch on FinalBurn Neo's “Allow patched romsets” and point it at the chosen screen's system folder, and roms/xmvsf6pd.zip.

Known limitsv1.1.1 fixes LK and MK pressed together in the two-player game while RetroArch's SOCD option is on, and the Mac launchers now bring RetroArch to the front; v1.1 added eighteen colours a fighter and HP+HK cycling through the whole team. It's still a preview: every run so far is scripted, with ordinary controller inputs, on the Linux core and its diagnostic build, apart from one Mac run of the two-player package. Not run yet: Windows PowerShell 5.1 on Windows, the other macOS runs, physical controllers, and the Fightcade client or online play. The third-tag fix for the reports on v1.0.0 hasn't been confirmed by the players who sent them, and the choppy snapback switch isn't changed yet. One refusal, a Team Hyper whose partner slot is still in use, hasn't come up in a run. The RetroArch packages need their included core: stock FinalBurn Neo, MAME and Fightcade can't run them, and there's no arcade-board version.

UpdatedOct 12, 2026Checksrelease receipt

Share it.

One line and the link. Copy it, or post it straight to X.

ready to post

XMvsSF 3v3 + DHC v1.1 for arcade X-Men vs. Street Fighter: three characters a side, tags to either reserve, Delayed Hyper Combos, snapbacks, eighteen colours and six players, in a wide arena if you like, plus a two-player build for Fightcade. A free patch for your own ROM.
Post on X ↗

Related: Cross Fever, four players in XMvsSF, MSHvsSF and MVC1 · MSH TAG, tag teams for Marvel Super Heroes · XCOTA TAG, tag teams for X-Men: Children of the Atom.

HOW IT WORKSHow a two-on-two engine holds three a side, how six controllers reach the right fighters, where Delayed Hyper Combos and snapbacks hook in, how the arena gets wider, and how the Fightcade build does without the core.

The current download is v1.1.1, still a preview. It fixes v1.1's RetroArch core: with its SOCD option on, LK and MK pressed on the same frame didn't both reach the two-player game. The Mac launchers also bring RetroArch to the front now. v1.0.0 and its r2 guide revision are withdrawn: v1.1 replaces them. The footage below shows v1.0.0, apart from the colour still. The preview checks list what has been checked and what is still open, including the snapback's choppy switch, which v1.1 doesn't change yet.

In the Delayed Hyper Combo post I wrote down the limit I couldn't route around in arcade Marvel vs. Capcom. Its teams are two fighters, so a Hyper can hand off once. MvC2's teams are three, so its chain goes three Hypers deep. The most MvC1 could get was MvC2's choice of Hyper, never its chain, and I left the choice out of v8.

X-Men vs. Street Fighter (1996) is where the Vs. series' tag teams started: two characters a side, HP+HK to swap, and a knocked-out point handing over to its partner. XMvsSF 3v3 + DHC gives each side a third character. With three, a Delayed Hyper Combo goes up to three Hypers deep, and the motion you enter picks which Hyper the next character comes in with. That's the chain and the choice, in the 1996 game.

The rest of the three-a-side game comes with it. MP+MK and MP+HK tag in the reserve you name, HP+HK (the arcade game's own tag) cycles through the whole team, and a snapback forces the other team's point out and drags in the reserve you pick. The arcade game's Team Hyper and Variable Counter keep their commands and costs.

It's a patch for your own ROM, the Europe 961004 set the emulators call xmvsf. It runs in a RetroArch core that comes in the download, for two players or six, one controller per character, on the original screen or in a 512×224 arena. The trailer shows all of it in three minutes. There's also a two-player build for Fightcade that needs no extra core, still a preview. Below is how it works, and what hasn't been tested yet.

New in v1.1
  • 18 colours a fighter. Nine inputs pick a colour, and holding Start doubles each one. How to pick them.
  • HP+HK cycles the whole team: first to second, second to third, third back to first. The arcade game's tag now reaches your third character.
  • The colours are packed. The sixteen new ones, for every fighter, fit in 6,964 bytes, and the game unpacks one into RAM the first time it's needed. How.
  • The Fightcade build sets itself up when a match starts from a savestate.

On this page: three a side · six controllers · Hyper into Hyper · snapbacks · the HUD · 18 colours · arcade mode · the wide arena · where the patch lives · what got tested · Fightcade

About the names

X-Men vs. Street Fighter calls its two-character Hyper the Variable Combination, and the move that brings the partner in out of a block the Variable Counter. I call the first one the Team Hyper here, like the rest of the page. DHC is MvC2's name, Delayed Hyper Combo, the same one the MvC1 patch uses, and the snapback comes from MvC2 too.

What's in v1.1

  • Three picks per side. Each side picks three in turn, and in six-player mode each player picks their own. The first pick starts on point; the other two are reserve A and reserve B, in the order they were picked. No character twice in one team; the same character on both teams is fine.
  • Tags. MP+MK brings in reserve A and MP+HK reserve B. HP+HK, the arcade game's own tag, brings in the next member still alive, in pick order: first to second, second to third, third to first. A knocked-out reserve keeps its place, and a request never swaps in the other one. MP+MK+HK together does nothing until you let go.
  • KO hand-off. A knocked-out point hands over to the partner the game keeps ready if it's alive, as in the arcade game, and to the third member if it isn't. All three count for the timer, and a team is out when all three are down.
  • Delayed Hyper Combos. During a Hyper, enter the next member's Hyper motion and finish it with MP+MK or MP+HK. They come in with their own Hyper and stay in. Each Hyper costs its own meter, and each character joins a chain once.
  • Snapbacks. QCF + MP+MK or QCF + MP+HK, one level, from neutral or cancelled from a ground normal that made contact. A clean hit forces the point out and the reserve you named in.
  • Team Hyper and Variable Counter, as the arcade game has them, with the first living reserve as the partner.
  • Eighteen colours per character. The six buttons and the three punch-and-kick pairs pick colours 1 to 9, and the same nine with Start held pick 10 to 18. A mirror gets the next free colour.
  • CPU teams of three all the way to Apocalypse, who stays a solo boss. The partner battle after him stays as the game has it.
  • Six players, P1, P2 and P3 against P4, P5 and P6, each playing the character they picked for the whole match.
  • A wide arena, 512×224, with its own launcher.
  • A Fightcade build for two players, on Fightcade's own FinalBurn Neo, without the six-player mode, the wide arena and the miniature faces. It's a preview: it hasn't been played in the Fightcade client or online yet.

There are no assists, and nothing is rebalanced: damage, meter gain, the meter's cap, red-life recovery and every character's moves are the arcade game's. There is no air snapback, no timed Duo and no new character.

Why three on three?

MvC2 made the Vs. series three a side, and its team play is built on that. A DHC chain is as long as the team. A snapback decides which of the other team's two reserves you fight next; against two a side there's only ever one.

X-Men vs. Street Fighter already has the tag-team pieces the later games built on: the HP+HK tag, the KO hand-off, the Team Hyper and the Variable Counter. Cross Fever put it on one board for four people, two on two, with MVC1's Duo. This is a different patch, and Cross Fever's XMvsSF builds stay as they are. Three characters a side makes six in a match, so the six-player version gives each one a player of its own.

I wanted three a side without turning it into a different game. The arcade game's own moves keep their inputs and their costs, and every new move in a fight uses MP+MK or MP+HK.

Three a side in a two-a-side engine

Character select in six-player mode: P3 picks the left team's third character while P5 picks the right team's second
Six players: each pick is labelled with its player.
The three-member HUD: Gambit, Cyclops and Wolverine against Ryu, Ken and Dhalsim, three portraits and three life bars a side
Three portraits and three bars per side.

X-Men vs. Street Fighter runs four fighters: each side's point and partner, in slots of $400 bytes at $FF4000, $FF4400, $FF4800 and $FF4C00. The game's own variables start right after them, at $FF5000, so there's no room for a fifth slot. Three a side doesn't need one: a side never has more than two characters on screen, and the partner slot already holds whoever a tag or a KO brings in next.

The code counts to two in more places than the slots. Here are three of them, decrypted (the CPS-2 encrypts its program; these are the instructions the 68000, the board's processor, actually runs):

; setting up a fighter: which of the side's two picks?
00FFFA  4A2E 0220  tst.b   $220(a6)     ; pick 0, or not?
00FFFE  6702       beq.s   $010002      ; 0: keep the first
010000  C141       exg     d0,d1        ; not 0: take the second
 
; a knocked-out point: anyone to send in?
00D588  206E 0224  movea.l $224(a6),a0  ; the other slot
00D58C  4A68 0210  tst.w   $210(a0)     ; its life
00D590  6710       beq.s   $00D5A2      ; 0: nobody comes in
 
; a helper whose owner's slot changed character
010358  206E 0054  movea.l $54(a6),a0   ; its owner's slot
01035C  302E 0052  move.w  $52(a6),d0   ; who made it
010360  B068 0052  cmp.w   $52(a0),d0   ; still there?
010364  6718       beq.s   $01037E
010366  2068 0224  movea.l $224(a0),a0  ; no: the other slot
01036A  2D48 0054  move.l  a0,$54(a6)   ; re-own it there

At $FFFA, setting up a fighter, the game asks one question about which pick it is: zero, or not zero. Zero gets the side's first character and anything else the second, so writing a 2 there doesn't get you a third character; it gets you the second one again. The patch replaces those three instructions with a lookup that can return any of the three.

At $D588, in one of the game's KO paths, a knocked-out point looks at exactly one fighter, the other slot on its side. If that one's life is zero, nobody comes in. The patch uses this path for the third member, as the next section shows.

At $010358, a helper object (the kind Cyclops, M. Bison and Sabretooth use) checks that its owner's slot still holds the character that made it. If it doesn't, the object decides the owner must be in the other slot, because with two a side there's nowhere else, and moves itself there. With three a side, the other slot can hold a different character altogether, and the object would end up belonging to them.

So the third member lives outside the game's own RAM. The included core adds 64 KiB at $810000, which the CPS-2 board doesn't have, and each side gets a 64-byte team record, at $810100 and $810140: a 16-byte header and 16 bytes per member, with its character, its life, its colour, its red-life ceiling and its recovery countdown. A record isn't a frozen copy of a fighter. When the third member is needed, the patch writes those fields into the partner slot, runs the game's own setup on it, and lets the stock tag or KO entry bring it in, the way any partner comes in.

It only moves into the slot once nothing still refers to the character there. The patch first walks the game's object lists, and if a live object still points at the slot's resident, the request is refused. It isn't queued, it isn't redirected to the other reserve, and nothing is deleted to make room.

The game's own RAM gets no new fields. The patch reads and writes only fields the game itself uses, and keeps the rest in the core's memory. A third member waiting off-slot recovers red life at the same pace as the partner on the bench. Each fight starts by clearing the team records and building all three members fresh from the picks, and a continue starts from new picks. Character select makes a third pass through the same grid and refuses a character the team already has, Chun-Li's alternate identity included.

The KO ghost

When both characters in a side's slots are down and the third is alive, the patch sends the knocked-out point down a path the stock code already has for a delayed replacement: wait 60 frames, then swap. On the way, that path spawns an object that shows the fallen fighter lying there, blinking, until it's removed. The project calls it the KO ghost: object type $50, subtype 2 (move.w #2,d0 at $D5AA, then the spawner at $4BF54). The arcade game doesn't use this path for an ordinary KO. In a stock MAME run with Chun-Li knocked out and Wolverine alive, the swap comes from another routine and no ghost is ever created.

Earlier KO tests passed. A full arcade run found the problem in its fifth fight, when Chun-Li went down: from frame 15,277 the game ran at about a twenty-first of its speed. The main loop's counter ($FF501C) moved once every 21 frames or so, and the 68000 was always inside the sprite builder.

Subtype 2 finds its state handlers through a table the code reads relative to the program counter (its address is counted from the instruction that reads it), at $4C0FE: 0008 002C 0128 012E. The first two entries are right. The last two are subtype 0's offsets. From subtype 0's own table at $4C05C they point at $4C184 and $4C18A, "advance" and "retire". Added to $4C0FE instead, they land at $4C226 and $4C22C, in the middle of the routine for a different object, a type-$54 mirror image.

So once the ghost reached state 4, it ran the mirror's code. That code took two words of the ROM, at $4C042 and $4C046, as the ghost's position ($6934, $7C21: nowhere near the stage), and it stepped Chun-Li's lying-down animation with the step routine for objects instead of the one for fighters. The two lay out their records differently. The object stepper read a field of her frame as the address of the next one and got $80800000; from there the frame pointer became $FFFFFFFF, and the sprite builder looped on a garbage count every frame.

The fix is one hook, a jump the patch writes over a few of the game's own instructions, where subtype 2 picks its state. States 0 and 2 go where they always went. States 4 and 6 go to the advance and retire that the other ghosts, and this ghost's own shadow, already use. It still lies there for 60 frames and blinks, and then it's gone.

the hook, as the 68000 sees it $04C0F2 · two-player build12 bytes · 10 changed
before302E0004323B00064EFB1002
after4EF9002FB3DE4E714E714E71
The state dispatch (move.w 4(a6),d0; move.w 6(pc,d0.w),d1; jmp 2(pc,d1.w)) becomes a jump to the patch's code at 2FB3DE and three NOPs. The new code sends states 0 and 2 where they always went, and 4 and 6 to the advance and retire routines at 4C184 and 4C18A.

Here is the same test, with the same inputs up to the ghost's state 4, on the build before the fix and on the build with it. The numbers are main-loop passes in each stretch of 240 frames after the ghost appears; 240 is full speed, one pass a frame.

After the ghost appearsBefore the fixWith the hook
1st 240 frames240240
2nd 240 frames136240
3rd 240 frames11240
4th 240 frames13240
5th 240 frames11240

Six controllers

The arcade game reads two players. The included core adds a read-only window at $808000 with six: each player's four directions, six buttons, Start and Coin, 72 switches in all, plus what was pressed and released since the last sample and a history 16 samples deep. A capability word at $80804C says what the running core adds, and the ROM checks it before it uses any of it.

The core only carries the buttons. The ROM decides who controls what: the left team's picks belong to P1, P2 and P3 in pick order, the right team's to P4, P5 and P6 (owner = 3 × side + pick, counting from zero), and that never changes during a match. The point's owner is the one who can tag, start a DHC or a snapback, or call the Team Hyper. The incoming character answers to its own player, and a waiting player's buttons reach nothing in combat: one test presses chords on every bench and checks that no request, swap or input changes. In the menus, P1 and P4 do what the arcade's P1 and P2 do.

A press that went missing

One six-player test failed on its fifth tag. At frame 4,261 a fresh MP+HK went in, and instead of a tag the fighter threw a normal attack. The tag check paired two things: the input the game had last stored in the fighter, and the core's current sample number. Each is right on its own, but they can come from different samples. The game's work for one frame can run across the boundary between two of the core's frames, and the new HUD code had moved where that boundary fell. The check had already filed sample 4,261 with nothing held, so the new press looked like a repeat and was thrown away.

The fix gives each fighter slot its own counter, four of them at $81020C–$81021B, bumped exactly when the game stores a new input sample in that fighter. The tag check uses that counter instead of the core's. The same 4,503 frames of scripted input that failed now start the tag at 4,261, and holding the chord doesn't start a second one.

Why it needs its own core

The patched game uses memory and inputs no CPS-2 board has: the six-player window at $808000 and 64 KiB of RAM at $810000. The core in the package adds both, and the expanded view for the wide arena. Stock FinalBurn Neo, MAME and Fightcade don't, so they can't run it, and there's no version for the arcade board or for online play.

That core is an unofficial build of FinalBurn Neo's libretro port, and the FinalBurn Neo team doesn't support it: please report problems with it here, not to them. Its complete source is the xmvsf3v3-v1.1.1 release of its public fork, under FinalBurn Neo's licence. I've also offered the driver to FinalBurn Neo itself, as four sets of its own, in pull request #2798; until it's merged, play with the package's core.

One Hyper into the next

A Delayed Hyper Combo: Ken's Hyper cut-in over the stage while the first Hyper's rays still fill the screen
The incoming character's own Hyper, with its own freeze and cut-in. v1.0.0 six-player build · FinalBurn Neo run, ordinary inputs

A DHC starts during your Hyper. You enter the next character's Hyper motion and finish it with MP+MK for reserve A or MP+HK for reserve B. Your character leaves, the next one comes in doing that Hyper, and stays in. The motion is read relative to the way your character faces, since the incoming one isn't on screen yet.

The motion picks the Hyper, the way it does in MvC2. A few of the table's rows:

IncomingMotion + MP+MK or MP+HKTheir Hyper
WolverineQCFBerserker Barrage X
WolverineDPWeapon X
CyclopsQCFMega Optic Blast
CyclopsQCBSuper Optic Blast
AkumaQCBMessatsu Gou Hadou
AkumaRDPMessatsu Gou Shoryu
ZangiefQCFFinal Atomic Buster (ends the chain)

The motion isn't always the Hyper's own command. Zangief's Final Atomic Buster is a 360 on its own and a quarter circle in a chain. Every character's motions are on the control card in each package.

In the chain test, Ryu's Shinkuu Hadouken goes into Ken's Shoryureppa (QCF + MP+MK), Ken's goes into Chun-Li's Kikoushou (QCF + MP+HK, with Chun-Li brought in from off-slot), and a request back to Ryu is refused because he's already been in that chain. Each Hyper spent one level. A chain goes A to B to C, never back to A.

Each step of a motion, and the button press after it, gets 14 input samples. The game's own motion reader gives each step 12 to 17 frames, drawn from its random number generator at $1608. The DHC reader never touches the generator, so it can't change the random numbers the rest of the game gets; 14 is the game's own button window, at $13D2C. Its clock counts the core's input samples, so the game's Turbo speed, which can run more than one update per frame, doesn't shorten it. When more than one motion finishes, the latest one wins, and on the same sample the longer one does (a half circle over a quarter circle).

The window opens on the first frame after the Hyper's freeze on which it has an attack box, on the fighter or on an object carrying its character, and closes 8 frames after the last one. The Hyper's startup before that and its recovery after are refused, and so are a chord without a motion, the ambiguous MP+MK+HK and a request without enough meter. A refused request spends nothing, swaps nothing, and never turns into a tag or a snapback.

The switch itself is stock. The outgoing character leaves through the game's tag-out and the incoming one enters through its tag-in. A hook at the end of the tag-in ($11DB0) puts the new character where the outgoing one stood, on the ground, and starts the Hyper you picked through that character's own Hyper start. So its own code runs the Hyper: it pays that Hyper's cost, freezes the screen and shows its own cut-in.

Throw Hypers end a chain: Zangief's, and Akuma's Shun Goku Satsu. Akuma's Tenma Gou Zankuu can't be a target, because it's an air Hyper and an incoming character starts on the ground, and neither can Rogue's absorbed Hyper, which depends on what she's absorbed; the tag swap leaves that absorbed state behind in the partner slot.

Chain and choice

In v8 of the MvC1 patch, the partner comes in on its own team Hyper whatever you typed, and a two-fighter team has room for one handoff. Here the motion you type picks the Hyper, and a three-fighter team has room for two.

Snapbacks

A snapback lands: Ryu's kick sends Cyclops out, and Wolverine, the reserve Ryu named, is forced in next
Ryu cancels a crouching light kick into a snapback. Cyclops goes out, and Wolverine, the reserve Ryu named, comes in. v1.0.0 six-player build · FinalBurn Neo run, ordinary inputs

MvC2's manual (the PS2 one, page 11) describes the snapback as a motion plus a partner button, for one level. Here it's QCF + MP+MK to name the other team's reserve A, or QCF + MP+HK for reserve B. It starts from neutral on the ground, or cancelled from a ground normal that made contact, under the game's own rule for cancelling a normal into a special move ($12442).

Each team has its own snapback, so both can start one in the same frame. The level is spent at startup, blocked or missed included. A clean hit on the live point marks it to leave, and the named reserve is forced in once its slot can be reused, under the same check as a tag. A lethal hit is an ordinary KO. Against a team's last character a snapback is just a strike, as in MvC2, and naming a reserve that's already down is refused while the other is alive. It works on airborne victims too.

The stage darkens for a moment. In the vertical-blank interrupt, where the game hands its palette to the video hardware, the patch turns the stage layers' brightness down to 3 of 15, lets the hardware take that copy, and puts every original colour back before the interrupt returns. The game's own palette never keeps a darkened colour, and the fighters and the HUD, which draw with other rows, keep theirs.

What the snapback tests caught

An early snapback test passed for the wrong reason. It read the meter at +$212, which is the gauge's fraction, not its level: one whiffed heavy punch made it 8, and the test was satisfied with zero levels. The level is the byte at +$214, where the game's own spender at $168E6 takes it from. That report is kept, marked invalid as evidence, and every snapback test since earns a full level with ordinary whiffed normals before it snaps.

A later build had a real bug. If the snapback's strike was hit out of its startup, the patch went on holding the player's input for a strike that was no longer happening, for 61 frames. Now the snapback checks right after the game's own collision code, and once the game has put the fighter in hitstun (its action becomes $36), the snapback ends. The level stays spent. The same 5,102 frames of scripted input that showed the bug run clean on the fix.

The HUD

The three-member HUD enlarged: Gambit, Cyclops and Wolverine on the left, Ryu, Ken and Dhalsim on the right, each with a miniature portrait at the screen edge and a thin green bar, the bars stepping outward from the timer
Three faces and three bars a side, stepping outward from the timer 14 pixels at a time. v1.0.0 six-player build · FinalBurn Neo run

Each side has three portraits and three thin life bars. The bars step outward along the timer's hexagon, 14 pixels apart, the game's own row spacing. There are no player numbers in a fight; P1 to P6 appear on character select and the versus screen. When a tag or a KO changes the point, its portrait, name and bar move to the top row, the other living member follows, and a knocked-out one goes to the bottom, its bar draining white over the gray track. Displayed life eases toward the real life one unit per game tick, and what's drawn never changes the real life.

The faces are the game's own 48×32 portraits, trimmed and fitted into 18×12, in 36 graphics tiles the original ROM leaves blank ($1FF00 to $1FF23). The builder checks that they're blank before it writes, and that no other graphics byte changes. Their colours were harder. In a fight the game uses all 32 sprite palette rows, so there's no row to lend six more faces. The included core recognizes the exact tile, position and attribute of a miniature and draws it with that character's colours straight from the ROM: the game's own two, or one of the patch's sixteen, unpacked from its colour table the same way the game does it. A core that can't says so in the capability word, and the ROM leaves the miniatures out.

A sprite list that was half old

The three-member HUD made the sprite list longer, and that uncovered a race in the game's own code. The game builds each frame's sprite list in one of two banks while the video hardware shows the other. When a list is done, the game flips a pending-bank word ($FFFA9A), and the next vertical-blank interrupt applies the flip and clears a busy flag ($FFFA90) that keeps a new list from starting. A long list can still be building when that interrupt comes. The interrupt clears the busy flag anyway, the list finishes and flips, and a second list can start in the same bank before the next interrupt applies the flip. The screen then gets the start of the new list and the end of the old one.

The saved frame looks normal. The test reads the sprite list the core actually latched, and it found 254 records with seven miniature portraits instead of six: two identical Ryu heads, one new and one old, drawn on the same spot. The mix happened to be invisible, and the list was still wrong. The fix is a hook at $1DCBA, where the game flips the bank, and 16 bytes of new code:

eori.w  #$FFFF,$7A9A(a5)  ; the game's own bank flip
st.b    $7A90(a5)         ; busy until the interrupt shows it
jmp     $01DCC0           ; back to the game

No interrupt is masked, and nothing else changes.

One more HUD failure turned out to be the test's fault. The core draws a frame, then lets the 68000 finish its vertical blank, and a palette upload in that tail can change the colours after the picture is drawn. The test read the palette after the frame and compared it with a picture drawn with the palette from before: 2,165 wrong pixels, a blue of #2277FF where the palette now said #3388FF. The fix was in the test. It captures all 128 palette banks right before each frame and accepts one whole palette, before or after, never a mix and never a tolerance.

Eighteen colours

You confirm a pick with one of nine inputs: a button, or a punch and its kick together. Holding Start doubles each of them, so there are eighteen colours a fighter:

ColoursInput
1 to 6LP, LK, MP, HP, MK, HK, as the arcade game reads them
7 to 9LP+LK, MP+MK, HP+HK
10 to 15Start held with LP, MP, HP, LK, MK or HK
16 to 18Start held with LP+LK, MP+MK or HP+HK
Magneto in all eighteen colours, in two rows of nine: the top row is the nine inputs alone (LP, LK, MP, HP, MK, HK, LP+LK, MP+MK, HP+HK, colours 1 to 9), the bottom row is the same nine with Start held (colours 10 to 18)
One fighter in every colour, laid out the way you pick them. v1.1 six-player build · drawn from the arcade graphics and the build's own colour table, not captured from a run
  • A pair is a punch and the kick of the same strength. They can land up to four frames apart, so a single button waits those four frames before it confirms, with or without Start.
  • Start counts if you hold it with the press or at any time inside those four frames.
  • A different button inside the window confirms the first one's colour straight away. Two buttons that aren't a pair confirm nothing.
  • Chun-Li keeps Start for her other outfit, as the arcade game does, so she has colours 1 to 9 in each outfit.
  • Six players: each pick's own player holds Start.
  • A mirror gets the next free colour, wrapping from 18 back to 1.

Colours 1 and 2 are the game's own. Colours 3 to 6 are the ones v1.0.0 added: MvC2's colours where my Marvel vs. Capcom 2 palette files had them, derived ones for the rest, the same button-per-colour idea as six MvC2 colors.

Colours 7 to 18 are mostly palettes the palmod community made and shared, in the PalMod gallery and in the MvC2 Discord's #palmodding channel, alongside Capcom's own console colours (the XMvsSF Saturn and PlayStation bonus colours, MSHvSF's and MvC1's). Each was chosen to look different from the fighter's other colours. Only where no palette like that was left, for M. Bison's last four and the second Chun-Li outfit's last ten, are there recolours made for this release. Every package names the source of every colour in licenses/PALETTE-CREDITS.txt. Colours stay with the character through tags, forced entries and rematches.

The HUD's faces use all eighteen in the included core. A core from v1.0.0 shows colours 3 to 18 there in the fighter's LK colour, since it can't read the new table. The big portraits, on Hypers, character select and the VS and win screens, show the LK colour for colours 3 to 18 in every core, as they did for 3 to 6 before.

Packing the colours

A colour is three rows of 16 colour words, 96 bytes. Colours 1 and 2 are already in the game. The sixteen new ones, stored whole for every fighter, would take 27,648 bytes, and the blank space set aside for them is 12,288.

Capcom hit the same wall on the Super NES. Street Fighter Alpha 2 and Star Ocean carry the S-DD1 chip, which decompresses graphics on their way from the cartridge to video memory. CPS-2 has no such chip, so here the game's own 68000 does the unpacking.

A new colour rarely changes every pen of a fighter's body row, so the table keeps only the pens that differ from the fighter's own LK colour (or LP, whichever is closer), as 12-bit colours packed two to three bytes. An index points at each colour, and a checksum stamps the table. All sixteen fit in 6,964 bytes, almost four times smaller. zlib comes to 6,994 bytes on the same colours and LZMA to 6,361, but to reach one colour, both have to unpack everything stored before it, into as much as 27,648 bytes of RAM the game doesn't have, with a decoder far bigger than this one. This format unpacks one colour on its own.

The first time a fighter's colour is needed, the game unpacks it into one of eight slots in the core's extra RAM, about 480 instructions on average. After that it copies it from there, as it copies a stock palette. At most six fighters and two character-select figures are on screen, so a colour in use is never pushed out. The slots are part of the savestate, and a table with a different checksum empties them first.

Nothing is lost on the way, and four separate checks say so:

  • The packer unpacks all 292 colours again and refuses to write the table unless every one matches.
  • The game's own unpacker, run on its own in a 68000 emulator, matches the source colours for every fighter and colour, then again over 8,000 requests that fill and empty the slots.
  • The core's HUD decoder matches every fighter's face in every colour.
  • In the game, the probes read the palette RAM after picks and after tags.

Arcade mode

CPU teams have three members too, and the whole arcade mode runs to the end. Two complete arcade runs found problems that only show up late in it.

After Apocalypse, the arcade game has one more fight: the two characters on your team at that point, one on one. The game takes them from the pair in the slots when Apocalypse goes down ($F5B4 sets a flag at $FF5083) and skips character select. An early build seeded that fight with the team's third pick as well, which gave a side a duplicate reserve that a tag or a KO could have brought in. Now the patch leaves that fight completely stock. While $FF5083 is set it seeds no team records, and every hook behaves as the arcade game does.

In a six-player run, the player's point, Ken, was knocked out, and time ran out before Cammy came in. Counting all three members' life, the player won on time, 144 to 77, but the patch only kept a winner's three members if its point was alive, so the next fight, against Apocalypse, started with two. The arcade game keeps a winner's pair whatever its point's life, and now the patch keeps all three the same way.

The wide arena

The same round start in the 512-pixel wide arena: the same scene in the middle, more of the mall on both sides, and the HUD's portraits moved out to the new edges
A round start on the mall stage in the arcade screen's 384 pixels, Ryu against Ken, three portraits and three bars a side, with black bands where the wide arena adds columns
‹›
Arcade screen, 384×224Wide arena, 512×224
The same round start, run twice with the same inputs on the v1.0.0 six-player wide ROM: once on the arcade screen, once in the wide arena. The middle 384 columns match pixel for pixel, except the HUD, which moves out to the new edges. FinalBurn Neo runs, ordinary inputs.

The core can draw the CPS-2's layers 512 pixels wide. The game can't use that as it is: it culls sprites, clamps fighters, follows with the camera, streams in tiles and scrolls its parallax layers for a 384-pixel screen. The wide ROM hooks 43 places for it, all generated from one census of the original ROM that lists every site with its original bytes and its meaning, including the 58 calls that stream in tiles and the 60 places that move a parallax layer.

Relative to the camera, the arcade screen shows 64 to 448, and the wide arena 0 to 512 around the same centre. Everything that was tuned to the old edges keeps its distance from the new ones:

Relative to the cameraArcade screenWide arena
What's drawn64 to 4480 to 512
Fighter clamps+85 and +427+21 and +491
The camera starts to follow at144 and 36780 and 431
Incoming fighters start at−16 and +528−80 and +592

The stage ends stay where they were. The camera's range shrinks by 64 at each end, so at the end of a stage the wide arena shows exactly the arcade game's outermost scenery, and the fighters' world limits don't move (21 to 747 on this stage, in both). What grows is how far apart two fighters can stand.

Ryu at the left edge and Ken at the right edge of the wide arena's mall stage, 470 pixels apart
As far apart as two fighters can stand: 470 pixels in the wide arena, 342 on the arcade screen. v1.0.0 six-player wide ROM · FinalBurn Neo run, ordinary inputs

Two layers needed more than new numbers. Scroll 1, the layer of 8-pixel tiles, is a 64-column map, exactly 512 pixels. A 512-pixel view that scrolls by less than a tile needs 65 columns, and the game's 32-pixel streaming wrote over the trailing edge, so stages 5, 9 and 10 showed black columns and repeated tiles. The wide build keeps columns 64 to 127 in the core's memory at $814000, and the core reads them for scroll 1 while a tag at $813FF8 (XS1X) says they're valid.

The parallax layers had the opposite problem. A background that moves at a fraction f of the camera's speed would show up to 64(1−f) pixels past anything the arcade game ever shows at each end, and on stages 1, 3 and 5 that's blank or unfinished art. Each layer's position gets 64(1−f)(1−2u) added, with u the camera's place between its bounds (0 at the left end, 1 at the right), so the layer spans exactly its native range.

The standard ROM carries none of this code. The wide hooks check the core's capability word every time, so a wide ROM on the arcade screen plays the arcade game, but their extra cycles still move things. Side by side with the standard ROM on the same inputs, the two made the same decisions for all 6,476 frames of the run, but the wide one's extra cycles shifted an occasional frame: at one round start, frame 3,146, a position differed by 3 pixels. So the two screens are two builds of the same source, each with its own launcher.

Two stubs that came back to the wrong place

A hook overwrites a few of the game's instructions with a jump to new code, and the new code has to come back after the bytes it overwrote, never into them, because those bytes now hold the jump. Two wide stubs broke that rule, and a full arcade run in the wide arena hung in its first fight.

One replaced six bytes at $108A8 (addi.w #$55,d0 and move.w d0,d1, part of a fighter clamp) but came back at $108AC, the last word of its own jump. That word is the low half of the stub's address, $68C2, and $68C2 as an instruction is bvc.b $10870, a branch the flags let through every time. The 68000 slid back into the clamp code, came around to the stub again, and looped forever, writing memory as it went. That clamp only runs when a fighter has a flag at +$30 set, which the stage tours never reached. The other stub, for fighters entering from the right, jumped into bytes its left-side twin had replaced, skipped a store, and left the entrant at its old position.

Both now come back after the bytes they replaced. The same mistake could hide anywhere, so a new audit (audit_hook_returns.py) checks every branch in every stub against every hook's replaced range. It finds nothing in the four v1.0.0 builds, and exactly the four branches of these two stubs in the builds before the fix.

Where the patch lives

The program is seven 512 KB chips, $000000 to $37FFFF. The patch puts its code and tables in regions that are blank in the original, all above $100000:

XMvsSF program ROM: where the patch goes
1 · encrypted game code2 · the rest of the gameA$000000$380000
  • 1
    encrypted game code
    $000000–$100000 · 1 MB
    the first megabyte; the hooks are written here, encrypted
  • 2
    the rest of the game
    $100000–$2FAB00 · 1.98 MB
    not encrypted
  • 3
    engine code
    $2FAB00–$2FE900 · 15.5 KB
    14,144 bytes in the six-player build (13,844 in the two-player one), of 15,872
  • 4
    wide-arena code
    $316800–$318000 · 6 KB
    1,966 bytes, wide builds only
  • 5
    colour code
    $31A500–$31CE00 · 10.3 KB
    1,522 bytes: the colour picker and the unpacker
  • 6
    wide-arena tables
    $32D000–$32E800 · 6 KB
    736 bytes of refresh and parallax tables
  • 7
    colour table
    $335000–$338000 · 12 KB
    6,964 bytes: 16 packed colours for 19 character identities (27,648 stored whole)
  • A
    the KO ghost's hook
    $04C0F2 · marker
Every region was checked before use: blank (every byte FF) in the original chip, and no aligned long anywhere in the program, decrypted or not, pointing into it.

The CPS-2 encrypts its first megabyte. On the board, what the 68000 fetches as instructions, and what it reads relative to the program counter, comes through decrypted; a plain data read gets the encrypted bytes. The hooks have to sit in the game's own code, so each one is written encrypted with the set's key. In the two-player build, the KO ghost's hook reads 2B75 5F7E 91B7 54CF 002B ABFB to a plain read and 4EF9 002F B3DE 4E71 4E71 4E71 to the CPU. The patch carries those bytes already encrypted for the Europe 961004 set, which is why it takes that set and no other: the clones have different program chips.

It matters for tables too. The KO ghost's state table is read relative to the program counter, so 0008 002C 0128 012E is what the CPU gets there, not what a plain read of the chip returns. The two PC-relative stock tables the patch's code needs (one in the tag-in code at $11DC2, and the parallax rates at $70690) it reads through the game's own instruction, or carries as constants.

The builder refuses a build if any byte it's about to replace differs from what it expects, in the decrypted view. It also has to keep the game's boot test happy. At power-on the game adds up one byte in every 4,095 of each program chip and compares the seven sums with a table at $0000D0. If a changed byte lands on one of those and the table stays as it was, the self-test fails. The builder doesn't assume that rule. It finds it by reproducing the table from the clean chips, then recomputes the sums after patching and rewrites the table (the first chip's sum goes from $3FA0 to $3F51).

The register nothing may touch

One rule runs under all of it. The game's interrupt handler, the code the board runs once a frame whatever else is running, uses the 68000's A5 register as the base of its work RAM, $FF8000, and never sets it; it trusts that A5 already holds it. An early build borrowed A5 for a moment while it walked the object lists, as the end marker of a list. An interrupt landed in that moment, and the handler's move.w -$2CB0(a5),-$2C2E(a5), with A5 at $FF7080 instead of $FF8000, wrote to $FF4452, the right point's character. Ken became Wolverine at frame 5,814. The next build stopped borrowing A5, and the same 6,200 frames of scripted input that broke the roster reach the third member with all six characters intact.

Now no added instruction changes A5, and no interrupt is masked. A static audit (audit_a5.py) follows every path from every hook and checks every save and restore of A5: nothing in the four v1.0.0 builds. On the old build it finds the three list-end loads into A5 that caused this.

What got tested

  • Gameplay: 95 run reports on the v1.0.0 build. 76 pass, over 1,896,628 frames. The 19 that failed each have a written reason: 14 arcade runs replaced after a check was corrected, one ownership run whose last check still expected an older refusal, two probes that need core internals the release build doesn't export, and two research runs into the one refusal below. Every run is scripted, with ordinary controller inputs. Covered: complete arcade modes, two and six players, both screens, through Apocalypse and the partner battle; continues with new picks; MP+MK and MP+HK tags from both sides, and HP+HK's first-living rule; KO hand-offs to the third member; DHC chains, and refusals before the Hyper is active, in its recovery, without a motion, for the ambiguous chord, without meter and for a character already in the chain; snapbacks with every character's strike, blocked, missed, cancelled, against airborne victims and on both sides at once; Team Hyper and the Variable Counter; six colours and mirrors; six-player ownership, including benched players' buttons changing nothing; retained-winner rematches; and tours of the wide arena to both ends of seven stages.
  • The release core itself: 15 of those probes also pass on the shipped Linux core, including all four complete arcade runs. The Linux release core matches the diagnostic build for 2,400 frames on every ROM and screen, and so does the Windows core under Wine.
  • The packages: 103 checks pass, 3 are blocked and none fail: no ROM data, key or game archive inside; every changed run re-applied independently; the installer and its refusals, including under Python 3.8; the launchers with a stand-in RetroArch. The Windows installer's own PowerShell passes 16 of 16 for each package under PowerShell 7 on Linux, and gives the same files as the Python one. That run found a StrictMode error in a refusal path, now fixed.
  • Static audits: A5 and hook returns, as above, on all four builds, each shown to find the bug it's for.
  • v1.1: the same kinds of runs on the v1.1 builds, all passing. 31 colour runs: every new input, timing rule and mirror on two and six players and both screens, and the HUD on three cores (the included one, the v1.0.0 one and a twelve-colour test core). 11 more: the HP+HK cycle from both sides, DHC chains and refusals, snapbacks on both sides at once, Team Hyper, the Variable Counter, six-player ownership, and both complete six-player arcade ladders, on the arcade screen and in the wide arena. Five runs on Fightcade's emulator code with rollbacks on, and one match there started from a state saved on the stock game. The unpacker in a 68000 emulator, the HUD decoder on every colour, and the A5 and hook-return audits, with no findings. All scripted, with ordinary controller inputs. The v1.1 packages: 103 checks pass, 3 are blocked and none fail, and the Windows installer's PowerShell passes 16 of 16 for each package under PowerShell 7 on Linux. The Fightcade package passes 25, with 2 blocked.
  • v1.1.1: v1.1's core let FinalBurn Neo's cleaning of player 3's stick act on the byte that holds P1's and P2's kicks, so with RetroArch's SOCD option on, LK and MK pressed on the same frame didn't both reach the two-player game. It turned up when the driver was ported to FinalBurn Neo itself and compared with this core frame by frame. With no directions pressed and LK+MK pressed together every second, switching the option on changes nothing in 20,000 frames on the v1.1.1 core, in both games and on both screens; on the v1.1 core it changes the two-player game two seconds in. The v1.1.1 core also plays 20,000 frames identically to v1.1's with the option off, and to the FinalBurn Neo driver with it on. The v1.1.1 packages: 109 checks pass, 3 are blocked and none fail; six of them are new and run the macOS launchers' branch with stand-ins for macOS's own tools. The Windows installer's PowerShell passes 16 of 16 for each package under PowerShell 7 on Linux, and the Fightcade package passes 25, with 2 blocked. The v1.1.1 receipt has the hash of every package, core and file.
  • Not yet run: the Windows installer and launchers under Windows PowerShell 5.1 on a Windows PC; on a Mac, the wide screen and the six-player package (the two-player package installed and played on one Mac); physical controllers, two or six. These RetroArch builds can't run on Fightcade, MAME, stock FinalBurn Neo or CPS-2 hardware; the Fightcade build is next.
One refusal that hasn't happened in a run

A Team Hyper whose partner is still referenced by a live object is refused, and the code for it has been reviewed, but no run has reached it. In every attempt with ordinary inputs, the resident's projectile was gone 4 frames after the new point settled, before a request was possible. It stays unverified until a run shows it.

The Fightcade build

v1.0.0 needs its own core. The engine keeps its state in 64 KiB of RAM the core adds at $810000, and it reads the six controllers and a sample clock through a window the core adds at $808000. Fightcade's FinalBurn Neo has neither, so the RetroArch packages can't run there.

The two-player game doesn't need six controllers, and its state fits in 16 KiB. FinalBurn Neo, the emulator inside Fightcade, maps 16 KiB of RAM at $660000–$663FFF for every CPS-2 game and keeps it in its save states, and MAME maps it too. X-Men vs. Street Fighter doesn't use it. On Fightcade's own emulator code, through 454,271 frames of attract mode, three arcade runs to the ending, continues and 119 versus matches, plus every test-menu item, the game didn't read or write one byte of it. The only code that can is a bookkeeping module at $07B53E–$07BF74, and the game switches that on only when the board reports extra memory.

So the Fightcade build keeps everything there. It's the same layout as v1.0.0's, 10,432 bytes, moved from $810000 to $660000. One new hook, in the power-on path after the game's RAM test, sets it up:

the boot hook, as the CPU reads it $000296 · xvse.03f6 bytes · 5 changed
before303900804030
after4EF9002FE222
MOVE.W 0x804030,D0 becomes JMP 0x2FE222. The chip is encrypted here: a plain read sees 27 52 91 6E E2 6E become 47 8E 1B 6C CF 52.

The instruction it replaces reads $804030, where bit 14 set means the board has no extra memory. The new code writes two test values into the block and reads them back, clears all 16 KiB, and writes XFC1 at $663FF0. Then it does the original read with bit 14 forced on, so the bookkeeping module can't start and share the block. If the RAM isn't there, the stamp isn't either, and every hook keeps the game's own behaviour.

The core's sample clock goes too. DHC and snapback motions count their windows in it, so the build keeps its own at $663FF4, which the game's VBlank interrupt adds one to right after it reads the controls. The miniature faces stay out, because the core colours them as it draws, and so do the six-player mode and the wide arena. The rest is v1.0.0's engine: its 46 hooks plus this one, 14,202 bytes of code in the same blank region, and three changed files.

I haven't played it in Fightcade yet. What ran is Fightcade's emulator source, fightcade-fbneo at commit c959501, built without its window and fed scripted inputs. Its states were saved and loaded the way Fightcade's GGPO code does a rollback: every 61 frames, the last 8 were loaded back and run again, and the whole state had to come out the same. Across 35 runs and 1,128,037 frames that's 18,407 rollbacks, and none of them differed. Every read and write of the block came from the engine's code, nothing touched $808000, and no VBlank was lost or counted twice in a fight. These two-player tests pass there: tags, KO hand-offs, Team Hyper and the Variable Counter, CPU third members, DHC chains and refusals, snapbacks with every character, six colours, the HUD, continues and rematches. An arcade run beat Apocalypse, but the test bot never won the partner battle after him, so the ending hasn't been reached on this build. It also boots and tags in MAME 0.264. v1.1 adds five more runs there, for the HP+HK cycle and every colour input with and without Start, with the same rollbacks and the same result.

Two things you'll see. FinalBurn Neo warns on every start that three files have the wrong CRC: they're the changed ones, and Misc > Options > Hide ROM Warnings turns the message off. And FinalBurn Neo doesn't keep its converted colours and its sprite list in a save state, so the first frame it draws after a rollback can show colours or sprites from before it. The stock game does the same, and the game state isn't affected.

Since v1.1 the build also sets itself up from a saved state. When Fightcade has savestates/xmvsf_fbneo.fs (or xmvsf_fbneo_ranked.fs), it starts a match by loading it, and a state saved on the stock game brings the 16 KiB back as the stock game left them, all zeros, with the CPU long past the boot hook. v1.0.0's build played that match two on two. In v1.1 the VBlank hook runs the same setup on the first frame the XFC1 stamp is missing. On Fightcade's emulator code, a match started from a state saved on the stock game had the stamp on its second frame and the three-pick character select after the coins and Start.

The installer builds a whole xmvsf.zip from yours and prints a build code, f98f-bfea-1816-8d9a for v1.1; both players need the same one. Its PowerShell ran under PowerShell 7 on Linux and wrote the same file as the Python installer. Not run yet: the Fightcade client, an online match between two machines, Fightcade's lobby with a changed xmvsf.zip, ranked matches, the installer under Windows PowerShell 5.1 on Windows and on a Mac, and physical controllers. Until they run, it's a preview.

Work note

I set the goal and the rules: three a side with the arcade game's own tag, Team Hyper and Variable Counter kept, MP+MK and MP+HK for everything new, DHCs where the motion picks the Hyper, snapbacks, eighteen colours that fit with the palmod community's palettes ahead of generated ones, two players or six, and the wide arena. AI agents mapped the engine, wrote the payload, the hooks, the core changes, the installers and the test harness, picked colours 7 to 18 by that rule, ran the tests, and drafted this post. v1.0.0 is candidate 44, the Fightcade build is candidate 45, and v1.1, its Fightcade build included, is candidate 53. The failed candidates and their reports are kept next to the ones that pass, and the gates that haven't run say "open", not "passed".

This is an unaffiliated fan modification. X-Men vs. Street Fighter belongs to Capcom and the X-Men characters to Marvel. The download is patch data; you bring your own game.

Community input · Next release

Help shape the next XMvsSF 3v3 + DHC update

Choose up to 3 priorities. No account needed.