Removed pointless "systems" folder from core.
3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
32 #include "GameDataFetcher.h"
35 #ifdef PLATFORM_WINDOWS
39 #include <sys/types.h>
40 #include <sys/socket.h>
41 #include <netinet/in.h>
43 #include <arpa/inet.h>
45 #define INVALID_SOCKET -1
46 #define closesocket close
49 #include "sh_vector.h"
50 #include "sh_string.h"
51 #include "sm_version.h"
53 #ifdef ORANGEBOX_BUILD
54 #include "convar_sm_ob.h"
56 #include "convar_sm.h"
59 #include "sourcemm_api.h"
62 #include "compat_wrappers.h"
63 #include "sm_stringutil.h"
65 #define QUERY_MAX_LENGTH 1024
67 BuildMD5ableBuffer g_MD5Builder;
68 FetcherThread g_FetchThread;
72 bool g_disableGameDataUpdate = false;
73 bool g_restartAfterUpdate = false;
75 int g_serverPort = 6500;
76 char g_serverAddress[100] = "hayate.alliedmods.net";
78 void FetcherThread::RunThread( IThreadHandle *pHandle )
80 char lock_path[PLATFORM_MAX_PATH];
81 g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp");
82 g_LibSys.CreateFolder(lock_path);
84 g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
86 g_Logger.LogMessage("Starting Gamedata update fetcher... please report problems to bugs.alliedmods.net");
88 char log_path[PLATFORM_MAX_PATH];
89 g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata");
91 g_LibSys.CreateFolder(log_path);
95 tm *curtime = localtime(&t);
97 g_SourceMod.BuildPath(Path_SM, log_path, sizeof(log_path), "logs/gamedata/L%02d%02d.log", curtime->tm_mon + 1, curtime->tm_mday);
99 logfile = fopen(log_path, "a");
103 g_Logger.LogError("Failed to create GameData log file");
107 //Create a blank lock file
108 FILE *fp = fopen(lock_path, "w");
114 char query[QUERY_MAX_LENGTH];
116 /* Check for updated gamedata files */
117 int len = BuildGameDataQuery(query, QUERY_MAX_LENGTH);
121 g_Logger.LogToOpenFile(logfile, "Query Writing failed");
128 if (g_disableGameDataUpdate)
131 g_Logger.LogMessage("Skipping GameData Query due to DisableAutoUpdate being set to true");
139 /* Create a new socket for this connection */
140 int socketDescriptor = ConnectSocket();
142 if (socketDescriptor == INVALID_SOCKET)
149 int sent = SendData(socketDescriptor, query, len);
152 g_Logger.LogToOpenFile(logfile, "Sent Query!");
157 g_Logger.LogToOpenFile(logfile, "Failed to send gamedata query data to remote host");
159 closesocket(socketDescriptor);
165 ProcessGameDataQuery(socketDescriptor);
167 /* And we're done! */
168 closesocket(socketDescriptor);
173 void FetcherThread::OnTerminate( IThreadHandle *pHandle, bool cancel )
175 g_blockGameDataLoad = false;
178 int FetcherThread::BuildGameDataQuery( char *buffer, int maxlen )
180 char gamedata_path[PLATFORM_MAX_PATH];
181 g_SourceMod.BuildPath(Path_SM, gamedata_path, sizeof(gamedata_path), "gamedata");
183 IDirectory *dir = g_LibSys.OpenDirectory(gamedata_path);
190 bf_write Writer = bf_write("GameDataQuery", buffer, maxlen);
192 Writer.WriteByte('A'); //Generic Header char
193 Writer.WriteByte('G'); //G for gamedata query, or green, like my hat.
195 short build[4] = { SVN_FILE_VERSION };
197 Writer.WriteBytes(&build[0], 8);
199 Writer.WriteByte(0); // Initialize the file counter - Index 10
201 while (dir->MoreFiles())
203 if (dir->IsEntryFile())
205 const char *name = dir->GetEntryName();
206 size_t len = strlen(name);
208 && strcmp(&name[len-4], ".txt") == 0)
210 char file[PLATFORM_MAX_PATH];
212 g_LibSys.PathFormat(file, sizeof(file), "%s/%s", gamedata_path, name);
215 if (g_TextParser.ParseFile_SMC(file, &g_MD5Builder, &states) == SMCError_Okay)
217 unsigned char *md5 = g_MD5Builder.GetMD5();
220 (uint8_t)buffer[10]++; //Increment the file counter
221 Writer.WriteBytes(md5, 16);
223 g_Logger.LogToOpenFile(logfile, "%s - \"%s\"", file, g_MD5Builder.GetMD5String());
225 FileData *data = new FileData();
226 data->filename = new SourceHook::String(file);
227 memcpy(data->checksum, g_MD5Builder.GetMD5String(), 33);
228 filenames.push_back(data);
233 g_Logger.LogToOpenFile(logfile, "%s no md5?", file);
240 g_Logger.LogToOpenFile(logfile, "%s failed!", file);
249 return Writer.GetNumBytesWritten();
252 int FetcherThread::ConnectSocket()
254 struct protoent *ptrp;
255 ptrp = getprotobyname("tcp");
259 WSAStartup(0x0101, &wsaData);
264 g_Logger.LogToOpenFile(logfile, "Failed to find TCP");
265 return INVALID_SOCKET;
268 int socketDescriptor = socket(AF_INET, SOCK_STREAM, ptrp->p_proto);
270 if (socketDescriptor == INVALID_SOCKET)
274 g_Logger.LogToOpenFile(logfile, "Failed to create a new socket - Error %i", WSAGetLastError());
276 g_Logger.LogToOpenFile(logfile, "Failed to create a new socket - Error %i", errno);
278 closesocket(socketDescriptor);
279 return INVALID_SOCKET;
283 struct sockaddr_in local_addr;
285 local_addr.sin_family = AF_INET;
286 local_addr.sin_port = htons((u_short)g_serverPort);
288 he = gethostbyname(g_serverAddress);
292 if ((local_addr.sin_addr.s_addr = inet_addr(g_serverAddress)) == INADDR_NONE)
294 g_Logger.LogToOpenFile(logfile, "Couldn't locate address");
295 closesocket(socketDescriptor);
296 return INVALID_SOCKET;
301 memcpy(&local_addr.sin_addr, (struct in_addr *)he->h_addr, he->h_length);
304 if (connect(socketDescriptor, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
307 g_Logger.LogToOpenFile(logfile, "Couldn't connect - Error %i", WSAGetLastError());
309 g_Logger.LogToOpenFile(logfile, "Couldn't connect - Error %i", errno);
311 closesocket(socketDescriptor);
312 return INVALID_SOCKET;
315 return socketDescriptor;
318 void FetcherThread::ProcessGameDataQuery(int socketDescriptor)
322 g_Logger.LogToOpenFile(logfile, "Waiting for reply!");
325 //Read in the header bytes
326 int returnLen = RecvData(socketDescriptor, buffer, 12);
329 g_Logger.LogToOpenFile(logfile, "Recv Completed");
335 g_Logger.LogToOpenFile(logfile, ",but it failed.");
337 /* Timeout or fail? */
342 g_Logger.LogToOpenFile(logfile, "Received Header!");
345 bf_read Reader = bf_read("GameDataQuery", buffer, 12);
347 if (Reader.ReadByte() != 'A' || Reader.ReadByte() != 'G')
349 g_Logger.LogToOpenFile(logfile, "Unknown Query Response");
353 UpdateStatus updateStatus = (UpdateStatus)Reader.ReadByte();
355 short build[4] = {0,0,0,0};
357 build[0] = Reader.ReadShort();
358 build[1] = Reader.ReadShort();
359 build[2] = Reader.ReadShort();
360 build[3] = Reader.ReadShort();
363 g_Logger.LogToOpenFile(logfile, "Update Status: %i - Latest %i.%i.%i.%i", updateStatus, build[0], build[1], build[2], build[3]);
366 HandleUpdateStatus(updateStatus, build);
368 int changedFiles = Reader.ReadByte();
371 g_Logger.LogToOpenFile(logfile, "Files to download: %i", changedFiles);
374 for (int i=0; i<changedFiles; i++)
376 //Read in the file index and byte count
377 returnLen = RecvData(socketDescriptor, buffer, 5);
381 /* Timeout or fail? */
385 Reader.StartReading(buffer, 5);
387 int index = Reader.ReadByte();
388 int tempLen = Reader.ReadUBitLong(32);
391 g_Logger.LogToOpenFile(logfile, "File index %i and length %i", index, tempLen);
395 memtable->CreateMem(tempLen+1, &memPtr);
397 //Read the contents of our file into the memtable
398 returnLen = RecvData(socketDescriptor, (char *)memPtr, tempLen);
401 g_Logger.LogToOpenFile(logfile, "Recieved %i bytes", returnLen);
406 /* Timeout or fail? */
410 ((unsigned char *)memPtr)[tempLen] = '\0';
412 FileData *data = filenames.at(index);
413 const char* filename;
416 filename = data->filename->c_str();
418 FILE *fp = fopen(filename, "w");
422 fprintf(fp, (const char *)memPtr);
427 g_Logger.LogToOpenFile(logfile, "Failed to open file \"%s\"", filename);
438 g_Logger.LogToOpenFile(logfile, "Updated File %s", filename);
443 g_Logger.LogToOpenFile(logfile, "File Downloads Completed!");
446 bool needsRestart = false;
448 if (changedFiles > 0)
451 g_Logger.LogMessage("New GameData Files have been downloaded to your gamedata directory. Please restart your server for these to take effect");
454 //Read changed file count
455 returnLen = RecvData(socketDescriptor, buffer, 1);
459 /* Timeout or fail? */
460 g_Logger.LogToOpenFile(logfile, "Failed to receive unknown count");
464 Reader.StartReading(buffer, 1);
466 changedFiles = Reader.ReadByte();
468 if (changedFiles == 0)
471 g_Logger.LogToOpenFile(logfile, "No unknown files. We're all done");
476 char *changedFileIndexes = new char[changedFiles];
479 g_Logger.LogToOpenFile(logfile, "%i Files were unknown", changedFiles);
482 returnLen = RecvData(socketDescriptor, changedFileIndexes, changedFiles);
486 /* Timeout or fail? */
488 g_Logger.LogToOpenFile(logfile, "Failed to receive unknown list");
493 Reader.StartReading(changedFileIndexes, changedFiles);
495 for (int i=0; i<changedFiles; i++)
497 int index = Reader.ReadByte();
500 FileData *data = filenames.at(index);
501 const char* pathname;
504 pathname = data->filename->c_str();
511 g_LibSys.GetFileFromPath(fileName, sizeof(fileName), pathname);
513 g_Logger.LogToOpenFile(logfile, "Unknown File %i : %s", index, fileName);
517 delete [] changedFileIndexes;
519 if (needsRestart && g_restartAfterUpdate)
521 g_Logger.LogMessage("Automatically restarting server after a successful gamedata update!");
522 engine->ServerCommand("quit\n");
526 int FetcherThread::RecvData( int socketDescriptor, char *buffer, int len )
531 /* Create a 10 Second Timeout */
535 int bytesReceivedTotal = 0;
537 while (bytesReceivedTotal < len)
539 /* Add our socket to a socket set */
541 FD_SET(socketDescriptor, &fds);
543 /* Wait max of 10 seconds for recv to become available */
544 select(socketDescriptor+1, &fds, NULL, NULL, &tv);
546 int bytesReceived = 0;
548 /* Is there a limit on how much we can receive? Some site said 1024 bytes, which will be well short of a file */
549 if (FD_ISSET(socketDescriptor, &fds))
551 bytesReceived = recv(socketDescriptor, buffer+bytesReceivedTotal, len-bytesReceivedTotal, 0);
554 if (bytesReceived == 0 || bytesReceived == -1)
559 bytesReceivedTotal += bytesReceived;
562 return bytesReceivedTotal;
565 int FetcherThread::SendData( int socketDescriptor, char *buffer, int len )
573 int sentBytesTotal = 0;
575 while (sentBytesTotal < len)
578 FD_SET(socketDescriptor, &fds);
580 select(socketDescriptor+1, NULL, &fds, NULL, &tv);
584 if (FD_ISSET(socketDescriptor, &fds))
586 sentBytes = send(socketDescriptor, buffer+sentBytesTotal, len-sentBytesTotal, 0);
589 if (sentBytes == 0 || sentBytes == -1)
594 sentBytesTotal += sentBytes;
597 return sentBytesTotal;
600 void FetcherThread::HandleUpdateStatus( UpdateStatus status, short version[4] )
610 case Update_NewBuild:
612 g_Logger.LogMessage("SourceMod Update: A new Mercurial build is available from sourcemod.net");
613 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]);
617 case Update_MinorAvailable:
619 g_Logger.LogMessage("SourceMod Update: An incremental minor release of SourceMod is now available from sourcemod.net");
620 g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
624 case Update_MajorAvailable:
626 g_Logger.LogMessage("SourceMod Update: An major release of SourceMod is now available from sourcemod.net");
627 g_Logger.LogMessage("Current Version: %i.%i.%i Available: %i.%i.%i", version[0], version[1], version[2], version[0], version[1], version[2]);
631 case Update_CriticalAvailable:
633 g_Logger.LogError("SourceMod Update: A new critical release of SourceMod is now available from sourcemod.net. It is strongly recommended that you update");
634 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]);
640 bool g_blockGameDataLoad = false;
642 class InitFetch : public SMGlobalClass
645 void OnSourceModAllInitialized_Post()
647 char lock_path[PLATFORM_MAX_PATH];
648 g_SourceMod.BuildPath(Path_SM, lock_path, sizeof(lock_path), "data/temp/gamedata.lock");
650 if (g_LibSys.IsPathFile(lock_path) && g_LibSys.PathExists(lock_path))
652 g_Logger.LogError("sourcemod/data/temp/gamedata.lock file detected. This is most likely due to a crash during GameData updating - Blocking GameData loading");
653 g_Logger.LogError("If this error persists delete the file manually");
654 g_blockGameDataLoad = true;
657 ThreadParams fetchThreadParams = ThreadParams();
658 fetchThreadParams.prio = ThreadPrio_Low;
659 g_pThreader->MakeThread(&g_FetchThread, &fetchThreadParams);
662 ConfigResult OnSourceModConfigChanged(const char *key,
668 if (strcmp(key, "DisableAutoUpdate") == 0)
670 if (strcmp(value, "yes") == 0)
672 g_disableGameDataUpdate = true;
673 return ConfigResult_Accept;
675 else if (strcmp(value, "no") == 0)
677 g_disableGameDataUpdate = false;
678 return ConfigResult_Accept;
681 return ConfigResult_Reject;
684 if (strcmp(key, "ForceRestartAfterUpdate") == 0)
686 if (strcmp(value, "yes") == 0)
688 g_restartAfterUpdate = true;
689 return ConfigResult_Accept;
691 else if (strcmp(value, "no") == 0)
693 g_restartAfterUpdate = false;
694 return ConfigResult_Accept;
697 return ConfigResult_Reject;
700 if (strcmp(key, "AutoUpdateServer") == 0)
702 UTIL_Format(g_serverAddress, sizeof(g_serverAddress), "%s", value);
704 return ConfigResult_Accept;
707 if (strcmp(key, "AutoUpdatePort") == 0)
709 int port = atoi(value);
713 return ConfigResult_Reject;
718 return ConfigResult_Accept;
721 return ConfigResult_Ignore;
725 CON_COMMAND(sm_gamedata_md5, "Checks the MD5 sum for a given gamedata file")
727 #if !defined ORANGEBOX_BUILD
733 g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
737 const char *file = args.Arg(1);
738 if (!file || file[0] == '\0')
740 g_SMAPI->ConPrint("Usage: sm_gamedata_md5 <file>\n");
744 SourceHook::CVector<FileData *>::iterator iter = g_FetchThread.filenames.begin();
748 while (iter != g_FetchThread.filenames.end())
754 g_LibSys.GetFileFromPath(fileName, sizeof(fileName), curData->filename->c_str());
756 if (strcmpi(fileName, file) == 0)
758 g_SMAPI->ConPrintf("MD5 Sum: %s\n", curData->checksum);
765 g_SMAPI->ConPrint("File not found!\n");