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