Getting the resolution address
In the previous post, we discussed how to find what resolution is a particular game using. Now, let's try to find where in the game code is it actually set.
Part 1. - Going through the game files
Let's start by checking the "app" folder. As you may know, original Vita games are encrypted (same with NoNpDrm rips, as they are 1:1 copy of the original game), but thankfully, with VitaShell we can comfortably browse decrypted game data. Why should we care for the game data? Well, some games do store the resolution in a separate config file, making our life much easier. Even though this is not going to be the case 99% of the time, we better check first as it only takes a couple of minutes and could potentially save us a lot of time.
In this example I will be using WRC 5 [EU] which is running at native 960x544.
By opening VitaShell, navigating to the ux0:app/ directory, finding the folder of our game and pressing △, a menu appears where we select Open decrypted.
Now we can browse decrypted game data and look for any text (configuration) file.
In the Common/Settings folder, we found this interesting file named Settings.cfg.
Indeed, this is where the game stores its' resolution.
Now it is only a matter of copying the file, editing the values and putting it back. Unless you're using a Mai/Vitamin dump, you can't simply replace the file as the original one is encrypted and we used a decrypted one (otherwise we wouldn't be able to read nor modify it). We will need to use rePatch to apply the mod.
Part 2. - Exploring the eboot.bin
Didn't find any configuration file with resolution values? No worries, this happens most of the time. We will now try to search for the values in the game binary executable.
- Hacked Vita
- Cartridge/PSN original or NoNpDrm game rip
- FAGDec by CelesteBlue & dots-tb (for dumping the eboot)
- IDA Pro 6.8 (32 or 64 bit)
- vitaldr by zecoxao
- fresh db.yml
- Python 3.x.x
- nidformat.py by xyzz
This guide uses the old vitaldr, even though newer tools are available now. For IDA 6.8 I'd recommend using VitaLoaderIDA instead - it's open source, uses db.yml directly (no need to build vita.txt nid list), properly decodes movw/movt pairs, adds function prototypes & useful comments, ... If you don't own IDA, give Ghidra with GhidraLoaderVita a try.
1. Setting up IDA Pro
Unfortunately IDA Pro is not a freeware tool and is quite expensive to buy ($3500+ for IDA Starter + ARM decompiler licenses). If you can't source it somewhere, feel free to use free alternatives such as VitaDecompilerMod, prxtool or Ghidra. The process is pretty much the same for all disassemblers/decompilers.
After you installed IDA Pro, copy vita.ldw to C:\Program Files (x86)\IDA 6.8\loaders\ (or to the loaders\ folder wherever you installed your IDA).
Now we need to create fresh vita.txt for vitaldr. You can skip this step entirely if you don't care that your NIDs list might be a little outdated, and instead use the one provided with vitaldr.
Copy downloaded db.yml to the same directory as nidformat.py. Open Windows PowerShell and navigate to the directory where nidformat.py is located by typing:
cd "dir_of_nidformat"in my case:
Run nidformat.py using the following command (for this you'll need to have Python 3 installed):
python nidformat.py db.yml vita.txt
A vita.txt file will be created in the same directory. Now copy this file to where you copied vita.ldw earlier (C:\Program Files (x86)\IDA 6.8\loaders\).
Your IDA should now be ready for disassembling.
2. Dumping your game
This is where we'll use FAGDec to dump the eboot.bin, which we can open later using IDA.
Simply install FAGDec.vpk on your Vita, open the app and find your game in the list. I'll be using MotoGP 14 [EU] (updated to 1.01) as an example.
Now press ✕ and wait a bit. If the game has a patch installed, you'll see two eboot.bin files, first in ux0:/app/ and second in ux0:/patch/. In that case you should select the one in ux0:/patch/, otherwise use the one in ux0:/app/. In this example I selected both.
Press ◯ to go back, and press SELECT. A decrypt menu will appear.
Choose START DECRYPT (ELF) and wait until it says Done.
Copy the dumped eboot.bin.elf to your PC using FTP or USB connection (use VitaShell). It'll be located in ux0:/FAGDec/ on your Vita.
3. Disassembling the eboot.bin.elf
Start by opening IDA Pro and selecting File -> Open.
Navigate to where you copied your eboot.bin.elf and select it.
Make sure every option is set as on this screenshot and press OK. If you don't see Playstation Vita Relocatable Executable [vita.ldw] in the list, you didn't install vitaldr correctly.
Now wait. This might take anywhere from a few seconds to 30 minutes (depending on the .elf size, etc...).
It might look like it's stuck (like on the screenshot below) but it is not, just wait patiently. You can use your PC as usual in the meanwhile.
Once it's done, you'll see something like this:
Make sure it says idle in the bottom-left corner. If it doesn't, just wait for it to finish disassembling.
4. Searching for the resolution values
The easiest way to find references is to search for the original resolution the game is running at. To do this, simply press ALT + I (or select Search -> immediate value) and type in the vertical resolution, in this case 448. Make sure to select Find all occurrences.
Few things can happen:
- the resolution can be stored in the DATA section
- as a plain 32-bit integer (~20% of cases) (e.g. Borderlands 2, Killzone, Wipeout 2048, CoD Black Ops, ...)
- as a floating-point value (~5% of cases) (e.g. Persona 4 G, ...)
- the resolution can be specified as an instruction operand (~70% of cases)
- the resolution is not stored as a plain value (~10% of cases) (e.g. FATE/EXTELLA Link, Utawarerumono series, ...)
- combination of multiple things (~5% of cases)
In this case (MotoGP 14), the resolution is stored as an instruction operand. How do we know? We don't. We simply check every possible scenario, starting with the most common ones.
When it finishes searching and shows us the results, we click on the Instruction label to sort the results.
Then we scroll down, until we get to the instructions starting with MOVS.W. When searching for the resolution as an instruction operand, we only look for MOVS.W, MOV.W or MOV instructions. These simply take an immediate value, a number, and copy it to one of the CPU registers so they can be used (in a function call, etc...) after that.
Check each result, one by one, by double-clicking on it. When the IDA View opens, simply look around that line to see if you can spot another MOVS.W on another line that has the horizontal resolution. They are almost always going to be near each other (< 10 lines). If you don't see one, simply go check the next search result. In this case we're looking for MOVS.W Rx, #0x2C0 (0x2C0 being 704 written as a hex value).
Voila, there it is.
0x2C0 = 704
0x1C0 = 448
We found a place where both Width and Height are used in a MOVS.W instruction near each other. There might be more than one place (in this case there is just one) like this, which means we would have to write down addresses of all locations.
The address of the MOVS.W for width is 0x8152B58A in the segment 0 (seg000:8152B58A). To get a relative offset we need to subtract the segment start address from this value.
Simply press SHIFT + F7 to open the view with list of segments. As we can see, seg000 starts at 0x81000000.
So, 0x8152B58A - 0x81000000 = 0x52B58A which is our relative address in the segment 0. We can use this address to patch the value using a taiHEN plugin (such as VitaGrafix).
Similarly, the MOVS.W for height is 0x8152B590 - 0x81000000 = 0x52B590 in the segment 0.
Now let's look at an example where the value is stored in the DATA section. We'll use Borderlands 2 [EU] (updated to 1.07).
We know that the game is running at native 960x544. So just like before, let's search for the height value (544) using the immediate value search function (ALT + I).
Instead of MOV/MOVS, we'll look for DCB/DCD "instructions". It might be very time-consuming to go through each result hit manually, especially if a lot of them are found during the search. What we can do instead is search for both width and height together. Since we're looking for plain 32-bit values, we'll do the ALT + B (sequence of bytes) search.
Convert the width and height to hex values:
960 = 0x3C0 (width)
544 = 0x220 (height)
expand them to 32-bits each (8 hex digits), by adding leading zeroes:
0x3C0 = 0x000003C0 (width)
0x220 = 0x00000220 (height)
and reverse the byte order to match ARMs' little-endianness:
0x000003C0 = 00 00 03 C0 -> C0 03 00 00 (width)
0x00000220 = 00 00 02 20 -> 20 02 00 00 (height)
Now simply put these next to each other, and do the search. Make sure to select Find all occurrences and specify that it is a Hex binary string.
As we can see, only one search result has come up:
and by double-clicking we get to:
This time it's in segment 1 (seg001:82132A94), so we do the same thing as before, SHIFT + F7 to open up the segment view, subtract the segment 1 start, and we get 0x82132A94 - 0x8210E000 = 0x24A94 relative address in the segment 1 for the width and 0x82132A98 - 0x8210E000 = 0x24A98 for height.
Searching for float values in the DATA section has the same exact process. (e.g. Persona 4 Golden)
Instead of converting the width and height directly to hex values, we convert these to a hex float values. We can use this handy calculator.
840.0 = 0x44520000
by swapping the bytes we get:
0x44520000 = 44 52 00 00 -> 00 00 52 44
And that's it!