3 * =============================================================================
4 * SourceMod Updater Extension
5 * Copyright (C) 2004-2009 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 <sourcemod_version.h>
34 #include "extension.h"
39 #define USTATE_FOLDERS 1
40 #define USTATE_CHANGED 2
41 #define USTATE_CHANGE_FILE 3
42 #define USTATE_ERRORS 4
44 using namespace SourceMod;
46 UpdateReader::UpdateReader() : partFirst(NULL), partLast(NULL)
50 UpdateReader::~UpdateReader()
54 void UpdateReader::ReadSMC_ParseStart()
60 SMCResult UpdateReader::ReadSMC_NewSection(const SMCStates *states, const char *name)
65 return SMCResult_Continue;
72 if (strcmp(name, "Folders") == 0)
74 ustate = USTATE_FOLDERS;
76 else if (strcmp(name, "Changed") == 0)
78 ustate = USTATE_CHANGED;
80 else if (strcmp(name, "Errors") == 0)
82 ustate = USTATE_ERRORS;
91 case USTATE_CHANGE_FILE:
101 ustate = USTATE_CHANGE_FILE;
106 return SMCResult_Continue;
109 SMCResult UpdateReader::ReadSMC_KeyValue(const SMCStates *states,
115 return SMCResult_Continue;
120 case USTATE_CHANGE_FILE:
122 if (strcmp(key, "md5sum") == 0)
124 if (strlen(value) != 32)
126 return SMCResult_Continue;
128 strcpy(checksum, value);
130 else if (strcmp(key, "location") == 0)
132 url.assign(update_url);
139 if (strcmp(key, "error") == 0)
141 AddUpdateError("%s", value);
152 return SMCResult_Continue;
155 SMCResult UpdateReader::ReadSMC_LeavingSection(const SMCStates *states)
160 return SMCResult_Continue;
169 ustate = USTATE_NONE;
172 case USTATE_CHANGE_FILE:
174 if (url.size() != 0 && checksum[0] != '\0')
180 AddUpdateError("Incomplete file definition in update manifest");
182 ustate = USTATE_CHANGED;
187 return SMCResult_Continue;
190 void UpdateReader::LinkPart(UpdatePart *part)
193 if (partFirst == NULL)
200 partLast->next = part;
205 void UpdateReader::HandleFile()
208 char real_checksum[33];
212 if (!xfer->Download(url.c_str(), &mdl, NULL))
214 AddUpdateError("Could not download \"%s\"", url.c_str());
215 AddUpdateError("Error: %s", xfer->LastErrorMessage());
219 md5.update((unsigned char *)mdl.GetBuffer(), mdl.GetSize());
221 md5.hex_digest(real_checksum);
223 if (mdl.GetSize() == 0)
225 AddUpdateError("Zero-length file returned for \"%s\"", curfile.c_str());
229 if (strcasecmp(checksum, real_checksum) != 0)
231 AddUpdateError("Checksums for file \"%s\" do not match:", curfile.c_str());
232 AddUpdateError("Expected: %s Real: %s", checksum, real_checksum);
236 UpdatePart *part = new UpdatePart;
237 part->data = (char*)malloc(mdl.GetSize());
238 memcpy(part->data, mdl.GetBuffer(), mdl.GetSize());
239 part->file = strdup(curfile.c_str());
240 part->length = mdl.GetSize();
244 void UpdateReader::HandleFolder(const char *folder)
246 UpdatePart *part = new UpdatePart;
249 part->file = strdup(folder);
253 static bool md5_file(const char *file, char checksum[33])
260 if ((fp = fopen(file, "rb")) == NULL)
265 fseek(fp, 0, SEEK_END);
267 fseek(fp, 0, SEEK_SET);
268 fdata = malloc(length);
269 if (fread(fdata, 1, length, fp) != size_t(length))
277 md5.update((unsigned char*)fdata, length);
279 md5.hex_digest(checksum);
286 /* Path should be sourcemod relative, not gamedata relative */
287 static bool add_file(IWebForm *form, const char *file, unsigned int &num_files)
289 char path[PLATFORM_MAX_PATH];
291 smutils->BuildPath(Path_SM, path, sizeof(path), "%s", file);
294 if (!md5_file(path, checksum))
300 smutils->Format(name, sizeof(name), "file_%d_name", num_files);
301 form->AddString(name, file);
302 smutils->Format(name, sizeof(name), "file_%d_md5", num_files);
303 form->AddString(name, checksum);
310 static void add_folders(IWebForm *form, const char *root, unsigned int &num_files)
313 char path[PLATFORM_MAX_PATH];
314 char name[PLATFORM_MAX_PATH];
316 smutils->BuildPath(Path_SM, path, sizeof(path), "%s", root);
317 dir = libsys->OpenDirectory(path);
320 AddUpdateError("Could not open folder: %s", path);
324 while (dir->MoreFiles())
326 if (strcmp(dir->GetEntryName(), ".") == 0 ||
327 strcmp(dir->GetEntryName(), "..") == 0)
332 smutils->Format(name, sizeof(name), "%s/%s", root, dir->GetEntryName());
333 if (dir->IsEntryDirectory())
335 add_folders(form, name, num_files);
337 else if (dir->IsEntryFile())
339 add_file(form, name, num_files);
344 libsys->CloseDirectory(dir);
347 void UpdateReader::PerformUpdate(const char *url)
350 MemoryDownloader master;
351 SMCStates states = {0, 0};
355 form = webternet->CreateForm();
356 xfer = webternet->CreateSession();
357 xfer->SetFailOnHTTPError(true);
359 form->AddString("version", SM_VERSION_STRING);
361 unsigned int num_files = 0;
362 add_folders(form, "gamedata", num_files);
365 smutils->Format(temp, sizeof(temp), "%d", num_files);
366 form->AddString("files", temp);
368 if (!xfer->PostAndDownload(url, form, &master, NULL))
370 AddUpdateError("Could not download \"%s\"", url);
371 AddUpdateError("Error: %s", xfer->LastErrorMessage());
377 error = textparsers->ParseSMCStream(master.GetBuffer(),
383 if (error != SMCError_Okay)
385 AddUpdateError("Parse error in update manifest: %s", errbuf);
394 UpdatePart *UpdateReader::DetachParts()