[Tutorial] - JAP Games Font Size Hacking ( Nakoruru - SEGAGAGA - Vermillion Desert - Puyo Puyo 4 )

User avatar
VincentNL
blackout!
Posts: 132
Contact:

[Tutorial] - JAP Games Font Size Hacking ( Nakoruru - SEGAGAGA - Vermillion Desert - Puyo Puyo 4 )

Post by VincentNL »

Thanking everyone in DC scene who helped me out in the past, I'd love to take this chance to give back to the community! :)

Special credits to Nanashi for introducing and teaching VFW concept / SH4 assemblers, Exant for bitflag ops, Mr.Neo, Ian Micheal and Derek for providing help on DC tools, sharing their HW and RE knowledge!

This tutorial is intended exclusively for educational purposes, it does not have any intention to damage SEGA and/or any other third party developers who owns the rights.
If this does cause any issue, please feel free to remove it anytime.



Preface:

Anyone getting into JP games translation will sooner or later get into a common roadblock.
Let's say our game letters are usually 24x24 px in size ( Wide ), but we need 12x24 px ( Half-width) to efficiently use western characters and optimize text area.

Image



--------------------


How does this method work?

It is based on the assumption that our game adds one letter at a time.

Image

Our target is tracking a RAM variable (character count or offset), leading us to text-drawing function parameters.
In the end we'll alter original function to reduce distance between each letter:


Image

This method advantages are:

[list][*]You can draw a smaller font than the original without resizing the texture
[*]Words can use any custom size, smaller than wide.
[*]It's variable font width-ready[/list]


--------------------

Click Here for "NAKORURU - Ano Hito Kara no Okurimono (JP)"

Click Here for "SEGAGAGA (JP)"

Click Here for "VERMILLION DESERT (JP)"

Click Here for "PUYO PUYO 4 (JP)"



What you will need:

[list][*]Ghidra 10.0.2 or higher
[*]Basic SH-4 asm knowledge
[*]Demul 0.7
[*]Cheat Engine 7.2[/list]


--------------------

DC Font Width Size Hacking Tutorial by VincentNL:

[list]
Index

[*]PART 1-1: SETUP - Ghidra
[*]PART 1-2: SETUP - Cheat Engine
[*]PART 2-1: SEARCH RAM VARIABLE
[*]PART 2-2: LOCK THE VARIABLE
[*]PART 2-3: LOCATE PARAMETERS
[*]PART 2-4: WREAK HAVOC!
[*]PART 3: BREAKPOINTS
[*]PART 4: GHIDRA!
[*]PART 5: HACKING!
[*]PART 6: TESTING![/list]


-------------


[list][*]PART 1-1: SETUP - Ghidra[/list]

1) Let's open Ghidra and drag game executable (1ST_READ.bin, in this case) in the program window.

2) Choose language processor as SuperH4 32 LE, click on OPTIONS button to specify base address 8C010000

Image

3) Once Ghidra loads the file, click on "Auto Analyze".

Image



[list][*]PART 1-2: SETUP - Cheat Engine[/list]

1) Let's open Demul and load the game.

2) Open Cheat Engine, and click on "Open Process" icon:

3) Select Demul with game name, and click Open button:

Image


--------------

[list][*]PART 2-1: SEARCH RAM VARIABLE[/list]

1) Choose any part of the game where text is rendered one letter at a time:

Image

2) Make a savestate before text is shown

3) Switch to Cheat Engine window when text start to show up,If done correctly, Demul will freeze.

4) Let's search for an "Unknown inital value", and hit "First Scan" button:

Image

5) Go back to Demul, and let it continue to draw a few more letters then go back to Cheat Engine.

6) From this time on we will choose "Increased Value" and hit "Next Scan":

Image

7) Now, repeat steps 5-6 until you get a number of results below 500.
Please note, the "Next Scan" is relative to the number of text words rendered!
In case you have exhausted all words and results are still over 500, you can reload the savestate
but you will have to choose "Decreased value" since the value has reset to a lower number.

8) Select results over 0x2c010000 range and ignore those before. Right-click "Add selected..." to put them into Cheat Engine main window.

Why ignore the rest?

Demul allocates executable program memory at 0x2c010000 instead of 0x8c010000!
So yeah, keep this in mind when looking at 0x2c.. in Cheat Engine. Those will be 0x8c.. in Ghidra!

Image


[list][*]PART 2-2: LOCK THE VARIABLE[/list]

Ok now that we have a range of variables, we need to find out which one is handling text offset.

1) On Demul, wait until a couple of letters are rendered in game, then jump to Cheat Engine:

2) Lock all values in main window, by selecting them all and press spacebar. A red X will show up near each address.

3) Reload your savestate



[list][*]PART 2-3: LOCATE PARAMETERS[/list]

*Case A*

If you are lucky, one of those variables will cause text to clutter on the very same spot.
enable/disable locked values until you figure which one is our target, then proceed on this guide step 2-4.

*Case B*

No changes found. We will have to start looking into those address and repeat the process until we find the correct parameters.
Usually RAM values are stored outside of game executable memory, if you check in Ghidra 0x8c0da3dc is where game executable ends.
So let's choose the next one: 0x2C4176D8




[list][*]PART 2-4: WREAK HAVOC![/list]

1) Right click on the value and choose "Browse this memory region":

Image

Usually this present as a set of values, short, long, byte or floats arrays.

Image

2) Now, start messing with them during text rendering will lead you to actually reverse engineer their purpose.
Remember, our target is to have all letters clutter in the same offset! So yeah just "00" each parameter or lock them with "0" value.

Image

Bingo!

We can notice that altering the address 0x2C4176E8 value, affects space between each letter.
The more we reverse engineer now, the easier will be to read the original function in Ghidra.
(keep note of every parameter effect / memory address)


--------------

[list][*]PART 3: BREAKPOINTS[/list]


1) First of all we know 0x8C4176E8 (0x2C4176E8 in Cheat Engine), is a RAM parameter produced by a function inside game executable.

2) We need to track down WHICH function is writing to 0x8C4176E8 and we'll need a breakpoint.

3) Reload the game when that value has not been written yet. (at the beginning of scene)

4) Immediately jump in Cheat Engine Memory window, right click on the value then, --> data breakpoint / break on Write:


Image

5) Now, go back in Demul until game will stop and write down the address in EBX register:

Image

--------------------

[list][*]PART 4: GHIDRA[/list]

You can use any similar tool, but Ghidra is my personal fave. It's free and perfect for the purpose.

1) Go to the address found in EBX register:

Image

Cheat Engine debugs the emulator layer, so it won't work like a proper debugger. But that's fine.

1) That address is a long value, accessed by multiple functions so let's inspect them to find a 0x8C4176E8 write.
(double click on more)

Image

2) Let's follow the first write

3) scroll down the function carefully until you notice a write to 0x8C4176E8:
Image

4) Label it (click on address in red, and press L) as "ram_character_spacing" so it's easier to refer.


--------------

[list][*]PART 5: HACKING![/list]

Let's inspect how this works:

[code]8c019388 c2 62 mov.l @r12=>DAT_8c0637d4,r2 = 0000002Ch
8c01938a 2a 1d mov.l r2,@(0x28,r13)=>ram_character_spacing[/code]

Basically whatever we found in EBX register, were an actual value that gets copied in "r2".
What interest us is that r2 will be finally passed to r13 for being written.

It would be ideal to rewrite the function since it may have different use of that same parameter, but it might not be necessary.
We'll just check the instructions below:

Image

1) As you can see r2 parameter isn't reused until 0x8c01939e, where a new function is stored having a different purpose.
So yeah we may proceed to change r2 value (character spacing), to our likings.

2) We'll use 0x10 as size, so: mov #0x10,r2.
Right click on 0x8c019388 in Ghidra and click on "Patch instruction", we will change:

[code]mov.l @r12,r2 --> mov #0x10,r2[/code]


--------------

[list][*]PART 6: TESTING![/list]

You can now choose to rebuild your game or test it live.
To preview changes, just go back to Cheat Engine at 0x8c01938a and replace 2A1D --> 10E2

Boom!


Please note this method will help you to track down progressive text rendering.
Even if it won't magically patch all different text functions of the game, it should provide a huge boost in RE hopefully making your life easier.



Image
Last edited by VincentNL on Wed Aug 03, 2022 3:22 pm, edited 16 times in total.
If you like my work or just want to show some love:
https://ko-fi.com/vincentnl
https://www.patreon.com/VincentNL

User avatar
ateam
DC-Talk Addict
Posts: 510

Re: [Tutorial] - JAP Games Font Size Hacking

Post by ateam »

Incredibly awesome, and thank you so much for taking the time last week to show me the magic of Cheat Engine for Dreamcast hacking/debugging!

I just want to put this out there to anyone who sees this and thinks my Nakoruru translation patch can now easily have a modified font width:

It's far more complicated than that :lol:

This game features about a dozen text-rendering routines, most of which include logic for built-in font scaling (an annoyance, to say the least). However, Vincent's assistance has helped me tremendously and has at least let me find the functions that I need to modify, although I have now determined I will need some new code in order to work around the limited space of modifying the original functions.

Just as one example, the function Vincent showed where mov.l @r12,r2 could be safely changed to mov #0x10,r2 is the exception and not the rule, unfortunately. The rest of the functions (most, if not all), will re-use the same register for tile width, height, and scale. This is by design and was intended to conform to the game's original auto-scaling feature with small, normal, and large text.

In the below example of what I'm referring to, I have a few memory addresses labeled (e.g., ram_char_space_value). As you can see, #1 is setting the tile width (0x16 = 22), and #2 shows the tile spacing, as well as the proportional height/width for drawing the tile...

Image

My goal is to solve these with jumps/branches that will execute my modified instructions and then return back to the function.

Anyway, thank you again, my friend! This has proven to be invaluable for me. My arsenal has been lxdream-nitro (for its debugger), Ghidra (for the obvious), and Flycast's tracelog dumping -- and now I can add in Cheat Engine for RAM searching and on-the-fly modification! A thing of beauty, indeed.
Find me on...

DreamcastForever.com
GitHub
Reddit
SegaXtreme
Twitter
YouTube
• Discord: derek.ateam

User avatar
VincentNL
blackout!
Posts: 132
Contact:

Re: [Tutorial] - JAP Games Font Size Hacking

Post by VincentNL »

Yeah that falls in the "function rewrite" category, but there are a few tricks to avoid major rewrite if you just need to add a single instruction! ;)

1) Search for repeating instructions ending with rts, below the address you need to change within bra range

Image

2) Fill with nops (09 00) the area you want to clear

3) Now we want to let the function resume at the other address, so at the end let's bra over there.

4) Rearrange your function, and add the instruction you need!
(Unless it's finalized, keep a few extra nops in the empty area, just in case you'll want to add more instructions later on)



It really takes a minute or two:


Image
If you like my work or just want to show some love:
https://ko-fi.com/vincentnl
https://www.patreon.com/VincentNL

User avatar
ateam
DC-Talk Addict
Posts: 510

Re: [Tutorial] - JAP Games Font Size Hacking

Post by ateam »

VincentNL wrote:Yeah that falls in the "function rewrite" category, but there are a few tricks to avoid major rewrite if you just need to add a single instruction! ;)
Awesome! Very easy logic to follow. Now time to see how many of the functions can be given this same treatment. Will report back.
Find me on...

DreamcastForever.com
GitHub
Reddit
SegaXtreme
Twitter
YouTube
• Discord: derek.ateam

User avatar
VincentNL
blackout!
Posts: 132
Contact:

Re: [Tutorial] - JAP Games Font Size Hacking

Post by VincentNL »

"SEGAGAGA (JP)"


[list][*]PART 2-2: LOCK THE VARIABLE[/list]
Variable found at 0x8CAC5E58

Image


[list][*]PART 2-3: LOCATE PARAMETERS + PART 2-4:WREAK HAVOC[/list]

Offset parameter at 0x8CAC5E60, not directly expressed as distance per letter.
Messing with it alters the whole text offset.

Image


---------
[list][*]PART 3: BREAKPOINTS[/list]
Setting up a "breakpoint on Write" at offset parameter gives EBX: 0x8C12FA00, let's check it in Ghidra!

Looks like another set of parameters, by messing with them in Cheat Engine turns out they are offset floats/short values.

Image


[list][*]PART 4: GHIDRA[/list]

They all lead to FUN_8c0ae8e2, so we need to check all of those instructions, using H_offset modifiying registers to RE how that works.
Does this look any familiar? :D

Image

----------------

[list][*]PART 5: HACKING![/list]
Let's inspect how this work:

By a using a sheer amount of instructions starting from 0x8c0af066, the function creates a 0x18 multiplier ( 24 in decimals) that put every new letter, 24 pixels after the previous one.

[code] 8c0af05c 41 85 mov.w @(0x2,r4),r0=>TEXT_H_OFFSET = 15Ch
8c0af05e 2c 30 add r2,r0
8c0af060 f6 52 mov.l @(0x18,r15)=>local_34,r2
8c0af062 0d 64 extu.w r0,r4
8c0af064 f6 85 mov.w @(0xc,r15)=>local_40,r0
8c0af066 03 61 mov r0,r1
8c0af068 00 40 shll r0
8c0af06a 1c 30 add r1,r0
8c0af06c 08 40 shll2 r0
8c0af06e 00 40 shll r0
8c0af070 21 62 mov.w @r2,r2
8c0af072 35 d1 mov.l ->thunk_FUN_8c023b48,r1
8c0af074 2c 30 add r2,r0
8c0af076 0d 60 extu.w r0,r0
8c0af078 28 40 shll16 r0
8c0af07a 0b 41 jsr @r1=>thunk_FUN_8c023b48
8c0af07c 0b 24 _or r0,r4[/code]

Now since it's an hassle to customize, we want to simplify and optimize the function:

1) Create our own multiplier using 2 empty bytes below the function
2) Replace all those shll and add, with muls.w and sts.

[code] 8c0af05c 41 85 mov.w @(0x2,r4),r0=>TEXT_H_OFFSET
8c0af05e 2c 30 add r2,r0
8c0af060 f6 52 mov.l @(0x18,r15)=>local_34,r2
8c0af062 0d 64 extu.w r0,r4
8c0af064 f6 85 mov.w @(0xc,r15)=>local_40,r0
--> 8c0af066 6e 91 mov.w PIXEL_DISTANCE,r1 = 0005h
--> 8c0af068 1f 20 muls.w r1,r0
--> 8c0af06a 1a 00 sts MACL,r0
--> 8c0af06c 09 00 nop
--> 8c0af06e 09 00 nop
8c0af070 21 62 mov.w @r2,r2
8c0af072 35 d1 mov.l ->thunk_FUN_8c023b48,r1
8c0af074 2c 30 add r2,r0
8c0af076 0d 60 extu.w r0,r0
8c0af078 28 40 shll16 r0
8c0af07a 0b 41 jsr @r1=>thunk_FUN_8c023b48
8c0af07c 0b 24 _or r0,r4[/code]


---------

[list][*]PART 6: TESTING![/list]

There we go!

Anyone interested on SGGG stranslation, feel free to use the code.

Image
Last edited by VincentNL on Fri Apr 15, 2022 4:37 pm, edited 1 time in total.
If you like my work or just want to show some love:
https://ko-fi.com/vincentnl
https://www.patreon.com/VincentNL

yzb
Developer
Posts: 130
Dreamcast Games you play Online: pso

Re: [Tutorial] - JAP Games Font Size Hacking ( Nakoruru | SEGAGAGA )

Post by yzb »

Very good tutorial

However, it should be noted that in a very few cases, the MACL register may have another purpose, which can only be known in context
If it's only a few multiplication runs, it's easier to use a shift

For example, X5
mov r0,r1
shll2 r0
add r1,r0


For example, X6
mov r0,r1
shll2 r0
shll r1
add r1,r0

User avatar
VincentNL
blackout!
Posts: 132
Contact:

Re: [Tutorial] - JAP Games Font Size Hacking ( Nakoruru | SEGAGAGA )

Post by VincentNL »

"VERMILLION DESERT (JP)"

Decided to make this one since Cheat Engine breakpoints are not usable here, so it's a slightly different procedure.

[list][*]PART 2-2: LOCK THE VARIABLE[/list]
Variable found at 8C8E88B0

Image

------------

[list][*]PART 2-3: LOCATE PARAMETERS + PART 2-4:WREAK HAVOC[/list]

Offset parameter at 0x8C8E88C4, affects distance per letter ( 0x18 = 24px ):

Image


---------
[list][*]PART 3: BREAKPOINTS[/list]
Setting up a "breakpoint on Write" at offset parameter gives an unusable EBX.
Parameters are relocating at each new text box, looking for 0x8C8E88C4 doesn't yield any result in Ghidra.

Do we need a emulator with debugger? Shall we give up?

Nope!

We have a series of values in parameters. As we know distance value is 0x18 and there's a fixed float 0x0AD723BF (-0.64) which is consistent between textboxes.

Image

How about we search for these in Ghidra to track the original function?
0x18 could lead up to a million of search results, so let's look for that float instead.


---------------

[list][*]PART 4: GHIDRA[/list]

By searching for the float we only have 4 results! That's definitely good.
Now let's check which function calls them, one by one:

Image


FUN_0x8c017b2, use the float on r0.
Does that 0x18 in r5 look any familiar to you? :D

Image


----------------

[list][*]PART 5: HACKING![/list]
Not much to say here, we can get away by changing mov instruction:

[code] 8c0187c0 18 e5 mov #0x18,r5[/code]
into:
[code] 8c0187c0 0c e5 mov #0x0c,r5[/code]


---------

[list][*]PART 6: TESTING![/list]
There we go!
Half-width for Vermillion Desert!

Image
If you like my work or just want to show some love:
https://ko-fi.com/vincentnl
https://www.patreon.com/VincentNL

User avatar
VincentNL
blackout!
Posts: 132
Contact:

Re: [Tutorial] - JAP Games Font Size Hacking ( Nakoruru - SEGAGAGA - Vermillion Desert - Puyo Puyo 4)

Post by VincentNL »

"Puyo Puyo 4 (JP)"

Tutorial for one of my fave DC games :)


[list][*]PART 2-2: LOCK THE VARIABLE[/list]
Variable found at 0x8CA36F68

Image



[list][*]PART 2-3: LOCATE PARAMETERS + PART 2-4:WREAK HAVOC[/list]

Offsets parameters at 0x8C8E88C4, please note these are dynamic floats stored into RAM.
They are being constantly updated to determine current letter offset.

Image

[list][*]PART 3: BREAKPOINTS[/list]
Setting up a "breakpoint on Write" at offset parameter and stepping into, leads to an instruction located at: 0x8C0CB174



[list][*]PART 4: GHIDRA[/list]

The instruction we have found belongs to FUN8c0ca540, which is a pretty long one.
According its several branches, we are dealing with control codes specific parameters.
What we want to do now, is to track down which float register is used to assign offset and modify it.
The easiest way is to nop (0x0900) those in code section and test in game.

Image


By nulling nearby instruction 0x8c0cb16c reveals our target being fr2

Image

let's label 0x8c0cb16c as text_h_offset and move on!


[list][*]PART 5: HACKING![/list]

First of all, whatever value fr2 is holding, on a basic level we just want to reduce the spacing between letters, right?
Assuming 24px being a full-tile size, first instruction that comes to mind is fadd with a -12.0 float value (0x000040C1).

Now that we have a plan, let's find a suitable space which is often a 0x0900 0900 0900 0900... area.
Luckily PP4 has alot of 0x0009000900090009 (they are not nop, just dummy!)
and the closest one is within bra and mov ranges:

Image

It's really alot of empty space for what we need to do but we're experimenting, so any optimization will come later on:

1) Let's change that dummy area to an usable nop area. [0009 --> 0900]

2) last two instructions will allow us to get back to the original function offset right after the bra jump:

[code] 8c0cb33c 18 af bra LAB_8c0cb170
8c0cb33e 09 00 nop[/code]


3) There's more empty space at 0x8c0cb0350 so let's add our new CUSTOM DISTANCE float (0x000040C1) there.
Image

4) Go back to the text_h_offset , copy those 2 instructions at the end of our new function:

Image
Image
To better explain, since we need 2 instructions to jump from the original position 0x8c0cb16c to our new code position 0x8c0cb324,
we still need to execute them for the game to work properly! So we'll move those near the end of our new code!

5) Cool. We need to verify if we made any mistakes and the game crashes:

Copy/paste as byte string the whole part we have altered in Ghidra (0x8c0cb16c to 0x8c0cb0354) into Demul RAM.
In our case at 0x2c0cb16c. (8c010000--> demul = 2c010000).
Be sure to enable writing in case Demul RAM is locked!

6) Let's make a save state before the scene loads with our new code already pasted in RAM.

7) Instruction time!

[code]
8c0cb324 0a c7 mova CUSTOM_DISTANCE,r0 = -12.0
8c0cb326 08 fb fmov.s @r0=>CUSTOM_DISTANCE,fr11 = -12.0
8c0cb328 b0 f2 fadd fr11,fr2
8c0cb32a 09 00 nop
8c0cb32c 09 00 nop
8c0cb32e 09 00 nop
8c0cb330 09 00 nop
8c0cb332 09 00 nop
8c0cb334 09 00 nop
8c0cb336 09 00 nop
8c0cb338 2a f1 fmov.s fr2,@r1
8c0cb33a e2 63 mov.l @r14,r3
8c0cb33c 18 af bra LAB_8c0cb170 # return to original function offset
8c0cb33e 09 00 nop
[/code]

Basically we read our CUSTOM_DISTANCE float address in r0, get the float in fr11 (unused)
and at last, simply add fr11 value to fr2.



[list][*]PART 6: TESTING![/list]
There we go!
Half-width for Puyo Puyo 4!

Image
Last edited by VincentNL on Fri Aug 05, 2022 11:52 am, edited 15 times in total.
If you like my work or just want to show some love:
https://ko-fi.com/vincentnl
https://www.patreon.com/VincentNL

User avatar
fafadou
Gold Lion
Posts: 1669

Re: [Tutorial] - JAP Games Font Size Hacking ( Nakoruru - SEGAGAGA - Vermillion Desert - Puyo Puyo 4 )

Post by fafadou »

Incredible topic dear Vincent !

colgate
Doom
Posts: 185
Dreamcast Games you play Online: PSO

Re: [Tutorial] - JAP Games Font Size Hacking ( Nakoruru - SEGAGAGA - Vermillion Desert - Puyo Puyo 4 )

Post by colgate »

Awesome!

  • Similar Topics
    Replies
    Views
    Last post