The palette system has a second version in the combined v8 research build. It is still in testing, including the five-level meter; it is not a released standalone download. The v1 palette installer remains available. The v2 work addresses two things this page originally described as scope limits and one thing that was simply wrong.
The six EX/secret ids no longer reuse their parent's banks literally — each is
anchored to its own stock identity and its other five banks are graded to
distinct themes. Ryu's Ken and Akuma forms get six blocks each, which is what
stops Ken wearing Akuma's palette on index 2 and Akuma rendering solid white on
index 2; both were the six-color reader walking off the end of a hardcoded
two-block table at $1CEC44 and $1CED04. The v8 Hyper-meter patch raises its cap to five
levels in the statically verified build, with full-match testing still pending —
that has its own write-up.
Still open: Mega Man's hyper palette rows pick their bank with the stock boolean, so Beat Plane and friends do not match the body on MP through HK. The fix is written and currently disabled, because the first build carrying it rendered him black.
This page remains the standalone v1 release history and complete palette tutorial. The current DHC download includes the same six physical-button color system alongside Super-to-Super, Special-to-Super, the 0–999 display, Magnetic Shockwave, Gold Mega Optic, and native-Duo isolation. The standalone v1 download remains available for players who want only the palette feature.
Marvel vs. Capcom now gives every vanilla-selectable fighter six color choices: LP, MP, HP, LK, MK, and HK. The match body, the large character-select figure, and the selected-character animation all follow the same button. The body colors come from Marvel vs. Capcom 2, and the CPS-2 ROM does not expand by one byte.

- Stock X-Men vs. Street Fighter2colors
LP/MP/HP share one index; LK/MK/HK share the other
- Stock Marvel Super Heroes vs. Street Fighter2colors
the same punch-versus-kick collapse
- Stock Marvel vs. Capcom2colors
six physical inputs already exist, but the selector keeps only 0 or 1
- MvC2 selectable donor banks6colors
LP, HP, A1, LK, HK, and A2 become the six MvC1 cabinet choices
- MvC1 with this patch6colors
the stored index remains distinct through select and gameplay
what changes on screen
One button reaches three patched surfaces; two families stay stock
Blue means the MvC2 color choice is bridged into MvC1. Green means Capcom's original artwork or effect path is deliberately untouched.
| route | LP | MP | HP | LK | MK | HK |
|---|---|---|---|---|---|---|
| match fighter | bridged | bridged | bridged | bridged | bridged | bridged |
| selected animation | bridged | bridged | bridged | bridged | bridged | bridged |
| large select figure | bridged | bridged | bridged | bridged | bridged | bridged |
| small roster cell | native | native | native | native | native | native |
| HUD + effect palettes | native | native | native | native | native | native |
How six colors became true everywhere
LP MP HP → 0 LK MK HK → 1Stock selector stores at $00B15C, $00BC62, and $00C464
Stock code keeps only punch or kick
2 indicessix buttons collapse to 0 / 1
Fresh MAME runs and the shared 68000 selector agree: LP, MP, and HP store 0; LK, MK, and HK store 1.
COVERAGE 0% OF THE ANIMATION TREE
Read this section
Six live Ryu MAME body captures, 48/48 RGB words each
The body reader accepts indices 0–5
6 body colorsselect presentation still incomplete
Ryu reached six exact $60-spaced body blocks, but that did not prove the character-select artwork used the same index.
COVERAGE 33% OF THE ANIMATION TREE
Read this sectionRetracted here
Six body colors meant the whole feature was finished
All 126 physical selections copied the expected 48-word source
The large select figure gets six sources
48 / 48large-figure words match
A separate hook routes the existing large select figure through a 21-by-6 palette arena while preserving Capcom's original artwork.
COVERAGE 67% OF THE ANIMATION TREE
Read this section
MAME palette-RAM trace at the selected-animation loader $07AF6C
War Machine exposes a third collapse
1–5 → bank 1stock animation loader behavior
HP looked like MP during confirmation and became correct only in the match. The transition proved another loader was reducing every nonzero index to one.
COVERAGE 67% OF THE ANIMATION TREE
Read this sectionRetracted here
A correct large figure and body proved the selected animation
21 fighters × six physical buttons; 32/32 + 48/48 + 48/48 exact uploads
All three loaders agree
126 / 126fighter-button selections pass
Every vanilla fighter was selected with every attack button. Animation, large figure, and body each matched the requested source before the cut was accepted.
COVERAGE 100% OF THE ANIMATION TREE
Read this sectionpalette write → ? identity change → ? capture both + intervening frameN41 report; not yet reproduced or assigned to this patch
One frame-order question stays open
trace pendingpalette write versus sprite change
N41 reported that some select transitions may update palette before character identity for one frame. The article labels that question instead of converting it into a claim.
COVERAGE 100% OF THE ANIMATION TREE
Read this section
- BODYnot yet true
- FIGUREnot yet true
- ANIMATIONnot yet true
Watch all 126 selections
The reel keeps every physical selection, the complete selected-character animation, and a live body result. Its evidence rail reports animation 32/32, large figure 48/48, and body 48/48 before each cut. The center roster-grid cells remain stock.
Just play: the patch is at the top. See the result: continue through the roster and the 126-selection video. Reverse it: jump to the Ghidra, MAME, assembly, and encrypted-hex walkthrough.
Will this work on vanilla MvC1? Yes—and it does not add two characters
The installer starts from the exact, unmodified 21-member Euro parent mvsc.zip. That is the
vanilla game: every member is checked by case-sensitive name, byte length, and SHA-256 before
one patch byte is applied. Repacked clean ZIPs are accepted even when their member order differs.
Dark Sakura ($2E) and Shuma-Gorath ($30) are not selectable characters in that parent.
The release prepares six palette blocks and directory rows for those IDs because the custom
character project uses them, but vanilla never asks the selector to load either row. Dormant
palette data cannot make a character appear, execute moves, or gain graphics.
So the result has two clean boundaries:
- On a vanilla parent, all 21 stock-selectable fighter IDs receive six body colors, six matching confirmed-selection animation colors, and six matching large select-side figures. The center roster-grid cells remain stock.
- On a separate custom build that already exposes
$2Eor$30, those IDs have compatible six-color body rows ready. This patch alone does not install either custom character or invent select artwork for it.
- 1null / non-fighter row$000000–$000002 · 2 B
- 216 vanilla main-form fighters$000002–$000022 · 32 B
- 3Onslaught boss row remains stock$000022–$000024 · 2 B
- 45 vanilla EX-form fighters$000024–$00002E · 10 B
- 5Dark Sakura + Shuma custom IDs$00002E–$000032 · 4 Bprepared palette rows; unreachable from the vanilla select screen
- ADark Sakura$00002E · marker
- BShuma-Gorath$000030 · marker
The untouched game reaches IDs $02 through $20 for its main forms and $24 through
$2C for its five EX forms. The $22 boss row keeps its stock pointer.
Those 21 selectable IDs now resolve six $60-byte body blocks instead of two, and
both the confirmed-selection animation and existing large select figure follow the
same six-way choice. No select-grid entry, moveset, graphics tile, or executable
character is added.
$2E and $30 point at prepared colors only. A custom build that already supplies Dark
Sakura or Shuma can consume them; a vanilla build never reaches them.
Input verification is against the clean parent, not against a custom roster. The extra palette
bytes are written into verified $FF space after that clean-parent proof succeeds.
Stock MvC, XvSF, and MSHvSF really had two colors
I began convinced that Marvel Super Heroes vs. Street Fighter and X-Men vs. Street Fighter gave every attack button its own color. I was wrong. The useful test is not “do these screenshots look different?” Color perception, animation timing, and duplicate-character rules can all muddy that. The decisive test is the value at the stock selector's store instruction.
For MSHvSF, that store is $00B15C. For XvSF, it is $00BC62. I ran each of the six
physical MAME buttons in a fresh process and logged d1 plus the byte after the store
retired:
| stock arcade program | LP | MP | HP | LK | MK | HK |
|---|---|---|---|---|---|---|
MSHvSF (mshvsfu) | 0 | 0 | 0 | 1 | 1 | 1 |
XvSF (xmvsfjr1) | 0 | 0 | 0 | 1 | 1 | 1 |
Every run passed. MP is the punch color. MK is the kick color. Neither is a third or fifth selector value in these stock CPS-2 binaries.
That result does not tell me which memory produced the original expectation. A home port, a hidden recolor entered with a button-and-direction combination, and a different Capcom game remain separate hypotheses. I am not converting any of those into a fact without another trace.
Run MSHvSF yourself before you believe me
With your own MAME ROM path:
mame mshvsfu -rompath "C:\path\to\your\roms" -window -skip_gameinfoPick one high-contrast fighter six times. Confirm with exactly one of LP, MP, HP, LK,
MK, or HK, reset with F3 between runs, and avoid a duplicate-character test. Pause the
same in-fight pose with P; use F12 for a frame capture.
To inspect the decisive byte instead of judging a costume, launch with -debug, set
bp 00b15c for MSHvSF or bp 00bc62 for XvSF, confirm with one attack button, then
single-step the move.b. D1 and the destination byte are 0 for LP/MP/HP and 1
for LK/MK/HK. Reset with F3 and start a fresh process for each button so a duplicate
choice or stale team state cannot answer the question for you.
One Capcom routine shipped in three games
Here is MvC's stock selector. XvSF and MSHvSF use the same instruction sequence; only the held-input offsets and fighter-object offsets differ.
; Marvel vs. Capcom, decrypted 68000 @ $00C426
00C426 342E 0024 move.w $24(a6),d2 ; P1 held-button word
00C42A 4A2C 0002 tst.b $2(a4)
00C42E 6704 beq.s $00C434
00C430 342E 0026 move.w $26(a6),d2 ; P2 held-button word
00C434 7200 moveq #0,d1
00C436 0242 0700 andi.w #$0700,d2 ; all three kicks, one test
00C43A 6702 beq.s $00C43E
00C43C 7201 moveq #1,d1 ; never 2, 3, 4, or 5
00C43E 6008 bra.s $00C448
00C464 1141 0277 move.b d1,$277(a0) ; store fighter color byteThe common fourteen-byte core is the receipt:
| game | core address | stored byte | common bytes |
|---|---|---|---|
| MvC | $00C42A | $277(a0) | 4a2c0002 6704 342e0026 7200 02420700 6702 7201 |
| XvSF | $00BC28 | $217(a0) | 4a2c0002 6704 342e0016 7200 02420700 6702 7201 |
| MSHvSF | $00B122 | $257(a0) | 4a2c0002 6704 342e0016 7200 02420700 6702 7201 |
One moveq #1,d1. That is the entire two-color decision.
What those hex patterns mean
This is the part I would want if I inherited the patch six months from now—and it is the part N41's questions consistently push toward: do not publish a range of changed bytes without decoding the state they carry.
| hex / value | 68000 meaning | why it matters |
|---|---|---|
0242 0700 | andi.w #$0700,d2 | keep only bits 8, 9, and 10: LK/MK/HK as one kick family |
7201 | moveq #1,d1 | collapse any kick to index 1 |
1141 0277 | move.b d1,$277(a0) | store the chosen index in the fighter object |
4a2e 0277 | tst.b $277(a6) | stock loader asks only “zero or nonzero?” |
41e8 0060 | lea $60(a0),a0 | nonzero means advance one 96-byte color block |
0281 f000f000 | andi.l #$F000F000,d1 | preserve the high nibble of two palette-RAM words before OR-ing RGB |
$0770 | binary 0000 0111 0111 0000 | all six attack bits: three punches plus three kicks |
The live MvC input sweep names every bit:
| MvC1 button | MAME field | held-word mask | patched index |
|---|---|---|---|
| LP | P1 Button 1 | $0010 | 0 |
| MP | P1 Button 2 | $0020 | 1 |
| HP | P1 Button 3 | $0040 | 2 |
| LK | P1 Button 4 | $0100 | 3 |
| MK | P1 Button 5 | $0200 | 4 |
| HK | P1 Button 6 | $0400 | 5 |
Those masks OR to $0770, exactly matching MvC's own presence check at $014494.
The game was already carrying six distinct inputs. Only the color routine threw four
of the distinctions away.
Follow the byte from the button to palette RAM
flowchart LR
input["held input word<br/>$24(a6) / $26(a6)"] --> decode["selector<br/>stock 0/1 · patch 0..5"]
decode --> object["fighter color byte<br/>$277(a0)"]
object --> preview["large select-figure hook<br/>$0B7690"]
preview --> previewArena["large-figure arena<br/>$3F5000 + fighter × $240 + index × $60"]
previewArena --> previewRam["select palette RAM<br/>$914180"]
object --> latch["confirmed-slot bytes<br/>$FF40C4..$FF40C7"]
latch --> selectAnim["selected-animation hook<br/>$07AF6C · index × $60"]
selectAnim --> animRam["two-row animation palette RAM<br/>32 exact RGB words"]
latch --> setup["fighter setup<br/>restores $277(a6)"]
setup --> directory["fighter directory<br/>$001C7268"]
directory --> scale["base + index × $60"]
scale --> rows["3 rows × 16 words"]
rows --> ram["body palette RAM slot<br/>$914000 + slot × $60"]The important distinction is storage versus interpretation. $277 is already one
byte, so values 0–5 fit without new RAM. The stock consumer is what makes the byte
boolean:
The confirmed-slot latch is now named too: $FF40C4 = P1 point, $FF40C5 = P1 bench,
$FF40C6 = P2 point, and $FF40C7 = P2 bench. They come from select objects
$FF3000/$FF3800/$FF3400/$FF3C00, respectively. That is why one-byte indices survive
without allocating new state.
The table at $1C7268 is not executable code. It is one big-endian 32-bit pointer for
each even character ID: entry = $1C7268 + (id / 2) × 4. For IDs $02..$20, the
compact arena row is (id/2)-1; after Onslaught's untouched $22 row, IDs
$24..$30 use (id/2)-2. The pointed block is
$184000 + compactRow × $240, and one button inside that block is
base + colorIndex × $60. Ryu (id=$12) therefore reads directory entry $1C728C,
which changes from $1C87C4 to $185200; HP/index 2 reaches $1852C0.
; MvC fighter-body loader @ $00A4BC
00A4BC 4A2E 0277 tst.b $277(a6)
00A4C0 6704 beq.s .base
00A4C2 41E8 0060 lea $60(a0),a0
.baseThe patched reader bounds the value to 0–5, multiplies it by $60, and adds that
offset to the fighter's directory pointer. Values above 5 fall back to 0. That makes an
out-of-range byte a safe first color rather than a read into unrelated data.
Register names matter here. At $00A4B0, $2C8(a6) loads a0, the ROM source pointer
previously resolved through $001C7268. $288(a6) loads a1, the live palette-RAM
destination; $28C(a6) loads a2, the second/mirror destination. Calling $2C8 the
destination would reverse the data flow.
The 2026 SoK on AI-augmented binary reversing
describes an iterative four-mode workflow: triage, static analysis, dynamic analysis,
and interventional testing. Here that means: search three games for a common byte
signature; decode the selector and its consumers in Ghidra; observe D1, $277, source
pointers, and palette RAM in MAME; then deliberately carry values 2–5 through paths the
stock game never produces. Parallel agents helped rank searches and automate repeated
runs, but an agent's interpretation is not an evidence class. The paper supplies the
workflow vocabulary. The listings, raw bytes, register stops, RAM dumps, and rendered
frames below prove this ROM.
Nine body readers widen; the effect reader does not
The fixed +$60 idiom appears ten times in this palette cluster. Nine sites read
fighter-body blocks and now scale by the 0–5 index:
$00A4C2 $00A5B8 $00A634 $00A688 $00A6CA
$00A7B8 $00A834 $00A888 $00A8C6Their six-byte hooks begin at $00A4BC, $00A5B2, $00A62E, $00A682, $00A6C2, $00A7B2, $00A82E, $00A882, $00A8C0. The tenth site, $00A920, selects an
assist/effect palette table that still contains only two colors, so the release leaves
it stock. Widening it would turn index 5 into a read beyond that table.
This is why “replace every +$60” is not a patch strategy. The index-5 propagation
run recorded 36 executions across the widened body-reader family; the effect path is a
deliberate boundary, not an overlooked hook.
The patch turns the stored bit into a six-way index
The release payload lives at $3F4000 in verified $FF space inside stock
mvc.10. A six-byte encrypted hook at $00C434 redirects into this plaintext routine:
; v1 payload @ $3F4000
moveq #0,d1
btst #4,d2 ; $0010 LP
bne.s .done
moveq #1,d1
btst #5,d2 ; $0020 MP
bne.s .done
moveq #2,d1
btst #6,d2 ; $0040 HP
bne.s .done
moveq #3,d1
btst #8,d2 ; $0100 LK
bne.s .done
moveq #4,d1
btst #9,d2 ; $0200 MK
bne.s .done
moveq #5,d1
btst #10,d2 ; $0400 HK
bne.s .done
moveq #0,d1 ; no attack button
.done jmp $00C448This also defines simultaneous-button behavior: the lowest-numbered physical button wins—LP before MP before HP before LK before MK before HK. With no attack button held, the routine defaults to index 0/LP, matching the stock punch-side default.
Six live selector runs passed: Button 1 stored 0, Button 2 stored 1, through Button 6
storing 5 in the original $277 field. The integrated release then repeated all six
runs on Ryu: each index selected the expected $60-spaced ROM block, produced a unique
live-palette hash, and matched all 48/48 lower RGB444 words with zero mismatches.
The normal duplicate-choice branch is $00C440, not $00C46A. It advances the
already-used index modulo six. A live two-player receipt closes the path: P1 chose
Button 6/index 5, P2 chose the same fighter, $3F4038 received d2=5, and P2 retired
index 0. A second live receipt carries index 5 through $FF40C4, fighter setup, and 36
widened body-reader hits. $00C46A is a separate duplicate normalizer used by automatic
preset/progression team synthesis (plus one zero-caller legacy/debug route); it is not
the normal player mirror-button path and remains stock.
Put the bodies side by side: all 21 selectable fighters, all six buttons
Palette swatches are useful to a hex editor. A player wants to see the costume. The showcase therefore uses the 126 harness-verified live MAME body snapshots, not an offline reconstruction of a multipart CPS-2 sprite. For each fighter, the renderer finds pixels that respond across the six matched frames, restores the adjacent shared dark outline, removes the unchanged stage, and floor-aligns the six intact cutouts. LP, MP, HP, LK, MK, and HK are printed under the bodies; the MvC2 source-bank label sits above each column.

Open the six-body roster at full resolution.
This is the full vanilla-selectable set: sixteen main fighters and five stock EX forms. Onslaught is a boss with one stock body color, not a six-button select choice. Dark Sakura and Shuma-Gorath are not in the vanilla character select, so presenting them in this playable-roster wall would repeat the exact confusion the installer section resolves.
Every rendered row produced six different pixel hashes. That is a static visual audit of 126 body-palette applications, not a claim that I played 126 matches. Ryu remains the live MAME body integration control; the roster wall uses the same screenshot-validated CPS-2 tile decoder, hardware pen 15 as transparency, each character's group-0/slot-0 idle record, and the generated body blocks whose SHA-256 values are in the manifest.
The second sheet makes smaller color changes easier to inspect. Each extracted body is floor- and center-aligned with its partner. The left half uses the punch palette, the right half uses the same-strength kick palette, and a narrow center band linearly blends both RGBA cutouts. The unchanged stage is absent, so it cannot masquerade as palette evidence at the seam.

Open the half-overlay roster at full resolution.
The audit records the live-snapshot census hash, source crop, foreground-mask method, palette-block hash, animation record, sprite dimensions, and six-way uniqueness for each fighter. No pose was generated, traced, or hand-recolored; the downloadable patch is independent of these presentation images.
The first patch fixed the body and missed the large select figure
The bug report was correct. Six body uploads did not prove six colors everywhere. MvC draws the large character-select figure with a separate 48-word loader. My first build widened the fighter-body readers while explicitly leaving that UI path stock, so the game remembered indices 0–5 but the preview still reduced them to its P/K pair.
The stock source setup begins here:
; large character-select figure @ $0B7690
0B7690 207C 001D 2A24 movea.l #$001D2A24,a0
0B7696 D1C0 adda.w d0,a0
; ... copy 24 longwords / 48 palette words to $914180Replacing body colors could never fix that instruction. The corrected release patches
the encrypted mvce.04a bytes at member offset $037690 from
f2f1ee8bca79 to 872b24e0e8cb; after CPS-2 decryption, that is a six-byte
jmp $003F40F0. The wrapper identifies P1/P2 and point/bench, reads the corresponding
fighter object's existing $277 index, validates 0–5, and computes:
preview source = $3F5000 + compactVanillaFighter × $240 + colorIndex × $60For Ryu, compact row 8 starts at $3F6200. Six fresh MAME processes reached
$3F6200, $3F6260, $3F62C0, $3F6320, $3F6380, $3F63E0. Then the final capture harness
repeated that contract for all 21 fighters × all six buttons. Each clean process
navigates with ordinary directions, enters Capcom's original arcade unlock code when
the target is hidden, and confirms with the labeled physical button. It never writes a
fighter ID or color index. All 126 runs matched the confirmed animation's selected body
bank 32/32, copied the large figure to $914180 and matched it 48/48, advanced into the
match, matched the body block at $914000 48/48, and produced six distinct
selected-animation uploads plus six palette-responsive large-select-figure/body
foregrounds per fighter.
A third loader made the selected animation show the wrong color
The next bug report was more specific than “the colors look close”: War Machine selected with HP animated in MP colors, then entered the match in the correct HP color. Captain America showed the same pattern. That transition proves two loaders disagree; it cannot be repaired by re-encoding the video or making the large figure more saturated.
The short clip keeps the transition that a still image loses. The large figure and selected animation appear first; the live fighter body follows. Matching only the last frame would have missed the exact bug that exposed this third consumer.
The live palette-RAM trace found the third consumer at $07AF36. It resolves the same
fighter directory as the match body and reads the already-committed point/bench color
byte—but Capcom's stock code reduces that byte to zero versus nonzero:
; confirmed character-select animation
07AF42 302E 0052 move.w $52(a6),d0
07AF48 207C 001C 7268 movea.l #$001C7268,a0
07AF4E 2070 0000 movea.l (a0,d0.w),a0
; a1 now points at the committed slot color byte
07AF6C 4A11 tst.b (a1)
07AF6E 6704 beq.s $07AF74
07AF70 D0FC 0060 adda.w #$60,a0That is the exact visual failure: index 0 uses bank 0; indices 1, 2, 3, 4, and 5 all add
one $60 block and animate with bank 1. The match later reaches the already-widened body
reader and uses the true index, so its color “suddenly” becomes correct.
The new wrapper preserves the stock animation, destination logic, and priority nibbles. It changes only source selection:
; payload @ $3F42D0, entered from encrypted hook $07AF6C
moveq #0,d0
move.b (a1),d0
cmpi.w #5,d0
bls.s .bounded
moveq #0,d0
.bounded:
mulu.w #$60,d0
adda.l d0,a0
jmp $07AF74The stock routine then copies 32 lower-RGB words into one of
$914040/$914080/$9140C0/$914100. The census accepts a select frame only after those
32 words equal the requested body bank and the separate 48-word large select-side figure
upload passes. War Machine HP now animates navy/red instead of green; Captain America's
MP, HP, LK, MK, and HK secondary ramps no longer collapse to MP before the fight.
This paired frame is the visual version of the debugger chain. At select time, index 2
chooses large-figure source $3F50C0 and selected-animation/body-bank source $1840C0.
When the match becomes gameplay-ready, $1840C0 is still the body source. Before the
third hook existed, the top animation used bank 1 while the bottom body used bank 2—the
exact “wrong on select, right in game” report that exposed the missing consumer.

These preview words cannot be literal copies of the fighter-body words: a portrait pen and a sprite-body pen do not necessarily color the same semantic feature. The final builder first matches each portrait pen through the stock body palette authored for that fighter. Costume pens then take the matched MvC2 pen's hue and chroma plus the portrait-to-body brightness difference. That target-hue result contributes 3/4 and a ridge-regularized affine fit contributes 1/4, followed by a restrained 5/4 chroma gain. This specifically prevents a blue target ramp becoming olive merely because the stock portrait was less blue than the stock body. Warm skin/hair pens may only use warm body pens whose RGB range stays stable across all six banks; those pens keep indexed channel motion and receive no chroma gain.
There is also a measured escape hatch, not a hand-tuned character exception. If two generated palettes differ in fewer than 12 visible words or 30 total RGB nibbles, the later costume reruns through the stronger unconstrained transfer. Strider MK is the one case that crosses that floor. Every channel still clamps to 0–15, and pen 15 remains the figure's original transparent/tag value.
That distinction matters: the body arena is literal MvC2 RGB data; the select arena is a deterministic, manifested transfer onto Capcom's different artwork. Calling both literal would make the screenshots simpler and the provenance wrong.
The full reel below is also an adversarial test of that transfer. The final gate hashes only palette-responsive pixels inside the large-select-figure ROI, then enforces minimum changed-area and RGB-distance floors. The HUD-free body gate evaluates each pair's own changed pixels, so a different intro pose cannot dilute—or impersonate—a palette change. That second surface caught Mega Man restoring the stock kick palette after the common six-color loader; three owner-aware intro-row guards now preserve his real animation while keeping LP/MP/HP/LK/MK/HK distinct.
Why a correct color can disappear midway through an animation
An animation record chooses poses and tiles. A palette-animation writer is separate: it can replace one 16-word color row while those poses continue. That distinction explains the “right for a frame, then wrong” class of bugs.
Mega Man first passes through the common three-row body loader and receives the correct six-way block. During his intro, three later stock workers repaint the rows from scripts that know only punch and kick:
| stock writer | row it can replace | v1 hook target | v1 source |
|---|---|---|---|
$064F0C | 0 | $3F4258 | $1861C0 + index×$60 + rowOffset |
$07D0C0 | 1 | $3F4294 | $1861C0 + index×$60 + rowOffset |
$064EAA | 2 | $3F421C | $1861C0 + index×$60 + rowOffset |
The wrappers do not skip his animation or freeze a palette. They keep the stock row
timing and destination, bound $277 to 0–5, and change only the row source.
Shadow Lady exposed a second trap. $92FC00 is a shared slot-local dynamic palette
address reached by both Shadow Lady and Mega Man; the address alone does not identify
its owner. The final resolver checks fighter ID before substituting either Mega Man's
six-bank base $1861C0 or Shadow Lady's $186AC0. A separate guard at $06987A
skips Shadow Lady's two-color row-0 overwrite while every other caller follows stock.
Treating “pointer equals $92FC00” as “this must be Shadow Lady” painted Mega Man from
the wrong fighter's arena—an intermediate build the settled-frame body comparison
caught.
The selected-character animation still executes its original pose sequence. Mega Man's match intro still executes all three original row-update moments. The patch changes the palette source chosen at those moments. That is why the reel retains motion and waits for a later gameplay-ready body frame: a correct first upload cannot hide a subsequent stock writer.
MvC2 already stores the six donor banks we need
The current Dreamcast definitions in PalMod expose the selectable banks in storage order:
LP, LK, HP, HK, A1, A2The PalMod author's MvC2 color-editing guide identifies those as the first six character-select choices. Each bank starts with eight rows of sixteen little-endian ARGB4444 words in the character's S2 data. MvC1's body loader needs the first three rows.
That is six defined selectable base palettes per character, not six choices for P1 plus another exposed six for P2. Neither PalMod's selectable-bank map nor the game guide defines colors 7–12; unrelated effect rows are not a mirror-only palette set. A same-character collision can permit, reuse, or redirect among those six choices, but this extraction does not pretend to have live-traced MvC2's exact same-button fallback. This MvC1 patch uses an explicit modulo-six rule instead: a collision advances to the next existing bank.
MvC1 has a six-attack-button cabinet while MvC2 labels the same physical positions as four attacks and two assists. The release mapping follows cabinet position:
| MvC1 target | MvC2 donor bank |
|---|---|
| LP | LP |
| MP | HP |
| HP | A1 |
| LK | LK |
| MK | HK |
| HK | A2 |
So the visible rows are MvC2 LP / HP / A1 across MvC1's punch buttons and MvC2 LK / HK / A2 across its kick buttons.

Yes—the MvC2 colors can be assigned to different buttons
Nothing in the RGB data knows which physical button selected it. The selector returns a target slot 0–5; the builder decides which MvC2 source bank occupies each slot.
PalMod storage order is [LP,LK,HP,HK,A1,A2]. The release's six-entry lookup is:
{
"target_order": ["LP", "MP", "HP", "LK", "MK", "HK"],
"mvc2_storage_order": ["LP", "LK", "HP", "HK", "A1", "A2"],
"cabinet_lut": [0, 2, 4, 1, 3, 5]
}Changing that lookup can apply any one-to-one permutation without changing the 68000 selector. For example, swapping entries 2 and 4 would exchange which physical button gets A1 and HK. The builder could technically repeat a source bank too, although the release deliberately uses all six exactly once. This is a build-time assignment, not a new in-game configuration menu.
The RGB conversion is exact nibble copying, not quantization


Early planning assumed Dreamcast colors might be RGB565 and need downscaling. The
bytes disproved that too. MvC2 stores ARGB4444. MvC1 expects 0RGB, also four bits per
channel. Conversion is simply:
source ARGB4444: AAAA RRRR GGGG BBBB
target 0RGB4444: 0000 RRRR GGGG BBBB
^^^^ discard alpha; copy RGB nibbles exactlyThe measured pen bridge is equally specific:
MvC1 rows 0..2, pens 0..14 <- MvC2 rows 0..2, pens 1..15
MvC1 rows 0..2, pen 15 <- preserved target row tagWhy the one-pen shift? MvC2 uses source pen 0 as transparent; MvC1 reserves destination pen 15 for its row tag/transparent hardware pen. Across the selected donor data, source pen 0 has alpha 0 and visible pens 1–15 have alpha F.
This bridge has a strong control: for 14 of the 16 stock returning fighters, MvC2 LP matches all 45 visible RGB words in MvC1 after mapping. Ryu and Chun-Li are the two named exceptions. I keep those differences visible in the audit instead of guessing why they differ or silently normalizing them.
Rebuild one fighter's donor blocks by hand
Use your own Dreamcast extraction and open Ryu's PL00_DAT.BIN. These are the six
source ranges; each row is $20 bytes and the three listed rows form the $60 bytes
MvC1 needs:
| MvC1 button | MvC2 bank | three source-row offsets | expected CPU-order output SHA-256 |
|---|---|---|---|
| LP | LP | $6E440/$6E460/$6E480 | 75ed3088c037783a68319efda6206da96fce48c7ede6594e4ed2e8fdbe027323 |
| MP | HP | $6E640/$6E660/$6E680 | bc7bbb2410be87e7b247e27b85df7028d5dbe8ab6352b12e72fa72fac57b8bdf |
| HP | A1 | $6E840/$6E860/$6E880 | 1c43faa7d0bcf21c862c710647b1e30fce06cec9be4d0f13788eb10d0a5c167c |
| LK | LK | $6E540/$6E560/$6E580 | 0b0901da492e8dfd8771758c68233c73509521912381b8fdd19097a27990302f |
| MK | HK | $6E740/$6E760/$6E780 | f0d2fb0dee07db6e4634e923979e920314a46729621753f8cd8ecb4cd45f9dfe |
| HK | A2 | $6E940/$6E960/$6E980 | 93235f97998357cfcaa9d2e49cb069933329cbbf6f5a682a63208947305c2b3c |
For example, xxd -g 2 -e -s 0x6e440 -l 0x60 PL00_DAT.BIN exposes the three LP
rows as little-endian 16-bit words. For each row, discard source pen 0, move source
pens 1–15 into MvC1 destination pens 0–14, clear the alpha nibble, and retain the
corresponding MvC1 row's pen 15 tag. Concatenating the six CPU-order blocks in the table
produces Ryu's $240-byte group with SHA-256
9efc9098dff6846549d5b68f40ba77dc14156fe04843c3d8d38cbbd2c11c65dd.
The group begins at CPU $185200; add index × $60 to reach any button. Reverse each
byte pair only when writing it into the word-swapped mvc.06a member.
Every target has a donor, but six targets are proxies
Seventeen targets have a direct MvC2 counterpart:
| direct stock returners | direct custom target |
|---|---|
| War Machine, Captain America, Hulk, Wolverine, Gambit, Venom, Spider-Man, Roll, Ryu, Captain Commando, Chun-Li, Jin Saotome, Zangief, Strider Hiryu, Morrigan, Mega Man | Shuma-Gorath |
Shuma is direct by identity, but its custom MvC1 target sits outside the valid vanilla 192-byte fighter blocks, so its target validation remains separate.
Six MvC1 variants do not exist as matching MvC2 identities. Their manifest rows say exactly which base fighter supplies the literal RGB words:
| MvC1 target | MvC2 source | provenance label |
|---|---|---|
| Red Venom | Venom | derived-proxy |
| Orange Hulk | Hulk | derived-proxy |
| Gold War Machine | War Machine | derived-proxy |
| Shadow Lady | Chun-Li | derived-proxy |
| Lilith-mode Morrigan | Morrigan | derived-proxy |
| Dark Sakura | Sakura | derived-proxy |
Those are not presented as official MvC2 Red Venom or Shadow Lady palettes. They are literal MvC2 base-form donor banks applied to named MvC1 variants. There are no unlabelled generated fillers and no missing roster rows.
No ROM expansion is arithmetic plus an enforced invariant
One body color is $60 bytes. Six colors are $240 bytes per fighter. For 23 targets:
23 fighters × 6 colors × $60 bytes = 13,248 bytes = $33C0The palette member mvc.06a already contains a 16,774-byte $FF run from $183E7A
to $188000 (exclusive). Aligning the arena to $184000 leaves exactly 16,384 bytes.
A 13,248-byte arena ends at $1873C0, leaving 3,136 bytes in the same stock member.
The corrected release uses two existing caves inside mvc.10: a 744-byte code payload
at CPU $3F4000 and the large-select-figure arena at $3F5000:
21 vanilla fighters × 6 previews × $60 bytes = 12,096 bytes = $2F40
$3F5000 + $2F40 = $3F7F40Both fit inside the stock 512 KiB member. No new member is added, no member gets longer, and no address beyond the stock program map is introduced.
- 1stock data before aligned cave$180000–$184000 · 16 KB
- 213,248-byte six-color arena$184000–$1873C0 · 12.9 KB23 × 6 × $60; every byte had a stock $FF preimage
- 33,136 aligned cave bytes remain$1873C0–$188000 · 3.1 KB
- 4stock data after cave$188000–$200000 · 480 KB
- Afirst widened fighter-directory row$1C726C · markerdirectory entries point back into the arena
mvc.06a is $80000 bytes before the patch and $80000 bytes after it. The picture's
endpoints do not move.
The arena consumes $33C0 bytes from an aligned $4000-byte part of a measured $FF
run. $0C40 bytes remain untouched after it.
The fighter directory at $1C726C is rewritten so each configured ID's base pointer lands
on its six contiguous blocks. That changes pointers and contents, not member length.
Acceptance opens the result again: 21 members, original member names, every member's
original byte length, exactly four changed member hashes, and romExpansion=false.
The patched members will have different CRCs and SHA-256 hashes. “No ROM expansion”
means every archive member keeps its original byte length. The builder checks that
invariant and records romExpansion: false; it does not pretend the output is a stock
ROM.
The final acceptance repeated the size proof after integrating the 13,248-byte body
arena, 12,096-byte large-select-figure arena, and all 23 body-directory pointers: 21 members
entered, 21 same-size members left, and only mvc.06a, mvc.10, mvce.03a, and
mvce.04a changed. Six live Ryu body runs prove the body path. The 126-run select
census proves the figure arena is not merely writable: every fighter/button pair reads
the expected address and uploads all 48 words without a mismatch.
The automated census does not trust a free-floating PASS line. Its receipt is bound to
the exact patched-ROM hash, the exact run definition, raw source/destination observations,
all 126 select/body frame pairs, foreground-separation metrics, and final video hash.
Changing any input makes the old evidence stale. The manual procedure below reaches the
same four decisive stops without requiring that automation.
Reproduce the patch in Ghidra, MAME, and a hex editor
This section is intentionally redundant with the story above. It turns the findings into a clean-room checklist another reverser can repeat with legally obtained MvC1 and MvC2 media. You can audit the shipped patch without rebuilding the derived artwork, or continue farther and reconstruct Ryu's donor blocks using the offsets already published.
A color index is the value 0–5 chosen by a button. A palette row is sixteen
16-bit words. A fighter body color is three rows, so one palette block is
3 × 16 × 2 = 96 = $60 bytes. An arena is verified unused space inside an existing
ROM member. A directory is the pointer table that tells a loader where a fighter's
palette block begins.
For each visible change, ask: Which physical input bit changed? Which byte stored the choice? Which routine consumed it? Which ROM address became the source? Which palette-RAM range received it? Which pixels changed on screen? The steps below keep all six answers connected.
1. Lock the exact clean parent before naming an address
The reference input is the dat-official 21-member Euro parent mvsc.zip, whole-archive
SHA-256:
6f63627cc37c554f74e8bf07b21730fa7f85511c7d5d07449850be98dde91da8These are the four members the release eventually changes:
| member | size | clean SHA-256 |
|---|---|---|
mvce.03a | $80000 | 48128b6f98c5171a0e734e99f01a273f526f2da74f08635732068c60c891d757 |
mvce.04a | $80000 | 71ded7040f279d0ddea1326fc062465159ccd832a327230098e5740d2a747d67 |
mvc.06a | $80000 | 132316e99d02d197cd8b4b9227dccd9215f5f741bcd21d5abf5c3f07fb64354b |
mvc.10 | $80000 | e24119140931bc861bf791758af8eb375892260d8ffb633369ebedcbe21f172a |
If one member hash differs, stop. Do not search for a similar-looking six-byte sequence and paste a jump over it. A wrong parent may boot and still fail only when a specific fighter, slot, or intro writer reaches the overwritten instruction. The installer refuses that ambiguity before its first write.
2. Build the same address space in Ghidra
- Preserve the raw ZIP and hashes above.
- Word-unswap
mvce.03aandmvce.04a, place them at CPU$000000and$080000, and apply the CPS-2 Euro-parent key schedule from MAME'scps2crypt.cppusing themvsc.keymember from your own archive. - The clean decrypted one-megabyte opcode image must hash to
c3e8fdff391d8666f231b6bde175b110b99e2e2da531eeeaea3189e38ec8910a. Its first two longwords are initial SP$00FF245Cand initial PC$000000E2. - Import that image as Motorola 68000, big-endian, 32-bit address space, image base
$000000. - Add the word-unswapped data members as memory blocks:
mvc.06aat CPU$180000andmvc.10at CPU$380000. - Label these landmarks before following any cross-reference:
| label | CPU address | clean observation |
|---|---|---|
select_color | $00C426 | loads P1/P2 held word, reduces it to D1=0/1 |
color_store | $00C464 | move.b d1,$277(a0) |
body_reader | $00A4B0 | source in A0, destination in A1, boolean at $00A4BC |
selected_animation | $07AF36 | resolves directory, boolean at $07AF6C |
large_select_figure | $0B7690 | loads stock side-figure source $1D2A24+d0 |
fighter_palette_directory | $1C7268 | one longword pointer per even fighter ID |
six_color_code | $3F4000 | clean $FF; patched 744-byte payload |
six_color_body_arena | $184000 | clean $FF; patched 23 × 6 × $60 |
six_color_select_arena | $3F5000 | clean $FF; patched 21 × 6 × $60 |
Ghidra and MAME can appear to disagree in the encrypted low megabyte. Ghidra's
decrypted image and MAME's disassembly show the opcode-fetch view. A hex editor shows
the encrypted bytes physically stored in .03a/.04a. Data tables and the high program
members are word-swapped on disk but are not CPS-2 opcode-encrypted.
3. Follow the stock byte before looking at the patch
In Ghidra, start at $00C426, not in the new cave:
- Rename
$24(a6)and$26(a6)as the P1/P2 held-button words. - At
$00C436, decode0242 0700asandi.w #$0700,d2. - At
$00C43C, note that7201is the only nonzero assignment: stock can emit only 0 or 1. - Follow
$00C464to the fighter object's$277color byte. - Follow
$277to$00A4BC. Thetst.bplus onelea $60(a0),a0proves the match body also interprets every nonzero value as bank 1. - Follow the directory load at
$07AF48to$07AF6C. That secondtst.bproves the selected animation repeats the collapse independently. - Inspect
$0B7690. Its absolute source$1D2A24proves the large side figure is a third data path, not another call to the body uploader.
This order matters. If you begin at $3F4000, the payload looks plausible but you have
not proved why each hook exists or which consumer was intentionally left stock.
4. Make MAME print the runtime chain
Run the patched output from its own ROM directory:
mame mvsc -rompath "C:\path\to\your\patched-roms;C:\path\to\mame-roms" -debug -window -skip_gameinfoWith the main 68000 selected in the debugger, these action breakpoints log the four stages and immediately continue:
bp 00c468,,{ printf "SELECT d2=%04X index=%X stored=%X\n",d2,d1,b@(a0+277); g }
bp 00a4c6,,{ printf "BODY id=%X index=%X src=%08X dst=%08X\n",w@(a6+52),b@(a6+277),a0,a1; g }
bp 07af74,,{ printf "ANIM index=%X src=%08X\n",b@a1,a0; g }
bp 0b7698,,{ printf "SIDE id=%X src=%08X\n",w@(a6+52),a0; g }Set a start and stop breakpoint around the stock selector, run to the first one, then turn on a bounded trace with the live data and address registers prefixed to every line:
bp 00c426
bp 00c468
gAfter $00C426 stops:
trace six-color-select.tr,,noloop,{tracelog "D0=%08X D1=%08X D2=%08X A0=%08X A1=%08X A6=%08X ",d0,d1,d2,a0,a1,a6}
gAt $00C468, enter trace off before continuing. The resulting
six-color-select.tr is the instruction-by-instruction proof between input load and
stored byte. Repeat with boundaries $00A4B0..$00A4C6 for the body reader,
$07AF36..$07AF74 for the selected animation, and $0B7690..$0B7698 for the large
figure. Use traceover with the same arguments if you intentionally want to skip
subroutine bodies; use trace when the question is which callee changed D1, A0, or
A1.
Choose Ryu with one attack button, reset with F3, and repeat. The expected source addresses are:
| button | stored index | body + selected-animation source | large-figure source |
|---|---|---|---|
| LP | 0 | $185200 | $3F6200 |
| MP | 1 | $185260 | $3F6260 |
| HP | 2 | $1852C0 | $3F62C0 |
| LK | 3 | $185320 | $3F6320 |
| MK | 4 | $185380 | $3F6380 |
| HK | 5 | $1853E0 | $3F63E0 |
The values advance by exactly $60; no visual judgement is involved. For raw writes,
set one watchpoint at a time because the ranges overlap across UI phases:
wp 914000,60,w
wp 914040,40,w
wp 914180,60,wThose are the point-fighter body, two-row selected-animation, and large-side-figure destinations used by the control run. After the relevant writer stops, MAME can save each range directly:
save body-pal.bin,914000,60
save select-animation-pal.bin,914040,40
save large-figure-pal.bin,914180,60For body and animation comparisons, ignore each word's high nibble: stock code preserves
live priority/brightness there and copies the lower RGB nibbles from ROM. The release
gate compares exactly those 48 body words, 32 animation words, and 48 side-figure words.
5. Translate CPU addresses into raw member offsets
Use this map before opening a hex editor:
CPU $000000..$07FFFF -> mvce.03a + CPU address
CPU $080000..$0FFFFF -> mvce.04a + (CPU address - $080000)
CPU $180000..$1FFFFF -> mvc.06a + (CPU address - $180000)
CPU $380000..$3FFFFF -> mvc.10 + (CPU address - $380000)These four primary hooks show the distinction between meaningful decrypted 68000 and encrypted bytes on disk:
The complete 17-hook raw ledger is below. Every entry is exactly six bytes long; every
patched low-ROM entry decrypts to an absolute JMP to the listed payload target.
| purpose | raw member + offset | payload target | clean raw bytes | patched raw bytes |
|---|---|---|---|---|
| six-button decode | mvce.03a + $00C434 | $3F4000 | ebd9765671ff | f1276db0719f |
| duplicate modulo six | mvce.03a + $00C440 | $3F4038 | 0ef18564e99d | a15762f7a171 |
| body reader A6 | mvce.03a + $00A4BC | $3F404C | 60753f547b95 | 3961c328b0f0 |
| body reader P1 slot A | mvce.03a + $00A5B2 | $3F405A | 67fb5daccaac | 70bdd1f958d5 |
| body reader P1 slot B | mvce.03a + $00A62E | $3F4068 | 514b658c75c6 | c5b063fea521 |
| body reader P1 copy A | mvce.03a + $00A682 | $3F40A2 | df942762045e | 2613d3509bef |
| body reader P1 copy B | mvce.03a + $00A6C2 | $3F40B0 | 2f02ba9ab95f | 2288c6c8f005 |
| body reader P2 slot A | mvce.03a + $00A7B2 | $3F4076 | 62d1c2bb0f78 | a80bb5e8d32d |
| body reader P2 slot B | mvce.03a + $00A82E | $3F4084 | d396708885bd | 2b1720145f7c |
| body reader P2 copy A | mvce.03a + $00A882 | $3F40BE | 4f581520a74f | 4f5ebcf01dae |
| body reader P2 copy B | mvce.03a + $00A8C0 | $3F40CC | 70048b4ed4ed | 388920215904 |
| Mega Man intro row 2 | mvce.03a + $064EAA | $3F421C | 0ed5e63cd579 | f4fc120c584f |
| Mega Man intro row 0 | mvce.03a + $064F0C | $3F4258 | f97c1685e04f | fc0e73756bd1 |
| Shadow Lady row guard | mvce.03a + $06987A | $3F41A2 | 2207ec1c56c0 | 40e7f7b19308 |
| selected animation | mvce.03a + $07AF6C | $3F42D0 | 6ee505c4a592 | 911551a5da56 |
| Mega Man intro row 1 | mvce.03a + $07D0C0 | $3F4294 | 864efdb0a015 | 363e5c1d0c1c |
| large select-side figure | mvce.04a + $037690 | $3F40F0 | f2f1ee8bca79 | 872b24e0e8cb |
The data edits remain legible in a raw member view, but each 16-bit word is byte-swapped:
| raw member location | clean first bytes | patched first bytes | meaning |
|---|---|---|---|
mvc.06a + $004000 | ffffffffffffffffffffffffffffffff | 0000ff0fab0b77084506340423031101 | start of War Machine's body arena |
mvc.06a + $04728C | 1c00c487 | 18000052 | Ryu directory pointer, CPU $1C87C4 → $185200 |
mvc.10 + $074000 | ffffffffffffffffffffffffffffffff | 0072020804002a660172020805002266 | first 16 on-disk bytes of the 744-byte code payload |
mvc.10 + $075000 | ffffffffffffffffffffffffffffffff | e00fee0edd0dcc0caa0a990988087707 | first 16 on-disk bytes of the select-figure arena |
Do not paste 4E F9 ... from Ghidra into .03a/.04a: those members require the
encrypted after-bytes in the ledger. Do not paste CPU-order data into mvc.06a or
mvc.10 without swapping each byte pair.
6. Assemble every line of the 744-byte payload
The patch-only bundle includes the complete
six_color.s—selector, duplicate policy, all
nine body readers, Shadow Lady ownership guard, all three Mega Man intro-row guards,
large-side-figure reader, and selected-animation reader. It is not a partial listing.
m68k-elf-as -m68000 -o six_color.o six_color.s
m68k-elf-ld -Ttext=0x3F4000 -e selector_decode -o six_color.elf six_color.o
m68k-elf-objcopy -O binary -j .text six_color.elf six_color.bin
m68k-elf-nm -n six_color.elfExpected results:
source SHA-256 8cbcdb5755ce60f19849d6ffd39fbee163be347ae33d02486fd9ca3e24212fb8
binary size 744 bytes
binary SHA-256 a58ce8b820d5bf486f560f02c02f454ba0985b74d69bd769035199576a0da7f3The symbols that must not drift are selector_decode=$3F4000,
duplicate_next_color=$3F4038, the nine body entries from $3F404C through
$3F40CC, select_sideart_six_color=$3F40F0,
shadow_palette_guard=$3F41A2, the Mega Man guards at
$3F421C/$3F4258/$3F4294, and select_animation_six_color=$3F42D0.
Word-swap the assembled binary before placing it at mvc.10 + $074000.
7. Close the audit with deterministic member hashes
After applying all hooks, arenas, and directory entries, exactly four member hashes change:
| member | clean SHA-256 | patched SHA-256 |
|---|---|---|
mvce.03a | 48128b6f98c5171a0e734e99f01a273f526f2da74f08635732068c60c891d757 | d69f8b5c05d745d899377cddbb9ccd4b39af698ec8cf565db6cc634a7a97de2b |
mvce.04a | 71ded7040f279d0ddea1326fc062465159ccd832a327230098e5740d2a747d67 | d7aa82828984f939b09569c79d62d77b65a05f1af9f7a1a2c8179f17ff13b37d |
mvc.06a | 132316e99d02d197cd8b4b9227dccd9215f5f741bcd21d5abf5c3f07fb64354b | be22d6cf23d84a12e3520e6c344dd974141c7b730aa58a1295ffa6df1eddcc0a |
mvc.10 | e24119140931bc861bf791758af8eb375892260d8ffb633369ebedcbe21f172a | f21f3a8881734f844350e9063b0ef8164cdd6edbafd7fb68c37b08d0b7980969 |
The other seventeen members must remain byte-identical. Container order can differ, so member hashes—not a casually repacked outer ZIP hash—are the portable result.
- Clean member mismatch: wrong region, wrong revision, merged set, or an earlier mod.
- Decrypted-megabyte mismatch: wrong key, decrypt range, concatenation order, or word swap.
D1reaches 0–5 butA0only chooses base/base+$60: an unpatched consumer remains.- Source pointer is correct but the saved RAM words differ: check slot, phase, and the live high nibble before blaming the ROM block.
- A fifth member changes or any member grows: the no-expansion build invariant failed.
Each failure narrows the problem. None should be “fixed” by weakening a hash check.
How a 118 KiB CMD changes the whole roster
The installer is small because it is a delta and verifier, not a game image. It contains 21 clean member identities and 21 exact patch records. Those records write 26,286 bytes total:
744 bytes is therefore the code payload, not the entire color data. It can do so much
because MvC already owns the input word, $277 state byte, palette upload loops, sprite
art, animation state machines, and palette RAM. The patch changes which $60-byte
source block those systems read. It does not add six copies of every sprite or a new
renderer.
The CMD is a Batch/PowerShell polyglot:
- The small Batch header finds the marker inside its own file and launches the embedded PowerShell.
- PowerShell opens the supplied ZIP in memory and requires exactly the 21 known members.
- It checks each member's case-sensitive name, length, and SHA-256.
- Before every copy, it checks the exact local preimage again. A failed preimage means nothing has been written.
- It copies same-length after-bytes over those 21 spans; no insertion operation exists.
- It hashes every resulting member against the four patched identities above.
- It writes a temporary ZIP, reopens it, rechecks member order, sizes, and hashes, then renames it and emits a receipt. The source ZIP is never opened for writing.
Most of the CMD's text is hex for the large before/after spans. The large preimages are
verified $FF caves; the after-images are compact RGB444 tables. That is why one
roughly 118 KiB text file can safely transform a multi-megabyte archive without
containing or downloading a complete Capcom ROM.
The supply-chain rule comes from the same place as the technical one. The Reproducible Builds paper distinguishes trusting source from trusting the executable artifact. Here, the practical bridge is the clean member identity, exact preimage, deterministic after-image, patched member hash, and install receipt. The paper motivates that chain; the published values let another person test it.
Release record
The standalone v1 patch is linked at the top and remains available as the historical single-feature release. Players who want this feature together with the combined gameplay work can use DHC v5. These are the sealed acceptance records for technical readers auditing the standalone download:
| item | SHA-256 / value |
|---|---|
clean mvsc.zip input | 6f63627cc37c554f74e8bf07b21730fa7f85511c7d5d07449850be98dde91da8 |
| 13,248-byte body arena | 833d74c203d7f2e90eb34e210e51a3b0623d38733b83135732a4dab44b6900e2 |
| 12,096-byte large-select-figure arena | 79879820f9bce737e5ad678ca3f0c4575b03a4c10e69317fed3ecbc54d77852c |
| 744-byte payload | a58ce8b820d5bf486f560f02c02f454ba0985b74d69bd769035199576a0da7f3 |
complete six_color.s source | 8cbcdb5755ce60f19849d6ffd39fbee163be347ae33d02486fd9ca3e24212fb8 |
| Windows installer CMD | e1e407dc5b61788bf6e8be75a3dd0c4c23a0a992d143d72cae4648dccb6f50cd |
| Public patch-only bundle | a54f822f188740d5c828ab48c6d4375ac8cc13f93b8c8676e8a8a6189d61a01b |
| 126-selection 1080p60 master | 7803da179519609ec1a5f57b91a12b7337699f8d1c2a892cb98cfa669dc520b4 |
| Published evidence reel | YouTube OrfUnVK_Ezk |
| ROM expansion | false |
The release applies reproducibility at the member level: fixed clean member contents produce deterministic patched member contents. The outer ZIP hash is pinned only for the canonical input archive; the member hashes are the portable contract.
What remains stock and what remains open
N41 reported a one-frame select-screen palette/character ordering mismatch. I have not captured the two writes and the frame between them, so this post does not call it reproduced, fixed, or caused by this patch. It remains an explicitly credited trace to run.
The body-palette, confirmed-animation, large select-side-figure, and installer features are complete. The remaining work is follow-up, not a hidden release gate:
- The confirmed-selection animation and large select-side figure are six-way. Small select cells, VS portraits, HUD headshots, nameplates, win art, and separately loaded effect palettes stay stock.
- The automatic/demo/preset
$00C46Apath retains its stock binary duplicate policy; ordinary human two-player duplicate selection is live-verified modulo six. - The release has static body verification for all 23 configured IDs, static preview verification for 21 vanilla IDs, and 126 physical-button runs with exact live uploads for the animated selection, large select-side figure, and match body. It does not claim 264 visual playthroughs.
- Vanilla PalMod definitions still point at the old two-bank blocks, so they do not
edit the relocated body data. A future DEF addendum can expose the new
$184000arena from the same six-bank provenance table; the ROM patch itself does not require PalMod. - N41's frame-order report remains the next timing trace: capture both writes and the intervening frame before assigning a cause.
The method here is iterative on purpose: static analysis finds the candidate path, dynamic observation names the real inputs, and controlled intervention tests values the stock game never emits. The arXiv reverse-engineering survey below gives that workflow a useful name; the ROM listings and receipts—not the paper—prove these addresses.
References
- PalMod source repository — the current MvC2 Dreamcast bank definitions and labels used as the offset oracle.
- PalMod author's MvC2 character-color guide — primary documentation that the first six choices are LP, LK, HP, HK, A1, and A2.
- Reproducible Builds: Increasing the Integrity of Software Supply Chains (arXiv:2104.06020) — context for fixed inputs, deterministic artifacts, and published hashes.
- SoK: AI-Augmented Binary Reversing (arXiv:2606.17398) — context for iterating among static analysis, dynamic observation, and controlled intervention.
I deliberately do not cite image-quantization or learned palette-transfer papers here. The body pipeline copies RGB nibbles exactly. The preview pipeline is the explicit stock-body semantic target-hue/affine algorithm documented above—not a learned model or an LCh method—and its stable-warm assignments, fallback decisions, outputs, and clipping counts are manifested.