KINGx - Das inoffizielle PlayStation Forum & News Portal

Normale Version: Aufruf von SDK Funktionen in PSP Spielen
Sie sehen gerade eine vereinfachte Darstellung unserer Inhalte. Normale Ansicht mit richtiger Formatierung.
Preface: Bevor jetzt alle aufschreien und sagen, das folgende Tutorial wäre von moskito geklaut, erlaube ich mir euch darauf hinzuweisen, dass dieses Tutorial zwar auf moskito's basiert, aber "realitätsnäher" als seines ist. Was meine ich mit "realitätsnäher"? Nun die PSP hat ein paar Eigenheiten, wenn es darum geht MIPS code auszuführen.

Eine letzte Sache noch: Bitte nehmt euch etwas Zeit, dass Tutorial durchzulesen, es ist ziemlich komplex!

Und los geht's!



Um euch dieses Konzept näher zu bringen, werde ich folgende Methode der PSPSDK API über MIPS in einem PSP Game aufrufen: sceKernelDelayThread.

Als erstes müssen wir uns diese Methode genauer anschauen und folgende Fragen beantworten:

1.) Braucht die Methode Parameter?
2.) Hat diese Methode einen Rückgabewert und wenn ja, von welchem Typ ist dieser?



Um diese Fragen zu beantworten, werden wir uns die API Dokumentation zu Nutze machen:

Code:
int sceKernelDelayThread  (SceUInt  delay)  

Delay the current thread by a specified number of microseconds.


Parameters:
delay  - Delay in microseconds.

Example:
sceKernelDelayThread(1000000); // Delay for a second


Wie wir sehen, verlangt unsere Methode einen Parameter vom Typ unsigned int (SceUInt), was ja auch logisch ist, es macht keinen Sinn, eine negative Zahl als Parameter zu übergeben.
Außerdem können wir feststellen, dass die Methode einen Rückgabewert vom Typ int hat, wir können der API aber nicht entnehmen welcher Wert zurückgeliefert wird.
(Wer Lust hat, kann den Rückgabewert herausfinden, indem er folgendes macht:

int test = sceKernelDelayThread(1000000);
pspDebugScreenPrintf("Value of sceKernelDelayThread: %d", test);


Nun wird es spannend, wie ihr hoffentlich wisst, hat MIPS 32 Register (unter anderem 10 temporäre register von $t0 -$t9, 4 argument register von $a0 - $a3 und acht saved register von $s0 - $s7).

Per Definition können wir die argument register ($a0 - $a3) verwenden, um Funktionen Parameter mitgeben zu können. Aber Moment, es gibt 4 argument register, unsere Methode verlangt aber nur einen Parameter. Woher erhalten wir die Informationen, welchen argument register wir verwenden sollen? Ganz einfach, wir untersuchen, wie unser entsprechendes PSP Spiel unsere Fuktion aufruft:

Ich verwende das Spiel Killzone Liberation und gebe euch jetzt einfach mal die Addresse des sceKernelDelayThread Stubs: 0x08d8f120
Wie wir wissen, muss dieser Stub gecalled werden, um die Funktion ausführen zu können. Nun bietet uns MIPS zwei Funktionen an, um Funktionen zu callen: jump; jump and link

Was ist der Unterschied zwischen diesen beiden Befehlen?


Wie der Name jump schon sagt, springt er zu einer angegeben Addresse, schauen wir uns diesen code an.

Code:
#code
0x08805000 ori $t0, $zero, 0x0001 //$t0 = 0x00000001
0x08805004 j $0x08801234 //Ausführung des codes der unteren Zeile...
0x08805008 ori $t1, $zero, 0x0001...//$t1 = 0x00000001, dann Sprung zu 0x08801234



Jetzt schauen wir uns den selben code mit einer JAL (jump and link) an:

Code:
#code
0x08805000 ori $t0, $zero, 0x0001 //$t0 = 0x00000001
0x08805004 jal $0x08801234 //Ausführung des codes der unteren Zeile...
0x08805008 ori $t1, $zero, 0x0001.../* $t1 = 0x00000001, dann sprung zu 0x08801234, Speicherung der Addresse 0x0880500c in $ra (PC + 0x8). */



Wie ihr seht, speichert die JAL die zweite Addresse unter ihr in den MIPS register 31, in $ra. Den Grund werden wir gleich erfahren.

Ok, jetzt kommen wir wieder zu unserem Ziel zurück...was wollten wir nochmal machen? Ach ja...wir wollten herausfinden, welchen argument register das PSP Spiel für unseren sceKernelDelayThread Paramater verwendet.
Nun, ich teile euch mit, dass das Spiel die Funktion über eine JAL called, also müssen wir nach folgendem Befehl suchen: JAL $08d8f120.
In hexadecimal ergibt das folgendes: 0x0e363c48

Um eine Addresse zu finden, die diesen hex Wert hat, benutze ich mkultra v10, ein cheat device für PSP Spiele.

Das Suchen liefert eine ganze Reihe von Addressen zurück, jede davon called unsere gesuchte Funktion, ich nehm einfach mal folgende Addresse: 0x088246a0

Schauen wir uns jetzt den code in diesem Bereich an:

Code:
0x0882469c NOP //no operation
0x088246a0 JAL $08d8f120 //call of sceKernelDelayThread
0x088246a4 ori $a0, $zero, 0x0001 //sceKernelDelayThread(1);


Wie wir sehen, wird der register a0 als Parameterübergabe verwendet.

Jetzt geht es an den eigentlichen code, wir wollen 1 sekunde (1000000) als Paramter übergeben.

1000000 (dec) = 0x000f4240 (hexadecimal)

hier ist der code:

Code:
lui $a0, 0x000f //$a0 = 0x000f0000
jal $08d8120 //sceKernelDelayThread(1000000)
ori $a0, $a0, 0x4240 //wird vor dem jump der JAL ausgeführt


Soweit so gut, bislang gab es nicht Aufregendes, was moskito nicht schon umrissen hat, jetzt wird es aber interessant und spannend. Wink

Wie wir nun wisssen, verwenden wir den argument register $a0 als Parameterübergabe, allerdings haben wir ein Problem. Aufgrund der Tatsache, dass wir den code über ein PSP Spiel ausführen, welches ja auch Gebrauch von den registern macht, ist unser $a0 register bereits mit einem Wert belegt! Was ist das für ein Wert? Nun, in den meißten Fällen ist ein pointer in $a0 gespeichert, der irgendwo hinpointet. Und jetzt kommts: Wenn wir den pointer in $a0 mit unserem Wert (0x000f4240) überschreiben und den code dann ausführen, wird unsere PSP freezen.

Dies stellt uns vor folgende Aufgabe:

Wir müssen sichergehen, dass NACH unserem Aufruf der Funktion sceKernelDelayThread und VOR dem Ende unseres Codes, $a0 wieder den ursprünglichen Wert enthält, danach können wir die Kontrolle wieder dem Spiel zurückgeben.

Wie können wir den original Wert von $a0 in MIPS speichern?

Nun, wir werden den Stack zu Rate ziehen. Für diejenigen, die nicht wissen, was der Stack ist, der Stack ist eine dynamische Datenstruktur, eine sogenannte Liste, die nach dem Last-In, First-Out Prinzip funktioniert, und ermöglicht uns das Speichern von Werten/Objekten.

Bevor wir den Wert von $a0 auf dem Stack speichern müssen, müssen wir erstmal Speicherplatz auf dem Stack allozieren. In MIPS gibt es einen register $sp (stack pointer), dieser pointet zu der "Spitze" des Stacks, also zu dem obersten Element, welches gerade auf dem Stack liegt. Eine Sache müssen wir aber noch beachten: In MIPS "läuft" der Stack verglichen mit den RAM Addressen eines Games (läuft von Unten nach Oben; Bsp.: 0x0 - 0x4000) andersherum, also Stack Bereich z.B. von 0x000f0000 - 0x000de1c (also von Oben nach Unten).

Nachdem wir das geklärt haben, wird es endlich Zeit, uns Platz auf dem Stack zu schaffen.

Wir verringern den Stack pointer um 16 byte, 16 byte sind ein Stack Paragraph, was uns das Speichern von bis zu 4 register Werten auf dem stack ermöglicht.

Code:
addiu $sp, $sp, - 16  //$sp = $sp + (-16)

Anmerkung: Wir ihr seht, habe ich den Befehl "add immediate unsigned" verwendet, gnauso gut könnte man den Befehl "subu $sp, $sp, 16" verwenden, dass ich den addiu Befehl genommen habe, liegt einfach nur an design-Gründen.

Jetzt können wir den original Wert von $a0 auf dem frei geworden Stack-Platz speichern, dies geschiet durch:

Code:
sw $a0, 0x00($sp)



Dieser Befehl lädt den Wert von $a0 in das angegebe Element des Stacks ($sp + 0x00).

Jetzt haben wir also den Wert gesichert und können mit unserem Code fortfahren.

Code:
addiu $sp, $sp, - 16
sw $a0, 0x00($sp)
lui $a0, 0x000f
jal $08d8120
ori $a0, $a0, 0x4240


Nachdem wir jetzt unseren code haben, der auch schon unsere SDk funktion called, müssen wir wieder den originalen Wert von $a0 wieder in $a0 speichern und anschließend unseren Stackparagraphen wieder löschen (C funktion: pop()).

Code:
lw $a0, 0x00($sp) //lädt den Wert von $sp + 0x0 in $a0
addiu $sp, $sp, 16 //setzt $sp wieder auf den Ausgangspunkt zurück


Unser Code sieht jetzt folgendermaßen aus und würde ohne Probleme funktionieren!

Code:
addiu $sp, $sp, - 16
sw $a0, 0x00($sp)
lui $a0, 0x000f
jal $08d8120
ori $a0, $a0, 0x4240
lw $a0, 0x00($sp)
addiu $sp, $sp, 16



Nachdem wir jetzt unseren code haben, kehren wir zu der Frage zurück, warum wir den Stub der Funktion sceKernelDelayThread mit einer JAL callen und nicht mit einem jump (J) command. Nun, wenn wir uns all das anschauen, was ich gerade geschrieben habe, sollte die Antwort klar sein, lasst uns trotzdem den Aufbau des Stubs anschauen.

Code:
sceKernelDelayThread function stub:
jr $ra
syscall //call of the function


Wie wir sehen, besteht dieser Stub (und die meißten anderen Stubs) aus zwei MIPS Befehlen, einmal aus jump register $ra (jr $ra) und einmal einem Syscall, wobei dieser die eigentliche Methode aufruft.
Nun, die Begründung, warum wir eine JAL verwenden und keine J liegt an dem Befehl jr $ra. Wie ich breits vorhin gesagt habe, speichert eine JAL die zweite Addresse unter ihr in den Register $ra, dass heißt, wir weisen unserem code Block jetzt mal Addressen zu:

Code:
0x08801000 addiu $sp, $sp, - 16
0x08801004 sw $a0, 0x00($sp)
0x08801008 lui $a0, 0x000f
0x0880100c jal $08d8120
0x08801010 ori $a0, $a0, 0x4240
0x08801014 lw $a0, 0x00($sp)
0x08801018 addiu $sp, $sp, 16


Wenn jetzt also der PC (Program Counter) bei unserer JAL angekommen ist (0x0880100c), führt er erst den Befehl darunter aus (der delay slot), speichert dann die Addresse 0x08801014 in $ra und springt dann zu unserem function stub. Dort wird dann die jr $ra ausgeführt, und da die jr $ra ebenfalls einen delay slot hat, wird zu erst der System Call ausgeführt. Danach wird zu der Addresse in register $ra gesprungen (also zu 0x08801014). Dort wieder angekommen, kann unser code zu Ende ausgeführt werden, also den Originalwert von $a0 wieder in $a0 speichern und den stack pointer wieder auf den Ausgangswert schließen.
Jetzt nehmen wir an, wir würden einen J (jump-Befehl) benutzen, um zu dem function stub zu springen. Was wäre die Folge?

Nun, erneut würde der System call ausgeführt, da aber ein J keine Addresse in register $ra schreibt, wird die PSP nicht wieder zu unserem code zurückspringen, wodurch $a0 nicht mehr seinen originalen Wert zurückerhält und auch der Stack nicht wieder bereinigt wird. Das Resultat ist eine ausgeknockte PSP und ein angefressener und genervter PSP Benutzer!

----------------------------------
Jetzt bleibt nur noch eine Sache zu erledigen.

Wir müssen diesen code jetzt nur noch irgendwo in den RAM Bereich mit User mode schreiben (ohne Exploit haben wir keinen Zugriff auf kernel RAM Bereiche). Der Usermode RAM Bereich geht von 0x08800000 - 0x09ffffff, und da wir keine bestehenden Funktionen überschreiben wollen, brauchen wir einen leeren Speicherplatz, der groß genug ist, unseren Code zu halten. Hierfür empfielt sich der Scratchpad-Bereich (0x08800000 - 0x08803fff), der überwiegend aus NOPs besteht. Nachdem wir eine Startaddresse für unseren Code gefunden haben (z.B. 0x08801000) können wir jetzt eine aktive JAL nehmen, und zu unserem code hooken.

Ich werde jetzt nicht darauf eingehen, wie man eine aktive JAL findet, bei Bedarf seitens der Leser, werde ich hierzu ein Tutorial schreiben.

Nach dem Hinzufügen einer aktiven JAL als hook, sieht unser code nun so aus:

Code:
0x08801000 addiu $sp, $sp, - 16
0x08801004 sw $a0, 0x00($sp)
0x08801008 lui $a0, 0x000f
0x0880100c jal $08d8120
0x08801010 ori $a0, $a0, 0x4240
0x08801014 lw $a0, 0x00($sp)
0x08801018 addiu $sp, $sp, 16
0x00056ef0  jal $08801000  //Beispiel JAL

.

Nun den code nur noch assemblen, und wir haben unseren funktionierenden sceKernelDelayThread call über ein PSP Spiel!

Hier ist ein entsprechender code für das PSP Spiel Killzone Liberation:

Code:
#sceKernelDelayThread_1 second
;press L + R buttons to pause the game for a second
;PSP_Lord
0x00000800 0x27bdfff0
0x00000804 0xafb00000
0x00000808 0xafb10004
0x0000080c 0xafb20008
0x00000810 0xafbf000c
0x00000814 0x00048021
0x00000818 0x00058821
0x0000081c 0x00069021
0x00000820 0x3c0508dd
0x00000824 0x8ca56e1c
0x00000828 0x8ca50004
0x0000082c 0x8ca50044
0x00000830 0x34060300
0x00000834 0x14a6000c
0x00000838 0x00000000
0x0000083c 0x3c04000f
0x00000840 0x0e363c48
0x00000844 0x34844240
0x00000848 0x00102021
0x0000084c 0x00112821
0x00000850 0x0e21406b
0x00000854 0x00123021
0x00000858 0x8fb00000
0x0000085c 0x8fb10004
0x00000860 0x8fb20008
0x00000864 0x8fbf000c
0x00000868 0x03e00008
0x0000086c 0x27bd0010
0x00056ef0 0x0e200200


Dieser code ist etwas länger als unser obiger Assembly code, das liegt zum einen an dem Button-Support und zweitens an einer JAL Besonderheit (Ich musste 3 register preserven, damit ich die JAL 100% stabil nutzen kann). Wer an letzterem interessiert ist, bitte hier in dem Thread melden.

-PSP_Lord-

Sehr gutes Tutorial! Smile
Gute Arbeit Smile
Gut gemacht thumb
Super TUT Smile
das PSPking forum hat einen bedeutenden Coder dazu gewonnen!
Wirklich SUPER TuT! DU solltest mal ein paar HB/CFW/HEN/GameZZ schreiben ;D

Cha0z :
Wirklich SUPER TuT! DU solltest mal ein paar HB/CFW/HEN/GameZZ schreiben ;D


wenn du wüsstest Wink

eMKayWe :

Cha0z :
Wirklich SUPER TuT! DU solltest mal ein paar HB/CFW/HEN/GameZZ schreiben ;D


wenn du wüsstest Wink


?

ich weis c schon aber milps lern ich grad Big Grin

Cha0z :
?

ich weis c schon aber milps lern ich grad Big Grin


ne ich meine was er sonst noch macht... musst halt nochn bsichen abwarten Big Grin

Referenz-URLs