User:Kmeisthax/Findings/2011/6/10/v65 Patch Notes
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).