@abw This may be better suited to a PM, but I'd like to ask, is this something you would explain to me how to determine on my own?
I don't know enough to know if it requires more background than I currently have in order to understand, or if it's something you could explain to a semi-neophyte. If I could grasp this one portion of reverse engineering I feel like I'd be able to dump and translate that many more scripts.
Well, I can sure give it a try
Finding the lookup table (or call it a pointer table, depending on how you want to think of it) is pretty easy with a debugger. You already knew the dictionary entries were at 0x135F0-0x13FE7, and we can find out that the game uses mapper #1 (a.k.a. MMC1; if you don't already know from somewhere else, FCEUX can tell you this if you look under the Help -> Message Log menu item). A quick peek in the debugger shows that the game is using MMC1 in its 2 x 16k bank mode (as opposed to its 1 x 32k bank mode; NESDev
can tell you about the available modes for each mapper), and $C000-$FFFF is mapped to bank 7. That being the case, when it appears in RAM at all, 0x135F0-0x13FE7 will show up in RAM in the $8000-$BFFF bank at $B5E0-$BFD7, so you can set a read breakpoint on $B5E0-$BFD7 (and add a "T==#04" condition if you want to ignore $B5E0-$BFD7 reads for other ROM banks) and then play through the game until it tries to read from the dictionary and the breakpoint triggers. I'll use the first dictionary read during the title intro sequence as an example below.
I actually wrote a fairly detailed account of tracing the source of some data in Dragon Warrior
recently; the details aren't exactly the same as in Marusa no Onna, but the basic ideas are exactly the same, so you might find that thread relevant and/or interesting to read. The example there uses a trace log because Dragon Warrior's text engine takes the scenic route, loading a character from ROM and then winding its way through a variety of other RAM addresses before writing the character to VRAM. For Marusa no Onna's dictionary, the path is much more direct. When the game pauses on the $B5E0-$BFD7 read breakpoint, this is what the debugger shows (a trace log would show something quite similar but with slightly different extra information like register and processor states):
07:CBAE:BD 80 B2 LDA $B280,X @ $B281 = #$B5
07:CBB1:85 10 STA $0010 = #$87
07:CBB3:BD 81 B2 LDA $B281,X @ $B282 = #$E5
07:CBB6:85 11 STA $0011 = #$B6
07:CBB8:E6 BA INC $00BA = #$01
07:CBBA:A6 BA LDX $00BA = #$01
07:CBBC:A9 00 LDA #$00
07:CBBE:9D E0 04 STA $04E0,X @ $04E1 = #$00
07:CBC1:A0 00 LDY #$00
>07:CBC3:B1 10 LDA ($10),Y @ $B687 = #$0F
In order to understand what the debugger's showing you, you'll want to know at least a little 6502 ASM. If that sounds intimidating, remember that you just need enough to follow along; it's not like you're writing your own homebrew from scratch
. Anyway, here we can see that the breakpoint triggered when the game read a #$0F byte from RAM address $B687 via the LDA ($10),Y instruction. The line above that set Y to #$00, so we know that $10-$11 is $B687, and a few lines above that we can see the game writing to $10 and $11 from $B280,X and $B281,X (scrolling up to $B280 will show you that ROM bank 4 is currently loaded into RAM $8000-$BFFF, so $B280 corresponds to 0x13290; you can also right-click on $B280 in the Hex Editor and use the "Go Here In ROM File" option to get to 0x13290).
After that, a visual scan of the ROM file shows what looks like a whole lot of pointers going down to 0x135EF, so I made an educated guess that the pointer table stops there; knowing that the dictionary starts at 0x135F0 adds some extra support for that guess.
That's basically all there was to it!