core/GameDataFetcher.cpp
branchrefac-jit
changeset 2168 c24a6fa957db
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/core/GameDataFetcher.cpp	Sun Jul 06 04:49:55 2008 +0000
     1.3 @@ -0,0 +1,693 @@
     1.4 +/**
     1.5 +* vim: set ts=4 :
     1.6 +* =============================================================================
     1.7 +* SourceMod
     1.8 +* Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     1.9 +* =============================================================================
    1.10 +*
    1.11 +* This program is free software; you can redistribute it and/or modify it under
    1.12 +* the terms of the GNU General Public License, version 3.0, as published by the
    1.13 +* Free Software Foundation.
    1.14 +* 
    1.15 +* This program is distributed in the hope that it will be useful, but WITHOUT
    1.16 +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    1.17 +* FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    1.18 +* details.
    1.19 +*
    1.20 +* You should have received a copy of the GNU General Public License along with
    1.21 +* this program.  If not, see <http://www.gnu.org/licenses/>.
    1.22 +*
    1.23 +* As a special exception, AlliedModders LLC gives you permission to link the
    1.24 +* code of this program (as well as its derivative works) to "Half-Life 2," the
    1.25 +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    1.26 +* by the Valve Corporation.  You must obey the GNU General Public License in
    1.27 +* all respects for all other code used.  Additionally, AlliedModders LLC grants
    1.28 +* this exception to all derivative works.  AlliedModders LLC defines further
    1.29 +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    1.30 +* or <http://www.sourcemod.net/license.php>.
    1.31 +*
    1.32 +* Version: $Id$
    1.33 +*/
    1.34 +
    1.35 +#include "GameDataFetcher.h"
    1.36 +#include "bitbuf.h"
    1.37 +
    1.38 +#ifdef PLATFORM_WINDOWS
    1.39 +#include <winsock2.h>
    1.40 +#else
    1.41 +#include <unistd.h>
    1.42 +#include <sys/types.h>
    1.43 +#include <sys/socket.h>
    1.44 +#include <netinet/in.h>
    1.45 +#include <netdb.h>
    1.46 +#include <arpa/inet.h>
    1.47 +
    1.48 +#define INVALID_SOCKET -1
    1.49 +#define closesocket close
    1.50 +#endif
    1.51 +
    1.52 +#include "sh_vector.h"
    1.53 +#include "sh_string.h"
    1.54 +#include "sm_version.h"
    1.55 +
    1.56 +#ifdef ORANGEBOX_BUILD
    1.57 +#include "convar_sm_ob.h"
    1.58 +#else
    1.59 +#include "convar_sm.h"
    1.60 +#endif
    1.61 +
    1.62 +#include "sourcemm_api.h"
    1.63 +#include "time.h"
    1.64 +#include "TimerSys.h"
    1.65 +#include "compat_wrappers.h"
    1.66 +
    1.67 +#define QUERY_MAX_LENGTH 1024
    1.68 +
    1.69 +BuildMD5ableBuffer g_MD5Builder;
    1.70 +FetcherThread g_FetchThread;
    1.71 +
    1.72 +FILE *logfile = NULL;
    1.73 +
    1.74 +bool g_disableGameDataUpdate = false;
    1.75 +bool g_restartAfterUpdate = false;
    1.76 +
    1.77 +void FetcherThread::RunThread( IThreadHandle *pHandle )
    1.78 +{
    1.79 +	char lock_path[PLATFORM_MAX_PATH];
    1.80 +	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp");
    1.81 +	g_LibSys.CreateFolder(lock_path);
    1.82 +
    1.83 +	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
    1.84 +
    1.85 +	g_Logger.LogMessage("Starting experimental gamedata update fetcher... please report problems to bugs.alliedmods.net");
    1.86 +
    1.87 +	char log_path[PLATFORM_MAX_PATH];
    1.88 +	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata");
    1.89 +
    1.90 +	g_LibSys.CreateFolder(log_path);
    1.91 +
    1.92 +	time_t t;
    1.93 +	GetAdjustedTime(&t);
    1.94 +	tm *curtime = localtime(&t);
    1.95 +
    1.96 +	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata/L%02d%02d.log", curtime->tm_mon + 1, curtime->tm_mday);
    1.97 +
    1.98 +	logfile = fopen(log_path, "a");
    1.99 +
   1.100 +	if (!logfile)
   1.101 +	{
   1.102 +		g_Logger.LogError("Failed to create GameData log file");
   1.103 +		return;
   1.104 +	}
   1.105 +
   1.106 +	//Create a blank lock file
   1.107 +	FILE *fp = fopen(lock_path, "w");
   1.108 +	if (fp)
   1.109 +	{
   1.110 +		fclose(fp);
   1.111 +	}
   1.112 +
   1.113 +	/* Create a new socket for this connection */
   1.114 +	int socketDescriptor = ConnectSocket();
   1.115 +
   1.116 +	if (socketDescriptor == INVALID_SOCKET)
   1.117 +	{
   1.118 +		fclose(logfile);
   1.119 +		unlink(lock_path);
   1.120 +		return;
   1.121 +	}
   1.122 +
   1.123 +	char query[QUERY_MAX_LENGTH];
   1.124 +
   1.125 +	/* Check for updated gamedata files */
   1.126 +	int len = BuildGameDataQuery(query, QUERY_MAX_LENGTH);
   1.127 +
   1.128 +	if (len == 0)
   1.129 +	{
   1.130 +		g_Logger.LogToOpenFile(logfile, "Query Writing failed");
   1.131 +
   1.132 +		closesocket(socketDescriptor);
   1.133 +		fclose(logfile);
   1.134 +		unlink(lock_path);
   1.135 +		return;
   1.136 +	}
   1.137 +
   1.138 +	if (g_disableGameDataUpdate)
   1.139 +	{
   1.140 +		g_Logger.LogMessage("Skipping GameData Query due to DisableAutoUpdate being set to true");
   1.141 +
   1.142 +		closesocket(socketDescriptor);
   1.143 +		fclose(logfile);
   1.144 +		unlink(lock_path);
   1.145 +		return;
   1.146 +	}
   1.147 +
   1.148 +	int sent = NonBlockingSend(socketDescriptor, query, len);
   1.149 +
   1.150 +	g_Logger.LogToOpenFile(logfile, "Sent Query!");
   1.151 +
   1.152 +	if (sent == 0)
   1.153 +	{
   1.154 +		g_Logger.LogToOpenFile(logfile, "Failed to send data");
   1.155 +
   1.156 +		closesocket(socketDescriptor);
   1.157 +		fclose(logfile);
   1.158 +		unlink(lock_path);
   1.159 +		return;
   1.160 +	}
   1.161 +
   1.162 +	ProcessGameDataQuery(socketDescriptor);
   1.163 +
   1.164 +	/* And we're done! */
   1.165 +	closesocket(socketDescriptor);
   1.166 +	fclose(logfile);
   1.167 +	unlink(lock_path);
   1.168 +}
   1.169 +
   1.170 +void FetcherThread::OnTerminate( IThreadHandle *pHandle, bool cancel )
   1.171 +{
   1.172 +	//delete this;
   1.173 +}
   1.174 +
   1.175 +int FetcherThread::BuildGameDataQuery( char *buffer, int maxlen )
   1.176 +{
   1.177 +	char gamedata_path[PLATFORM_MAX_PATH];
   1.178 +	g_SourceMod.BuildPath(Path_SM, gamedata_path, sizeof(gamedata_path), "gamedata");
   1.179 +
   1.180 +	IDirectory *dir = g_LibSys.OpenDirectory(gamedata_path);
   1.181 +
   1.182 +	if (dir == NULL)
   1.183 +	{
   1.184 +		return 0;
   1.185 +	}
   1.186 +
   1.187 +	bf_write Writer = bf_write("GameDataQuery", buffer, maxlen);
   1.188 +
   1.189 +	Writer.WriteByte('A'); //Generic Header char
   1.190 +	Writer.WriteByte('G'); //G for gamedata query, or green, like my hat.
   1.191 +
   1.192 +	short build[4] = { SVN_FILE_VERSION };
   1.193 +
   1.194 +	Writer.WriteBytes(&build[0], 8);
   1.195 +
   1.196 +	Writer.WriteByte(0); // Initialize the file counter - Index 10
   1.197 +
   1.198 +	while (dir->MoreFiles())
   1.199 +	{
   1.200 +		if (dir->IsEntryFile())
   1.201 +		{
   1.202 +			const char *name = dir->GetEntryName();
   1.203 +			size_t len = strlen(name);
   1.204 +			if (len >= 4
   1.205 +				&& strcmp(&name[len-4], ".txt") == 0)
   1.206 +			{
   1.207 +				char file[PLATFORM_MAX_PATH];
   1.208 +
   1.209 +				g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
   1.210 +
   1.211 +				SMCStates states;
   1.212 +				if (g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states) == SMCError_Okay)
   1.213 +				{
   1.214 +					unsigned char *md5 = g_MD5Builder.GetMD5();
   1.215 +					if (md5 != NULL)
   1.216 +					{
   1.217 +						(uint8_t)buffer[10]++; //Increment the file counter
   1.218 +						Writer.WriteBytes(md5, 16);
   1.219 +
   1.220 +						g_Logger.LogToOpenFile(logfile, "%s - \"%s\"", file, g_MD5Builder.GetMD5String());
   1.221 +
   1.222 +						FileData *data = new FileData();
   1.223 +						data->filename = new SourceHook::String(file);
   1.224 +						memcpy(data->checksum, g_MD5Builder.GetMD5String(), 33);
   1.225 +						filenames.push_back(data);
   1.226 +					}
   1.227 +					else
   1.228 +					{
   1.229 +						g_Logger.LogToOpenFile(logfile, "%s no md5?", file);
   1.230 +					}
   1.231 +				}
   1.232 +				else
   1.233 +				{
   1.234 +					g_Logger.LogToOpenFile(logfile, "%s failed!", file);
   1.235 +				}
   1.236 +			}
   1.237 +		}
   1.238 +
   1.239 +		dir->NextEntry();
   1.240 +	}
   1.241 +
   1.242 +	return Writer.GetNumBytesWritten();
   1.243 +}
   1.244 +
   1.245 +int FetcherThread::ConnectSocket()
   1.246 +{
   1.247 +	struct protoent *ptrp;
   1.248 +	ptrp = getprotobyname("tcp");
   1.249 +
   1.250 +#ifdef WIN32
   1.251 +	WSADATA wsaData;
   1.252 +	WSAStartup(0x0101, &wsaData);
   1.253 +#endif
   1.254 +
   1.255 +	if (ptrp == NULL) 
   1.256 +	{
   1.257 +		g_Logger.LogToOpenFile(logfile, "Failed to find TCP");
   1.258 +		return INVALID_SOCKET;
   1.259 +	}
   1.260 +
   1.261 +	int socketDescriptor = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
   1.262 +
   1.263 +	if (socketDescriptor == INVALID_SOCKET)
   1.264 +	{
   1.265 +		//bugger aye?
   1.266 +		g_Logger.LogToOpenFile(logfile, "Failed to create a new socket");
   1.267 +		closesocket(socketDescriptor);
   1.268 +		return INVALID_SOCKET;
   1.269 +	}
   1.270 +
   1.271 +	struct hostent *he;
   1.272 +	struct sockaddr_in local_addr;
   1.273 +
   1.274 +	local_addr.sin_family = AF_INET;
   1.275 +	local_addr.sin_port = htons((u_short)6500);
   1.276 +
   1.277 +	he = gethostbyname("hayate.alliedmods.net");
   1.278 +
   1.279 +	if (!he)
   1.280 +	{
   1.281 +		if ((local_addr.sin_addr.s_addr = inet_addr("hayate.alliedmods.net")) == INADDR_NONE)
   1.282 +		{
   1.283 +			g_Logger.LogToOpenFile(logfile, "Couldnt locate address");
   1.284 +			closesocket(socketDescriptor);
   1.285 +			return INVALID_SOCKET;
   1.286 +		}
   1.287 +	} 
   1.288 +	else 
   1.289 +	{
   1.290 +		memcpy(&local_addr.sin_addr, (struct in_addr *)he->h_addr, he->h_length);
   1.291 +	}
   1.292 +
   1.293 +	if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
   1.294 +	{
   1.295 +		g_Logger.LogToOpenFile(logfile, "Couldn't connect");
   1.296 +		closesocket(socketDescriptor);
   1.297 +		return INVALID_SOCKET;
   1.298 +	}
   1.299 +
   1.300 +	return socketDescriptor;
   1.301 +}
   1.302 +
   1.303 +void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
   1.304 +{
   1.305 +	char buffer[50];
   1.306 +
   1.307 +	g_Logger.LogToOpenFile(logfile, "Waiting for reply!");
   1.308 +
   1.309 +	//Read in the header bytes
   1.310 +	int returnLen = NonBlockingRecv(socketDescriptor, buffer, 12);
   1.311 +
   1.312 +	g_Logger.LogToOpenFile(logfile, "Recv Completed");
   1.313 +
   1.314 +	if (returnLen == 0)
   1.315 +	{
   1.316 +		g_Logger.LogToOpenFile(logfile, ",but it failed.");
   1.317 +		/* Timeout or fail? */
   1.318 +		return;
   1.319 +	}
   1.320 +
   1.321 +	g_Logger.LogToOpenFile(logfile, "Received Header!");
   1.322 +
   1.323 +	bf_read Reader = bf_read("GameDataQuery", buffer, 12);
   1.324 +
   1.325 +	if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
   1.326 +	{
   1.327 +		g_Logger.LogToOpenFile(logfile, "Invalid Query to handle");
   1.328 +		return;
   1.329 +	}
   1.330 +
   1.331 +	UpdateStatus updateStatus = (UpdateStatus)Reader.ReadByte();
   1.332 +
   1.333 +	short build[4] = {0,0,0,0};
   1.334 +
   1.335 +	build[0] = Reader.ReadShort();
   1.336 +	build[1] = Reader.ReadShort();
   1.337 +	build[2] = Reader.ReadShort();
   1.338 +	build[3] = Reader.ReadShort();
   1.339 +
   1.340 +	g_Logger.LogToOpenFile(logfile, "Update Status: %i - Latest %i.%i.%i.%i", updateStatus, build[0], build[1], build[2], build[3]);
   1.341 +
   1.342 +	HandleUpdateStatus(updateStatus, build);
   1.343 +
   1.344 +	int changedFiles = Reader.ReadByte();
   1.345 +
   1.346 +	g_Logger.LogToOpenFile(logfile, "Files to download: %i", changedFiles);
   1.347 +
   1.348 +	for (int i=0; i<changedFiles; i++)
   1.349 +	{
   1.350 +		//Read in the file index and byte count
   1.351 +		returnLen = NonBlockingRecv(socketDescriptor, buffer, 5);
   1.352 +
   1.353 +		if (returnLen == 0)
   1.354 +		{
   1.355 +			/* Timeout or fail? */
   1.356 +			return;
   1.357 +		}
   1.358 +
   1.359 +		Reader.StartReading(buffer, 5);
   1.360 +
   1.361 +		int index = Reader.ReadByte();
   1.362 +		int tempLen = Reader.ReadUBitLong(32);
   1.363 +
   1.364 +		g_Logger.LogToOpenFile(logfile, "File index %i and length %i", index, tempLen);
   1.365 +
   1.366 +		void *memPtr;
   1.367 +		memtable->CreateMem(tempLen+1, &memPtr);
   1.368 +
   1.369 +		//Read the contents of our file into the memtable
   1.370 +		returnLen = NonBlockingRecv(socketDescriptor, (char *)memPtr, tempLen);
   1.371 +
   1.372 +		g_Logger.LogToOpenFile(logfile, "Recieved %i bytes", returnLen);
   1.373 +
   1.374 +		if (returnLen == 0)
   1.375 +		{
   1.376 +			/* Timeout or fail? */
   1.377 +			return;
   1.378 +		}
   1.379 +
   1.380 +		((unsigned char *)memPtr)[tempLen] = '\0';
   1.381 +
   1.382 +		FileData *data = filenames.at(index);
   1.383 +		const char* filename;
   1.384 +		if (data != NULL)
   1.385 +		{
   1.386 +			filename = data->filename->c_str();
   1.387 +
   1.388 +			FILE *fp = fopen(filename, "w");
   1.389 +
   1.390 +			if (fp)
   1.391 +			{
   1.392 +				fprintf(fp, (const char *)memPtr);
   1.393 +				fclose(fp);
   1.394 +			}
   1.395 +		}
   1.396 +		else
   1.397 +		{
   1.398 +			filename = "";
   1.399 +		}
   1.400 +
   1.401 +		memtable->Reset();
   1.402 +
   1.403 +		g_Logger.LogToOpenFile(logfile, "Updated File %s", filename);
   1.404 +	}
   1.405 +
   1.406 +	g_Logger.LogToOpenFile(logfile, "File Downloads Completed!");
   1.407 +
   1.408 +	bool needsRestart = false;
   1.409 +
   1.410 +	if (changedFiles > 0)
   1.411 +	{
   1.412 +		needsRestart = true;
   1.413 +		g_Logger.LogMessage("New GameData Files have been downloaded to your gamedata directory. Please restart your server for these to take effect");
   1.414 +	}
   1.415 +
   1.416 +	//Read changed file count
   1.417 +	returnLen = NonBlockingRecv(socketDescriptor, buffer, 1);
   1.418 +
   1.419 +	if (returnLen == 0)
   1.420 +	{
   1.421 +		/* Timeout or fail? */
   1.422 +		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown count");
   1.423 +		return;
   1.424 +	}
   1.425 +
   1.426 +	Reader.StartReading(buffer, 1);
   1.427 +
   1.428 +	changedFiles = Reader.ReadByte();
   1.429 +
   1.430 +	if (changedFiles == 0)
   1.431 +	{
   1.432 +		g_Logger.LogToOpenFile(logfile, "No unknown files. We're all done");
   1.433 +		return;
   1.434 +	}
   1.435 +
   1.436 +	char *changedFileIndexes = new char[changedFiles];
   1.437 +	
   1.438 +	g_Logger.LogToOpenFile(logfile, "%i Files were unknown", changedFiles);
   1.439 +	
   1.440 +	returnLen = NonBlockingRecv(socketDescriptor, changedFileIndexes, changedFiles);
   1.441 +
   1.442 +	if (returnLen == 0)
   1.443 +	{
   1.444 +		/* Timeout or fail? */
   1.445 +		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown list");
   1.446 +		return;
   1.447 +	}
   1.448 +
   1.449 +	Reader.StartReading(changedFileIndexes, changedFiles);
   1.450 +
   1.451 +	for (int i=0; i<changedFiles; i++)
   1.452 +	{
   1.453 +		int index = Reader.ReadByte();
   1.454 +		char fileName[30];
   1.455 +
   1.456 +		FileData *data = filenames.at(index);
   1.457 +		const char* pathname;
   1.458 +		if (data != NULL)
   1.459 +		{
   1.460 +			pathname = data->filename->c_str();
   1.461 +		}
   1.462 +		else
   1.463 +		{
   1.464 +			pathname = "";
   1.465 +		}
   1.466 +
   1.467 +		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
   1.468 +
   1.469 +		g_Logger.LogToOpenFile(logfile, "Unknown File %i : %s", index, fileName);
   1.470 +	}
   1.471 +
   1.472 +	delete [] changedFileIndexes;
   1.473 +
   1.474 +	if (needsRestart && g_restartAfterUpdate)
   1.475 +	{
   1.476 +		g_Logger.LogMessage("Automatically restarting server after a successful gamedata update!");
   1.477 +		engine->ServerCommand("quit\n");
   1.478 +	}
   1.479 +}
   1.480 +
   1.481 +int FetcherThread::NonBlockingRecv( int socketDescriptor, char *buffer, int len )
   1.482 +{
   1.483 +	fd_set fds;
   1.484 +	struct timeval tv;
   1.485 +
   1.486 +	/* Create a 10 Second Timeout */
   1.487 +	tv.tv_sec = 10;
   1.488 +	tv.tv_usec = 0;
   1.489 +
   1.490 +	/* Add our socket to a socket set */
   1.491 +	FD_ZERO(&fds);
   1.492 +	FD_SET(socketDescriptor, &fds);
   1.493 +
   1.494 +	/* Wait max of 10 seconds for recv to become available */
   1.495 +	select(socketDescriptor+1, &fds, NULL, NULL, &tv);
   1.496 +
   1.497 +	int bytesReceived = 0;
   1.498 +
   1.499 +	/* Is there a limit on how much we can receive? Some site said 1024 bytes, which will be well short of a file */
   1.500 +	if (FD_ISSET(socketDescriptor, &fds))
   1.501 +	{
   1.502 +		bytesReceived = recv(socketDescriptor, buffer, len, 0);
   1.503 +	}
   1.504 +
   1.505 +	if (bytesReceived == 0 || bytesReceived == -1)
   1.506 +	{
   1.507 +		return 0;
   1.508 +	}
   1.509 +
   1.510 +	if (bytesReceived < len)
   1.511 +	{
   1.512 +		return bytesReceived + NonBlockingRecv(socketDescriptor, buffer+bytesReceived, len-bytesReceived);
   1.513 +	}
   1.514 +
   1.515 +	return bytesReceived;	
   1.516 +}
   1.517 +
   1.518 +int FetcherThread::NonBlockingSend( int socketDescriptor, char *buffer, int len )
   1.519 +{
   1.520 +	fd_set fds;
   1.521 +	struct timeval tv;
   1.522 +
   1.523 +	tv.tv_sec = 10;
   1.524 +	tv.tv_usec = 0;
   1.525 +
   1.526 +	FD_ZERO(&fds);
   1.527 +	FD_SET(socketDescriptor, &fds);
   1.528 +
   1.529 +	select(socketDescriptor+1, NULL, &fds, NULL, &tv);
   1.530 +
   1.531 +	int sentBytes = 0;
   1.532 +
   1.533 +	if (FD_ISSET(socketDescriptor, &fds))
   1.534 +	{
   1.535 +		sentBytes = send(socketDescriptor, buffer, len, 0);
   1.536 +	}
   1.537 +
   1.538 +	if (sentBytes == 0 || sentBytes == -1)
   1.539 +	{
   1.540 +		return 0;
   1.541 +	}
   1.542 +
   1.543 +	if (sentBytes < len)
   1.544 +	{
   1.545 +		return sentBytes + NonBlockingSend(socketDescriptor, buffer+sentBytes, len-sentBytes);
   1.546 +	}
   1.547 +
   1.548 +	return sentBytes;
   1.549 +}
   1.550 +
   1.551 +void FetcherThread::HandleUpdateStatus( UpdateStatus status, short version[4] )
   1.552 +{
   1.553 +	switch (status)
   1.554 +	{
   1.555 +		case Update_Unknown:
   1.556 +		case Update_Current:
   1.557 +		{
   1.558 +			break;
   1.559 +		}
   1.560 +
   1.561 +		case Update_NewBuild:
   1.562 +		{
   1.563 +			g_Logger.LogMessage("SourceMod Update: A new SVN build is available from sourcemod.net");
   1.564 +			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]);
   1.565 +			break;
   1.566 +		}
   1.567 +
   1.568 +		case Update_MinorAvailable:
   1.569 +		{
   1.570 +			g_Logger.LogMessage("SourceMod Update: An incremental minor release of SourceMod is now available from sourcemod.net");
   1.571 +			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   1.572 +			break;
   1.573 +		}
   1.574 +
   1.575 +		case Update_MajorAvailable:
   1.576 +		{
   1.577 +			g_Logger.LogMessage("SourceMod Update: An major release of SourceMod is now available from sourcemod.net");
   1.578 +			g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
   1.579 +			break;
   1.580 +		}
   1.581 +
   1.582 +		case Update_CriticalAvailable:
   1.583 +		{
   1.584 +			g_Logger.LogError("SourceMod Update: A new critical release of SourceMod is now available from sourcemod.net. It is strongly recommended that you update");
   1.585 +			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]);
   1.586 +			break;
   1.587 +		}
   1.588 +	}
   1.589 +}
   1.590 +
   1.591 +bool g_blockGameDataLoad = false;
   1.592 +
   1.593 +class InitFetch : public SMGlobalClass
   1.594 +{
   1.595 +public:
   1.596 +	void OnSourceModAllInitialized_Post()
   1.597 +	{
   1.598 +		char lock_path[PLATFORM_MAX_PATH];
   1.599 +		g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
   1.600 +
   1.601 +		if (g_LibSys.IsPathFile(lock_path) && g_LibSys.PathExists(lock_path))
   1.602 +		{
   1.603 +			g_Logger.LogError("sourcemod/data/temp/gamedata.lock file detected. This is most likely due to a crash during GameData updating - Blocking GameData loading");
   1.604 +			g_Logger.LogError("If this error persists delete the file manually");
   1.605 +			g_blockGameDataLoad = true;
   1.606 +		}
   1.607 +
   1.608 +		ThreadParams fetchThreadParams = ThreadParams();
   1.609 +		fetchThreadParams.prio = ThreadPrio_Low;
   1.610 +		g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
   1.611 +	}
   1.612 +
   1.613 +	ConfigResult OnSourceModConfigChanged(const char *key, 
   1.614 +		const char *value, 
   1.615 +		ConfigSource source,
   1.616 +		char *error, 
   1.617 +		size_t maxlength)
   1.618 +	{
   1.619 +		if (strcmp(key, "DisableAutoUpdate") == 0)
   1.620 +		{
   1.621 +			if (strcmp(value, "yes") == 0)
   1.622 +			{
   1.623 +				g_disableGameDataUpdate = true;
   1.624 +				return ConfigResult_Accept;
   1.625 +			}
   1.626 +			else if (strcmp(value, "no") == 0)
   1.627 +			{
   1.628 +				g_disableGameDataUpdate = false;
   1.629 +				return ConfigResult_Accept;
   1.630 +			}
   1.631 +
   1.632 +			return ConfigResult_Reject;
   1.633 +		}
   1.634 +
   1.635 +		if (strcmp(key, "ForceRestartAfterUpdate") == 0)
   1.636 +		{
   1.637 +			if (strcmp(value, "yes") == 0)
   1.638 +			{
   1.639 +				g_restartAfterUpdate = true;
   1.640 +				return ConfigResult_Accept;
   1.641 +			}
   1.642 +			else if (strcmp(value, "no") == 0)
   1.643 +			{
   1.644 +				g_restartAfterUpdate = false;
   1.645 +				return ConfigResult_Accept;
   1.646 +			}
   1.647 +
   1.648 +			return ConfigResult_Reject;
   1.649 +		}
   1.650 +
   1.651 +		return ConfigResult_Ignore;
   1.652 +	}
   1.653 +} g_InitFetch;
   1.654 +
   1.655 +CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
   1.656 +{
   1.657 +#if !defined ORANGEBOX_BUILD
   1.658 +	CCommand args;
   1.659 +#endif
   1.660 +
   1.661 +	if (args.ArgC() < 2)
   1.662 +	{
   1.663 +		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
   1.664 +		return;
   1.665 +	}
   1.666 +
   1.667 +	const char *file = args.Arg(1);
   1.668 +	if (!file || file[0] == '\0')
   1.669 +	{
   1.670 +		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
   1.671 +		return;
   1.672 +	}
   1.673 +
   1.674 +	SourceHook::CVector<FileData *>::iterator iter = g_FetchThread.filenames.begin();
   1.675 +
   1.676 +	FileData *curData;
   1.677 +
   1.678 +	while (iter != g_FetchThread.filenames.end())
   1.679 +	{
   1.680 +		curData = (*iter);
   1.681 +
   1.682 +		char fileName[30];
   1.683 +
   1.684 +		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), curData->filename->c_str());
   1.685 +
   1.686 +		if (strcmpi(fileName, file) == 0)
   1.687 +		{
   1.688 +			g_SMAPI->ConPrintf("MD5 Sum: %s\n", curData->checksum);
   1.689 +			return;
   1.690 +		}
   1.691 +
   1.692 +		iter++;
   1.693 +	}
   1.694 +
   1.695 +	g_SMAPI->ConPrint("File not found!\n");
   1.696 +}