Skip to content
Index / $04

What's Really Happening Inside a Binary's UI State Machine?

Reverse engineering MvC1's character select screen - discovering cursor navigation tables, decoding entity ID mappings, and mapping three distinct secret character table formats

Date
Jan 24, 2026 · upd Jul 4, 2026
Runtime
11 min · 2,502 words
Tags
reverse-engineering, binary-analysis, embedded-systems
Slot
$04
Contents
tip // Evidence Strip

Goal: Map MvC1's character select screen entity system: cursor navigation, character ID assignment, and secret character table formats -- all from the compiled binary.

Constraints: No source code or debug symbols. Hardware-encrypted program code (mitigated via Phoenix Edition). Multiple data formats for the same conceptual operation (secret character pairs).

Approach: MAME debugger memory watches + breakpoints during character select. Ghidra static analysis for cross-references. Pattern matching for character ID byte pairs across ROM.

Result: Character data table located at $065CCA (32 bytes/entry). Player structure mapped: char ID at +$53, data ptr at +$54, health at +$60. Unlock flags at $FF803C-$FF804C. 3 distinct secret table types documented. 18 grid positions + 5 secret positions mapped. Hardcoded position checks found for positions 1, 6, 11, 12, 13 only (position 5 missing = blocker).

Proof / Validation: ULTRA SETTINGS screen verifies: NORMAL IDS: 16, SECRET IDS: 05, palettes OK, all 4 secret load functions verified. Cursor position variables confirmed via MAME memory watch at $FF2000/$FF2002.

Repo / Artifacts: CHARACTER_SELECT/docs/02_CHARACTER_ID_MAP.md, DECOMP_MVC/include/memory_map.h, DECOMP_MVC/include/character_ids.h.

note // Status update (July 2026)

The table forensics and memory maps 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) -- a same-generation port whose animation/frame tables carry over byte-identical to MvC1, so the work is table porting plus tile bank remapping rather than new art.

When you only have a compiled binary, how do you understand its UI system? MvC1's character select screen uses cursor navigation tables, multiple secret character table formats, and entity ID mappings across different ROM sections. This guide documents the forensic techniques used to identify all of them.

Character Select Screen Layout

Loading diagram...

Note: Secret characters are accessed via directional input codes starting from Zangief's portrait, except where noted.

Secret Character Pairs Found in ROM

Analysis Results (mvc.05a)

Secret CharacterBase → SecretROM LocationOccurrences
Shadow Lady0x16 → 0x2A0x014D6E1
Lilith0x1E → 0x2C0x04A3F21
Orange Hulk0x06 → 0x260x024D3A1
Gold War Machine0x02 → 0x280x03EDD42
Red Venom0x0C → 0x240x07249E1
RollSpecialN/AVia code

Secret Character Table Types

Loading diagram...

Secret Box Entry Structure

Shadow Lady Entry (0x014D6E)

Context at 0x014D60:
  014D60: 40 A0 00 40 00 00 49 00 00 00 06 00 2B 00 16 2A
                                                    ^^ ^^
                                        Chun-Li → Shadow Lady

Structure (16 bytes):

ByteValueDescription
0-140 A0Unknown - flags?
2-300 40Position/coordinate?
4-500 00Padding?
649Unknown - box type?
700Padding?
8-900 00Unknown
1006Unknown - count?
1100Padding?
12-132B 00Pointer/offset?
1416BASE CHARACTER ID - Chun-Li
152ASECRET CHARACTER ID - Shadow Lady

Lilith Entry (0x04A3F2)

Context at 0x04A3F2:
  04A3F2: 1E 2C 62 2C 86 2C 1A 2D 8E 2D 02 2E 76 2E CA 2E
          ^^ ^^
          Morrigan → Lilith

Different structure! This appears to be in a different table with different format.

Orange Hulk Entry (0x024D3A)

Context at 0x024D3A:
  024D3A: 06 26 1A 26 1A 26 00 00 00 26 74 26 28 27 3C 28
          ^^ ^^
          Hulk → Orange Hulk

Also different structure! Followed by more data.

Key Findings

1. Multiple Table Types

Secret characters are NOT in a single unified table. They appear in at least 3 different table structures:

  • Type A (0x014D6E): 16-byte structured entries (Shadow Lady)
  • Type B (0x04A3F2): Sequence/list format (Lilith)
  • Type C (0x024D3A): Packed format (Orange Hulk)

2. Location Patterns

SecretROM LocationTable TypeSection
Shadow Lady0x014D6EType ACharacter select
Lilith0x04A3F2Type BAnimation/graphics
Orange Hulk0x024D3AType CPalette/graphics
Gold War Machine0x03EDD4Type BAnimation/graphics
Red Venom0x07249EType CLate section

3. Character Data Structure (from Decompilation)

The character table at $065CCA uses 32-byte entries. Each character's runtime data is stored in the player structure at $FF3000 (P1) and $FF3100 (P2):

OffsetSizePurposeExample
0x10wordPlayer stateAnimation state machine
0x20wordX positionScreen coordinates
0x24wordY positionScreen coordinates
0x53byteCharacter ID0x0E = Spider-Man
0x54longCharacter data pointerPoints to ROM table entry
0x60wordHealthRemaining HP

The unlock flags are stored relative to a base address (A5 = $FF8000):

OffsetCharacter
+$3CRed Venom
+$40Orange Hulk
+$44Gold War Machine
+$48Shadow Lady
+$4CLilith

4. Secret Load Function Patterns

From decompilation of all 4 secret character load functions:

FunctionAddressSizeKey Operation
load_red_venom$022B2E66 bytesmove.w #$24, d0 + bsr.w $22CF2
load_orange_hulk$05D04460 bytesmove.w #$24, d0 + property writes
load_gold_war_machine$07EF9012 bytesmove.w #$24, d0 + movep.l d0, $3141(a0)
load_shadow_lady$0A26C820 bytesmove.b #$24, d0 + flag setup

All functions follow a pattern: set a value in D0, then write character properties to the player structure via indirect addressing through A0 or A6. Gold War Machine is the simplest (12 bytes), suggesting it was either the first implemented or the most straightforward.

5. Hardcoded Position Checks

The assembly code that controls secret box visibility has hardcoded character positions:

; Only these positions trigger a secret box check:
Positions: 1 (Morrigan), 6 (Hulk), 11 (Venom), 12 (War Machine), 13 (Chun-Li)

Position 5 (Wolverine) is NOT in the list. This is why data-only patches fail -- the code never even reads the table for Wolverine's position. Fixing this requires patching the assembly to add a position 5 check.

6. CPS-2 Encryption Note

Only program code is encrypted in CPS-2 ROMs. Data sections (character tables, palettes, graphics pointers) are unencrypted. This is why palette patches always work. Using the Phoenix Edition (mvscud) with decrypted code eliminates the encryption complication entirely.

Cursor Navigation System

Loading diagram...

Grid Positions (from Decompilation)

Position:  0     1     2     3     4     5
Row 0:    [Zan] [Mor] [Cap] [Meg] [Str] [Wol]
 
Position:  6     7     8     9    10    11
Row 1:    [Hul] [Gam] [Ryu] [Jin] [CCo] [Ven]
 
Position: 12    13    14    15    16    17
Row 2:    [W.M] [Chu] [Rol] [Spi] [?]   [?]
 
Secret positions (virtual row below visible grid):
  Below Gambit    → Shadow Lady      (Chun-Li unlock code)
  Below War Mach  → Lilith           (Morrigan unlock code)
  Right of Megaman→ Roll             (Zangief code)
  Above Zangief   → Gold War Machine (Zangief code)
  Above Ryu       → Orange Hulk      (Chun-Li code)
  Above Venom     → Red Venom        (Chun-Li code)
  Below Wolverine → Armored Spidey   (TARGET - position 5)

Cursor position tracked at: P1 = $FF2000, P2 = $FF2002 Character ID written to: P1 = $FF3053, P2 = $FF3153

Down Navigation

When pressing Down on certain characters:

  • Chun-Li (pos 13) → Secret box appears (Shadow Lady)
  • Morrigan (pos 1) → Secret box appears (Lilith)
  • Hulk (pos 6) → Secret box appears (Orange Hulk)
  • War Machine (pos 12) → Secret box appears (Gold War Machine)
  • Venom (pos 11) → Secret box appears (Red Venom)

Target: Wolverine (pos 5) → Secret box (Armored Spider-Man)

ROM Patching Strategy

Loading diagram...

Required ROM Patches

To add Armored Spider-Man below Wolverine, we need to patch:

1. Character Pair Entry (Type A - 0x014D6E style)

Location: Find or create entry near 0x014D6E

Pattern:

40 A0 00 40 00 00 49 00 00 00 06 00 2B 00 08 0E
                                          ^^ ^^
                              Wolverine → Armored Spider-Man

Status: ⚠️ Added at 0x014D70 but doesn't work alone

2. Animation/Graphics Entry (Type B - 0x04A3F2 style)

Location: Find similar table and add entry

Pattern: TBD (needs more analysis)

3. Palette/Graphics Entry (Type C - 0x024D3A style)

Location: Find similar table and add entry

Pattern: TBD (needs more analysis)

4. Cursor Navigation Table

Location: TBD (needs MAME debugger)

Modification: Make Down on Wolverine (pos 5) go to secret box position

ROM Table Locations

Loading diagram...

Analysis TODO

Immediate Research Needed

  • Find cursor navigation table (where Down destinations are defined)
  • Understand Type B table structure (Lilith/Gold WM)
  • Understand Type C table structure (Orange Hulk/Red Venom)
  • Find secret box graphics data
  • Find secret box positioning data

Tools Needed

  • MAME debugger to trace character select code
  • Memory watch to find cursor position variable
  • Breakpoint on Down input during character select
  • Disassembly of character select routine

Hypothesis: Why Only One Type A Entry?

Observation: Only Shadow Lady has a Type A entry at 0x014D6E.

Possible reasons:

  1. Shadow Lady was added last / is special case
  2. Other secrets use older/different system
  3. Type A is newer format, others use legacy format
  4. Each secret type has unique implementation

Implication: We may need to add Wolverine entry to MULTIPLE tables, not just one.

Next Steps

Step 1: Find Cursor Navigation Table

Use MAME debugger:

1. Set breakpoint on character select screen
2. Watch memory when pressing Down on Chun-Li
3. Find variable that changes (cursor position)
4. Find code that reads this variable
5. Find table that defines Down destinations

Step 2: Add All Required Entries

Once we understand all table types:

1. Add Type A entry (done at 0x014D70)
2. Add Type B entry (location TBD)
3. Add Type C entry (location TBD)
4. Patch cursor navigation

Step 3: Test Incrementally

1. Test after each patch
2. Document which patch enables the box
3. Refine as needed

Current Status

ComponentStatusNotes
Secret character pairs✅ CompleteAll 6 located in ROM
Multiple table types✅ Identified3 types: structured, sequence, packed
Entry structures✅ DocumentedShadow Lady (Type A) fully mapped
ROM expansion✅ Complete128MB GFX + 16MB Audio (CPS-2 max)
In-game verification✅ CompleteULTRA SETTINGS screen in F2 menu
Palette locations✅ VerifiedP palette at $0C8642, K palette at $0C86A2
Secret load functions✅ VerifiedAll 4 entry points contain valid code
Character ID 0x2E✅ Found85 code references, present in roster scan
Cursor navigation tableIn ProgressTraced via MAME debugger
New character injectionIn ProgressInfrastructure built, wiring remaining

Key Discovery: False Positive IDs

During analysis, IDs 0x30 and 0x32 initially appeared to have 80+ ROM references. Deeper investigation revealed these were false positives: the 68000 MOVE.W #imm,D0 instruction encodes as 0x303C, which pattern-matched against ID 0x30 searches. The actual animation marker count for both IDs is zero -- they are completely empty slots available for new characters.

References

  • PalMod: Shows Lilith/Shadow Lady have separate palette entries
  • ROM Analysis: Multiple table types found across mvc.05a
  • MAME Debugger: Instruction tracing and memory watches
  • Ghidra: Static analysis via GhidraMCP plugin
  • Community: Secret character codes documented by MvC1 modding community
EOF · $04 · 2,502 words · Daniel Plas Rivera
Share[X][LinkedIn]