core/GameDataFetcher.cpp
changeset 2479 44e8e76d4e53
parent 2400 76b7b4575e21
parent 2476 c21fd6e89ff5
child 2488 f7f1bbcc85e4
     1.1 --- a/core/GameDataFetcher.cpp	Sun Dec 28 03:15:15 2008 -0500
     1.2 +++ b/core/GameDataFetcher.cpp	Sun Dec 28 03:23:45 2008 -0500
     1.3 @@ -1,33 +1,33 @@
     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 + * vim: set ts=4 :
    1.35 + * =============================================================================
    1.36 + * SourceMod
    1.37 + * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
    1.38 + * =============================================================================
    1.39 + *
    1.40 + * This program is free software; you can redistribute it and/or modify it under
    1.41 + * the terms of the GNU General Public License, version 3.0, as published by the
    1.42 + * Free Software Foundation.
    1.43 + * 
    1.44 + * This program is distributed in the hope that it will be useful, but WITHOUT
    1.45 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    1.46 + * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    1.47 + * details.
    1.48 + *
    1.49 + * You should have received a copy of the GNU General Public License along with
    1.50 + * this program.  If not, see <http://www.gnu.org/licenses/>.
    1.51 + *
    1.52 + * As a special exception, AlliedModders LLC gives you permission to link the
    1.53 + * code of this program (as well as its derivative works) to "Half-Life 2," the
    1.54 + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    1.55 + * by the Valve Corporation.  You must obey the GNU General Public License in
    1.56 + * all respects for all other code used.  Additionally, AlliedModders LLC grants
    1.57 + * this exception to all derivative works.  AlliedModders LLC defines further
    1.58 + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    1.59 + * or <http://www.sourcemod.net/license.php>.
    1.60 + *
    1.61 + * Version: $Id$
    1.62 + */
    1.63  
    1.64  #include "GameDataFetcher.h"
    1.65  #include "bitbuf.h"
    1.66 @@ -42,8 +42,9 @@
    1.67  #include <netdb.h>
    1.68  #include <arpa/inet.h>
    1.69  
    1.70 -#define INVALID_SOCKET -1
    1.71 -#define closesocket close
    1.72 +#define INVALID_SOCKET		-1
    1.73 +#define closesocket 		close
    1.74 +#define WSAGetLastError()	errno
    1.75  #endif
    1.76  
    1.77  #include "sh_vector.h"
    1.78 @@ -63,21 +64,52 @@
    1.79  #include "TimerSys.h"
    1.80  #include "compat_wrappers.h"
    1.81  #include "sm_stringutil.h"
    1.82 +#include "md5.h"
    1.83 +#include "frame_hooks.h"
    1.84  
    1.85  #define QUERY_MAX_LENGTH 1024
    1.86  
    1.87 -BuildMD5ableBuffer g_MD5Builder;
    1.88 -FetcherThread g_FetchThread;
    1.89 +static BuildMD5ableBuffer g_MD5Builder;
    1.90 +static FetcherThread g_FetchThread;
    1.91  
    1.92 -FILE *logfile = NULL;
    1.93 +static FILE *logfile = NULL;
    1.94  
    1.95  bool g_disableGameDataUpdate = false;
    1.96 +
    1.97 +/**
    1.98 + * Note on this.  If we issue a reload and changelevel, my srcds.exe will emit 
    1.99 + *   Assertion Failed: !m_bServiceStarted
   1.100 + * on quit.  This seems like a non-issue, because before we just terminated the 
   1.101 + * server anyway.  If anyone notices and files a bug, we can look into it further.
   1.102 + */
   1.103  bool g_restartAfterUpdate = false;
   1.104  
   1.105 -int g_serverPort = 6500;
   1.106 -char g_serverAddress[100] = "hayate.alliedmods.net";
   1.107 +static bool was_level_started = false;
   1.108 +static int g_serverPort = 6500;
   1.109 +static char g_serverAddress[100] = "smupdate.alliedmods.net";
   1.110  
   1.111 -void FetcherThread::RunThread( IThreadHandle *pHandle )
   1.112 +static void _ForceRestart(void *data)
   1.113 +{
   1.114 +	char cmd[300];
   1.115 +	g_Logger.LogMessage("Automatically restarting SourceMod after a successful gamedata update.");
   1.116 +	UTIL_Format(cmd, sizeof(cmd), "meta unload %d\n", g_PLID);
   1.117 +	engine->ServerCommand(cmd);
   1.118 +	UTIL_Format(cmd, sizeof(cmd), "changelevel \"%s\"\n", STRING(gpGlobals->mapname));
   1.119 +	engine->ServerCommand(cmd);
   1.120 +	UTIL_Format(cmd, sizeof(cmd), "echo SourceMod restarted after gamedata update.\n");
   1.121 +	engine->ServerCommand(cmd);
   1.122 +}
   1.123 +
   1.124 +static void ForceRestart()
   1.125 +{
   1.126 +	FrameAction action;
   1.127 +
   1.128 +	action.action = _ForceRestart;
   1.129 +	action.data = NULL;
   1.130 +	AddFrameAction(action);
   1.131 +}
   1.132 +
   1.133 +void FetcherThread::RunThread(IThreadHandle *pHandle)
   1.134  {
   1.135  	char lock_path[PLATFORM_MAX_PATH];
   1.136  	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp");
   1.137 @@ -85,8 +117,6 @@
   1.138  
   1.139  	g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
   1.140  
   1.141 -	g_Logger.LogMessage("Starting Gamedata update fetcher... please report problems to bugs.alliedmods.net");
   1.142 -
   1.143  	char log_path[PLATFORM_MAX_PATH];
   1.144  	g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata");
   1.145  
   1.146 @@ -102,7 +132,7 @@
   1.147  
   1.148  	if (!logfile)
   1.149  	{
   1.150 -		g_Logger.LogError("Failed to create GameData log file");
   1.151 +		/* :( */
   1.152  		return;
   1.153  	}
   1.154  
   1.155 @@ -120,19 +150,16 @@
   1.156  
   1.157  	if (len == 0)
   1.158  	{
   1.159 -		g_Logger.LogToOpenFile(logfile, "Query Writing failed");
   1.160 -
   1.161 +		g_Logger.LogToFileOnly(logfile, "Could not build gamedata query!");
   1.162  		fclose(logfile);
   1.163  		unlink(lock_path);
   1.164  		return;
   1.165  	}
   1.166  
   1.167 +	/* We check this late so we have the MD5 sums available.  This may change in the future. */
   1.168  	if (g_disableGameDataUpdate)
   1.169  	{
   1.170 -#ifdef DEBUG
   1.171 -		g_Logger.LogMessage("Skipping GameData Query due to DisableAutoUpdate being set to true");
   1.172 -#endif
   1.173 -
   1.174 +		g_Logger.LogToFileOnly(logfile, "Skipping gamedata fetcher (DisableAutoUpdate set)");
   1.175  		fclose(logfile);
   1.176  		unlink(lock_path);
   1.177  		return;
   1.178 @@ -150,13 +177,15 @@
   1.179  
   1.180  	int sent = SendData(socketDescriptor, query, len);
   1.181  
   1.182 -#ifdef DEBUG
   1.183 -	g_Logger.LogToOpenFile(logfile, "Sent Query!");
   1.184 -#endif
   1.185 +IF_DEBUG_SPEW
   1.186 +	g_Logger.LogToFileOnly(logfile, "Sent gamedata query");
   1.187 +ENDIF_DEBUG_SPEW
   1.188  
   1.189  	if (sent == 0)
   1.190  	{
   1.191 -		g_Logger.LogToOpenFile(logfile, "Failed to send gamedata query data to remote host");
   1.192 +IF_DEBUG_SPEW
   1.193 +		g_Logger.LogToFileOnly(logfile, "Failed to send gamedata query data to remote host");
   1.194 +ENDIF_DEBUG_SPEW
   1.195  
   1.196  		closesocket(socketDescriptor);
   1.197  		fclose(logfile);
   1.198 @@ -172,12 +201,41 @@
   1.199  	unlink(lock_path);
   1.200  }
   1.201  
   1.202 -void FetcherThread::OnTerminate( IThreadHandle *pHandle, bool cancel )
   1.203 +void FetcherThread::OnTerminate(IThreadHandle *pHandle, bool cancel)
   1.204  {
   1.205  	g_blockGameDataLoad = false;
   1.206 +
   1.207 +	if (cancel)
   1.208 +	{
   1.209 +		return;
   1.210 +	}
   1.211 +
   1.212 +	if (wasSuccess)
   1.213 +	{
   1.214 +		HandleUpdateStatus(updateStatus, build);
   1.215 +
   1.216 +		if (needsRestart)
   1.217 +		{
   1.218 +			if (g_restartAfterUpdate)
   1.219 +			{
   1.220 +				if (was_level_started)
   1.221 +				{
   1.222 +					ForceRestart();
   1.223 +				}
   1.224 +			}
   1.225 +			else
   1.226 +			{
   1.227 +				g_Logger.LogMessage("Your gamedata files have been updated, please restart your server.");
   1.228 +			}
   1.229 +		}
   1.230 +	}
   1.231 +	else if (!g_disableGameDataUpdate)
   1.232 +	{
   1.233 +		g_Logger.LogError("An error occurred in the gamedata fetcher, see your gamedata log files for more information.");
   1.234 +	}
   1.235  }
   1.236  
   1.237 -int FetcherThread::BuildGameDataQuery( char *buffer, int maxlen )
   1.238 +int FetcherThread::BuildGameDataQuery(char *buffer, int maxlen)
   1.239  {
   1.240  	char gamedata_path[PLATFORM_MAX_PATH];
   1.241  	g_SourceMod.BuildPath(Path_SM, gamedata_path, sizeof(gamedata_path), "gamedata");
   1.242 @@ -206,45 +264,42 @@
   1.243  		{
   1.244  			const char *name = dir->GetEntryName();
   1.245  			size_t len = strlen(name);
   1.246 -			if (len >= 4
   1.247 -				&& strcmp(&name[len-4], ".txt") == 0)
   1.248 +			if (len >= 4 && strcmp(&name[len-4], ".txt") == 0)
   1.249  			{
   1.250 +				MD5 md5;
   1.251 +				SMCError err;
   1.252 +				SMCStates states;
   1.253 +				unsigned char raw[16];
   1.254  				char file[PLATFORM_MAX_PATH];
   1.255  
   1.256  				g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
   1.257  
   1.258 -				SMCStates states;
   1.259 -				if (g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states) == SMCError_Okay)
   1.260 +				g_MD5Builder.checksum = &md5;
   1.261 +				if ((err = g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states)) == SMCError_Okay)
   1.262  				{
   1.263 -					unsigned char *md5 = g_MD5Builder.GetMD5();
   1.264 -					if (md5 != NULL)
   1.265 -					{
   1.266 -						(uint8_t)buffer[10]++; //Increment the file counter
   1.267 -						Writer.WriteBytes(md5, 16);
   1.268 -
   1.269 -						g_Logger.LogToOpenFile(logfile, "%s - \"%s\"", file, g_MD5Builder.GetMD5String());
   1.270 +					md5.raw_digest(raw);
   1.271 +					(uint8_t)buffer[10]++; //Increment the file counter
   1.272 +					Writer.WriteBytes(raw, 16);
   1.273  
   1.274 -						FileData *data = new FileData();
   1.275 -						data->filename = new SourceHook::String(file);
   1.276 -						memcpy(data->checksum, g_MD5Builder.GetMD5String(), 33);
   1.277 -						filenames.push_back(data);
   1.278 -					}
   1.279 -					else
   1.280 -					{
   1.281 -#ifdef DEBUG
   1.282 -						g_Logger.LogToOpenFile(logfile, "%s no md5?", file);
   1.283 -#endif
   1.284 -					}
   1.285 +					FileData *data = new FileData();
   1.286 +					data->filename = new SourceHook::String(file);
   1.287 +					md5.hex_digest(data->checksum);
   1.288 +					filenames.push_back(data);
   1.289 +
   1.290 +IF_DEBUG_SPEW
   1.291 +					g_Logger.LogToFileOnly(logfile, "Parsed file: %s as %s", file, data->checksum);
   1.292 +ENDIF_DEBUG_SPEW
   1.293 +
   1.294  				}
   1.295  				else
   1.296  				{
   1.297 -#ifdef DEBUG
   1.298 -					g_Logger.LogToOpenFile(logfile, "%s failed!", file);
   1.299 -#endif
   1.300 +IF_DEBUG_SPEW
   1.301 +					const char *error = g_TextParser.GetSMCErrorString(err);
   1.302 +					g_Logger.LogToFileOnly(logfile, "Parsing of file %s failed: %s", file, error);
   1.303 +ENDIF_DEBUG_SPEW
   1.304  				}
   1.305  			}
   1.306  		}
   1.307 -
   1.308  		dir->NextEntry();
   1.309  	}
   1.310  
   1.311 @@ -253,17 +308,16 @@
   1.312  
   1.313  int FetcherThread::ConnectSocket()
   1.314  {
   1.315 -	struct protoent *ptrp;
   1.316 -	ptrp = getprotobyname("tcp");
   1.317 -
   1.318 -#ifdef WIN32
   1.319 +#if defined PLATFORM_WINDOWS
   1.320  	WSADATA wsaData;
   1.321  	WSAStartup(0x0101, &wsaData);
   1.322  #endif
   1.323  
   1.324 -	if (ptrp == NULL) 
   1.325 +	struct protoent *ptrp;
   1.326 +	
   1.327 +	if ((ptrp = getprotobyname("tcp")) == NULL) 
   1.328  	{
   1.329 -		g_Logger.LogToOpenFile(logfile, "Failed to find TCP");
   1.330 +		g_Logger.LogToFileOnly(logfile, "Error: Failed to find TCP protocol");
   1.331  		return INVALID_SOCKET;
   1.332  	}
   1.333  
   1.334 @@ -271,12 +325,9 @@
   1.335  
   1.336  	if (socketDescriptor == INVALID_SOCKET)
   1.337  	{
   1.338 -		//bugger aye?
   1.339 -#ifdef WIN32
   1.340 -		g_Logger.LogToOpenFile(logfile, "Failed to create a new socket - Error %i", WSAGetLastError());
   1.341 -#else
   1.342 -		g_Logger.LogToOpenFile(logfile, "Failed to create a new socket - Error %i", errno);
   1.343 -#endif
   1.344 +		char error[255];
   1.345 +		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   1.346 +		g_Logger.LogToFileOnly(logfile, "Error: Failed to create socket: %s", error);
   1.347  		closesocket(socketDescriptor);
   1.348  		return INVALID_SOCKET;
   1.349  	}
   1.350 @@ -293,7 +344,7 @@
   1.351  	{
   1.352  		if ((local_addr.sin_addr.s_addr = inet_addr(g_serverAddress)) == INADDR_NONE)
   1.353  		{
   1.354 -			g_Logger.LogToOpenFile(logfile, "Couldn't locate address");
   1.355 +			g_Logger.LogToFileOnly(logfile, "Couldn't locate address: %s", g_serverAddress);
   1.356  			closesocket(socketDescriptor);
   1.357  			return INVALID_SOCKET;
   1.358  		}
   1.359 @@ -305,11 +356,9 @@
   1.360  
   1.361  	if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
   1.362  	{
   1.363 -#ifdef WIN32
   1.364 -		g_Logger.LogToOpenFile(logfile, "Couldn't connect - Error %i", WSAGetLastError());
   1.365 -#else
   1.366 -		g_Logger.LogToOpenFile(logfile, "Couldn't connect - Error %i", errno);
   1.367 -#endif
   1.368 +		char error[255];
   1.369 +		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   1.370 +		g_Logger.LogToFileOnly(logfile, "Couldn't connect to %s: %s", g_serverAddress, error);
   1.371  		closesocket(socketDescriptor);
   1.372  		return INVALID_SOCKET;
   1.373  	}
   1.374 @@ -320,58 +369,59 @@
   1.375  void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
   1.376  {
   1.377  	char buffer[50];
   1.378 -#ifdef DEBUG
   1.379 -	g_Logger.LogToOpenFile(logfile, "Waiting for reply!");
   1.380 -#endif
   1.381 +
   1.382 +IF_DEBUG_SPEW
   1.383 +	g_Logger.LogToFileOnly(logfile, "Waiting for reply!");
   1.384 +ENDIF_DEBUG_SPEW
   1.385  
   1.386  	//Read in the header bytes
   1.387  	int returnLen = RecvData(socketDescriptor, buffer, 12);
   1.388  
   1.389 -#ifdef DEBUG
   1.390 -	g_Logger.LogToOpenFile(logfile, "Recv Completed");
   1.391 -#endif
   1.392  
   1.393  	if (returnLen == 0)
   1.394  	{
   1.395 -#ifdef DEBUG
   1.396 -		g_Logger.LogToOpenFile(logfile, ",but it failed.");
   1.397 -#endif
   1.398 -		/* Timeout or fail? */
   1.399 +		char error[255];
   1.400 +		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   1.401 +		g_Logger.LogToFileOnly(logfile, "Did not receive reply: %s", error);
   1.402  		return;
   1.403  	}
   1.404  
   1.405 -#ifdef DEBUG
   1.406 -	g_Logger.LogToOpenFile(logfile, "Received Header!");
   1.407 -#endif
   1.408 +IF_DEBUG_SPEW
   1.409 +	g_Logger.LogToFileOnly(logfile, "Received Header!");
   1.410 +ENDIF_DEBUG_SPEW
   1.411  
   1.412  	bf_read Reader = bf_read("GameDataQuery", buffer, 12);
   1.413  
   1.414  	if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
   1.415  	{
   1.416 -		g_Logger.LogToOpenFile(logfile, "Unknown Query Response");
   1.417 +IF_DEBUG_SPEW
   1.418 +		g_Logger.LogToFileOnly(logfile, "Unknown Query Response");
   1.419 +ENDIF_DEBUG_SPEW
   1.420  		return;
   1.421  	}
   1.422  
   1.423 -	UpdateStatus updateStatus = (UpdateStatus)Reader.ReadByte();
   1.424 -
   1.425 -	short build[4] = {0,0,0,0};
   1.426 +	updateStatus = (UpdateStatus)Reader.ReadByte();
   1.427  
   1.428  	build[0] = Reader.ReadShort();
   1.429  	build[1] = Reader.ReadShort();
   1.430  	build[2] = Reader.ReadShort();
   1.431  	build[3] = Reader.ReadShort();
   1.432  
   1.433 -#ifdef DEBUG
   1.434 -	g_Logger.LogToOpenFile(logfile, "Update Status: %i - Latest %i.%i.%i.%i", updateStatus, build[0], build[1], build[2], build[3]);
   1.435 -#endif
   1.436 -
   1.437 -	HandleUpdateStatus(updateStatus, build);
   1.438 +IF_DEBUG_SPEW
   1.439 +	g_Logger.LogToFileOnly(logfile,
   1.440 +						   "Update Status: %i - Latest %i.%i.%i.%i",
   1.441 +						   updateStatus,
   1.442 +						   build[0],
   1.443 +						   build[1],
   1.444 +						   build[2],
   1.445 +						   build[3]);
   1.446 +ENDIF_DEBUG_SPEW
   1.447  
   1.448  	int changedFiles = Reader.ReadByte();
   1.449  
   1.450 -#ifdef DEBUG
   1.451 -	g_Logger.LogToOpenFile(logfile, "Files to download: %i", changedFiles);
   1.452 -#endif
   1.453 +IF_DEBUG_SPEW
   1.454 +	g_Logger.LogToFileOnly(logfile, "Files to download: %i", changedFiles);
   1.455 +ENDIF_DEBUG_SPEW
   1.456  
   1.457  	for (int i=0; i<changedFiles; i++)
   1.458  	{
   1.459 @@ -389,9 +439,9 @@
   1.460  		int index = Reader.ReadByte();
   1.461  		int tempLen = Reader.ReadUBitLong(32);
   1.462  
   1.463 -#ifdef DEBUG
   1.464 -		g_Logger.LogToOpenFile(logfile, "File index %i and length %i", index, tempLen);
   1.465 -#endif
   1.466 +IF_DEBUG_SPEW
   1.467 +		g_Logger.LogToFileOnly(logfile, "File index %i and length %i", index, tempLen);
   1.468 +ENDIF_DEBUG_SPEW
   1.469  
   1.470  		void *memPtr;
   1.471  		memtable->CreateMem(tempLen+1, &memPtr);
   1.472 @@ -399,9 +449,9 @@
   1.473  		//Read the contents of our file into the memtable
   1.474  		returnLen = RecvData(socketDescriptor, (char *)memPtr, tempLen);
   1.475  
   1.476 -#ifdef DEBUG
   1.477 -		g_Logger.LogToOpenFile(logfile, "Recieved %i bytes", returnLen);
   1.478 -#endif
   1.479 +IF_DEBUG_SPEW
   1.480 +		g_Logger.LogToFileOnly(logfile, "Received %i bytes", returnLen);
   1.481 +ENDIF_DEBUG_SPEW
   1.482  
   1.483  		if (returnLen == 0)
   1.484  		{
   1.485 @@ -412,7 +462,7 @@
   1.486  		((unsigned char *)memPtr)[tempLen] = '\0';
   1.487  
   1.488  		FileData *data = filenames.at(index);
   1.489 -		const char* filename;
   1.490 +		const char *filename;
   1.491  		if (data != NULL)
   1.492  		{
   1.493  			filename = data->filename->c_str();
   1.494 @@ -421,12 +471,12 @@
   1.495  
   1.496  			if (fp)
   1.497  			{
   1.498 -				fprintf(fp, (const char *)memPtr);
   1.499 +				fprintf(fp, "%s", (const char *)memPtr);
   1.500  				fclose(fp);
   1.501  			}
   1.502  			else
   1.503  			{
   1.504 -				g_Logger.LogToOpenFile(logfile, "Failed to open file \"%s\"", filename);
   1.505 +				g_Logger.LogToFileOnly(logfile, "Failed to open file \"%s\" for writing", filename);
   1.506  			}
   1.507  		}
   1.508  		else
   1.509 @@ -436,21 +486,18 @@
   1.510  
   1.511  		memtable->Reset();
   1.512  
   1.513 -#ifdef DEBUG
   1.514 -		g_Logger.LogToOpenFile(logfile, "Updated File %s", filename);
   1.515 -#endif
   1.516 +		g_Logger.LogToFileOnly(logfile, "Updated file: %s", filename);
   1.517  	}
   1.518  
   1.519 -#ifdef DEBUG
   1.520 -	g_Logger.LogToOpenFile(logfile, "File Downloads Completed!");
   1.521 -#endif
   1.522 +IF_DEBUG_SPEW
   1.523 +	g_Logger.LogToFileOnly(logfile, "File Downloads Completed!");
   1.524 +ENDIF_DEBUG_SPEW
   1.525  
   1.526 -	bool needsRestart = false;
   1.527 +	needsRestart = false;
   1.528  
   1.529  	if (changedFiles > 0)
   1.530  	{
   1.531  		needsRestart = true;
   1.532 -		g_Logger.LogMessage("New GameData Files have been downloaded to your gamedata directory. Please restart your server for these to take effect");
   1.533  	}
   1.534  
   1.535  	//Read changed file count
   1.536 @@ -458,8 +505,9 @@
   1.537  
   1.538  	if (returnLen == 0)
   1.539  	{
   1.540 -		/* Timeout or fail? */
   1.541 -		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown count");
   1.542 +		char error[255];
   1.543 +		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   1.544 +		g_Logger.LogToFileOnly(logfile, "Did not receive count reply: %s", error);
   1.545  		return;
   1.546  	}
   1.547  
   1.548 @@ -467,28 +515,27 @@
   1.549  
   1.550  	changedFiles = Reader.ReadByte();
   1.551  
   1.552 -	if (changedFiles == 0)
   1.553 +	if (changedFiles < 1)
   1.554  	{
   1.555 -#ifdef DEBUG
   1.556 -		g_Logger.LogToOpenFile(logfile, "No unknown files. We're all done");
   1.557 -#endif
   1.558 +IF_DEBUG_SPEW
   1.559 +		g_Logger.LogToFileOnly(logfile, "No unknown files. We're all done");
   1.560 +ENDIF_DEBUG_SPEW
   1.561  		return;
   1.562  	}
   1.563  
   1.564  	char *changedFileIndexes = new char[changedFiles];
   1.565  
   1.566 -#ifdef DEBUG
   1.567 -	g_Logger.LogToOpenFile(logfile, "%i Files were unknown", changedFiles);
   1.568 -#endif
   1.569 +IF_DEBUG_SPEW
   1.570 +	g_Logger.LogToFileOnly(logfile, "%i files were unknown", changedFiles);
   1.571 +ENDIF_DEBUG_SPEW
   1.572  
   1.573  	returnLen = RecvData(socketDescriptor, changedFileIndexes, changedFiles);
   1.574  
   1.575  	if (returnLen == 0)
   1.576  	{
   1.577 -		/* Timeout or fail? */
   1.578 -#ifdef DEBUG
   1.579 -		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown list");
   1.580 -#endif
   1.581 +		char error[255];
   1.582 +		g_LibSys.GetPlatformErrorEx(WSAGetLastError(), error, sizeof(error));
   1.583 +		g_Logger.LogToFileOnly(logfile, "Did not receive list reply: %s", error);
   1.584  		return;
   1.585  	}
   1.586  
   1.587 @@ -511,21 +558,17 @@
   1.588  		}
   1.589  
   1.590  		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
   1.591 -#ifdef DEBUG
   1.592 -		g_Logger.LogToOpenFile(logfile, "Unknown File %i : %s", index, fileName);
   1.593 -#endif
   1.594 +IF_DEBUG_SPEW
   1.595 +		g_Logger.LogToFileOnly(logfile, "Unknown File %i : %s", index, fileName);
   1.596 +ENDIF_DEBUG_SPEW
   1.597  	}
   1.598  
   1.599  	delete [] changedFileIndexes;
   1.600  
   1.601 -	if (needsRestart && g_restartAfterUpdate)
   1.602 -	{
   1.603 -		g_Logger.LogMessage("Automatically restarting server after a successful gamedata update!");
   1.604 -		engine->ServerCommand("quit\n");
   1.605 -	}
   1.606 +	wasSuccess = true;
   1.607  }
   1.608  
   1.609 -int FetcherThread::RecvData( int socketDescriptor, char *buffer, int len )
   1.610 +int FetcherThread::RecvData(int socketDescriptor, char *buffer, int len)
   1.611  {
   1.612  	fd_set fds;
   1.613  	struct timeval tv;
   1.614 @@ -564,7 +607,7 @@
   1.615  	return bytesReceivedTotal;	
   1.616  }
   1.617  
   1.618 -int FetcherThread::SendData( int socketDescriptor, char *buffer, int len )
   1.619 +int FetcherThread::SendData(int socketDescriptor, char *buffer, int len)
   1.620  {
   1.621  	fd_set fds;
   1.622  	struct timeval tv;
   1.623 @@ -599,7 +642,7 @@
   1.624  	return sentBytesTotal;
   1.625  }
   1.626  
   1.627 -void FetcherThread::HandleUpdateStatus( UpdateStatus status, short version[4] )
   1.628 +void FetcherThread::HandleUpdateStatus(UpdateStatus status, short version[4])
   1.629  {
   1.630  	switch (status)
   1.631  	{
   1.632 @@ -611,14 +654,12 @@
   1.633  
   1.634  		case Update_NewBuild:
   1.635  		{
   1.636 -			g_Logger.LogMessage("SourceMod Update: A new Mercurial build is available from sourcemod.net");
   1.637 -			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.638  			break;
   1.639  		}
   1.640  
   1.641  		case Update_MinorAvailable:
   1.642  		{
   1.643 -			g_Logger.LogMessage("SourceMod Update: An incremental minor release of SourceMod is now available from sourcemod.net");
   1.644 +			g_Logger.LogMessage("SourceMod Update: A new release of SourceMod is now available from sourcemod.net");
   1.645  			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.646  			break;
   1.647  		}
   1.648 @@ -632,14 +673,15 @@
   1.649  
   1.650  		case Update_CriticalAvailable:
   1.651  		{
   1.652 -			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.653 -			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.654 +			g_Logger.LogError("SourceMod Update: A critical SourceMod release is available from sourcemod.net. It is strongly recommended that you update!");
   1.655 +			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.656  			break;
   1.657  		}
   1.658  	}
   1.659  }
   1.660  
   1.661  bool g_blockGameDataLoad = false;
   1.662 +static IThreadHandle *fetch_thread_hndl;
   1.663  
   1.664  class InitFetch : public SMGlobalClass
   1.665  {
   1.666 @@ -658,7 +700,30 @@
   1.667  
   1.668  		ThreadParams fetchThreadParams = ThreadParams();
   1.669  		fetchThreadParams.prio = ThreadPrio_Low;
   1.670 -		g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
   1.671 +		fetch_thread_hndl = g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
   1.672 +	}
   1.673 +
   1.674 +	void OnSourceModShutdown()
   1.675 +	{
   1.676 +		fetch_thread_hndl->WaitForThread();
   1.677 +		fetch_thread_hndl->DestroyThis();
   1.678 +	}
   1.679 +
   1.680 +	void OnSourceModLevelActivated()
   1.681 +	{
   1.682 +		was_level_started = true;
   1.683 +
   1.684 +		if (g_restartAfterUpdate && 
   1.685 +			g_FetchThread.wasSuccess && 
   1.686 +			g_FetchThread.needsRestart)
   1.687 +		{
   1.688 +			ForceRestart();
   1.689 +		}
   1.690 +	}
   1.691 +
   1.692 +	void OnSourceModLevelEnd()
   1.693 +	{
   1.694 +		was_level_started = false;
   1.695  	}
   1.696  
   1.697  	ConfigResult OnSourceModConfigChanged(const char *key, 
   1.698 @@ -667,14 +732,14 @@
   1.699  		char *error, 
   1.700  		size_t maxlength)
   1.701  	{
   1.702 -		if (strcmp(key, "DisableAutoUpdate") == 0)
   1.703 +		if (strcasecmp(key, "DisableAutoUpdate") == 0)
   1.704  		{
   1.705 -			if (strcmp(value, "yes") == 0)
   1.706 +			if (strcasecmp(value, "yes") == 0)
   1.707  			{
   1.708  				g_disableGameDataUpdate = true;
   1.709  				return ConfigResult_Accept;
   1.710  			}
   1.711 -			else if (strcmp(value, "no") == 0)
   1.712 +			else if (strcasecmp(value, "no") == 0)
   1.713  			{
   1.714  				g_disableGameDataUpdate = false;
   1.715  				return ConfigResult_Accept;
   1.716 @@ -683,14 +748,14 @@
   1.717  			return ConfigResult_Reject;
   1.718  		}
   1.719  
   1.720 -		if (strcmp(key, "ForceRestartAfterUpdate") == 0)
   1.721 +		if (strcasecmp(key, "ForceRestartAfterUpdate") == 0)
   1.722  		{
   1.723 -			if (strcmp(value, "yes") == 0)
   1.724 +			if (strcasecmp(value, "yes") == 0)
   1.725  			{
   1.726  				g_restartAfterUpdate = true;
   1.727  				return ConfigResult_Accept;
   1.728  			}
   1.729 -			else if (strcmp(value, "no") == 0)
   1.730 +			else if (strcasecmp(value, "no") == 0)
   1.731  			{
   1.732  				g_restartAfterUpdate = false;
   1.733  				return ConfigResult_Accept;
   1.734 @@ -699,14 +764,14 @@
   1.735  			return ConfigResult_Reject;
   1.736  		}
   1.737  
   1.738 -		if (strcmp(key, "AutoUpdateServer") == 0)
   1.739 +		if (strcasecmp(key, "AutoUpdateServer") == 0)
   1.740  		{
   1.741  			UTIL_Format(g_serverAddress, sizeof(g_serverAddress), "%s", value);
   1.742  
   1.743  			return ConfigResult_Accept;
   1.744  		}
   1.745  
   1.746 -		if (strcmp(key, "AutoUpdatePort") == 0)
   1.747 +		if (strcasecmp(key, "AutoUpdatePort") == 0)
   1.748  		{
   1.749  			int port = atoi(value);
   1.750  
   1.751 @@ -724,6 +789,55 @@
   1.752  	}
   1.753  } g_InitFetch;
   1.754  
   1.755 +BuildMD5ableBuffer::BuildMD5ableBuffer()
   1.756 +{
   1.757 +	stringTable = new BaseStringTable(2048);
   1.758 +}
   1.759 +
   1.760 +BuildMD5ableBuffer::~BuildMD5ableBuffer()
   1.761 +{
   1.762 +	delete stringTable;
   1.763 +}
   1.764 +
   1.765 +void BuildMD5ableBuffer::ReadSMC_ParseStart()
   1.766 +{
   1.767 +	stringTable->Reset();
   1.768 +}
   1.769 +
   1.770 +SMCResult BuildMD5ableBuffer::ReadSMC_KeyValue(const SMCStates *states,
   1.771 +											   const char *key,
   1.772 +											   const char *value)
   1.773 +{
   1.774 +	stringTable->AddString(key);
   1.775 +	stringTable->AddString(value);
   1.776 +
   1.777 +	return SMCResult_Continue;
   1.778 +}
   1.779 +
   1.780 +SMCResult BuildMD5ableBuffer::ReadSMC_NewSection(const SMCStates *states, const char *name)
   1.781 +{
   1.782 +	stringTable->AddString(name);
   1.783 +
   1.784 +	return SMCResult_Continue;
   1.785 +}
   1.786 +
   1.787 +void BuildMD5ableBuffer::ReadSMC_ParseEnd(bool halted, bool failed)
   1.788 +{
   1.789 +	if (halted || failed)
   1.790 +	{
   1.791 +		return;
   1.792 +	}
   1.793 +
   1.794 +	void *data = stringTable->GetMemTable()->GetAddress(0);
   1.795 +
   1.796 +	if (data != NULL)
   1.797 +	{
   1.798 +		checksum->update((unsigned char *)data, stringTable->GetMemTable()->GetActualMemUsed());
   1.799 +	}
   1.800 +
   1.801 +	checksum->finalize();
   1.802 +}
   1.803 +
   1.804  CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
   1.805  {
   1.806  #if SOURCE_ENGINE == SE_EPISODEONE
   1.807 @@ -766,3 +880,26 @@
   1.808  
   1.809  	g_SMAPI->ConPrint("File not found!\n");
   1.810  }
   1.811 +
   1.812 +FetcherThread::~FetcherThread()
   1.813 +{
   1.814 +	SourceHook::CVector<FileData *>::iterator iter = filenames.begin();
   1.815 +
   1.816 +	FileData *curData;
   1.817 +
   1.818 +	while (iter != filenames.end())
   1.819 +	{
   1.820 +		curData = (*iter);
   1.821 +		delete curData->filename;
   1.822 +		delete curData;
   1.823 +		iter = filenames.erase(iter);
   1.824 +	}
   1.825 +}
   1.826 +
   1.827 +FetcherThread::FetcherThread() 
   1.828 +{
   1.829 +	memtable = new BaseMemTable(4096);
   1.830 +	wasSuccess = false;
   1.831 +	needsRestart = false;
   1.832 +}
   1.833 +