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