diff -r 000000000000 -r c24a6fa957db core/GameDataFetcher.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/core/GameDataFetcher.cpp Sun Jul 06 04:49:55 2008 +0000
@@ -0,0 +1,693 @@
+/**
+* vim: set ts=4 :
+* =============================================================================
+* SourceMod
+* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
+* =============================================================================
+*
+* This program is free software; you can redistribute it and/or modify it under
+* the terms of the GNU General Public License, version 3.0, as published by the
+* Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+* details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this program. If not, see .
+*
+* As a special exception, AlliedModders LLC gives you permission to link the
+* code of this program (as well as its derivative works) to "Half-Life 2," the
+* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+* by the Valve Corporation. You must obey the GNU General Public License in
+* all respects for all other code used. Additionally, AlliedModders LLC grants
+* this exception to all derivative works. AlliedModders LLC defines further
+* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+* or .
+*
+* Version: $Id$
+*/
+
+#include "GameDataFetcher.h"
+#include "bitbuf.h"
+
+#ifdef PLATFORM_WINDOWS
+#include
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define INVALID_SOCKET -1
+#define closesocket close
+#endif
+
+#include "sh_vector.h"
+#include "sh_string.h"
+#include "sm_version.h"
+
+#ifdef ORANGEBOX_BUILD
+#include "convar_sm_ob.h"
+#else
+#include "convar_sm.h"
+#endif
+
+#include "sourcemm_api.h"
+#include "time.h"
+#include "TimerSys.h"
+#include "compat_wrappers.h"
+
+#define QUERY_MAX_LENGTH 1024
+
+BuildMD5ableBuffer g_MD5Builder;
+FetcherThread g_FetchThread;
+
+FILE *logfile = NULL;
+
+bool g_disableGameDataUpdate = false;
+bool g_restartAfterUpdate = false;
+
+void FetcherThread::RunThread( IThreadHandle *pHandle )
+{
+ char lock_path[PLATFORM_MAX_PATH];
+ g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp");
+ g_LibSys.CreateFolder(lock_path);
+
+ g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
+
+ g_Logger.LogMessage("Starting experimental gamedata update fetcher... please report problems to bugs.alliedmods.net");
+
+ char log_path[PLATFORM_MAX_PATH];
+ g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata");
+
+ g_LibSys.CreateFolder(log_path);
+
+ time_t t;
+ GetAdjustedTime(&t);
+ tm *curtime = localtime(&t);
+
+ g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata/L%02d%02d.log", curtime->tm_mon + 1, curtime->tm_mday);
+
+ logfile = fopen(log_path, "a");
+
+ if (!logfile)
+ {
+ g_Logger.LogError("Failed to create GameData log file");
+ return;
+ }
+
+ //Create a blank lock file
+ FILE *fp = fopen(lock_path, "w");
+ if (fp)
+ {
+ fclose(fp);
+ }
+
+ /* Create a new socket for this connection */
+ int socketDescriptor = ConnectSocket();
+
+ if (socketDescriptor == INVALID_SOCKET)
+ {
+ fclose(logfile);
+ unlink(lock_path);
+ return;
+ }
+
+ char query[QUERY_MAX_LENGTH];
+
+ /* Check for updated gamedata files */
+ int len = BuildGameDataQuery(query, QUERY_MAX_LENGTH);
+
+ if (len == 0)
+ {
+ g_Logger.LogToOpenFile(logfile, "Query Writing failed");
+
+ closesocket(socketDescriptor);
+ fclose(logfile);
+ unlink(lock_path);
+ return;
+ }
+
+ if (g_disableGameDataUpdate)
+ {
+ g_Logger.LogMessage("Skipping GameData Query due to DisableAutoUpdate being set to true");
+
+ closesocket(socketDescriptor);
+ fclose(logfile);
+ unlink(lock_path);
+ return;
+ }
+
+ int sent = NonBlockingSend(socketDescriptor, query, len);
+
+ g_Logger.LogToOpenFile(logfile, "Sent Query!");
+
+ if (sent == 0)
+ {
+ g_Logger.LogToOpenFile(logfile, "Failed to send data");
+
+ closesocket(socketDescriptor);
+ fclose(logfile);
+ unlink(lock_path);
+ return;
+ }
+
+ ProcessGameDataQuery(socketDescriptor);
+
+ /* And we're done! */
+ closesocket(socketDescriptor);
+ fclose(logfile);
+ unlink(lock_path);
+}
+
+void FetcherThread::OnTerminate( IThreadHandle *pHandle, bool cancel )
+{
+ //delete this;
+}
+
+int FetcherThread::BuildGameDataQuery( char *buffer, int maxlen )
+{
+ char gamedata_path[PLATFORM_MAX_PATH];
+ g_SourceMod.BuildPath(Path_SM, gamedata_path, sizeof(gamedata_path), "gamedata");
+
+ IDirectory *dir = g_LibSys.OpenDirectory(gamedata_path);
+
+ if (dir == NULL)
+ {
+ return 0;
+ }
+
+ bf_write Writer = bf_write("GameDataQuery", buffer, maxlen);
+
+ Writer.WriteByte('A'); //Generic Header char
+ Writer.WriteByte('G'); //G for gamedata query, or green, like my hat.
+
+ short build[4] = { SVN_FILE_VERSION };
+
+ Writer.WriteBytes(&build[0], 8);
+
+ Writer.WriteByte(0); // Initialize the file counter - Index 10
+
+ while (dir->MoreFiles())
+ {
+ if (dir->IsEntryFile())
+ {
+ const char *name = dir->GetEntryName();
+ size_t len = strlen(name);
+ if (len >= 4
+ && strcmp(&name[len-4], ".txt") == 0)
+ {
+ char file[PLATFORM_MAX_PATH];
+
+ g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
+
+ SMCStates states;
+ if (g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states) == SMCError_Okay)
+ {
+ unsigned char *md5 = g_MD5Builder.GetMD5();
+ if (md5 != NULL)
+ {
+ (uint8_t)buffer[10]++; //Increment the file counter
+ Writer.WriteBytes(md5, 16);
+
+ g_Logger.LogToOpenFile(logfile, "%s - \"%s\"", file, g_MD5Builder.GetMD5String());
+
+ FileData *data = new FileData();
+ data->filename = new SourceHook::String(file);
+ memcpy(data->checksum, g_MD5Builder.GetMD5String(), 33);
+ filenames.push_back(data);
+ }
+ else
+ {
+ g_Logger.LogToOpenFile(logfile, "%s no md5?", file);
+ }
+ }
+ else
+ {
+ g_Logger.LogToOpenFile(logfile, "%s failed!", file);
+ }
+ }
+ }
+
+ dir->NextEntry();
+ }
+
+ return Writer.GetNumBytesWritten();
+}
+
+int FetcherThread::ConnectSocket()
+{
+ struct protoent *ptrp;
+ ptrp = getprotobyname("tcp");
+
+#ifdef WIN32
+ WSADATA wsaData;
+ WSAStartup(0x0101, &wsaData);
+#endif
+
+ if (ptrp == NULL)
+ {
+ g_Logger.LogToOpenFile(logfile, "Failed to find TCP");
+ return INVALID_SOCKET;
+ }
+
+ int socketDescriptor = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
+
+ if (socketDescriptor == INVALID_SOCKET)
+ {
+ //bugger aye?
+ g_Logger.LogToOpenFile(logfile, "Failed to create a new socket");
+ closesocket(socketDescriptor);
+ return INVALID_SOCKET;
+ }
+
+ struct hostent *he;
+ struct sockaddr_in local_addr;
+
+ local_addr.sin_family = AF_INET;
+ local_addr.sin_port = htons((u_short)6500);
+
+ he = gethostbyname("hayate.alliedmods.net");
+
+ if (!he)
+ {
+ if ((local_addr.sin_addr.s_addr = inet_addr("hayate.alliedmods.net")) == INADDR_NONE)
+ {
+ g_Logger.LogToOpenFile(logfile, "Couldnt locate address");
+ closesocket(socketDescriptor);
+ return INVALID_SOCKET;
+ }
+ }
+ else
+ {
+ memcpy(&local_addr.sin_addr, (struct in_addr *)he->h_addr, he->h_length);
+ }
+
+ if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
+ {
+ g_Logger.LogToOpenFile(logfile, "Couldn't connect");
+ closesocket(socketDescriptor);
+ return INVALID_SOCKET;
+ }
+
+ return socketDescriptor;
+}
+
+void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
+{
+ char buffer[50];
+
+ g_Logger.LogToOpenFile(logfile, "Waiting for reply!");
+
+ //Read in the header bytes
+ int returnLen = NonBlockingRecv(socketDescriptor, buffer, 12);
+
+ g_Logger.LogToOpenFile(logfile, "Recv Completed");
+
+ if (returnLen == 0)
+ {
+ g_Logger.LogToOpenFile(logfile, ",but it failed.");
+ /* Timeout or fail? */
+ return;
+ }
+
+ g_Logger.LogToOpenFile(logfile, "Received Header!");
+
+ bf_read Reader = bf_read("GameDataQuery", buffer, 12);
+
+ if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
+ {
+ g_Logger.LogToOpenFile(logfile, "Invalid Query to handle");
+ return;
+ }
+
+ UpdateStatus updateStatus = (UpdateStatus)Reader.ReadByte();
+
+ short build[4] = {0,0,0,0};
+
+ build[0] = Reader.ReadShort();
+ build[1] = Reader.ReadShort();
+ build[2] = Reader.ReadShort();
+ build[3] = Reader.ReadShort();
+
+ g_Logger.LogToOpenFile(logfile, "Update Status: %i - Latest %i.%i.%i.%i", updateStatus, build[0], build[1], build[2], build[3]);
+
+ HandleUpdateStatus(updateStatus, build);
+
+ int changedFiles = Reader.ReadByte();
+
+ g_Logger.LogToOpenFile(logfile, "Files to download: %i", changedFiles);
+
+ for (int i=0; iCreateMem(tempLen+1, &memPtr);
+
+ //Read the contents of our file into the memtable
+ returnLen = NonBlockingRecv(socketDescriptor, (char *)memPtr, tempLen);
+
+ g_Logger.LogToOpenFile(logfile, "Recieved %i bytes", returnLen);
+
+ if (returnLen == 0)
+ {
+ /* Timeout or fail? */
+ return;
+ }
+
+ ((unsigned char *)memPtr)[tempLen] = '\0';
+
+ FileData *data = filenames.at(index);
+ const char* filename;
+ if (data != NULL)
+ {
+ filename = data->filename->c_str();
+
+ FILE *fp = fopen(filename, "w");
+
+ if (fp)
+ {
+ fprintf(fp, (const char *)memPtr);
+ fclose(fp);
+ }
+ }
+ else
+ {
+ filename = "";
+ }
+
+ memtable->Reset();
+
+ g_Logger.LogToOpenFile(logfile, "Updated File %s", filename);
+ }
+
+ g_Logger.LogToOpenFile(logfile, "File Downloads Completed!");
+
+ bool needsRestart = false;
+
+ if (changedFiles > 0)
+ {
+ needsRestart = true;
+ g_Logger.LogMessage("New GameData Files have been downloaded to your gamedata directory. Please restart your server for these to take effect");
+ }
+
+ //Read changed file count
+ returnLen = NonBlockingRecv(socketDescriptor, buffer, 1);
+
+ if (returnLen == 0)
+ {
+ /* Timeout or fail? */
+ g_Logger.LogToOpenFile(logfile, "Failed to receive unknown count");
+ return;
+ }
+
+ Reader.StartReading(buffer, 1);
+
+ changedFiles = Reader.ReadByte();
+
+ if (changedFiles == 0)
+ {
+ g_Logger.LogToOpenFile(logfile, "No unknown files. We're all done");
+ return;
+ }
+
+ char *changedFileIndexes = new char[changedFiles];
+
+ g_Logger.LogToOpenFile(logfile, "%i Files were unknown", changedFiles);
+
+ returnLen = NonBlockingRecv(socketDescriptor, changedFileIndexes, changedFiles);
+
+ if (returnLen == 0)
+ {
+ /* Timeout or fail? */
+ g_Logger.LogToOpenFile(logfile, "Failed to receive unknown list");
+ return;
+ }
+
+ Reader.StartReading(changedFileIndexes, changedFiles);
+
+ for (int i=0; ifilename->c_str();
+ }
+ else
+ {
+ pathname = "";
+ }
+
+ g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
+
+ g_Logger.LogToOpenFile(logfile, "Unknown File %i : %s", index, fileName);
+ }
+
+ delete [] changedFileIndexes;
+
+ if (needsRestart && g_restartAfterUpdate)
+ {
+ g_Logger.LogMessage("Automatically restarting server after a successful gamedata update!");
+ engine->ServerCommand("quit\n");
+ }
+}
+
+int FetcherThread::NonBlockingRecv( int socketDescriptor, char *buffer, int len )
+{
+ fd_set fds;
+ struct timeval tv;
+
+ /* Create a 10 Second Timeout */
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ /* Add our socket to a socket set */
+ FD_ZERO(&fds);
+ FD_SET(socketDescriptor, &fds);
+
+ /* Wait max of 10 seconds for recv to become available */
+ select(socketDescriptor+1, &fds, NULL, NULL, &tv);
+
+ int bytesReceived = 0;
+
+ /* Is there a limit on how much we can receive? Some site said 1024 bytes, which will be well short of a file */
+ if (FD_ISSET(socketDescriptor, &fds))
+ {
+ bytesReceived = recv(socketDescriptor, buffer, len, 0);
+ }
+
+ if (bytesReceived == 0 || bytesReceived == -1)
+ {
+ return 0;
+ }
+
+ if (bytesReceived < len)
+ {
+ return bytesReceived + NonBlockingRecv(socketDescriptor, buffer+bytesReceived, len-bytesReceived);
+ }
+
+ return bytesReceived;
+}
+
+int FetcherThread::NonBlockingSend( int socketDescriptor, char *buffer, int len )
+{
+ fd_set fds;
+ struct timeval tv;
+
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&fds);
+ FD_SET(socketDescriptor, &fds);
+
+ select(socketDescriptor+1, NULL, &fds, NULL, &tv);
+
+ int sentBytes = 0;
+
+ if (FD_ISSET(socketDescriptor, &fds))
+ {
+ sentBytes = send(socketDescriptor, buffer, len, 0);
+ }
+
+ if (sentBytes == 0 || sentBytes == -1)
+ {
+ return 0;
+ }
+
+ if (sentBytes < len)
+ {
+ return sentBytes + NonBlockingSend(socketDescriptor, buffer+sentBytes, len-sentBytes);
+ }
+
+ return sentBytes;
+}
+
+void FetcherThread::HandleUpdateStatus( UpdateStatus status, short version[4] )
+{
+ switch (status)
+ {
+ case Update_Unknown:
+ case Update_Current:
+ {
+ break;
+ }
+
+ case Update_NewBuild:
+ {
+ g_Logger.LogMessage("SourceMod Update: A new SVN build is available from sourcemod.net");
+ 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]);
+ break;
+ }
+
+ case Update_MinorAvailable:
+ {
+ g_Logger.LogMessage("SourceMod Update: An incremental minor release of SourceMod is now available from sourcemod.net");
+ g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
+ break;
+ }
+
+ case Update_MajorAvailable:
+ {
+ g_Logger.LogMessage("SourceMod Update: An major release of SourceMod is now available from sourcemod.net");
+ g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
+ break;
+ }
+
+ case Update_CriticalAvailable:
+ {
+ g_Logger.LogError("SourceMod Update: A new critical release of SourceMod is now available from sourcemod.net. It is strongly recommended that you update");
+ 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]);
+ break;
+ }
+ }
+}
+
+bool g_blockGameDataLoad = false;
+
+class InitFetch : public SMGlobalClass
+{
+public:
+ void OnSourceModAllInitialized_Post()
+ {
+ char lock_path[PLATFORM_MAX_PATH];
+ g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
+
+ if (g_LibSys.IsPathFile(lock_path) && g_LibSys.PathExists(lock_path))
+ {
+ g_Logger.LogError("sourcemod/data/temp/gamedata.lock file detected. This is most likely due to a crash during GameData updating - Blocking GameData loading");
+ g_Logger.LogError("If this error persists delete the file manually");
+ g_blockGameDataLoad = true;
+ }
+
+ ThreadParams fetchThreadParams = ThreadParams();
+ fetchThreadParams.prio = ThreadPrio_Low;
+ g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
+ }
+
+ ConfigResult OnSourceModConfigChanged(const char *key,
+ const char *value,
+ ConfigSource source,
+ char *error,
+ size_t maxlength)
+ {
+ if (strcmp(key, "DisableAutoUpdate") == 0)
+ {
+ if (strcmp(value, "yes") == 0)
+ {
+ g_disableGameDataUpdate = true;
+ return ConfigResult_Accept;
+ }
+ else if (strcmp(value, "no") == 0)
+ {
+ g_disableGameDataUpdate = false;
+ return ConfigResult_Accept;
+ }
+
+ return ConfigResult_Reject;
+ }
+
+ if (strcmp(key, "ForceRestartAfterUpdate") == 0)
+ {
+ if (strcmp(value, "yes") == 0)
+ {
+ g_restartAfterUpdate = true;
+ return ConfigResult_Accept;
+ }
+ else if (strcmp(value, "no") == 0)
+ {
+ g_restartAfterUpdate = false;
+ return ConfigResult_Accept;
+ }
+
+ return ConfigResult_Reject;
+ }
+
+ return ConfigResult_Ignore;
+ }
+} g_InitFetch;
+
+CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
+{
+#if !defined ORANGEBOX_BUILD
+ CCommand args;
+#endif
+
+ if (args.ArgC() < 2)
+ {
+ g_SMAPI->ConPrint("Usage: sm_gamedata_md5 \n");
+ return;
+ }
+
+ const char *file = args.Arg(1);
+ if (!file || file[0] == '\0')
+ {
+ g_SMAPI->ConPrint("Usage: sm_gamedata_md5 \n");
+ return;
+ }
+
+ SourceHook::CVector::iterator iter = g_FetchThread.filenames.begin();
+
+ FileData *curData;
+
+ while (iter != g_FetchThread.filenames.end())
+ {
+ curData = (*iter);
+
+ char fileName[30];
+
+ g_LibSys.GetFileFromPath(fileName, sizeof(fileName), curData->filename->c_str());
+
+ if (strcmpi(fileName, file) == 0)
+ {
+ g_SMAPI->ConPrintf("MD5 Sum: %s\n", curData->checksum);
+ return;
+ }
+
+ iter++;
+ }
+
+ g_SMAPI->ConPrint("File not found!\n");
+}