Gamespy Emulator WIP

Network protocols, internals and low-level information about games
maraakate
Posts: 13
Joined: Sat May 09, 2015 7:18 pm

Gamespy Emulator WIP

Post by maraakate »

Hi Aluigi and fellow programmers,

I have been writing a gamespy enctype 0 emulator for the older games to teach myself some basic sockets programming and it works very well. Using Daikatana with the Gamespy SDK I am able to see all the handshakes and good stuff going on.

During this time, I have noticed that QTracker always sends the same \\basic\\secure\\TXKOAT key every time. I want to use your enctypex_decoder.c to cross-check the key during a list request or even a heartbeat. Experimenting I found out that gamespy will periodically send a heartbeat packet out that is just \\status\\ but you can do \\status\\secure\\<your key> and then the game will use the gs_encrypt and gs_encode functions to send the special key back.

Currently, I just ignore the key and allow the server (or query list request) to go through, and QTracker is doing the same. I would like to get it working properly for fun.

My question is, how do I use your enctypex_decoder.c to accomplish this?

Here is a snippet from how the SDK is handling parsing a \\secure\\

Code: Select all

   char data[256], *ptr, result[64];

   ptr = strstr ( data, SECURE ) + strlen(SECURE);
   gs_encrypt   ( (uchar *)serverlist->seckey, 6, (uchar *)ptr, 6 );
   gs_encode ( (uchar *)ptr, 6, (uchar *)result );

   //validate to the master
   sprintf(data, "\\gamename\\%s\\gamever\\%s\\location\\0\\validate\\%s\\final\\\\queryid\\1.1\\",
         serverlist->enginename, ENGINE_VERSION, result); //validate us      

In the above code, gamespy strips out the \\secure and any other data before it. The following 6 characters are the key sent out by the server (in this hardcoded case TXKOAT)

Here is the Gamespy SDK functions for enctype 0 (I added the printfs to see how it works)

Code: Select all

/*****************************************************************************/
/* Various encryption / encoding routines */

void swap_byte ( uchar *a, uchar *b )
{
   uchar swapByte;
   
   swapByte = *a;
   *a = *b;     
   *b = swapByte;
}

uchar encode_ct ( uchar c )
{
   if (c <  26) return ('A'+c);
   if (c <  52) return ('a'+c-26);
   if (c <  62) return ('0'+c-52);
   if (c == 62) return ('+');
   if (c == 63) return ('/');
   
   return 0;
}

void gs_encode ( uchar *ins, int size, uchar *result )
{
   int    i,pos;
   uchar  trip[3];
   uchar  kwart[4];
   
   Com_Printf("Ins[%i]: %s\n", size, ins);
   i=0;
   while (i < size)
   {
      for (pos=0 ; pos <= 2 ; pos++, i++)
         if (i < size) trip[pos] = *ins++;
         else trip[pos] = '\0';
         kwart[0] =   (trip[0])       >> 2;
         kwart[1] = (((trip[0]) &  3) << 4) + ((trip[1]) >> 4);
         kwart[2] = (((trip[1]) & 15) << 2) + ((trip[2]) >> 6);
         kwart[3] =   (trip[2]) & 63;
         for (pos=0; pos <= 3; pos++) *result++ = encode_ct(kwart[pos]);
   }
   *result='\0';
}

void gs_encrypt ( uchar *key, int key_len, uchar *buffer_ptr, int buffer_len )
{
   short counter;     
   uchar x, y, xorIndex;
   uchar state[256];       
   
   Com_Printf("Key[%i]: %s\n", key_len, key);
   Com_Printf("Ptr[%i]: %s\n", buffer_len, buffer_ptr);
   for ( counter = 0; counter < 256; counter++) state[counter] = (uchar) counter;
   
   x = 0; y = 0;
   for ( counter = 0; counter < 256; counter++)
   {
      y = (key[x] + state[counter] + y) & 255;
      x = (x + 1) % key_len;
      swap_byte ( &state[counter], &state[y] );
   }
   
   x = 0; y = 0;
   for ( counter = 0; counter < buffer_len; counter ++)
   {
      x = (x + buffer_ptr[counter] + 1)& 255;
      y = (state[x] + y) & 255;
      swap_byte ( &state[x], &state[y] );
      xorIndex = (state[x] + state[y])& 255;
      buffer_ptr[counter] ^= state[xorIndex];
   }
}


And here is what I am trying to do in the master server for list request

Code: Select all

         char *buffer = "\\basic\\\\secure\\TXKOAT"; // FS: This is the generic encode key QTracker is sending out.
         char *validateKey;
         char decodedKey[64];

         // FS: This doesn't work yet.
         validateKey = value_for_key(incomingTcpValidate, "validate");
//         printf("Validate Key: %s\n", validateKey);
         enctype = enctypex_wrapper((unsigned char *)"TXKOAT", (unsigned char*)validateKey, decodedKey, 6);
         printf("Decoded[%i]: %u\n", enctype, decodedKey);



value_for_key is another internal gamespy SDK function to just simply grab the validate key. In the case of daikatana,
Gamespy validate server key: \basic\\secure\TXKOAT
Key[6]: fl8aY7 <-- secret ket
Ptr[6]: TXKOAT <-- what I am sending
Ins[6]: ©ü<‡/Ã <-- what the function is turning it into before it goes to gs_encode
Result: 1243196 <-- the final value as %u.
qfw8hy/D <-- the result as a string sent out to the world
Gamespy validate to the master: \gamename\daikatana\gamever\0.5\location\0\validate\qfw8hy/D\final\\queryid\1.1\

Also, I am not skilled with any kind of encryption/decryption techniques. So, I'm hoping this is something as simple as not using the proper functions or using them correctly.

Thanks for everyones help in advance.
maraakate
Posts: 13
Joined: Sat May 09, 2015 7:18 pm

Re: Gamespy Emulator WIP

Post by maraakate »

A temporary solution is to use a lookup table for the gamename and it's secret key then cross checking your unique challenge key.

I would still rather take advantage of your functions to decrypt and decode it instead of a table.
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Gamespy Emulator WIP

Post by aluigi »

I'm a bit rusty on this topic.

I used my gsmsalg code to work on this stuff, I don't remember all the details but I guess it's just gsseckey(output, "TXKOAT", "fl8aY7", 0);
maraakate
Posts: 13
Joined: Sat May 09, 2015 7:18 pm

Re: Gamespy Emulator WIP

Post by maraakate »

That is what I am doing now by cross-referencing that to a hardcoded game table.
I also now use OpenSPY's gen_random function to make a new unique challenge key every time.

I suppose this is the easiest way to do it.

Here is how I am doing it presently with your gsmalg code, keep in mind I am using value_for_key for the validatePacket, strlcpy to cut off anything (with a null terminator) if it's longer than 64 (which I feel is OK for this purpose). It should never really be longer than 8 though afaik for enctype 0. And challenge packet is never going to be longer than \\basic\\secure\\<6 digit key+null terminator>:

Code: Select all

int Gamespy_Challenge_Cross_Check(char *challengePacket, char *validatePacket, int rawsecurekey)
{
   char *ptr = NULL;
   char validateKey[64];
   char gameKey[64];
   char *decodedKey = NULL;
   char *gameSecKey = NULL;
   char challengeKey[64];
   int len = 0;

   if(!validation_required) // FS: Just pass it if we want to.
   {
      Con_DPrintf("[I] Skipping validation checks.\n");
      return 1;
   }
   else if(validation_required == 1 && rawsecurekey) // FS: This is an "ack" sent from a heartbeat, dropserver, or addserver
   {
      Con_DPrintf("[I] Skipping server validation checks.\n");
      return 1;
   }
   else if(validation_required == 2 && !rawsecurekey) // FS: This is "list" requests sent from clients
   {
      Con_DPrintf("[I] Skipping client validation checks.\n");
      return 1;
   }

   if(rawsecurekey)
      ptr = challengePacket;
   else
      ptr = value_for_key(challengePacket, "secure");
   if(!ptr)
   {
      Con_DPrintf("[E] Validation failed.  \\secure\\ missing from packet!\n");
      return 0;
   }
   DG_strlcpy(challengeKey,ptr,sizeof(challengeKey));

   ptr = NULL;
   ptr = value_for_key(validatePacket, "gamename");
   if(!ptr)
   {
      Con_DPrintf("[E] Validation failed.  \\gamename\\ missing from packet!\n");
      return 0;
   }
   DG_strlcpy(gameKey,ptr,sizeof(gameKey));

   ptr = NULL;
   ptr = value_for_key(validatePacket, "validate");
   if(!ptr)
   {
      Con_DPrintf("[E] Validation failed.  \\validate\\ missing from packet!\n");
      return 0;
   }
   DG_strlcpy(validateKey,ptr,sizeof(validateKey));

   gameSecKey = Gamespy_Get_Game_SecKey (gameKey);
   if(!gameSecKey)
   {
      Con_DPrintf("[E] Validation failed.  Game not supported!\n");
      return 0;
   }

   decodedKey = (char *)gsseckey(NULL, (unsigned char*)challengeKey, (unsigned char*)gameSecKey, 0);
   if(decodedKey && decodedKey[0] != '\0' && !strcmp(decodedKey, validateKey))
   {
      Con_DPrintf("[I] Validation passed!\n");
      return 1;
   }

   Con_DPrintf("[E] Validation failed.  Incorrect key sent!\n");
   return 0;
}

typedef struct
{
   char *gamename;
   char *seckey;
} game_table_t;

game_table_t gameTable[] =
{
   {"blood2", "jUOF0p"},
   {"daikatana", "fl8aY7"},
   {"gspylite", "mgNUaC"},
   {"kingpin", "QFWxY2"},
   {"nolf", "Jn3Ab4"},
   {"quake1", "7W7yZz"},
   {"quake2", "rtW0xg"},
   {"quakeworld", "FU6Vqn"},
   {"turok2", "RWd3BG"},
   {NULL, NULL}
};

char *Gamespy_Get_Game_SecKey (char *gamename)
{
   int x = 0;

   if (!gamename || gamename[0] == 0)
      return NULL;

   while (gameTable[x].gamename != NULL)
   {
      if(!strcmp(gamename, gameTable[x].gamename))
      {
         return gameTable[x].seckey;
      }
      x++;
   }
   return NULL;
}
maraakate
Posts: 13
Joined: Sat May 09, 2015 7:18 pm

Re: Gamespy Emulator WIP

Post by maraakate »

Also, did any of your utilities keep track of which games were encode type 0? I'm not interested in writing an emulator for newer games that had cd-key verification and all that other crap. Mostly early lithtech and quake/quake 2 engine games.

I was slightly interested in allowing it to work with gamespy3d, but I believe it's not enough to just send the ip/port as the int/short without a \\final\\ header. I don't know if the list packet itself sent to gs3d is encrypted as well somehow. Oh well, doesn't really matter.
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Gamespy Emulator WIP

Post by aluigi »

I would really like to help you but my memory is not good enough.
Gamespy maintained some list of information online, those that I used to build my gslist.cfg file, but now they are no longer available and I don't remember if there was something about the minimum enctype supported by the game.

One of the games that I remember using enctype 0 was Wheel of Time, it was a game based on the Unreal 1 engine (<= UT99) so probably most of the games based on the same engine use enctype 0.

Regarding gs3d my suggestion is... who cares :)
I don't think many people used it and it's just a waste of time.
Anyway my code supports enctype 1 too, the one used by gs3d.
maraakate
Posts: 13
Joined: Sat May 09, 2015 7:18 pm

Re: Gamespy Emulator WIP

Post by maraakate »

I figure pretty much any game using Q1, Q2, Unreal 1, Early lithtech (like NOLF, maybe Tron 2.0, Shogo, etc). are probably all encode type 0. Basically any game before 2001 or so.

Your code supports enctype 1, but i believe only in the enctypex file which I couldn't figure out how to use properly so I gave up on this. Supporting GS3D is rather silly, but I thought it was a fun little program to use back in the 90s for getting Quake servers.