Goal: Add Armored Spider-Man (character ID 0x2E) to MvC1 through direct ROM patching -- new palette, new input code, new load function -- no source code required.
Constraints: CPS-2 program code is hardware-encrypted (using Phoenix Edition to bypass). Character system spans 5+ subsystems with no unified API. Secret characters use 3 different table formats. Armor implementation varies (flag-based vs animation-based).
Approach: Reverse engineer existing secret character implementations. Copy the simplest pattern (Gold War Machine: 12 bytes). Design RGB444 palette. Add entries to all 3 table types. Patch position check assembly in expanded ROM space.
Result: ROM expanded to 148MB (CPS-2 max). All 4 existing secret load functions decompiled. Position check blocker bypassed via Autovector 2 interrupt hook -- 116 bytes of 68000 assembly at $0F1FA0 intercept VBlank to detect Wolverine (ID $08) + Down → write Spider-Man (ID $0E) to $FF3053. Safety: 60-frame char-change window (char select only) + input edge detection. ROM built and verified.
Proof / Validation: Assembled hook verified via Capstone disassembly: CMPI.B #$08, D0 → BTST #2, D1 → MOVE.B #$0E, $FF3053. Original handler at $054A preserved. ULTRA SETTINGS: FN REDVENOM: OK, FN ORNGHULK: OK, FN GOLDW.M.: OK, FN SHDWLADY: OK. P PALETTE: OK, K PALETTE: OK. Build: 145MB ROM set output.
Repo / Artifacts: CHARACTER_SELECT/scripts/patch_rom_check_complete.py (unified build with VBlank hook), CHARACTER_SELECT/scripts/armored_spiderman.lua (Lua prototype), DECOMP_MVC/src/character/ (decompiled functions).
The binary analysis and injection techniques below stand as documented. The plan for the $2E slot has changed: the Armored Spider-Man / Iron Spider line was retired, and the slot's character work pivoted to a Dark Sakura port from MSHvSF (char-id $2A in that game). Because MSHvSF and MvC1 are same-generation engines, her animation/frame tables carry over byte-identical -- the port is table porting plus tile bank remapping, not new art or new movesets.
Injecting new runtime behaviors into legacy binaries requires reverse engineering their finite state machines, memory layouts, and entity management systems. This deep dive documents the process of adding "Armored Spider-Man" (character ID 0x2E) to Marvel vs. Capcom 1 through direct ROM patching -- no source code, no emulator cheats, pure binary modification.
Key Engineering Concepts:
- Binary state machine analysis and modification
- Memory-mapped I/O and entity ID systems
- 68000 assembly-level patching for behavior injection
- CPS-2 palette format (RGB555) and resource management
- ROM expansion to CPS-2 theoretical maximums (128MB GFX, 16MB Audio)
Character Select Screen Layout
Visual Grid Layout
Grid Navigation:
- 18 selectable characters in a 6×3 grid
- 2 random select slots
- 6 secret characters accessible via directional input codes
- P1 starts left side, P2 starts right side
Character ID Table (Internal)
| Character | ID (Hex) | Type |
|---|---|---|
| War Machine | 0x02 | Normal |
| Captain America | 0x04 | Normal |
| Hulk | 0x06 | Normal |
| Wolverine | 0x08 | Normal |
| Gambit | 0x0A | Normal |
| Venom | 0x0C | Normal |
| Spider-Man | 0x0E | Normal |
| Roll | 0x10 | Secret (unique) |
| Ryu | 0x12 | Normal |
| Capt Commando | 0x14 | Normal |
| Chun-Li | 0x16 | Normal |
| Jin | 0x18 | Normal |
| Zangief | 0x1A | Normal |
| Strider | 0x1C | Normal |
| Morrigan | 0x1E | Normal |
| Megaman | 0x20 | Normal |
| Red Venom | 0x24 | Secret |
| Orange Hulk | 0x26 | Secret |
| Gold War Machine | 0x28 | Secret |
| Shadow Lady | 0x2A | Secret |
| Lilith | 0x2C | Secret |
| Armored Spider-Man | 0x2E | Target |
Secret Character Codes Reference
Existing Secret Character Codes (All Arrow-Only Pattern)
| Character | Starting Position | Input Sequence |
|---|---|---|
| Roll | Zangief | Left, Left, Down, Down, Right, Right, Down, Down, Left, Left, Up, Right, Up, Up, Right, Right |
| Lilith | Zangief | Left, Left, Down, Down, Right, Right, Down, Down, Left, Left, Up, Up, Right, Up, Up, Right, Right |
| Gold War Machine | Zangief | Left, Left, Down, Down, Right, Right, Down, Down, Left, Left, Up, Up, Up, Right, Right |
| Orange Hulk | Zangief | Left, Down, Down, Right, Right, Down, Down, Left, Left, Up, Up, Up, Up, Right |
| Shadow Lady | Zangief | Left, Left, Down, Down, Right, Right, Down, Down, Left, Left, Up, Up, Up, Up, Right, Down |
| Red Venom | Zangief | Left, Down, Down, Right, Right, Down, Down, Left, Left, Up, Up, Up, Right, Right |
Proposed Armored Spider-Man Code
Starting Position: Spider-Man (directly on Spider-Man's portrait)
Input Sequence (Arrow-Only, Similar to Roll):
Down, Down, Left, Left, Up, Up, Right, Right, Down, DownThis is a 10-input sequence (shorter than Roll's 16-input sequence) that forms a "box" pattern, easy to remember.
Alternative (Longer, More Secure):
Down, Down, Left, Left, Up, Up, Right, Right, Down, Left, Up, RightCharacter ID Mapping
Complete Character ID Table
Palette Swap Relationships
| Secret Character ID | Base Character ID | Base Character Name |
|---|---|---|
| 0x24 | 0x0C | Venom |
| 0x26 | 0x06 | Hulk |
| 0x28 | 0x02 | War Machine |
| 0x2A | 0x16 | Chun-Li (!) |
| 0x2C | 0x1E | Morrigan |
| 0x2E | 0x0E | Spider-Man |
Phase 1: ROM Structure and Decryption
CPS2 ROM File Structure
Decryption/Encryption Process
# Step 1: Extract ROM files from ZIP
unzip mvscj.zip -d mvscj_extracted/
# Step 2: Decrypt program ROM using cps2crypt
# (cps2crypt is part of MAME tools)
./cps2crypt -d mvscj_extracted/mvscj.03 mvscj.03.dec mvsc.key
# Step 3: Make modifications to mvscj.03.dec
# Step 4: Re-encrypt the modified ROM
./cps2crypt -e mvscj.03.dec mvscj.03.mod mvsc.key
# Step 5: Replace original file
cp mvscj.03.mod mvscj_extracted/mvscj.03Phase 2: Character Table Analysis
Character Entry Structure (from Decompilation)
The character data table lives at $065CCA in the program ROM with 32-byte entries. The player runtime structure (in Work RAM) has been partially mapped:
Memory Address Map (Confirmed via Decompilation)
=== CPS2 Memory Map (68000 CPU) - CONFIRMED ===
ROM Space:
0x000000 - 0x0FFFFF : Program ROM (1MB used, 4MB max)
Hardware Registers:
0x400000 - 0x40000F : CPS-B registers (layer ctrl, priority, palette)
0x660000 - 0x66BFFF : Video RAM (48KB, scroll layers)
0x700000 - 0x701FFF : Object RAM bank 1 (8KB, 1024 sprites)
0x708000 - 0x709FFF : Object RAM bank 2 (8KB, double-buffered)
0x800000 - 0x800007 : Input ports (P1/P2 joy+buttons)
0x900000 - 0x92FFFF : Palette/GFX RAM (192KB)
RAM Space:
0xFF0000 - 0xFFFFFF : Work RAM (64KB)
Player Structure Bases:
0xFF3000 : P1 Character (char ID at +0x53 = $FF3053)
0xFF3100 : P2 Character (char ID at +0x53 = $FF3153)
Unlock System (base = A5 = $FF8000):
0xFF803C : Red Venom unlock flag
0xFF8040 : Orange Hulk unlock flag
0xFF8044 : Gold War Machine unlock flag
0xFF8048 : Shadow Lady unlock flag
0xFF804C : Lilith unlock flag
Cursor Position:
0xFF2000 : P1 cursor grid position
0xFF2002 : P2 cursor grid position
=== ROM Data Tables (CONFIRMED) ===
0x065CCA : Character data table (32 bytes/entry)
0x00BD00 : Animation/sprite data (~512 bytes)
0x014D6E : Secret table Type A (Shadow Lady, 16-byte entries)
0x04A3F2 : Secret table Type B (Lilith/Gold WM, 2-byte pairs)
0x024D3A : Secret table Type C (Orange Hulk/Red Venom, 2-byte pairs)
=== Secret Character Load Functions (CONFIRMED) ===
0x022B2E : load_red_venom (66 bytes)
0x05D044 : load_orange_hulk (60 bytes)
0x07EF90 : load_gold_war_machine (12 bytes)
0x0A26C8 : load_shadow_lady (20 bytes)
0x0FF9B4 : character_load_main (98 bytes)
=== Spider-Man Palette Offsets (in mvc.06a) ===
0x48642 : P button palette (32 bytes)
0x486A2 : K button palette (32 bytes)
0x48682 : Web attack P palette (32 bytes)
0x486E2 : Web attack K palette (32 bytes)Phase 3: Input Code Implementation
Input Detection State Machine
68000 Assembly Pseudo-Code
; ============================================
; Armored Spider-Man Selection Code Handler
; Insert into character select input routine
; ============================================
ARMORED_CODE_CHECK:
; Check if cursor is on Spider-Man (position 0x0F)
CMP.B #$0F, D0 ; D0 = current cursor position
BNE .skip_armored ; Not on Spider-Man, skip
; Check input sequence buffer
LEA input_buffer, A0 ; A0 = input history buffer
LEA armored_code, A1 ; A1 = expected sequence
MOVEQ #9, D1 ; 10 inputs to check (0-9)
.check_loop:
MOVE.B (A0)+, D2 ; Get buffered input
CMP.B (A1)+, D2 ; Compare with expected
BNE .skip_armored ; Mismatch, skip
DBRA D1, .check_loop ; Continue checking
; Code matched! Select Armored Spider-Man
MOVE.B #$2E, D0 ; Set character ID to 0x2E
MOVE.B #$01, armored_flag ; Set flag for palette load
BRA .select_character
.skip_armored:
; Continue with normal selection
RTS
; Expected input sequence (Down=2, Left=4, Up=8, Right=6)
armored_code:
DC.B $02, $02 ; Down, Down
DC.B $04, $04 ; Left, Left
DC.B $08, $08 ; Up, Up
DC.B $06, $06 ; Right, Right
DC.B $02, $02 ; Down, DownPhase 4: Palette Creation
CPS2 Palette Format (RGB444)
CPS-2 palettes use RGB444 big-endian format, NOT RGB555 as commonly assumed:
Spider-Man has 4 separate palettes in mvc.06a: P button, K button, Web P, and Web K -- each 32 bytes (16 colors).
Armored Spider-Man Color Scheme
| Index | Original (Spider-Man) | Armored Version | RGB444 Value |
|---|---|---|---|
| 0 | Transparent | Transparent | $0000 |
| 1 | Red (Main) | Silver | $0999 |
| 2 | Red (Light) | Light Silver | $0BBB |
| 3 | Red (Dark) | Dark Silver | $0666 |
| 4 | Blue (Main) | Gunmetal | $0444 |
| 5 | Blue (Light) | Light Gunmetal | $0777 |
| 6 | Blue (Dark) | Dark Gunmetal | $0333 |
| 7 | Black (Outline) | Black | $0000 |
| 8 | White (Eyes) | Yellow (Visor) | $0FF0 |
| 9 | Skin | Skin | $0DA8 |
| 10 | Web (Gray) | Chrome | $0DDD |
Palette Binary Data
# Armored Spider-Man Palette (32 bytes, RGB444 big-endian)
ARMORED_PALETTE_P = bytes([
0x00, 0x00, # 0: Transparent
0x09, 0x99, # 1: Silver (main body)
0x0B, 0xBB, # 2: Light Silver (highlights)
0x06, 0x66, # 3: Dark Silver (shadows)
0x04, 0x44, # 4: Gunmetal (secondary)
0x07, 0x77, # 5: Light Gunmetal
0x03, 0x33, # 6: Dark Gunmetal
0x00, 0x00, # 7: Black (outline)
0x0F, 0xF0, # 8: Yellow (visor glow)
0x0D, 0xA8, # 9: Skin tone
0x0D, 0xDD, # 10: Chrome (web)
0x05, 0x55, # 11: Mid-gray
0x02, 0x22, # 12: Dark accent
0x08, 0x88, # 13: Medium gray
0x0A, 0xAA, # 14: Light gray
0x0F, 0xFF, # 15: White
])Phase 5: Complete Implementation Workflow
Python ROM Patcher Script
#!/usr/bin/env python3
"""
MvC1 Armored Spider-Man ROM Patcher v2.0
Adds Armored Spider-Man as character ID 0x2E
Selection Code: Down, Down, Left, Left, Up, Up, Right, Right, Down, Down
Starting from Spider-Man's position
"""
import struct
import sys
import os
# Configuration - TO BE DISCOVERED VIA MAME DEBUGGER
CONFIG = {
"char_table_offset": 0x000000, # TODO: Find via debugger
"char_entry_size": 0x20, # Estimated 32 bytes per entry
"char_count": 24, # Number of character entries
"palette_table_offset": 0x000000, # TODO: Find via debugger
"palette_entry_size": 0x40, # 64 bytes per palette
"input_handler_offset": 0x000000, # TODO: Find via debugger
"spider_man_id": 0x0E,
"armored_spider_man_id": 0x2E,
"spider_man_position": 0x0F, # Grid position
}
# Armored Spider-Man Palette (RGB555 Format)
ARMORED_PALETTE = bytes([
0x00, 0x00, # 0: Transparent
0x18, 0x63, # 1: Silver (main)
0x9C, 0x73, # 2: Light Silver
0x52, 0x4A, # 3: Dark Silver
0x4A, 0x29, # 4: Gunmetal Blue
0xEF, 0x3D, # 5: Light Gunmetal
0xC6, 0x18, # 6: Dark Gunmetal
0x00, 0x00, # 7: Black
0xE0, 0x7F, # 8: Yellow (visor)
0xCB, 0x5A, # 9: Skin tone
0xDE, 0x7B, # 10: Chrome
0x10, 0x42, # 11: Shadow
0x8C, 0x31, # 12: Mid-tone
0x00, 0x00, # 13: Reserved
0x00, 0x00, # 14: Reserved
0xFF, 0x7F, # 15: White
# Colors 16-31: Copy from original Spider-Man
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
])
# Input sequence (Down=2, Left=4, Up=8, Right=6)
INPUT_SEQUENCE = bytes([
0x02, 0x02, # Down, Down
0x04, 0x04, # Left, Left
0x08, 0x08, # Up, Up
0x06, 0x06, # Right, Right
0x02, 0x02, # Down, Down
])
def read_rom(filepath):
"""Read ROM file into bytearray"""
with open(filepath, 'rb') as f:
return bytearray(f.read())
def write_rom(filepath, data):
"""Write bytearray to ROM file"""
with open(filepath, 'wb') as f:
f.write(data)
def find_pattern(data, pattern, start=0):
"""Find byte pattern in data"""
pattern_len = len(pattern)
for i in range(start, len(data) - pattern_len):
if data[i:i+pattern_len] == pattern:
return i
return -1
def hex_dump(data, offset, length=64):
"""Print hex dump of data region"""
print(f"\nHex dump at 0x{offset:06X}:")
for i in range(0, length, 16):
hex_str = ' '.join(f'{data[offset+i+j]:02X}'
for j in range(min(16, length-i)))
print(f" {offset+i:06X}: {hex_str}")
if __name__ == '__main__':
print("MvC1 Armored Spider-Man ROM Patcher")
print("=" * 40)
print("NOTE: This script requires ROM offsets to be")
print("discovered via MAME debugger first.")
print()
print("See documentation for MAME debugging steps.")Current Project Status
The Phoenix Edition project has reached a major milestone: the Armored Spider-Man activation logic is built and assembled into ROM. The position check blocker was bypassed entirely using a VBlank interrupt hook instead of modifying the game's complex state machine.
| Phase | Task | Status |
|---|---|---|
| 1 | ROM Decryption | Complete -- Using Phoenix Edition (decrypted mvscud) ROMs |
| 2 | Character Table Analysis | Complete -- Table at $065CCA (32 bytes/entry), roster at $AC7A, stage table at $BB04C |
| 3 | ROM Expansion | Complete -- 128MB GFX + 16MB Audio (CPS-2 MAX) |
| 4 | In-Game Verification | Complete -- ULTRA SETTINGS submenu in F2 Test Menu |
| 5 | Secret Function Decompilation | Complete -- All 4 load functions decompiled to C, patterns documented |
| 6 | Palette Location | Complete -- Spider-Man P: $48642, K: $486A2, Web P: $48682, Web K: $486E2 (in mvc.06a) |
| 7 | ID 0x2E Analysis | Complete -- 85 code references confirmed real, 4 conditional load points found |
| 8 | Secret Table Forensics | Complete -- 3 table types documented, corrected $100000+ base for mvc.05a |
| 9 | Player Structure Mapping | Complete -- Char ID at +$53, data ptr at +$54, health at +$60, unlock flags at $FF803C-$FF804C |
| 10 | Position Check Analysis | Complete -- 13-state machine at $01DFA0 disassembled; hardcoded positions 1,6,11,12,13 |
| 11 | VBlank Interrupt Hook | Complete -- 116 bytes at $0F1FA0, Autovector 2 redirected from $054A |
| 12 | Lua Prototype | Complete -- armored_spiderman.lua validated logic in MAME before ROM patch |
| 13 | ROM Build | Complete -- patch_rom_check_complete.py outputs 145MB ROM set with hook |
| 14 | Armored Palette Creation | Planned -- RGB444 silver/gunmetal design ready |
| 15 | Sprite Copy to Expanded GFX | Planned -- Copy Spider-Man tiles to 96MB free GFX space (optional) |
| 16 | Integration Testing | Next -- Boot patched ROM in MAME |
What's Been Built
- 148MB ROM set at CPS-2 theoretical maximum capacity
- ULTRA SETTINGS diagnostic screen verifying 22 metrics in real time
- Autovector 2 interrupt hook -- 116 bytes of 68000 assembly at
$0F1FA0:- Wolverine (ID
$08) + Down → Spider-Man (ID$0E) character swap - 60-frame char-change window ensures hook only fires on character select screen
- Input edge detection prevents repeated triggers from held buttons
- Original handler at
$054Apreserved viaJMPtail call
- Wolverine (ID
- Lua prototype (
armored_spiderman.lua) -- validated all memory addresses and logic in MAME before ROM commit - Python build pipeline automating expansion + patching + assembly + packaging
- 4 secret character load functions decompiled and verified:
- Red Venom:
$022B2E(66 bytes, complex with shared subroutine call) - Orange Hulk:
$05D044(60 bytes, property writes to player structure) - Gold War Machine:
$07EF90(12 bytes, simplest --movep.lto structure) - Shadow Lady:
$0A26C8(20 bytes, flag setup viaori.b)
- Red Venom:
- Character data structure partially mapped (32-byte entries at
$065CCA) - Unlock flag system documented (base
$FF8000, offsets$3C-$4C) - 4 conditional load points for ID 0x2E found (3/4 near Shadow Lady code)
Key Insight: Bypass vs. Modify
The original plan called for finding and patching the hardcoded position check assembly to add position 5 (Wolverine). After disassembling the 13-state character select state machine at $01DFA0, we realized modifying the tightly-coupled system was unnecessarily risky. The interrupt hook approach operates completely outside the game's state machine, with zero coupling to existing logic.
Remaining Work
- Integration testing -- boot patched ROM in MAME, verify Wolverine + Down activates Spider-Man
- Create armored palette in RGB444 format (silver/gunmetal scheme designed, values ready)
- Wire ID 0x2E load function -- connect to palette swap for visual differentiation
- Copy Spider-Man sprite data to expanded GFX space for armor modifications (optional)