ARTIKEL 4 - Compilieren auf anderen Plattformen/Compilern ( Teil I )

von HypoThermia


Dieser Artikel richtet sich an diejenigen Mod-Entwickler, die nicht mit MS Visual C++ arbeiten können (oder wollen). Endlich müssen nicht mehr alle an einem Project beteiligten Coder die gleiche Entwicklungsumgebung (IDE) nutzen.
Ich setze voraus, dass du die Quake3 Sourcen + Tools hast.
Der Artikel fasst die Erfahrungen zusammen, die ich beim Erstellen der Q3Sourcen mit dem Borland Compiler gemacht habe. Ich hoffe er hilft dir, die Tools zum Laufen zu bringen, die du bei der Arbeit mit den Q3Sourcen auf deinem Compiler brauchst.
Es wäre sinnvoll, als Grundlage für deine Compilerlösung, unter \quake3\source ein neues Verzeichnis anzulegen.
Die Beispiele beruhen allesamt auf meinen Erfahrungen. Es gibt keinen Ersatz für reale Erfahrungen.
Code3Arena wird bald Compilerlösungen zum Erstellen der Q3Sourcen anbieten. Als Mod-Entwickler kannst du dir dann die Plattform aussuchen, mit der du arbeiten willst, ohne den hier beschriebenen Aufwand zu betreiben :).
OK, lehn dich zurück, zieh die Schuhe aus, und versuche nicht an Käse zu denken.

1.Hintergrund
Die Quake 3 Sourcen, veröffentlicht von id Software, erstellen einen Teil des Spielcodes und erlauben es den eingefleischten Fans so, Quake 3 für die Community zu verbessern und auszubauen. Da das Spiel bereits von Haus aus mehrere Plattformen unterstützt, muss natürlich auch der Q3Source so plattformunabhängig wie möglich sein. Er wurde in portablem ANSI C geschrieben, und wird zu einem Bytecode compiliert, der auf jedem System lauffähig ist, auf dem Quake3 läuft.
Mod-Entwickler können also ihre Spiel-Erweiterungen auf einer Plattform entwickeln, und sie werden auf allen anderen laufen.
Ein Vorteil von ANSI C ist, dass es tonnenweise Programmierer mit C-Erfahrung gibt. Der andere grosse Vorteil besteht darin, dass sich für die jeweilige Arbeitsplattform Binaries erstellen lassen, die das Entwickeln und Debuggen erleichtern.


2. Ziele
Wir können die Aufgabe in drei wesentliche Bereiche aufteilen:
1. den Bytecode für Quake3 erstellen
2. entsprechende Binarys für Tests und Debugging erstellen
3. deine Arbeit für andere Entwickler zugänglich machen
Wenn du anderen Entwicklern deine Portierung zur Verfügung stellen willst, sollte sie nur die für die Portierung unbedingt nötigen Änderungen gegenüber den "originalen" Q3Sourcen enthalten. Bestenfalls fügst du eigene Batchdateien oder Scripte bei, die allerdings diejenigen der Q3Sourcen nicht überschreiben sollten. id Software könnte aktuellere Sourcen veröffentlichen, die deine Scripte wieder überschreiben. Im Idealfall sollte ein anderer Coder nur die von dir gefundenen notwendigen Veränderungen auf seine Q3Sourcen übertragen müssen.
Sieh dir "Veröffentlichen deines Projektes" im zweiten Teil dieses Artikels an, um zu sehen wie das konkret aussehen könnte.
Für die ersten Beiden Aufgaben werden die Headerdateien deines Compilers von grosser Bedeutung sein. Für den Bytecode ist das wichtigste, dass sich deine Header wie ANSI C verhalten. Dazu gleich mehr unter "3. Erste Schritte...".
Das Erstellen von Binaries auf deiner Plattform setzt die Beherrschung der Kunst voraus, portablen Code so zu modifizieren, dass er portabel bleibt. Einfach ausgedrückt, jeder andere Compiler sollte deine Q3Sourcen ohne Probleme erstellen können. Mehr dazu in "Binaries compilieren" im zweiten Teil des Artikels.


3. Erste Schritte mit Bytecode
Der Bytecode, der auf der Quake3 Virtual Machine (QVM) läuft, ist plattformunabhängig. Er wird mit Hilfe der Header deines Compilers durch lcc.exe compiliert, ein Tool von id Software. Jede der compilierten Dateien wird dann durch q3asm.exe erstellt und verlinkt.
Du wirst 3 verschiedene QVM Dateien erstellen:

qagame.qvm
Enthält den Code für den Spielserver und die Bot KI.
cgame.qvm
Verarbeitet die Ereignisse( events) und die Bildschirmdarstellung auf Clientseite.
ui.qvm
Stellt die Benutzerschnittstelle und Menüs für den Singleplayer Modus zur Verfügung.

Um den Bytecode erstellen zu können, musst du die Headerdateien überreden, sich wie plattformunabhängiges ANSI C zu verhalten.

4.Compilierung mit Hilfe von Scripts automatisieren
Teil der Q3Sourcen sind vier Batchdateien, die im DOS Modus laufen. Drei dieser Dateien kümmern sich um das Erstellen der Bytecode Module qagame, cgame und ui. Sie heissen game.bat, cgame.bat und ui.bat. Jede von ihnen ruft die vierte Batchdatei namens compile.bat auf, und übergibt ihr den Namen der Sourcedatei, die für das jeweilige QVM Modul übersetzt werden muss.
Nach dem Compilieren muss sich das Script nur noch um das Erstellen und Verlinken der Dateien kümmern. Das dazu aufgerufene q3asm.exe nutzt für jedes Modul eine entsprechende Datei: game.asm, cgame.asm und ui.asm.
Kopiere diese Dateien in dein "Compilerverzeichnis" unter Quake3\source. Da sie ihre Arbeit eigentlich im Unterverzeichnisse vm erledigen, solltest du sie folgendermassen modifizieren:
Passe die Batchdatei auf die Scriptsprache deines Systems an, achte dabei darauf, dass nur das compile-Script lcc.exe aufruft.

  • Ändere die relativen Pfade entsprechend der Sourcedateien.
  • Ändere die relativen Pfade von lcc.exe und q3asm.exe oder füge sie der PATH Umgebungsvariable ( dem executable path) hinzu ( dokumentieren!).
  • Ändere die relativen Pfade zu ..\game, ..\cgame, und ..\ui im compile-Script.

In jeder der .q3asm Dateien: ändere jeweils nur den Pfad zu cg_syscalls, ui_syscalls bzw g_syscalls, damit die richtige .asm Datei aus den Q3Source Unterverzeichnissen cgame, ui bzw game benutzt wird.
Wenn du jetzt versuchst die Scripte auszuführen, sollten sie eine Fehlermeldung über nicht gefundene Headerdateien ausspucken.

5. Deine Header verstehen
Die nachfolgenden Änderungen beschränken sich auf das compile-Script.
Zuerst müssen wir dem compile-Script sagen, wo sich die Header befinden. Versichere dich, das folgendes Argument an lcc.exe übergeben wird:

-I<path to header files>

wobei "path to header files" ein konkreter Pfad auf deinem System ist.
Jetzt, wo deine Header bekannt sind, musst du dich mit Plattform- und Compilerspezifischen Problemen befassen. Die meisten sollten durch die Übergabe von so etwas ähnlichem wie einem "#define xxxx" an lcc.exe gelöst werden. Konkret machst du das durch hinzufügen des Arguments -Dxxxx bzw -Dxxxx="" zum compile-Script.
Wenn deine Header mehr als ein Compiliermodell unterstützen, musst du sie austricksen, um eine "pure" ANSI C Definition aller Funktionen zu bekommen. Zum Beispiel könntest du die Headerdatei suchen, in der die compilerspezifischen Informationen gespeichert sind, diese Überbrücken, und stattdessen eigene Definitionen an lcc.exe übergeben.
Ausserdem musst du evt. einige Compilerspezifische Werte definieren, um die Route durch die Headerdateien zu kontrollieren. Prüfe, ob die Werte nicht schon im Q3Source verwendet werden, und falls doch, ob sie Probleme verursachen.
Beispiel:
Borland C++ benutzt Header, die sowohl ausführbare Dateien als auch DLLs unter Win32, Win16 und (nur ausführbare Dateien) unter DOS mit 6 verschiedenen Speichermodellen erstellen können.
Ich erzwang den Pfad für ausführbare Dateien unter Win32 durch die Definition von __FLAT__.Da ich den Aufruf von Win32 Funktionen im Q3Source verhindern musste ( ich wollte ja nicht nur für diese Plattform compilieren), definierte ich nicht WIN32, _WIN32 oder __WIN32__. Diese Variablen werden sowohl von Borland als auch von Microsoft Tools definiert und benutzt.
Ausserdem definierte ich die Compilerspezifische Variable __BORLANDC__, die ebenfalls die Route durch die Header beeinflusst.
Mit diesen Definitionen bekam ich Fehler von lcc.exe bezüglich _RLENTRY und ähnlichen Konstanten, also musste ich die Headerdatei entfernen, die diese Definitionen enthält: <_defs.h> (ich erreichte das durch Definition von __DEFS_H, da der Header diese Variable benutzt, um sich vor wiederholter Einbindung zu schützen)
Eine typische Borland Definition sieht so aus:

int _RTLENTRYF atoi(const char _FAR *__s);

Der Präprozessor macht durch Ersetzen der #defines folgendes daraus:

int atoi(const char *__s);

6. Halte den Code portabel
Vermeide Modifikationen an den Q3Sourcen, falls sie nicht absolut notwendig sind. Versuche stattdessen, deine Modifikationen mit Hilfe der Kommandozeilen-Optionen von lcc.exe (also dem compile-Script) zu erreichen.
Prüfe also erst, ob ein -Dxxxx dein Problem lösen kann. Falls du doch in den Sourcen arbeiten musst, mache deine Änderungen von einer Konstanten abhängig, die nur dein Compiler definiert. Versichere dich, dass diese Konstante ebenfalls im compile-Script definiert ist.
Begrenze die notwendigen Änderungen auf Dateien, die sowieso Compilerspezifische Komponenten enthalten (z.B. game\q_shared.h). Modifikationen sollten nur bei sehr wenigen Headern nötig sein.
Für das Verändern von .c (source) Dateien gilt noch noch grössere Vorsicht. Die unter "Erwartete Fehler" aufgeführten Fehler, kannst du normalerweise ignorieren.
Wenn für deine Plattform bereits eine Route durch die Q3Source Header verfügbar ist, du aber einen anderen Compiler verwendest, kannst du dir vielleicht etwas abschauen.
Dokumentiere deine Änderungen und überlasse es den Nutzern deiner Arbeit sie zu verbinden, so das sie den Prozess nachvollziehen und evt. daraus lernen können. Bedenke: die Leute die von deiner Arbeit profitieren sind selbst erfahrene C-Coder.
Beispiel #1:

Mit den Borland Headerdateien gab es einen Namenskonflikt bei der Funktion random(). Da die Q3Source Version Vorrang haben sollte, fügte ich in game\q_shared.h über der Definition von random() folgenden Code ein:


#if (defined __BORLANDC__ && defined random)
#undef random
#endif
 
   

Dies hilft auch beim Erstellen von Binarys, wo das gleiche Problem auftaucht.
Beispiel #2:
Die einzige andere Modifikation die bei mir nötig war, war in einer Borland Header Datei. lcc.exe erzeugte einen Fehler beim Parsen einer #error Anweisung (obwohl diese nicht ausgeführt wurde). Die Änderung bestand darin, die Errormessage in Anführungszeichen zu setzen.

#error "Can't include both STDARG.H and VARARGS.H"

in Zeile 20.


7. Erwartete Fehler
Trotz der Portabilität der Q3Sourcen, erzeugen die Sourcen Warnungen. Vielleicht erzeugen auch die Headerdateien einige Warnungen. Schau sie dir genauer an, und entscheide ob Modifikationen in deinen Headerdateien notwendig sind.

ui/ui_ingame.c:103
ui/ui_ingame.c:107
ui/ui_atoms.c:739
ui/ui_atoms.c:742

"Warning: Conversion of 'pointer to void'... ...das ist Compilerabhängig"

Für die QVM ist das kein Problem, für deinen Compiler könnte es schon eins sein.

Eventuell wirst du feststellen, dass sich die Grösse der erstellten QVMs deutlich von der der Quake3 QVMs unterscheidet. Die Ursache dafür ist wahrscheinlich die unterschiedliche Art, auf die Headerdateien statische Daten für ihre Implementierung nutzen.
Beispiel:
Die Borland Header erzeugen eine weitere Warnung:

limits.h:31 Character constant taken as not signed

Paradoxerweise bezieht sich diese Warnung auf Borlands Methode, um bei chars zwischen signed und unsigned zu unterscheiden. Sie kann getrost ignoriert werden.


8. Den Bytecode testen
Wende Tutorial#3 an, um einen "Slow Rocket Mod" zu erstellen. Vergiss nicht, die Änderungen rückgängig zu machen! Versuche die Demos mit "timedemo 1" abzuspielen. Nicht als Performancetest, sondern um sicherzustellen, dass jedes Mal die gleichen Frames dargestellt werden.
Spiel ein paar Runden gegen Bots oder zieh deine Kumpels ab. Du hast es dir verdient!


9.Soweit so gut, und jetzt ?
Die compilierten QVMs hast du schon. Als nächstes solltest du versuchen den Code zu Binarys zu compilieren. Das ist Thema des zweiten Teils dieses Artikels, inklusive einiger Vorschläge, wie du deine Arbeit organisieren kannst, wenn du sie anderen zur Verfügung stellen willst.

<< zurück/back >>