core/GameDataFetcher.cpp
author Scott Ehlert <ds@alliedmods.net>
Fri Nov 14 09:18:30 2008 -0600 (2008-11-14)
changeset 2400 76b7b4575e21
parent 2371 323fbf1ab608
child 2479 44e8e76d4e53
permissions -rw-r--r--
Added preliminary support for Left 4 Dead; some things may not yet work.
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
pred@2142
    45
#define INVALID_SOCKET -1
pred@2142
    46
#define closesocket close
pred@2142
    47
#endif
pred@2142
    48
pred@2142
    49
#include "sh_vector.h"
pred@2142
    50
#include "sh_string.h"
pred@2142
    51
#include "sm_version.h"
pred@2142
    52
ds@2400
    53
#if SOURCE_ENGINE == SE_LEFT4DEAD
ds@2400
    54
#include "convar_sm_l4d.h"
ds@2400
    55
#elif SOURCE_ENGINE == SE_ORANGEBOX
pred@2142
    56
#include "convar_sm_ob.h"
pred@2142
    57
#else
pred@2142
    58
#include "convar_sm.h"
pred@2142
    59
#endif
pred@2142
    60
pred@2142
    61
#include "sourcemm_api.h"
pred@2142
    62
#include "time.h"
pred@2142
    63
#include "TimerSys.h"
pred@2150
    64
#include "compat_wrappers.h"
pred@2247
    65
#include "sm_stringutil.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;
pred@2247
    78
char g_serverAddress[100] = "hayate.alliedmods.net";
pred@2247
    79
pred@2142
    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
#ifdef DEBUG
pred@2367
   133
		g_Logger.LogMessage("Skipping GameData Query due to DisableAutoUpdate being set to true");
pred@2367
   134
#endif
pred@2367
   135
pred@2367
   136
		fclose(logfile);
pred@2367
   137
		unlink(lock_path);
pred@2367
   138
		return;
pred@2367
   139
	}
pred@2367
   140
pred@2142
   141
	/* Create a new socket for this connection */
pred@2142
   142
	int socketDescriptor = ConnectSocket();
pred@2142
   143
pred@2142
   144
	if (socketDescriptor == INVALID_SOCKET)
pred@2142
   145
	{
pred@2146
   146
		fclose(logfile);
pred@2146
   147
		unlink(lock_path);
pred@2142
   148
		return;
pred@2142
   149
	}
pred@2142
   150
pred@2367
   151
	int sent = SendData(socketDescriptor, query, len);
pred@2146
   152
pred@2367
   153
#ifdef DEBUG
pred@2142
   154
	g_Logger.LogToOpenFile(logfile, "Sent Query!");
pred@2367
   155
#endif
pred@2142
   156
pred@2142
   157
	if (sent == 0)
pred@2142
   158
	{
pred@2367
   159
		g_Logger.LogToOpenFile(logfile, "Failed to send gamedata query data to remote host");
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
pred@2142
   175
void FetcherThread::OnTerminate( IThreadHandle *pHandle, bool cancel )
pred@2142
   176
{
pred@2186
   177
	g_blockGameDataLoad = false;
pred@2142
   178
}
pred@2142
   179
pred@2142
   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);
pred@2142
   209
			if (len >= 4
pred@2142
   210
				&& strcmp(&name[len-4], ".txt") == 0)
pred@2142
   211
			{
pred@2142
   212
				char file[PLATFORM_MAX_PATH];
pred@2142
   213
pred@2142
   214
				g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
pred@2142
   215
pred@2142
   216
				SMCStates states;
pred@2142
   217
				if (g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states) == SMCError_Okay)
pred@2142
   218
				{
pred@2142
   219
					unsigned char *md5 = g_MD5Builder.GetMD5();
pred@2142
   220
					if (md5 != NULL)
pred@2142
   221
					{
pred@2142
   222
						(uint8_t)buffer[10]++; //Increment the file counter
pred@2142
   223
						Writer.WriteBytes(md5, 16);
pred@2142
   224
pred@2142
   225
						g_Logger.LogToOpenFile(logfile, "%s - \"%s\"", file, g_MD5Builder.GetMD5String());
pred@2142
   226
pred@2142
   227
						FileData *data = new FileData();
pred@2142
   228
						data->filename = new SourceHook::String(file);
pred@2142
   229
						memcpy(data->checksum, g_MD5Builder.GetMD5String(), 33);
pred@2142
   230
						filenames.push_back(data);
pred@2142
   231
					}
pred@2142
   232
					else
pred@2142
   233
					{
pred@2367
   234
#ifdef DEBUG
pred@2142
   235
						g_Logger.LogToOpenFile(logfile, "%s no md5?", file);
pred@2367
   236
#endif
pred@2142
   237
					}
pred@2142
   238
				}
pred@2142
   239
				else
pred@2142
   240
				{
pred@2367
   241
#ifdef DEBUG
pred@2142
   242
					g_Logger.LogToOpenFile(logfile, "%s failed!", file);
pred@2367
   243
#endif
pred@2142
   244
				}
pred@2142
   245
			}
pred@2142
   246
		}
pred@2142
   247
pred@2142
   248
		dir->NextEntry();
pred@2142
   249
	}
pred@2142
   250
pred@2142
   251
	return Writer.GetNumBytesWritten();
pred@2142
   252
}
pred@2142
   253
pred@2142
   254
int FetcherThread::ConnectSocket()
pred@2142
   255
{
pred@2142
   256
	struct protoent *ptrp;
pred@2142
   257
	ptrp = getprotobyname("tcp");
pred@2142
   258
pred@2142
   259
#ifdef WIN32
pred@2142
   260
	WSADATA wsaData;
pred@2142
   261
	WSAStartup(0x0101, &wsaData);
pred@2142
   262
#endif
pred@2142
   263
pred@2142
   264
	if (ptrp == NULL) 
pred@2142
   265
	{
pred@2142
   266
		g_Logger.LogToOpenFile(logfile, "Failed to find TCP");
pred@2142
   267
		return INVALID_SOCKET;
pred@2142
   268
	}
pred@2142
   269
pred@2142
   270
	int socketDescriptor = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
pred@2142
   271
pred@2142
   272
	if (socketDescriptor == INVALID_SOCKET)
pred@2142
   273
	{
pred@2142
   274
		//bugger aye?
pred@2367
   275
#ifdef WIN32
pred@2367
   276
		g_Logger.LogToOpenFile(logfile, "Failed to create a new socket - Error %i", WSAGetLastError());
pred@2367
   277
#else
pred@2367
   278
		g_Logger.LogToOpenFile(logfile, "Failed to create a new socket - Error %i", errno);
pred@2367
   279
#endif
pred@2142
   280
		closesocket(socketDescriptor);
pred@2142
   281
		return INVALID_SOCKET;
pred@2142
   282
	}
pred@2142
   283
pred@2142
   284
	struct hostent *he;
pred@2142
   285
	struct sockaddr_in local_addr;
pred@2142
   286
pred@2142
   287
	local_addr.sin_family = AF_INET;
pred@2247
   288
	local_addr.sin_port = htons((u_short)g_serverPort);
pred@2142
   289
pred@2247
   290
	he = gethostbyname(g_serverAddress);
pred@2142
   291
pred@2142
   292
	if (!he)
pred@2142
   293
	{
pred@2247
   294
		if ((local_addr.sin_addr.s_addr = inet_addr(g_serverAddress)) == INADDR_NONE)
pred@2142
   295
		{
pred@2367
   296
			g_Logger.LogToOpenFile(logfile, "Couldn't locate address");
pred@2142
   297
			closesocket(socketDescriptor);
pred@2142
   298
			return INVALID_SOCKET;
pred@2142
   299
		}
pred@2142
   300
	} 
pred@2142
   301
	else 
pred@2142
   302
	{
pred@2142
   303
		memcpy(&local_addr.sin_addr, (struct in_addr *)he->h_addr, he->h_length);
pred@2142
   304
	}
pred@2142
   305
pred@2142
   306
	if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
pred@2142
   307
	{
pred@2367
   308
#ifdef WIN32
pred@2367
   309
		g_Logger.LogToOpenFile(logfile, "Couldn't connect - Error %i", WSAGetLastError());
pred@2367
   310
#else
pred@2367
   311
		g_Logger.LogToOpenFile(logfile, "Couldn't connect - Error %i", errno);
pred@2367
   312
#endif
pred@2142
   313
		closesocket(socketDescriptor);
pred@2142
   314
		return INVALID_SOCKET;
pred@2142
   315
	}
pred@2142
   316
pred@2142
   317
	return socketDescriptor;
pred@2142
   318
}
pred@2142
   319
pred@2142
   320
void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
pred@2142
   321
{
pred@2142
   322
	char buffer[50];
pred@2367
   323
#ifdef DEBUG
pred@2142
   324
	g_Logger.LogToOpenFile(logfile, "Waiting for reply!");
pred@2367
   325
#endif
pred@2142
   326
pred@2142
   327
	//Read in the header bytes
pred@2367
   328
	int returnLen = RecvData(socketDescriptor, buffer, 12);
pred@2142
   329
pred@2367
   330
#ifdef DEBUG
pred@2142
   331
	g_Logger.LogToOpenFile(logfile, "Recv Completed");
pred@2367
   332
#endif
pred@2142
   333
pred@2142
   334
	if (returnLen == 0)
pred@2142
   335
	{
pred@2367
   336
#ifdef DEBUG
pred@2142
   337
		g_Logger.LogToOpenFile(logfile, ",but it failed.");
pred@2367
   338
#endif
pred@2142
   339
		/* Timeout or fail? */
pred@2142
   340
		return;
pred@2142
   341
	}
pred@2142
   342
pred@2367
   343
#ifdef DEBUG
pred@2142
   344
	g_Logger.LogToOpenFile(logfile, "Received Header!");
pred@2367
   345
#endif
pred@2142
   346
pred@2142
   347
	bf_read Reader = bf_read("GameDataQuery", buffer, 12);
pred@2142
   348
pred@2142
   349
	if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
pred@2142
   350
	{
pred@2367
   351
		g_Logger.LogToOpenFile(logfile, "Unknown Query Response");
pred@2142
   352
		return;
pred@2142
   353
	}
pred@2142
   354
pred@2142
   355
	UpdateStatus updateStatus = (UpdateStatus)Reader.ReadByte();
pred@2142
   356
pred@2142
   357
	short build[4] = {0,0,0,0};
pred@2142
   358
pred@2142
   359
	build[0] = Reader.ReadShort();
pred@2142
   360
	build[1] = Reader.ReadShort();
pred@2142
   361
	build[2] = Reader.ReadShort();
pred@2142
   362
	build[3] = Reader.ReadShort();
pred@2142
   363
pred@2367
   364
#ifdef DEBUG
pred@2142
   365
	g_Logger.LogToOpenFile(logfile, "Update Status: %i - Latest %i.%i.%i.%i", updateStatus, build[0], build[1], build[2], build[3]);
pred@2367
   366
#endif
pred@2142
   367
pred@2142
   368
	HandleUpdateStatus(updateStatus, build);
pred@2142
   369
pred@2142
   370
	int changedFiles = Reader.ReadByte();
pred@2142
   371
pred@2367
   372
#ifdef DEBUG
pred@2142
   373
	g_Logger.LogToOpenFile(logfile, "Files to download: %i", changedFiles);
pred@2367
   374
#endif
pred@2142
   375
pred@2142
   376
	for (int i=0; i<changedFiles; i++)
pred@2142
   377
	{
pred@2142
   378
		//Read in the file index and byte count
pred@2367
   379
		returnLen = RecvData(socketDescriptor, buffer, 5);
pred@2142
   380
pred@2142
   381
		if (returnLen == 0)
pred@2142
   382
		{
pred@2142
   383
			/* Timeout or fail? */
pred@2142
   384
			return;
pred@2142
   385
		}
pred@2142
   386
pred@2142
   387
		Reader.StartReading(buffer, 5);
pred@2142
   388
pred@2142
   389
		int index = Reader.ReadByte();
pred@2142
   390
		int tempLen = Reader.ReadUBitLong(32);
pred@2142
   391
pred@2367
   392
#ifdef DEBUG
pred@2142
   393
		g_Logger.LogToOpenFile(logfile, "File index %i and length %i", index, tempLen);
pred@2367
   394
#endif
pred@2142
   395
pred@2142
   396
		void *memPtr;
pred@2142
   397
		memtable->CreateMem(tempLen+1, &memPtr);
pred@2142
   398
pred@2142
   399
		//Read the contents of our file into the memtable
pred@2367
   400
		returnLen = RecvData(socketDescriptor, (char *)memPtr, tempLen);
pred@2142
   401
pred@2367
   402
#ifdef DEBUG
pred@2142
   403
		g_Logger.LogToOpenFile(logfile, "Recieved %i bytes", returnLen);
pred@2367
   404
#endif
pred@2142
   405
pred@2142
   406
		if (returnLen == 0)
pred@2142
   407
		{
pred@2142
   408
			/* Timeout or fail? */
pred@2142
   409
			return;
pred@2142
   410
		}
pred@2142
   411
pred@2142
   412
		((unsigned char *)memPtr)[tempLen] = '\0';
pred@2142
   413
pred@2142
   414
		FileData *data = filenames.at(index);
pred@2142
   415
		const char* filename;
pred@2142
   416
		if (data != NULL)
pred@2142
   417
		{
pred@2142
   418
			filename = data->filename->c_str();
pred@2142
   419
pred@2142
   420
			FILE *fp = fopen(filename, "w");
pred@2142
   421
pred@2142
   422
			if (fp)
pred@2142
   423
			{
pred@2142
   424
				fprintf(fp, (const char *)memPtr);
pred@2142
   425
				fclose(fp);
pred@2142
   426
			}
pred@2367
   427
			else
pred@2367
   428
			{
pred@2367
   429
				g_Logger.LogToOpenFile(logfile, "Failed to open file \"%s\"", filename);
pred@2367
   430
			}
pred@2142
   431
		}
pred@2142
   432
		else
pred@2142
   433
		{
pred@2142
   434
			filename = "";
pred@2142
   435
		}
pred@2142
   436
pred@2142
   437
		memtable->Reset();
pred@2142
   438
pred@2367
   439
#ifdef DEBUG
pred@2142
   440
		g_Logger.LogToOpenFile(logfile, "Updated File %s", filename);
pred@2367
   441
#endif
pred@2142
   442
	}
pred@2142
   443
pred@2367
   444
#ifdef DEBUG
pred@2142
   445
	g_Logger.LogToOpenFile(logfile, "File Downloads Completed!");
pred@2367
   446
#endif
pred@2142
   447
pred@2142
   448
	bool needsRestart = false;
pred@2142
   449
pred@2142
   450
	if (changedFiles > 0)
pred@2142
   451
	{
pred@2142
   452
		needsRestart = true;
pred@2142
   453
		g_Logger.LogMessage("New GameData Files have been downloaded to your gamedata directory. Please restart your server for these to take effect");
pred@2142
   454
	}
pred@2142
   455
pred@2142
   456
	//Read changed file count
pred@2367
   457
	returnLen = RecvData(socketDescriptor, buffer, 1);
pred@2142
   458
pred@2142
   459
	if (returnLen == 0)
pred@2142
   460
	{
pred@2142
   461
		/* Timeout or fail? */
pred@2142
   462
		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown count");
pred@2142
   463
		return;
pred@2142
   464
	}
pred@2142
   465
pred@2142
   466
	Reader.StartReading(buffer, 1);
pred@2142
   467
pred@2142
   468
	changedFiles = Reader.ReadByte();
pred@2142
   469
pred@2142
   470
	if (changedFiles == 0)
pred@2142
   471
	{
pred@2367
   472
#ifdef DEBUG
pred@2142
   473
		g_Logger.LogToOpenFile(logfile, "No unknown files. We're all done");
pred@2367
   474
#endif
pred@2142
   475
		return;
pred@2142
   476
	}
pred@2142
   477
pred@2142
   478
	char *changedFileIndexes = new char[changedFiles];
pred@2367
   479
pred@2367
   480
#ifdef DEBUG
pred@2142
   481
	g_Logger.LogToOpenFile(logfile, "%i Files were unknown", changedFiles);
pred@2367
   482
#endif
pred@2367
   483
pred@2367
   484
	returnLen = RecvData(socketDescriptor, changedFileIndexes, changedFiles);
pred@2142
   485
pred@2142
   486
	if (returnLen == 0)
pred@2142
   487
	{
pred@2142
   488
		/* Timeout or fail? */
pred@2367
   489
#ifdef DEBUG
pred@2142
   490
		g_Logger.LogToOpenFile(logfile, "Failed to receive unknown list");
pred@2367
   491
#endif
pred@2142
   492
		return;
pred@2142
   493
	}
pred@2142
   494
pred@2142
   495
	Reader.StartReading(changedFileIndexes, changedFiles);
pred@2142
   496
pred@2142
   497
	for (int i=0; i<changedFiles; i++)
pred@2142
   498
	{
pred@2142
   499
		int index = Reader.ReadByte();
pred@2142
   500
		char fileName[30];
pred@2142
   501
pred@2142
   502
		FileData *data = filenames.at(index);
pred@2142
   503
		const char* pathname;
pred@2142
   504
		if (data != NULL)
pred@2142
   505
		{
pred@2142
   506
			pathname = data->filename->c_str();
pred@2142
   507
		}
pred@2142
   508
		else
pred@2142
   509
		{
pred@2142
   510
			pathname = "";
pred@2142
   511
		}
pred@2142
   512
pred@2142
   513
		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
pred@2367
   514
#ifdef DEBUG
pred@2142
   515
		g_Logger.LogToOpenFile(logfile, "Unknown File %i : %s", index, fileName);
pred@2367
   516
#endif
pred@2142
   517
	}
pred@2142
   518
pred@2142
   519
	delete [] changedFileIndexes;
pred@2142
   520
pred@2142
   521
	if (needsRestart && g_restartAfterUpdate)
pred@2142
   522
	{
pred@2142
   523
		g_Logger.LogMessage("Automatically restarting server after a successful gamedata update!");
pred@2142
   524
		engine->ServerCommand("quit\n");
pred@2142
   525
	}
pred@2142
   526
}
pred@2142
   527
pred@2367
   528
int FetcherThread::RecvData( int socketDescriptor, char *buffer, int len )
pred@2142
   529
{
pred@2142
   530
	fd_set fds;
pred@2142
   531
	struct timeval tv;
pred@2142
   532
pred@2142
   533
	/* Create a 10 Second Timeout */
pred@2142
   534
	tv.tv_sec = 10;
pred@2142
   535
	tv.tv_usec = 0;
pred@2142
   536
pred@2367
   537
	int bytesReceivedTotal = 0;
pred@2142
   538
pred@2367
   539
	while (bytesReceivedTotal < len)
pred@2367
   540
	{
pred@2367
   541
		/* Add our socket to a socket set */
pred@2367
   542
		FD_ZERO(&fds);
pred@2367
   543
		FD_SET(socketDescriptor, &fds);
pred@2367
   544
pred@2367
   545
		/* Wait max of 10 seconds for recv to become available */
pred@2367
   546
		select(socketDescriptor+1, &fds, NULL, NULL, &tv);
pred@2367
   547
pred@2367
   548
		int bytesReceived = 0;
pred@2142
   549
pred@2367
   550
		/* 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
   551
		if (FD_ISSET(socketDescriptor, &fds))
pred@2367
   552
		{
pred@2371
   553
			bytesReceived = recv(socketDescriptor, buffer+bytesReceivedTotal, len-bytesReceivedTotal, 0);
pred@2367
   554
		}
pred@2142
   555
pred@2367
   556
		if (bytesReceived == 0 || bytesReceived == -1)
pred@2367
   557
		{
pred@2367
   558
			return 0;
pred@2367
   559
		}
pred@2367
   560
pred@2367
   561
		bytesReceivedTotal += bytesReceived;
pred@2142
   562
	}
pred@2142
   563
pred@2367
   564
	return bytesReceivedTotal;	
pred@2142
   565
}
pred@2142
   566
pred@2367
   567
int FetcherThread::SendData( int socketDescriptor, char *buffer, int len )
pred@2142
   568
{
pred@2142
   569
	fd_set fds;
pred@2142
   570
	struct timeval tv;
pred@2142
   571
pred@2142
   572
	tv.tv_sec = 10;
pred@2142
   573
	tv.tv_usec = 0;
pred@2142
   574
pred@2367
   575
	int sentBytesTotal = 0;
pred@2142
   576
pred@2367
   577
	while (sentBytesTotal < len)
pred@2367
   578
	{
pred@2367
   579
		FD_ZERO(&fds);
pred@2367
   580
		FD_SET(socketDescriptor, &fds);
pred@2367
   581
pred@2367
   582
		select(socketDescriptor+1, NULL, &fds, NULL, &tv);
pred@2367
   583
pred@2367
   584
		int sentBytes = 0;
pred@2142
   585
pred@2367
   586
		if (FD_ISSET(socketDescriptor, &fds))
pred@2367
   587
		{
pred@2371
   588
			sentBytes = send(socketDescriptor, buffer+sentBytesTotal, len-sentBytesTotal, 0);
pred@2367
   589
		}
pred@2142
   590
pred@2367
   591
		if (sentBytes == 0 || sentBytes == -1)
pred@2367
   592
		{
pred@2367
   593
			return 0;
pred@2367
   594
		}		
pred@2367
   595
pred@2367
   596
		sentBytesTotal += sentBytes;
pred@2142
   597
	}
pred@2142
   598
pred@2367
   599
	return sentBytesTotal;
pred@2142
   600
}
pred@2142
   601
pred@2142
   602
void FetcherThread::HandleUpdateStatus( UpdateStatus status, short version[4] )
pred@2142
   603
{
pred@2142
   604
	switch (status)
pred@2142
   605
	{
pred@2142
   606
		case Update_Unknown:
pred@2142
   607
		case Update_Current:
pred@2142
   608
		{
pred@2142
   609
			break;
pred@2142
   610
		}
pred@2142
   611
pred@2142
   612
		case Update_NewBuild:
pred@2142
   613
		{
pred@2367
   614
			g_Logger.LogMessage("SourceMod Update: A new Mercurial build is available from sourcemod.net");
pred@2142
   615
			g_Logger.LogMessage("Current Version: %i.%i.%i.%i Available: %i.%i.%i.%i", version[0], version[1], version[2], version[3], version[0], version[1], version[2], version[3]);
pred@2142
   616
			break;
pred@2142
   617
		}
pred@2142
   618
pred@2142
   619
		case Update_MinorAvailable:
pred@2142
   620
		{
pred@2142
   621
			g_Logger.LogMessage("SourceMod Update: An incremental minor release of SourceMod is now available from sourcemod.net");
pred@2142
   622
			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
   623
			break;
pred@2142
   624
		}
pred@2142
   625
pred@2142
   626
		case Update_MajorAvailable:
pred@2142
   627
		{
pred@2142
   628
			g_Logger.LogMessage("SourceMod Update: An major release of SourceMod is now available from sourcemod.net");
pred@2142
   629
			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
   630
			break;
pred@2142
   631
		}
pred@2142
   632
pred@2142
   633
		case Update_CriticalAvailable:
pred@2142
   634
		{
pred@2142
   635
			g_Logger.LogError("SourceMod Update: A new critical release of SourceMod is now available from sourcemod.net. It is strongly recommended that you update");
pred@2142
   636
			g_Logger.LogMessage("Current Version: %i.%i.%i.%i Available: %i.%i.%i.%i", version[0], version[1], version[2], version[3], version[0], version[1], version[2], version[3]);
pred@2142
   637
			break;
pred@2142
   638
		}
pred@2142
   639
	}
pred@2142
   640
}
pred@2142
   641
pred@2142
   642
bool g_blockGameDataLoad = false;
pred@2142
   643
pred@2142
   644
class InitFetch : public SMGlobalClass
pred@2142
   645
{
pred@2142
   646
public:
pred@2142
   647
	void OnSourceModAllInitialized_Post()
pred@2142
   648
	{
pred@2142
   649
		char lock_path[PLATFORM_MAX_PATH];
pred@2142
   650
		g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
pred@2142
   651
pred@2142
   652
		if (g_LibSys.IsPathFile(lock_path) && g_LibSys.PathExists(lock_path))
pred@2142
   653
		{
pred@2142
   654
			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
   655
			g_Logger.LogError("If this error persists delete the file manually");
pred@2142
   656
			g_blockGameDataLoad = true;
pred@2142
   657
		}
pred@2142
   658
pred@2142
   659
		ThreadParams fetchThreadParams = ThreadParams();
pred@2142
   660
		fetchThreadParams.prio = ThreadPrio_Low;
pred@2142
   661
		g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
pred@2142
   662
	}
pred@2142
   663
pred@2142
   664
	ConfigResult OnSourceModConfigChanged(const char *key, 
pred@2142
   665
		const char *value, 
pred@2142
   666
		ConfigSource source,
pred@2142
   667
		char *error, 
pred@2142
   668
		size_t maxlength)
pred@2142
   669
	{
pred@2142
   670
		if (strcmp(key, "DisableAutoUpdate") == 0)
pred@2142
   671
		{
pred@2142
   672
			if (strcmp(value, "yes") == 0)
pred@2142
   673
			{
pred@2142
   674
				g_disableGameDataUpdate = true;
pred@2142
   675
				return ConfigResult_Accept;
pred@2142
   676
			}
pred@2142
   677
			else if (strcmp(value, "no") == 0)
pred@2142
   678
			{
pred@2142
   679
				g_disableGameDataUpdate = false;
pred@2142
   680
				return ConfigResult_Accept;
pred@2142
   681
			}
pred@2142
   682
pred@2142
   683
			return ConfigResult_Reject;
pred@2142
   684
		}
pred@2142
   685
pred@2142
   686
		if (strcmp(key, "ForceRestartAfterUpdate") == 0)
pred@2142
   687
		{
pred@2142
   688
			if (strcmp(value, "yes") == 0)
pred@2142
   689
			{
pred@2142
   690
				g_restartAfterUpdate = true;
pred@2142
   691
				return ConfigResult_Accept;
pred@2142
   692
			}
pred@2142
   693
			else if (strcmp(value, "no") == 0)
pred@2142
   694
			{
pred@2142
   695
				g_restartAfterUpdate = false;
pred@2142
   696
				return ConfigResult_Accept;
pred@2142
   697
			}
pred@2142
   698
pred@2142
   699
			return ConfigResult_Reject;
pred@2142
   700
		}
pred@2142
   701
pred@2247
   702
		if (strcmp(key, "AutoUpdateServer") == 0)
pred@2247
   703
		{
pred@2247
   704
			UTIL_Format(g_serverAddress, sizeof(g_serverAddress), "%s", value);
pred@2247
   705
pred@2247
   706
			return ConfigResult_Accept;
pred@2247
   707
		}
pred@2247
   708
pred@2247
   709
		if (strcmp(key, "AutoUpdatePort") == 0)
pred@2247
   710
		{
pred@2247
   711
			int port = atoi(value);
pred@2247
   712
pred@2247
   713
			if (!port)
pred@2247
   714
			{
pred@2247
   715
				return ConfigResult_Reject;
pred@2247
   716
			}
pred@2247
   717
pred@2247
   718
			g_serverPort = port;
pred@2247
   719
pred@2247
   720
			return ConfigResult_Accept;
pred@2247
   721
		}
pred@2247
   722
pred@2142
   723
		return ConfigResult_Ignore;
pred@2142
   724
	}
pred@2142
   725
} g_InitFetch;
pred@2142
   726
pred@2142
   727
CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
pred@2142
   728
{
ds@2400
   729
#if SOURCE_ENGINE == SE_EPISODEONE
pred@2142
   730
	CCommand args;
pred@2142
   731
#endif
pred@2142
   732
pred@2142
   733
	if (args.ArgC() < 2)
pred@2142
   734
	{
pred@2142
   735
		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
pred@2142
   736
		return;
pred@2142
   737
	}
pred@2142
   738
pred@2142
   739
	const char *file = args.Arg(1);
pred@2142
   740
	if (!file || file[0] == '\0')
pred@2142
   741
	{
pred@2142
   742
		g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
pred@2142
   743
		return;
pred@2142
   744
	}
pred@2142
   745
pred@2142
   746
	SourceHook::CVector<FileData *>::iterator iter = g_FetchThread.filenames.begin();
pred@2142
   747
pred@2142
   748
	FileData *curData;
pred@2142
   749
pred@2142
   750
	while (iter != g_FetchThread.filenames.end())
pred@2142
   751
	{
pred@2142
   752
		curData = (*iter);
pred@2142
   753
pred@2142
   754
		char fileName[30];
pred@2142
   755
pred@2142
   756
		g_LibSys.GetFileFromPath(fileName, sizeof(fileName), curData->filename->c_str());
pred@2142
   757
pred@2142
   758
		if (strcmpi(fileName, file) == 0)
pred@2142
   759
		{
pred@2142
   760
			g_SMAPI->ConPrintf("MD5 Sum: %s\n", curData->checksum);
pred@2142
   761
			return;
pred@2142
   762
		}
pred@2142
   763
pred@2142
   764
		iter++;
pred@2142
   765
	}
pred@2142
   766
pred@2142
   767
	g_SMAPI->ConPrint("File not found!\n");
pred@2142
   768
}