src/network/nKrawall.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 *************************************************************************
00004 
00005 ArmageTron -- Just another Tron Lightcycle Game in 3D.
00006 Copyright (C) 2000  Manuel Moos (manuel@moosnet.de)
00007 
00008 **************************************************************************
00009 
00010 This program is free software; you can redistribute it and/or
00011 modify it under the terms of the GNU General Public License
00012 as published by the Free Software Foundation; either version 2
00013 of the License, or (at your option) any later version.
00014 
00015 This program is distributed in the hope that it will be useful,
00016 but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018 GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00023   
00024 ***************************************************************************
00025 
00026 Although it is already included in the GPL, let me clarify:
00027 anybody, especially the Krawall Gaming network, is allowed
00028 to run a server/master server build from modified versions of
00029 this file WHITHOUT releasing the source to the public (provided
00030 the executable is not distributed).
00031 
00032 
00033 */
00034 
00035 #include "nKrawall.h"
00036 #include "nNetwork.h"
00037 #include "nServerInfo.h"
00038 #include "nNetObject.h"
00039 #include "tString.h"
00040 #include "tArray.h"
00041 #include "tConsole.h"
00042 #include "tSysTime.h"
00043 #include "tMemManager.h"
00044 #include "tRandom.h"
00045 
00046 #include <stdlib.h>
00047 #include <string>
00048 #include <vector>
00049 #include <string.h>
00050 
00051 #ifndef DEDICATED
00052 // on the client, we want to disable the broken bmd5 by default.
00053 static tString sn_methodBlacklist( "bmd5" );
00054 #else
00055 // servers should still accept it, though.
00056 static tString sn_methodBlacklist( "" );
00057 #endif
00058 static tConfItemLine sn_methodBlacklistConf( "HASH_METHOD_BLACKLIST", sn_methodBlacklist );
00059 
00060 static void sn_GetSupportedMethods( std::vector< tString > & toFill )
00061 {
00062     char const * protocols[] = { 
00063         "md5",
00064         "bmd5",
00065         0
00066     };
00067 
00068     // iterate through methods, starting from best, and return the first that fits
00069     char const * const * run = protocols;
00070     while ( *run )
00071     {
00072         tString method( *run );
00073         if ( !tIsInList( sn_methodBlacklist, method ) )
00074         {
00075             toFill.push_back( method );
00076         }
00077         ++run;
00078     }
00079 }
00080 
00081 static bool sn_IsSupportedMethod( tString const & method )
00082 {
00083     std::vector< tString > methods;
00084     sn_GetSupportedMethods( methods);
00085     
00086     for( std::vector< tString >::iterator iter = methods.begin(); iter != methods.end(); ++iter )
00087     {
00088         if ( method == *iter )
00089         {
00090             return true;
00091         }
00092     }
00093 
00094     return false;
00095 }
00096 
00097 // supported authentication methods of this client in a comma separated list 
00098 tString nKrawall::nMethod::SupportedMethods()
00099 {
00100     std::ostringstream s;
00101 
00102     std::vector< tString > methods;
00103     sn_GetSupportedMethods( methods);
00104     
00105     bool first = false;
00106     for( std::vector< tString >::iterator iter = methods.begin(); iter != methods.end(); ++iter )
00107     {
00108         if ( !first )
00109         {
00110             s << ", ";
00111         }
00112         s << *iter;
00113 
00114         first = false;
00115     }
00116 
00117     return tString( s.str().c_str() );
00118 }
00119 
00120 // checks whether both a and b support protocol m
00121 static bool sn_BothHave( tString const & a, tString const & b, tString const & m)
00122 {
00123     return tIsInList( a, m ) && tIsInList( b, m );
00124 }
00125 
00126 // from two strings of supported-method-lists, select the best one
00127 tString nKrawall::nMethod::BestMethod( tString const & a, tString const & b )
00128 {
00129     tString ret;
00130     
00131     // iterate through methods, starting from best, and return the first that fits
00132     std::vector< tString > methods;
00133     sn_GetSupportedMethods( methods);
00134     for( std::vector< tString >::iterator iter = methods.begin(); iter != methods.end(); ++iter )
00135     {
00136         if ( sn_BothHave( a, b, *iter ) )
00137         {
00138             return *iter;
00139         }
00140     }
00141 
00142     return tString("");
00143 }
00144 
00146 bool nKrawall::nMethod::BestLocalMethod( tString const & supportedOnClient, nMethod & result )
00147 {
00148     nMethod const * const * run = LocalMethods();
00149 
00150     while ( * run )
00151     {
00152         if ( sn_IsSupportedMethod( (*run)->method ) && tIsInList( supportedOnClient, (*run)->method ) )
00153         {
00154             result = **run;
00155             return true;
00156         }
00157         
00158         ++run;
00159     }
00160 
00161     return false;
00162 }
00163 
00164 bool nKrawall::nMethod::Equal( nMethod const & a, nMethod const & b )
00165 {
00166     return a.method == b.method && a.prefix == b.prefix && a.suffix == b.suffix;
00167 }
00168 
00169 // does standard replacements to prefix and suffix;
00170 // %u -> username
00171 static tString sn_Replace( nKrawall::nScrambleInfo const & info, tString const & original )
00172 {
00173     std::istringstream in( static_cast< char const * >( original ) );
00174     std::ostringstream out;
00175 
00176     char s = in.get();
00177     while ( !in.eof() )
00178     {
00179         if ( s != '%' || in.eof() )
00180         {
00181             out.put(s);
00182         }
00183         else
00184         {
00185             s = in.get();
00186             if ( s == 'u' )
00187             {
00188                 out << info.username;
00189             }
00190             else
00191             {
00192                 out << '%' << s;
00193             }
00194         }
00195 
00196         s = in.get();
00197     }
00198 
00199     return tString( out.str().c_str() );
00200 }
00201 
00202 void nKrawall::nMethod::ScramblePassword( nScrambleInfo const & info, tString const & password, nScrambledPassword & scramble ) const
00203 {
00204     if ( method == "bmd5" )
00205     {
00206         nKrawall::BrokenScramblePassword( password, scramble );
00207     }
00208     else // must be "md5"
00209     {
00210         tASSERT( method == "md5" );
00211         nKrawall::ScramblePassword( sn_Replace(info,prefix) + password + sn_Replace(info,suffix), scramble );
00212     }
00213 }
00214 
00216 void nKrawall::nMethod::ScrambleSalt( nSalt & salt, tString const & serverIP ) const
00217 {
00218     if ( method != "bmd5" )
00219     {
00220         // just some random operation
00221         nSalt tmp;
00222         nKrawall::ScramblePassword( serverIP, tmp );
00223         nKrawall::ScrambleWithSalt2( salt, tmp, salt );
00224     }
00225 }
00226 
00227 // scramble a password hash with a salt
00228 void nKrawall::nMethod::ScrambleWithSalt( nScrambleInfo const & info, nScrambledPassword const & scrambled, nSalt const & salt, nScrambledPassword & result ) const
00229 {
00230     // sanity check
00231     if ( !sn_IsSupportedMethod( method ) )
00232     {
00233         memset( &result, sizeof(result), 0);
00234         con << tColoredStringProxy(1,0,0) << "INTERNAL ERROR OR PHARMING ATTEMPT:" <<  tColoredStringProxy(1,1,1) << " unsupported hash method " << method << " selected.\n";
00235         return;
00236     }
00237 
00238     // nothing fancy heere
00239     nKrawall::ScrambleWithSalt2( scrambled, salt, result );
00240 }
00241 
00242 // construct a method from the type and a stream with properties
00243 // the stream is supposed to consist of lines of the "property_name property_value" form.
00244 nKrawall::nMethod::nMethod( char const * method_, std::istream & properties )
00245 {
00246     method = method_;
00247     
00248     while ( !properties.eof() )
00249     {
00250         tString property;
00251         properties >> property;
00252         tToLower( property );
00253 
00254         std::ws( properties );
00255         if ( property == "prefix" )
00256         {
00257             prefix.ReadLine( properties );
00258         }
00259         else if ( property == "suffix" )
00260         {
00261             suffix.ReadLine( properties );
00262         }
00263 
00264     }
00265 }
00266 
00267 nKrawall::nMethod::nMethod( char const * method_, char const * prefix_, char const * suffix_)
00268 
00269     : method( method_ ),
00270       prefix( prefix_ ),
00271       suffix( suffix_ )
00272 {
00273 }
00274 
00275 #ifdef KRAWALL_SERVER_LEAGUE
00276 bool nKrawall::MayRequirePassword(tString& adress, unsigned int port)
00277 {
00278     return true;
00279     // TODO: Check for krawall adress
00280 
00281     if (adress.Len() < 4)
00282         return false;
00283 
00284     if (!strncmp(adress, "127.", 4))
00285         return true;
00286 
00287     return false;
00288 }
00289 #endif
00290 
00291 bool nKrawall::ArePasswordsEqual(const nScrambledPassword& a,
00292                                  const nScrambledPassword& b)
00293 {
00294     for (int i=15; i>=0; i--)
00295         if (a[i] != b[i])
00296             return false;
00297 
00298     return true;
00299 }
00300 
00301 nKrawall::nCheckResult::nCheckResult()
00302 : aborted( false ), automatic( false ){}
00303 
00304 nKrawall::nCheckResult::~nCheckResult(){}
00305 
00306 nKrawall::nCheckResult::nCheckResult( nCheckResult const & other )
00307 : nCheckResultBase( other ), user( other.user ), aborted( other.aborted ), automatic( other.automatic )
00308 {
00309 }
00310 
00311 static void sn_WriteHexByte( std::ostream & s, int c )
00312 {
00313     // don't want to rely on filling type iomanip things, never learned how to use them reliably
00314     s << std::hex <<  std::setfill('0') << std::setw(2) << c;
00315     // s << ( val & 0xF0 ) / 0x10;
00316     // s << ( val & 0x0F );
00317 }
00318 
00319 // encode scrambled passwords and salts as hexcode strings
00320 tString nKrawall::EncodeScrambledPassword( nScrambledPassword const & scrambled )
00321 {
00322     std::ostringstream s;
00323     for( int i = 0; i < 16; ++i )
00324     {
00325         unsigned int val = scrambled[i];
00326         sn_WriteHexByte( s, val );
00327     }
00328 
00329     return tString( s.str().c_str() );
00330 }
00331 
00332 // encode a string for safe inclusion into an URL
00333 tString nKrawall::EncodeString( tString const & original )
00334 {
00335     std::istringstream in( static_cast< char const * >( original ) );
00336     std::ostringstream out;
00337 
00338     char c = in.get();
00339     while ( !in.eof() )
00340     {
00341         if ( c == ' ' )
00342         {
00343             out.put( '+' );
00344         }
00345         else if ( isalnum( c ) )
00346         {
00347             out.put( c );
00348         }
00349         else
00350         {
00351             out.put('%');
00352             out << std::uppercase;
00353             sn_WriteHexByte( out, c );
00354         }
00355         c = in.get();
00356     }
00357 
00358     return tString( out.str().c_str() );
00359 }
00360 
00361 // network read/write operations of these data types
00362 void nKrawall::WriteScrambledPassword(const nScrambledPassword& scrambled,
00363                                       nMessage &m)
00364 {
00365     for (int i = 7; i>=0; i--)
00366         m.Write(scrambled[i << 1] + (scrambled[(i << 1) + 1] << 8));
00367 }
00368 
00369 void nKrawall::ReadScrambledPassword( nMessage &m,
00370                                       nScrambledPassword& scrambled)
00371 {
00372     for (int i = 7; i>=0; i--)
00373     {
00374         unsigned short x;
00375         m.Read(x);
00376         unsigned char low  = x & 255;
00377         unsigned char high = (x - low) >> 8;
00378 
00379         scrambled[ i << 1     ] = low;
00380         scrambled[(i << 1) + 1] = high;
00381     }
00382 }
00383 
00384 // network read/write operations of these data types
00385 void nKrawall::WriteScrambledPassword(const nScrambledPassword& scrambled,
00386                                       std::ostream &s)
00387 {
00388     for (int i = 15; i>=0; i--)
00389         s << (int)scrambled[i] << ' ';
00390 }
00391 
00392 void nKrawall::ReadScrambledPassword( std::istream &s,
00393                                       nScrambledPassword& scrambled)
00394 {
00395     for (int i = 15; i>=0; i--)
00396     {
00397         int x;
00398         s >> x;
00399         scrambled[i] = x;
00400     }
00401 }
00402 
00403 
00404 
00405 // scramble a password locally (so it does not have to be stored on disk)
00406 void nKrawall::ScramblePassword(const tString& password,
00407                                 nScrambledPassword &scrambled)
00408 {
00409     md5_state_t state;
00410     md5_init(&state);
00411     md5_append(&state, (md5_byte_t const *)(password.c_str()), password.size());
00412     md5_finish(&state, scrambled.content);
00413 }
00414 
00415 // scramble a password locally (so it does not have to be stored on disk)
00416 void nKrawall::BrokenScramblePassword(const tString& password,
00417                                       nScrambledPassword &scrambled)
00418 {
00419     md5_state_t state;
00420     md5_init(&state);
00421     md5_append(&state, (md5_byte_t const *)(password.c_str()), password.Len());
00422     md5_finish(&state, scrambled.content);
00423 }
00424 
00425 
00426 // scramble it again before transfering it over the network
00427 void nKrawall::ScrambleWithSalt2(const nScrambledPassword& source,
00428                                 const nSalt& salt,
00429                                 nScrambledPassword& dest)
00430 {
00431     md5_state_t state;
00432     md5_init(&state);
00433     md5_append(&state, source.content, 16);
00434     md5_append(&state, salt.content  , 16);
00435     md5_finish(&state, dest.content);
00436 }
00437 
00438 
00439 
00440 
00441 
00442 
00443 
00444 
00445 
00446 // get a random salt value
00447 void nKrawall::RandomSalt(nSalt& salt)
00448 {
00449     // oh dear. getting a random salt value with this method is EVIL...
00450     tRandomizer & randomizer = tRandomizer::GetInstance();
00451     for (int i=15; i>=0; i--)
00452         salt[i] = randomizer.Get( 256 );
00453     //        salt[i] = (int)(256.0 * rand() / (RAND_MAX + 1.0));
00454 }
00455 
00456 #ifdef KRAWALL_SERVER
00457 
00459 void nKrawall::SplitUserName( tString const & original, tString & username, tString & authority )
00460 {
00461     std::ostringstream filter; 
00462     
00463     for( int i = original.Len()-2; i >=0 ; --i )
00464     {
00465         if ( original[i] == '@' )
00466         {
00467                 username = original.SubStr( 0, i );
00468                 authority = original.SubStr( i+1, original.Len() - i -2 );
00469                 return;
00470         }
00471     }
00472     
00473     username = original;
00474     authority = "";
00475 }
00476 
00477 #ifdef KRAWALL_SERVER_LEAGUE
00478 
00479 // called on the master server when the league message is received
00480 void nKrawall::ReceiveLeagueMessage(const tString& message)
00481 {
00482     //  con << message;
00483 
00484     if (message[0] == 'K')
00485     {
00486         tString killer(message.c_str()+1);
00487         tString victim(message.c_str()+1+killer.Len());
00488 
00489         MasterFrag(killer, victim);
00490     }
00491     else if (message[0] == 'R')
00492     {
00493         tString numP(message.c_str()+1);
00494         int pos = 1 + numP.Len();
00495 
00496         int numPlayers = atoi(numP);
00497         tArray<tString> players(numPlayers);
00498         for (int i = numPlayers-1; i>=0; i--)
00499         {
00500             players(i) = tString(message.c_str()+pos);
00501             pos += players(i).Len();
00502         }
00503 
00504         MasterRoundEnd(&players[0], numPlayers);
00505     }
00506 }
00507 
00508 
00509 
00510 // called when victim drives against killer's wall
00511 void nKrawall::Frag(const tString &killer, const tString& victim, tString& message)
00512 {
00513     message << 'K';
00514     message << killer << '\0';
00515     message << victim << '\0';
00516 }
00517 
00518 // called at the end of a round; the last survivor is stored in
00519 // players[numPlayers-1], the first death in players[0]
00520 void nKrawall::RoundEnd(const tString* players, int numPlayers, tString& message)
00521 {
00522     message << 'R';
00523     message << numPlayers << '\0';
00524     for (int i = numPlayers-1; i>=0; i--)
00525         message << players[i] << '\0';
00526 }
00527 
00528 
00529 
00530 // called ON THE SERVER when victim drives against killer's wall
00531 void nKrawall::ServerFrag(const tString &killer, const tString& victim)
00532 {
00533     tString message;
00534     Frag(killer, victim, message);
00535     SendLeagueMessage(message);
00536 }
00537 
00538 // called ON THE SERVER at the end of a round; the last survivor is stored in
00539 // players[numPlayers-1], the first death in players[0]
00540 void nKrawall::ServerRoundEnd(const tString* players, int numPlayers)
00541 {
00542     tString message;
00543     RoundEnd(players, numPlayers, message);
00544     SendLeagueMessage(message);
00545 }
00546 
00547 
00548 // league messages are sent without network connection, so we need to add acks manually
00549 
00550 void ReceiveLeagueMessage(nMessage &m);
00551 void ReceiveLeagueMessageAck(nMessage &m);
00552 
00553 static nDescriptor nLeagueMessage(42, &ReceiveLeagueMessage, "password_request", true);
00554 
00555 static nDescriptor nLeagueMessageAck(43, &ReceiveLeagueMessageAck, "password_answer", true);
00556 
00557 
00558 // league security
00559 static void SignMessage(unsigned int id, const tString& message, nKrawall::nScrambledPassword& signature)
00560 {
00561     tString pw = message;
00562     pw << " " << id;
00563     nKrawall::nScrambledPassword temp;
00564     nKrawall::ScramblePassword(pw, temp);
00565     nKrawall::ScrambleWithSalt(temp, nKrawall::SecretLeagueKey(), signature);
00566 }
00567 
00568 
00569 static unsigned int S_NextID = 3;
00570 
00571 // league messages
00572 class nLM
00573 {
00574 public:
00575     tString        message;
00576     unsigned int id;
00577     REAL           sentTime;
00578 };
00579 
00580 
00581 // the league messages still waiting for an ack
00582 static tArray<nLM> S_WFA;
00583 
00584 
00585 // called on the servers to create a league message
00586 void nKrawall::SendLeagueMessage(const tString& message)
00587 {
00588     // z-man: disabled for now, we want no central league messages sent to my master
00589     return;
00590 
00591     int i;
00592     REAL time = tSysTimeFloat();
00593 
00594     // bend the network port to the master server
00595     nServerInfo *master = nServerInfo::GetMasters();
00596     if (!master)
00597         return;
00598 
00599     sn_Bend(master->GetConnectionName(), master->GetPort());
00600 
00601     // Resend old messages
00602     for (i=0; i < S_WFA.Len(); i++)
00603     {
00604         nLM& resend = S_WFA(i);
00605         if (resend.sentTime + 2 < time)
00606         {
00607             // resend the mesage
00608             nMessage *m = tNEW(nMessage) (nLeagueMessage);
00609             (*m) << resend.id;
00610             (*m) << resend.message;
00611             // sign the message
00612             nKrawall::nScrambledPassword signature;
00613             SignMessage(resend.id, resend.message, signature);
00614             nKrawall::WriteScrambledPassword(signature, *m);
00615 
00616             m->SendImmediately(0, false);
00617 
00618             // update send time
00619             resend.sentTime = time;
00620         }
00621     }
00622 
00623     if (!&message)
00624         return;
00625 
00626     // make a new ack-waiting entry and fill it
00627     nLM& send = S_WFA[S_WFA.Len()];
00628     send.id = S_NextID++;
00629     send.sentTime = time;
00630     send.message  = message;
00631 
00632 
00633 
00634     // sign the message
00635     nKrawall::nScrambledPassword signature;
00636     SignMessage(send.id, message, signature);
00637 
00638     // pack a message and go
00639     nMessage *m = tNEW(nMessage) (nLeagueMessage);
00640     (*m) << send.id;
00641     (*m) << message;
00642     nKrawall::WriteScrambledPassword(signature, *m);
00643 
00644     m->SendImmediately(0, false);
00645     nMessage::SendCollected(0);
00646 }
00647 
00648 
00649 
00650 
00651 
00652 
00653 
00654 
00655 
00656 
00657 
00658 
00659 
00660 
00661 #define STOREBACK 40
00662 
00663 // league messages
00664 class nLastLeagueMessage
00665 {
00666 public:
00667     tString        adr;
00668     unsigned int   port;
00669     unsigned int   ids[STOREBACK];
00670 };
00671 
00672 
00673 // the league messages we last got
00674 static tArray<nLastLeagueMessage> S_LLM;
00675 
00676 
00677 
00678 
00679 void ReceiveLeagueMessage(nMessage &m)
00680 {
00681     int i;
00682 
00683     // get the adress of the sender
00684     tString      senderAdr;
00685     sn_GetAdr(m.SenderID(), senderAdr);
00686     unsigned int senderPort = sn_GetPort(m.SenderID());
00687 
00688     unsigned int id;
00689     tString message;
00690 
00691     // read the message
00692     m >> id;
00693     m >> message;
00694 
00695     // do nothing if the message is from an unknown location
00696     if (!nKrawall::IsFromKrawall(senderAdr, senderPort))
00697     {
00698         con << "Rejecting league message " << id << " from " << senderAdr << ":" << senderPort << " : not from Krawall.\n";
00699         return;
00700     }
00701 
00702     // return an ack
00703     nMessage *ret = tNEW(nMessage)(nLeagueMessageAck);
00704     (*ret) << id;
00705     ret->SendImmediately(m.SenderID(), false);
00706     nMessage::SendCollected(m.SenderID());
00707 
00708     // check the signature
00709     nKrawall::nScrambledPassword realsignature, receivedsignature;
00710     SignMessage(id, message, realsignature);
00711     nKrawall::ReadScrambledPassword(m, receivedsignature);
00712     if (!nKrawall::ArePasswordsEqual(realsignature, receivedsignature))
00713     {
00714         con << "Rejecting league message " << id << " from " << senderAdr << ":" << senderPort << " : invalid signature.\n";
00715         return;
00716     }
00717 
00718     // find/create the nLastLeagueMessage entry
00719     nLastLeagueMessage* lastFromThisSender = NULL;
00720     for (i=S_LLM.Len()-1; i>=0 && !lastFromThisSender; i--)
00721         if (S_LLM(i).adr == senderAdr && S_LLM(i).port == senderPort)
00722             lastFromThisSender = &(S_LLM(i));
00723 
00724     // not found: create it
00725     if (!lastFromThisSender)
00726     {
00727         lastFromThisSender = &(S_LLM[S_LLM.Len()]);
00728         lastFromThisSender->adr  = senderAdr;
00729         lastFromThisSender->port = senderPort;
00730         for (i = STOREBACK-1; i>=0; i--)
00731             lastFromThisSender->ids[i] = id - i - 10000;
00732     }
00733 
00734 
00735 
00736     // check if the id is new
00737     for (i = STOREBACK-1; i>=0; i--)
00738         if (lastFromThisSender->ids[i] == id)
00739         {
00740             con << "Ignoring league message " << id << " from " << senderAdr << ":" << senderPort << " : already processed.\n";
00741             return;
00742         }
00743 
00744     // store the ID
00745     for (i = STOREBACK-2; i>=0; i--)
00746         lastFromThisSender->ids[i+1] = lastFromThisSender->ids[i];
00747     lastFromThisSender->ids[0] = id;
00748 
00749 #ifdef DEBUG
00750     con << "Receiving league message " << id << "\n";
00751 #endif
00752 
00753     // evaluate it
00754     nKrawall::ReceiveLeagueMessage(message);
00755 }
00756 
00757 
00758 
00759 
00760 
00761 
00762 
00763 void ReceiveLeagueMessageAck(nMessage &m)
00764 {
00765     // read the return
00766     unsigned int id;
00767     m >> id;
00768 
00769     for (int i=S_WFA.Len()-1; i>=0; i--)
00770     {
00771         nLM& resend = S_WFA(i);
00772         if (resend.id == id)
00773         {
00774             // delete it
00775             S_WFA(i) = S_WFA(S_WFA.Len()-1);
00776             S_WFA.SetLen(S_WFA.Len()-1);
00777 
00778             return;
00779         }
00780     }
00781 }
00782 
00783 #endif
00784 #endif
00785 
00786 

Generated on Sat Mar 15 22:55:49 2008 for Armagetron Advanced by  doxygen 1.5.4