GBA-internes Programmieren einer Flash-Karte:

[Wozu überhaupt?] [How does it work?] [Des Rätzels Lösung] [Funktionen im Arbeitsspeicher] [Ende gut, alles gut]

Wozu überhaupt?

Wenn man hört, dass jemand eine GBA- Flash-Karte besitzt, denkt man als erstes, dass er sie nur zum spielen von Roms benutzt. Vermutlich ist das in 90% der Fälle auch die Wahrheit, dabei kann man mit diesen Karten mehr machen als man glaubt. Man kann mit ihnen sogar mehr machen, als ein GBA- Programmierer mit einem echten GBA- Modul machen kann ;) Man ist nämlich in der Lage Daten auch WÄHREND dem Betrieb des GBA auf der Karte Daten zu Flashen. Das ist insofern faszinierend, da sich dadurch eine Vielzahl neuer Möglichkeiten für GBA- Entwickler eröffnet. Wenn man ein gesamtes Modul ansprechen kann, bedeutet das, dass sich der Speicherplatz für Savegames und dergleichen schlagartig von max. 64 KB auf bis zu 32 MB erhöht. Dieses enorme Plus an zugänglichem Speicher wäre vor allem bei Projekten, wie speicherintensiven Rollenspielen, mit einer wesentlich vielschichtigeren und interaktiveren Spielwelt, als bei bisherigen GBA-Spielen, oder GBA- Betriebssystemen, bei denen dann Bild-, und Text-Verarbeitungsprogramme möglich wären, sehr interessant.

How does it work?

Möglich wird das ganze über die Bibliothek cartlib.c, die schon vor einiger Zeit von Jeff Frohwein geschrieben wurde. In ihr sind Funktionen definiert, mit denen wir auf den Flash- Speicher unserer Karte zugreifen können. Jedoch ist diese nun bereits weit über ein Jahr alt und so ist es nicht verwunderlich, dass es damals nur Visoly und Borden Flashkarten gab. Keine F2A wie man sie heute überall findet. Schauen wir uns die cartlib mal kurz im gröberen Überblick an. Am Anfang der Datei befindet sich ein ganzer Block von Kommentaren, in denen auf die Anwendung der Funktionen eingegangen wird. Als nächstes kommen eine Vielzahl von defines. Acht geben sollte man vor Allem auf die ersten vier:
0 | #define NONTURBO_FA_SUPPORT 1 // Visoly Non-Turbo flash carts
1 | #define TURBO_FA_SUPPORT 1 // Visoly Turbo flash carts
2 | #define NOA_FLASH_CART_SUPPORT 1 // Official Nintendo flash carts
3 | #define SET_CL_SECTION 1 // Enable setting code section for cartlib
Hier kann man Teile der Lib einfach weglassen, und so Arbeitsspeicher sparen, indem man unbenutzte Funktionen, wie die für die offiziellen Nintendo- Flash- Karten, es sei denn jemand von euch besitzt so etwas, einfach weglässt. Dazu muss man das entsprechende define, in diesem Fall NOA_FLASH_CART_SUPPORT, lediglich zu einem Kommentar machen. Mit SET_CL_SECTION wird definiert, wo die Funktionen im Arbeitsspeicher abgelegt werden. Darum kümmern wir uns später selbst. Deshalb können wir auch das ruhig weglassen. Nun beginnt der eigentliche Teil der cartlib. In ihm sind die ganzen Funktionen definiert. Am Anfang steht noch eine Liste mit allen Funktions-Prototypen. Die können wir aber ruhig vergessen, da sie durch #ifdef SET_CL_SECTION und #endif automatisch ignoriert wird. Es ist allerdings gut zu wissen, wo sich eine Kopie der Prototypen befindet, da wir dann nicht selber alles von Hand Tippen müssen. OK, nun zu den Funktionen. Hier sind mal die, die wir verwenden werden, um eine Turbo-Flash-Karte von Visoly ansprechen zu können: void SetFAFlashRWMode (void); Mit dieser Funktion aktiviert man den „Schreib-Modus“ der Karte. Sie muss vor jedem Aufruf einer anderen Funktion der Bibliothek aufgerufen werden, damit die Karte überhaupt beschreibbar ist. u32 EraseTurboFABlocks (u32 StartAddr, u32 BlockCount); Diese Funktion formatiert einen Bereich der Speicherkarte um ihn beschreibbar zu machen.
BlockCount | sagt der Funktion, wie viele Blöcke sie Formatieren soll. Ein Block ist eine Ordnungseinheit der Flash-Karte. Wenn man der Flash-Karte den Befehl gibt Speicher zu formatieren kann sie nur ganze Blöcke löschen. Ein Block ist bei alten Flash-Karten(die langen (65 mm)) 128 KB groß. Bei den neuen, also auch bei unserem Beispiel beträgt die Größe eines Blocks 256 KB.
StartAddr | gibt die Adresse an, ab der Karte formatiert werden soll. Ich bin mir nicht sicher, aber ich glaube die Funktion geht automatisch an den Anfang des Blocks, in dem sich die Adresse befindet. Deshalb sollte man darauf acht geben, dass sich in diesem Bereich keine sensiblen Daten, oder gar Programm-Code befindet, und besser einen ordentlichen Sicherheitsabstand vom übrigen Programm nehmen.
u32 WriteTurboFACart (u32 SrcAddr, u32 FlashAddr, u32 Length); Hier geschieht endlich das worauf wir alle gewartet haben: Die Daten werden auf die Karte geschrieben.
SrcAddr | ist die Start-Adresse der Daten, die wir auf den Speicher schreiben wollen.
Length | gibt die Länge der Daten in 64 Byte Blöcken(bei alten Karten 32 Byte) an. Das bedeutet, dass man, wenn man z.B. 1 KByte in den Speicher schreiben will, dem Parameter den Wert 16 (1024/64 = 16) übergeben muss. Ist der Wert nicht durch 64 teilbar, muss man aufrunden.
FlashAddr | ist die Start-Adresse des zu beschreibenden Speicherbereiches. Wie bei EraseTurboFABlocks() vermute ich, dass hier automatisch auf den Beginn eines 64 Byte Blocks abgerundet wird.. Also: Aufpassen
Die meisten anderen Funktionen sind praktisch ident mit den oberen, nur mit dem Unterschied, dass sie eben für andere Flash-Karten ausgelegt sind. Erwähnenswert wäre vielleicht auch noch CartTypeDetect(). Diese Funktion gibt einen u8 Wert aus, mit dem man die Art der Flashkarte feststellen kann, die gerade im GBA steckt. Jedoch schließt dies Visoly EXTREME Flashkarten sowie als auch F2A Karten aus die damals einfach noch nicht auf dem Markt waren. Oki, wir kennen jetzt alle Funktionen, die wir im Moment brauchen, linken die Bibliothek mit unserer .c File, und rufen die Funktionen in der richtigen Reihenfolge auf. Dann schreiben wir die kompilierte .gba File auf unsere Flash-Karte, und... Der GBA friert ein.

Des Rätsels Lösung:

Das Problem liegt daran, dass der GBA, während sich die Karte im Schreib-Modus befindet, nicht auf Daten, die sich auf ihr befinden zugreifen kann. Und das gilt natürlich auch für Programm-Code. Will man also in den Schreib-Modus wechseln und ruft deshalb die Funktion SetFAFlashRWMode() auf, blockiert der GBA noch vor deren Beendigung, da sie sich in einem Speicher-Bereich befindet, der von nun an unzugänglich ist. Deshalb muss man dafür sorgen, dass sich jedes Stückchen Code, das während unserem Vorgang ausgeführt wird im Arbeitsspeicher des GBA befindet.

Funktionen im Arbeitsspeicher

Zum anlegen einer Funktion im Arbeitsspeicher gibt es mehrere Methoden. Allerdings sind die Funktionen der cartlib bereits für eine spezielle ausgelegt. Sie verwendet das crt0.s Linker-Skript, das ebenfalls von Jeff Frohwein geschrieben wurde, und bei der aktuellen Version des DevKit Advance Compilers standard- mäßig dabei ist. Um also eine Funktion beim Start des GBA in den Arbeitsspeicher(wir nehmen den internen, da er eine höhere Datenrate hat, und außerdem weil so der größere externe Arbeitsspeicher nicht mit Funktionen blockiert wird) zu kopieren muss man lediglich ihren Prototypen so deklarieren: Typ Funktion(Parameter) __attribute__ ((section (".iwram"))); Das ".iwram" lässt sich auch durch ".ewram" ersetzen, wenn man möchte, dass die Funktion im externen Arbeitsspeicher abgelegt wird. Der Einfachheit halber werden wir den hinteren Teil einfach durch ein Makro ersetzen:
0 | #define CL_SECTION __attribute__ ((section (".iwram")))
um die Funktion dann lediglich so Typ Funktion(Parameter) CL_SECTION; deklarieren zu müssen. Falls ihr euch darüber Sorgen mach, dass euer schöner Arbeitsspeicher blockiert wird habe ich eine gute und eine schlechte Nachricht. Erstens. Ja, ein Teil des Arbeitsspeichers wird dauerhaft durch die Funktionen blockiert. Allerdings ist das nicht so schlimm wie es Anfangs scheint, da sie in kompilierter Form gerade mal etwas mehr als 5 KB haben, was für diesen Bonus an Speicherkapazität getrost geopfert werden kann. Leider lassen sich so angelegte Funktionen nicht einfach mit ihrem Namen aufrufen. Was bedeutet, dass der GBA beim Aufruf der Funktion einfrieren wird. Man muss einen Funktions-Pointer an sie binden und den dann ausführen. Ein Pointer auf eine Funktion wird auf diese Weise deklariert und aufgerufen:
0 | void (*ptr2Function)();
1 | ptr2Function = Funktion;
2 | ptr2Function(Parameter);
Glücklicherweise kann man diese umständliche Methode in zwei Makros kapseln:
0 | #define INIT_BRANCH void (*ptr2Function)();
1 | #define BRANCH(a,b...) ({ ptr2Function = a; ptr2Function(b);})
Mit INIT_BRANCH wird der Zeiger erzeugt und mit BRANCH wird die Funktion aufgerufen.
0 | INIT_BRANCH
1 | BRANCH(Funktion, Parameter1, Parameter2, usw);
Wobei man so viele Parameter übergeben kann wie man will. Oder besser gesagt, wie es die Funktion verlangt. Diese Methode funktioniert so lange gut, bis man auch einen Rückgabewert von der Funktion benötigt. Dann muss man einen neuen Pointer deklarieren, der den Datentyp des Rückgabewertes der Funktion hat. Sooo weit so gut . . . Zweiter Versuch. Wir deklarieren nun die Funktionen der cartlib in einer selbst gemachten cartlib.h Header, damit wir sie auch von unserer .c-File aus aufrufen können. Außerdem benötigen wir noch eine Funktion, die die einzelnen cartlib- Funktionen aufruft. Diese muss auch im Arbeitsspeicher abgelegt sein. Ich habe dafür eine eigene Header und .c-File angelegt. Wenn wir das nun kompilieren wollen, bekommen wir erst mal eine Vielzahl von Warnungen, was daran liegt, dass wir die Funktionen mit einem void Zeiger aufrufen. Das ist allerdings völlig normal und kann ignoriert werden. Die .gba-File wird anstandslos erzeugt. Wenn wir sie nun auf unserem GBA starten, sehen wir, dass es diesmal hingehaut hat.

Ende gut, alles gut...

Ich hoffe ich habe euch einigermaßen verständlich erklärt, wie man Daten auf seiner Flash-Karte speichern kann. Falls jedoch etwas bei meinem Tutorial unklar ist, könnt ihr mir jederzeit Fragen im planetgameboy.de Forum, sowohl im dev-Bereich als auch per pn, stellen. Anbei ist eine Kopie meiner Demo. Wie gesagt läuft sie bis jetzt nur auf Visoly Turbo-Flash-Karten. Ich werde versuchen die darin enthaltene Funktion einfacher anwendbar und flexibler zu machen. Ob es je möglich sein wird, F2A-Karten zu beschreiben kann ich nicht sagen. Ich werde mich auf jeden Fall bemühen es zu ermöglichen

Source Download
DevGBA
www.robsite.de