From bed61b5a23e8310a953f5fa32736a9991f096a8b Mon Sep 17 00:00:00 2001 From: BotoX Date: Sun, 26 Feb 2017 00:25:51 +0100 Subject: [PATCH] Add SteamCore + inviter --- SteamCore/cfg/plugin.steamcore.cfg | 20 + SteamCore/scripting/include/steamcore.inc | 89 +++ SteamCore/scripting/inviter.sp | 157 ++++ SteamCore/scripting/steamcore.sp | 833 ++++++++++++++++++++++ SteamCore/scripting/steamcore/bigint.sp | 634 ++++++++++++++++ SteamCore/scripting/steamcore/rsa.sp | 115 +++ 6 files changed, 1848 insertions(+) create mode 100644 SteamCore/cfg/plugin.steamcore.cfg create mode 100644 SteamCore/scripting/include/steamcore.inc create mode 100644 SteamCore/scripting/inviter.sp create mode 100644 SteamCore/scripting/steamcore.sp create mode 100644 SteamCore/scripting/steamcore/bigint.sp create mode 100644 SteamCore/scripting/steamcore/rsa.sp diff --git a/SteamCore/cfg/plugin.steamcore.cfg b/SteamCore/cfg/plugin.steamcore.cfg new file mode 100644 index 0000000..ecd52cc --- /dev/null +++ b/SteamCore/cfg/plugin.steamcore.cfg @@ -0,0 +1,20 @@ +// This file was auto-generated by SourceMod (v1.9.0.5932) +// ConVars for plugin "steamcore.smx" + + +// Toggles debugging. +// - +// Default: "0" +// Minimum: "0.000000" +// Maximum: "1.000000" +sc_debug "0" + +// Steam login username. +// - +// Default: "" +sc_username "" + +// Steam login password. +// - +// Default: "" +sc_password "" diff --git a/SteamCore/scripting/include/steamcore.inc b/SteamCore/scripting/include/steamcore.inc new file mode 100644 index 0000000..38f8b7e --- /dev/null +++ b/SteamCore/scripting/include/steamcore.inc @@ -0,0 +1,89 @@ +#if defined _steamcore_included + #endinput +#endif +#define _steamcore_included + +/* + Error Codes: + 0x00: No error, request successful. + 0x01: Plugin is busy with another task at this time. + 0x02: Connection timed out. + + 0x03: Login Error: Invalid login information, it means there are errors in the Cvar Strings. + 0x04: Login Error: Failed http RSA Key request. + 0x05: Login Error: RSA Key response failed, unknown reason, probably server side. + 0x06: Login Error: Failed htpps login request. + 0x07: Login Error: Incorrect login data, required captcha or e-mail confirmation (Steam Guard). + 0x08: Login Error: Failed http token request. + 0x09: Login Error: Invalid session token. Incorrect cookie?. + + 0x10: Announcement Error: Failed http group announcement request. + 0x11: Announcement Error: Invalid steam login token. + 0x12: Announcement Error: Form error on request. + + // Invitee: Who receives the invite. + 0x20: Invite Error: Failed http group invite request. + 0x21: Invite Error: Incorrect invitee or another error. + 0x22: Invite Error: Incorrect Group ID or missing data. + 0x23: Invite Error: Logged out. Retry to login. + 0x24: Invite Error: Inviter account is not a member of the group or does not have permissions to invite. + 0x25: Invite Error: Limited account. Only full Steam accounts can send Steam group invites + 0x26: Invite Error: Unknown error. + 0x27: Invite Error: Invitee has already received an invite or is already on the group. +*/ + +/** + * Callback function called at the end of a request + * + * @param client Calling client. + * @param success Result of the request. + * @param errorCode Result error code if error, otherwise 0. + * @param data Extra data if any, otherwise 0 + * + * @noreturn + */ +functag SteamCoreCallback public(client, bool:success, errorCode, any:data); + +/** + * Returns wheter the plugin is currently busy with a request. + * + * @return True is plugin is busy, false otherwise. +*/ +native bool:IsSteamCoreBusy(); + +/** + * Posts an announcement on a desired Steam group. + * + * @param client Debug purposes, calling client, use 0 if no client. + * @param title Title of the announce. + * @param body Body of the announce. + * @param group GroupID. + * @param func Callback function to be called at the end of the request. + * + * @noreturn + */ +native SteamGroupAnnouncement(client, const String:title[], const String:body[], const String:group[], SteamCoreCallback:func); + +/** + * Sends a Steam group invitation to an account. + * + * @param client Debug purposes, calling client, use 0 if no client. + * @param invitee SteamID64 of the account to invite. + * @param group GroupID. + * @param func Callback function to be called at the end of the request. + * + * @noreturn + */ +native SteamGroupInvite(client, const String:invitee[], const String:group[], SteamCoreCallback:func); + + +public SharedPlugin:__pl_steamcore = +{ + name = "steamcore", + file = "steamcore.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; \ No newline at end of file diff --git a/SteamCore/scripting/inviter.sp b/SteamCore/scripting/inviter.sp new file mode 100644 index 0000000..3a6328c --- /dev/null +++ b/SteamCore/scripting/inviter.sp @@ -0,0 +1,157 @@ +#pragma semicolon 1 + +#include +#include + +#define PLUGIN_URL "" +#define PLUGIN_VERSION "1.1" +#define PLUGIN_NAME "Inviter" +#define PLUGIN_AUTHOR "Statik" + +public Plugin:myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = "Steam group invites via game commands.", + version = PLUGIN_VERSION, + url = PLUGIN_URL +} + +new Handle:cvarGroupID; +new Handle:cvarAdminFlags; +new Handle:cvarAllInviteThemselves; +new Handle:cvarAllInviteOthers; +new Handle:cvarTimeBetweenInvites; + +new Handle:disabledClients; +new ReplySource:sources[MAXPLAYERS + 1]; + +public OnPluginStart() +{ + // Cvars + CreateConVar("inviter_version", PLUGIN_VERSION, "Force Picker Version", FCVAR_SPONLY | FCVAR_DONTRECORD | FCVAR_NOTIFY); + cvarGroupID = CreateConVar("in_steamgroupid", "", "Group id where people is going to be invited.", 0); + cvarAdminFlags = CreateConVar("in_adminflags", "b", "Administrator flags to bypass the restrictions.", 0); + cvarAllInviteThemselves = CreateConVar("in_allcaninvitethemselves.", "1", "Allows everybody to send invites to them themselves.", 0, true, 0.0, true, 1.0); + cvarAllInviteOthers = CreateConVar("in_allcaninviteothers.", "0", "Allows everybody to send invites to other clients.", 0, true, 0.0, true, 1.0); + cvarTimeBetweenInvites = CreateConVar("in_timebetweeninvites", "240", "Time between invites that non-admins must wait to send more invites.", 0, true, 0.0, true, 7200.0); + + RegConsoleCmd("sm_invite", cmdInvite, "Sends a group invite"); + RegConsoleCmd("sm_join", cmdInvite, "Sends a group invite"); + + disabledClients = CreateArray(); + + LoadTranslations("common.phrases"); + + AutoExecConfig(true, "plugin.inviter"); +} + +public Action:cmdInvite(client, args) +{ + new bool:isAdmin = IsClientAdmin(client); + + decl String:steamGroup[65]; + GetConVarString(cvarGroupID, steamGroup, sizeof(steamGroup)); + if (StrEqual(steamGroup, "")) + { + ReplyToCommand(client, "\x07FFF047Steam group is not configured."); + return Plugin_Handled; + } + + if (!isAdmin) + { + new id = GetSteamAccountID(client); + if (FindValueInArray(disabledClients, id) != -1) + { + ReplyToCommand(client, "\x07FFF047You must wait \x01%i \x07FFF047seconds or less to send another invite.", GetConVarInt(cvarTimeBetweenInvites)); + return Plugin_Handled; + } + new interval = GetConVarInt(cvarTimeBetweenInvites); + PushArrayCell(disabledClients, id); + CreateTimer(Float:interval, cooldown, id); + } + + if (args == 0) + { + if (client == 0) + { + ReplyToCommand(client, "You cannot invite a server to a Steam group."); + return Plugin_Handled; + } + if (isAdmin || GetConVarBool(cvarAllInviteThemselves)) + { + new String:steamID64[32]; + GetClientAuthId(client, AuthId_SteamID64, steamID64, sizeof steamID64); + sources[client] = GetCmdReplySource(); + SteamGroupInvite(client, steamID64, steamGroup, callback); + return Plugin_Handled; + } + ReplyToCommand(client, "\x07FFF047You do not have access to this command."); + return Plugin_Handled; + } + else if (args == 1) + { + if (isAdmin || GetConVarBool(cvarAllInviteOthers)) + { + decl String:arg[64]; + GetCmdArg(1, arg, sizeof arg); + new target = FindTarget(client, arg, true, false); + if (target == -1) + { + decl String:buffer[32]; + GetCmdArg(0, buffer, sizeof(buffer)); + ReplyToCommand(client, "\x07FFF047Incorrect target, usage: \x01%s [#userid|name]", buffer); + return Plugin_Handled; + } + new String:steamID64[32]; + GetClientAuthId(target, AuthId_SteamID64, steamID64, sizeof steamID64); + sources[client] = GetCmdReplySource(); + SteamGroupInvite(client, steamID64, steamGroup, callback); + return Plugin_Handled; + } + ReplyToCommand(client, "\x07FFF047You are not allowed to invite other people."); + return Plugin_Handled; + } + ReplyToCommand(client, "\x07FFF047Incorrect syntax, usage: \x01%s [#userid|name]"); + return Plugin_Handled; +} + +public Action:cooldown(Handle:timer, any:id) +{ + new i; + if ((i = FindValueInArray(disabledClients, id)) != -1) + RemoveFromArray(disabledClients, i); +} + +public callback(client, bool:success, errorCode, any:data) +{ + if (client != 0 && !IsClientInGame(client)) return; + + SetCmdReplySource(sources[client]); + if (success) ReplyToCommand(client, "\x07FFF047The group invite has been sent."); + else + { + if (errorCode < 0x10 || errorCode == 0x23) + { + new id = GetSteamAccountID(client); + new i; + if ((i = FindValueInArray(disabledClients, id)) != -1) + RemoveFromArray(disabledClients, i); + } + if (errorCode == 0x01) ReplyToCommand(client, "\x07FFF047Server is busy with another task at this time, try again in a few seconds."); + else if (errorCode == 0x02) ReplyToCommand(client, "\x07FFF047There was a timeout in your request, try again."); + else if (errorCode == 0x23) ReplyToCommand(client, "\x07FFF047Session expired, retry to reconnect."); + else if (errorCode == 0x27) ReplyToCommand(client, "\x07FFF047Target has already received an invite or is already on the group."); + else ReplyToCommand(client, "\x07FFF047There was an error \x010x%02x \x07FFF047while sending your invite :(", errorCode); + } +} + +public bool:IsClientAdmin(client) +{ + decl String:strFlags[32]; + GetConVarString(cvarAdminFlags, strFlags, sizeof strFlags); + new flags = ReadFlagString(strFlags); + if (flags & GetUserFlagBits(client) || ADMFLAG_ROOT & GetUserFlagBits(client)) + return true; + return false; +} diff --git a/SteamCore/scripting/steamcore.sp b/SteamCore/scripting/steamcore.sp new file mode 100644 index 0000000..15dd945 --- /dev/null +++ b/SteamCore/scripting/steamcore.sp @@ -0,0 +1,833 @@ +#pragma dynamic 4194304 // Increases stack space to 4mb, needed for encryption + +#include +#include + +// Core includes +#include "steamcore/bigint.sp" +#include "steamcore/rsa.sp" + +#define AUTOLOAD_EXTENSIONS +#define REQUIRE_EXTENSIONS +#include + +#define PLUGIN_URL "" +#define PLUGIN_VERSION "1.7" +#define PLUGIN_NAME "SteamCore" +#define PLUGIN_AUTHOR "Statik" + +public Plugin:myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = "Sourcemod natives to interact with Steam functions.", + version = PLUGIN_VERSION, + url = PLUGIN_URL +} + +new bool:DEBUG = false; + +new const TIMER_UPDATE_TIME = 6; +new const TOKEN_LIFETIME = 50; +new const Float:TIMEOUT_TIME = 10.0; + +new Handle:cvarUsername; +new Handle:cvarPassword; +new Handle:cvarDebug; + +new String:username[32] = ""; +new String:passphrase[32] = ""; +new String:sessionToken[32] = ""; +new String:sessionCookie[256] = ""; +new bool:isLogged = false; +new bool:isBusy = false; +new Handle:request; + +new caller; + +new timeSinceLastLogin; +new Handle:hTimeIncreaser; + +new Handle:timeoutTimer; +new bool:connectionInterrupted; + +new Handle:callbackHandle; +new Handle:callbackPlugin; +new Function:callbackFunction; +new Handle:finalRequest; +new SteamWorksHTTPRequestCompleted:finalFunction; + +// =================================================================================== +// =================================================================================== + +public APLRes:AskPluginLoad2(Handle:plugin, bool:late, String:error[], err_max) +{ + // Native creation + CreateNative("IsSteamCoreBusy", nativeIsSteamCoreBusy); + CreateNative("SteamGroupAnnouncement", nativeGroupAnnouncement); + CreateNative("SteamGroupInvite", nativeGroupInvite); + + RegPluginLibrary("steamcore"); + + return APLRes_Success; +} + +public OnPluginStart() +{ + // Callbacks + callbackHandle = CreateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell); + + // Timers + hTimeIncreaser = CreateTimer(TIMER_UPDATE_TIME*60.0, timeIncreaser, INVALID_HANDLE, TIMER_REPEAT); + + // Convars + CreateConVar("steamcore_version", PLUGIN_VERSION, "SteamCore Version", FCVAR_SPONLY | FCVAR_DONTRECORD | FCVAR_NOTIFY); + cvarUsername = CreateConVar("sc_username", "", "Steam login username.", FCVAR_PROTECTED); + cvarPassword = CreateConVar("sc_password", "", "Steam login password.", FCVAR_PROTECTED); + cvarDebug = CreateConVar("sc_debug", "0", "Toggles debugging.", 0, true, 0.0, true, 1.0); + + HookConVarChange(cvarUsername, OnLoginInfoChange); + HookConVarChange(cvarPassword, OnLoginInfoChange); + HookConVarChange(cvarDebug, OnDebugStatusChange); + + timeSinceLastLogin = TOKEN_LIFETIME; +} + +public OnLoginInfoChange(Handle:cvar, const String:oldVal[], const String:newVal[]) +{ + isLogged = false; +} + +public OnDebugStatusChange(Handle:cvar, const String:oldVal[], const String:newVal[]) +{ + DEBUG = bool:StringToInt(newVal); +} + +public Action:timeIncreaser(Handle:timer) +{ + timeSinceLastLogin += TIMER_UPDATE_TIME; + PrintDebug(0, "\n============================================================================\n"); + PrintDebug(0, "Time since last login: %i minutes.", timeSinceLastLogin); + if (timeSinceLastLogin >= TOKEN_LIFETIME) + { + isLogged = false; + PrintDebug(0, "Expired token lifetime (%i)", TOKEN_LIFETIME); + } + return Plugin_Continue; +} + +public OnConfigsExecuted() +{ + DEBUG = GetConVarBool(FindConVar("sc_debug")); + if (timeSinceLastLogin > 10) + { + PrintDebug(0, "\n============================================================================\n"); + PrintDebug(0, "Logging in to keep login alive..."); + startRequest(0, INVALID_HANDLE, INVALID_FUNCTION, INVALID_HANDLE, INVALID_FUNCTION); // Starts an empty login request + } +} + +// =================================================================================== +// =================================================================================== + +public nativeIsSteamCoreBusy(Handle:plugin, numParams) +{ + return _:isBusy; +} + +public nativeGroupAnnouncement(Handle:plugin, numParams) +{ + decl String:title[256]; + decl String:body[1024]; + decl String:groupID[64]; + new client = GetNativeCell(1); + GetNativeString(2, title, sizeof(title)); + GetNativeString(3, body, sizeof(body)); + GetNativeString(4, groupID, sizeof(groupID)); + + decl String:URL[128]; + Format(URL, sizeof(URL), "http://steamcommunity.com/gid/%s/announcements", groupID); + + PrintDebug(client, "\n============================================================================\n"); + PrintDebug(client, "Preparing request to: \n%s...", URL); + PrintDebug(client, "Title: \n%s", title); + PrintDebug(client, "Body: \n%s", body); + PrintDebug(client, "Verifying login..."); + + new Handle:_finalRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, URL); + SteamWorks_SetHTTPRequestHeaderValue(_finalRequest, "Cookie", sessionCookie); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "action", "post"); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "sessionID", sessionToken); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "headline", title); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "body", body); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "languages[0][headline]", title); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "languages[0][body]", body); + + startRequest(client, _finalRequest, cbkGroupAnnouncement, plugin, GetNativeFunction(5)); +} + +public nativeGroupInvite(Handle:plugin, numParams) +{ + decl String:invitee[64]; + decl String:groupID[64]; + new client = GetNativeCell(1); + GetNativeString(2, invitee, sizeof invitee); + GetNativeString(3, groupID, sizeof groupID); + + decl String:URL[] = "http://steamcommunity.com/actions/GroupInvite"; + + PrintDebug(client, "\n============================================================================\n"); + PrintDebug(client, "Preparing request to: \n%s...", URL); + PrintDebug(client, "Invitee community ID: \n%s", invitee); + PrintDebug(client, "Group community ID: \n%s", groupID); + PrintDebug(client, "Verifying login..."); + + new Handle:_finalRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, URL); + + SteamWorks_SetHTTPRequestHeaderValue(_finalRequest, "Accept", "*/*"); + SteamWorks_SetHTTPRequestHeaderValue(_finalRequest, "Accept-Encoding", "gzip, deflate"); + SteamWorks_SetHTTPRequestHeaderValue(_finalRequest, "User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)"); + SteamWorks_SetHTTPRequestHeaderValue(_finalRequest, "Cookie", sessionCookie); + + //SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "json", "1"); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "type", "groupInvite"); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "sessionID", sessionToken); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "group", groupID); + SteamWorks_SetHTTPRequestGetOrPostParameter(_finalRequest, "invitee", invitee); + + startRequest(client, _finalRequest, cbkGroupInvite, plugin, GetNativeFunction(4)); +} + +// =================================================================================== +// =================================================================================== + +startRequest(client, Handle:_finalRequest, SteamWorksHTTPRequestCompleted:_finalFunction, Handle:_callbackPlugin, Function:_callbackFunction) +{ + if (isBusy) + { + PrintDebug(client, "\n============================================================================\n"); + PrintDebug(client, "Plugin is busy with other task at this time, rejecting request..."); + + if (_callbackFunction != INVALID_FUNCTION) // There is an actual function callback + { + new pluginIteratorNumber = GetPluginIteratorNumber(_callbackPlugin); + new Handle:datapack; + CreateDataTimer(0.1, tmrBusyCallback, datapack); + WritePackCell(datapack, client); + WritePackCell(datapack, pluginIteratorNumber); + WritePackFunction(datapack, Function:_callbackFunction); + + CloseHandle(_finalRequest); + } + return; + } + isBusy = true; + connectionInterrupted = false; + + caller = client; + finalRequest = _finalRequest; + finalFunction = _finalFunction; + callbackPlugin = _callbackPlugin; + callbackFunction = _callbackFunction; + + PrintDebug(caller, "\n============================================================================\n"); + + if (callbackFunction != INVALID_FUNCTION) // There is an actual function callback + { + AddToForward(callbackHandle, callbackPlugin, callbackFunction); + + if (isLogged) + { + PrintDebug(caller, "Already logged in, executing request..."); + SteamWorks_SetHTTPCallbacks(finalRequest, finalFunction) + SteamWorks_SendHTTPRequest(finalRequest); + startTimeoutTimer(); + return; + } + } + GetConVarString(cvarUsername, username, sizeof(username)); + GetConVarString(cvarPassword, passphrase, sizeof(passphrase)); + + if (StrEqual(username, "") || StrEqual(passphrase, "")) + { + PrintDebug(caller, "Invalid login information, check cvars. ABORTED."); + onRequestResult(caller, false, 0x03); // Invalid login information + return; + } + + request = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, "http://steamcommunity.com/login/getrsakey/"); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "username", username); + SteamWorks_SetHTTPCallbacks(request, cbkRsaKeyRequest); + SteamWorks_SendHTTPRequest(request); + startTimeoutTimer(); + + PrintDebug(caller, "Obtaining RSA Key from steamcommunity.com/login/getrsakey..."); +} + +startTimeoutTimer() +{ + stopTimeoutTimer(); + timeoutTimer = CreateTimer(TIMEOUT_TIME, tmrTimeout); +} + +stopTimeoutTimer() +{ + if (timeoutTimer != INVALID_HANDLE) + { + KillTimer(timeoutTimer); + timeoutTimer = INVALID_HANDLE; + } +} + +public Action:tmrTimeout(Handle:timer) +{ + PrintDebug(caller, "Connection timed out."); + connectionInterrupted = true; + onRequestResult(caller, false, 0x02); + timeoutTimer = INVALID_HANDLE; +} + +public Action:tmrBusyCallback(Handle:timer, Handle:pack) +{ + ResetPack(pack); + new client = ReadPackCell(pack); + new pluginIteratorNumber = ReadPackCell(pack); + new Handle:callbackPl = FindPluginFromNumber(pluginIteratorNumber); + new Function:callbackFunc = ReadPackFunction(pack); + + new bool:success = RemoveFromForward(callbackHandle, callbackPlugin, callbackFunction); + new functionCount = GetForwardFunctionCount(callbackHandle); + PrintDebug(caller, "Removing main callback from forward - Result: %i, - Forward Function Count: %i", success, functionCount); + + success = AddToForward(callbackHandle, callbackPl, callbackFunc); + functionCount = GetForwardFunctionCount(callbackHandle); + PrintDebug(caller, "Adding temporal callback from forward - Result: %i, - Forward Function Count: %i", success, functionCount); + + // Start function call + Call_StartForward(callbackHandle); + // Push parameters one at a time + Call_PushCell(client); + Call_PushCell(false); + Call_PushCell(0x01); // Plugin is busy + Call_PushCell(0); + // Finish the call + new result = Call_Finish(); + PrintDebug(caller, "Temporal callback calling error code: %i (0: Success)", result); + + success = RemoveFromForward(callbackHandle, callbackPl, callbackFunc); + functionCount = GetForwardFunctionCount(callbackHandle); + PrintDebug(caller, "Removing temporal callback from forward - Result: %i, - Forward Function Count: %i", success, functionCount); + + success = AddToForward(callbackHandle, callbackPlugin, callbackFunction); + functionCount = GetForwardFunctionCount(callbackHandle); + PrintDebug(caller, "Re-adding main callback from forward - Result: %i, - Forward Function Count: %i", success, functionCount); + + callbackPl = INVALID_HANDLE; + callbackFunc = INVALID_FUNCTION; + + PrintDebug(caller, "Task rejected."); +} + +public cbkRsaKeyRequest(Handle:response, bool:failure, bool:requestSuccessful, EHTTPStatusCode:statusCode) +{ + stopTimeoutTimer(); + if (connectionInterrupted) + { + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + + if (response == INVALID_HANDLE || !requestSuccessful || statusCode != k_EHTTPStatusCode200OK) + { + PrintDebug(caller, "RSA Key request failed (%i). Status Code: %i. ABORTED", requestSuccessful, statusCode); + onRequestResult(caller, false, 0x04); // Failed http RSA Key request + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + new bodySize; + SteamWorks_GetHTTPResponseBodySize(request, bodySize); + decl String:responseBody[bodySize]; + SteamWorks_GetHTTPResponseBodyData(request, responseBody, bodySize); + PrintDebug(caller, responseBody); + + if (StrContains(responseBody, "\"success\":true", false) == -1) + { + PrintDebug(caller, "Could not get RSA Key, aborting..."); + onRequestResult(caller, false, 0x05); // RSA Key response failed, unknown reason + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + new Handle:regex; + regex = CompileRegex("\"publickey_mod\":\"(.*?)\""); + MatchRegex(regex, responseBody); + decl String:rsaPublicMod[1024]; + GetRegexSubString(regex, 1, rsaPublicMod, sizeof(rsaPublicMod)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + PrintDebug(caller, "RSA KEY MODULUS (%i): \n%s", strlen(rsaPublicMod), rsaPublicMod); + + regex = CompileRegex("\"publickey_exp\":\"(.*?)\""); + MatchRegex(regex, responseBody); + decl String:rsaPublicExp[16]; + GetRegexSubString(regex, 1, rsaPublicExp, sizeof(rsaPublicExp)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + PrintDebug(caller, "RSA KEY EXPONENT (%i): %s", strlen(rsaPublicExp), rsaPublicExp); + + regex = CompileRegex("\"timestamp\":\"(.*?)\""); + MatchRegex(regex, responseBody); + decl String:steamTimestamp[16]; + GetRegexSubString(regex, 1, steamTimestamp, sizeof(steamTimestamp)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + PrintDebug(caller, "STEAM TIMESTAMP (%i): %s", strlen(steamTimestamp), steamTimestamp); + + PrintDebug(caller, "\n============================================================================\n"); + + PrintDebug(caller, "Encrypting passphrase ******** with RSA public key..."); + decl String:encryptedPassword[1024]; + rsaEncrypt(rsaPublicMod, rsaPublicExp, passphrase, encryptedPassword, sizeof(encryptedPassword)); + PrintDebug(caller, "Encrypted passphrase with RSA cryptosystem (%i): \n%s", strlen(encryptedPassword), encryptedPassword); + + decl numericPassword[1024]; + hexString2BigInt(encryptedPassword, numericPassword, sizeof(numericPassword)); + encodeBase64(numericPassword, strlen(rsaPublicMod),encryptedPassword, sizeof(encryptedPassword)); + PrintDebug(caller, "Encoded encrypted passphrase with base64 algorithm (%i): \n%s", strlen(encryptedPassword), encryptedPassword); + + CloseHandle(request); + request = INVALID_HANDLE; + + PrintDebug(caller, "\n============================================================================\n"); + + PrintDebug(caller, "Logging in to steamcommunity.com/login/dologin/..."); + request = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, "https://steamcommunity.com/login/dologin/"); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "password", encryptedPassword); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "username", username); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "twofactorcode", ""); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "emailauth", ""); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "loginfriendlyname", ""); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "captchagid", ""); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "captcha_text", ""); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "emailsteamid", ""); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "rsatimestamp", steamTimestamp); + SteamWorks_SetHTTPRequestGetOrPostParameter(request, "remember_login", "false"); + SteamWorks_SetHTTPCallbacks(request, cbkLoginRequest); + SteamWorks_SendHTTPRequest(request); + startTimeoutTimer(); +} + +public cbkLoginRequest(Handle:response, bool:failure, bool:requestSuccessful, EHTTPStatusCode:statusCode) +{ + stopTimeoutTimer(); + if (connectionInterrupted) + { + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + + if (response == INVALID_HANDLE || !requestSuccessful || statusCode != k_EHTTPStatusCode200OK) + { + PrintDebug(caller, "Login request failed (%i). Status Code: %i. ABORTED", requestSuccessful, statusCode); + onRequestResult(caller, false, 0x06); // Failed htpps login request + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + new bodySize; + SteamWorks_GetHTTPResponseBodySize(response, bodySize); + decl String:responseBody[bodySize]; + SteamWorks_GetHTTPResponseBodyData(response, responseBody, bodySize); + + new Handle:regex; + regex = CompileRegex("\"success\":(.*?),"); + MatchRegex(regex, responseBody); + decl String:successString[20]; + GetRegexSubString(regex, 1, successString, sizeof(successString)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + if (strcmp(successString, "true") != 0) // successString != true + { + PrintDebug(caller, "Aborted logging, incorrect response body (%i): \n%s", strlen(responseBody), responseBody); + onRequestResult(caller, false, 0x07); // Incorrect login data, required captcha or e-mail confirmation (Steam Guard) + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + new cookieSize; + SteamWorks_GetHTTPResponseHeaderSize(response, "Set-Cookie", cookieSize); + SteamWorks_GetHTTPResponseHeaderValue(response, "Set-Cookie", sessionCookie, cookieSize); + + // Cleaning cookie + ReplaceString(sessionCookie, sizeof sessionCookie, "path=/,", ""); + ReplaceString(sessionCookie, sizeof sessionCookie, "path=/; httponly,", ""); + ReplaceString(sessionCookie, sizeof sessionCookie, "path=/; secure; httponly", ""); + + PrintDebug(caller, "Success, got response (%i): \n%s", strlen(responseBody), responseBody); + PrintDebug(caller, "Stored Cookie (%i): \n%s", strlen(sessionCookie), sessionCookie); + + + CloseHandle(request); + request = INVALID_HANDLE; + + PrintDebug(caller, "\n============================================================================\n"); + + PrintDebug(caller, "Logging successful, obtaining session token..."); + + request = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, "http://steamcommunity.com/profiles/RedirectToHome"); + SteamWorks_SetHTTPRequestHeaderValue(request, "Cookie", sessionCookie); + SteamWorks_SetHTTPCallbacks(request, cbkTokenRequest); + SteamWorks_SendHTTPRequest(request); + startTimeoutTimer(); +} + +public cbkTokenRequest(Handle:response, bool:failure, bool:requestSuccessful, EHTTPStatusCode:statusCode) +{ + stopTimeoutTimer(); + if (connectionInterrupted) + { + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + + if (response == INVALID_HANDLE || !requestSuccessful || statusCode != k_EHTTPStatusCode200OK) + { + PrintDebug(caller, "Session Token request failed (%i). Status Code: %i. ABORTED", requestSuccessful, statusCode); + onRequestResult(caller, false, 0x08); // Failed http token request + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + new bodySize; + SteamWorks_GetHTTPResponseBodySize(response, bodySize); + decl String:responseBody[bodySize]; + SteamWorks_GetHTTPResponseBodyData(response, responseBody, bodySize); + + new Handle:regex; + regex = CompileRegex("g_steamID = (.*?);"); + MatchRegex(regex, responseBody); + decl String:steamId[20]; + GetRegexSubString(regex, 1, steamId, sizeof(steamId)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + regex = CompileRegex("g_sessionID = \"(.*?)\""); + MatchRegex(regex, responseBody); + GetRegexSubString(regex, 1, sessionToken, sizeof(sessionToken)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + if (strcmp(steamId, "false") == 0) // steamId == false + { + PrintDebug(caller, "Could not get session token. Got: \"%s\". Incorrect Cookie?", steamId); + onRequestResult(caller, false, 0x09); // Invalid session token. Incorrect cookie? + CloseHandle(request); + request = INVALID_HANDLE; + return; + } + isLogged = true; + + // Cleaning cookie + ReplaceString(sessionCookie, sizeof sessionCookie, "path=/; httponly,", ""); + ReplaceString(sessionCookie, sizeof sessionCookie, "path=/; secure; httponly", ""); + + Format(sessionCookie, sizeof sessionCookie, "Steam_Language=english; sessionid=%s; %s", sessionToken, sessionCookie); + + PrintDebug(caller, "Session token successfully acquired (%i): %s", strlen(sessionToken), sessionToken); + PrintDebug(caller, "Current session for Steam ID (%i): %s", strlen(steamId), steamId); + PrintDebug(caller, "Appended session token to clean cookie, actual cookie (%i): \n%s", strlen(sessionCookie), sessionCookie); + + if (finalRequest != INVALID_HANDLE) + { + PrintDebug(caller, "\n============================================================================\n"); + + PrintDebug(caller, "Executing final request..."); + SteamWorks_SetHTTPCallbacks(finalRequest, finalFunction); + SteamWorks_SendHTTPRequest(finalRequest); + startTimeoutTimer(); + } + else + { + PrintDebug(caller, "There is no final request, logged in successfully."); + onRequestResult(caller, true); + } + + CloseHandle(request); + request = INVALID_HANDLE; +} + +public cbkGroupAnnouncement(Handle:response, bool:failure, bool:requestSuccessful, EHTTPStatusCode:statusCode) +{ + stopTimeoutTimer(); + if (connectionInterrupted) + { + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + return; + } + + if (response == INVALID_HANDLE || !requestSuccessful || statusCode != k_EHTTPStatusCode200OK) + { + PrintDebug(caller, "Group announcement request failed (%i). Status Code: %i", requestSuccessful, statusCode); + onRequestResult(caller, false, 0x10); // Failed http group announcement request + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + return; + } + new cookieSize; + SteamWorks_GetHTTPResponseHeaderSize(response, "Set-Cookie", cookieSize); + decl String:cookie[cookieSize]; + SteamWorks_GetHTTPResponseHeaderValue(response, "Set-Cookie", cookie, cookieSize); + + new bodySize; + SteamWorks_GetHTTPResponseBodySize(response, bodySize); + decl String:responseBody[bodySize]; + SteamWorks_GetHTTPResponseBodyData(response, responseBody, bodySize); + + new Handle:regex; + regex = CompileRegex("steamLogin=(.*?);"); + MatchRegex(regex, cookie); + decl String:steamLogin[20]; + GetRegexSubString(regex, 1, steamLogin, sizeof(steamLogin)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + regex = CompileRegex("(.*?)"); + MatchRegex(regex, responseBody); + decl String:title[40]; + GetRegexSubString(regex, 1, title, sizeof(title)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + if (strcmp(steamLogin, "deleted") == 0) + { + isLogged = false; + PrintDebug(caller, "Invalid steam login token."); + onRequestResult(caller, false, 0x11); // Invalid steam login token + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + return; + } + if (strcmp(title, "Steam Community :: Error") == 0) + { + PrintDebug(caller, "Form error on request."); + onRequestResult(caller, false, 0x12); // Form error on request + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + return; + } + + onRequestResult(caller, true); + + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; +} + +public cbkGroupInvite(Handle:response, bool:failure, bool:requestSuccessful, EHTTPStatusCode:statusCode) +{ + stopTimeoutTimer(); + if (connectionInterrupted) + { + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + return; + } + + if (response == INVALID_HANDLE || !requestSuccessful || statusCode != k_EHTTPStatusCode200OK) + { + PrintDebug(caller, "Group invite request failed (%i). Status Code: %i", requestSuccessful, statusCode); + onRequestResult(caller, false, 0x20); // Failed http group invite request + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + return; + } + new bodySize; + SteamWorks_GetHTTPResponseBodySize(response, bodySize); + decl String:responseBody[bodySize]; + SteamWorks_GetHTTPResponseBodyData(response, responseBody, bodySize); + + new Handle:regex; + regex = CompileRegex("<\\/results>", PCRE_DOTALL); + MatchRegex(regex, responseBody); + decl String:result[2048]; + GetRegexSubString(regex, 1, result, sizeof(result)); + CloseHandle(regex); + regex = INVALID_HANDLE; + + if (!StrEqual(result, "OK")) + { + if (StrEqual(result, "The invitation to that player failed. Please try again.\n\nError code: 19")) + { + PrintDebug(caller, "Invite failed. Incorrect invitee id on request or another error."); + onRequestResult(caller, false, 0x21); // Incorrect invitee or another error + } + else if (StrEqual(result, "Missing Data")) + { + PrintDebug(caller, "Invite failed. Incorrect group id or missing data on request."); + onRequestResult(caller, false, 0x22); // Incorrect Group ID or missing data. + } + else if (StrEqual(result, "Missing or invalid form session key")) + { + isLogged = false; + PrintDebug(caller, "Invite failed. Plugin is not logged in. Try again to login."); + onRequestResult(caller, false, 0x23); // Logged out. Retry to login + } + else if (StrEqual(result, "You do not have permission to invite to the group specified.")) + { + PrintDebug(caller, "Invite failed. Inviter account is not a member of the group or does not have permissions to invite."); + onRequestResult(caller, false, 0x24); // Account does not have permissions to invite. + } + else if (StrEqual(result, "Your account does not meet the requirements to use this feature. Visit Steam Support for more information.")) + { + PrintDebug(caller, "Invite failed. Account is limited, only full Steam accounts can send group invites."); + onRequestResult(caller, false, 0x25); // Limited account. Only full Steam accounts can send Steam group invites + } + else + { + PrintDebug(caller, "Invite failed. Unknown error response received when sending the group invite."); + onRequestResult(caller, false, 0x26); // Unknown error + } + } + else + { + if (StrContains(responseBody, "") != -1) + { + PrintDebug(caller, "Invite failed. Invitee has already received an invite or is already on the group."); + onRequestResult(caller, false, 0x27); // Invitee has already received an invite or is already on the group. + } + else + { + PrintDebug(caller, "Group invite sent."); + onRequestResult(caller, true); // Success + } + } + PrintDebug(caller, "Response body (%i):\n %s", strlen(responseBody), responseBody); + + CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; +} + +onRequestResult(client, bool:success, errorCode=0, any:data=0) +{ + isBusy = false; + + PrintDebug(caller, "\n============================================================================\n"); + + PrintDebug(caller, "Final request result: %i - Error Code : %i", success, errorCode); + + if (success) + { + timeSinceLastLogin = 0; + KillTimer(hTimeIncreaser); + hTimeIncreaser = CreateTimer(TIMER_UPDATE_TIME*60.0, timeIncreaser, INVALID_HANDLE, TIMER_REPEAT); + } + // In case there was an error before the last request was executed, they are freed. + else if (errorCode > 0 && errorCode <= 0x0A) + { + if (finalRequest != INVALID_HANDLE) CloseHandle(finalRequest); + finalRequest = INVALID_HANDLE; + finalFunction = INVALID_FUNCTION; + } + if (callbackFunction != INVALID_FUNCTION) + { + PrintDebug(caller, "Calling callback..."); + // Start function call + Call_StartForward(callbackHandle); + // Push parameters one at a time + Call_PushCell(client); // Client + Call_PushCell(success); // Success + Call_PushCell(errorCode); // Error code + Call_PushCell(data); // Extra data, in this case nothing + // Finish the call + new result = Call_Finish(); + PrintDebug(caller, "Callback calling error code: %i (0: Success)", result); + + removeCallback(); + } + caller = 0; +} + +removeCallback() +{ + new bool:removed = RemoveFromForward(callbackHandle, callbackPlugin, callbackFunction); + new functionCount = GetForwardFunctionCount(callbackHandle); + PrintDebug(caller, "Removing callback from forward - Result: %i, - Forward Function Count: %i", removed, functionCount); + callbackFunction = INVALID_FUNCTION; +} + +// =================================================================================== +// =================================================================================== + +// Obtains the plugin index in a plugin iterator +GetPluginIteratorNumber(Handle:plugin) +{ + new pluginNumber = 0; + decl String:pluginName[256]; + decl String:auxPluginName[256]; + GetPluginFilename(plugin, pluginName, sizeof(pluginName)); + new Handle:pluginIterator = GetPluginIterator(); + while (MorePlugins(pluginIterator)) + { + pluginNumber++; + GetPluginFilename(ReadPlugin(pluginIterator), auxPluginName, sizeof(auxPluginName)); + if (StrEqual(pluginName, auxPluginName)) break; + } + CloseHandle(pluginIterator); + pluginIterator = INVALID_HANDLE; + + return pluginNumber; +} + +Handle:FindPluginFromNumber(pluginNumber) +{ + new Handle:pluginIterator = GetPluginIterator(); + new Handle:plugin; + for (new i = 0; i < pluginNumber; i++) + { + if (!MorePlugins(pluginIterator)) + { + plugin = INVALID_HANDLE; + break; + } + plugin = ReadPlugin(pluginIterator); + } + CloseHandle(pluginIterator); + pluginIterator = INVALID_HANDLE; + + return plugin; +} + +// =================================================================================== +// =================================================================================== + +PrintDebug(client, const String:format[], any:...) +{ + if (DEBUG) + { + decl String:text[1024]; + VFormat(text, sizeof(text), format, 3); + if (client == 0) PrintToServer(text); + else if (IsClientInGame(client)) PrintToConsole(client, text); + } +} + +stock GetCaller() +{ + return caller; +} diff --git a/SteamCore/scripting/steamcore/bigint.sp b/SteamCore/scripting/steamcore/bigint.sp new file mode 100644 index 0000000..953f376 --- /dev/null +++ b/SteamCore/scripting/steamcore/bigint.sp @@ -0,0 +1,634 @@ +/*** +bigint.inc - Big Integers Operation Library Functions + +Version: 0.1 +Date: 28-12-2014 +Author: Statik + +Provides some arithmetic and logical maths functions to operate +with big integers. + +**UNFINISHED** + +***/ + +#if defined _BigInt_included + #endinput +#endif + +#define _BigInt_included + +stock modpowBigInt(base[], exponent[], modulus[], nBase, output[], oSize) // http://en.wikipedia.org/wiki/Modular_exponentiation +{ // Modular exponentiation, Right-to-left binary method (binary exponentiation + memory efficient method) + new eSize = getBigIntSize(exponent); + new mSize = getBigIntSize(modulus); + new auxSize = (mSize+1)*2; // output and base (pow base) are never bigger than modulus + decl aux[auxSize]; + decl auxBase[auxSize]; // Is used as a replacemente of the power base, not the numeric base + decl auxExponent[eSize+1]; + new parity[2]; + + output[0] = 1; + output[1] = -1; + copyBigInt(base, auxBase, auxSize); + copyBigInt(exponent, auxExponent, eSize+1); + modBigInt(base, modulus, nBase, auxBase, auxSize); + + while (isBiggerThanBigNumber(auxExponent, {0,-1})) // exponent > 0 + { + fulldivBigInt(auxExponent, {2,-1}, nBase, auxExponent, eSize+1, parity, sizeof(parity)); + if (parity[0] == 1) // (exponent % 2) == 1 / Is exponent odd? + { + multBigInt(output, auxBase, nBase, aux, auxSize); + modBigInt(aux, modulus, nBase, output, oSize); + } + multBigInt(auxBase, auxBase, nBase, auxBase, auxSize); + modBigInt(auxBase, modulus, nBase, auxBase, auxSize); + } +} + +stock powBigInt(number[], exponent[], base, output[], oSize) // Exponentiation by squaring recursive algorithm **UNFINISHED** +{ // http://en.wikipedia.org/wiki/Exponentiation_by_squaring + new nSize = getBigIntSize(number); + new eSize = getBigIntSize(exponent); + if (!isBiggerThanBigNumber(exponent, {0,-1})) // Exponent is 0 + { + output[0] = 1; + output[1] = -1; + return; + } + if (!isBiggerThanBigNumber(exponent, {1,-1})) // Exponent is 1 + { + for (new i = 0; i <= nSize; i++) + { + output[i] = number[i]; + } + return; + } + new bool:isExponentEven = (exponent[0] % 2) == 0; + multBigInt(number, number, base, output, oSize); // number^2 + if (!isBiggerThanBigNumber(exponent, {2,-1})) return; + + if (isExponentEven) + { + //powBigInt(output, exponent/2, base, output, oSize); + } + else + { + + //powBigInt(output, exponent-1/2, base, output, oSize); + } +} + +stock multBigInt(n1[], n2[], base, output[], oSize) +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + if (n1Size < 40000 && n2Size < 40000) + { + standardMult(n1, n2, base, output, oSize); + } + else + { + karatsubaMult(n1, n2, base, output, oSize); + } +} + +stock divBigInt(n1[], n2[], base, output[], oSize) +{ + new n2Size = getBigIntSize(n2); + decl remainder[n2Size+1]; // Remainder is never bigger than divisor/denominator + fulldivBigInt(n1, n2, base, output, oSize, remainder, n2Size+1); +} + +stock modBigInt(n1[], n2[], base, output[], oSize) +{ + new n1Size = getBigIntSize(n1); + decl quotient[n1Size+1]; // Quotient is never bigger than dividend/numerator + fulldivBigInt(n1, n2, base, quotient, n1Size+1, output, oSize); +} + +stock fulldivBigInt(n1[], n2[], base, quotient[], qSize, remainder[], rSize) +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + + if (qSize < (n1Size+1)) return; + if (!isBiggerThanBigNumber(n2, {0,-1})) // n2 == 0 + return; + + if (n2Size > n1Size) + { + quotient[0] = 0; + quotient[1] = -1; + copyBigInt(n1, remainder, rSize); + return; + } + decl tempRemainder[rSize+1]; + tempRemainder[0] = -1; + quotient[n1Size] = -1; + new i; + for (i = n1Size-1; i >= 0; i--) + { + leftShiftBigInt(tempRemainder, 1); + trimBigInt(tempRemainder); + tempRemainder[0] = n1[i]; + quotient[i] = 0; + while (!isBiggerThanBigNumber(n2, tempRemainder)) // (tempRemainder >= n2) + { + subBigInt(tempRemainder, n2, base, tempRemainder, rSize); + quotient[i]++; + } + } + copyBigInt(tempRemainder, remainder, rSize); + trimBigInt(quotient); +} + +stock karatsubaMult(n1[], n2[], base, output[], oSize) // Karatsuba recursive algorithm +{ // http://en.wikipedia.org/wiki/Karatsuba_algorithm#Pseudo_Code_Implementation + if (oSize < 2) return; + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + if (n1Size == 1 || n2Size == 1) // Base case + { + standardMult(n1, n2, base, output, oSize); + return; + } + new m = (n1Size > n2Size) ? n1Size : n2Size; + new m2 = m/2; + + decl l1[oSize], h1[oSize]; + decl l2[oSize], h2[oSize]; + splitBigIntAt(n1, m2, l1, oSize, h1, oSize); + splitBigIntAt(n2, m2, l2, oSize, h2, oSize); + + decl z0[oSize]; // <- + karatsubaMult(l1, l2, base, z0, oSize); + //standardMult(l1, l2, base, z0, oSize); + + decl z1[oSize]; // <- + decl l1plush1[oSize]; + decl l2plush2[oSize]; + addBigInt(l1, h1, base, l1plush1, oSize); + addBigInt(l2, h2, base, l2plush2, oSize); + karatsubaMult(l1plush1, l2plush2, base, z1, oSize); + //standardMult(l1plush1, l2plush2, base, z1, oSize); + + decl z2[oSize]; // <- + karatsubaMult(h1, h2, base, z2, oSize); + //standardMult(h1, h2, base, z2, oSize); + + decl z3[oSize]; // <- + subBigInt(z1, z2, base, z3, oSize); + subBigInt(z3, z0, base, z3, oSize); + + leftShiftBigInt(z2, 2*m2); + leftShiftBigInt(z3, m2); + + addBigInt(z2, z3, base, output, oSize); + addBigInt(output, z0, base, output, oSize); + + trimBigInt(output); +} + +stock standardMult(n1[], n2[], base, output[], oSize) +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + new bool:n1b = isBiggerThanBigNumber(n1, n2); // Is n1 bigger + new sSize = (n1b) ? n2Size : n1Size; // Smallest size + new bSize = (n1b) ? n1Size : n2Size; // Biggest size + new carry = 0; + decl value[sSize][oSize]; + new temp, i, u; + for (i = 0; i < sSize; i++) + { + for (u = 0; u < bSize; u++) + { + temp = n1[(n1b)?u:i] * n2[(n1b)?i:u] + carry; + if (temp >= base) + { + carry = temp / base; + value[i][u] = temp % base; + } + else + { + carry = 0; + value[i][u] = temp; + } + } + if (carry != 0) + { + value[i][u] = carry; + value[i][u+1] = -1; + carry = 0; + } + else + { + value[i][u] = -1; + } + leftShiftBigInt(value[i], i); + } + output[0] = -1; // Initializes output + for (i = 0; i < sSize; i++) + { + addBigInt(output, value[i], base, output, oSize); + } + trimBigInt(output); +} + +stock addBigInt(n1[], n2[], base, output[], oSize) // Standard algorithm +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + new carry = 0; + new temp; + new i; + for (i = 0; (i < n1Size || i < n2Size); i++) + { + if (i == oSize) return; + if (i >= n1Size) temp = n2[i] + carry; + else if (i >= n2Size) temp = n1[i] + carry; + else temp = n1[i] + n2[i] + carry; + + if (temp >= base) + { + output[i] = temp - base; + carry = 1; + } + else + { + output[i] = temp; + carry = 0; + } + } + if (carry == 1) // Adds the last carry + { + output[i] = carry; + output[i+1] = -1; + } + else output[i] = -1; + trimBigInt(output); +} + +stock subBigInt(n1[], n2[], base, output[], oSize) // Standard algorithm +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + new carry = 0; + new temp; + new i; + for (i = 0; (i < n1Size || i < n2Size); i++) + { + if (i == oSize) return; + if (i >= n1Size) temp = n2[i] - carry; + else if (i >= n2Size) temp = n1[i] - carry; + else temp = n1[i] - n2[i] - carry; + + if (temp < 0) + { + output[i] = temp + base; + carry = 1; + } + else + { + output[i] = temp; + carry = 0; + } + } + output[i] = -1; + trimBigInt(output); +} + +stock bool:splitBigIntAt(number[], index, lowOut[], loSize, highOut[], hoSize) +{ + new nSize = getBigIntSize(number); + if (index == 0) return false; + if (index >= nSize) return false; + if (index >= loSize) return false; + if (nSize-index >= hoSize) return false; + + new i; + for (i = 0; i < index; i++) + { + lowOut[i] = number[i]; + } + lowOut[i] = -1; + trimBigInt(lowOut); + + for (i = index; i < nSize; i++) + { + highOut[i-index] = number[i]; + } + highOut[i-index] = -1; + trimBigInt(highOut); + + return true; +} + +stock bool:isEqualToBigNumber(n1[], n2[]) +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + if (n1Size != n2Size) return false; + + for (new i = 0; i < n1Size; i++) + { + if (n1[i] != n2[i]) return false; + } + return true; +} + +stock bool:isBiggerThanBigNumber(n1[], n2[]) +{ + new n1Size = getBigIntSize(n1); + new n2Size = getBigIntSize(n2); + if (n1Size > n2Size) return true; + if (n1Size < n2Size) return false; + + for (new i = (n1Size-1); i >= 0; i--) + { + if (n1[i] == n2[i]) continue; + else if (n1[i] > n2[i]) return true; + else return false; + } + return false; // In case both numbers are the same +} + +stock leftShiftBigInt(number[], digits) // Logical shift +{ + if (digits == 0) return; + new nSize = getBigIntSize(number); + decl temp[nSize+1]; + for (new i = 0; i <= nSize; i++) // Creates a backup + { + temp[i] = number[i]; + } + for (new a = 0; a < digits; a++) // Fills with zeros + { + number[a] = 0; + } + for (new i = 0; i <= nSize; i++) // Puts the backup back in + { + number[i+digits] = temp[i]; + } + trimBigInt(number); +} + +stock trimBigInt(number[]) // Removes left padded zeros +{ + new nSize = getBigIntSize(number); + for (new zeros = 0; number[nSize-zeros-1] == 0; zeros++) + { + if (nSize-zeros-1 != 0) number[nSize-zeros-1] = -1; + } +} + +stock copyBigInt(number[], output[], oSize) +{ + new nSize = getBigIntSize(number); + if (oSize <= nSize) return; + for (new i = 0; i <= nSize; i++) + { + output[i] = number[i]; + } +} + +stock getBigIntSize(number[]) +{ + new i; + for (i = 0; number[i] != -1; i++){} + return i; +} + +stock toBase256BigInt(number[], output[], oLength) +{ + new nSize = getBigIntSize(number); + new finalLength = nSize/2 + (nSize%2); + if (oLength < finalLength) return 0; + new i; + new high, low; + + for (i = 0; i < finalLength; i++) + { + if ((i == finalLength-1) && nSize%2 == 1) + high = 0; + else + high = number[i*2+1]; + low = number[i*2]; + output[finalLength-i-1] = (high << 4) + low; + } + return finalLength; +} + +stock bool:hexString2BigInt(const String:hexString[], output[], oSize) +{ + + new i; + new temp[oSize]; + for(i = 0; hexString[i] != 0; i++) + { + if (i >= oSize) return false; + temp[i] = hexChar2Int(hexString[i]); + + if (temp[i] == -1) return false; + } + // Inverts number string + for (new u = 0; u < i; u++) + { + output[u] = temp[i-u-1]; + } + output[i] = -1; // Terminates the number string + trimBigInt(output); + return true; +} + +stock bool:bigInt2HexString(input[], String:hexString[], hsSize) +{ + new nSize = getBigIntSize(input); + new i; + for (i = 0; i < (hsSize-1); i++) + { + if (i == nSize) break; + if ((hexString[i] = int2HexChar(input[nSize-i-1])) == -1) return false; + } + hexString[i] = 0; + return true; + +} + +stock hexChar2Int(input) +{ + if(input >= '0' && input <= '9') + return input - '0'; + if(input >= 'A' && input <= 'F') + return input - 'A' + 10; + if(input >= 'a' && input <= 'f') + return input - 'a' + 10; + return -1; +} + +stock int2HexChar(input) +{ + if (input >= 0 && input <= 9) + return input + '0'; + if (input >= 10 && input <= 16) + return input + 'A' - 10; + return -1; +} + +/* +test() +{ + // LOTS OF TESTS + PrintDebug(caller, "Modulus size: %i", getBigIntSize(modulus)); + + /// Addition test + new n1[] = {0xC,9,4,9,0xF,0,-1}; + new n2[] = {0,0x3,5,7,-1}; + decl String:s1[7]; + decl String:s2[7]; + bigInt2HexString(n1, s1, sizeof(s1)); + bigInt2HexString(n2, s2, sizeof(s2)); + PrintDebug(caller, "HEX 1 : %s", s1); + PrintDebug(caller, "HEX 2 : %s", s2); + new a[20]; + addBigInt(n1, n2, 16, a, sizeof(a)); + new String:sa[20]; + bigInt2HexString(a, sa, sizeof(sa)); + PrintDebug(caller, "HEX 1 + HEX 2 = %s", sa); + + /// Split test + new n3[] = {4,6,8,2,5,7,3,4,7,2,-1}; + new n4[15]; + new n5[15]; + splitBigIntAt(n3, 5, n4, sizeof(n4), n5, sizeof(n5)); + new String:s3[30]; + new String:s4[15]; + new String:s5[15]; + bigInt2HexString(n3, s3, sizeof(s3)); + bigInt2HexString(n4, s4, sizeof(s4)); + bigInt2HexString(n5, s5, sizeof(s5)); + PrintDebug(caller, "%s splitted at index %i", s3, 5); + PrintDebug(caller, "Low: %s", s4); + PrintDebug(caller, "High: %s", s5); + + /// Subtraction test + new n6[] = {3,1,3,5,5,9,6,9,-1}; + new n7[] = {1,3,5,6,9,6,9,2,-1}; + decl String:s6[10]; + decl String:s7[10]; + bigInt2HexString(n6, s6, sizeof(s6)); + bigInt2HexString(n7, s7, sizeof(s7)); + new n8[10]; + new String:s8[10]; + subBigInt(n6, n7, 16, n8, sizeof(n8)); + bigInt2HexString(n8, s8, sizeof(s8)); + PrintDebug(caller, "Subtracting %s to %s...", s7, s6); + PrintDebug(caller, "Result: %s", s8); + + /// Shift test + new n9[] = {3,7,9,1,5,7,8,1,-1}; + decl String:s9[15]; + bigInt2HexString(n9, s9, sizeof(s9)); + PrintDebug(caller, "Shifting %s 3 numbers to left", s9); + leftShiftBigInt(n9, 3); + bigInt2HexString(n9, s9, sizeof(s9)); + PrintDebug(caller, "Result: %s", s9); + + /// Standard multiplication test + new n10[] = {3,1,3,5,5,9,6,9,-1}; + new n11[] = {1,3,5,6,9,6,9,2,-1}; + decl String:s10[10]; + decl String:s11[10]; + bigInt2HexString(n10, s10, sizeof(s10)); + bigInt2HexString(n11, s11, sizeof(s11)); + new n12[30]; + new String:s12[30]; + standardMult(n10, n11, 16, n12, sizeof(n12)); + bigInt2HexString(n12, s12, sizeof(s12)); + PrintDebug(caller, "Multiplying (standard) %s to %s...", s10, s11); + PrintDebug(caller, "Result: %s", s12); + + /// Karatsuba multiplication test + new n13[] = {3,1,3,5,5,9,6,9,-1}; + new n14[] = {1,3,5,6,9,6,9,2,-1}; + decl String:s13[10]; + decl String:s14[10]; + bigInt2HexString(n13, s13, sizeof(s13)); + bigInt2HexString(n14, s14, sizeof(s14)); + new n15[30]; + new String:s15[30]; + karatsubaMult(n13, n14, 16, n15, sizeof(n15)); + bigInt2HexString(n15, s15, sizeof(s15)); + PrintDebug(caller, "Multiplying (karatsuba) %s to %s...", s13, s14); + PrintDebug(caller, "Result: %s", s15); + + /// Exponentiation test + new n16[] = {3,4,5,6,2,5,-1}; + new n17[] = {0,1,-1}; + decl String:s16[20]; + decl String:s17[20]; + bigInt2HexString(n16, s16, sizeof(s16)); + bigInt2HexString(n17, s17, sizeof(s17)); + new n18[30]; + new String:s18[30]; + powBigInt(n16, n17, 16, n18, sizeof(n18)); + bigInt2HexString(n18, s18, sizeof(s18)); + PrintDebug(caller, "Exponentiating %s to %s...", s16, s17); + PrintDebug(caller, "Result: %s", s18); + + /// Division test + new n19[] = {9,0xC,7,2,-1}; + new n20[] = {6,2,6,-1}; + decl String:s19[20]; + decl String:s20[20]; + bigInt2HexString(n19, s19, sizeof(s19)); + bigInt2HexString(n20, s20, sizeof(s20)); + new n21[30]; + new String:s21[30]; + new n22[30]; + new String:s22[30]; + fulldivBigInt(n19, n20, 16, n21, sizeof(n21), n22, sizeof(n22)); + bigInt2HexString(n21, s21, sizeof(s21)); + bigInt2HexString(n22, s22, sizeof(s22)); + PrintDebug(caller, "Dividing %s to %s...", s19, s20); + PrintDebug(caller, "Quotient: %s - Remainder: %s", s21, s22); + + /// PKCS#1 v1.5 Padding Scheme test + //decl String:aux[1024] = "BE629EA2D835880BF2572379CC751C5FA44F4A09B6F8CE5CB52C53EEDD0314E77AC827219E78DB1473BBA7BBE8BABC85C02CBC308B75375AE7C4B3AA31A491BB08D1946328F2B1BCE3E07E96D1CFF5E95A553C083A424CD5F6B7F2B55F89B958F0AE3B80A94CF5FEB3BD9417ABD09E1A42456E99128169CCEC176FDF7D2893A5"; + new k = strlen(hexModulus); + new String:paddedMessage[k+1]; + pkcs1v15Pad(message, k, paddedMessage, k+1); + PrintDebug(caller, "Padding message: %s with %i length modulus ", message, k); + PrintDebug(caller, "Result: %i - %s", strlen(paddedMessage), paddedMessage); + + /// Modular exponentiation test + + decl n23[1024]; + decl n24[20]; + decl n25[1024]; + hexString2BigInt(paddedMessage, n23, 1024); + hexString2BigInt(hexExponent, n24, 20); + hexString2BigInt(hexModulus, n25, 1024); + + //new n23[] = {3,2,-1}; + //new n24[] = {5,-1}; + //new n25[] = {6,2,6,-1}; + new String:s23[1024]; + new String:s24[40]; + new String:s25[1024]; + new n26[1024]; + new String:s26[1024]; + modpowBigInt(n23, n24, n25, 16, n26, sizeof(n26)); + bigInt2HexString(n23, s23, sizeof(s23)); + bigInt2HexString(n24, s24, sizeof(s24)); + bigInt2HexString(n25, s25, sizeof(s25)); + bigInt2HexString(n26, s26, sizeof(s26)); + PrintDebug(caller, "Modular Power Test"); + PrintDebug(caller, "base = %s", s23); + PrintDebug(caller, "exponent = %s", s24); + PrintDebug(caller, "modulus = %s", s25); + PrintDebug(caller, "Result size: %i, result = \n%s", getBigIntSize(n26), s26); +} +*/ \ No newline at end of file diff --git a/SteamCore/scripting/steamcore/rsa.sp b/SteamCore/scripting/steamcore/rsa.sp new file mode 100644 index 0000000..b8caf6c --- /dev/null +++ b/SteamCore/scripting/steamcore/rsa.sp @@ -0,0 +1,115 @@ +/*** +rsa.inc - RSA Encrypting Algorithms Library Functions + +Version: 0.1 +Date: 28-03-2015 +Author: Statik + +Provides RSA PKCS #1 v1.5 encrypting functions. + +**UNFINISHED** + +***/ + +#if defined _RSA_included + #endinput +#endif + +#define _RSA_included + +rsaEncrypt(const String:hexModulus[], const String:hexExponent[], const String:message[], String:ciphertext[], ctSize) +{ + decl modulus[1024]; + decl exponent[16]; + + if (!hexString2BigInt(hexModulus, modulus, sizeof(modulus))) + { + PrintDebug(GetCaller(), "Error encrypting passphrase: Invalid modulus."); + return; + } + + if (!hexString2BigInt(hexExponent, exponent, sizeof(exponent))) + { + PrintDebug(GetCaller(), "Error encrypting passphrase: Invalid exponent."); + return; + } + + new k = strlen(hexModulus); + new mSize = k + 1; + if (ctSize < mSize) + { + PrintDebug(GetCaller(), "Error encrypting passphrase: ciphertext size is can't be smaller than modulus size"); + + } + decl String:paddedMessage[mSize]; + pkcs1v15Pad(message, k, paddedMessage, mSize); + PrintDebug(GetCaller(), "Padded message with PKCS#1 v1.5 standard (%i): \n%s", strlen(paddedMessage), paddedMessage); + + decl numericMessage[mSize]; + hexString2BigInt(paddedMessage, numericMessage, mSize); + + decl encryptedMessage[mSize]; + modpowBigInt(numericMessage, exponent, modulus, 16, encryptedMessage, mSize); + bigInt2HexString(encryptedMessage, ciphertext, ctSize); +} + +pkcs1v15Pad(const String:data[], k, String:message[], maxSize) // Message must be even +{ + new dSize = strlen(data); + new psSize = k - (dSize*2) - 6; // Padding string Size + decl String:ps[psSize+1]; // Padding string / 1 more to add the string delimiter + decl String:ds[(dSize*2)+1]; // Data string + new i; + for (i = 0; i < psSize; i++) + { + if ((i % 2) == 0) ps[i] = int2HexChar(GetRandomInt(1,15)); + else ps[i] = int2HexChar(GetRandomInt(0,15)); + } + ps[i] = 0; + for (i = 0; i < dSize; i++) + { + ds[i*2] = int2HexChar(data[i] / 16); // High nibble + ds[i*2+1] = int2HexChar(data[i] % 16); // Low nibble + } + ds[i*2] = 0; + + Format(message, maxSize, "0002%s00%s", ps, ds); +} + +encodeBase64(input[], paddingSize, String:output[], oSize) +{ + static const String:base64Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + new iSize = getBigIntSize(input); + if (paddingSize < iSize) return 0; + new zeros = paddingSize - iSize; + for (new e = 0; e < zeros; e++) { input[iSize++] = 0; } + new finalSize = (iSize / 6) * 4; + if (oSize < finalSize) return 0; + + new bitString = 0, u = 0, i; + for (i = iSize-1; i >= 0; i-=3) + { + if (i == 1) + { + if (((iSize/3)%2) == 1) bitString = (input[i--] << 8) + (input[i--]); + else bitString = (input[i--] << 8) + (input[i--] << 4); + } + else if (i == 0) + { + if (((iSize/3)%2) == 1) bitString = input[i--] << 8; + else bitString = input[i--] << 4; + } + else bitString = (input[i] << 8) + (input[i-1] << 4) + (input[i-2]); + + output[u++] = base64Table[(bitString & 0b111111_000000)>>6]; + output[u++] = base64Table[bitString & 0b000000_111111]; + } + + for (new a = 0; a < (u%4); a++) + { + output[u++] = '='; + } + output[u++] = 0; + return u; +} \ No newline at end of file