DialogueText in r8
3b7b3cc0k FF 50 58 48 C7 03 0F 00 00 00 48 83 3B 10
Hooking the fancy center text
All for a 50 second cutscene…
- +4897E (E8 EDDAFFFF) potential for fancy center text
- Center text appears in memory after E8 FBF01900
- +1E7BD8
- Center text appears after (E8 D3AC3F00 EB 07)… at address 85A04620?
- Not in registers yet
- Invoked twice
- Center text appears after (E8 D3AC3F00 EB 07)… at address 85A04620?
- ==+1E7C14 (FF 50 20 8B 45 67 48 C7 45 F7 0F 00 00 00)==
- RDX has center text after, offset by +40
- Invoked twice
- First byte is probably how many paragraphs there are
- After sentence is
00 02, from that position:- Offset 16 is
42 - Offset 36 is the next sentence
- If end with CD, there’s no more paragraphs
- Offset 16 is
- +1E7DA5
- Center text moves to regular address range
- Spammed for every cutscene text in RDI?
- +1E7EB1 (E8 9A2EE2FF) AFTER
- RCX go backwards
- RDI go forwards
- Invoked for each paragraph, granblue strategy?
- Find hook that invokes only on center text
telop01 gets extracted during each change in fancy center text
ONENTER: HotPotato
telop01
Callstack:
0x1401ef046,
0x1401eea8a,
0x1401e77c2,
0x1401e7a7e,
0x1402480c1,
0x1402468bc,
0x1402fd418,
0x140653d5b
ONENTER: HotPotato
telop00
Callstack: 0x1402ef8da,
0xfffffffffffff,
0x1402ef917,
0x1402f0768,
==0x1401ef09f, called for both telop00 and telop01==
- Not found in callstack of telop01, had to manually step through code to figure this out
0x1401eea8a,
0x1401e77c2,
0x1401e7a7e
Dialogue Text and Notification Banner Are Roommates?
ONENTER: HotPotato
おはよう。<CR>今日も元気そうね
Callstack: 0x140280cac,0x140049516,0x140048b26,0x140047a3a,0x1400453ff,0x14004ad7f,0x1401e77c2,0x140246940
Need to ignore this one
ONENTER: HotPotato
クエスト「わたしの『お仕事』」が発生しました <IM19>
Callstack: 0x140280cac,0x1400e9157,0x1400e887c,0x1400e8916,0x1401f3ce7,0x140240086,0x14060fc22,0x14066358c
0x14004b868 only for overworld text? probably not

If I hook 0x140049516 (+49516 in screenshot), it’ll get executed by both dialogue text and overworld dialogue. Hooking a function down the callstack not only means that text might not be directly in the registers, I’ll also have to tediously observe and replicate how the game performs the math to obtain text.
Is there a faster way to get a cleaner hook?
Below that is a je instruction that jumps to different address if certain conditions are met.

The instructions below the jump won’t be executed if the check passes.
It’s possible that either the dialogue text or notification banner can pass the check while the other fails it. If I hook the instruction that only one of the two can access, it gives us an opportunity to create separate hooks for each of them.
Let’s put a breakpoint on the call instruction below and see what happens… The result is that the mov instruction only gets triggered by the dialogue text; success! I’ll have to figure out where the text resides in memory at this place however, as R8 is all zeroes.

When I hooked 0x140049516 (+49516) earlier, the text address was in R8, but where did that address come from? Let’s reverse and see how R8 was calculated.

Let’s rewind to the previous instruction at +49516 and say that value of R8 from is the address 0x3B6E73A0 which contains our string “おはよう”.
graph LR stringAddress["0x3B6E73A0"] string(("おはよう")) subgraph R8 stringAddress end stringAddress --has--> string
That address originated from the instruction at +49508 where it data copied from another address 0x44B868F8.
graph LR stringAddress["0x3B6E73A0"] string(("おはよう")) calculatedAddress["0x44B868F8"] subgraph R8 calculatedAddress end calculatedAddress --has--> stringAddress --has--> string
Looking above, the address was obtained at +494FB when the lea instruction added 0x28 to the address of RBX and loaded the result into R8. 0x44B868F8 subtracted by 0x28 is 0x44B868D0, so the final graph should look something like this at that point:
graph LR stringAddress["0x3B6E73A0"] string(("おはよう")) calculatedAddress["0x44B868F8"] rbxAddress["0x44B868D0"] subgraph R8 calculatedAddress end subgraph RBX rbxAddress end rbxAddress -.+28.-> calculatedAddress --has--> stringAddress --has--> string
Now we go back to the call function beneath the jump and hope RBX+28 still has the string address even after all the call instructions. Because I’m advancing the text, the addresses in Cheat Engine will be different compared to the graphs above.

Show the address in RBX in hexview, move up by 0x28 or 40 bytes, convert the address in little endian to big endian, jump to that address in Cheat Engine…

And there’s the string.
Porting over the steps into JavaScript, we get the following code:
function dialogueTextHandler() {
const rbxAddress = regs.rbx;
const r8Address = rbxAddress.add(0x28);
const text = r8Address.readPointer().readUtf8String();
return text;
}