Inhaltsverzeichnis
- Vorwort
-
Was ist diese Toolchain, was brauchen wir überhaupt?
- Ein Template für alle Fälle
- Codeerläuterung
- Schlusswort
Vorwort
Einige mögen vielleicht bemerkt haben, dass es zwar mittlerweile immer mehr Leute gibt, die sich mit Assembly, und Programmierung für den GBA im Allgemeinen beschäftigen, allerdings holt man sich dieses Wissen zumeist aus dem Englischen Bereich, ein wirklich modernes, deutsches, Tutorial dafür gibt es noch nicht. Dem möchte ich gerne Abhilfe schaffen und ein bisschen erklären, wie ich denn meine Projekte organisiere, und wie man es trotz riesengroßer Codemengen (Im Falle von Pokémon Sovereign of the Skies allein etwa 15000 Zeilen Reincode) schafft, ein solches Projekt zu verwalten. Ein paar User aus diesem Forum haben quasi unter sich, eine kleine Projektstruktur entwickelt, auf der zumindest 2 der größten Projekte auf diesem Forum basieren. (Naja, zumindest auf einer ähnlichen Struktur) - und um dieses Know-How einmal weiterzugeben, schreibe ich hier etwas darüber.
Das Hauptproblem, wenn man einmal mehr als nur ein paar kleine ASM Hacks in seinem Projekt hat, ist die Übersicht. Wo habe ich Pointer geändert, wo steht welcher Code, Snippet XY fehlt in meiner Dokumentation, etc. - Was kann man also tun, naja, am schönsten wäre es, den Reincode, und die entsprechenden "Patchingstellen", also Addressen, an denen Bytes, Pointer etc. geändert werden, einfach in einem Projekt zu haben, und dieses Projekt dann einfügen zu können. Da diese Problematik nicht nur irgendwann bei Romhackern auftritt, sondern allgemein bei größeren Projekten, gibt es eine Auswahl an Programmen, die genau das ermöglichen. Welche das genau sind, und wie man sie verwendet, das soll dieses Tutorial behandeln. Eine kleine Randinfo: Wenn ihr Assembly oder C an sich lernen wollt, dann seit ihr hier nicht ganz richtig. Ich werde versuchen alle meine Beispielcodes zu erklären, aber ich glaube, diese Thematik lernt man am besten, indem man mit den Leuten redet, ihr könnt mich jederzeit Privat, oder in Skype/Discord anschreiben, und ich bin sicher, wenn man auf andere User zugeht, sind diese genau so hilfsbereit. Ich habe damals auch im direkten Gespräch gelernt wie der ganze Kram funktioniert, und ich habe viele Datenblätter gelesen. Einige Tutorials gibt es hier auch noch dazu, falls man sich das ganze einmal ansehen möchte. Wichtig für den GBA spezifisch ist natürlich die entsprechende Architektur, und die Grundlagenidee, wie so ein Prozessor überhaupt funktioniert. Ansonsten gilt: Wer Fragen hat, der soll Fragen stellen, und dämliche Fragen gibt es nicht.
Was ist diese Toolchain, was brauchen wir überhaupt?
Unix ist Liebe, Unix ist Leben
Ich habe von einer Kollektion von Programmen gesprochen, die uns ermöglichen solche Projekte zu erstellen, hier treffen wir nun erst ein paar Vorbereitende Schritte, um diese Programme zu verwenden. First Things first: Wenn ihr unter Windows operiert, braucht ihr auf jedenfall eine Unixoide Umgebung, also ein Terminal, das ein wenig besser ist, als die "Eingabeaufforderung" von Windows. Dazu gibt es zum Beispiel cygwin - Einfach die Version für eure Architektur (32- oder 64Bit) herunterladen, und das Setup ausführen. Das Setup installiert das Grundpaket und ist gleichzeitig ein Paketmanager, kann also Programme für euch installieren. Im Setup könnt ihr euch durch die ersten paar Seiten klicken, und wenn es darum geht, einen Mirror auszuwählen, nehmt ihr am besten einen Server, der nahe bei euch ist. Für Österreicher zu empfehlen ist z.B. http://gd.tuwien.ac.at - Der Server der TU Wien. Es ist aber grundsätzlich egal, von wo ihr euch das Zeug ladet (Ihr schont nur das Internet ein wenig). Zusätzlich zum Standardpaket installieren müsst ihr das Programm "make", dazu in der Suchleiste "make" eingeben, das Tool mit der Beschreibung "make - The GNU Version of the 'make' Utility" suchen, und einmal auf "skip" drücken, sodass dort nicht mehr "skip" sondern eine Versionsnummer steht. Das ganze sieht dann in etwa so aus:
Außerdem zu empfehlen, für die Versionskontrolle ist "git", das werde ich höchstwarscheinlich in einem späteren Teil behandeln. Die volle Bezeichnung dazu lautet "git: Distributed Version Control System"
Compiler
Um unseren Code in etwas für den GBA ausführbares zu übersetzen, brauchen wir einen Compiler, am besten einen Assembler noch dazu, weil wir ein paar Dinge nicht in C schreiben können. (Das meiste jedoch schon, und das ist ein Segen, man glaube mir) Eine gute Compiler Toolchain für den GBA ist "DevkitARM", ein Bestandteil von DevkitPro - Einfach herunterladen und installieren. Danach müsst ihr noch den Ordner "<DevkitProInstallation>/devkitARM/bin" zu eurer PATH Variable hinzufügen. Wie das auf eurem entsprechenden Betriebssystem geht, könnt ihr sicher im Internet nachlesen, hier wird zum Beispiel erklärt, wie das unter ein paar verschiedenen Systemen funktioniert: https://superuser.com/question…or-use-them/284351#284351
Gut, testen, ob ihr den PATH richtig gesetzt habt, könnt ihr, indem ihr das zuvor installierte cygwin terminal öffnet (Das Programm heißt Cygwin Terminal, oder Cygwin64 Terminal) und einfach einmal
arm-none-eabi-gcc --version
eingebt. Das sieht dann z.B. so aus:
$ arm-none-eabi-gcc --version
arm-none-eabi-gcc.exe (devkitARM release 46) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Wenn ihr euch vertippt oder PATH nicht richtig gesetzt ist, bekommt ihr eine Fehlermeldung, die euch sagt, dass das Programm nicht gefunden wurde. Um auch zu testen, ob die anderen Programme installiert sind, könnt ihr respektive
make --version
git --version
eingeben, und schauen, ob ihr eine sinnvolle Ausgabe, oder einen Fehler erhaltet.
Nachdem ihr das alles erledigt habt, sind sowohl euer Compiler, als auch make und git Einsatzbereit. Als nächstes schauen wir uns an, wie man damit arbeiten kann.
Ein Template für alle Fälle
Weil das alles nicht unbedingt intuitiv ist, und ich selbst ein paar Monate an dem geschraubt habe, was ich euch hier als Template anbiete, und überhaupt, weil man das Rad nicht neu erfinden muss, ... TL;DR: Es gibt ein Template. Dieses beinhaltet ein wenig Beispielcode, und ist vollständig durchkommentiert, sodass man sich damit auskennen sollte. Hier erkläre ich euch, wie ihr dieses Template aufsetzt. Zuerst einmal, müsst ihr euch das Ding von meiner git Repository herunterladen. Wer will kann dazu die Konsole verwenden, oder einfach hier drauf kliTemplate Download. Anschließend müsst ihr euch eine Ordnerstruktur erstellen, die wie folgt aussieht
Für den Tools Ordner wichtig ist das Tool, armips, und ein (VBA) Emulator, um euer ROM zu starten. Ihr könnt euch den Inhalt dieses Ordners von unserer Filebase laden. Damit ist das Projekt auch schon vollständig aufgesetzt. Um das Template zu benutzt müsst ihr eine Rom, mit Namen "bpre0.gba" in den Ordner "base" legen. Diese wird dann beim einfügen des Codes verwendet.
Ihr könnt den Beispielcode testen, indem ihr in eurem cygwin Terminal in den Ordner rhr_coding_template wechselt und
make run
ausführt. Als Ausgabe seht ihr, was die ganze Toolchain tut, welche Programme ausgeführt werden, und wie am Ende der Emulator gestartet wird. Das sieht zum Beispiel so aus:
arm-none-eabi-gcc -mthumb -mthumb-interwork -g -mcpu=arm7tdmi -fno-inline -mlong-calls -march=armv4t -O0 -std=c99 -Wall -Wextra -Wunreachable-code -Iinclude -DBPRE -DSOFTWARE_VERSION=0 -c src/example.c -o object/src/example.o
arm-none-eabi-gcc -mthumb -mthumb-interwork -g -mcpu=arm7tdmi -fno-inline -mlong-calls -march=armv4t -O0 -std=c99 -Wall -Wextra -Wunreachable-code -Iinclude -DBPRE -DSOFTWARE_VERSION=0 -c src/agb_debug/agb_debug.c -o object/src/agb_debug/agb_debug.o
arm-none-eabi-ld -z muldefs -T linker.lsc -T bpre.sym --whole-archive -r -o object/linked.o --start-group object/src/example.o object/src/agb_debug/agb_debug.o --end-group
armips main.asm
arm-none-eabi-nm object/linked.o -n -g --defined-only | \
sed -e '{s/^/0x/g};{/.*\sA\s.*/d};{s/\sT\s/ /g}' > build/__symbols.sym
*** SUCCESSFULLY BUILT PROJECT ***
Die kompilierte ROM liegt anschließend im Ordner "build", beachtet, dass sämtlicher Code an die Addresse 0x09000000, bzw. 0x1000000 geschrieben wird. Also nach den ersten 16MB. Wie man das ändert, zeige ich im nächsten Abschnitt. Im build Ordner liegt außerdem noch eine "__symbols.sym" Datei, welche euch genau zeigt, wo euer Code eigentlich liegt. Um zu testen, ob der Code, der eingebaut wurde funktioniert, müsst ihr nur ins hohe Gras gehen, und dabei das Log des VBA aufmachen, dort solltet ihr eine Ausgabe feststellen. (Für C versierte, ihr könnt euch natürlich den Code ansehen, und nachvollziehen versuchen, wieso das so ist) - Im letzten Abschnitt gehe ich einmal kurz durch den Code, und erkläre was dort so schönes passiert.
Codeerläuterung
Das ganze ist ja ganz nett und schön, aber irgendwie ist vieles davon eine ganze Menge an Kuhmagie? Nun, für die meisten Teile reicht es erst einmal zu glauben, dass es funktioniert. Wer daran interessiert ist, wie das Linker Script funktioniert, kann sich im Internet gerne weiter umsehen, erklären werde ich das hier nicht. (Schwer ist es allerdings keinesfalls).
makefile
- ### Heart of the toolchain, compiles your code, ###
- ### links it, and inserts it into the ROM ###
- ### ###
- AS := arm-none-eabi-as
- LD := arm-none-eabi-ld
- OBJCOPY := arm-none-eabi-objcopy
- CC := arm-none-eabi-gcc
- ARS := armips
- ### Other tools you might need at some point ###
- ### can be defined here as well ###
- GRIT := grit
- NM := arm-none-eabi-nm
- VBA := vba
- MAKE := make
- ### Define a standard path for make to look for ###
- ### your tools, as well as your standard path ###
- export PATH := $(realpath ../tools):$(PATH)
- ### Some arguments for gcc you can acces in ###
- ### your code, for example with #ifdef ###
- DEFINES := -DBPRE -DSOFTWARE_VERSION=0
- ### Flags to pass to your compilers, note ###
- ### that our include directory is "include", ###
- ### defined via -Iinclude if you wish to change ###
- ### it ###
- ASFLAGS := -mthumb
- CFLAGS := -mthumb -mthumb-interwork -g -mcpu=arm7tdmi -fno-inline -mlong-calls
- CFLAGS += -march=armv4t -O0 -std=c99 -Wall -Wextra -Wunreachable-code
- CFLAGS += -Iinclude $(DEFINES)
- GRITFLAGS := -ftc -fa
- LDFLAGS := -z muldefs
- ### Standard directories to put our files into ###
- ### or take the source files from ###
- BLDPATH := object
- OUTPATH := build
- SOURCEDIR := src
- LINKER := linker.lsc
- SYMBOLS := bpre.sym
- PROJECT_NAME := YOUR_PROJECT
- MAIN_OBJ := $(BLDPATH)/linked.o
- ### some commands to make enumerating files ###
- ### a bit easier ###
- rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
- ### lists of our source files, note that they ###
- ### have to be given unique extensions ###
- ### .S/.s for ASM, .c for C files, ###
- ### capitalization does matter here! ###
- ASM_SRC_PP := $(call rwildcard,src/,*.S)
- ASM_SRC := $(call rwildcard,src/,*.s)
- C_SRC := $(call rwildcard,src/,*.c)
- ASM_OBJ_PP := $(ASM_SRC_PP:%.S=$(BLDPATH)/%.o)
- ASM_OBJ := $(ASM_SRC:%.s=$(BLDPATH)/%.o)
- C_OBJ := $(C_SRC:%.c=$(BLDPATH)/%.o)
- ALL_OBJ := $(C_OBJ) $(ASM_OBJ_PP) $(ASM_OBJ)
- ### rules for making objects out of source ###
- ### we use gcc for all of our compilation, as ###
- ### it will call the assembler respectively ###
- ### and can resolve preprocessor commands ###
- $(BLDPATH)/%.o: %.c
- $(shell mkdir -p $(dir $@))
- $(CC) $(CFLAGS) -c $< -o $@
- $(BLDPATH)/%.o: %.S
- $(shell mkdir -p $(dir $@))
- $(CC) $(CFLAGS) -c $< -o $@
- $(BLDPATH)/%.o: %.s
- $(shell mkdir -p $(dir $@))
- $(AS) $(ASFLAGS) $< -o $@
- ### rules for building the whole project ###
- all: rom
- .PHONY: rom
- rom: main.asm $(MAIN_OBJ)
- $(ARS) $<
- $(NM) $(BLDPATH)/linked.o -n -g --defined-only | \
- sed -e '{s/^/0x/g};{/.*\sA\s.*/d};{s/\sT\s/ /g}' > $(OUTPATH)/__symbols.sym
- @echo "*** SUCCESSFULLY BUILT PROJECT ***"
- $(MAIN_OBJ): $(ALL_OBJ)
- $(LD) $(LDFLAGS) -T $(LINKER) -T $(SYMBOLS) --whole-archive -r -o $@ --start-group $^ --end-group
- .PHONY: clean
- clean:
- rm -f $(OUTPATH)/__symbols.sym $(OUTPATH)/build.gba
- rm -R -f object/*
- .PHONY: run
- run: rom
- $(VBA) "build/$(PROJECT_NAME).gba"
Die wichtigen Sektionen wurden von mir bereits auf Englisch kommentiert. (Generell empfielt es sich, für sowohl Code als auch Kommentare die Englische Sprache zu verwenden, es ist technisch einfach einfacher zu verstehen und international lesbar. Ihr könnt hier zum Beispiel den Namen eures Projektes ändern, in dem ihr YOUR_PROJECT durch euren Projektnamen ersetzt. (Beachtet, dass ihr dann auch in main.asm den Namen ändern müsst) - Was hier sonst noch so passiert, naja, es werden Regeln erstellt um die ganzen Dateien von ASM/ zu Maschinencode zu übersetzen. Beachtet, dass alle eure source Dateien im Ordner src liegen müssen, und ASM Dateien die Erweiterung .S/.s haben müssen, C Dateien die Erweiterung .c (Groß und Kleinschreibung ist wichtig!) - Wenn ihr euch mit makefiles auskennt, könnt ihr euch hier weiter austoben, ansonsten meine Empfehlung: Nicht angreifen.
main.asm
- //this file can access the ROM directly,
- //use it to place hooks, overwrite bytes etc.
- //open a gba file named bpre0.gba in the base folder
- //set the base symbol to 0x08000000, the ROM section
- .gba
- .thumb
- .open "base/bpre0.gba","build/YOUR_PROJECT.gba",0x08000000
- //you can include other files like so, the path is arbitrary
- //.include "patches/some_hooks.asm"
- //inside the file you only need to place instructions to override
- //locations in the rom like so
- //.org 0x08ABCDEF //some random address
- //ldr r0, =some_global_symbol_in_the_source+1
- //bx r0
- //this would create a hook over r0 to some_global_symbol_in_the_source
- //make sure the symbol is defined, and that it is thumb code
- //NOTE: All code we define should be thumb code with our compiler settings
- //You can also add hooks directly in here, this is an example which will
- //be compiled and can be tinkered with
- .org 0x0806D698
- ldr r1, =ex_per_step_scripts+1
- bx r1
- .pool
- //NOTE: We jump in r1 here because r0 is used by convention to pass the argument
- // We could also use r2-r3, those are caller-save convention registers as well
- // .pool indicates that armips may put symbolic constants here, so in this case
- // the address of ex_per_step_scripts will be located at the .pool pseudo-op
- //this needs to be done to include all our code
- //address of our code, I use a 32 MB rom, so I place it after the first
- //16 MB of our main output file, after the original code has ended
- .org 0x09000000
- .importobj "object/linked.o"
- .close
Auch hier wurde das wichtigste bereits wieder in Kommentaren dazu geschrieben. Ihr könnt eure Hooks, sofern es nur wenige sind, einfach in diese Datei schreiben, und der Syntax folgen, die das Beispiel vorgibt. Ihr könnt aber auch wie oben beschrieben, andere Dateien inkludieren, um die Übersicht zu behalten. Für ein größeres Beispiel könnt ihr zum Beispiel den source Code von Pokémon SotS oder Pokémon Violet betrachten (In den respektiven Unterforen zu finden)
bpre.sym
- /*
- * Define symbols in this file, which can be used in your C code as
- * method definitions to call internal functions, or access internal
- * memory areas (structures etc. )
- */
- script_reset_environments_and_start = 0x08069AE4|1;
- in_trade_center = 0x0811B0D0|1;
- happiness_algorithm_step = 0x0806D74C|1;
- is_tile_control_override = 0x08059D70|1;
- overworld_poison_step = 0x0806D79C|1;
- get_egg = 0x080463B8|1;
- sav_xor_increment = 0x08054E90|1;
- safari_step = 0x080A0F0C|1;
- script_walk_810c4ec = 0x0810C4EC|1;
- __aeabi_idiv = 0x081E4018|1;
- __aeabi_idivmod = 0x081E40F4|1;
- __aeabi_uidiv = 0x081E460C|1;
- __aeabi_uidivmod = 0x081E4684|1;
- memset = 0x081E5ED8|1;
- memcpy = 0x081E5E78|1;
- vram_decompress = 0x081E3B6C|1;
- wram_decompress = 0x081E3B70|1;
- random = 0x08044EC8|1;
- malloc = 0x08002BB0|1;
- free = 0x08002BC4|1;
- scr_overworld_poison_death = 0x081A8DFD;
- scr_egg_hatch = 0x081BF546;
- scr_unk_per_step_end = 0x081A8CED;
- prev_quest_mode = 0x0203ADFA;
- walkrun_state = 0x02037078|1;
Hier passiert nicht viel, ihr könnt Addressen zu Spielinternen Funktionen angeben, die ihr verwenden könnt. Beispiele (Die wir alle im Beispielcode mehr oder weniger brauchen) sind auch schon angegeben. Außerdem könnt ihr natürlich auch RAM Addressen angeben, und diese dann verwenden. Ihr müsst dem C Compiler nur sagen, wie sie zu interpretieren sind.
- /*
- * This is an example file to show you how to use the template
- */
- /* === INCLUDES === */
- #include <game_engine.h>
- #include <types.h>
- #include <agb_debug.h>
- /* === EXTERN SCRIPTS === */
- void* scr_overworld_poison_death;
- void* scr_egg_hatch;
- void* scr_unk_per_step_end;
- /* === PROTOTYPES === */
- /**
- * @brief executed each step, executes scripts based on steps
- * @param tile_to the behavior ID of the tile we walk towards
- */
- bool ex_per_step_scripts(u16 tile_to);
- /* === IMPLEMENTATIONS === */
- bool ex_per_step_scripts(u16 tile_to)
- {
- if(tile_to == 0x2)
- {
- /* this is custom added code, will be executed if we step on grass in
- * bpre vanilla, which has behavior id 0x2
- */
- dprintf("we just stepped on grass, tile_to is %d", tile_to);
- /* this also shows off the debug functionality of this template, the string
- * above will be printed to the vba log, if you are using vba
- */
- }
- if(in_trade_center() || prev_quest_mode == 2)
- {
- return false;
- }
- happiness_algorithm_step();
- if((walkrun_state.bitfield & 0x40) || is_tile_control_override(tile_to))
- {
- return safari_step();
- }
- if(!script_walk_810C4EC())
- {
- if(overworld_poison_step())
- {
- script_reset_environments_and_start(scr_overworld_poison_death);
- return true;
- }
- if(get_egg())
- {
- sav_xor_increment(0xD);
- script_reset_environments_and_start(scr_egg_hatch);
- return true;
- }
- return safari_step();
- }
- script_reset_environments_and_start(scr_unk_per_step_end);
- return true;
- }
Okay, endlich ein wenig richtiger C Code, und eigentlich auch kein allzu schwierig zu lesender. Wir inkludieren uns zuerst ein paar Header Dateien (Die könnt ihr euch anschauen, dort seht ihr wie ihr Spielinterne Methoden deklariert) und definieren eine Methode, welche wir auch in main.asm referenzieren. Nämlich überschreiben wir dort eine Originalmethode, mit unserer eigenen. Im Endeffekt ist diese Methode ein Nachbau, der Spielinternen Methode "per_step_scripts", welche ich mit einem Disassembler und Decompiler analysiert habe. Wie man sieht, weiß ich bei einigen Funktionen nicht genau, was sie tun, weswegen sie diese komischen Namen haben. Hier passiert also alles, was möglicherweise einen Script ausführen könnte, wenn man einen Schritt im Spiel macht. Die ersten Zeilen sind dafür verantwortlich, dass auf dem Log eine Ausgabe getätigt wird, wenn man durch das hohe Gras läuft. Wir können also mit der bereitgestellten "agb_debug.h" einen string auf dem Log ausgeben, und so noch mehr Debugging im Spiel unterbringen. Wir könnten hier auch eine Funktion implementieren, die bei betreten eines bestimmten tiles (Welches wir als Parameter übergeben haben) einen Script ausführt. (z.b. allen Pokémon Schaden machen, wenn man auf Feuer tritt, oder ein Pokémon im Team zufällig einschlafen lassen) Die Methode, die wir hierzu sogar schon definiert haben ist "script_reset_environments_and_start" welche als Parameter einen Scriptpointer übergeben bekommt. Vielleicht zeige ich euch in einem späteren Abschnitt, wie man mit diesem System sogar einen kleinen Script Compiler erstellen kann, der wohl mächtiger ist als XSE. Ihr könnt aber auch mit einem anderen Script Compiler Scripts in die ROM schreiben und die Methode mit "script_reset_environments_and_start((void*)(0x08ABCDEF)); aufrufen, wenn ihr einen statischen Script habt. Spielt euch einfach ein wenig mit dieser Methode, ich glaube es ist ein sehr anschauliches Beispiel, und kann ein bisschen mehr, als einfach das klassische Rot Tinten von Palletten. Außerdem in dieser Routine zu finden ist: Die Funktionalität für das Schlüpfen von Eiern und vergiftete Pokémon (Die ja alle paar Schritte Schaden bekommen) - Recht umfangreich.
Das soll es erstmals gewesen sein. In den Headern seht ihr noch, wie man dem Compiler beibringt, was z.b. an "walkrun_state" steht, nämlich eine Struktur. Für weiteres könnt ihr mich gerne fragen, was wie wo, und euch natürlich in die 2 großen Projekte hineinlesen, welche so ein System momentan verwenden.
Schlusswort
Das ist natürlich nicht das einzige, was man hiermit tun kann. Mit dem Tool "grit" lassen sich Bilder in ASM und C Code übersetzen, und mit ein paar hilfsmitteln kann man auch Musik und Texte mit dieser netten Toolchain einfügen. Dazu vielleicht mehr, in einem anderen Tutorial.
~Sturmvogel