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