Tracking Which Hero

Alpha Draft from Claude Opus 4.5

;=====================================================================
; NEW CODE - Place at E8:5280 (file offset: $340280 for headered ROM)
;=====================================================================
              org $E85280

;---------------------------------------------------------------------
; Lookup table: Hero index (0-4) → Base address within bank E8
; Each entry is 2 bytes (16-bit address)
;---------------------------------------------------------------------
HeroBaseTable:
              dw BARTZ_BASE       ; $0000 - Hero 0 (Bartz: $00 & $07 = 0)
              dw LENNA_BASE       ; $1080 - Hero 1 (Lenna: $09 & $07 = 1)
              dw GALUF_BASE       ; $2100 - Hero 2 (Galuf: $02 & $07 = 2)
              dw FARIS_BASE       ; $3180 - Hero 3 (Faris: $0B & $07 = 3)
              dw KRILE_BASE       ; $4200 - Hero 4 (Krile: $0C & $07 = 4)

; Hex bytes: 00 00  80 10  40 20  C0 30  40 41
;            (little-endian 16-bit values)

;---------------------------------------------------------------------
; Lookup table: Slot index (0-3) → WRAM base address
; Used to find the hero/job bytes for each party slot
;---------------------------------------------------------------------
SlotWRAMTable:
              dw $2000            ; Slot 0 - party position 1
              dw $2080            ; Slot 1 - party position 2
              dw $2100            ; Slot 2 - party position 3
              dw $2180            ; Slot 3 - party position 4

; Hex bytes: 00 20  80 20  00 21  80 21

;---------------------------------------------------------------------
; Main routine: ComputeWWAddress
;---------------------------------------------------------------------
; Entry conditions (ASSUMED - VERIFY!):
;   X = slot × 8 (so $00, $08, $10, $18 for slots 0-3)
;   DB = ?? (we'll set it ourselves)
;   Processor likely in 8-bit mode from prior code
;
; Exit conditions:
;   $76-$77 = sprite address within bank (computed)
;   $78 = $E8 (bank byte)
;   All other registers preserved
;---------------------------------------------------------------------
ComputeWWAddress:
     PHP                 ; Save processor status
     PHB                 ; Save data bank
     PHX                 ; Save X (we'll need it)
     PHY                 ; Save Y

     ; Set data bank to $7E for WRAM access
     PHA
     LDA #$7E
     PHA
     PLB                 ; DB = $7E
     PLA

				 ;-------------------------------------------------
				 ; Step 1: Convert X to slot number (0-3)
				 ; REVISED: X = $08, $10, $18, $20 for slots 0-3
				 ;-------------------------------------------------
         REP #$20          ; 16-bit accumulator
         TXA               ; A = X value ($08-$20)
         SEC
         SBC #$0008        ; Subtract 8: now $00, $08, $10, $18
         LSR A             ; ÷2
         LSR A             ; ÷4
         LSR A             ; ÷8 → slot number 0-3
         AND #$0003        ; Safety mask
         ASL A             ; ×2 for word table index
         TAX               ; X = table index for SlotWRAMTable

        ;-------------------------------------------------
        ; Step 2: Get WRAM base address for this slot
        ;-------------------------------------------------
        ; We need to access our table in bank E8
        ; Using long addressing to reach it
        LDA $E8528A,X       ; Load from SlotWRAMTable (E85280 + $0A)
        TAY                 ; Y = WRAM address ($2000, $2080, etc.)

        ;-------------------------------------------------
        ; Step 3: Read hero byte, compute hero base addr
        ;-------------------------------------------------
        SEP #$20            ; 8-bit accumulator
        LDA $0000,Y         ; Read hero byte from $7E:20x0
        AND #$07            ; Mask to get hero index (0-4)
        ASL A               ; ×2 for word table index
        TAX                 ; X = hero table index

        REP #$20            ; 16-bit accumulator
        LDA $E85280,X       ; Load from HeroBaseTable
        STA $76             ; Store base address to $76-$77

        ;-------------------------------------------------
        ; Step 4: Read job byte, multiply by $C0, add to base
        ;-------------------------------------------------
        SEP #$20            ; 8-bit accumulator
        INY                 ; Y now points to job byte ($7E:20x1)
        LDA $0000,Y         ; Read job byte

        ; Multiply job by $C0 (192 decimal)
        ; $C0 = $80 + $40 = 128 + 64
        ; job × $C0 = (job × $80) + (job × $40)
        ;           = (job << 7) + (job << 6)
        REP #$20            ; 16-bit accumulator
        AND #$00FF          ; Clear high byte
        STA $00             ; Temp storage: job value

        ASL A               ; ×2
        ASL A               ; ×4
        ASL A               ; ×8
        ASL A               ; ×16
        ASL A               ; ×32
        ASL A               ; ×64
        STA $02             ; Temp: job × $40
        ASL A               ; ×128
        CLC
        ADC $02             ; A = job × $C0

        ; Add offset to base address
        CLC
        ADC $76
        STA $76             ; $76-$77 = final sprite address

        ;-------------------------------------------------
        ; Step 5: Set bank byte to $E8
        ;-------------------------------------------------
        SEP #$20            ; 8-bit accumulator
        LDA #WW_BANK        ; $E8
        STA $78             ; $78 = bank byte

        ;-------------------------------------------------
        ; Restore and return
        ;-------------------------------------------------
        PLY                 ; Restore Y
        PLX                 ; Restore X
        PLB                 ; Restore data bank
        PLP                 ; Restore processor status
        RTL                 ; Return from long subroutine

; End of routine - next free byte is at approximately E8:52E0

;=====================================================================
; HOOK PATCH - Apply at C1:2517
;=====================================================================
; Original 8 bytes at C1:2517:
;   BF A7 24 C1   LDA $C124A7,X
;   BF A9 24 C1   LDA $C124A9,X
;
; Replace with:
;   22 92 52 E8   JSL $E85292   ; Jump to ComputeWWAddress
;   EA            NOP           ; \
;   EA            NOP           ;  } Pad remaining 4 bytes
;   EA            NOP           ;  }
;   EA            NOP           ; /

      org $C12517
      JSL ComputeWWAddress    ; 22 92 52 E8
      NOP                     ; EA
      NOP                     ; EA
      NOP                     ; EA
      NOP                     ; EA

; HEX SUMMARY FOR MANUAL PATCHING
;================================================================
;
; == At file offset $285280 (E8:5280) - New tables and routine ==
;
; HeroBaseTable (10 bytes):
; 00 00 80 10 40 20 C0 30 40 41
;
; SlotWRAMTable (8 bytes):
; 00 20 80 20 00 21 80 21

; @ E8:5292

; ComputeWWAddress routine (~70 bytes):
; REVISED: X = $08, $10, $18, $20 for slots 0-3
; 08 8B DA 5A 48 A9 7E 48 AB 68 C2 20 8A 38 FD 08  
; 00 4A 4A 4A 29 03 00 0A AA BF 8A 52 E8 A8 E2 20 
; B9 00 00 29 07 0A AA C2 20 BF 80 52 E8 85 76 E2 
; 20 C8 B9 00 08 8B DA 5A 48 A9 7E 48 AB 68 C2 20 
; 8A 4A 4A 4A 29 03 00 0A AA BF 8A 52 E8 A8 E2 20 
; B9 00 00 29 07 0A AA C2 20 BF 80 52 E8 85 76 E2 
; 20 C8 B9 00 00 C2 20 29 FF 00 85 00 0A 0A 0A 0A 
; 0A 0A 85 02 0A 18 65 02 18 65 76 85 76 E2 20 A9 
; E8 85 78 7A FA AB 28 6B
;
; === At file offset $012517 (C1:2517) - Hook patch ===
; 22 92 52 E8 EA EA EA EA
;
;================================================================

Once you have those observations, we can refine. The routine is ~70 bytes, well within the space after your sprites end at E8:527F.

;-------------------------------------------------
; Step 1: Convert X to slot number (0-3)
; REVISED: X = $08, $10, $18, $20 for slots 0-3
;-------------------------------------------------
         REP #$20          ; 16-bit accumulator
         TXA               ; A = X value ($08-$20)
      38 SEC
E9 08 00 SBC #$0008        ; Subtract 8: now $00, $08, $10, $18
         LSR A             ; ÷2
         LSR A             ; ÷4
         LSR A             ; ÷8 → slot number 0-3
         AND #$0003        ; Safety mask
         ASL A             ; ×2 for word table index
         TAX               ; X = table index for SlotWRAMTable

image.png

Working hack, with incorrect sprites!

Debugging which Tiles are Loading

Finding which tiles are actually appearing is straightforward: the Mesen Sprite Viewer (Ctrl + 3).

Choosing to “View in Memory Viewer” on right click pulls up the first tile’s data in VRAM; copying these $20 bytes and then searching in ROM will locate them.

image.png

Nice offset, $140000 aka #D4:0000.

image.png

To quickly recap why a single tile is $20 bytes… we know from other measurements that $C0 is 6 tiles; $20 = 32, and 32 bytes is enough to cover 64 pixels at 4 bits per pixel (64 x 4 / 8 = 32).

So hero #4 is landing at $140000 using the alpha hack logic, and checking the other 3 battle positions will yield other data to help adjust things.

Surprise— all 4 battle positions load these tiles!

image.png

So the loop to change which address is loaded from per battle position is not yet firing, or somehow avoiding the hook.

Revisions to v0.01 code, 5 June, Claude Opus 4.5

Tables Look Correct Now
E8:5280  00 00    HeroBase[0] = $0000 (Bartz)  ✓
E8:5282  80 10    HeroBase[1] = $1080 (Lenna)  ✓
E8:5284  00 21    HeroBase[2] = $2100 (Galuf)  ✓
E8:5286  80 31    HeroBase[3] = $3180 (Faris)  ✓
E8:5288  00 42    HeroBase[4] = $4200 (Krile)  ✓
E8:528A  00 20    SlotWRAM[0] = $2000  ✓
E8:528C  80 20    SlotWRAM[1] = $2080  ✓
E8:528E  00 21    SlotWRAM[2] = $2100  ✓
E8:5290  80 21    SlotWRAM[3] = $2180  ✓

Bug #1  
  E8:52C4  B9 00 08     LDA $0800,Y   ; ← BUG! Should be LDA $0000,Y

  The byte at file offset $3402C6 is 08 but should be 00.

That confirms the routine isn't computing addresses correctly yet — it's falling back to the original D4:0000 instead
  of the new E8:xxxx addresses.

  *Looking at your bytecode dump* more carefully, I found two more bugs beyond the one we fixed:

  Bug #2: Fall-Through into Duplicate Code

  After Version 1 reads the job byte at E8:52C4, it falls straight through into a second copy of the routine's prologue
  at E8:52C7 (8B DA 5A 48...). This pushes registers again, corrupts the stack, and re-runs the calculations with a
  different formula.

  Fix: Insert a branch to skip over V2's redundant setup and jump directly to the job multiplication code.

  At E8:52C7-52C8 (file offset $3402C7-C8):
  - Current: 8B DA (PHD, PHX)
  - Change to: 80 32 (BRA +$32 → jumps to E8:52FB)

  Bug #3: Wrong Opcode in Epilogue

  The epilogue uses AB (PLB - Pull Data Bank, 8-bit) but the prologue pushed with 8B (PHD - Push Direct Page, 16-bit).
  These aren't symmetric!

  At E8:531B (file offset $34031B):
  - Current: AB (PLB)
  - Change to: 2B (PLD)

Summary of All Fixes

  ┌─────────────┬─────────┬───────────┬──────────────────────────┐
  │ File Offset │ Current │ Change To │         Purpose          │
  ├─────────────┼─────────┼───────────┼──────────────────────────┤
  │ $3402C6     │ 08      │ 00        │ Fix job byte read (DONE) │
  ├─────────────┼─────────┼───────────┼──────────────────────────┤
  │ $3402C7     │ 8B      │ 80        │ BRA opcode               │
  ├─────────────┼─────────┼───────────┼──────────────────────────┤
  │ $3402C8     │ DA      │ 32        │ BRA offset (+$32)        │
  ├─────────────┼─────────┼───────────┼──────────────────────────┤
  │ $340317     │ AB      │ 2B        │ PLB → PLD in epilogue    │
  └─────────────┴─────────┴───────────┴──────────────────────────┘