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