From 3636ecd7cddc2b228ef022cc2502a596c5a80c4a Mon Sep 17 00:00:00 2001 From: Marvin Lukaschek Date: Wed, 19 Aug 2020 20:37:30 +0200 Subject: [PATCH] Upload mirror --- scripting/include/updater.inc | 97 ++++++ scripting/updater.sp | 316 +++++++++++++++++++ scripting/updater/api.sp | 115 +++++++ scripting/updater/download.sp | 235 ++++++++++++++ scripting/updater/download_curl.sp | 62 ++++ scripting/updater/download_socket.sp | 203 ++++++++++++ scripting/updater/download_steamtools.sp | 51 +++ scripting/updater/download_steamworks.sp | 40 +++ scripting/updater/filesys.sp | 385 +++++++++++++++++++++++ scripting/updater/plugins.sp | 196 ++++++++++++ 10 files changed, 1700 insertions(+) create mode 100644 scripting/include/updater.inc create mode 100644 scripting/updater.sp create mode 100644 scripting/updater/api.sp create mode 100644 scripting/updater/download.sp create mode 100644 scripting/updater/download_curl.sp create mode 100644 scripting/updater/download_socket.sp create mode 100644 scripting/updater/download_steamtools.sp create mode 100644 scripting/updater/download_steamworks.sp create mode 100644 scripting/updater/filesys.sp create mode 100644 scripting/updater/plugins.sp diff --git a/scripting/include/updater.inc b/scripting/include/updater.inc new file mode 100644 index 0000000..98626a6 --- /dev/null +++ b/scripting/include/updater.inc @@ -0,0 +1,97 @@ +#if defined _updater_included + #endinput +#endif +#define _updater_included + +/** + * Adds your plugin to the updater. The URL will be updated if + * your plugin was previously added. + * + * @param url URL to your plugin's update file. + * @noreturn + */ +native void Updater_AddPlugin(const char[] url); + +/** + * Removes your plugin from the updater. This does not need to + * be called during OnPluginEnd. + * + * @noreturn + */ +native void Updater_RemovePlugin(); + +/** + * Forces your plugin to be checked for updates. The behaviour + * of the update is dependant on the server's configuration. + * + * @return True if an update was triggered. False otherwise. + * @error Plugin not found in updater. + */ +native bool Updater_ForceUpdate(); + +/** + * Called when your plugin is about to be checked for updates. + * + * @return Plugin_Handled to prevent checking, Plugin_Continue to allow it. + */ +forward Action Updater_OnPluginChecking(); + +/** + * Called when your plugin is about to begin downloading an available update. + * + * @return Plugin_Handled to prevent downloading, Plugin_Continue to allow it. + */ +forward Action Updater_OnPluginDownloading(); + +/** + * Called when your plugin's update files have been fully downloaded + * and are about to write to their proper location. This should be used + * to free read-only resources that require write access for your update. + * + * @note OnPluginUpdated will be called later during the same frame. + * + * @noreturn + */ +forward void Updater_OnPluginUpdating(); + +/** + * Called when your plugin's update has been completed. It is safe + * to reload your plugin at this time. + * + * @noreturn + */ +forward void Updater_OnPluginUpdated(); + +/** + * @brief Reloads a plugin. + * + * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin). + * @noreturn + */ +stock void ReloadPlugin(Handle plugin=null) +{ + char filename[64]; + GetPluginFilename(plugin, filename, sizeof(filename)); + ServerCommand("sm plugins reload %s", filename); +} + + +public SharedPlugin __pl_updater = +{ + name = "updater", + file = "updater.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_updater_SetNTVOptional() +{ + MarkNativeAsOptional("Updater_AddPlugin"); + MarkNativeAsOptional("Updater_RemovePlugin"); + MarkNativeAsOptional("Updater_ForceUpdate"); +} +#endif diff --git a/scripting/updater.sp b/scripting/updater.sp new file mode 100644 index 0000000..eb94ceb --- /dev/null +++ b/scripting/updater.sp @@ -0,0 +1,316 @@ +#pragma semicolon 1 +#pragma newdecls required + +/* SM Includes */ +#include +#undef REQUIRE_EXTENSIONS +#include +#include +#include +#include +#define REQUIRE_EXTENSIONS + +/* Plugin Info */ +#define PLUGIN_NAME "Updater" +#define PLUGIN_VERSION "1.2.2" + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = "GoD-Tony", + description = "Automatically updates SourceMod plugins and files", + version = PLUGIN_VERSION, + url = "http://forums.alliedmods.net/showthread.php?t=169095" +}; + +/* Globals */ +//#define DEBUG // This will enable verbose logging. Useful for developers testing their updates. + +#define CURL_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "curl_easy_init") == FeatureStatus_Available) +#define SOCKET_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "SocketCreate") == FeatureStatus_Available) +#define STEAMTOOLS_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "Steam_CreateHTTPRequest") == FeatureStatus_Available) +#define STEAMWORKS_AVAILABLE() (GetFeatureStatus(FeatureType_Native, "SteamWorks_WriteHTTPResponseBodyToFile") == FeatureStatus_Available) + +#define EXTENSION_ERROR "This plugin requires one of the cURL, Socket, SteamTools, or SteamWorks extensions to function." +#define TEMP_FILE_EXT "temp" // All files are downloaded with this extension first. +#define MAX_URL_LENGTH 256 + +#define UPDATE_URL "http://godtony.mooo.com/updater/updater.txt" + +enum UpdateStatus { + Status_Idle, + Status_Checking, // Checking for updates. + Status_Downloading, // Downloading an update. + Status_Updated, // Update is complete. + Status_Error, // An error occured while downloading. +}; + +bool g_bGetDownload; +bool g_bGetSource; + +Handle g_hPluginPacks = INVALID_HANDLE; +Handle g_hDownloadQueue = INVALID_HANDLE; +Handle g_hRemoveQueue = INVALID_HANDLE; +bool g_bDownloading = false; + +static Handle _hUpdateTimer = INVALID_HANDLE; +static float _fLastUpdate = 0.0; +static char _sDataPath[PLATFORM_MAX_PATH]; + +/* Core Includes */ +#include "updater/plugins.sp" +#include "updater/filesys.sp" +#include "updater/download.sp" +#include "updater/api.sp" + +/* Plugin Functions */ +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + // cURL + MarkNativeAsOptional("curl_OpenFile"); + MarkNativeAsOptional("curl_slist"); + MarkNativeAsOptional("curl_slist_append"); + MarkNativeAsOptional("curl_easy_init"); + MarkNativeAsOptional("curl_easy_setopt_int_array"); + MarkNativeAsOptional("curl_easy_setopt_handle"); + MarkNativeAsOptional("curl_easy_setopt_string"); + MarkNativeAsOptional("curl_easy_perform_thread"); + MarkNativeAsOptional("curl_easy_strerror"); + + // Socket + MarkNativeAsOptional("SocketCreate"); + MarkNativeAsOptional("SocketSetArg"); + MarkNativeAsOptional("SocketSetOption"); + MarkNativeAsOptional("SocketConnect"); + MarkNativeAsOptional("SocketSend"); + + // SteamTools + MarkNativeAsOptional("Steam_CreateHTTPRequest"); + MarkNativeAsOptional("Steam_SetHTTPRequestHeaderValue"); + MarkNativeAsOptional("Steam_SendHTTPRequest"); + MarkNativeAsOptional("Steam_WriteHTTPResponseBody"); + MarkNativeAsOptional("Steam_ReleaseHTTPRequest"); + + API_Init(); + RegPluginLibrary("updater"); + + return APLRes_Success; +} + +public void OnPluginStart() +{ + if (!CURL_AVAILABLE() && !SOCKET_AVAILABLE() && !STEAMTOOLS_AVAILABLE() && !STEAMWORKS_AVAILABLE()) + { + SetFailState(EXTENSION_ERROR); + } + + LoadTranslations("common.phrases"); + + // Convars. + Handle hCvar = INVALID_HANDLE; + + hCvar = CreateConVar("sm_updater_version", PLUGIN_VERSION, PLUGIN_NAME, 0|FCVAR_NOTIFY|FCVAR_DONTRECORD); + OnVersionChanged(hCvar, "", ""); + HookConVarChange(hCvar, OnVersionChanged); + + hCvar = CreateConVar("sm_updater", "2", "Determines update functionality. (1 = Notify, 2 = Download, 3 = Include source code)", 0, true, 1.0, true, 3.0); + OnSettingsChanged(hCvar, "", ""); + HookConVarChange(hCvar, OnSettingsChanged); + + // Commands. + RegAdminCmd("sm_updater_check", Command_Check, ADMFLAG_RCON, "Forces Updater to check for updates."); + RegAdminCmd("sm_updater_status", Command_Status, ADMFLAG_RCON, "View the status of Updater."); + + // Initialize arrays. + g_hPluginPacks = CreateArray(); + g_hDownloadQueue = CreateArray(); + g_hRemoveQueue = CreateArray(); + + // Temp path for checking update files. + BuildPath(Path_SM, _sDataPath, sizeof(_sDataPath), "data/updater.txt"); + +#if !defined DEBUG + // Add this plugin to the autoupdater. + Updater_AddPlugin(GetMyHandle(), UPDATE_URL); +#endif + + // Check for updates every 24 hours. + _hUpdateTimer = CreateTimer(86400.0, Timer_CheckUpdates, _, TIMER_REPEAT); +} + +public void OnAllPluginsLoaded() +{ + // Check for updates on startup. + TriggerTimer(_hUpdateTimer, true); +} + +public Action Timer_CheckUpdates(Handle timer) +{ + Updater_FreeMemory(); + + // Update everything! + int maxPlugins = GetMaxPlugins(); + for (int i = 0; i < maxPlugins; i++) + { + if (Updater_GetStatus(i) == Status_Idle) + { + Updater_Check(i); + } + } + + _fLastUpdate = GetTickedTime(); + + return Plugin_Continue; +} + +public Action Command_Check(int client, int args) +{ + float fNextUpdate = _fLastUpdate + 3600.0; + + if (fNextUpdate > GetTickedTime()) + { + ReplyToCommand(client, "[Updater] Updates can only be checked once per hour. %.1f minutes remaining.", (fNextUpdate - GetTickedTime()) / 60.0); + } + else + { + ReplyToCommand(client, "[Updater] Checking for updates."); + TriggerTimer(_hUpdateTimer, true); + } + + return Plugin_Handled; +} + +public Action Command_Status(int client, int args) +{ + char sFilename[64]; + Handle hPlugin = INVALID_HANDLE; + int maxPlugins = GetMaxPlugins(); + + ReplyToCommand(client, "[Updater] -- Status Begin --"); + ReplyToCommand(client, "Plugins being monitored for updates:"); + + for (int i = 0; i < maxPlugins; i++) + { + hPlugin = IndexToPlugin(i); + + if (IsValidPlugin(hPlugin)) + { + GetPluginFilename(hPlugin, sFilename, sizeof(sFilename)); + ReplyToCommand(client, " [%i] %s", i, sFilename); + } + } + + ReplyToCommand(client, "Last update check was %.1f minutes ago.", (GetTickedTime() - _fLastUpdate) / 60.0); + ReplyToCommand(client, "[Updater] --- Status End ---"); + + return Plugin_Handled; +} + +public void OnVersionChanged(Handle convar, const char[] oldValue, const char[] newValue) +{ + if (!StrEqual(newValue, PLUGIN_VERSION)) + { + SetConVarString(convar, PLUGIN_VERSION); + } +} + +public void OnSettingsChanged(Handle convar, const char[] oldValue, const char[] newValue) +{ + switch (GetConVarInt(convar)) + { + case 1: // Notify only. + { + g_bGetDownload = false; + g_bGetSource = false; + } + + case 2: // Download updates. + { + g_bGetDownload = true; + g_bGetSource = false; + } + + case 3: // Download with source code. + { + g_bGetDownload = true; + g_bGetSource = true; + } + } +} + +#if !defined DEBUG +public void Updater_OnPluginUpdated() +{ + Updater_Log("Reloading Updater plugin... updates will resume automatically."); + + // Reload this plugin. + char filename[64]; + GetPluginFilename(INVALID_HANDLE, filename, sizeof(filename)); + ServerCommand("sm plugins reload %s", filename); +} +#endif + +void Updater_Check(int index) +{ + if (Fwd_OnPluginChecking(IndexToPlugin(index)) == Plugin_Continue) + { + char url[MAX_URL_LENGTH]; + Updater_GetURL(index, url, sizeof(url)); + Updater_SetStatus(index, Status_Checking); + AddToDownloadQueue(index, url, _sDataPath); + } +} + +void Updater_FreeMemory() +{ + // Make sure that no threads are active. + if (g_bDownloading || GetArraySize(g_hDownloadQueue)) + { + return; + } + + // Remove all queued plugins. + int index; + int maxPlugins = GetArraySize(g_hRemoveQueue); + for (int i = 0; i < maxPlugins; i++) + { + index = PluginToIndex(GetArrayCell(g_hRemoveQueue, i)); + + if (index != -1) + { + Updater_RemovePlugin(index); + } + } + + ClearArray(g_hRemoveQueue); + + // Remove plugins that have been unloaded. + for (int i = 0; i < GetMaxPlugins(); i++) + { + if (!IsValidPlugin(IndexToPlugin(i))) + { + Updater_RemovePlugin(i); + i--; + } + } +} + +int Updater_Log(const char[] format, any:...) +{ + char buffer[256]; + char path[PLATFORM_MAX_PATH]; + VFormat(buffer, sizeof(buffer), format, 2); + BuildPath(Path_SM, path, sizeof(path), "logs/Updater.log"); + LogToFileEx(path, "%s", buffer); +} + +#if defined DEBUG +int Updater_DebugLog(const char[] format, any:...) +{ + char buffer[256]; + char path[PLATFORM_MAX_PATH]; + VFormat(buffer, sizeof(buffer), format, 2); + BuildPath(Path_SM, path, sizeof(path), "logs/Updater_Debug.log"); + LogToFileEx(path, "%s", buffer); +} +#endif diff --git a/scripting/updater/api.sp b/scripting/updater/api.sp new file mode 100644 index 0000000..210391a --- /dev/null +++ b/scripting/updater/api.sp @@ -0,0 +1,115 @@ + +/* API - Natives & Forwards */ + +static Handle fwd_OnPluginChecking = INVALID_HANDLE; +static Handle fwd_OnPluginDownloading = INVALID_HANDLE; +static Handle fwd_OnPluginUpdating = INVALID_HANDLE; +static Handle fwd_OnPluginUpdated = INVALID_HANDLE; + +void API_Init() +{ + CreateNative("Updater_AddPlugin", Native_AddPlugin); + CreateNative("Updater_RemovePlugin", Native_RemovePlugin); + CreateNative("Updater_ForceUpdate", Native_ForceUpdate); + + fwd_OnPluginChecking = CreateForward(ET_Event); + fwd_OnPluginDownloading = CreateForward(ET_Event); + fwd_OnPluginUpdating = CreateForward(ET_Ignore); + fwd_OnPluginUpdated = CreateForward(ET_Ignore); +} + +// native Updater_AddPlugin(const char[] url); +public int Native_AddPlugin(Handle plugin, int numParams) +{ + char url[MAX_URL_LENGTH]; + GetNativeString(1, url, sizeof(url)); + + Updater_AddPlugin(plugin, url); +} + +// native Updater_RemovePlugin(); +public int Native_RemovePlugin(Handle plugin, int numParams) +{ + int index = PluginToIndex(plugin); + + if (index != -1) + { + Updater_QueueRemovePlugin(plugin); + } +} + +// native bool Updater_ForceUpdate(); +public int Native_ForceUpdate(Handle plugin, int numParams) +{ + int index = PluginToIndex(plugin); + + if (index == -1) + { + ThrowNativeError(SP_ERROR_NOT_FOUND, "Plugin not found in updater."); + } + else if (Updater_GetStatus(index) == Status_Idle) + { + Updater_Check(index); + return 1; + } + + return 0; +} + +// forward Action:Updater_OnPluginChecking(); +Action Fwd_OnPluginChecking(Handle plugin) +{ + Action result = Plugin_Continue; + Function func = GetFunctionByName(plugin, "Updater_OnPluginChecking"); + + if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginChecking, plugin, func)) + { + Call_StartForward(fwd_OnPluginChecking); + Call_Finish(result); + RemoveAllFromForward(fwd_OnPluginChecking, plugin); + } + + return result; +} + +// forward Action:Updater_OnPluginDownloading(); +Action Fwd_OnPluginDownloading(Handle plugin) +{ + Action result = Plugin_Continue; + Function func = GetFunctionByName(plugin, "Updater_OnPluginDownloading"); + + if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginDownloading, plugin, func)) + { + Call_StartForward(fwd_OnPluginDownloading); + Call_Finish(result); + RemoveAllFromForward(fwd_OnPluginDownloading, plugin); + } + + return result; +} + +// forward Updater_OnPluginUpdating(); +void Fwd_OnPluginUpdating(Handle plugin) +{ + Function func = GetFunctionByName(plugin, "Updater_OnPluginUpdating"); + + if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginUpdating, plugin, func)) + { + Call_StartForward(fwd_OnPluginUpdating); + Call_Finish(); + RemoveAllFromForward(fwd_OnPluginUpdating, plugin); + } +} + +// forward Updater_OnPluginUpdated(); +void Fwd_OnPluginUpdated(Handle plugin) +{ + Function func = GetFunctionByName(plugin, "Updater_OnPluginUpdated"); + + if (func != INVALID_FUNCTION && AddToForward(fwd_OnPluginUpdated, plugin, func)) + { + Call_StartForward(fwd_OnPluginUpdated); + Call_Finish(); + RemoveAllFromForward(fwd_OnPluginUpdated, plugin); + } +} diff --git a/scripting/updater/download.sp b/scripting/updater/download.sp new file mode 100644 index 0000000..24c9d67 --- /dev/null +++ b/scripting/updater/download.sp @@ -0,0 +1,235 @@ + +/* Download Manager */ + +#include "updater/download_curl.sp" +#include "updater/download_socket.sp" +#include "updater/download_steamtools.sp" +#include "updater/download_steamworks.sp" + +DataPackPos QueuePack_URL; + +void FinalizeDownload(int index) +{ + /* Strip the temporary file extension from downloaded files. */ + char newpath[PLATFORM_MAX_PATH]; + char oldpath[PLATFORM_MAX_PATH]; + Handle hFiles = Updater_GetFiles(index); + + int maxFiles = GetArraySize(hFiles); + for (int i = 0; i < maxFiles; i++) + { + GetArrayString(hFiles, i, newpath, sizeof(newpath)); + Format(oldpath, sizeof(oldpath), "%s.%s", newpath, TEMP_FILE_EXT); + + // Rename doesn't overwrite on Windows. Make sure the path is clear. + if (FileExists(newpath)) + { + DeleteFile(newpath); + } + + RenameFile(newpath, oldpath); + } + + ClearArray(hFiles); +} + +void AbortDownload(int index) +{ + /* Delete all downloaded temporary files. */ + char path[PLATFORM_MAX_PATH]; + Handle hFiles = Updater_GetFiles(index); + + int maxFiles = GetArraySize(hFiles); + for (int i = 0; i < maxFiles; i++) + { + GetArrayString(hFiles, 0, path, sizeof(path)); + Format(path, sizeof(path), "%s.%s", path, TEMP_FILE_EXT); + + if (FileExists(path)) + { + DeleteFile(path); + } + } + + ClearArray(hFiles); +} + +void ProcessDownloadQueue(bool force=false) +{ + if (!force && (g_bDownloading || !GetArraySize(g_hDownloadQueue))) + { + return; + } + + Handle hQueuePack = GetArrayCell(g_hDownloadQueue, 0); + SetPackPosition(hQueuePack, QueuePack_URL); + + char url[MAX_URL_LENGTH]; + char dest[PLATFORM_MAX_PATH]; + ReadPackString(hQueuePack, url, sizeof(url)); + ReadPackString(hQueuePack, dest, sizeof(dest)); + + if (!CURL_AVAILABLE() && !SOCKET_AVAILABLE() && !STEAMTOOLS_AVAILABLE() && !STEAMWORKS_AVAILABLE()) + { + SetFailState(EXTENSION_ERROR); + } + +#if defined DEBUG + Updater_DebugLog("Download started:"); + Updater_DebugLog(" [0] URL: %s", url); + Updater_DebugLog(" [1] Destination: %s", dest); +#endif + + g_bDownloading = true; + + if (STEAMWORKS_AVAILABLE()) + { + if (SteamWorks_IsLoaded()) + { + Download_SteamWorks(url, dest); + } + else + { + CreateTimer(10.0, Timer_RetryQueue); + } + } + else if (STEAMTOOLS_AVAILABLE()) + { + if (g_bSteamLoaded) + { + Download_SteamTools(url, dest); + } + else + { + CreateTimer(10.0, Timer_RetryQueue); + } + } + else if (CURL_AVAILABLE()) + { + Download_cURL(url, dest); + } + else if (SOCKET_AVAILABLE()) + { + Download_Socket(url, dest); + } +} + +public Action Timer_RetryQueue(Handle timer) +{ + ProcessDownloadQueue(true); + + return Plugin_Stop; +} + +void AddToDownloadQueue(int index, const char[] url, const char[] dest) +{ + Handle hQueuePack = CreateDataPack(); + WritePackCell(hQueuePack, index); + + QueuePack_URL = GetPackPosition(hQueuePack); + WritePackString(hQueuePack, url); + WritePackString(hQueuePack, dest); + + PushArrayCell(g_hDownloadQueue, hQueuePack); + + ProcessDownloadQueue(); +} + +void DownloadEnded(bool successful, const char[] error="") +{ + Handle hQueuePack = GetArrayCell(g_hDownloadQueue, 0); + ResetPack(hQueuePack); + + char url[MAX_URL_LENGTH]; + char dest[PLATFORM_MAX_PATH]; + int index = ReadPackCell(hQueuePack); + ReadPackString(hQueuePack, url, sizeof(url)); + ReadPackString(hQueuePack, dest, sizeof(dest)); + + // Remove from the queue. + CloseHandle(hQueuePack); + RemoveFromArray(g_hDownloadQueue, 0); + +#if defined DEBUG + Updater_DebugLog(" [2] Successful: %s", successful ? "Yes" : "No"); +#endif + + switch (Updater_GetStatus(index)) + { + case Status_Checking: + { + if (!successful || !ParseUpdateFile(index, dest)) + { + Updater_SetStatus(index, Status_Idle); + +#if defined DEBUG + if (error[0] != '\0') + { + Updater_DebugLog(" [2] %s", error); + } +#endif + } + } + + case Status_Downloading: + { + if (successful) + { + // Check if this was the last file we needed. + char lastfile[PLATFORM_MAX_PATH]; + Handle hFiles = Updater_GetFiles(index); + + GetArrayString(hFiles, GetArraySize(hFiles) - 1, lastfile, sizeof(lastfile)); + Format(lastfile, sizeof(lastfile), "%s.%s", lastfile, TEMP_FILE_EXT); + + if (StrEqual(dest, lastfile)) + { + Handle hPlugin = IndexToPlugin(index); + + Fwd_OnPluginUpdating(hPlugin); + FinalizeDownload(index); + + char sName[64]; + if (!GetPluginInfo(hPlugin, PlInfo_Name, sName, sizeof(sName))) + { + strcopy(sName, sizeof(sName), "Null"); + } + + Updater_Log("Successfully updated and installed \"%s\".", sName); + + Updater_SetStatus(index, Status_Updated); + Fwd_OnPluginUpdated(hPlugin); + } + } + else + { + // Failed during an update. + AbortDownload(index); + Updater_SetStatus(index, Status_Error); + + char filename[64]; + GetPluginFilename(IndexToPlugin(index), filename, sizeof(filename)); + Updater_Log("Error downloading update for plugin: %s", filename); + Updater_Log(" [0] URL: %s", url); + Updater_Log(" [1] Destination: %s", dest); + + if (error[0] != '\0') + { + Updater_Log(" [2] %s", error); + } + } + } + + case Status_Error: + { + // Delete any additional files that this plugin had queued. + if (successful && FileExists(dest)) + { + DeleteFile(dest); + } + } + } + + g_bDownloading = false; + ProcessDownloadQueue(); +} diff --git a/scripting/updater/download_curl.sp b/scripting/updater/download_curl.sp new file mode 100644 index 0000000..67a99df --- /dev/null +++ b/scripting/updater/download_curl.sp @@ -0,0 +1,62 @@ + +/* Extension Helper - cURL */ + +void Download_cURL(const char[] url, const char[] dest) +{ + char sURL[MAX_URL_LENGTH]; + PrefixURL(sURL, sizeof(sURL), url); + + Handle hFile = curl_OpenFile(dest, "wb"); + + if (hFile == INVALID_HANDLE) + { + char sError[256]; + FormatEx(sError, sizeof(sError), "Error writing to file: %s", dest); + DownloadEnded(false, sError); + return; + } + + int CURL_Default_opt[][2] = { + {view_as(CURLOPT_NOSIGNAL), 1}, + {view_as(CURLOPT_NOPROGRESS), 1}, + {view_as(CURLOPT_TIMEOUT), 30}, + {view_as(CURLOPT_CONNECTTIMEOUT), 60}, + {view_as(CURLOPT_VERBOSE), 0} + }; + + Handle headers = curl_slist(); + curl_slist_append(headers, "Pragma: no-cache"); + curl_slist_append(headers, "Cache-Control: no-cache"); + + Handle hDLPack = CreateDataPack(); + WritePackCell(hDLPack, view_as(hFile)); + WritePackCell(hDLPack, view_as(headers)); + + Handle curl = curl_easy_init(); + curl_easy_setopt_int_array(curl, CURL_Default_opt, sizeof(CURL_Default_opt)); + curl_easy_setopt_handle(curl, CURLOPT_WRITEDATA, hFile); + curl_easy_setopt_string(curl, CURLOPT_URL, url); + curl_easy_setopt_handle(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_perform_thread(curl, OnCurlComplete, hDLPack); +} + +public void OnCurlComplete(Handle curl, CURLcode code, any hDLPack) +{ + ResetPack(hDLPack); + CloseHandle(ReadPackCell(hDLPack)); // hFile + CloseHandle(ReadPackCell(hDLPack)); // headers + CloseHandle(hDLPack); + CloseHandle(curl); + + if(code == CURLE_OK) + { + DownloadEnded(true); + } + else + { + char sError[256]; + curl_easy_strerror(code, sError, sizeof(sError)); + Format(sError, sizeof(sError), "cURL error: %s", sError); + DownloadEnded(false, sError); + } +} diff --git a/scripting/updater/download_socket.sp b/scripting/updater/download_socket.sp new file mode 100644 index 0000000..faae258 --- /dev/null +++ b/scripting/updater/download_socket.sp @@ -0,0 +1,203 @@ + +/* Extension Helper - Socket */ + +#define MAX_REDIRECTS 5 + +static DataPackPos DLPack_Header; +static DataPackPos DLPack_Redirects; +static DataPackPos DLPack_File; +static DataPackPos DLPack_Request; + +void Download_Socket(const char[] url, const char[] dest) +{ + char sURL[MAX_URL_LENGTH]; + PrefixURL(sURL, sizeof(sURL), url); + + if (strncmp(sURL, "https://", 8) == 0) + { + char sError[256]; + FormatEx(sError, sizeof(sError), "Socket does not support HTTPs (URL: %s).", sURL); + DownloadEnded(false, sError); + return; + } + + Handle hFile = OpenFile(dest, "wb"); + + if (hFile == INVALID_HANDLE) + { + char sError[256]; + FormatEx(sError, sizeof(sError), "Error writing to file: %s", dest); + DownloadEnded(false, sError); + return; + } + + // Format HTTP GET method. + char hostname[64]; + char location[128]; + char filename[64]; + char sRequest[MAX_URL_LENGTH+128]; + ParseURL(sURL, hostname, sizeof(hostname), location, sizeof(location), filename, sizeof(filename)); + FormatEx(sRequest, sizeof(sRequest), "GET %s/%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", location, filename, hostname); + + Handle hDLPack = CreateDataPack(); + + DLPack_Header = GetPackPosition(hDLPack); + WritePackCell(hDLPack, 0); + + DLPack_Redirects = GetPackPosition(hDLPack); + WritePackCell(hDLPack, 0); + + DLPack_File = GetPackPosition(hDLPack); + WritePackCell(hDLPack, view_as(hFile)); + + DLPack_Request = GetPackPosition(hDLPack); + WritePackString(hDLPack, sRequest); + + Handle socket = SocketCreate(SOCKET_TCP, OnSocketError); + SocketSetArg(socket, hDLPack); + SocketSetOption(socket, ConcatenateCallbacks, 4096); + SocketConnect(socket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, hostname, 80); +} + +public void OnSocketConnected(Handle socket, any hDLPack) +{ + char sRequest[MAX_URL_LENGTH+128]; + SetPackPosition(hDLPack, DLPack_Request); + ReadPackString(hDLPack, sRequest, sizeof(sRequest)); + + SocketSend(socket, sRequest); +} + +public void OnSocketReceive(Handle socket, char[] data, const int size, any hDLPack) +{ + int idx = 0; + + // Check if the HTTP header has already been parsed. + SetPackPosition(hDLPack, DLPack_Header); + bool bParsedHeader = view_as(ReadPackCell(hDLPack)); + int iRedirects = ReadPackCell(hDLPack); + + if (!bParsedHeader) + { + // Parse header data. + if ((idx = StrContains(data, "\r\n\r\n")) == -1) + { + idx = 0; + } + else + { + idx += 4; + } + + if (strncmp(data, "HTTP/", 5) == 0) + { + // Check for location header. + int idx2 = StrContains(data, "\nLocation: ", false); + + if (idx2 > -1 && (idx2 < idx || !idx)) + { + if (++iRedirects > MAX_REDIRECTS) + { + CloseSocketHandles(socket, hDLPack); + DownloadEnded(false, "Socket error: too many redirects."); + return; + } + else + { + SetPackPosition(hDLPack, DLPack_Redirects); + WritePackCell(hDLPack, iRedirects); + } + + // skip to url + idx2 += 11; + + char sURL[MAX_URL_LENGTH]; + strcopy(sURL, (FindCharInString(data[idx2], '\r') + 1), data[idx2]); + + PrefixURL(sURL, sizeof(sURL), sURL); + +#if defined DEBUG + Updater_DebugLog(" [ ] Redirected: %s", sURL); +#endif + + if (strncmp(sURL, "https://", 8) == 0) + { + CloseSocketHandles(socket, hDLPack); + + char sError[256]; + FormatEx(sError, sizeof(sError), "Socket does not support HTTPs (URL: %s).", sURL); + DownloadEnded(false, sError); + return; + } + + char hostname[64]; + char location[128]; + char filename[64]; + char sRequest[MAX_URL_LENGTH+128]; + ParseURL(sURL, hostname, sizeof(hostname), location, sizeof(location), filename, sizeof(filename)); + FormatEx(sRequest, sizeof(sRequest), "GET %s/%s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n\r\n", location, filename, hostname); + + SetPackPosition(hDLPack, DLPack_Request); // sRequest + WritePackString(hDLPack, sRequest); + + Handle newSocket = SocketCreate(SOCKET_TCP, OnSocketError); + SocketSetArg(newSocket, hDLPack); + SocketSetOption(newSocket, ConcatenateCallbacks, 4096); + SocketConnect(newSocket, OnSocketConnected, OnSocketReceive, OnSocketDisconnected, hostname, 80); + + CloseHandle(socket); + return; + } + + // Check HTTP status code + char sStatusCode[64]; + strcopy(sStatusCode, (FindCharInString(data, '\r') - 8), data[9]); + + if (strncmp(sStatusCode, "200", 3) != 0) + { + CloseSocketHandles(socket, hDLPack); + + char sError[256]; + FormatEx(sError, sizeof(sError), "Socket error: %s", sStatusCode); + DownloadEnded(false, sError); + return; + } + } + + SetPackPosition(hDLPack, DLPack_Header); + WritePackCell(hDLPack, 1); // bParsedHeader + } + + // Write data to file. + SetPackPosition(hDLPack, DLPack_File); + Handle hFile = view_as(ReadPackCell(hDLPack)); + + while (idx < size) + { + WriteFileCell(hFile, data[idx++], 1); + } +} + +public void OnSocketDisconnected(Handle socket, any hDLPack) +{ + CloseSocketHandles(socket, hDLPack); + + DownloadEnded(true); +} + +public void OnSocketError(Handle socket, const int errorType, const int errorNum, any hDLPack) +{ + CloseSocketHandles(socket, hDLPack); + + char sError[256]; + FormatEx(sError, sizeof(sError), "Socket error: %d (Error code %d)", errorType, errorNum); + DownloadEnded(false, sError); +} + +void CloseSocketHandles(Handle socket, Handle hDLPack) +{ + SetPackPosition(hDLPack, DLPack_File); + CloseHandle(view_as(ReadPackCell(hDLPack))); // hFile + CloseHandle(hDLPack); + CloseHandle(socket); +} diff --git a/scripting/updater/download_steamtools.sp b/scripting/updater/download_steamtools.sp new file mode 100644 index 0000000..cb7a1e7 --- /dev/null +++ b/scripting/updater/download_steamtools.sp @@ -0,0 +1,51 @@ + +/* Extension Helper - SteamTools */ + +void Download_SteamTools(const char[] url, const char[] dest) +{ + char sURL[MAX_URL_LENGTH]; + PrefixURL(sURL, sizeof(sURL), url); + + Handle hDLPack = CreateDataPack(); + WritePackString(hDLPack, dest); + + HTTPRequestHandle hRequest = Steam_CreateHTTPRequest(HTTPMethod_GET, sURL); + Steam_SetHTTPRequestHeaderValue(hRequest, "Pragma", "no-cache"); + Steam_SetHTTPRequestHeaderValue(hRequest, "Cache-Control", "no-cache"); + Steam_SendHTTPRequest(hRequest, OnSteamHTTPComplete, hDLPack); +} + +public void OnSteamHTTPComplete(HTTPRequestHandle HTTPRequest, bool requestSuccessful, HTTPStatusCode statusCode, any hDLPack) +{ + char sDest[PLATFORM_MAX_PATH]; + ResetPack(hDLPack); + ReadPackString(hDLPack, sDest, sizeof(sDest)); + CloseHandle(hDLPack); + + if (requestSuccessful && statusCode == HTTPStatusCode_OK) + { + Steam_WriteHTTPResponseBody(HTTPRequest, sDest); + DownloadEnded(true); + } + else + { + char sError[256]; + FormatEx(sError, sizeof(sError), "SteamTools error (status code %i). Request successful: %s", view_as(statusCode), requestSuccessful ? "True" : "False"); + DownloadEnded(false, sError); + } + + Steam_ReleaseHTTPRequest(HTTPRequest); +} + +/* Keep track of SteamTools load state. */ +bool g_bSteamLoaded; + +public void Steam_FullyLoaded() +{ + g_bSteamLoaded = true; +} + +public void Steam_Shutdown() +{ + g_bSteamLoaded = false; +} diff --git a/scripting/updater/download_steamworks.sp b/scripting/updater/download_steamworks.sp new file mode 100644 index 0000000..d2c2fbf --- /dev/null +++ b/scripting/updater/download_steamworks.sp @@ -0,0 +1,40 @@ + +/* Extension Helper - SteamWorks */ + +void Download_SteamWorks(const char[] url, const char[] dest) +{ + char sURL[MAX_URL_LENGTH]; + PrefixURL(sURL, sizeof(sURL), url); + + Handle hDLPack = CreateDataPack(); + WritePackString(hDLPack, dest); + + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sURL); + SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Pragma", "no-cache"); + SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Cache-Control", "no-cache"); + SteamWorks_SetHTTPCallbacks(hRequest, OnSteamWorksHTTPComplete); + SteamWorks_SetHTTPRequestContextValue(hRequest, hDLPack); + SteamWorks_SendHTTPRequest(hRequest); +} + +public void OnSteamWorksHTTPComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any hDLPack) +{ + char sDest[PLATFORM_MAX_PATH]; + ResetPack(hDLPack); + ReadPackString(hDLPack, sDest, sizeof(sDest)); + CloseHandle(hDLPack); + + if (bRequestSuccessful && eStatusCode == k_EHTTPStatusCode200OK) + { + SteamWorks_WriteHTTPResponseBodyToFile(hRequest, sDest); + DownloadEnded(true); + } + else + { + char sError[256]; + FormatEx(sError, sizeof(sError), "SteamWorks error (status code %i). Request successful: %s", view_as(eStatusCode), bRequestSuccessful ? "True" : "False"); + DownloadEnded(false, sError); + } + + CloseHandle(hRequest); +} diff --git a/scripting/updater/filesys.sp b/scripting/updater/filesys.sp new file mode 100644 index 0000000..a3569ba --- /dev/null +++ b/scripting/updater/filesys.sp @@ -0,0 +1,385 @@ + +/* File System Parsers */ + +// Strip filename from path. +void StripPathFilename(char[] path) +{ + strcopy(path, FindCharInString(path, '/', true) + 1, path); +} + +// Return the filename and extension from a given path. +void GetPathBasename(char[] path, char[] buffer, int maxlength) +{ + int check = -1; + if ((check = FindCharInString(path, '/', true)) != -1 || + (check = FindCharInString(path, '\\', true)) != -1) + { + strcopy(buffer, maxlength, path[check+1]); + } + else + { + strcopy(buffer, maxlength, path); + } +} + +// Add http protocol to url if it's missing. +void PrefixURL(char[] buffer, int maxlength, const char[] url) +{ + if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0) + { + Format(buffer, maxlength, "http://%s", url); + } + else + { + strcopy(buffer, maxlength, url); + } +} + +// Split URL into hostname, location, and filename. No trailing slashes. +void ParseURL(const char[] url, char[] host, int maxHost, char[] location, int maxLoc, char[] filename, int maxName) +{ + // Strip url prefix. + int idx = StrContains(url, "://"); + idx = (idx != -1) ? idx + 3 : 0; + + char dirs[16][64]; + int total = ExplodeString(url[idx], "/", dirs, sizeof(dirs), sizeof(dirs[])); + + // host + Format(host, maxHost, "%s", dirs[0]); + + // location + location[0] = '\0'; + for (int i = 1; i < total - 1; i++) + { + Format(location, maxLoc, "%s/%s", location, dirs[i]); + } + + // filename + Format(filename, maxName, "%s", dirs[total-1]); +} + +// Converts Updater SMC file paths into paths relative to the game folder. +void ParseSMCPathForLocal(const char[] path, char[] buffer, int maxlength) +{ + char dirs[16][64]; + int total = ExplodeString(path, "/", dirs, sizeof(dirs), sizeof(dirs[])); + + if (StrEqual(dirs[0], "Path_SM")) + { + BuildPath(Path_SM, buffer, maxlength, ""); + } + else // Path_Mod + { + buffer[0] = '\0'; + } + + // Construct the path and create directories if needed. + for (int i = 1; i < total - 1; i++) + { + Format(buffer, maxlength, "%s%s/", buffer, dirs[i]); + + if(!DirExists(buffer)) + { + CreateDirectory(buffer, 511); + } + } + + // Add the filename to the end of the path. + Format(buffer, maxlength, "%s%s", buffer, dirs[total-1]); +} + +// Converts Updater SMC file paths into paths relative to the plugin's update URL. +void ParseSMCPathForDownload(const char[] path, char[] buffer, int maxlength) +{ + char dirs[16][64]; + int total = ExplodeString(path, "/", dirs, sizeof(dirs), sizeof(dirs[])); + + // Construct the path. + buffer[0] = '\0'; + for (int i = 1; i < total; i++) + { + Format(buffer, maxlength, "%s/%s", buffer, dirs[i]); + } +} + +// Parses a plugin's update file. +// Logs update notes and begins download if required. +// Returns true if an update was available. +static Handle SMC_Sections; +static Handle SMC_DataTrie; +static Handle SMC_DataPack; +static int SMC_LineNum; + +bool ParseUpdateFile(int index, const char[] path) +{ + /* Return true if an update was available. */ + SMC_Sections = CreateArray(64); + SMC_DataTrie = CreateTrie(); + SMC_DataPack = CreateDataPack(); + SMC_LineNum = 0; + + Handle smc = SMC_CreateParser(); + + SMC_SetRawLine(smc, Updater_RawLine); + SMC_SetReaders(smc, Updater_NewSection, Updater_KeyValue, Updater_EndSection); + + char sBuffer[MAX_URL_LENGTH]; + Handle hPack; + bool bUpdate = false; + SMCError err = SMC_ParseFile(smc, path); + + if (err == SMCError_Okay) + { + // Initialize data + Handle hPlugin = IndexToPlugin(index); + Handle hFiles = Updater_GetFiles(index); + ClearArray(hFiles); + + // current version. + char sCurrentVersion[16]; + + if (!GetPluginInfo(hPlugin, PlInfo_Version, sCurrentVersion, sizeof(sCurrentVersion))) + { + strcopy(sCurrentVersion, sizeof(sCurrentVersion), "Null"); + } + + // latest version. + char smcLatestVersion[16]; + + if (GetTrieValue(SMC_DataTrie, "version->latest", hPack)) + { + ResetPack(hPack); + ReadPackString(hPack, smcLatestVersion, sizeof(smcLatestVersion)); + } + + // Check if we have the latest version. + if (!StrEqual(sCurrentVersion, smcLatestVersion)) + { + char sFilename[64]; + char sName[64]; + GetPluginFilename(hPlugin, sFilename, sizeof(sFilename)); + + if (GetPluginInfo(hPlugin, PlInfo_Name, sName, sizeof(sName))) + { + Updater_Log("Update available for \"%s\" (%s). Current: %s - Latest: %s", sName, sFilename, sCurrentVersion, smcLatestVersion); + } + else + { + Updater_Log("Update available for \"%s\". Current: %s - Latest: %s", sFilename, sCurrentVersion, smcLatestVersion); + } + + if (GetTrieValue(SMC_DataTrie, "information->notes", hPack)) + { + ResetPack(hPack); + + int iCount = 0; + while (IsPackReadable(hPack, 1)) + { + ReadPackString(hPack, sBuffer, sizeof(sBuffer)); + Updater_Log(" [%i] %s", iCount++, sBuffer); + } + } + + // Log update notes, save file list, and begin downloading. + if (g_bGetDownload && Fwd_OnPluginDownloading(hPlugin) == Plugin_Continue) + { + // Get previous version. + char smcPrevVersion[16]; + if (GetTrieValue(SMC_DataTrie, "version->previous", hPack)) + { + ResetPack(hPack); + ReadPackString(hPack, smcPrevVersion, sizeof(smcPrevVersion)); + } + + // Check if we only need the patch files. + if (StrEqual(sCurrentVersion, smcPrevVersion) && GetTrieValue(SMC_DataTrie, "patch->plugin", hPack)) + { + ParseSMCFilePack(index, hPack, hFiles); + + if (g_bGetSource && GetTrieValue(SMC_DataTrie, "patch->source", hPack)) + { + ParseSMCFilePack(index, hPack, hFiles); + } + } + else if (GetTrieValue(SMC_DataTrie, "files->plugin", hPack)) + { + ParseSMCFilePack(index, hPack, hFiles); + + if (g_bGetSource && GetTrieValue(SMC_DataTrie, "files->source", hPack)) + { + ParseSMCFilePack(index, hPack, hFiles); + } + } + + Updater_SetStatus(index, Status_Downloading); + } + else + { + // We don't want to spam the logs with the same update notification. + Updater_SetStatus(index, Status_Updated); + } + + bUpdate = true; + } + +#if defined DEBUG + iCount = 0; + + Updater_DebugLog(" "); + Updater_DebugLog("SMC DEBUG"); + ResetPack(SMC_DataPack); + + while (IsPackReadable(SMC_DataPack, 1)) + { + ReadPackString(SMC_DataPack, sBuffer, sizeof(sBuffer)); + Updater_DebugLog("%s", sBuffer); + + if (GetTrieValue(SMC_DataTrie, sBuffer, hPack)) + { + iCount = 0; + ResetPack(hPack); + + while (IsPackReadable(hPack, 1)) + { + ReadPackString(hPack, sBuffer, sizeof(sBuffer)); + Updater_DebugLog(" [%i] %s", iCount++, sBuffer); + } + } + } + Updater_DebugLog("END SMC DEBUG"); + Updater_DebugLog(" "); +#endif + } + else + { + Updater_Log("SMC parsing error on line %d", SMC_LineNum); + + Updater_GetURL(index, sBuffer, sizeof(sBuffer)); + Updater_Log(" [0] URL: %s", sBuffer); + + if (SMC_GetErrorString(err, sBuffer, sizeof(sBuffer))) + { + Updater_Log(" [1] ERROR: %s", sBuffer); + } + } + + // Clean up SMC data. + ResetPack(SMC_DataPack); + + while (IsPackReadable(SMC_DataPack, 1)) + { + ReadPackString(SMC_DataPack, sBuffer, sizeof(sBuffer)); + + if (GetTrieValue(SMC_DataTrie, sBuffer, hPack)) + { + CloseHandle(hPack); + } + } + + CloseHandle(SMC_Sections); + CloseHandle(SMC_DataTrie); + CloseHandle(SMC_DataPack); + CloseHandle(smc); + + return bUpdate; +} + +void ParseSMCFilePack(int index, Handle hPack, Handle hFiles) +{ + // Prepare URL + char urlprefix[MAX_URL_LENGTH]; + char url[MAX_URL_LENGTH]; + char dest[PLATFORM_MAX_PATH]; + char sBuffer[MAX_URL_LENGTH]; + Updater_GetURL(index, urlprefix, sizeof(urlprefix)); + StripPathFilename(urlprefix); + + ResetPack(hPack); + + while (IsPackReadable(hPack, 1)) + { + ReadPackString(hPack, sBuffer, sizeof(sBuffer)); + + // Merge url. + ParseSMCPathForDownload(sBuffer, url, sizeof(url)); + Format(url, sizeof(url), "%s%s", urlprefix, url); + + // Make sure the current plugin path matches the update. + ParseSMCPathForLocal(sBuffer, dest, sizeof(dest)); + + char sLocalBase[64]; + char sPluginBase[64]; + char sFilename[64]; + GetPathBasename(dest, sLocalBase, sizeof(sLocalBase)); + GetPathBasename(sFilename, sPluginBase, sizeof(sPluginBase)); + + if (StrEqual(sLocalBase, sPluginBase)) + { + StripPathFilename(dest); + Format(dest, sizeof(dest), "%s/%s", dest, sFilename); + } + + // Save the file location for later. + PushArrayString(hFiles, dest); + + // Add temporary file extension. + Format(dest, sizeof(dest), "%s.%s", dest, TEMP_FILE_EXT); + + // Begin downloading file. + AddToDownloadQueue(index, url, dest); + } +} + +public SMCResult Updater_RawLine(Handle smc, const char[] line, int lineno) +{ + SMC_LineNum = lineno; + return SMCParse_Continue; +} + +public SMCResult Updater_NewSection(Handle smc, const char[] name, bool opt_quotes) +{ + PushArrayString(SMC_Sections, name); + return SMCParse_Continue; +} + +public SMCResult Updater_KeyValue(Handle smc, const char[] key, const char[] value, bool key_quotes, bool value_quotes) +{ + char sCurSection[MAX_URL_LENGTH]; + char sKey[MAX_URL_LENGTH]; + Handle hPack; + + GetArrayString(SMC_Sections, GetArraySize(SMC_Sections)-1, sCurSection, sizeof(sCurSection)); + FormatEx(sKey, sizeof(sKey), "%s->%s", sCurSection, key); + StringToLower(sKey); + + if (!GetTrieValue(SMC_DataTrie, sKey, hPack)) + { + hPack = CreateDataPack(); + SetTrieValue(SMC_DataTrie, sKey, hPack); + WritePackString(SMC_DataPack, sKey); + } + + WritePackString(hPack, value); + return SMCParse_Continue; +} + +public SMCResult Updater_EndSection(Handle smc) +{ + if (GetArraySize(SMC_Sections)) + { + RemoveFromArray(SMC_Sections, GetArraySize(SMC_Sections)-1); + } + + return SMCParse_Continue; +} + +stock void StringToLower(char[] input) +{ + int length = strlen(input); + + for (int i = 0; i < length; i++) + { + input[i] = CharToLower(input[i]); + } +} diff --git a/scripting/updater/plugins.sp b/scripting/updater/plugins.sp new file mode 100644 index 0000000..cfb2658 --- /dev/null +++ b/scripting/updater/plugins.sp @@ -0,0 +1,196 @@ + +/* PluginPack Helpers */ + +static DataPackPos PluginPack_Plugin; +static DataPackPos PluginPack_Files; +static DataPackPos PluginPack_Status; +static DataPackPos PluginPack_URL; + +int GetMaxPlugins() +{ + return GetArraySize(g_hPluginPacks); +} + +bool IsValidPlugin(Handle plugin) +{ + /* Check if the plugin handle is pointing to a valid plugin. */ + Handle hIterator = GetPluginIterator(); + bool bIsValid = false; + + while (MorePlugins(hIterator)) + { + if (plugin == ReadPlugin(hIterator)) + { + bIsValid = true; + break; + } + } + + CloseHandle(hIterator); + return bIsValid; +} + +int PluginToIndex(Handle plugin) +{ + Handle hPluginPack = INVALID_HANDLE; + + int maxPlugins = GetMaxPlugins(); + for (int i = 0; i < maxPlugins; i++) + { + hPluginPack = GetArrayCell(g_hPluginPacks, i); + SetPackPosition(hPluginPack, PluginPack_Plugin); + + if (plugin == view_as(ReadPackCell(hPluginPack))) + { + return i; + } + } + + return -1; +} + +Handle IndexToPlugin(int index) +{ + Handle hPluginPack = GetArrayCell(g_hPluginPacks, index); + SetPackPosition(hPluginPack, PluginPack_Plugin); + return view_as(ReadPackCell(hPluginPack)); +} + +void Updater_AddPlugin(Handle plugin, const char[] url) +{ + int index = PluginToIndex(plugin); + + if (index != -1) + { + // Remove plugin from removal queue. + int maxPlugins = GetArraySize(g_hRemoveQueue); + for (int i = 0; i < maxPlugins; i++) + { + if (plugin == GetArrayCell(g_hRemoveQueue, i)) + { + RemoveFromArray(g_hRemoveQueue, i); + break; + } + } + + // Update the url. + Updater_SetURL(index, url); + } + else + { + Handle hPluginPack = CreateDataPack(); + Handle hFiles = CreateArray(PLATFORM_MAX_PATH); + + PluginPack_Plugin = GetPackPosition(hPluginPack); + WritePackCell(hPluginPack, view_as(plugin)); + + PluginPack_Files = GetPackPosition(hPluginPack); + WritePackCell(hPluginPack, view_as(hFiles)); + + PluginPack_Status = GetPackPosition(hPluginPack); + WritePackCell(hPluginPack, view_as(Status_Idle)); + + PluginPack_URL = GetPackPosition(hPluginPack); + WritePackString(hPluginPack, url); + + PushArrayCell(g_hPluginPacks, hPluginPack); + } +} + +void Updater_QueueRemovePlugin(Handle plugin) +{ + /* Flag a plugin for removal. */ + int maxPlugins = GetArraySize(g_hRemoveQueue); + for (int i = 0; i < maxPlugins; i++) + { + // Make sure it wasn't previously flagged. + if (plugin == GetArrayCell(g_hRemoveQueue, i)) + { + return; + } + } + + PushArrayCell(g_hRemoveQueue, plugin); + Updater_FreeMemory(); +} + +void Updater_RemovePlugin(int index) +{ + /* Warning: Removing a plugin will shift indexes. */ + CloseHandle(Updater_GetFiles(index)); // hFiles + CloseHandle(GetArrayCell(g_hPluginPacks, index)); // hPluginPack + RemoveFromArray(g_hPluginPacks, index); +} + +Handle Updater_GetFiles(int index) +{ + Handle hPluginPack = GetArrayCell(g_hPluginPacks, index); + SetPackPosition(hPluginPack, PluginPack_Files); + return view_as(ReadPackCell(hPluginPack)); +} + +UpdateStatus Updater_GetStatus(int index) +{ + Handle hPluginPack = GetArrayCell(g_hPluginPacks, index); + SetPackPosition(hPluginPack, PluginPack_Status); + return view_as(ReadPackCell(hPluginPack)); +} + +void Updater_SetStatus(int index, UpdateStatus status) +{ + Handle hPluginPack = GetArrayCell(g_hPluginPacks, index); + SetPackPosition(hPluginPack, PluginPack_Status); + WritePackCell(hPluginPack, view_as(status)); +} + +void Updater_GetURL(int index, char[] buffer, int size) +{ + Handle hPluginPack = GetArrayCell(g_hPluginPacks, index); + SetPackPosition(hPluginPack, PluginPack_URL); + ReadPackString(hPluginPack, buffer, size); +} + +void Updater_SetURL(int index, const char[] url) +{ + Handle hPluginPack = GetArrayCell(g_hPluginPacks, index); + SetPackPosition(hPluginPack, PluginPack_URL); + WritePackString(hPluginPack, url); +} + +/* Stocks */ +stock void ReloadPlugin(Handle plugin=INVALID_HANDLE) +{ + char filename[64]; + GetPluginFilename(plugin, filename, sizeof(filename)); + ServerCommand("sm plugins reload %s", filename); +} + +stock void UnloadPlugin(Handle plugin=INVALID_HANDLE) +{ + char filename[64]; + GetPluginFilename(plugin, filename, sizeof(filename)); + ServerCommand("sm plugins unload %s", filename); +} + +stock void DisablePlugin(Handle plugin=INVALID_HANDLE) +{ + char filename[64]; + char path_disabled[PLATFORM_MAX_PATH]; + char path_plugin[PLATFORM_MAX_PATH]; + + GetPluginFilename(plugin, filename, sizeof(filename)); + BuildPath(Path_SM, path_disabled, sizeof(path_disabled), "plugins/disabled/%s", filename); + BuildPath(Path_SM, path_plugin, sizeof(path_plugin), "plugins/%s", filename); + + if (FileExists(path_disabled)) + { + DeleteFile(path_disabled); + } + + if (!RenameFile(path_disabled, path_plugin)) + { + DeleteFile(path_plugin); + } + + ServerCommand("sm plugins unload %s", filename); +}