Programmieren für den GBA

  • Inhaltsverzeichnis

    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

    Code
    1. PROJECT_ROOT
    2. /tools
    3. /rhr_coding_template
    4. /src
    5. /base
    6. ...



    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

    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


    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

    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.


    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


    Let the old ways live and prosper in the hearts of our young


  • Ist das eigentlich Absicht, dass du bei den CFLAGS -O0 drin hast? Weil wirklich auf Source Code Ebene tut man aufm GBA ja eh nie debuggen und kanns sogesehen direkt auf O3/O2 schalten.
    Das -z muldefs sollte man eigentlich auch mal rausnehmen. Das macht einem idR auch mehr kaputt als richtig (wobei ich grade gar nicht weiß wie das bei SotS ist).

    Du möchtest mich mal treffen? Dann sag hallo und besuche mich an der FAU in Erlangen.

  • Der Debug Code wollte früher ohne Optimierung nicht kompilieren, außerdem ist es manchmal schön zu sehen, was der Compiler mit dem Output eigentlich genau macht, um das ein wenig nachvollziehen zu können. Im Regelfall würde man wsl. mit -O3 kompilieren, aber das sind Dinge, die man im Endeffekt eh aus der Dokumentation des Compilers entsprechend einmal ablesen kann / wird.


    Bezüglich der muldefs: Das ist tatsächlich etwas altes aus SotS, viel kaputt machen tut das nicht, wenn man keine multiplen Definitionen verwendet.


    ~Sturmvogel


    Let the old ways live and prosper in the hearts of our young