User:Kmeisthax/Findings/2011/6/10/v65 Patch Notes

From Wikifang, a definitive guide to Telefang, Dino Device and Bugsite
Jump to navigation Jump to search

Main Script Hack

There is a main script hack used to, er, 'fix' some weirdness in the main script routine which causes newlines to be rendered a tile ahead. The core VWF will, when 16 tiles are drawn, move back HL on the NEXT draw call. This is done by writing to WRAM C7C6, which is the Main Script Hack predicate byte. In my earlier disassembly of the VWF Core, this was called "MyMisteryBite". The Main Script Hack, when turned on, moves back HL and some main script internal byte by one tile.

This is fine except that the core VWF routine gets called by EVERYTHING now and this is giving us text glitches.

APPROACH 1: Make the Main Script Hack only happen for Main Script by removing it from the Core VWF and moving it into the Main Script routines.

So where are the Main Script routines? Tracing and stack-grepping give us a call at 2CE29. Some more disassembly eventually gets us to 2C100, which is essentially a giant switch that acts as the core of the Main Script routine. It has multiple states stored in C9C9. State 1 is a control code interpreter which also draws regular letters as well (however, Telefang does NOT interleave control codes and text. it jumps to the text properly.)

State 1's code starts at 2C13C, and tracing it we get to this particular branch:

   ROM:0002C1D3                 cp      $E1 ; 'ß'
   ROM:0002C1D5                 jp      nc, 2C337       ; filters out unknown control codes
   ROM:0002C1D8                 ld      a, [byte_C9CC]
   ROM:0002C1DB                 or      a
   ROM:0002C1DC                 jp      z, 2c1e3
   ROM:0002C1DF                 dec     a
   ROM:0002C1E0                 jp      nz, 2c34a
   ROM:0002C1E3                 push    hl
   ROM:0002C1E4                 ld      a, [byte_C9CD]
   ROM:0002C1E7                 ld      [byte_C9CC], a
   ROM:0002C1EA                 ld      a, [WRAM_MainScript_TilesDrawn]
   ROM:0002C1ED                 ld      b, a
   ROM:0002C1EE                 ld      a, [byte_C91F]
   ROM:0002C1F1                 add     a, b
   ROM:0002C1F2                 call    sub_35C2
   ROM:0002C1F5                 call    2c22d
   ROM:0002C1F8                 ld      a, c
   ROM:0002C1F9                 call    2cE29           ; This is the VWF Core service routine.
   ROM:0002C1FC                 pop     hl
   ROM:0002C1FD                 ld      a, [WRAM_MainScript_TilesDrawn]
   ROM:0002C200                 inc     a
   ROM:0002C201                 ld      [WRAM_MainScript_TilesDrawn], a
   ROM:0002C204                 cp      $10
   ROM:0002C206                 jr      nz, 2C212
   ROM:0002C208                 ld      a, [WRAM_MainScript_NumNewlines]
   ROM:0002C20B                 inc     a
   ROM:0002C20C                 ld      [WRAM_MainScript_NumNewlines], a
   ROM:0002C20F                 jp      2C2EA

Fun fact: 2C22D is just ld a, c and return. So we can remove that instruction with no effect on our program. This is where we need to insert our advice to reconstruct the Main Script Hack:

   ROM:0002F9C1                 ld      a, [WRAM_VWF_MainScriptHack]
   ROM:0002F9C4                 cp      1
   ROM:0002F9C6                 jr      nz, 2F9D3
   ROM:0002F9C8                 ld      a, [WRAM_MainScript_TilesDrawn]
   ROM:0002F9CB                 dec     a
   ROM:0002F9CC                 ld      [WRAM_MainScript_TilesDrawn], a
   ROM:0002F9CF                 ld      bc, $FFF0
   ROM:0002F9D2                 add     hl, bc          ; Roll back HL...
   ROM:0002F9D3                 ld      b, 8

(This version of the hack is from the entry point of the VWF core routine.)

The important memory addresses are:

   WRAM_VWF_MainScriptHack     WRAM C7C6
   WRAM_MainScript_TilesDrawn  WRAM C9CB

We'll modify the advice to look like this:

   ld      a, [WRAM_VWF_MainScriptHack]        FA C6 C7
   cp      1                                   FE 01
   ret     nz                                  C0
   ld      a, [WRAM_MainScript_TilesDrawn]     FA CB C9
   dec     a                                   3D
   ld      [WRAM_MainScript_TilesDrawn], a     EA CB C9
   ld      bc, $FFF0                           01 F0 FF
   add     hl, bc                              09
   ret                                         C9

We also have to NOP out that part of the VWF Core so that the main script hack will NOT execute when non-main-script text is being drawn. The VWF Core does touch WRAM_MainScript_TilesDrawn in other places, and proper separation of concerns would dictate that we move that logic out of VWF Core, but that will take too much effort. It's not harming anything on it's own once we get this main script hack out of VWF Core.

Now we only need to decide where the new advice gets stored. The first empty byte in this bank is 2FC09 (ROM B:7C09) so that's where we'll inject this routine. It's 18 bytes long, so future advice should start from 2FC1F (ROM B:7C1F).

Getting our advice called is easy! Just change the call address at 2C1F5, which as I said earlier is a redundant call. Make it call 7C09 instead. Then NOP out the main script hack, and move up the call to VWFCore in the advice function...

   ROM:0002CE53                 call    2F9C1           ; VWF Core
   ROM:0002CE56                 jr      2CE64

The call at 2CE53 is where the advice function (ROM B 4E29 is where the entrypoint is, BTW) calls the core function. We need to change it to 2F9D3. Then NOP out the skipped bytes so they can't be executed by accident.

After installing this, it seems that we're getting JUNK TILES on the second VRAM line of main script drawn text. Well it turns out C is where the letter was, and we've been consistently setting it to F0. A minor change of advice will fix this:

   ld      a, [WRAM_VWF_MainScriptHack]        FA C6 C7
   cp      1                                   FE 01
   ret     nz                                  C0
   ld      a, [WRAM_MainScript_TilesDrawn]     FA CB C9
   dec     a                                   3D
   ld      [WRAM_MainScript_TilesDrawn], a     EA CB C9
   push    bc                                  C5
   ld      bc, $FFF0                           01 F0 FF
   add     hl, bc                              09
   pop     bc                                  C1
   ret                                         C9

This changes the length of our advice to 20 bytes, so the nearest safe free space is actually 2FC21.

This is great! Mostly. Start a Denjuu encounter and open the status screen. Then close it. See how the first line is off? Well that's because the Main Script Hack is being triggered on that line. Let's stop it.

I traced the routine that first sets the Main Script State when you do something that opens the main script window. 2C7B5 sets up the state, and it does a lot of other init too! The routine's entry point is 2C769 (ROM B:4769) and all we have to do is clear that main script hack byte when we start!

   ROM:0002C798                 xor     a
   ROM:0002C799                 ld      [WRAM_MainScript_TilesDrawn], a
   ROM:0002C79C                 ld      [WRAM_MainScript_NumNewlines], a
   ROM:0002C79F                 ld      a, 0
   ROM:0002C7A1                 ld      [byte_C9CC], a
   ROM:0002C7A4                 ld      a, 1
   ROM:0002C7A6                 ld      [byte_C9CD], a
   ROM:0002C7A9                 ld      a, 0
   ROM:0002C7AB                 ld      [loc_CAD3], a
   ROM:0002C7AE                 ld      a, 2
   ROM:0002C7B0                 ld      [loc_CADA], a
   ROM:0002C7B3                 ld      a, 1
   ROM:0002C7B5                 ld      [WRAM_MainScript_State], a

We want to set the script hack byte to 0 as well. This is a 32 byte long chunk of code. By reorganizing saves we can avoid having to write external advice code... This is what I changed the routine to:

   xor     a                                       AF
   ld      [WRAM_MainScript_TilesDrawn], a         EA CB C9
   ld      [WRAM_MainScript_NumNewlines], a        EA CE C9
   ld      [byte_C9CC], a                          EA CC C9
   ld      [loc_CAD3], a                           EA D3 CA
   ld      [WRAM_VWF_MainScriptHack], a            EA C6 C7
   inc     a                                       3C
   ld      [byte_C9CD], a                          EA CD C9
   ld      [WRAM_MainScript_State], a              EA C9 C9
   inc     a                                       3C
   ld      [loc_CADA], a                           EA DA CA

Now we only use 27 bytes, a full 5 BYTES of savings!

Denjuu Calls are initialized differently, however, and that text also needs to get the Main Script Hack init as well. Let's look at that routine... It's entry point is 2C7ED (ROM B:47ED)...

   ROM:0002C816                 xor     a
   ROM:0002C817                 ld      [WRAM_MainScript_TilesDrawn], a
   ROM:0002C81A                 ld      [WRAM_MainScript_NumNewlines], a
   ROM:0002C81D                 ld      [byte_C9CC], a
   ROM:0002C820                 ld      [loc_CAD3], a
   ROM:0002C823                 ld      a, 0
   ROM:0002C825                 ld      [byte_C9CD], a
   ROM:0002C828                 ld      [loc_CADA], a
   ROM:0002C82B                 ld      a, 1
   ROM:0002C82D                 ld      [WRAM_MainScript_State], a

Huh. At first glance, this seems like we'll have to insert some jumps. BUT NO THEY FAILED AGAIN an extraneous LD A, 0 APPEARS! Removing that, and replacing another ld a,1 with an inc gives us three bytes for an extra load.

BEHOLD

   xor     a                                       AF
   ld      [WRAM_MainScript_TilesDrawn], a         EA CB C9
   ld      [WRAM_MainScript_NumNewlines], a        EA CE C9
   ld      [byte_C9CC], a                          EA CC C9
   ld      [loc_CAD3], a                           EA D3 CA
   ld      [WRAM_VWF_MainScriptHack], a            EA C6 C7
   ld      [byte_C9CD], a                          EA CD C9
   ld      [loc_CADA], a                           EA DA CA
   inc a                                           3C
   ld      [WRAM_MainScript_State], a              EA C9 C9

After injecting the routine I am proud to report that the problem is completely unsolved! It turns out this isn't an initialization routine for denjuu calls. We fixed something but we don't know what and probably never will. Are there any other things that use Main Script? IDK. But Denjuu calls actually never get initialized!

The elephant in the room is that the VWF core moves back TilesDrawn if it's not time to move up the tile pointer. A few patches ago we altered the status item text routines to properly move HL back and forward based on OldTileMode, and I think it's right about time that the Main Script stuff get the same treatment. After all, it's not technically the VWF's job to care about the tile management concerns of it's caller. That's what OldTileMode is for.

   ROM:0002FA50                 ld      a, [WRAM_VWF_LetterShift]
   ROM:0002FA53                 add     a, b
   ROM:0002FA54                 bit     3, a
   ROM:0002FA56                 jr      z, 2fa71
   ROM:0002FA58                 sub     8
   ROM:0002FA5A                 push    af
   ROM:0002FA5B                 ld      a, 1
   ROM:0002FA5D                 ld      [WRAM_VWF_OldTileMode], a
   ROM:0002FA60                 ld      a, [WRAM_VWF_MainScriptHack]
   ROM:0002FA63                 cp      1
   ROM:0002FA65                 jr      nz, loc_FA6C
   ROM:0002FA67                 ld      a, 0
   ROM:0002FA69                 ld      [WRAM_VWF_MainScriptHack], a
   ROM:0002FA6C                 pop     af
   ROM:0002FA6D                 ld      [WRAM_VWF_LetterShift], a
   ROM:0002FA70                 ret
   ROM:0002FA71 ; ---------------------------------------------------------------------------
   ROM:0002FA71                 push    af
   ROM:0002FA72                 ld      a, [WRAM_MainScript_TilesDrawn]
   ROM:0002FA75                 cp      $10
   ROM:0002FA77                 jr      z, 2FA7F
   ROM:0002FA79                 dec     a
   ROM:0002FA7A                 ld      [WRAM_MainScript_TilesDrawn], a
   ROM:0002FA7D                 jr      loc_FA6C
   ROM:0002FA7F ; ---------------------------------------------------------------------------
   ROM:0002FA7F                 ld      a, 1
   ROM:0002FA81                 ld      [WRAM_VWF_MainScriptHack], a
   ROM:0002FA84                 jr      loc_FA6C

No advice here: just nop out everything after 2FA71, and retarget the jump relative to 2FA6C. (Ugh that will be fun) This will make it no longer manage the Main Script routine's internal details anymore - we've excused every mention of WRAM_MainScript anything from the VWF core now.

Of course we need to inject more advice into the main script interpreter to make it manage it's own damn tiles now. Here's what the bottom half of our script interpreter routine looks like:

   ROM:0002C1F9                 call    2cE29           ; This is the VWF Core service routine.
   ROM:0002C1FC                 pop     hl
   ROM:0002C1FD                 ld      a, [WRAM_MainScript_TilesDrawn]
   ROM:0002C200                 inc     a
   ROM:0002C201                 ld      [WRAM_MainScript_TilesDrawn], a
   ROM:0002C204                 cp      $10
   ROM:0002C206                 jr      nz, 2C212
   ROM:0002C208                 ld      a, [WRAM_MainScript_NumNewlines]
   ROM:0002C20B                 inc     a
   ROM:0002C20C                 ld      [WRAM_MainScript_NumNewlines], a
   ROM:0002C20F                 jp      2C2EA

Huh, that Tiles Drawn thing looks ripe for some changing. But there's no room. So let's stick in a call to some advice like so:

   ld      a, [WRAM_VWF_OldTileMode]           FA C5 C7
   cp      1                                   FE 01
   ret     c                                   D8
   ld      a, [WRAM_MainScript_TilesDrawn]     FA CB C9
   inc     a                                   3C
   ld      [WRAM_MainScript_TilesDrawn], a     EA CB C9
   ret                                         C9

(Calls may be slightly slower but they save advice space.)

Our advice will be placed at 2FC21, since as before I said that was the start of free space. Well now we have to insert 14 more bytes... So now our free space starts at, uh, 2FC2F. We need to insert our pointcut, so we'll do that at 2C1FD since that instruction is the same size as a call. We will then nop out the increment and convert the store to a load. This is so that the newline check that happens later on can still occur.

   call    2ce29
   pop     hl
   call    2fc21
   nop
   ld      a, [WRAM_MainScript_TilesDrawn]
   cp      $10
   ...

Finally we just need to NOP out those bytes I mentioned earlier on the main script routine.

   ROM:0002FA50                 ld      a, [WRAM_VWF_LetterShift]
   ROM:0002FA53                 add     a, b
   ROM:0002FA54                 bit     3, a
   ROM:0002FA56                 jr      z, 2FA6D  ;
   ROM:0002FA58                 sub     8
   ROM:0002FA5A                 push    af
   ROM:0002FA5B                 ld      a, 1
   ROM:0002FA5D                 ld      [WRAM_VWF_OldTileMode], a
   ROM:0002FA60                 ld      a, [WRAM_VWF_MainScriptHack]
   ROM:0002FA63                 cp      1
   ROM:0002FA65                 jr      nz, 2FA6C
   ROM:0002FA67                 ld      a, 0
   ROM:0002FA69                 ld      [WRAM_VWF_MainScriptHack], a
   ROM:0002FA6C                 pop     af
   ROM:0002FA6D                 ld      [WRAM_VWF_LetterShift], a
   ROM:0002FA70                 ret
   ROM:0002FA71 ; ---------------------------------------------------------------------------
        to                      NOTHING BUT NOPS
   ROM:0002FA88                 

There's just one tiny matter of the fact that we can't actually see any VWF Mode 2s in our advice. This is kind of bad, because this means we can't reliably move up our tile pointer on a newline, and the main script state machine gets terminated early. This means no second line.

It looks like we forgot to enable the main script hack. It was an ancillary responsibility of the code we NOP'd out. So let's look at our second advice function again:

   ld      a, [WRAM_VWF_OldTileMode]           FA C5 C7
   cp      1                                   FE 01
   ld      a, [WRAM_MainScript_TilesDrawn]     FA CB C9
   jr      c, +4                               38 04
   inc     a                                   3C
   ld      [WRAM_MainScript_TilesDrawn], a     EA CB C9
   cp      $10                                 FE 10
   ret     nz                                  C0
   ld      a, 1                                3E 01
   ld      [WRAM_VWF_MainScriptHack], a        EA C6 C7
   ld      a, [WRAM_MainScript_TilesDrawn]     FA CB C9
   inc     a                                   3C
   ld      [WRAM_MainScript_TilesDrawn], a     EA CB C9
   ret                                         C9

The new free space pointer for Bank B is 2FC3F now (ROM B:7C3F).