[PATCH] cl_guid

Tony J. White tjw at webteam.net
Mon Apr 10 17:07:00 EDT 2006


What is cl_guid?

It is a CVAR_USERINFO cvar that is used by Even Balance's Punkbuster software
in quake3 and deriviatives.  It's a unique identifier that may or may not
be tied to a CD-Key.  In games without a CD Key like Wolf:ET, it is
randomly generated and can be reset at any time by removing the file
full of binary garbage at etmain/etkey.

What use is it?

I use it in my Wolf:ET mod (etpub) to grant authentication for administrators,
store a player's gamestate information while that player is not connected, and
to store player statistics.  There is also an option to append the cl_guid to
game log records since some log analyzers for Wolf:ET also use it for
more accurate player statistics (as opposed to netname).  I'm in the
process of adapting the administration system to work with Tremulous and
it will obviously not work without a cl_guid.

Why add it to ioq3?

As the name applies, cl_guid belongs in the client engine, not cgame.  It
is meant to be consistent no matter which client mod is loaded.  There is
also the fact that cl_guid was in Id's quake3 binaries, ioq3 should be more
compatabile with any mods that made use of it. 

How does it work?

On startup, the client engine looks for a file called qkey.  If it does
not exist, 2KiB worth of random binary data is inserted into the qkey file.
A MD5 digest is then made of the qkey file and it is inserted into the
cl_guid cvar.  This is essentially the same way it works on ET except that
pb uses some other secret voodoo to hide how it comes up with the MD5 digest. 

How about authentication and duplicates?

Punkbuster authenticates each guid and keeps track of when they are created
and squashes generation of duplicates.  It is obviously out of the scope of
ioq3 to do this so duplicates are possible, but very unlikely. 

-Tony
-------------- next part --------------
Index: code/qcommon/md5.c
===================================================================
--- code/qcommon/md5.c	(revision 0)
+++ code/qcommon/md5.c	(revision 0)
@@ -0,0 +1,299 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include "q_shared.h"
+#include "qcommon.h"
+
+typedef struct MD5Context {
+	unsigned long int buf[4];
+	unsigned long int bits[2];
+	unsigned char in[64];
+} MD5_CTX;
+
+#ifndef Q3_BIG_ENDIAN
+	#define byteReverse(buf, len)	/* Nothing */
+#else
+	static void byteReverse(unsigned char *buf, unsigned longs);
+
+	/*
+	 * Note: this code is harmless on little-endian machines.
+	 */
+	static void byteReverse(unsigned char *buf, unsigned longs)
+	{
+	    unsigned long int t;
+	    do {
+		t = (unsigned long int)
+			((unsigned) buf[3] << 8 | buf[2]) << 16 |
+			((unsigned) buf[1] << 8 | buf[0]);
+		*(unsigned long int *) buf = t;
+		buf += 4;
+	    } while (--longs);
+	}
+#endif // Q3_BIG_ENDIAN
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+static void MD5Init(struct MD5Context *ctx)
+{
+    ctx->buf[0] = 0x67452301;
+    ctx->buf[1] = 0xefcdab89;
+    ctx->buf[2] = 0x98badcfe;
+    ctx->buf[3] = 0x10325476;
+
+    ctx->bits[0] = 0;
+    ctx->bits[1] = 0;
+}
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(unsigned long int buf[4],
+	unsigned long int const in[16])
+{
+    register unsigned long int a, b, c, d;
+
+    a = buf[0];
+    b = buf[1];
+    c = buf[2];
+    d = buf[3];
+
+    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+    buf[0] += a;
+    buf[1] += b;
+    buf[2] += c;
+    buf[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+static void MD5Update(struct MD5Context *ctx, unsigned char const *buf,
+	unsigned len)
+{
+    register unsigned long int t;
+
+    /* Update bitcount */
+
+    t = ctx->bits[0];
+    if ((ctx->bits[0] = t + ((unsigned long int) len << 3)) < t)
+	ctx->bits[1]++;		/* Carry from low to high */
+    ctx->bits[1] += len >> 29;
+
+    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+
+    /* Handle any leading odd-sized chunks */
+
+    if (t) {
+	unsigned char *p = (unsigned char *) ctx->in + t;
+
+	t = 64 - t;
+	if (len < t) {
+	    memcpy(p, buf, len);
+	    return;
+	}
+	memcpy(p, buf, t);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (unsigned long int *) ctx->in);
+	buf += t;
+	len -= t;
+    }
+    /* Process data in 64-byte chunks */
+
+    while (len >= 64) {
+	memcpy(ctx->in, buf, 64);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (unsigned long int *) ctx->in);
+	buf += 64;
+	len -= 64;
+    }
+
+    /* Handle any remaining bytes of data. */
+
+    memcpy(ctx->in, buf, len);
+}
+
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern 
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+static void MD5Final(struct MD5Context *ctx, unsigned char *digest)
+{
+    unsigned long int count;
+    unsigned char *p;
+
+    /* Compute number of bytes mod 64 */
+    count = (ctx->bits[0] >> 3) & 0x3F;
+
+    /* Set the first char of padding to 0x80.  This is safe since there is
+       always at least one byte free */
+    p = ctx->in + count;
+    *p++ = 0x80;
+
+    /* Bytes of padding needed to make 64 bytes */
+    count = 64 - 1 - count;
+
+    /* Pad out to 56 mod 64 */
+    if (count < 8) {
+	/* Two lots of padding:  Pad the first block to 64 bytes */
+	memset(p, 0, count);
+	byteReverse(ctx->in, 16);
+	MD5Transform(ctx->buf, (unsigned long int *) ctx->in);
+
+	/* Now fill the next block with 56 bytes */
+	memset(ctx->in, 0, 56);
+    } else {
+	/* Pad block to 56 bytes */
+	memset(p, 0, count - 8);
+    }
+    byteReverse(ctx->in, 14);
+
+    /* Append length in bits and transform */
+    ((unsigned long int *) ctx->in)[14] = ctx->bits[0];
+    ((unsigned long int *) ctx->in)[15] = ctx->bits[1];
+
+    MD5Transform(ctx->buf, (unsigned long int *) ctx->in);
+    byteReverse((unsigned char *) ctx->buf, 4);
+    
+    if (digest!=NULL)
+	    memcpy(digest, ctx->buf, 16);
+    //memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */
+}
+
+
+char *Com_MD5File(const char *fn, int length)
+{
+	static char final[33] = {"unknown"};
+	unsigned char digest[16] = {""}; 
+	fileHandle_t f;
+	MD5_CTX md5;
+	char buffer[2048];
+	int i;
+	int filelen = 0;
+	int r = 0;
+	int total = 0;
+
+	filelen = FS_FOpenFileRead(fn, &f, qtrue);
+	if(filelen < 1) {
+		return final;
+	}
+	if(filelen < length || !length) {
+		length = filelen;
+	}
+
+	MD5Init(&md5);
+	for(;;) {
+		r = FS_Read2(buffer, sizeof(buffer), f);
+		if(r < 1)
+			break;
+		if(r + total > length)
+			r = length - total;
+		total += r;
+		MD5Update(&md5 , (unsigned char *)buffer, r);
+		if(r < sizeof(buffer) || total >= length)
+			break;
+	}
+	FS_FCloseFile(f);
+	MD5Final(&md5, digest);
+	final[0] = '\0';
+	for(i = 0; i < 16; i++) {
+		Q_strcat(final, sizeof(final), va("%02X", digest[i]));
+	}
+	return final;
+}
Index: code/qcommon/qcommon.h
===================================================================
--- code/qcommon/qcommon.h	(revision 697)
+++ code/qcommon/qcommon.h	(working copy)
@@ -729,6 +729,7 @@
 int			Com_EventLoop( void );
 int			Com_Milliseconds( void );	// will be journaled properly
 unsigned	Com_BlockChecksum( const void *buffer, int length );
+char		*Com_MD5File(const char *filename, int length);
 int			Com_HashKey(char *string, int maxlen);
 int			Com_Filter(char *filter, char *name, int casesensitive);
 int			Com_FilterPath(char *filter, char *name, int casesensitive);
Index: code/client/cl_main.c
===================================================================
--- code/client/cl_main.c	(revision 697)
+++ code/client/cl_main.c	(working copy)
@@ -2378,6 +2378,28 @@
   CL_CloseAVI( );
 }
 
+static void CL_GenerateQKey(void)
+{
+	int len = 0;
+	unsigned char buff[2048];
+
+	len = FS_ReadFile(QKEY_FILE, NULL);
+	if(len >= (int)sizeof(buff)) {
+		Com_Printf("QKEY found.\n");
+		return;
+	}
+	else {
+		int i;
+		srand(time(0));
+		for(i = 0; i < sizeof(buff) - 1; i++) {
+			buff[i] = (unsigned char)(rand() % 255);
+		}
+		buff[i] = 0;
+		Com_Printf("QKEY generated\n");
+		FS_WriteFile(QKEY_FILE, buff, sizeof(buff));
+	}
+} 
+
 /*
 ====================
 CL_Init
@@ -2526,6 +2548,9 @@
 
 	Cvar_Set( "cl_running", "1" );
 
+	CL_GenerateQKey();	
+	Cvar_Get("cl_guid", Com_MD5File(QKEY_FILE, 0), CVAR_USERINFO | CVAR_ROM);
+
 	Com_Printf( "----- Client Initialization Complete -----\n" );
 }
 
Index: code/client/client.h
===================================================================
--- code/client/client.h	(revision 697)
+++ code/client/client.h	(working copy)
@@ -30,6 +30,9 @@
 #include "../cgame/cg_public.h"
 #include "../game/bg_public.h"
 
+// tjw: file full of random crap that gets used to create cl_guid
+#define QKEY_FILE "qkey"
+
 #define	RETRANSMIT_TIMEOUT	3000	// time between connection packet retransmits
 
 
Index: Makefile
===================================================================
--- Makefile	(revision 697)
+++ Makefile	(working copy)
@@ -779,6 +779,7 @@
   $(B)/client/cvar.o \
   $(B)/client/files.o \
   $(B)/client/md4.o \
+  $(B)/client/md5.o \
   $(B)/client/msg.o \
   $(B)/client/net_chan.o \
   $(B)/client/huffman.o \
@@ -1029,6 +1030,7 @@
 $(B)/client/cvar.o : $(CMDIR)/cvar.c; $(DO_CC)
 $(B)/client/files.o : $(CMDIR)/files.c; $(DO_CC)
 $(B)/client/md4.o : $(CMDIR)/md4.c; $(DO_CC)
+$(B)/client/md5.o : $(CMDIR)/md5.c; $(DO_CC)
 $(B)/client/msg.o : $(CMDIR)/msg.c; $(DO_CC)
 $(B)/client/net_chan.o : $(CMDIR)/net_chan.c; $(DO_CC)
 $(B)/client/huffman.o : $(CMDIR)/huffman.c; $(DO_CC)


More information about the quake3 mailing list