core/GameDataFetcher.cpp
author David Anderson <dvander@alliedmods.net>
Sun Dec 28 03:23:45 2008 -0500 (2008-12-28)
changeset 2479 44e8e76d4e53
parent 2400 76b7b4575e21
parent 2476 c21fd6e89ff5
child 2488 f7f1bbcc85e4
permissions -rw-r--r--
Merge from sourcemod-1.1 branch.
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "GameDataFetcher.h"
    33 #include "bitbuf.h"
    34 
    35 #ifdef PLATFORM_WINDOWS
    36 #include <winsock2.h>
    37 #else
    38 #include <unistd.h>
    39 #include <sys/types.h>
    40 #include <sys/socket.h>
    41 #include <netinet/in.h>
    42 #include <netdb.h>
    43 #include <arpa/inet.h>
    44 
    45 #define INVALID_SOCKET		-1
    46 #define closesocket 		close
    47 #define WSAGetLastError()	errno
    48 #endif
    49 
    50 #include "sh_vector.h"
    51 #include "sh_string.h"
    52 #include "sm_version.h"
    53 
    54 #if SOURCE_ENGINE == SE_LEFT4DEAD
    55 #include "convar_sm_l4d.h"
    56 #elif SOURCE_ENGINE == SE_ORANGEBOX
    57 #include "convar_sm_ob.h"
    58 #else
    59 #include "convar_sm.h"
    60 #endif
    61 
    62 #include "sourcemm_api.h"
    63 #include "time.h"
    64 #include "TimerSys.h"
    65 #include "compat_wrappers.h"
    66 #include "sm_stringutil.h"
    67 #include "md5.h"
    68 #include "frame_hooks.h"
    69 
    70 #define QUERY_MAX_LENGTH 1024
    71 
    72 static BuildMD5ableBuffer g_MD5Builder;
    73 static FetcherThread g_FetchThread;
    74 
    75 static FILE *logfile = NULL;
    76 
    77 bool g_disableGameDataUpdate = false;
    78 
    79 /**
    80  * Note on this.  If we issue a reload and changelevel, my srcds.exe will emit 
    81  *   Assertion Failed: !m_bServiceStarted
    82  * on quit.  This seems like a non-issue, because before we just terminated the 
    83  * server anyway.  If anyone notices and files a bug, we can look into it further.
    84  */
    85 bool g_restartAfterUpdate = false;
    86 
    87 static bool was_level_started = false;
    88 static int g_serverPort = 6500;
    89 static char g_serverAddress[100] = "smupdate.alliedmods.net";
    90 
    91 static void _ForceRestart(void *data)
    92 {
    93 	char cmd[300];
    94 	g_Logger.LogMessage("Automatically restarting SourceMod after a successful gamedata update.");
    95 	UTIL_Format(cmd, sizeof(cmd), "meta unload %d\n", g_PLID);
    96 	engine->ServerCommand(cmd);
    97 	UTIL_Format(cmd, sizeof(cmd), "changelevel \"%s\"\n", STRING(gpGlobals->mapname));
    98 	engine->ServerCommand(cmd);
    99 	UTIL_Format(cmd, sizeof(cmd), "echo SourceMod restarted after gamedata update.\n");
   100 	engine->ServerCommand(cmd);
   101 }
   102 
   103 static void ForceRestart()
   104 {
   105 	FrameAction action;
   106 
   107 	action.action = _ForceRestart;
   108 	action.data = NULL;
   109 	AddFrameAction(action);
   110 }
   111 
   112 void FetcherThread::RunThread(IThreadHandle *pHandle)
   113 {
   114 	char lock_path[PLATFORM_MAX_PATH];
   115 	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp");
   116 	g_LibSys.CreateFolder(lock_path);
   117 
   118 	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
   119 
   120 	char log_path[PLATFORM_MAX_PATH];
   121 	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata");
   122 
   123 	g_LibSys.CreateFolder(log_path);
   124 
   125 	time_t t;
   126 	GetAdjustedTime(&t);
   127 	tm *curtime = localtime(&t);
   128 
   129 	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata/L%02d%02d.log", curtime->tm_mon + 1, curtime->tm_mday);
   130 
   131 	logfile = fopen(log_path, "a");
   132 
   133 	if (!logfile)
   134 	{
   135 		/* :( */
   136 		return;
   137 	}
   138 
   139 	//Create a blank lock file
   140 	FILE *fp = fopen(lock_path, "w");
   141 	if (fp)
   142 	{
   143 		fclose(fp);
   144 	}
   145 
   146 	char query[QUERY_MAX_LENGTH];
   147 
   148 	/* Check for updated gamedata files */
   149 	int len = BuildGameDataQuery(query, QUERY_MAX_LENGTH);
   150 
   151 	if (len == 0)
   152 	{
   153 		g_Logger.LogToFileOnly(logfile, "Could not build gamedata query!");
   154 		fclose(logfile);
   155 		unlink(lock_path);
   156 		return;
   157 	}
   158 
   159 	/* We check this late so we have the MD5 sums available.  This may change in the future. */
   160 	if (g_disableGameDataUpdate)
   161 	{
   162 		g_Logger.LogToFileOnly(logfile, "Skipping gamedata fetcher (DisableAutoUpdate set)");
   163 		fclose(logfile);
   164 		unlink(lock_path);
   165 		return;
   166 	}
   167 
   168 	/* Create a new socket for this connection */
   169 	int socketDescriptor = ConnectSocket();
   170 
   171 	if (socketDescriptor == INVALID_SOCKET)
   172 	{
   173 		fclose(logfile);
   174 		unlink(lock_path);
   175 		return;
   176 	}
   177 
   178 	int sent = SendData(socketDescriptor, query, len);
   179 
   180 IF_DEBUG_SPEW
   181 	g_Logger.LogToFileOnly(logfile, "Sent gamedata query");
   182 ENDIF_DEBUG_SPEW
   183 
   184 	if (sent == 0)
   185 	{
   186 IF_DEBUG_SPEW
   187 		g_Logger.LogToFileOnly(logfile, "Failed to send gamedata query data to remote host");
   188 ENDIF_DEBUG_SPEW
   189 
   190 		closesocket(socketDescriptor);
   191 		fclose(logfile);
   192 		unlink(lock_path);
   193 		return;
   194 	}
   195 
   196 	ProcessGameDataQuery(socketDescriptor);
   197 
   198 	/* And we're done! */
   199 	closesocket(socketDescriptor);
   200 	fclose(logfile);
   201 	unlink(lock_path);
   202 }
   203 
   204 void FetcherThread::OnTerminate(IThreadHandle *pHandle, bool cancel)
   205 {
   206 	g_blockGameDataLoad = false;
   207 
   208 	if (cancel)
   209 	{
   210 		return;
   211 	}
   212 
   213 	if (wasSuccess)
   214 	{
   215 		HandleUpdateStatus(updateStatus, build);
   216 
   217 		if (needsRestart)
   218 		{
   219 			if (g_restartAfterUpdate)
   220 			{
   221 				if (was_level_started)
   222 				{
   223 					ForceRestart();
   224 				}
   225 			}
   226 			else
   227 			{
   228 				g_Logger.LogMessage("Your gamedata files have been updated, please restart your server.");
   229 			}
   230 		}
   231 	}
   232 	else if (!g_disableGameDataUpdate)
   233 	{
   234 		g_Logger.LogError("An error occurred in the gamedata fetcher, see your gamedata log files for more information.");
   235 	}
   236 }
   237 
   238 int FetcherThread::BuildGameDataQuery(char *buffer, int maxlen)
   239 {
   240 	char gamedata_path[PLATFORM_MAX_PATH];
   241 	g_SourceMod.BuildPath(Path_SM, gamedata_path, sizeof(gamedata_path), "gamedata");
   242 
   243 	IDirectory *dir = g_LibSys.OpenDirectory(gamedata_path);
   244 
   245 	if (dir == NULL)
   246 	{
   247 		return 0;
   248 	}
   249 
   250 	bf_write Writer = bf_write("GameDataQuery", buffer, maxlen);
   251 
   252 	Writer.WriteByte('A'); //Generic Header char
   253 	Writer.WriteByte('G'); //G for gamedata query, or green, like my hat.
   254 
   255 	short build[4] = { SVN_FILE_VERSION };
   256 
   257 	Writer.WriteBytes(&build[0], 8);
   258 
   259 	Writer.WriteByte(0); // Initialize the file counter - Index 10
   260 
   261 	while (dir->MoreFiles())
   262 	{
   263 		if (dir->IsEntryFile())
   264 		{
   265 			const char *name = dir->GetEntryName();
   266 			size_t len = strlen(name);
   267 			if (len >= 4 && strcmp(&name[len-4], ".txt") == 0)
   268 			{
   269 				MD5 md5;
   270 				SMCError err;
   271 				SMCStates states;
   272 				unsigned char raw[16];
   273 				char file[PLATFORM_MAX_PATH];
   274 
   275 				g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
   276 
   277 				g_MD5Builder.checksum = &md5;
   278 				if ((err = g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states)) == SMCError_Okay)
   279 				{
   280 					md5.raw_digest(raw);
   281 					(uint8_t)buffer[10]++; //Increment the file counter
   282 					Writer.WriteBytes(raw, 16);
   283 
   284 					FileData *data = new FileData();
   285 					data->filename = new SourceHook::String(file);
   286 					md5.hex_digest(data->checksum);
   287 					filenames.push_back(data);
   288 
   289 IF_DEBUG_SPEW
   290 					g_Logger.LogToFileOnly(logfile, "Parsed file: %s as %s", file, data->checksum);
   291 ENDIF_DEBUG_SPEW
   292 
   293 				}
   294 				else
   295 				{
   296 IF_DEBUG_SPEW
   297 					const char *error = g_TextParser.GetSMCErrorString(err);
   298 					g_Logger.LogToFileOnly(logfile, "Parsing of file %s failed: %s", file, error);
   299 ENDIF_DEBUG_SPEW
   300 				}
   301 			}
   302 		}
   303 		dir->NextEntry();
   304 	}
   305 
   306 	return Writer.GetNumBytesWritten();
   307 }
   308 
   309 int FetcherThread::ConnectSocket()
   310 {
   311 #if defined PLATFORM_WINDOWS
   312 	WSADATA wsaData;
   313 	WSAStartup(0x0101, &wsaData);
   314 #endif
   315 
   316 	struct protoent *ptrp;
   317 	
   318 	if ((ptrp = getprotobyname("tcp")) == NULL) 
   319 	{
   320 		g_Logger.LogToFileOnly(logfile, "Error: Failed to find TCP protocol");
   321 		return INVALID_SOCKET;
   322 	}
   323 
   324 	int socketDescriptor = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
   325 
   326 	if (socketDescriptor == INVALID_SOCKET)
   327 	{
   328 		char error[255];
   329 		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   330 		g_Logger.LogToFileOnly(logfile, "Error: Failed to create socket: %s", error);
   331 		closesocket(socketDescriptor);
   332 		return INVALID_SOCKET;
   333 	}
   334 
   335 	struct hostent *he;
   336 	struct sockaddr_in local_addr;
   337 
   338 	local_addr.sin_family = AF_INET;
   339 	local_addr.sin_port = htons((u_short)g_serverPort);
   340 
   341 	he = gethostbyname(g_serverAddress);
   342 
   343 	if (!he)
   344 	{
   345 		if ((local_addr.sin_addr.s_addr = inet_addr(g_serverAddress)) == INADDR_NONE)
   346 		{
   347 			g_Logger.LogToFileOnly(logfile, "Couldn't locate address: %s", g_serverAddress);
   348 			closesocket(socketDescriptor);
   349 			return INVALID_SOCKET;
   350 		}
   351 	} 
   352 	else 
   353 	{
   354 		memcpy(&local_addr.sin_addr, (struct in_addr *)he->h_addr, he->h_length);
   355 	}
   356 
   357 	if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
   358 	{
   359 		char error[255];
   360 		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   361 		g_Logger.LogToFileOnly(logfile, "Couldn't connect to %s: %s", g_serverAddress, error);
   362 		closesocket(socketDescriptor);
   363 		return INVALID_SOCKET;
   364 	}
   365 
   366 	return socketDescriptor;
   367 }
   368 
   369 void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
   370 {
   371 	char buffer[50];
   372 
   373 IF_DEBUG_SPEW
   374 	g_Logger.LogToFileOnly(logfile, "Waiting for reply!");
   375 ENDIF_DEBUG_SPEW
   376 
   377 	//Read in the header bytes
   378 	int returnLen = RecvData(socketDescriptor, buffer, 12);
   379 
   380 
   381 	if (returnLen == 0)
   382 	{
   383 		char error[255];
   384 		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   385 		g_Logger.LogToFileOnly(logfile, "Did not receive reply: %s", error);
   386 		return;
   387 	}
   388 
   389 IF_DEBUG_SPEW
   390 	g_Logger.LogToFileOnly(logfile, "Received Header!");
   391 ENDIF_DEBUG_SPEW
   392 
   393 	bf_read Reader = bf_read("GameDataQuery", buffer, 12);
   394 
   395 	if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
   396 	{
   397 IF_DEBUG_SPEW
   398 		g_Logger.LogToFileOnly(logfile, "Unknown Query Response");
   399 ENDIF_DEBUG_SPEW
   400 		return;
   401 	}
   402 
   403 	updateStatus = (UpdateStatus)Reader.ReadByte();
   404 
   405 	build[0] = Reader.ReadShort();
   406 	build[1] = Reader.ReadShort();
   407 	build[2] = Reader.ReadShort();
   408 	build[3] = Reader.ReadShort();
   409 
   410 IF_DEBUG_SPEW
   411 	g_Logger.LogToFileOnly(logfile,
   412 						   "Update Status: %i - Latest %i.%i.%i.%i",
   413 						   updateStatus,
   414 						   build[0],
   415 						   build[1],
   416 						   build[2],
   417 						   build[3]);
   418 ENDIF_DEBUG_SPEW
   419 
   420 	int changedFiles = Reader.ReadByte();
   421 
   422 IF_DEBUG_SPEW
   423 	g_Logger.LogToFileOnly(logfile, "Files to download: %i", changedFiles);
   424 ENDIF_DEBUG_SPEW
   425 
   426 	for (int i=0; i<changedFiles; i++)
   427 	{
   428 		//Read in the file index and byte count
   429 		returnLen = RecvData(socketDescriptor, buffer, 5);
   430 
   431 		if (returnLen == 0)
   432 		{
   433 			/* Timeout or fail? */
   434 			return;
   435 		}
   436 
   437 		Reader.StartReading(buffer, 5);
   438 
   439 		int index = Reader.ReadByte();
   440 		int tempLen = Reader.ReadUBitLong(32);
   441 
   442 IF_DEBUG_SPEW
   443 		g_Logger.LogToFileOnly(logfile, "File index %i and length %i", index, tempLen);
   444 ENDIF_DEBUG_SPEW
   445 
   446 		void *memPtr;
   447 		memtable->CreateMem(tempLen+1, &memPtr);
   448 
   449 		//Read the contents of our file into the memtable
   450 		returnLen = RecvData(socketDescriptor, (char *)memPtr, tempLen);
   451 
   452 IF_DEBUG_SPEW
   453 		g_Logger.LogToFileOnly(logfile, "Received %i bytes", returnLen);
   454 ENDIF_DEBUG_SPEW
   455 
   456 		if (returnLen == 0)
   457 		{
   458 			/* Timeout or fail? */
   459 			return;
   460 		}
   461 
   462 		((unsigned char *)memPtr)[tempLen] = '\0';
   463 
   464 		FileData *data = filenames.at(index);
   465 		const char *filename;
   466 		if (data != NULL)
   467 		{
   468 			filename = data->filename->c_str();
   469 
   470 			FILE *fp = fopen(filename, "w");
   471 
   472 			if (fp)
   473 			{
   474 				fprintf(fp, "%s", (const char *)memPtr);
   475 				fclose(fp);
   476 			}
   477 			else
   478 			{
   479 				g_Logger.LogToFileOnly(logfile, "Failed to open file \"%s\" for writing", filename);
   480 			}
   481 		}
   482 		else
   483 		{
   484 			filename = "";
   485 		}
   486 
   487 		memtable->Reset();
   488 
   489 		g_Logger.LogToFileOnly(logfile, "Updated file: %s", filename);
   490 	}
   491 
   492 IF_DEBUG_SPEW
   493 	g_Logger.LogToFileOnly(logfile, "File Downloads Completed!");
   494 ENDIF_DEBUG_SPEW
   495 
   496 	needsRestart = false;
   497 
   498 	if (changedFiles > 0)
   499 	{
   500 		needsRestart = true;
   501 	}
   502 
   503 	//Read changed file count
   504 	returnLen = RecvData(socketDescriptor, buffer, 1);
   505 
   506 	if (returnLen == 0)
   507 	{
   508 		char error[255];
   509 		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   510 		g_Logger.LogToFileOnly(logfile, "Did not receive count reply: %s", error);
   511 		return;
   512 	}
   513 
   514 	Reader.StartReading(buffer, 1);
   515 
   516 	changedFiles = Reader.ReadByte();
   517 
   518 	if (changedFiles < 1)
   519 	{
   520 IF_DEBUG_SPEW
   521 		g_Logger.LogToFileOnly(logfile, "No unknown files. We're all done");
   522 ENDIF_DEBUG_SPEW
   523 		return;
   524 	}
   525 
   526 	char *changedFileIndexes = new char[changedFiles];
   527 
   528 IF_DEBUG_SPEW
   529 	g_Logger.LogToFileOnly(logfile, "%i files were unknown", changedFiles);
   530 ENDIF_DEBUG_SPEW
   531 
   532 	returnLen = RecvData(socketDescriptor, changedFileIndexes, changedFiles);
   533 
   534 	if (returnLen == 0)
   535 	{
   536 		char error[255];
   537 		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   538 		g_Logger.LogToFileOnly(logfile, "Did not receive list reply: %s", error);
   539 		return;
   540 	}
   541 
   542 	Reader.StartReading(changedFileIndexes, changedFiles);
   543 
   544 	for (int i=0; i<changedFiles; i++)
   545 	{
   546 		int index = Reader.ReadByte();
   547 		char fileName[30];
   548 
   549 		FileData *data = filenames.at(index);
   550 		const char* pathname;
   551 		if (data != NULL)
   552 		{
   553 			pathname = data->filename->c_str();
   554 		}
   555 		else
   556 		{
   557 			pathname = "";
   558 		}
   559 
   560 		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
   561 IF_DEBUG_SPEW
   562 		g_Logger.LogToFileOnly(logfile, "Unknown File %i : %s", index, fileName);
   563 ENDIF_DEBUG_SPEW
   564 	}
   565 
   566 	delete [] changedFileIndexes;
   567 
   568 	wasSuccess = true;
   569 }
   570 
   571 int FetcherThread::RecvData(int socketDescriptor, char *buffer, int len)
   572 {
   573 	fd_set fds;
   574 	struct timeval tv;
   575 
   576 	/* Create a 10 Second Timeout */
   577 	tv.tv_sec = 10;
   578 	tv.tv_usec = 0;
   579 
   580 	int bytesReceivedTotal = 0;
   581 
   582 	while (bytesReceivedTotal < len)
   583 	{
   584 		/* Add our socket to a socket set */
   585 		FD_ZERO(&fds);
   586 		FD_SET(socketDescriptor, &fds);
   587 
   588 		/* Wait max of 10 seconds for recv to become available */
   589 		select(socketDescriptor+1, &fds, NULL, NULL, &tv);
   590 
   591 		int bytesReceived = 0;
   592 
   593 		/* Is there a limit on how much we can receive? Some site said 1024 bytes, which will be well short of a file */
   594 		if (FD_ISSET(socketDescriptor, &fds))
   595 		{
   596 			bytesReceived = recv(socketDescriptor, buffer+bytesReceivedTotal, len-bytesReceivedTotal, 0);
   597 		}
   598 
   599 		if (bytesReceived == 0 || bytesReceived == -1)
   600 		{
   601 			return 0;
   602 		}
   603 
   604 		bytesReceivedTotal += bytesReceived;
   605 	}
   606 
   607 	return bytesReceivedTotal;	
   608 }
   609 
   610 int FetcherThread::SendData(int socketDescriptor, char *buffer, int len)
   611 {
   612 	fd_set fds;
   613 	struct timeval tv;
   614 
   615 	tv.tv_sec = 10;
   616 	tv.tv_usec = 0;
   617 
   618 	int sentBytesTotal = 0;
   619 
   620 	while (sentBytesTotal < len)
   621 	{
   622 		FD_ZERO(&fds);
   623 		FD_SET(socketDescriptor, &fds);
   624 
   625 		select(socketDescriptor+1, NULL, &fds, NULL, &tv);
   626 
   627 		int sentBytes = 0;
   628 
   629 		if (FD_ISSET(socketDescriptor, &fds))
   630 		{
   631 			sentBytes = send(socketDescriptor, buffer+sentBytesTotal, len-sentBytesTotal, 0);
   632 		}
   633 
   634 		if (sentBytes == 0 || sentBytes == -1)
   635 		{
   636 			return 0;
   637 		}		
   638 
   639 		sentBytesTotal += sentBytes;
   640 	}
   641 
   642 	return sentBytesTotal;
   643 }
   644 
   645 void FetcherThread::HandleUpdateStatus(UpdateStatus status, short version[4])
   646 {
   647 	switch (status)
   648 	{
   649 		case Update_Unknown:
   650 		case Update_Current:
   651 		{
   652 			break;
   653 		}
   654 
   655 		case Update_NewBuild:
   656 		{
   657 			break;
   658 		}
   659 
   660 		case Update_MinorAvailable:
   661 		{
   662 			g_Logger.LogMessage("SourceMod Update: A new release of SourceMod is now available from sourcemod.net");
   663 			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   664 			break;
   665 		}
   666 
   667 		case Update_MajorAvailable:
   668 		{
   669 			g_Logger.LogMessage("SourceMod Update: An major release of SourceMod is now available from sourcemod.net");
   670 			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   671 			break;
   672 		}
   673 
   674 		case Update_CriticalAvailable:
   675 		{
   676 			g_Logger.LogError("SourceMod Update: A critical SourceMod release is available from sourcemod.net. It is strongly recommended that you update!");
   677 			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   678 			break;
   679 		}
   680 	}
   681 }
   682 
   683 bool g_blockGameDataLoad = false;
   684 static IThreadHandle *fetch_thread_hndl;
   685 
   686 class InitFetch : public SMGlobalClass
   687 {
   688 public:
   689 	void OnSourceModAllInitialized_Post()
   690 	{
   691 		char lock_path[PLATFORM_MAX_PATH];
   692 		g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
   693 
   694 		if (g_LibSys.IsPathFile(lock_path) && g_LibSys.PathExists(lock_path))
   695 		{
   696 			g_Logger.LogError("sourcemod/data/temp/gamedata.lock file detected. This is most likely due to a crash during GameData updating - Blocking GameData loading");
   697 			g_Logger.LogError("If this error persists delete the file manually");
   698 			g_blockGameDataLoad = true;
   699 		}
   700 
   701 		ThreadParams fetchThreadParams = ThreadParams();
   702 		fetchThreadParams.prio = ThreadPrio_Low;
   703 		fetch_thread_hndl = g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
   704 	}
   705 
   706 	void OnSourceModShutdown()
   707 	{
   708 		fetch_thread_hndl->WaitForThread();
   709 		fetch_thread_hndl->DestroyThis();
   710 	}
   711 
   712 	void OnSourceModLevelActivated()
   713 	{
   714 		was_level_started = true;
   715 
   716 		if (g_restartAfterUpdate && 
   717 			g_FetchThread.wasSuccess && 
   718 			g_FetchThread.needsRestart)
   719 		{
   720 			ForceRestart();
   721 		}
   722 	}
   723 
   724 	void OnSourceModLevelEnd()
   725 	{
   726 		was_level_started = false;
   727 	}
   728 
   729 	ConfigResult OnSourceModConfigChanged(const char *key, 
   730 		const char *value, 
   731 		ConfigSource source,
   732 		char *error, 
   733 		size_t maxlength)
   734 	{
   735 		if (strcasecmp(key, "DisableAutoUpdate") == 0)
   736 		{
   737 			if (strcasecmp(value, "yes") == 0)
   738 			{
   739 				g_disableGameDataUpdate = true;
   740 				return ConfigResult_Accept;
   741 			}
   742 			else if (strcasecmp(value, "no") == 0)
   743 			{
   744 				g_disableGameDataUpdate = false;
   745 				return ConfigResult_Accept;
   746 			}
   747 
   748 			return ConfigResult_Reject;
   749 		}
   750 
   751 		if (strcasecmp(key, "ForceRestartAfterUpdate") == 0)
   752 		{
   753 			if (strcasecmp(value, "yes") == 0)
   754 			{
   755 				g_restartAfterUpdate = true;
   756 				return ConfigResult_Accept;
   757 			}
   758 			else if (strcasecmp(value, "no") == 0)
   759 			{
   760 				g_restartAfterUpdate = false;
   761 				return ConfigResult_Accept;
   762 			}
   763 
   764 			return ConfigResult_Reject;
   765 		}
   766 
   767 		if (strcasecmp(key, "AutoUpdateServer") == 0)
   768 		{
   769 			UTIL_Format(g_serverAddress, sizeof(g_serverAddress), "%s", value);
   770 
   771 			return ConfigResult_Accept;
   772 		}
   773 
   774 		if (strcasecmp(key, "AutoUpdatePort") == 0)
   775 		{
   776 			int port = atoi(value);
   777 
   778 			if (!port)
   779 			{
   780 				return ConfigResult_Reject;
   781 			}
   782 
   783 			g_serverPort = port;
   784 
   785 			return ConfigResult_Accept;
   786 		}
   787 
   788 		return ConfigResult_Ignore;
   789 	}
   790 } g_InitFetch;
   791 
   792 BuildMD5ableBuffer::BuildMD5ableBuffer()
   793 {
   794 	stringTable = new BaseStringTable(2048);
   795 }
   796 
   797 BuildMD5ableBuffer::~BuildMD5ableBuffer()
   798 {
   799 	delete stringTable;
   800 }
   801 
   802 void BuildMD5ableBuffer::ReadSMC_ParseStart()
   803 {
   804 	stringTable->Reset();
   805 }
   806 
   807 SMCResult BuildMD5ableBuffer::ReadSMC_KeyValue(const SMCStates *states,
   808 											   const char *key,
   809 											   const char *value)
   810 {
   811 	stringTable->AddString(key);
   812 	stringTable->AddString(value);
   813 
   814 	return SMCResult_Continue;
   815 }
   816 
   817 SMCResult BuildMD5ableBuffer::ReadSMC_NewSection(const SMCStates *states, const char *name)
   818 {
   819 	stringTable->AddString(name);
   820 
   821 	return SMCResult_Continue;
   822 }
   823 
   824 void BuildMD5ableBuffer::ReadSMC_ParseEnd(bool halted, bool failed)
   825 {
   826 	if (halted || failed)
   827 	{
   828 		return;
   829 	}
   830 
   831 	void *data = stringTable->GetMemTable()->GetAddress(0);
   832 
   833 	if (data != NULL)
   834 	{
   835 		checksum->update((unsigned char *)data, stringTable->GetMemTable()->GetActualMemUsed());
   836 	}
   837 
   838 	checksum->finalize();
   839 }
   840 
   841 CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
   842 {
   843 #if SOURCE_ENGINE == SE_EPISODEONE
   844 	CCommand args;
   845 #endif
   846 
   847 	if (args.ArgC() < 2)
   848 	{
   849 		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
   850 		return;
   851 	}
   852 
   853 	const char *file = args.Arg(1);
   854 	if (!file || file[0] == '\0')
   855 	{
   856 		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
   857 		return;
   858 	}
   859 
   860 	SourceHook::CVector<FileData *>::iterator iter = g_FetchThread.filenames.begin();
   861 
   862 	FileData *curData;
   863 
   864 	while (iter != g_FetchThread.filenames.end())
   865 	{
   866 		curData = (*iter);
   867 
   868 		char fileName[30];
   869 
   870 		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), curData->filename->c_str());
   871 
   872 		if (strcmpi(fileName, file) == 0)
   873 		{
   874 			g_SMAPI->ConPrintf("MD5 Sum: %s\n", curData->checksum);
   875 			return;
   876 		}
   877 
   878 		iter++;
   879 	}
   880 
   881 	g_SMAPI->ConPrint("File not found!\n");
   882 }
   883 
   884 FetcherThread::~FetcherThread()
   885 {
   886 	SourceHook::CVector<FileData *>::iterator iter = filenames.begin();
   887 
   888 	FileData *curData;
   889 
   890 	while (iter != filenames.end())
   891 	{
   892 		curData = (*iter);
   893 		delete curData->filename;
   894 		delete curData;
   895 		iter = filenames.erase(iter);
   896 	}
   897 }
   898 
   899 FetcherThread::FetcherThread() 
   900 {
   901 	memtable = new BaseMemTable(4096);
   902 	wasSuccess = false;
   903 	needsRestart = false;
   904 }
   905