Dienstag, 19. April 2011

Wolfenstein: Enemy Territory 64Bit

Ein ganzer Tag für nichts!

Vor ein paar Tagen hatte ich schon über das neue "True Combat : Close Quarters Battle" gehört und versucht, es zum laufen zu bekommen. Heute früh war ein How-To vom LinuxGameCast online


(http://linuxgamecast.com/2011/04/l-g-c-how-to-%E2%80%94-truecombat-close-quarters-battle-cqb/)

Dieses befolgend, kam ich genauso weit, wie beim letzten mal. Wieder kam ein netter "Segmentation Fault" und die Suche nach Lösungen brachte mich nicht weiter. Leicht genervt erinnerte ich mich daran, dass der Quellcode frei zugänglich sein sollte. Eben diesen habe ich mir dann von hier geladen:
ftp://ftp.idsoftware.com/idstuff/source/ET-GPL.zip
Das Kompilieren mit SCons sollte eigentlich recht einfach gehen. Doch dann kam auch schon ein Fehler beim Linken. libstdc++ und libgcc und libgcc_s seien nicht da. Diese haben mir gefehlt, da ich die passenden multilib-Pakete nicht installiert hatte. Die werden benötigt, da der Code für 32Bit erstellt wird und nicht, wie mein System eigentlich läuft, für 64Bit. Ein wenig Neugier hatte mich gepackt und schon versuchte ich, die passenden Flags von 32 auf 64 zu stellen. Die Option "-m32" musste auf "-m64" umgeändert werden. Eine weitere kleine Verbesserung war, "-march=i586" auf "-march=nativ" zu setzten. Damit soll der Compiler versuchen, den Code ganz genau auf die gerade aktive Plattform zu zuschneiden. Das war auch schnell erledigt, jedoch kamen damit auch schon ein paar Fehlermeldungen.
qcommon/vm_x86.c - Assembler-Code !
Eine kleine Internetbefragung ergab, dass 64Bit-Register mir "r" anfangen und nicht mit "e". eax, ebx, ..., esi, edi wurden also entsprechend abgeändert und schon kamen neue Überraschungen. Die netten Befehle mit einem "l" am Ende, wie z.B. movl, addl, shll, pushl, popl, sind nicht für die 64Bit-Register. Dafür gibt es Befehle mit einem q am Ende. Also wurden auch die schnell abgeändert. Der nächste Fehler kam bei popa und pusha, die normalerweise zum Sichern und Wiederherstellen von allen Registern genutzt werden. Diese beiden Befehle sind aber für 64Bit nicht zulässig. Zum Glück war der Code nicht so groß und lies sich gut überblicken. Daraus zog ich, welche Register ich nun speichern und wiederherstellen müsse.
Damit war der Abschnitt auch schon erledigt.

unix/snapvector.asm war nun der nächste Problemfall. Hierbei war jedoch nur dessen Assembler zu sagen, dass das zu assemblierende Objekt für 64Bit ausgelegt sein soll. Dies gestaltet sich mit der Option "-f elf64".

unix/unix_main.c enthält ebenfalls eine kleine Stolperfalle.
#if defined __i386__
    return va( "%s.mp.i386.so", name );
#elif defined __ppc__
    return va( "%s.mp.ppc.so", name );
#elif defined __axp__
    return va( "%s.mp.axp.so", name );
#elif defined __mips__
    return va( "%s.mp.mips.so", name );
#else
    #error Unknown arch
#endif
Mein System läuft, wie gesagt, auf 64Bit. "cpp -dM /dev/null" brachte mir eine Liste mit definierten Macros, wie sie auf meinem System gesetzt sind. Darunter sind auch "__amd64__" und "__x86_64__".  Schnell sind also die paar Zeilen hinzugefügt:
#elif defined __amd64__
    return va( "%s.mp.amd64.so", name );
#elif defined __x86_64__
    return va( "%s.mp.x86_64.so", name );

Nun gab es noch ein Problem mit einer nicht definierten Funktion. In der Datei client/snd_mix.c steckt die Funktion
"void S_WriteLinearBlastStereo16( void );", die für Linux nicht definiert wird. Bisher weiß ich leider nicht, wie ich die korrekt lösen könnte. Deshalb habe ich mir damit beholfen, den Code von weiter unten einfach zu übernehmen.

Der Code sollte nun Compilieren. Beim laufen jedoch kommt es zu Fehlern.
Einer ist im qsort versteckt. qcommon/files.c beherbergt qsort mit der festen Größenangabe von 4, welche auf 64Bit jedoch nicht mehr stimmt und somit zu Speicherverletzungen führt.

Einen Fehler bin ich soweit hinterher gekommen, dass ich ihn an passender Stelle abbrechen konnte. Leider finde ich die Stelle nicht mehr wieder. Es hatte allerdings mit var_name und value zu tun und in besagtem war var_name nicht belegt und value hatte einen Wert, der ein Stück einem Pointer ähnelte. An einer Stelle fragte ich also ab, ob var_name überhaupt da wäre und wenn nicht, sollte entsprechende Funktion gar nicht aufgerufen werden.  Die aufzurufende Funktion hat normalerweise einen Rückgabewert, der gleich weiter verwendet wird. Dieser Wert war ein Pointer, der jedoch nicht auf NULL überprüft wurde und so Fehler verursachen konnte.

In den ersten Bereichen war mit gdb und ddd eine sehr große Hilfe. Im Debug-Modus kompiliert, spuckten sie sehr viele Informationen aus und ließen so den Code schnell, einfach und passend verändern. Ab einem bestimmten Punkt schien jedoch ein Thread mit dabei zu seien, der weitere Untersuchungen unmöglich gemacht hat. Normalerweise kann gdb, soweit ich weiß, mit Threads umgehen. Hier jedoch schient er nicht mitspielen zu wollen. Nach Stunden voller Kleinarbeit musste ich also erst einmal aufgeben.

Ich besann mich auf die 32Bit und installierte die Bibliotheken. Den Code entpackte ich frisch um keine etwaigen Fehler darin zu haben. Diesmal klappte das Compilieren. Dank der herrlichen Link-Funktion unter Linux konnte ich die erstellten Dateien mit dem Spieleordner verbinden ohne sie hinein kopieren zu müssen. Dies machte es sehr einfach, immer wieder diese Dateien zu erstellen und eben nicht jedes mal neu kopieren zu müssen.
Trotz dessen, dass ich nun "eigene" Dateien hatte, wollte W:ET nicht anspringen. Langsam wurde es mir echt zu dumm.
In der Zwischenzeit hatte ich mit der Kommandozeilenoption "+set r_fullscreen 0" einen Weg gefunden, dass das Spiel nicht ständig im Vollbildmodus startete. Dieser war noch immer auf 800x600 eingerichtet.
Noch einmal das Glück im Internet versuchen, gelang ich auf:
http://ubuntuforums.org/archive/index.php/t-1070349.html
gab mir dies:
A simple google with your latest error gave me this:

try to start et with: $LD_PRELOAD=/usr/lib/libGL.so.1 et
Das brachte mich allerdings auch nicht weiter.
Oder doch.
Da das Spiel 32Bit war und mein Rechner 64Bit, war mir klar, dass in /usr/lib/ die 64Bit Bibliotheken liegen. Für die 32Bit Bibliotheken gibt es /usr/lib32/. Also habe ich es mit "
$LD_PRELOAD=/usr/lib32/libGL.so.1 et"
probiert. Und siehe da: Es hat funktioniert!

Der Sound wollte zwar nicht, und das, obwohl ich mich an das How-To (siehe Anfang) gehalten hatte, aber wenigstens hatte ich es geschafft, das Spiel zum laufen zu bekommen.
Ich habe es nun noch nicht angespielt, war aber auf einem Server und hatte mich etwas umgesehen. TC:CQB sieht noch aus, wie True Combat (was auch kein Wunder ist, da es ja eine Weiterentwicklung davon ist).


So bin ich heute zwar leider nicht zum Spielen gekommen, konnte dafür aber ein wenig im Sourcecode von ID herum wühlen und hier und da ein paar Dinge lernen. Und es gab auch immer mal etwas zum Schmunzeln, wie zum Beispiel dieses hier:
/*
============
VM_DllSyscall

Dlls will call this directly

 rcg010206 The horror; the horror.

  The syscall mechanism relies on stack manipulation to get it's args.
   This is likely due to C's inability to pass "..." parameters to
   a function in one clean chunk. On PowerPC Linux, these parameters
   are not necessarily passed on the stack, so while (&arg[0] == arg)
   is true, (&arg[1] == 2nd function parameter) is not necessarily
   accurate, as arg's value might have been stored to the stack or
   other piece of scratch memory to give it a valid address, but the
   next parameter might still be sitting in a register.

  Quake's syscall system also assumes that the stack grows downward,
   and that any needed types can be squeezed, safely, into a signed int.

  This hack below copies all needed values for an argument to a
   array in memory, so that Quake can get the correct values. This can
   also be used on systems where the stack grows upwards, as the
   presumably standard and safe stdargs.h macros are used.

  As for having enough space in a signed int for your datatypes, well,
   it might be better to wait for DOOM 3 before you start porting.  :)

  The original code, while probably still inherently dangerous, seems
   to work well enough for the platforms it already works on. Rather
   than add the performance hit for those platforms, the original code
   is still in use there.

  For speed, we just grab 15 arguments, and don't worry about exactly
   how many the syscall actually needs; the extra is thrown away.

============
*/