ASM-Scripts in Pokémon RS
aus RHWiki, der freien Romhacking-Enzyklopädie
| Inhaltsverzeichnis |
Deutsche Version
Dies ist ein Beispiel für einen ASM-Hack unter Benutzung des komplexesten Scriptbefehls in Pokemon R/S/F/L/E.
Benötigt
- Hexeditor
- VisualBoyAdvance (http://sourceforge.net/projects/vba) (Developer Version, wegen Logging)
- EliteMap (http://helmetedrodent.kickassgamers.com/elitemap37.exe)
- DevkitARM (http://www.devkit.tk)
Ziel
Das Ziel ist ein kleiner Palettenhack, der die gesamte Palette rot färbt.
Vorarbeit
Der Paletten-RAM beginnt beim Gameboy Advance bei 0x05000000. Zunächst starten wir den VBA und laden ein US-Amerikanisches Saphir ROM. Wir laden also unseren Spielstand und öffnen den MemoryViewer (Tools->MemoryViewer) und wählen "0x05000000 Palette". Wenn man die Paletten jedoch ändern will, wechseln sie sofort wieder in den Ursprungszustand...
Diejenigen unter uns, mit ein wenig GBA Programmierkenntnissen, werden wissen, dass man Grafiken und Paletten schnell kopieren will. Es gibt zwei bedeutende Möglichkeiten dies zu tun - eine Biosfunktion um Daten zu kopieren (CPUFastSet oder CPUSet) oder DMA.
Gehen wir mal davon aus, dass DMA benutzt wird. Wir öffnen also ein Logging-Fenster(Tools->Logging), aktivieren DMA3 (das ist die meistgenutzte DMA Leitung) und lassen das Spiel ein wenig laufen.
OK... Der Logger gibt folgendes(oder ähnliches) aus:
DMA3: s=0202eec8 d=05000000 c=8000 count=00000400 DMA3: s=08376e14 d=06003f80 c=8000 count=00000080 DMA3: s=0202eec8 d=05000000 c=8000 count=00000400
Die erste Zeile ist interessant. Es werden Daten von 0x0202eec8 nach 0x05000000 (Paletten Ram) kopiert. Dies wiederholt sich ständig. Lasst uns im Memory Viewer die Daten bei 0x0202eec8 ein wenig verändern... Tatsächlich, die Farben verändern sich!
Der Hack
Also was brauchen wir für den bösen Rot-Hack? Ihr wisst ja, dass Farbinformationen auf dem Gameboy Advance in 16bit-Werten gespeichert sind. 5Bit für r, g und b. 0x7FFF ist demnach Weiß, 0x0000 Schwarz, 0x001F Hellrot (Rot auf höchster Stufe, alle anderen sind null). Wir möchten die anderen Farbkanäle entfernen, damit nur noch Rot übrig bleibt. Ein AND 0x001F auf die Paletten-Daten angewendet würde also nur Rot übrig lassen (siehe Binäroperation UND).
Lasst uns anfagen zu coden:
typedef unsigned short u16;
void PalFilterRed()
{
u16 *PalPtr;
int i;
PalPtr=0x0202eec8; // Pointer auf die Palettendaten
for(i=0; i<0x100; i++)
{
*PalPtr&=0x1F;
PalPtr+=1;
}
}
Das sollte den Job erledigen - aber wie können wir diese Funktion aufrufen?
Nun, zunächst müssen wir es kompilieren: Wir entpacken also DevkitARM, falls dies nocht nicht geschehen ist, nach C:\DevkitARM\ und fügen C:\DevkitARM\bin\ in unsere Path-Systemvariable ein.
Jetzt speichern wir unseren C-Quelltext als Red.c.
Wir öffnen ein die Kommandozeile und geben folgendes ein (die .exe ist in C:\DevkitARM\bin\):
arm-elf-gcc -S -mthumb -mthumb-interwork -O3 Red.c
Gcc ist der Kompilier. Der Parameter -S teilt dem Kompilier mit, dass er lesbaren ASM-Code erzeugen soll. (wir müssen überprüfen, was passiert) -mthumb und -mthumb-interwork veranlassen den Kompilier Thumb-Source zu erstellen, -marm würde ASM-Source erstellen, doch das ist für Funktionen im ROM nicht empfohlen. -O3 optimiert die Geschwindigkeit, um nutzlose Kommandos zu vermeiden.
Der Kompilierer hat nun Red.s erstellt, die folgenden Inhalt haben sollte:
.code 16
.file "Red.c"
.text
.align 2
.global PalFilterRed
.code 16
.thumb_func
.type PalFilterRed, %function
PalFilterRed:
push {lr}
ldr r2, .L11
mov r0, #31
mov r1, #255
.L5:
ldrh r3, [r2]
and r3, r3, r0
strh r3, [r2]
add r2, r2, #2
sub r1, r1, #1
bpl .L5
@ sp needed for prologue
pop {r0}
bx r0
.L12:
.align 2
.L11:
.word 33746632
.size PalFilterRed, .-PalFilterRed
.ident "GCC: (GNU) 3.4.0"
Wir müssen sicher gehen, dass die Funktionen keinen anderen Daten überschreibt, wenn sie Variablen sichert. Ein bisschen ASM-Kenntnis hilft uns dabei... (du hast die Funktion doch geschrieben, es sollte also nicht schwer sein, herauszufinden, was welcher Befehl macht :D). Falls Probleme entstehen, ist es eine gute Idee, VisualBoyAdvance-sdl zu starten und einen Breakpoint auf push {lr} zu setzen.
Falls du ein ASM-Junkie bist, kannst du die Funktion natürlich auch in ASM schreiben.
Okay, fahren wir fort (die Funktion sieht gut aus):
arm-elf-as -o Red.o Red.s
Das assembliert den Assembler-Code zu echtem Maschinencode, aber immernoch im elf-Format - das brauchen wir jedoch nicht, also konvertieren wir es zu Binär.
arm-elf-objcopy -O binary Red.o Red.bin
Wenn wir jetzt die .bin in einem Hexeditor öffnen, sehen wir folgendes:
00B5054A1F20FF2113880340138002320139F9D501BC0047C8EE0202
Nun kopieren wir das an eine freie Stele in dem Saphir-ROM. (Gehe sicher, dass der Code bei einem geraden Offset beginnt) Nun starte EliteMap und öffne eine Event deiner Wahl in ScriptEd. Es wird das Offset des Scripts angezeigt. Ändere das Script (ich nehme an, dass du einfache Scriptänderungen und das Repointen von Scripts beherrscht, falls nicht, lerne es jetzt.) so, dass unsere Funktion aufgerufen wird. Dafür benutzt du den Befehl 23h. Die Syntax ist [23][PointerZuARMSub] bzw. [23][PointerZuTHUMBSub+1]. Wir haben den Thumb-Befehlssatz benutzt, also schreiben wir, angenommen wur hätten die Funktion bei 2000 (bzw. 08002000 bei GBA-Adressierung) geschrieben, müssten wir 2301200008 schreiben. Dieser Befehl wird unseren Filter aufrufen, also nach der Beendigung des Filters wieder zurückkehren.
Falls alles gut gelaufen ist, solltest du jetzt einen roten Screen haben.
Falls du irgendwelche Probleme hast, so habe ich es gemacht:
008DFFD0:
2301008E08042BD71408FFFFFFFFFFFF <-- 1.umgepointetes Script: 23|01008E08 ''RufeASMauf'',
04|2BD71408 ''Springe zum eigentlichen Script''
2301008E0804CDD61408FFFFFFFFFFFF <-- 2.umgepointetes Script (selbes)
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
008E0000:
00B5054A1F20FF211388034013800232 <-- Das ist unsere Funktion
0139F9D501BC0047C8EE0202FFFFFFFF
Schlusswort
Mit diesem Wissen kannst du fast alles machen, das du je machen wolltest. Es kommt nur darauf an, die richtigen RAM Adressen zu kennen.
Um deine Fähigkeiten ein wenig zu trainieren:
Versuche eine Psychofrau einzufügen, die das erste Monster deines Teams klont und in deiner Box ablegt. Falls du das kannst, könntest du eine Abfrage hinzufügen, ob bereits ein Monster in Box1 auf Platz1 ist und nur klonen, wenn dort noch keins ist.
-- loadingNOW 2004
Siehe auch
English Version
What we need
- Hexeditor
- VisualBoyAdvance (Developer Version because we need logging)
- EliteMap
- DevkitARM (www.devkit.tk)
The Plan
We want to make a hack which pulls of some fancy palette trick to make the whole screen red
Prerequisites
Well we know palette ram starts at 05000000 on gba. So we start-up VBA and load Saphire US Version. Load our save and open the memory viever (Tools->MemoryViewer) then we select 0x05000000 Palette. Okay here we are; now we try to change it. Dammit it changes back immediately.
Now those with some more gba coding knowledge might know you want to copy tiledata and palettes fast. There are two major options to do that - a bios call (CPUFastSet or CPUSet) to copy data or DMA.
Fine… let's check for DMA Tools->Logging enable DMA3 (that's the genaral use DMA line) run the game a litte. w00t: whe get this:
DMA3: s=0202eec8 d=05000000 c=8000 count=00000400 DMA3: s=08376e14 d=06003f80 c=8000 count=00000080 DMA3: s=0202eec8 d=05000000 c=8000 count=00000400
now the 1st line is interesting; Copy data from 0x0202eec8 to 0x05000000 (palette ram!) and that's happening all the time. Nice! So lets go to 0x0202eec8 in the memory editor and mess with the data a bit.
The Actual Hacking And Coding
Great we see some results. So what do we need for an evil red hack? You know color information is stored in 16 bit values on gba. 5Bit for r, g and b. 0x7FFF would be white, 0x0000 black, 0x001F bright red (red on max all other colors on zero). We want to remove the other channels so only shades of red remain. An AND 0x001F on all the pallette data would do the job (see binary operation AND). So we start coding:
typedef unsigned short u16;
void PalFilterRed()
{
u16 *PalPtr;
int i;
PalPtr=0x0202eec8; // Pointer to the PaletteMem
for(i=0; i<0x100; i++)
{
*PalPtr&=0x1F;
PalPtr+=1;
}
}
This should get the job done - now how to run this nice little function? Well, first we need to compile it. Unpack devkitARM, if you haven't already, probably to C:\DevkitARM\. You might want to add C:\DevkitARM\bin\ to your $path (path variables), too. Save your c cource as Red.c.
Open the command line and type (the exe is in C:\DevkitARM\bin\) arm-elf-gcc -S -mthumb -mthumb-interwork -O3 Red.c
Gcc is the compiler. -S tells it to create readable asm source (we need to check what's going on); -mthumb -mthumb-interwork tells it to compile to thumb source; -marm would create arm source but it's not recommended for functions in roms. -O3 is an optimize for speed, so we avoid useless junk commands.
this will create Red.s open it and you'll see:
.code 16
.file "Red.c"
.text
.align 2
.global PalFilterRed
.code 16
.thumb_func
.type PalFilterRed, %function
PalFilterRed:
push {lr}
ldr r2, .L11
mov r0, #31
mov r1, #255
.L5:
ldrh r3, [r2]
and r3, r3, r0
strh r3, [r2]
add r2, r2, #2
sub r1, r1, #1
bpl .L5
@ sp needed for prologue
pop {r0}
bx r0
.L12:
.align 2
.L11:
.word 33746632
.size PalFilterRed, .-PalFilterRed
.ident "GCC: (GNU) 3.4.0"
We need to make sure the function does not wite to any unintented places (e.g. to store variables). ASM knowledge helps here but ha well... you wrote the function so it should be very easy to figure what each command is doing :D - if problems occur, it might be a good idea to fire up VisualBoyAdvance-sdl and set a breakpoint (bt) on push {lr}.
If you're an ASM guru, you could as well write the function in ASM itself.
Okay we can continue (since the function looks fine):
arm-elf-as -o Red.o Red.s
This assembles the assembly source to real machine code, but it's still in elf format. - That's not what we need so we do a quick objcopy to convert it to binary.
arm-elf-objcopy -O binary Red.o Red.bin
Open the .bin in a Hexeditor it will look like this:
00B5054A1F20FF2113880340138002320139F9D501BC0047C8EE0202
Now copy it to some free space in the Sapphire ROM. (Make sure you start at an even offset). Now start EliteMap and open an event of your choice in ScriptEd. It will display the start address of the script. Change the script (I assume you know how to do simple script editing and repointing, if not: learn it now) Now you use the ASM command 23. Syntax is [23][PointerToARMSub] or [23][PointerToTHUMBSub+1]. We used the thumb instructionset, so in case we placed our function at 2000 (or 08002000 in gba addressing) we have to write 2301200008. This command will CALL our filter and after the filter is executed, it will jump back to the script.
If everything worked fine, you will have a red screen now. If you have problems, this is how I did it. I placed this at 008DFFD0:
008DFFD0:
2301008E08042BD71408FFFFFFFFFFFF <-- RepointedScript1 23|01008E08 RunASM,
04|2BD71408 JumpToOriginalScript
2301008E0804CDD61408FFFFFFFFFFFF <-- RepointedScript2 (same)
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
008E0000:
00B5054A1F20FF211388034013800232 <-- this is our function
0139F9D501BC0047C8EE0202FFFFFFFF
Conclusion
Now with that you can do pretty much anything you've ever wanted. It's just a matter of knowing the right ram values. To train your skills a little: Try to add a psi-woman woman which clones the first pokémon in your party and places it in your box. If you can do that, you might want to add a check if there is a pokémon in box1 slot 1 and you clone only if there is none.
-- loadingNOW 2004

