[Technical][PS2] Finding the Text Routine to find the VWF code

Started by Risae, April 07, 2020, 11:57:11 AM

Previous topic - Next topic

Risae

Hi!

the game "Growlanser 5" got an english release on the PS2, and with it a proper font and a variable width font.
Sadly, when developers made Growlanser 6 they didn't port (as far as i can see) the VWF code and all characters are fixed width.
I would like to port the VWF over to GL6, somehow.

I have been reading a bit about finding VWF code in the games executable and in the RAM while the game is running.
As far as i understand, i would have to do the following:

- Find a function that calls certain text and put a breakpoint on it
- once the breakpoints hits, trace the Routine back to its origin and find the code that tells the game how to display characters on screen

I have text that i can use for this, for example "Removing". I am using PCSX2dis with pcsx2-1.2.1-r5875 for this demonstration:

I prepared the game and set up the breakpoint:

Spoiler
[close]

The breakpoint hits:
Spoiler
[close]

I don't really have much MIPS assembly knowledge.
What should i do next to find the text routine?
In the call Stack there is something else written down, but when i go to that address i don't really know what i am looking at:

Spoiler
[close]

Anybody got some tipps?

paul_met

Is the PCSX2 debugger capable of displaying the coordinates of primitives? If not, then personally I have no idea how to find a VWF routine.
But usually, the characters step value should be somewhere near to the routine of reading  character's code.
Meduza Team

Risae

Hi paul_met,

Quote from: paul_met on April 07, 2020, 12:32:52 PM
Is the PCSX2 debugger capable of displaying the coordinates of primitives? If not, then personally I have no idea how to find a VWF routine.

I am not quiet following on that, what exacly are primitives? Could you explain what those are?
The current PCSX2 debugger is sadly not that great, so i have been using the PCSX2dis tool:

https://forums.pcsx2.net/Thread-PCSX2dis-v1-1-A-ps2dis-inspired-PCSX2-enabled-Game-Hacking-Tool-W-I-P-13-02-2015

paul_met

Quote from: Risae on April 07, 2020, 12:38:23 PM
I am not quiet following on that, what exacly are primitives? Could you explain what those are?
Primitives are polygons on which textures are superimposed. Either each displayed symbol is a polygon, or the textures of all symbols are collected in one large polygon. I judge by the example of PS1 or Saturn.
Meduza Team

Risae

Quote from: paul_met on April 07, 2020, 12:43:24 PM
Primitives are polygons on which textures are superimposed. Either each displayed symbol is a polygon, or the textures of all symbols are collected in one large polygon. I judge by the example of PS1 or Saturn.

Hm, thank you for your reply, but that might be a little bit above my skill right now. I apologize.

I wanted to give my thread a little update, because i have awesome news!

The super-MIPS-Hacker called "Ethanol Pwned" has been helping me a lot the last few days on figuring out the Text Routine and he was able to find the VWF code in Growlanser 5! And not only that, he managed to make a first test on porting it to Growlanser 6!:

Spoiler

Hello again!

QuoteBut i couldn't find anything like that in the ELF.

I got news! I've just found the table, it's at address 0x314C68 inside GL5's ELF.
It's a simple 1Byte-per-character table and starts with zeros, the way to get the width of any given character is to add the character's hex value to the table's base address (so to get letter A's width you just add 0x41 to 0x314C68 and so on)

This also unveils the incredibly simple VWF function, it actually is z_un_002622d8, and the other one I mentioned before (z_un_0025b9d0) was actually the textbox draw function :P

QuoteSo, i think porting the Text Routine over to GL6 "should" be possible.

With this information, the only thing left to do in order to try to make the port is to find the exact bit in GL6's font routine that sets the constant character width and replace it with this one. Only roadblock left is where to fit the table inside GL6's ELF (or where to load it) after overcoming that, only thing left would be testing for any problems that the port could've caused (Thinking about special symbols, like button prompts).
[close]

I stumbled upon this spot in the ELF in the past already, but i didn't believe that it would have anything to do with the text.
The width values are 1 byte each, and to get to the letter that you want to modify you just have to add the hex value of the letter to 0x314C68.
As a little test i ported the GL6 font back into GL5 and changed all the values to see what happens:

Spoiler
[close]

Spoiler
[close]

Spoiler
[close]

Spoiler
[close]

Spoiler
[close]

After that he managed to deassamble the GL6 text routine even more, and assembled code so the VWF table is used by the Text Routine:

Spoiler

Hello again!

QuoteI did a little test to port the GL6 font into GL5 and change all of the values and see how it goes (see attachments)
And it worked like a charm.

It looks very funny :P

QuoteWould it be okay if i publish the information that you were able to find?

Sure, go ahead!

QuoteAgain, i can't thank you enough for all the help.

I see. Do you think it would be an idea to compare the GL5 JPN and ENG + the GL6 JPN text routine?
Seeing the differences should make it easier to find the place that stores those values, or?

GL5 (ENG) & GL6's routines seem different, in GL5's there's a "GetWidth" function that gets called for every letter, in GL6 the text routine calls a "DrawCharacter" function that parses two bytes at a time for SHIFT-JIS,
gets the texture sheet and more! I don't know most of the functions that this "DrawCharacter" function points to, but I know it adds the constant 0xA spacing in a simple loop.

The cool thing is that this adding with a loop is a just that, a simple "add the current string width + the currently drawn character width".
This is cool because we can let the game do the special characters spacing (Assuming it's different, I'm not sure) for us, and we only change the expected ASCII characters length.
With this in mind, I made a proof-of-concep (bear in mind that there's a any number of ways this could be accomplished, I've just done it this way for simplicity :P):

First I replaced the instruction at 0x00151DA8 (add.s f26,f26,f23) with a simple jump to an empty block in executable memory (I used 0x582000 for this experiment)
so we went from this:
0x00151DA8   add.s f26,f26,f23
0x00151DAC   addiu s6,0x1


To this:
0x00151DA8   j 00582000
0x00151DAC   nop


(We need to NOP the following instruction because MIPS executes that when doing the jump)
Now in 0x582000 I just did this simple ASM function:
slti s0,s1,0x007F     ;s1 holds the currently drawn character, so I just check if it's in the ASCII range (less than 0x7F)
beqz s0,original      ;If it's not in the ASCII range, we just execute the original addition
nop
li s0,0x00583A00      ;I load the width table's position to s0
addu s0,s1            ;Add the current character to the table's address, just like GL5
lbu       s0,(s0)     ;Store the value to s0
mtc1      s0,f23      ;Put the new width value into f23
nop
cvt.s.w f23,f23       ;Convert the value to a proper float


.original:
add.s f26,f26,f23     ;Add the current character's width (f23) to the line width (f26)
addiu s6,0x1          ;Adds 1 to the character counter? I'm not sure, but it was there in the original
j 00151DB0            ;Go back to the original function

After putting the width table in the address I designated.... it works! And it doesn't nuke the Japanese text rendering either, cool.



It also works on the normal speech boxes (the weird displaced "l" is because the "l" isn't aligned to the left of the texture for some reason)



(I wonder if some menus would get misaligned because of the VWF tho)

Now, to make this permanent (remember, all of this was made in RAM as an experiment) we would need:
- 0x7F bytes for the table, the game loads the window definitions from disc at start-up, so if we can change one of those files and add the table, that will work - About 0x38 bytes for that bit of ASM (maybe a little bit more), for this one we could either find a dead function we can safely remove, alternatively, we could try expanding the ELF file.
(there's also the possibility that if we nuke the SHIFT-JIS part of the "DrawCharacter" function that might leave us with enough space at the expense of Japanese text rendering :P)

I think the better option would be to just expand the ELF file, as it has the benefit of making ELF strings of any length (which you might need) however, I'm not too sure how to expand a PS2 ELF.
[close]

I ported the code he wrote into a free space i found inside the ELF. It didn't work that properly, but it was a start:

Spoiler
[close]

Spoiler

Awesome!
I actually think I know why this is, if you notice, the wonky letters follow a pattern, when they look good is when they are in pairs, this is a result from the "DrawChar" function, it draws 1 "character" at a time (1 SHIFT-JIS character that is, and those are 2 bytes, so for us it draws 2 at a time).

I stepped out two functions and saw that there's a constant 0x14 added to the DrawChar X parameter, so this might be the reason.
Let's try this out as a solution:
Go to 0x00152384 and NOP that function (This way we don't discard our VWF "X" value)
Then go to 0x002792D4 this bit adds 0x14 to s1 in order to draw the next character.
As we can't set it to 0 (that would make every character render on top of each other) we are going to use our saved X value.
For that we do:

0x002792D4    cvt.w.s f26,f26   ;Convert the value in f26 to an integer of the same value
0x002792D8    mfc1 s1,f26       ;Move to our X value variable (s1)
0x002792DC    addiu s2,0x1      ;Add 1 to s1 like in the original
0x002792E0    b 0x00279058      ;Branch from the original
0x002792E4    nop               ;NOP from the original


That should make them render at the correct X position every time, try it and tell me how it goes!

On an unrelated note, the FLK files actually have a size field, offset 0x14 from the start of the FLK header, and the files themselves are all aligned to 0x200 boundaries, probably to make them all start with an LBA? If we assume the other Fields are the same across files we could trivially reconstruct a bigger FLK file, but it needs some investigating.

[close]

So i did another test and input the suggestions he mentioned:

Spoiler

OLD
0x00151DA8 (0x51E28)  add.s f26,f26,f23 80 D6 17 46
0x00151DAC (0x51E2C)  addiu s6,0x1 01 00 D6 26

NEW
0x00151DA8 (0x51E28)  j 003D7920 48 5E 0F 08
0x00151DAC (0x51E2C)  nop 00 00 00 00


VWF Code 0x2D79A0 (ingame 003D7920)

slti s0,s1,0x007F 7F 00 30 2A ;s1 holds the currently drawn character, so I just check if it's in the ASCII range (less than 0x7F)
beqz s0,0x003D7A80 50 00 00 12 ;If it's not in the ASCII range, we just execute the original addition
nop 00 00 00 00


li s0,0x003D7F80 ;I load the width table's position to s0

lui s0,0x003D 3D 00 10 3C ;Set the upper bytes of s0 to 0x003D
ori s0,0x7F80 80 7F 10 36 ;OR s0 to 0x7F80


addu s0,s1            21 80 11 02 ;Add the current character to the table's address, just like GL5
lbu s0,(s0) 00 00 10 92 ;Store the value to s0
mtc1 s0,f23 00 B8 90 44 ;Put the new width value into f23
nop
cvt.s.w f23,f23 E0 BD 80 46 ;Convert the value to a proper float

.original Code 0x2D7B00 (ingame 003D7A80)
add.s f26,f26,f23 80 D6 17 46 ;Add the current character's width (f23) to the line width (f26)
addiu s6,0x1 01 00 D6 26 ;Adds 1 to the character counter? I'm not sure, but it was there in the original
j 00151DB0 6C 47 05 08 ;Go back to the original function

VWF Table at 0x2D8000 (ingame 003D7F80) :

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 05 06 0A 09 0F 0E 03 05 05 07 0A 05 09 04 09
09 09 09 08 09 08 09 0A 09 09 04 05 0A 0A 0A 07
0D 0E 0B 0D 0E 0A 0A 0F 0E 06 07 0E 0A 10 0E 0F
0B 0E 0C 0B 0D 0D 0F 10 0C 0C 0B 06 09 05 09 0A
04 08 0A 07 0A 08 08 0A 0A 05 05 0A 05 0F 0A 0A
0A 0A 07 07 07 0A 0A 10 08 0B 08 04 03 04 0A 04

ELF Pos 0x52404
00152384 lwc1 f26,0x48(sp) 48 00 BA C7 > 00 00 00 00


Before 0x179354 (ingame 0x002792D4)

002792D4 addiu s1,0x14 14 00 31 26
002792D8 addiu s2,0x1 01 00 52 26
002792DC b pos_00279058 5E ff 00 10
002792E0 nop 00 00 00 00
002792E4 nop 00 00 00 00



After 0x179354 (ingame 0x002792D4)

002792D4    cvt.w.s f26,f26 A4 D6 00 46 ;Convert the value in f26 to an integer of the same value
002792D8    mfc1 s1,f26      00 D0 11 44 ;Move to our X value variable (s1)
002792DC    addiu s2,0x1    01 00 52 26 ;Add 1 to s1 like in the original
002792E0    b 0x00279058 5D FF 00 10 ;Branch from the original
002792E4    nop 00 00 00 00 ;NOP from the original
[close]

But there were still some issues when different text boxes were used:

Spoiler

I couldn't get the exact same buggy spacing you had for some reason, even after reconstructing the ISO with GL5's font. However, there were some minor spacing issues I've fixed some, but there are other parts that assume fixed width characters (Only the scrolling text tips so far) that are still kinda broken.

Now onto the fixing
First things first, your edits to the elf are good but you left the word "Original" between our new width calculation and the the original, nothing wrong with that as-is, but as the CPU can't differentiate between text or instructions that bit of text gets executed, good thing it didn't cause issues, the fix for that is to just add a jump at the end of our new width calculation to avoid ever executing "Original" as instructions, either that
or remove the text.

But we need to re-do that anyway, it seems that spaces widths are calculated differently, and that messes with the assumptions in the original asm hack, I made this updated version that correctly handles spaces (I'm not sure why are they handled differently, but it probably is to align menus so let's look for problems related to spaces)

It's here in this gist:
https://gist.github.com/Mc-muffin/3ef6ceeed308186a3ebd5da2d51f6877
Make sure to replace the labels with the correct addresses, and note that in the original table the space has width 0, I replaced it with 10 (0xA)

With that changes it looks like this:



If you notice, the "m" in "ye mighty" is wrongly spaced. This is another case like the one I explained before where we re-used the (leaked) width value (f26) in another function, the same function in fact.
So we just go to 0x002792BC (addiu s1,0xA) this is just a single instruction tho, so we need to make a detour, we replace that with a jump:
j 0x003D79DC (I used this address, just for testing, we only need 12 bytes so choose any one from our free space)

and in 0x003D79DC we just do this:
cvt.w.s f26,f26    ;Convert f26 to an integer
mfc1      s1,f26      ;copy it to s1
j          0x2792C0   ;Go back to the original function

And that fixes that!


Next step is figuring where the scrolling tips store the X value so we can detour that to the new VWF width. And finding weird spacing issues to iron out.
[close]


Big thanks to the help of Ethanol Pwned! Its great to see a working VWF working in Growlanser 6.
Once its fully operational i will update this thread.
I hope it might help other Romhackers in the future!