case studyUpdated Aug 8, 202613 min read$17

Shuma-Gorath's Sprites Weren't Encrypted. My Tool Had Scrambled Them.

A false compression diagnosis nearly sent the port into weeks of codec work. Fixing the read exposed 11,587 tiles and a path to running his real code in MvC.

cps2reverse-engineeringmarvel-vs-capcommshvsfshuma-gorath
CPS-2 Reverse EngineeringPart 13 of 43
Browse all writing
On this page

Shuma-Gorath was the test I could not pass by making one character look right. Dark Sakura had shown that MvC1 could be forced to accept a native fighter id; Shuma had to prove the method could carry a second character's real MSHvSF logic into a game that never shipped him.

The first diagnosis sent the work in the wrong direction. His sprite region measured 7.95 bits per byte, which looked like an unknown compression format and put 30–50 hours of codec archaeology on the plan. The sprites were never compressed. The analysis tool had applied CPS-2 opcode decryption to plaintext data. Read through the correct view, the same region measured 4.43, and all 11,587 tiles came through the existing pipeline.

That correction changed the decision: relocate his real code block, re-enter it through narrow trampolines, and accept no milestone until it survived live play. I directed that program and decided which fixes entered the build chain. Claude Code agents served as investigators, harness operators, and adversarial checkers; the emulator captures and regression gates, not their reports, are the proof.

Evidence Strip

Goal: Put Shuma-Gorath -- a character Capcom never shipped in Marvel vs Capcom 1 -- into the game as a native fighter: his own MSHvSF code and animations running inside MvC1, not a sprite swap over someone else's moveset.

Constraints: No source, encrypted program code, a secret character-id ($30) that falls off the end of every per-character table, and a custom 64MB graphics core that has to hold his tiles without breaking any stock character.

Approach: Relocate an entire block of his real MSHvSF logic into unencrypted arena space and re-enter it through trampolines; extract his tiles with the same pipeline built for Dark Sakura; then fix, one live-gated increment at a time, every crash the past-end table reads cause.

Result: He commits from the select screen with no cheat code, loads, and traverses relocated animation data under his native id. His name reads SHUMA-GORATH. A historical live build of Mystic Stare spawned six eyeballs in a hexagonal ring, but the current destination has not passed complete contact, damage, stun, teardown and reaction parity. The port is an integration build, not a finished fighter.

Proof: A live 23-second gameplay capture (below), a separate six-dimension adversarial pass on the names, and a fresh re-derivation and screenshot gate for every byte of the eyeball build. Claude Code agents performed those checks under refutation prompts; they are verification tools here, not the source of truth. The method, including its confident failures, is documented in the methodology post.

Artifacts: The companion Dark Sakura native port (the recipe he generalized) and the deployed ROM lineage.

August 8 Character Lab: source truth and the destination gap

The revamped harness now pins Shuma's source and destination ROM identities and expands his work into 36 required lanes. Only 1/36 is closed, and it is the documented waiver for a non-applicable persistent flight mode. Prior match traces, the six-eye visual and stock-roster survival remain observations until their named behavior gates carry every required artifact.

Marvel Super Heroes vs Street Fighter HIT EDITER showing source row 05 SHUMATGOLUS
SOURCE ORACLE // MSHvSF identifies hexadecimal row $05 as SHUMATGOLUS. The matching CATCH capture starts one row later, so it reaches the same row after four—not five—cursor steps.

The source HIT and CATCH rooms now provide reproducible animation and paired throw/victim indexes. The destination does not yet provide the symmetric view: MvC's CATCH editor cycles through its 22 stock rows without a native $30 entry, while the current combined native build's automated HIT scan returns to boot/title and fails its positive control. That is a harness finding and an engine integration defect—not permission to substitute a stock row.

This audit also retracts every older damage conclusion derived from fighter object +$7C. That field is not health; the live health field is +$270. Devitalization and both supers may finish an animation while remaining indistinguishable from heavy kick in the broad suite, so none is accepted until command, unique parent state, source animation, active boxes, +$270 damage, stun, cancels, victim reaction, recovery and audio all agree.

Native Shuma-Gorath — live gameplay in Marvel vs Capcom 1YouTube ↗

Why This One

I started with Marvel Super Heroes a long time ago, when I was a child. And I saw this weird, weird creature — green, sometimes purple, with six or seven arms. I was like, what is that thing? Then I noticed I liked it. Playing with it was funny. It was flexible. I thought: this is awesome.

Then I moved past those games, and the green creature wasn't there anymore. Marvel vs. Capcom came out without him. Nobody really plays with this thing. Nobody really likes it. So I thought — let me go try to bring that thing back.

That meant going into the code, finding the green creature inside Marvel Super Heroes vs. Street Fighter, looking at what was similar between the two games, and importing him piece by piece.

The eyeballs were the hard part. I couldn't find anything in Marvel vs. Capcom to borrow from, because nothing in that game does what his Mystic Stare does — nobody had six projectiles coming out at once, spinning in a circle. It wasn't there. It was a new thing, so I had to build it and work harder for it.

I wanted to give up on that too. It was taking so much time and eating so many credits, and I'm the only one doing this. People told me to give up. Who cares about this game? Who wants to play it? Why are you putting your time into that? There's so much out there to do — AI is opening up all these avenues, and here you are wasting your life on an old game.

I kept going.

The Blocker That Wasn't

Dark Sakura proved the native recipe: import a character from Marvel Super Heroes vs Street Fighter into MvC1 by running her own relocated code, not by force-fitting her onto a stock fighter. Shuma-Gorath ($30) was the test of whether it generalized -- and he arrived with a scary flag: sprite data that measured 7.95 bits/byte of entropy, the signature of a compression codec CPS-2 doesn't have.

One ROM, two truths -- the short version

The "compression" was our own tool's garbage: an analysis dump had run CPS-2 opcode decryption over plaintext data in the same address range. Read through the correct data view, his animation tree is an ordinary table (entropy 4.43), his 11,587 tiles walk out with the exact pipeline built for Dark Sakura, and his idle stance -- authentic green body, palette solved -- rendered on the first try. The full telling, with the entropy chart, is in the Dark Sakura native port; the war-story version, with the machinery that caught it, is in the methodology post.

Relocating the Whole Block

Where Dark Sakura's port leans on shared engine paths, Shuma's relocates an entire block of his real MSHvSF code. The block $0301DE..$032A3E is copied verbatim into unencrypted arena space at $3FA060 and re-entered through trampolines, so his logic runs as his own code rather than being fitted onto anyone else's spine. The instructions his relocation displaces are replayed cleanly on the way in. The block is 10,336 bytes$032A3E minus $0301DE — and it does not move to a convenient hole; it moves to the only space on the bus that is not encrypted at all:

mvsc program space // where his code came from and where it runs
13 · stock program5 · stock programAB$000000$400000
  • 1
    stock program
    $000000$0301DE · 192.5 KB
    Opcode fetches here are decrypted by the CPS-2 hardware.
  • 2
    his MSHvSF code block
    $0301DE$032A3E · 10.1 KB
    10,336 bytes, copied verbatim. This is the source, not the destination.
  • 3
    stock program
    $032A3E$23C000 · 2.04 MB
  • 4
    thunks and helpers
    $23C000$23D600 · 5.5 KB
    Everything I wrote to get in and out: $23C018, the voice helper $23C222 and its table $23C852, the type-$78 trampoline $23CF70, the win-crash clamp $23D4C8.
  • 5
    stock program
    $23D600$3F0000 · 1.70 MB
  • 6
    unencrypted arena
    $3F0000$400000 · 64 KB
    The relocated copy lands at $3FA060 and runs as his own code. The 2-byte spike fix sits at $3FB75A; the eyeball handler at $3FC8C0.
  • A
    the block lands here
    $3FA060 · marker
  • B
    eyeball handler
    $3FC8C0 · marker
Region boundaries bracket the addresses named in this post; every address in the notes is one the build actually uses.
Dispatch path for char-id $30
Drawing the diagram…
His logic runs as his own relocated code, not fitted onto another character's spine.

He commits from the character select with no cheat code -- one cell to the right of Wolverine. His name reads SHUMA on the select grid and SHUMA-GORATH on the lifebar, and the old Iron Spider branding is gone from both screens, verified by an independent six-dimension adversarial pass that included a GFX poison test.

Shuma's select cell: a blank wordmark before, SHUMA after
Shuma's select cell: before
‹›
BeforeAfter
Select cell // drag to compare — blank wordmark becomes SHUMA
In-match lifebar, before and after the name-routing fix: Shuma reads SHUMA-GORATH
In-match lifebar, before and after the name-routing fix: before
‹›
BeforeAfter
Lifebar // drag to compare — the name-routing fix seats SHUMA-GORATH

The Defect Behind His Crashes

Shuma kept tripping crashes that felt unrelated -- a knockdown crash, ×5.94 damage, corrupted stun, a pinned camera, garbage sounds -- and they share one root cause: every per-character table in the engine has exactly 23 entries (ids $00..$2C), and $30 reads past the end of all of them. Capcom left no half-wired 24th row to finish -- $30 was never provisioned at all, and its sibling $2E only resolves because it borrows Spider-Man's $0E entry. A 203-site census of the past-end reads exists, and because the tables are packed with zero slack (id $30 physically aliases the next table's first rows), every fix has to be a reader-side clamp or redirect, never a table rewrite -- the knockdown crash, for instance, fell to one byte (0x3D359, d1 -> d0), and the ×5.94 damage clamped back to ×1.0. The master defect gets its full treatment -- diagram, fingerprint-by-fingerprint breakdown, and the census -- in the Dark Sakura native port.

Mystic Stare: An Eyeball That Actually Flies

This is the payoff of the native era. Marvel vs Capcom 1 never had Shuma's Mystic Stare; teaching the 27-year-old engine to recognize the input (charge Back, then Forward + Punch) was the easy half. Making it fire was the hard half -- and it now does the whole thing: one cast spawns six eyeballs seeded at the six vertices of a ring.

The cast: Shuma's Mystic Stare pose -- 8 frames of his real MSHvSF animation
The cast // 8 frames of his real MSHvSF animation
Frame +10: the eyeball (cream body, pink iris) just launched, Shuma's tentacles at frame left, Venom mid-screen
Frame +10 // eyeball just launched
Frame +22 of the same cast: the eyeball crosses the screen toward Venom
Frame +22 // crossing toward Venom
The move, live: one cast spawns all six eyeballs in a hexagonal ring with a hollow center -- each seeded at a hexagon vertex, the whole formation drifting toward the opponent
The move, live // six eyeballs, hexagonal ring, hollow center
Mystic Stare: from input to eyeballs on screen
Drawing the diagram…

Two details make this honest engineering rather than a lucky poke. First, the object allocator $3FAE was found by tracing a live Ryu Hadouken, because static matching failed to locate it -- and Ryu's own Hadouken still fires afterward, which is the proof the reroute is stock-safe. Second, the type-$78 dispatch is rerouted with only 3 encrypted bytes into a trampoline ($23CF70) that jumps to a relocated EYE_HANDLER at $3FC8C0. The result was independently verified: the object is real, owned by Shuma, and it moves.

Bounded, on the record

One cast fires all six eyeballs, each seeded at a vertex of a hexagonal ring (per-eyeball position offsets written at spawn) -- verified live on the build where it landed. A later build in the chain replaced the rigid formation with a target-relative orbit, phase seeded in object field $1E. What's still bounded: the eyeballs have no hitbox yet, they freeze during hitfreeze, and the ring has not been visually re-confirmed on the current tip (the last capture landed during the opponent's intro). The spawn-and-ring pipeline is proven; the weaponization is not done.

Honest Caveats

Shuma is a hybrid: his own code and animations, with his own walk rows (WALK_A-D), jump rows (JUMP_J1-J4, MSH idx5 data) and gravity now seated -- but residual Ryu physics clamps remain at other dispatch sites, which is why a mini "RYU" tag still shows under his lifebar portrait. Specifically:

A known open defect, caught on camera: the blue boxshape-mirror columns rising from the ground during a super-jump -- root known (VRAM mirror at $922000), fix not yet shipped
Known open defect // blue boxshape-mirror columns, root known at $922000, unfixed
  • The moveset is incomplete, and the honest number is the identity gate. An earlier version of this list claimed the old donor build had more moves wired than the native -- a measured 29-move battery refuted that: zero moves confirmed lost. What's real is the moveset identity gate on the current tip: pass=11, FAIL=6 -- his supers, Devitalization, the stare/smash strength variants, and two command normals still collapse into other moves. The path forward remains driving Ryu's working move skeleton with Shuma's own animations and hitboxes.
  • Voice routing is observed, not fully accepted. $0D5EF4 routes through helper $23C222 to a Shuma table at $23C852, but a QSound command does not prove the correct banked sample is present and audible for every move. Dark Sakura's wrong sounds are still silenced, not replaced.
  • Supers remain semantically open. Devitalization, Chaos Dimension and Chaos Split can complete in the broad suite, but the measured attack/reaction pair collapses onto heavy-kick behavior. The stricter identity gate therefore overrides the survival result. Sekika (air d+MK) and Upward Stare (u+HP) landed as observations, and j.MK / j.HK rose from zero to 11/14 attack frames; all still need current source-to-destination consequence gates.
  • Pre-fight walk-back is missing. (The win/KO crash is fixed -- $07D950 clamped through thunk $23D4C8, and the win-condition probe reads win=1 survived=1 -- and the dizzy softlock now exits.)
  • The blue vertical glitches rising from the ground are the boxshape VRAM-mirror columns at $922000 -- known root, poke-proven, unfixed.
  • The 64MB core breaks Fightcade netplay. He is an offline character until that changes.

Status

SurfaceState
Secret commit, loads, fights native (block $0301DE..$032A3E -> $3FA060)Working; 0 wild jumps, 0 non-boot resets (earlier audit in the chain)
Name both screens (SHUMA select, SHUMA-GORATH lifebar)Working; Iron Spider gone; 6-dim adversarial pass
Master defect (past-end 23-entry reads)203-site census; fixes are reader-side clamps
Cumulative build chainHistorical gates are preserved; current-tip parity is accepted only by the August 8 lane manifest
Mystic Stare eyeballFires the 6-eyeball ring (verified when the ring landed; not visually re-confirmed on the tip); target-relative orbit landed in a later build; no hitbox yet; freezes in hitfreeze
Mystic Smash spikes + hitboxPartial: 2-byte spike route and a live attack box were observed; smooth-form reset after passing the opponent remains open
Win/KO crash + dizzy softlockFixed ($07D950 clamp thunk $23D4C8; win-condition probe reads win=1 survived=1)
Voice table routeObserved ($0D5EF4 -> helper $23C222, table $23C852); full audible voice/effect matrix remains open
Air normalsj.MK / j.HK produced 11/14 attack frames in a prior gate; Sekika and Upward Stare are observed, with current consequence parity open
Supers / pre-fight walk / identity-gate FAILs (pass=11 FAIL=6)Open

He shares the native recipe with Dark Sakura, and the honest theme of both ports is the same: the parts you can prove, you prove; the parts you cannot, you say so on the record. That rule matters more than usual here, because the reverse engineering itself was agent-driven -- and this character is where it failed loudest. A "spawns satellite objects" model for one of his attacks was built and shipped before an adversarial pass killed it; the real defect was a missing hitbox on the object that already existed, and the real fix was two bytes. The retraction is recorded, not quietly dropped.

Written by Daniel Plas Rivera · 2,958 words · $17

ShareXLinkedIn