core/GameDataFetcher.cpp
author David Anderson <dvander@alliedmods.net>
Mon Sep 29 20:02:19 2008 -0700 (2008-09-29)
changeset 2335 cf312a67fb8f
parent 2247 da4a6d013772
child 2367 0a45bbad3bde
permissions -rw-r--r--
Fixed OnConfigsExecuted() not working on listen servers (bug 3287, r=me).
     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 #endif
    48 
    49 #include "sh_vector.h"
    50 #include "sh_string.h"
    51 #include "sm_version.h"
    52 
    53 #ifdef ORANGEBOX_BUILD
    54 #include "convar_sm_ob.h"
    55 #else
    56 #include "convar_sm.h"
    57 #endif
    58 
    59 #include "sourcemm_api.h"
    60 #include "time.h"
    61 #include "TimerSys.h"
    62 #include "compat_wrappers.h"
    63 #include "sm_stringutil.h"
    64 
    65 #define QUERY_MAX_LENGTH 1024
    66 
    67 BuildMD5ableBuffer g_MD5Builder;
    68 FetcherThread g_FetchThread;
    69 
    70 FILE *logfile = NULL;
    71 
    72 bool g_disableGameDataUpdate = false;
    73 bool g_restartAfterUpdate = false;
    74 
    75 int g_serverPort = 6500;
    76 char g_serverAddress[100] = "hayate.alliedmods.net";
    77 
    78 void FetcherThread::RunThread( IThreadHandle *pHandle )
    79 {
    80 	char lock_path[PLATFORM_MAX_PATH];
    81 	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp");
    82 	g_LibSys.CreateFolder(lock_path);
    83 
    84 	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
    85 
    86 	g_Logger.LogMessage("Starting experimental gamedata update fetcher... please report problems to bugs.alliedmods.net");
    87 
    88 	char log_path[PLATFORM_MAX_PATH];
    89 	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata");
    90 
    91 	g_LibSys.CreateFolder(log_path);
    92 
    93 	time_t t;
    94 	GetAdjustedTime(&t);
    95 	tm *curtime = localtime(&t);
    96 
    97 	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata/L%02d%02d.log", curtime->tm_mon + 1, curtime->tm_mday);
    98 
    99 	logfile = fopen(log_path, "a");
   100 
   101 	if (!logfile)
   102 	{
   103 		g_Logger.LogError("Failed to create GameData log file");
   104 		return;
   105 	}
   106 
   107 	//Create a blank lock file
   108 	FILE *fp = fopen(lock_path, "w");
   109 	if (fp)
   110 	{
   111 		fclose(fp);
   112 	}
   113 
   114 	/* Create a new socket for this connection */
   115 	int socketDescriptor = ConnectSocket();
   116 
   117 	if (socketDescriptor == INVALID_SOCKET)
   118 	{
   119 		fclose(logfile);
   120 		unlink(lock_path);
   121 		return;
   122 	}
   123 
   124 	char query[QUERY_MAX_LENGTH];
   125 
   126 	/* Check for updated gamedata files */
   127 	int len = BuildGameDataQuery(query, QUERY_MAX_LENGTH);
   128 
   129 	if (len == 0)
   130 	{
   131 		g_Logger.LogToOpenFile(logfile, "Query Writing failed");
   132 
   133 		closesocket(socketDescriptor);
   134 		fclose(logfile);
   135 		unlink(lock_path);
   136 		return;
   137 	}
   138 
   139 	if (g_disableGameDataUpdate)
   140 	{
   141 		g_Logger.LogMessage("Skipping GameData Query due to DisableAutoUpdate being set to true");
   142 
   143 		closesocket(socketDescriptor);
   144 		fclose(logfile);
   145 		unlink(lock_path);
   146 		return;
   147 	}
   148 
   149 	int sent = NonBlockingSend(socketDescriptor, query, len);
   150 
   151 	g_Logger.LogToOpenFile(logfile, "Sent Query!");
   152 
   153 	if (sent == 0)
   154 	{
   155 		g_Logger.LogToOpenFile(logfile, "Failed to send data");
   156 
   157 		closesocket(socketDescriptor);
   158 		fclose(logfile);
   159 		unlink(lock_path);
   160 		return;
   161 	}
   162 
   163 	ProcessGameDataQuery(socketDescriptor);
   164 
   165 	/* And we're done! */
   166 	closesocket(socketDescriptor);
   167 	fclose(logfile);
   168 	unlink(lock_path);
   169 }
   170 
   171 void FetcherThread::OnTerminate( IThreadHandle *pHandle, bool cancel )
   172 {
   173 	g_blockGameDataLoad = false;
   174 }
   175 
   176 int FetcherThread::BuildGameDataQuery( char *buffer, int maxlen )
   177 {
   178 	char gamedata_path[PLATFORM_MAX_PATH];
   179 	g_SourceMod.BuildPath(Path_SM, gamedata_path, sizeof(gamedata_path), "gamedata");
   180 
   181 	IDirectory *dir = g_LibSys.OpenDirectory(gamedata_path);
   182 
   183 	if (dir == NULL)
   184 	{
   185 		return 0;
   186 	}
   187 
   188 	bf_write Writer = bf_write("GameDataQuery", buffer, maxlen);
   189 
   190 	Writer.WriteByte('A'); //Generic Header char
   191 	Writer.WriteByte('G'); //G for gamedata query, or green, like my hat.
   192 
   193 	short build[4] = { SVN_FILE_VERSION };
   194 
   195 	Writer.WriteBytes(&build[0], 8);
   196 
   197 	Writer.WriteByte(0); // Initialize the file counter - Index 10
   198 
   199 	while (dir->MoreFiles())
   200 	{
   201 		if (dir->IsEntryFile())
   202 		{
   203 			const char *name = dir->GetEntryName();
   204 			size_t len = strlen(name);
   205 			if (len >= 4
   206 				&& strcmp(&name[len-4], ".txt") == 0)
   207 			{
   208 				char file[PLATFORM_MAX_PATH];
   209 
   210 				g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
   211 
   212 				SMCStates states;
   213 				if (g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states) == SMCError_Okay)
   214 				{
   215 					unsigned char *md5 = g_MD5Builder.GetMD5();
   216 					if (md5 != NULL)
   217 					{
   218 						(uint8_t)buffer[10]++; //Increment the file counter
   219 						Writer.WriteBytes(md5, 16);
   220 
   221 						g_Logger.LogToOpenFile(logfile, "%s - \"%s\"", file, g_MD5Builder.GetMD5String());
   222 
   223 						FileData *data = new FileData();
   224 						data->filename = new SourceHook::String(file);
   225 						memcpy(data->checksum, g_MD5Builder.GetMD5String(), 33);
   226 						filenames.push_back(data);
   227 					}
   228 					else
   229 					{
   230 						g_Logger.LogToOpenFile(logfile, "%s no md5?", file);
   231 					}
   232 				}
   233 				else
   234 				{
   235 					g_Logger.LogToOpenFile(logfile, "%s failed!", file);
   236 				}
   237 			}
   238 		}
   239 
   240 		dir->NextEntry();
   241 	}
   242 
   243 	return Writer.GetNumBytesWritten();
   244 }
   245 
   246 int FetcherThread::ConnectSocket()
   247 {
   248 	struct protoent *ptrp;
   249 	ptrp = getprotobyname("tcp");
   250 
   251 #ifdef WIN32
   252 	WSADATA wsaData;
   253 	WSAStartup(0x0101, &wsaData);
   254 #endif
   255 
   256 	if (ptrp == NULL) 
   257 	{
   258 		g_Logger.LogToOpenFile(logfile, "Failed to find TCP");
   259 		return INVALID_SOCKET;
   260 	}
   261 
   262 	int socketDescriptor = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
   263 
   264 	if (socketDescriptor == INVALID_SOCKET)
   265 	{
   266 		//bugger aye?
   267 		g_Logger.LogToOpenFile(logfile, "Failed to create a new socket");
   268 		closesocket(socketDescriptor);
   269 		return INVALID_SOCKET;
   270 	}
   271 
   272 	struct hostent *he;
   273 	struct sockaddr_in local_addr;
   274 
   275 	local_addr.sin_family = AF_INET;
   276 	local_addr.sin_port = htons((u_short)g_serverPort);
   277 
   278 	he = gethostbyname(g_serverAddress);
   279 
   280 	if (!he)
   281 	{
   282 		if ((local_addr.sin_addr.s_addr = inet_addr(g_serverAddress)) == INADDR_NONE)
   283 		{
   284 			g_Logger.LogToOpenFile(logfile, "Couldnt locate address");
   285 			closesocket(socketDescriptor);
   286 			return INVALID_SOCKET;
   287 		}
   288 	} 
   289 	else 
   290 	{
   291 		memcpy(&local_addr.sin_addr, (struct in_addr *)he->h_addr, he->h_length);
   292 	}
   293 
   294 	if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
   295 	{
   296 		g_Logger.LogToOpenFile(logfile, "Couldn't connect");
   297 		closesocket(socketDescriptor);
   298 		return INVALID_SOCKET;
   299 	}
   300 
   301 	return socketDescriptor;
   302 }
   303 
   304 void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
   305 {
   306 	char buffer[50];
   307 
   308 	g_Logger.LogToOpenFile(logfile, "Waiting for reply!");
   309 
   310 	//Read in the header bytes
   311 	int returnLen = NonBlockingRecv(socketDescriptor, buffer, 12);
   312 
   313 	g_Logger.LogToOpenFile(logfile, "Recv Completed");
   314 
   315 	if (returnLen == 0)
   316 	{
   317 		g_Logger.LogToOpenFile(logfile, ",but it failed.");
   318 		/* Timeout or fail? */
   319 		return;
   320 	}
   321 
   322 	g_Logger.LogToOpenFile(logfile, "Received Header!");
   323 
   324 	bf_read Reader = bf_read("GameDataQuery", buffer, 12);
   325 
   326 	if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
   327 	{
   328 		g_Logger.LogToOpenFile(logfile, "Invalid Query to handle");
   329 		return;
   330 	}
   331 
   332 	UpdateStatus updateStatus = (UpdateStatus)Reader.ReadByte();
   333 
   334 	short build[4] = {0,0,0,0};
   335 
   336 	build[0] = Reader.ReadShort();
   337 	build[1] = Reader.ReadShort();
   338 	build[2] = Reader.ReadShort();
   339 	build[3] = Reader.ReadShort();
   340 
   341 	g_Logger.LogToOpenFile(logfile, "Update Status: %i - Latest %i.%i.%i.%i", updateStatus, build[0], build[1], build[2], build[3]);
   342 
   343 	HandleUpdateStatus(updateStatus, build);
   344 
   345 	int changedFiles = Reader.ReadByte();
   346 
   347 	g_Logger.LogToOpenFile(logfile, "Files to download: %i", changedFiles);
   348 
   349 	for (int i=0; i<changedFiles; i++)
   350 	{
   351 		//Read in the file index and byte count
   352 		returnLen = NonBlockingRecv(socketDescriptor, buffer, 5);
   353 
   354 		if (returnLen == 0)
   355 		{
   356 			/* Timeout or fail? */
   357 			return;
   358 		}
   359 
   360 		Reader.StartReading(buffer, 5);
   361 
   362 		int index = Reader.ReadByte();
   363 		int tempLen = Reader.ReadUBitLong(32);
   364 
   365 		g_Logger.LogToOpenFile(logfile, "File index %i and length %i", index, tempLen);
   366 
   367 		void *memPtr;
   368 		memtable->CreateMem(tempLen+1, &memPtr);
   369 
   370 		//Read the contents of our file into the memtable
   371 		returnLen = NonBlockingRecv(socketDescriptor, (char *)memPtr, tempLen);
   372 
   373 		g_Logger.LogToOpenFile(logfile, "Recieved %i bytes", returnLen);
   374 
   375 		if (returnLen == 0)
   376 		{
   377 			/* Timeout or fail? */
   378 			return;
   379 		}
   380 
   381 		((unsigned char *)memPtr)[tempLen] = '\0';
   382 
   383 		FileData *data = filenames.at(index);
   384 		const char* filename;
   385 		if (data != NULL)
   386 		{
   387 			filename = data->filename->c_str();
   388 
   389 			FILE *fp = fopen(filename, "w");
   390 
   391 			if (fp)
   392 			{
   393 				fprintf(fp, (const char *)memPtr);
   394 				fclose(fp);
   395 			}
   396 		}
   397 		else
   398 		{
   399 			filename = "";
   400 		}
   401 
   402 		memtable->Reset();
   403 
   404 		g_Logger.LogToOpenFile(logfile, "Updated File %s", filename);
   405 	}
   406 
   407 	g_Logger.LogToOpenFile(logfile, "File Downloads Completed!");
   408 
   409 	bool needsRestart = false;
   410 
   411 	if (changedFiles > 0)
   412 	{
   413 		needsRestart = true;
   414 		g_Logger.LogMessage("New GameData Files have been downloaded to your gamedata directory. Please restart your server for these to take effect");
   415 	}
   416 
   417 	//Read changed file count
   418 	returnLen = NonBlockingRecv(socketDescriptor, buffer, 1);
   419 
   420 	if (returnLen == 0)
   421 	{
   422 		/* Timeout or fail? */
   423 		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown count");
   424 		return;
   425 	}
   426 
   427 	Reader.StartReading(buffer, 1);
   428 
   429 	changedFiles = Reader.ReadByte();
   430 
   431 	if (changedFiles == 0)
   432 	{
   433 		g_Logger.LogToOpenFile(logfile, "No unknown files. We're all done");
   434 		return;
   435 	}
   436 
   437 	char *changedFileIndexes = new char[changedFiles];
   438 	
   439 	g_Logger.LogToOpenFile(logfile, "%i Files were unknown", changedFiles);
   440 	
   441 	returnLen = NonBlockingRecv(socketDescriptor, changedFileIndexes, changedFiles);
   442 
   443 	if (returnLen == 0)
   444 	{
   445 		/* Timeout or fail? */
   446 		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown list");
   447 		return;
   448 	}
   449 
   450 	Reader.StartReading(changedFileIndexes, changedFiles);
   451 
   452 	for (int i=0; i<changedFiles; i++)
   453 	{
   454 		int index = Reader.ReadByte();
   455 		char fileName[30];
   456 
   457 		FileData *data = filenames.at(index);
   458 		const char* pathname;
   459 		if (data != NULL)
   460 		{
   461 			pathname = data->filename->c_str();
   462 		}
   463 		else
   464 		{
   465 			pathname = "";
   466 		}
   467 
   468 		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
   469 
   470 		g_Logger.LogToOpenFile(logfile, "Unknown File %i : %s", index, fileName);
   471 	}
   472 
   473 	delete [] changedFileIndexes;
   474 
   475 	if (needsRestart && g_restartAfterUpdate)
   476 	{
   477 		g_Logger.LogMessage("Automatically restarting server after a successful gamedata update!");
   478 		engine->ServerCommand("quit\n");
   479 	}
   480 }
   481 
   482 int FetcherThread::NonBlockingRecv( int socketDescriptor, char *buffer, int len )
   483 {
   484 	fd_set fds;
   485 	struct timeval tv;
   486 
   487 	/* Create a 10 Second Timeout */
   488 	tv.tv_sec = 10;
   489 	tv.tv_usec = 0;
   490 
   491 	/* Add our socket to a socket set */
   492 	FD_ZERO(&fds);
   493 	FD_SET(socketDescriptor, &fds);
   494 
   495 	/* Wait max of 10 seconds for recv to become available */
   496 	select(socketDescriptor+1, &fds, NULL, NULL, &tv);
   497 
   498 	int bytesReceived = 0;
   499 
   500 	/* Is there a limit on how much we can receive? Some site said 1024 bytes, which will be well short of a file */
   501 	if (FD_ISSET(socketDescriptor, &fds))
   502 	{
   503 		bytesReceived = recv(socketDescriptor, buffer, len, 0);
   504 	}
   505 
   506 	if (bytesReceived == 0 || bytesReceived == -1)
   507 	{
   508 		return 0;
   509 	}
   510 
   511 	if (bytesReceived < len)
   512 	{
   513 		return bytesReceived + NonBlockingRecv(socketDescriptor, buffer+bytesReceived, len-bytesReceived);
   514 	}
   515 
   516 	return bytesReceived;	
   517 }
   518 
   519 int FetcherThread::NonBlockingSend( int socketDescriptor, char *buffer, int len )
   520 {
   521 	fd_set fds;
   522 	struct timeval tv;
   523 
   524 	tv.tv_sec = 10;
   525 	tv.tv_usec = 0;
   526 
   527 	FD_ZERO(&fds);
   528 	FD_SET(socketDescriptor, &fds);
   529 
   530 	select(socketDescriptor+1, NULL, &fds, NULL, &tv);
   531 
   532 	int sentBytes = 0;
   533 
   534 	if (FD_ISSET(socketDescriptor, &fds))
   535 	{
   536 		sentBytes = send(socketDescriptor, buffer, len, 0);
   537 	}
   538 
   539 	if (sentBytes == 0 || sentBytes == -1)
   540 	{
   541 		return 0;
   542 	}
   543 
   544 	if (sentBytes < len)
   545 	{
   546 		return sentBytes + NonBlockingSend(socketDescriptor, buffer+sentBytes, len-sentBytes);
   547 	}
   548 
   549 	return sentBytes;
   550 }
   551 
   552 void FetcherThread::HandleUpdateStatus( UpdateStatus status, short version[4] )
   553 {
   554 	switch (status)
   555 	{
   556 		case Update_Unknown:
   557 		case Update_Current:
   558 		{
   559 			break;
   560 		}
   561 
   562 		case Update_NewBuild:
   563 		{
   564 			g_Logger.LogMessage("SourceMod Update: A new SVN build is available from sourcemod.net");
   565 			g_Logger.LogMessage("Current Version: %i.%i.%i.%i Available: %i.%i.%i.%i", version[0], version[1], version[2], version[3], version[0], version[1], version[2], version[3]);
   566 			break;
   567 		}
   568 
   569 		case Update_MinorAvailable:
   570 		{
   571 			g_Logger.LogMessage("SourceMod Update: An incremental minor release of SourceMod is now available from sourcemod.net");
   572 			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   573 			break;
   574 		}
   575 
   576 		case Update_MajorAvailable:
   577 		{
   578 			g_Logger.LogMessage("SourceMod Update: An major release of SourceMod is now available from sourcemod.net");
   579 			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   580 			break;
   581 		}
   582 
   583 		case Update_CriticalAvailable:
   584 		{
   585 			g_Logger.LogError("SourceMod Update: A new critical release of SourceMod is now available from sourcemod.net. It is strongly recommended that you update");
   586 			g_Logger.LogMessage("Current Version: %i.%i.%i.%i Available: %i.%i.%i.%i", version[0], version[1], version[2], version[3], version[0], version[1], version[2], version[3]);
   587 			break;
   588 		}
   589 	}
   590 }
   591 
   592 bool g_blockGameDataLoad = false;
   593 
   594 class InitFetch : public SMGlobalClass
   595 {
   596 public:
   597 	void OnSourceModAllInitialized_Post()
   598 	{
   599 		char lock_path[PLATFORM_MAX_PATH];
   600 		g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
   601 
   602 		if (g_LibSys.IsPathFile(lock_path) && g_LibSys.PathExists(lock_path))
   603 		{
   604 			g_Logger.LogError("sourcemod/data/temp/gamedata.lock file detected. This is most likely due to a crash during GameData updating - Blocking GameData loading");
   605 			g_Logger.LogError("If this error persists delete the file manually");
   606 			g_blockGameDataLoad = true;
   607 		}
   608 
   609 		ThreadParams fetchThreadParams = ThreadParams();
   610 		fetchThreadParams.prio = ThreadPrio_Low;
   611 		g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
   612 	}
   613 
   614 	ConfigResult OnSourceModConfigChanged(const char *key, 
   615 		const char *value, 
   616 		ConfigSource source,
   617 		char *error, 
   618 		size_t maxlength)
   619 	{
   620 		if (strcmp(key, "DisableAutoUpdate") == 0)
   621 		{
   622 			if (strcmp(value, "yes") == 0)
   623 			{
   624 				g_disableGameDataUpdate = true;
   625 				return ConfigResult_Accept;
   626 			}
   627 			else if (strcmp(value, "no") == 0)
   628 			{
   629 				g_disableGameDataUpdate = false;
   630 				return ConfigResult_Accept;
   631 			}
   632 
   633 			return ConfigResult_Reject;
   634 		}
   635 
   636 		if (strcmp(key, "ForceRestartAfterUpdate") == 0)
   637 		{
   638 			if (strcmp(value, "yes") == 0)
   639 			{
   640 				g_restartAfterUpdate = true;
   641 				return ConfigResult_Accept;
   642 			}
   643 			else if (strcmp(value, "no") == 0)
   644 			{
   645 				g_restartAfterUpdate = false;
   646 				return ConfigResult_Accept;
   647 			}
   648 
   649 			return ConfigResult_Reject;
   650 		}
   651 
   652 		if (strcmp(key, "AutoUpdateServer") == 0)
   653 		{
   654 			UTIL_Format(g_serverAddress, sizeof(g_serverAddress), "%s", value);
   655 
   656 			return ConfigResult_Accept;
   657 		}
   658 
   659 		if (strcmp(key, "AutoUpdatePort") == 0)
   660 		{
   661 			int port = atoi(value);
   662 
   663 			if (!port)
   664 			{
   665 				return ConfigResult_Reject;
   666 			}
   667 
   668 			g_serverPort = port;
   669 
   670 			return ConfigResult_Accept;
   671 		}
   672 
   673 		return ConfigResult_Ignore;
   674 	}
   675 } g_InitFetch;
   676 
   677 CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
   678 {
   679 #if !defined ORANGEBOX_BUILD
   680 	CCommand args;
   681 #endif
   682 
   683 	if (args.ArgC() < 2)
   684 	{
   685 		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
   686 		return;
   687 	}
   688 
   689 	const char *file = args.Arg(1);
   690 	if (!file || file[0] == '\0')
   691 	{
   692 		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
   693 		return;
   694 	}
   695 
   696 	SourceHook::CVector<FileData *>::iterator iter = g_FetchThread.filenames.begin();
   697 
   698 	FileData *curData;
   699 
   700 	while (iter != g_FetchThread.filenames.end())
   701 	{
   702 		curData = (*iter);
   703 
   704 		char fileName[30];
   705 
   706 		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), curData->filename->c_str());
   707 
   708 		if (strcmpi(fileName, file) == 0)
   709 		{
   710 			g_SMAPI->ConPrintf("MD5 Sum: %s\n", curData->checksum);
   711 			return;
   712 		}
   713 
   714 		iter++;
   715 	}
   716 
   717 	g_SMAPI->ConPrint("File not found!\n");
   718 }