Introduction
More than 10 years have passed since the PSP was released. Since the day when developers could run unsigned codes, thousands of hombrew applications, games, plugins or other sorts of tools have been released to make the PSP great, literally everything of this device has been exploited to its fullest.
However there was something missing: right analog stick for camera movement.
PSP games were developed with special controls mapping in order to have camera movement. Some games use X, O, /\, [], some the digital pad, and then there are special ones which use L/R + left analog stick.
For the first and second case, mapping the right analog stick to the camera is easy enough, whereas for the L/R + left analog stick case, it isn't a good idea to just simulate the combo using the right analog stick. The reason therefore is logicial, yet many people oversee this little fact that you will not be able to use camera and walk and the same time using this kind of mapping; you can only do either of them.
After I have successfully gained native control over the pspemu on PS Vita, I have written the patch for my favourite PSP game: GTA LCS
In this topic I will explain how I have analysed and found the camera functions for this game.
The goal is to motivate people (developers) to write patches for their own games. Don't forget that every game is written differently, so you won't be able to find same code structure.
I tried to write the explaination in a simplictic way so anyone with enough programming knowledge, but no RE experience can follow and reproduce the final patch (I'm sorry if the tutorial is still hard to follow, as it is really difficult for me to describe the whole 2h process that I have done in words). If you are either a total beginner without any programming knowledge, or a talented reverse engineer, this tutorial is not addressed to you.
Some skills you must have:
- Intermediate programming knowledge
- Basic understanding of MIPS assembly.
- Basic maths
Other requirements:
- GTA LCS GER (ULES-00182 v1)
- UMDGen
- pspdecrypter for decrypting eboot.bin
- Prxtool to output eboot.bin as mips assembly code
- Pspdecompiler to output eboot.bin as pseudo-c
So let us begin:
1) Dump eboot.bin of the game using UMDGen
2) Decrypt it if it isn’t yet using pspdecrypter
3) Output the assembly code with: prxtool –w eboot.bin > eboot.asm
4) Find the module name with: prxtool –m eboot.bin
5) Output pseudo code with: pspdecompiler –c eboot.bin > eboot.c
6) Available as download is my included plugin sample, where you need to overwrite GTA3 by the module name you have found. If you're currently doing it with GTA LCS, you don't need to do this step as the module name of GTA LCS is already GTA3.
In GTA, you have got the camera mapped to L+left analog stick. Imagine this combo as such a code:
SceCtrlData pad;
sceCtrlPeekBufferPositive(&pad, 1);
if (pad.Buttons & PSP_CTRL_LTRIGGER) {
...camera stuff using pad.Lx/pad.Ly
}
Where PSP_CTRL_LTRIGGER is 0x00000100
We are exactly looking for such a code in our game, but first of all we need to find where exactly the ctrl API is used within the code:
Those are the ctrl functions which are commonly used:
- sceCtrlPeekBufferPositive (nid: 0x3A622550)
- sceCtrlPeekBufferNegative (nid: 0xC152080A)
- sceCtrlReadBufferPositive (nid: 0x1F803938)
- sceCtrlReadBufferNegative (nid: 0x60B81F86)
Find which of them (maybe more than only one) is used in your game by searching the nid code in the assembly file. For GTA you will find this:
; ======================================================
; Subroutine sceCtrl_1F803938 - Address 0x00307934
; Imported from sceCtrl
sceCtrl_1F803938: ; Refs: 0x00066CAC 0x001BE7E4 0x001BE81C 0x002D17F0
0x00307934: 0x03E00008 '....' - jr $ra
0x00307938: 0x00000000 '....' - nop
Note that ‘Refs:’ lists all the addresses that jump to this function.
To find out which call is used for the camera movement, we can now do this trick:
if (strcmp(modname, "GTA3") == 0) {
_sw(0, text_addr + ref0);
_sw(0, text_addr + ref1);
…
_sw(0, text_addr + refn);
}
We
nop (no operation/set to zero) all the references so to
prevent the ctrl function from working. Compile the plugin using the references that you have just added and launch gta using the patch.
Our goal is to find out
which jump responsible for in-game controls is by testing the game running with the patch plugin. Instead of just testing them one by one, we'd rather do a manual binary search, simplified: you test half of them:
if the buttons don't work in the game anymore, you have patched the good half. Ignore the other half then. Do the same again and again until you
end up having the exact jump which is responsible for the camera.
For GTA, this is the address:
0x00066CAC: 0x0C0C1E4D 'M...' - jal sceCtrl_1F803938
Now you want to know where the start of its subroutine is by looking at the prxtool around this specific address.
Backward-search the term 'sub_' and you'll find sub_XXXXXXXX where XXXXXXXX is the address of the method.
In this case:
; ======================================================
; Subroutine sub_00066C88 - Address 0x00066C88
sub_00066C88: ; Refs: 0x00033FF0
Now it's time we look at the pseudo code generated by pspdecompiler. We search the address of the subroutine 0x00066C88.
In the pseudo code you will find the ctrl function again, for GTA it's here:
var3 = sp + 0x00000014;
var6 = sceCtrl_1F803938 (var3, 0x00000001); // Note that var3 is actually the SceCtrlData structure (pad).
Note that pad.Buttons entry is at
pad+4 (sp + 0x00000018) and
pad.Lx is at pad+8 (sp + 0x0000001C), pad.Ly at pad+9 (0x0000001D).
How is pad handled? Is it saved globally, is it copied to the function's parameters, or is it directly used in the code?
Later in this subroutine we find:
if (var6 == 0x00000000) <- no analog
{
((char *) sp)[18] = 0x00000080;
var13 = 0x00000000;
((char *) sp)[19] = 0x00000080;
}
else
{
var13 = ((int *) sp)[6]; <- buttons
var14 = ((unsigned char *) sp)[28];
var15 = ((unsigned char *) sp)[29];
((char *) sp)[18] = var14; <- left analog x
((char *) sp)[19] = var15; <- left analog y
}
((char *) sp)[16] = 0x00000080; <- dummy right analog?
var16 = ((int *) var2)[21];
((char *) sp)[17] = 0x00000080; <- dummy right analog?
((int *) var2)[22] = ((var13 ^ var16) & var13); <- pressed buttons
((int *) var2)[21] = var13; <- current buttons
Where
((int *) sp)[6] is equivalent to
*(int *)(sp + 6 * 4) (6 * 4) is 24, which in hex is 0x00000018. If you remember, that's the same value we have found when we looked at the ctrl's parameter.
At the beginning of the subroutine we see that
var2 = arg1, so this means the ctrl functions are now copied to the first parameter of this subroutine. Let us rename
sub_66C88 to
readbuttons in the whole file. so we can easiely find it again.
If you search for this function you'll end up with the following subroutine: sub_33FE8 which calles
readbuttons ((arg1 + 0x00000008));
You do the same process again: rename sub_33FE8 to something understandable, look where it is called.
Tip, it's 0x00294E84:
We can now see that the ctrl input is saved in the global variable 0x00356E14:
var11 = *((int *) 0x00356E14);
var12 = 0x43000000;
__asm__ ("mtc1 $a1, $fpr26;"
:
: "=r"(0x43000000));
if (var11 == 0x00000000)
{
sub_2F664C ();
var11 = *((int *) 0x00356E14);
}
else
{
}
sub_33FE8 (var11);
Later in this function we also see that there is a & 0x00000100 which, if you remember, is used to recognize if the L trigger is pressed.
var61 = *((int *) 0x00356E14);
var62 = 0x00000000;
if (!(var61 != 0x00000000))
{
sub_2F664C ();
var61 = *((int *) 0x00356E14);
}
// check L trigger input
var65 = ((int *) var61)[23]; // note that var61 is from the global variable 0x00356E14 that I have just mentioned
if ((((0x00000000 < (var65 & 0x00000100))) & 0x000000FF) != 0x00000000)
{
var62 = 0x000000FF;
}
((short *) var1)[5] = var62; // set L trigger action in var1, where var1 is the first parameter of this subroutine
We see that ((short *) var1)[5] is set to 0xFF if L trigger is pressed. To confirm this without understanding much of the code, we can simply hook the whole method to our custom one, and then force this value.
This trick can be applied to any subroutine and it's highly suggested that you do it for others too.
Write this in the OnModuleStart method (remove the ctrl nop patches again of course):
HIJACK_FUNCTION(text_addr + 0x00294E84, buttonsToActionPatched, buttonsToAction);
and this in the scope:
int (* buttonsToAction)(void *a1);
int buttonsToActionPatched(void *a1) {
int res = buttonsToAction(a1);
// L trigger counter
((short *)a1)[0] = 7;
// Simulate L trigger
((short *)a1)[5] = 0xFF;
// this blocks left and right analog movement
// ((short *)a1)[1] = 0;
// blocks up and down
// ((short *)a1)[2] = 0;
return res;
}
HIJACK_FUNCTION is already defined in the sample plugin.
Using this new patch in GTA you will now not be able to walk anymore but you can only move the camera.
The rest is now all the same, you find where 0x00294E84 (buttonsToAction) is called; how it is called.
We will find:
/**
* Subroutine at address 0x00292404
*/
int sub_292404 (int arg1)
{
var1 = arg1 << 0x00000006;
return ((var1 + (var1 + var1)) + 0x00385C60); // if arg1 is 0, the return is 0x00385C60
}
/**
* Subroutine at address 0x00292420
*/
void sub_292420 ()
{
sp = sp + 0xFFFFFFE0;
((int *) sp)[4] = ra;
var3 = sub_292404 (0x00000000);
buttonsToAction(var3, 0x00000000); // var3 is actually 0x00385C60
var8 = sub_292404 (0x00000001);
sub_292294 ((var8 + 0x00000002));
var13 = sub_292404 (0x00000001);
sub_292294 ((var13 + 0x00000034));
ra = ((int *) sp)[4];
sp = sp + 0x00000020;
return;
}
If you look at the code you can see that buttonsToAction's first parameter is actually 0x00385C60
Rename all 0x00385C60 to buttons_to_action_variable or something else that you can easiely find.
Now do all the steps again and find out where exactly buttons_to_action_variable is used (better, search for sub_292404 (this function returns buttons_to_action_variable as I have mentioned before)).
Remember, ((short *)buttons_to_action_variable)[5] is the LTRIGGER
The rest is now really a pain in the ***, you search this function and try to find somewhere where either analog stick or l trigger is used. I ended up finding these methods:
/**
* Subroutine at address 0x00292C5C
*/
int analogleftright (int arg1)
{
var1 = *((unsigned char *) 0x003522B4);
if (var1 == 0x00000000)
{
var5 = ((short *) arg1)[1];
}
else
{
var2 = ((short *) arg1)[12];
var3 = ((short *) arg1)[11];
var4 = var2 - var3;
var5 = (((var4 + ((var4 >> 0x00000001) >> 0x0000001F)) >> 0x00000001) << 0x00000010) >> 0x00000010;
}
return var5;
}
/**
* Subroutine at address 0x00292CA0
*/
int analogupdown (int arg1)
{
var1 = *((unsigned char *) 0x003522B4);
if (var1 == 0x00000000)
{
var5 = ((short *) arg1)[2]; // remember this value in the buttonsToAction patch I showed you?
}
else
{
var2 = ((short *) arg1)[10];
var3 = ((short *) arg1)[9];
var4 = var2 - var3;
var5 = (((var4 + ((var4 >> 0x00000001) >> 0x0000001F)) >> 0x00000001) << 0x00000010) >> 0x00000010;
}
return var5;
}
/**
* Subroutine at address 0x00293C28
*/
int ltriggerpressed (int arg1)
{
var1 = *((int *) 0x00354C70);
if (var1 != 0x00000000)
{
var5 = 0x00000000;
}
else
{
var2 = ((unsigned short *) arg1)[67];
if (var2 != 0x00000000)
{
var5 = 0x00000000;
}
else
{
var3 = ((short *) arg1)[7];
var4 = 0x00000000;
if (var3 != 0x00000000)
{
var6 = ((short *) arg1)[5]; // REMEMBER THIS?? ;)
if (!(var6 == 0x00000000))
{
var4 = 0x00000001;
}
}
else
{
}
var5 = var4 & 0x000000FF;
}
}
return var5;
}
I continued searching these functions and in the end found them in these camera functions:
/**
* Subroutine at address 0x00294C88
*/
int cameraLeftRight (int arg1)
{
sp = sp + 0xFFFFFFE0;
var1 = ((short *) arg1)[0];
((int *) sp)[4] = s0;
var2 = arg1;
((int *) sp)[5] = ra;
if (((var1 < 0x00000007)) != 0x00000000)
{
var26 = 0x00000000;
}
else
{
var5 = ltriggerpressed (var2);
if (!(var5 == 0x00000000))
{
var8 = sub_1D18B8 ();
var9 = ((int *) var8)[213];
if (!(var9 != 0x00000001))
{
var12 = sub_1D18B8 ();
var13 = ((int *) var12)[211];
if (var13 == 0x00000010)
{
label16:
((short *) var2)[0] = 0x00000000;
}
else
{
var16 = sub_1D18B8 ();
var17 = ((int *) var16)[211];
if (!(var17 != 0x00000011))
goto label16;
}
}
}
var18 = ((unsigned short *) var2)[65];
__asm__ ("mtc1 $zr, $fpr12;");
if (!(var18 < 0))
{
if (!(((var18 < 0x00000004)) == 0x00000000))
{
var21 = sub_292404 (0x00000000);
var24 = analogleftright (var21);
__asm__ ("mtc1 $v0, $fpr12;"
:
: "=r"(var24));
__asm__ ("cvt.s.w $fpr12, $fpr12;");
}
}
__asm__ ("mtc1 $zr, $fpr13;"
"c.le.s $fpr12, $fpr13;");
__asm__ ("bc1t 0x00294D60;");
{
__asm__ ("trunc.w.s $fpr12, $fpr12;"
"mfc1 $a0, $fpr12;"
: "=r"(var28));
var26 = (var28 << 0x00000010) >> 0x00000010;
}
else
{
__asm__ ("trunc.w.s $fpr12, $fpr12;"
"mfc1 $a0, $fpr12;"
: "=r"(var25));
var26 = (var25 << 0x00000010) >> 0x00000010;
}
}
var27 = ((int *) sp)[4];
ra = ((int *) sp)[5];
sp = sp + 0x00000020;
return var26;
}
/**
* Subroutine at address 0x00294D88
*/
int cameraUpDown (int arg1)
{
sp = sp + 0xFFFFFFE0;
var1 = ((short *) arg1)[0];
((int *) sp)[4] = s0;
var2 = arg1;
((int *) sp)[5] = ra;
if (((var1 < 0x00000007)) != 0x00000000)
{
var15 = 0x00000000;
}
else
{
var5 = ltriggerpressed (var2);
if (!(var5 == 0x00000000))
{
((short *) var2)[0] = 0x00000000;
}
var6 = ((unsigned short *) var2)[65];
__asm__ ("mtc1 $zr, $fpr12;");
if (!(var6 < 0))
{
if (!(((var6 < 0x00000004)) == 0x00000000))
{
var9 = sub_292404 (0x00000000);
var12 = analogupdown (var9);
__asm__ ("mtc1 $v0, $fpr12;"
:
: "=r"(var12));
var13 = *((unsigned char *) 0x003522B3);
__asm__ ("cvt.s.w $fpr12, $fpr12;");
if (!(var13 == 0x00000000))
{
__asm__ ("neg.s $fpr12, $fpr12;");
}
}
}
__asm__ ("mtc1 $zr, $fpr13;"
"c.le.s $fpr12, $fpr13;");
__asm__ ("bc1t 0x00294E2C;");
{
__asm__ ("trunc.w.s $fpr12, $fpr12;"
"mfc1 $a0, $fpr12;"
: "=r"(var17));
var15 = (var17 << 0x00000010) >> 0x00000010;
}
else
{
__asm__ ("trunc.w.s $fpr12, $fpr12;"
"mfc1 $a0, $fpr12;"
: "=r"(var14));
var15 = (var14 << 0x00000010) >> 0x00000010;
}
}
var16 = ((int *) sp)[4];
ra = ((int *) sp)[5];
sp = sp + 0x00000020;
return var15;
}
If you have also found these functions by yourself, congratulations. These functions returns the lx, resp. ly in a range between -128 and +128, and they return 0 if analog stick is not moved
You can now map the camera to the digital pad, or to the right analog on ds3 (refer to DS3Remapper plugin).
Remember that this code is only for GTA LCS. You can definitely find the patches for other LCS regions, as the code structure will be similar. I believe that VCS will have the same code structure so you can easiely find them. For other games, I can't predict. Maybe they are harder to find, perhaps even easier. Even if you can't track the camera code until the end, any progress is welcomed to be shared, so other people can try to continue. To make this possible please post every progress that you have made.
I really look forward to more results, as this will allow us the really BEST GAMING EXPERIENCE for PPSSPP, PS Vita and also for PSPgo.
Furthermore, if there are any mistake in my explaination, please tell me.
I wish you the best luck.
Download
GTA LCS GER (ULES-00182 v1) analog stick mapped to digital pad sample:
https://drive.google.com/file/d/0B8d...ew?usp=sharing
Tasks:
If you have read and understood the whole explaination, there are some tasks for you:
* Find offsets for other regions and/or for GTA VCS.
** GTA LCS uses DOWN + R trigger + left analog stick to aim with a gun. Find the function by testing all analogleftright/analogupdown functions.
** Find patches for other games