diff --git a/.gitignore b/.gitignore index aa90dbb..a5dea84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea/ /target/ /ext/plugins/ +*.smx diff --git a/ext/sourcepawn-client/Facti13BackendIntegration.sp b/ext/sourcepawn-client/Facti13BackendIntegration.sp index 9f5b8d7..dec1c8b 100644 --- a/ext/sourcepawn-client/Facti13BackendIntegration.sp +++ b/ext/sourcepawn-client/Facti13BackendIntegration.sp @@ -1,6 +1,18 @@ #include "core/globals.sp" -#define PLUGIN_VERSION "1.0" +//#define RIPEXT_SUPPORT +#define STEAMWORKS_SUPPORT + +#if defined RIPEXT_SUPPORT +#include +#define PLUGIN_VERSION "1.0 (RipExt)" +#endif + +#if defined STEAMWORKS_SUPPORT +#include +#include +#define PLUGIN_VERSION "1.0 (SteamWorks)" +#endif public Plugin myinfo = { name = "Facti13 Backend Integration", @@ -15,7 +27,6 @@ int g_lastupdate = 0; bool g_warned = true; bool g_updating = false; - public OnPluginStart() { SetupGlobalConVar(); RegAdminCmd("fbi_test", TestUpdate, ADMFLAG_ROOT); @@ -29,6 +40,150 @@ public OnPluginEnd() { } } +#if defined STEAMWORKS_SUPPORT + +public void OnJsonRequestComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode) +{ + g_updating = false; + if (bFailure) + { + if(g_warned) { + LogMessage("Failed response! Code: %i", eStatusCode); + g_warned = false; + } + + delete hRequest; + return; + } + + if (!bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) + { + if(g_warned) { + LogMessage("Failed response! Code: %i", eStatusCode); + g_warned = false; + } + + delete hRequest; + return; + } + + if (eStatusCode == k_EHTTPStatusCode200OK) { + if(!g_warned) { + LogMessage("Success send payload, after error"); + g_warned = true; + } + } + + delete hRequest; + return; +} + +stock void createRequest(const char[] sJsonPayload) { + char sURL[256]; + Format(sURL, sizeof(sURL), "%s/report", g_url); + LogMessage("Use report endpoint: %s", sURL); + + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, sURL); + if (hRequest == INVALID_HANDLE) + { + LogError("Не удалось создать запрос"); + return; + } + + SteamWorks_SetHTTPRequestRawPostBody(hRequest, "application/json; charset=UTF-8", sJsonPayload, strlen(sJsonPayload)); + + if (strlen(g_cookie) > 0) + { + SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Cookie", g_cookie); + } + + SteamWorks_SetHTTPCallbacks(hRequest, OnJsonRequestComplete); + + if (!SteamWorks_SendHTTPRequest(hRequest)) + { + LogError("Cannot send server info"); + delete hRequest; + } +} + +stock void ProcessUpdateStatus() { + JSON_Object payload = new JSON_Object(); + payload.SetBool("status", true); + + payload.SetInt("player_count", GetClientCount(true)); + payload.SetInt("max_players", MaxClients); + + char map_name[128]; + GetCurrentMap(map_name, sizeof(map_name)); + payload.SetString("map_name", map_name); + + JSON_Array players = new JSON_Array(); + + for(int client = 0; client <= MaxClients; client++) { + if (IsValidClient(client)) { + JSON_Object player = new JSON_Object(); + /* Name */ + char name[64]; + GetClientName(client, name, sizeof(name)); + player.SetString("name", name); + /* Score */ + player.SetInt("score", GetClientFrags(client)); + player.SetInt("deads", GetClientDeaths(client)); + /* Duration */ + char duration[16]; + int ct = RoundFloat(GetClientTime(client)); + int h = ct / 3600; + int m = ct % 3600 / 60; + int s = ct % 60; + Format(duration, sizeof(duration), "%02d:%02d:%02d", h, m, s); + player.SetString("duration", duration); + /* Id */ + player.SetInt("id", GetClientUserId(client)); + /* Ip */ + char ip[32]; + GetClientIP(client, ip, sizeof(ip), false); + player.SetString("ip", ip); + /* Loss */ + player.SetInt("loss",RoundFloat(GetClientAvgLoss(client, NetFlow_Both)*1000.0)); + /* Ping */ + player.SetInt("ping",RoundFloat(GetClientLatency(client, NetFlow_Both)*1000.0)); + /* State */ + player.SetString("state", "active") + /* Steam 2 так надо, не надо спрашивать почему*/ + char steam2[32]; + GetClientAuthId(client, AuthId_Steam3, steam2, sizeof(steam2)); + player.SetString("steam2", steam2); + /* Position on map */ + float pos[3]; + GetClientAbsOrigin(client, pos); + player.SetFloat("pos_x", pos[0]); + player.SetFloat("pos_y", pos[1]); + player.SetFloat("pos_z", pos[2]); + /* Player class */ + player.SetInt("clz", TF2_GetPlayerClass(client)); + /* Player team */ + player.SetInt("team", TF2_GetClientTeam(client)); + /* push */ + players.PushObject(player); + delete player; + } + } + + payload.SetObject("players", players); + + //int payload_size = 1024*8; + char payload_str[1024*8]; + payload.Encode(payload_str, 1024*8); + + delete players; + delete payload; + + createRequest(payload_str); +} + +#endif + +#if defined RIPEXT_SUPPORT stock HTTPRequest createRequest() { HTTPRequest client = INVALID_HANDLE; if (strlen(g_url)>0) { @@ -54,7 +209,7 @@ stock JSONObject createPayload() { JSONArray players = new JSONArray(); - for(int client = 0; client <= MAXPLAYERS; client++) { + for(int client = 0; client <= MaxClients; client++) { if (IsValidClient(client)) { JSONObject player = new JSONObject(); /* Name */ @@ -108,15 +263,9 @@ stock JSONObject createPayload() { return payload; } -stock UpdateStatus(){ - if (!g_setuped) return; - if (g_updating) return; - if (GetTime() - g_lastupdate < 5) return; - - g_updating = true; +stock void ProcessUpdateStatus() { JSONObject payload = createPayload(); createRequest().Post(payload, Request_Callback); - g_lastupdate = GetTime(); } static void Request_Callback(HTTPResponse response, any value){ @@ -136,6 +285,17 @@ static void Request_Callback(HTTPResponse response, any value){ return; } } +#endif + +stock UpdateStatus(){ + if (!g_setuped) return; + if (g_updating) return; + if (GetTime() - g_lastupdate < 5) return; + + g_updating = true; + ProcessUpdateStatus(); + g_lastupdate = GetTime(); +} public Action timerCall(Handle:t, any:d) { UpdateStatus(); diff --git a/ext/sourcepawn-client/Facti13Reports.sp b/ext/sourcepawn-client/Facti13Reports.sp index 8f0f023..a069af0 100644 --- a/ext/sourcepawn-client/Facti13Reports.sp +++ b/ext/sourcepawn-client/Facti13Reports.sp @@ -1,5 +1,17 @@ #include "core/globals.sp" +//#define RIPEXT_SUPPORT +#define STEAMWORKS_SUPPORT + +#if defined RIPEXT_SUPPORT +#include +#endif + +#if defined STEAMWORKS_SUPPORT +#include +#include +#endif + #define PLUGIN_VERSION "1.0" public Plugin myinfo = { @@ -98,6 +110,101 @@ public void OnClientPutInServer(int cid){ g_clients_reported_uid[cid] = -1; } +#if defined STEAMWORKS_SUPPORT +stock void createRequest(const char[] sJsonPayload, int uid) { + char sURL[256]; + Format(sURL, sizeof(sURL), "%s/report", g_url); + LogMessage("Use report endpoint: %s", sURL); + + Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodPOST, sURL); + if (hRequest == INVALID_HANDLE) + { + LogError("Не удалось создать запрос"); + return; + } + + SteamWorks_SetHTTPRequestContextValue(hRequest, uid); + SteamWorks_SetHTTPRequestRawPostBody(hRequest, "application/json; charset=UTF-8", sJsonPayload, strlen(sJsonPayload)); + + if (strlen(g_cookie) > 0) + { + SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Cookie", g_cookie); + } + + SteamWorks_SetHTTPCallbacks(hRequest, OnJsonRequestComplete); + + if (!SteamWorks_SendHTTPRequest(hRequest)) + { + LogError("Cannot send report"); + delete hRequest; + } +} + +public void OnJsonRequestComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any value) +{ + int cid_author = GetClientOfUserId(value); + + if (bFailure) + { + OnClientPutInServer(cid_author); + PrintToChat(cid_author, "РЕПОРТ НЕ БЫЛ ДОСТАВЛЕН, ПОВТОРИ ПОПЫТКУ ПОЗЖЕ"); + + LogError("[OnJsonRequestComplete] pizdec"); + delete hRequest; + return; + } + + if (!bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK) + { + OnClientPutInServer(cid_author); + PrintToChat(cid_author, "РЕПОРТ НЕ БЫЛ ДОСТАВЛЕН, ПОВТОРИ ПОПЫТКУ ПОЗЖЕ"); + + LogError("[OnJsonRequestComplete] return status code: %d", eStatusCode); + + delete hRequest; + return; + } + + if (eStatusCode == k_EHTTPStatusCode200OK) { + PrintToChat(cid_author, "Репорт отправлен!"); + } + + delete hRequest; + return; +} + +static void SendReport(const char[] reason, int uid){ + int cid_author = GetClientOfUserId(uid); + int cid_reported = GetClientOfUserId(g_clients_reported_uid[cid_author]); + LogMessage("author: %N, reported: %N", cid_author, cid_reported); + + char author_steam64[64]; + char reported_steam64[64]; + GetClientAuthId(cid_author, AuthId_SteamID64, author_steam64, 64); + if (cid_reported != 0) + GetClientAuthId(cid_reported, AuthId_SteamID64, reported_steam64, 64); + + JSON_Object payload_obj = new JSON_Object(); + payload_obj.SetString("author_steam64", author_steam64); + payload_obj.SetString("reason", reason); + if (cid_reported != 0) + payload_obj.SetString("reported_steam64", reported_steam64); + + char payload_str[1024]; + payload_obj.Encode(payload_str, 1024); + delete payload_obj; + createRequest(payload_str, uid); + + //////////////////////////////////////////////////////////////// + g_clients_cooldown[cid_author] = GetTime(); + g_clients_reported_uid[cid_author] = -1; + g_clients_reasons_wait[cid_author] = false; + //////////////////////////////////////////////////////////////// +} +#endif + +#if defined RIPEXT_SUPPORT +//RIPEXT START stock HTTPRequest createRequest() { HTTPRequest client = INVALID_HANDLE; char url[256]; @@ -141,6 +248,22 @@ static void SendReport(const char[] reason, int uid){ //////////////////////////////////////////////////////////////// } +static void Report_Callback(HTTPResponse response, any value){ + int cid_author = GetClientOfUserId(value); + + if (response.Status == 200){ + PrintToChat(cid_author, "Репорт отправлен!"); + return; + } else { + OnClientPutInServer(cid_author); + PrintToChat(cid_author, "РЕПОРТ НЕ БЫЛ ДОСТАВЛЕН, ПОВТОРИ ПОПЫТКУ ПОЗЖЕ"); + PrintToServer("Failed response! Code: %i", response.Status); + return; + } +} +//RIPEXT END +#endif + public Action COMMAND_ClientReport(int cid, int args){ if(cid == 0){ @@ -167,7 +290,7 @@ public int ShowDisclaimer(int cid){ return 0; } -public int ShowReasonSolution(int cid, int reason_id, const char[] reason){ +public void ShowReasonSolution(int cid, int reason_id, const char[] reason){ strcopy(g_clients_reason[cid], sizeof(g_clients_reason[]), reason); Handle Solution = CreateMenu(PreDisplayPlayers); SetMenuTitle(Solution, "Смотри друг на твою проблему есть решение, без учатия модераторов: \n%s\nТы всеравно хочешь отправить репорт?", g_reasons_solution[reason_id]); @@ -247,7 +370,6 @@ public int DisplayReasons(int cid){ SetWaitChatMessage(cid); return 0; } - return 0; } public int SelectReasonsHandle(Handle ReasonMenu, MenuAction eAction, int cid, int select){ @@ -324,7 +446,7 @@ public int DisplayPlayers(int cid){ } // Menu functions -public int SelectPlayerHandle(Handle ReportMenu, MenuAction eAction, int cid, int select){ +public void SelectPlayerHandle(Handle ReportMenu, MenuAction eAction, int cid, int select){ switch(eAction){ case MenuAction_End:CloseHandle(ReportMenu); case MenuAction_Select:{ @@ -399,18 +521,4 @@ stock ReportProcessing(int cid, const char[] Reason){ PrintToChat(GetClientOfUserId(g_clients_reported_uid[cid]), "\n\n[REPORT.SYSTEM] На вас составили репорт, ожидайте пока вам дадут пизды!\n\n"); } SendReport(Reason, GetClientUserId(cid)); -} - -static void Report_Callback(HTTPResponse response, any value){ - int cid_author = GetClientOfUserId(value); - - if (response.Status == 200){ - PrintToChat(cid_author, "Репорт отправлен!"); - return; - } else { - OnClientPutInServer(cid_author); - PrintToChat(cid_author, "РЕПОРТ НЕ БЫЛ ДОСТАВЛЕН, ПОВТОРИ ПОПЫТКУ ПОЗЖЕ"); - PrintToServer("Failed response! Code: %i", response.Status); - return; - } } \ No newline at end of file diff --git a/ext/sourcepawn-client/core/globals.sp b/ext/sourcepawn-client/core/globals.sp index ee00cf6..044e09f 100644 --- a/ext/sourcepawn-client/core/globals.sp +++ b/ext/sourcepawn-client/core/globals.sp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/ext/sourcepawn-client/include/README b/ext/sourcepawn-client/include/README new file mode 100644 index 0000000..9e6bec5 --- /dev/null +++ b/ext/sourcepawn-client/include/README @@ -0,0 +1 @@ +ripext from https://github.com/ErikMinekus/sm-ripext \ No newline at end of file diff --git a/ext/sourcepawn-client/include/SteamWorks.inc b/ext/sourcepawn-client/include/SteamWorks.inc new file mode 100644 index 0000000..15a7f18 --- /dev/null +++ b/ext/sourcepawn-client/include/SteamWorks.inc @@ -0,0 +1,1280 @@ +#if defined _SteamWorks_Included + #endinput +#endif +#define _SteamWorks_Included + +/* results from UserHasLicenseForApp */ +enum EUserHasLicenseForAppResult +{ + k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app + k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app + k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated +}; + +/* General result codes */ +enum EResult +{ + k_EResultOK = 1, // success + k_EResultFail = 2, // generic failure + k_EResultNoConnection = 3, // no/failed network connection +// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed + k_EResultInvalidPassword = 5, // password/ticket is invalid + k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere + k_EResultInvalidProtocolVer = 7, // protocol version is incorrect + k_EResultInvalidParam = 8, // a parameter is incorrect + k_EResultFileNotFound = 9, // file was not found + k_EResultBusy = 10, // called method busy - action not taken + k_EResultInvalidState = 11, // called object was in an invalid state + k_EResultInvalidName = 12, // name is invalid + k_EResultInvalidEmail = 13, // email is invalid + k_EResultDuplicateName = 14, // name is not unique + k_EResultAccessDenied = 15, // access is denied + k_EResultTimeout = 16, // operation timed out + k_EResultBanned = 17, // VAC2 banned + k_EResultAccountNotFound = 18, // account not found + k_EResultInvalidSteamID = 19, // steamID is invalid + k_EResultServiceUnavailable = 20, // The requested service is currently unavailable + k_EResultNotLoggedOn = 21, // The user is not logged on + k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party) + k_EResultEncryptionFailure = 23, // Encryption or Decryption failed + k_EResultInsufficientPrivilege = 24, // Insufficient privilege + k_EResultLimitExceeded = 25, // Too much of a good thing + k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes) + k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired + k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again + k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time + k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user + k_EResultIPNotFound = 31, // IP address not found + k_EResultPersistFailed = 32, // failed to write change to the data store + k_EResultLockingFailed = 33, // failed to acquire access lock for this operation + k_EResultLogonSessionReplaced = 34, + k_EResultConnectFailed = 35, + k_EResultHandshakeFailed = 36, + k_EResultIOFailure = 37, + k_EResultRemoteDisconnect = 38, + k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested + k_EResultBlocked = 40, // a user didn't allow it + k_EResultIgnored = 41, // target is ignoring sender + k_EResultNoMatch = 42, // nothing matching the request found + k_EResultAccountDisabled = 43, + k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now + k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available + k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin + k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol. + k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another. + k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed. + k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait + k_EResultSuspended = 51, // Long running operation (content download) suspended/paused + k_EResultCancelled = 52, // Operation canceled (typically by user: content download) + k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable + k_EResultDiskFull = 54, // Operation canceled - not enough disk space. + k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed + k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side + k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account + k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid + k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first + k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files + k_EResultIllegalPassword = 61, // The requested new password is not legal + k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer ) + k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure + k_EResultCannotUseOldPassword = 64, // The requested new password is not legal + k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid + k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent + k_EResultHardwareNotCapableOfIPT = 67, // + k_EResultIPTInitError = 68, // + k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user + k_EResultFacebookQueryError = 70, // Facebook query returned an error + k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired + k_EResultIPLoginRestrictionFailed = 72, + k_EResultAccountLockedDown = 73, + k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, + k_EResultNoMatchingURL = 75, + k_EResultBadResponse = 76, // parse failure, missing field, etc. + k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password + k_EResultValueOutOfRange = 78, // the value entered is outside the acceptable range + k_EResultUnexpectedError = 79, // something happened that we didn't expect to ever happen + k_EResultDisabled = 80, // The requested service has been configured to be unavailable + k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG server are not valid ! + k_EResultRestrictedDevice = 82, // The device being used is not allowed to perform this action + k_EResultRegionLocked = 83, // The action could not be complete because it is region restricted + k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent + k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to login + k_EResultItemDeleted = 86, // The thing we're trying to access has been deleted + k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to throttle response to possible attacker + k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch + k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for two-factor didn't match + k_EResultAccountAssociatedToMultiplePartners = 90, // account has been associated with multiple partners + k_EResultNotModified = 91, // data not modified + k_EResultNoMobileDevice = 92, // the account does not have a mobile device associated with it + k_EResultTimeNotSynced = 93, // the time presented is out of range or tolerance + k_EResultSmsCodeFailed = 94, // SMS code failure (no match, none pending, etc.) + k_EResultAccountLimitExceeded = 95, // Too many accounts access this resource + k_EResultAccountActivityLimitExceeded = 96, // Too many changes to this account + k_EResultPhoneActivityLimitExceeded = 97, // Too many changes to this phone + k_EResultRefundToWallet = 98, // Cannot refund to payment method, must use wallet + k_EResultEmailSendFailure = 99, // Cannot send an email + k_EResultNotSettled = 100, // Can't perform operation till payment has settled + k_EResultNeedCaptcha = 101, // Needs to provide a valid captcha + k_EResultGSLTDenied = 102, // a game server login token owned by this token's owner has been banned + k_EResultGSOwnerDenied = 103, // game server owner is denied for other reason (account lock, community ban, vac ban, missing phone) + k_EResultInvalidItemType = 104, // the type of thing we were requested to act on is invalid + k_EResultIPBanned = 105, // the ip address has been banned from taking this action + k_EResultGSLTExpired = 106, // this token has expired from disuse; can be reset for use + k_EResultInsufficientFunds = 107, // user doesn't have enough wallet funds to complete the action + k_EResultTooManyPending = 108, // There are too many of this thing pending already + k_EResultNoSiteLicensesFound = 109, // No site licenses found + k_EResultWGNetworkSendExceeded = 110, // the WG couldn't send a response because we exceeded max network send size + k_EResultAccountNotFriends = 111, // the user is not mutually friends + k_EResultLimitedUserAccount = 112, // the user is limited + k_EResultCantRemoveItem = 113, // item can't be removed + k_EResultAccountDeleted = 114, // account has been deleted + k_EResultExistingUserCancelledLicense = 115, // A license for this already exists, but cancelled + k_EResultCommunityCooldown = 116, // access is denied because of a community cooldown (probably from support profile data resets) + k_EResultNoLauncherSpecified = 117, // No launcher was specified, but a launcher was needed to choose correct realm for operation. + k_EResultMustAgreeToSSA = 118, // User must agree to china SSA or global SSA before login + k_EResultLauncherMigrated = 119, // The specified launcher type is no longer supported; the user should be directed elsewhere + k_EResultSteamRealmMismatch = 120, // The user's realm does not match the realm of the requested resource + k_EResultInvalidSignature = 121, // signature check did not match + k_EResultParseFailure = 122, // Failed to parse input + k_EResultNoVerifiedPhone = 123, // account does not have a verified phone number + k_EResultInsufficientBattery = 124, // user device doesn't have enough battery charge currently to complete the action + k_EResultChargerRequired = 125, // The operation requires a charger to be plugged in, which wasn't present + k_EResultCachedCredentialInvalid = 126, // Cached credential was invalid - user must reauthenticate + K_EResultPhoneNumberIsVOIP = 127, // The phone number provided is a Voice Over IP number + k_EResultNotSupported = 128, // The data being accessed is not supported by this API + k_EResultFamilySizeLimitExceeded = 129, // Reached the maximum size of the family + k_EResultOfflineAppCacheInvalid = 130, // The local data for the offline mode cache is insufficient to login + k_EResultTryLater = 131 // retry the operation later +}; + +/* This enum is used in client API methods, do not re-number existing values. */ +enum EHTTPMethod +{ + k_EHTTPMethodInvalid = 0, + k_EHTTPMethodGET, + k_EHTTPMethodHEAD, + k_EHTTPMethodPOST, + k_EHTTPMethodPUT, + k_EHTTPMethodDELETE, + k_EHTTPMethodOPTIONS, + k_EHTTPMethodPATCH, + + // The remaining HTTP methods are not yet supported, per rfc2616 section 5.1.1 only GET and HEAD are required for + // a compliant general purpose server. We'll likely add more as we find uses for them. + + // k_EHTTPMethodTRACE, + // k_EHTTPMethodCONNECT +}; + + +/* HTTP Status codes that the server can send in response to a request, see rfc2616 section 10.3 for descriptions + of each of these. */ +enum EHTTPStatusCode +{ + // Invalid status code (this isn't defined in HTTP, used to indicate unset in our code) + k_EHTTPStatusCodeInvalid = 0, + + // Informational codes + k_EHTTPStatusCode100Continue = 100, + k_EHTTPStatusCode101SwitchingProtocols = 101, + + // Success codes + k_EHTTPStatusCode200OK = 200, + k_EHTTPStatusCode201Created = 201, + k_EHTTPStatusCode202Accepted = 202, + k_EHTTPStatusCode203NonAuthoritative = 203, + k_EHTTPStatusCode204NoContent = 204, + k_EHTTPStatusCode205ResetContent = 205, + k_EHTTPStatusCode206PartialContent = 206, + + // Redirection codes + k_EHTTPStatusCode300MultipleChoices = 300, + k_EHTTPStatusCode301MovedPermanently = 301, + k_EHTTPStatusCode302Found = 302, + k_EHTTPStatusCode303SeeOther = 303, + k_EHTTPStatusCode304NotModified = 304, + k_EHTTPStatusCode305UseProxy = 305, + //k_EHTTPStatusCode306Unused = 306, (used in old HTTP spec, now unused in 1.1) + k_EHTTPStatusCode307TemporaryRedirect = 307, + k_EHTTPStatusCode308PermanentRedirect = 308, + + // Error codes + k_EHTTPStatusCode400BadRequest = 400, + k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response. + k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients + k_EHTTPStatusCode403Forbidden = 403, + k_EHTTPStatusCode404NotFound = 404, + k_EHTTPStatusCode405MethodNotAllowed = 405, + k_EHTTPStatusCode406NotAcceptable = 406, + k_EHTTPStatusCode407ProxyAuthRequired = 407, + k_EHTTPStatusCode408RequestTimeout = 408, + k_EHTTPStatusCode409Conflict = 409, + k_EHTTPStatusCode410Gone = 410, + k_EHTTPStatusCode411LengthRequired = 411, + k_EHTTPStatusCode412PreconditionFailed = 412, + k_EHTTPStatusCode413RequestEntityTooLarge = 413, + k_EHTTPStatusCode414RequestURITooLong = 414, + k_EHTTPStatusCode415UnsupportedMediaType = 415, + k_EHTTPStatusCode416RequestedRangeNotSatisfiable = 416, + k_EHTTPStatusCode417ExpectationFailed = 417, + k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown + k_EHTTPStatusCode421MisdirectedRequest = 421, + k_EHTTPStatusCode422UnprocessableContent = 422, + k_EHTTPStatusCode423Locked = 423, + k_EHTTPStatusCode424FailedDependency = 424, + k_EHTTPStatusCode425TooEarly = 425, + k_EHTTPStatusCode426UpgradeRequired = 426, + k_EHTTPStatusCode428PreconditionRequired = 428, + k_EHTTPStatusCode429TooManyRequests = 429, + k_EHTTPStatusCode431RequestHeaderFieldsTooLarge = 431, + k_EHTTPStatusCode444ConnectionClosed = 444, // nginx only? + k_EHTTPStatusCode451UnavailableForLegalReasons = 451, + + // Server error codes + k_EHTTPStatusCode500InternalServerError = 500, + k_EHTTPStatusCode501NotImplemented = 501, + k_EHTTPStatusCode502BadGateway = 502, + k_EHTTPStatusCode503ServiceUnavailable = 503, + k_EHTTPStatusCode504GatewayTimeout = 504, + k_EHTTPStatusCode505HTTPVersionNotSupported = 505, + k_EHTTPStatusCode506VariantAlsoNegotiates = 506, + k_EHTTPStatusCode507InsufficientStorage = 507, + k_EHTTPStatusCode508LoopDetected = 508, + k_EHTTPStatusCode510NotExtended = 510, + k_EHTTPStatusCode511NetworkAuthenticationRequired = 511, + k_EHTTPStatusCode5xxUnknown = 599, +}; + +/** + * Returns whether an HTTP status code represents success (a 2xx code). + * + * @param eStatusCode HTTP status code to test. + * @return True if the code is in the 2xx range, false otherwise. + */ +stock bool IsHTTPStatusSuccess(EHTTPStatusCode eStatusCode) +{ + return (eStatusCode >= k_EHTTPStatusCode200OK && eStatusCode < k_EHTTPStatusCode300MultipleChoices); +} + +/* list of possible return values from the ISteamGameCoordinator API */ +enum EGCResults +{ + k_EGCResultOK = 0, + k_EGCResultNoMessage = 1, // There is no message in the queue + k_EGCResultBufferTooSmall = 2, // The buffer is too small for the requested message + k_EGCResultNotLoggedOn = 3, // The client is not logged onto Steam + k_EGCResultInvalidMessage = 4, // Something was wrong with the message being sent with SendMessage +}; + +/** + * Returns whether the server is VAC (Valve Anti-Cheat) secured. + * + * @return True if the server is VAC secured, false otherwise (including + * when not yet connected to Steam). + */ +native bool SteamWorks_IsVACEnabled(); + +/** + * Retrieves the server's public IP address as four octets. + * + * @param ipaddr Array that receives the IP address, most-significant octet first + * (e.g. 127.0.0.1 becomes {127, 0, 0, 1}). + * @return True on success, false if not connected to Steam or the public + * IP is not yet known. + */ +native bool SteamWorks_GetPublicIP(int ipaddr[4]); + +/** + * Retrieves the server's public IP address packed into a single cell. + * + * @return The IPv4 address as a 32-bit value (host byte order), or 0 if not + * connected to Steam or the public IP is not yet known. + */ +native int SteamWorks_GetPublicIPCell(); + +/** + * Returns whether the Steam client library has been loaded by the extension. + * + * @return True if the Steam library is loaded, false otherwise. + */ +native bool SteamWorks_IsLoaded(); + +/** + * Sets the "gamedata" string for the server, used for matchmaking/server-browser filtering. + * + * @param sData Game data string. + * @return True on success, false if not connected to Steam. + */ +native bool SteamWorks_SetGameData(const char[] sData); + +/** + * Sets the game description reported to the server browser and client queries. + * + * @param sDesc Game description string. + * @return True on success, false if not connected to Steam. + */ +native bool SteamWorks_SetGameDescription(const char[] sDesc); + +/** + * Sets the map name reported to the server browser and client queries. + * + * @param sMapName Map name string. + * @return True on success, false if not connected to Steam. + */ +native bool SteamWorks_SetMapName(const char[] sMapName); + +/** + * Returns whether the server is currently logged on to Steam. + * + * @return True if logged on to Steam, false otherwise. + */ +native bool SteamWorks_IsConnected(); + +/** + * Adds or updates a key/value pair sent in A2S rules queries. + * + * @param sKey Rule key. + * @param sValue Rule value. + * @return True on success, false if not connected to Steam. + */ +native bool SteamWorks_SetRule(const char[] sKey, const char[] sValue); + +/** + * Clears the entire list of key/value pairs sent in rules queries. + * + * @return True on success, false if not connected to Steam. + */ +native bool SteamWorks_ClearRules(); + +/** + * Sets whether the server should be advertised on the master server list and + * respond to server browser / LAN discovery packets. Defaults to false; set + * other server parameters before enabling advertising. + * + * @param bActive True to advertise the server, false to hide it. + * @return True on success, false if not connected to Steam. + */ +native bool SteamWorks_SetAdvertiseServerActive(bool bActive); + +/** + * Deprecated no-op. Newer Steamworks SDKs removed ForceHeartbeat; server list + * heartbeats are now sent implicitly by Steam. + * + * @return Always false. + */ +#pragma deprecated This function is deprecated in the SDK and no longer does anything in this extension +native bool SteamWorks_ForceHeartbeat(); + +/** + * Asynchronously requests whether a client is a member of a given Steam group. + * The result is delivered through the SteamWorks_OnClientGroupStatus forward. + * + * @param client Client index. + * @param groupid 32-bit account ID of the Steam group. + * @return True if the request was sent, false if not connected to Steam. + * @error Invalid client index. + */ +native bool SteamWorks_GetUserGroupStatus(int client, int groupid); + +/** + * Asynchronously requests whether a user is a member of a given Steam group. + * The result is delivered through the SteamWorks_OnClientGroupStatus forward. + * + * @param authid 32-bit account ID of the user to query. + * @param groupid 32-bit account ID of the Steam group. + * @return True if the request was sent, false if not connected to Steam. + */ +native bool SteamWorks_GetUserGroupStatusAuthID(int authid, int groupid); + +/** + * Returns whether a client owns/has a license for the given application. + * + * @param client Client index. + * @param app Application (AppID) to check ownership of. + * @return An EUserHasLicenseForAppResult value; k_EUserHasLicenseResultNoAuth + * if not connected to Steam. + * @error Invalid client index. + */ +native EUserHasLicenseForAppResult SteamWorks_HasLicenseForApp(int client, int app); + +/** + * Returns whether a user owns/has a license for the given application. + * + * @param authid 32-bit account ID of the user to check. + * @param app Application (AppID) to check ownership of. + * @return An EUserHasLicenseForAppResult value; k_EUserHasLicenseResultNoAuth + * if not connected to Steam. + */ +native EUserHasLicenseForAppResult SteamWorks_HasLicenseForAppId(int authid, int app); + +/** + * Retrieves a client's 64-bit Steam ID (community ID) as a string. + * + * @param client Client index. + * @param sSteamID Buffer to store the rendered 64-bit Steam ID. + * @param length Maximum length of the buffer. + * @return Number of bytes written, including the null terminator. + * @error Invalid client index. + */ +native int SteamWorks_GetClientSteamID(int client, char[] sSteamID, int length); + +/** + * Asynchronously requests the stats of a user from Steam. Stats become available + * afterwards through SteamWorks_GetStatAuthIDCell / SteamWorks_GetStatAuthIDFloat. + * + * @param authid 32-bit account ID of the user whose stats to request. + * @param appid Application (AppID) to request stats for. + * @return True if the request was sent, false if not connected to Steam. + */ +native bool SteamWorks_RequestStatsAuthID(int authid, int appid); + +/** + * Asynchronously requests the stats of a client from Steam. Stats become available + * afterwards through SteamWorks_GetStatCell / SteamWorks_GetStatFloat. + * + * @param client Client index. + * @param appid Application (AppID) to request stats for. + * @return True if the request was sent, false if not connected to Steam. + * @error Invalid client index. + */ +native bool SteamWorks_RequestStats(int client, int appid); + +/** + * Retrieves an integer stat for a client. The client's stats must have been + * requested first with SteamWorks_RequestStats. + * + * @param client Client index. + * @param sKey Stat name. + * @param value Variable to store the stat value in. + * @return True on success, false on failure or if not connected to Steam. + * @error Invalid client index. + */ +native bool SteamWorks_GetStatCell(int client, const char[] sKey, int &value); + +/** + * Retrieves an integer stat for a user. The user's stats must have been + * requested first with SteamWorks_RequestStatsAuthID. + * + * @param authid 32-bit account ID of the user. + * @param sKey Stat name. + * @param value Variable to store the stat value in. + * @return True on success, false on failure or if not connected to Steam. + */ +native bool SteamWorks_GetStatAuthIDCell(int authid, const char[] sKey, int &value); + +/** + * Retrieves a floating-point stat for a client. The client's stats must have been + * requested first with SteamWorks_RequestStats. + * + * @param client Client index. + * @param sKey Stat name. + * @param value Variable to store the stat value in. + * @return True on success, false on failure or if not connected to Steam. + * @error Invalid client index. + */ +native bool SteamWorks_GetStatFloat(int client, const char[] sKey, float &value); + +/** + * Retrieves a floating-point stat for a user. The user's stats must have been + * requested first with SteamWorks_RequestStatsAuthID. + * + * @param authid 32-bit account ID of the user. + * @param sKey Stat name. + * @param value Variable to store the stat value in. + * @return True on success, false on failure or if not connected to Steam. + */ +native bool SteamWorks_GetStatAuthIDFloat(int authid, const char[] sKey, float &value); + +/** + * Creates a new HTTP request. The URL must be absolute and start with http:// or https://. + * + * The returned handle must be freed with CloseHandle/delete once the request is finished. + * + * @param method HTTP method to use. + * @param sURL Absolute URL for the request. + * @return A handle to the new HTTP request, or INVALID_HANDLE on failure + * (including when not connected to Steam). + */ +native Handle SteamWorks_CreateHTTPRequest(EHTTPMethod method, const char[] sURL); + +/** + * Sets one or two context values that will be passed back to the request's callbacks. + * These let you associate arbitrary data with a request. + * + * @param hHandle HTTP request handle. + * @param data1 First context value. + * @param data2 Second context value. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ +native bool SteamWorks_SetHTTPRequestContextValue(Handle hHandle, any data1, any data2=0); + +/** + * Sets a network-activity timeout, in seconds, for the request. Must be called before sending. + * The default is 60 seconds. The timer resets whenever more data is received. + * + * @param hHandle HTTP request handle. + * @param timeout Timeout in seconds. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ +native bool SteamWorks_SetHTTPRequestNetworkActivityTimeout(Handle hHandle, int timeout); + +/** + * Sets a request header value. Must be called before sending the request. + * + * @param hHandle HTTP request handle. + * @param sName Header name. + * @param sValue Header value. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ +native bool SteamWorks_SetHTTPRequestHeaderValue(Handle hHandle, const char[] sName, const char[] sValue); + +/** + * Sets a GET or POST parameter on the request (which is used depends on the request method). + * Must be called before sending the request. + * + * @param hHandle HTTP request handle. + * @param sName Parameter name. + * @param sValue Parameter value. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ +native bool SteamWorks_SetHTTPRequestGetOrPostParameter(Handle hHandle, const char[] sName, const char[] sValue); + +/** + * Appends extra user-agent info to the request. This does not clobber the normal user + * agent; it is added to the end. + * + * @param hHandle HTTP request handle. + * @param sUserAgentInfo Extra user-agent info string. + * @return True on success, false on an invalid handle. + */ +native bool SteamWorks_SetHTTPRequestUserAgentInfo(Handle hHandle, const char[] sUserAgentInfo); + +/** + * Enables or disables verification of SSL/TLS certificates. By default, certificates + * are verified for all HTTPS requests. + * + * @param hHandle HTTP request handle. + * @param bRequireVerifiedCertificate True to require a verified certificate, false to disable. + * @return True on success, false on an invalid handle. + */ +native bool SteamWorks_SetHTTPRequestRequiresVerifiedCertificate(Handle hHandle, bool bRequireVerifiedCertificate); + +/** + * Sets an absolute timeout, in milliseconds, on the request. Unlike the network-activity + * timeout, this is a total time limit that does not reset as data arrives. + * + * @param hHandle HTTP request handle. + * @param unMilliseconds Total timeout in milliseconds. + * @return True on success, false on an invalid handle. + */ +native bool SteamWorks_SetHTTPRequestAbsoluteTimeoutMS(Handle hHandle, int unMilliseconds); + +/** + * Called when an HTTP request has completed (or failed). The number of trailing context + * parameters matches how many values were passed to SteamWorks_SetHTTPRequestContextValue. + * + * @param hRequest HTTP request handle. + * @param bFailure True if the request failed due to an internal or network + * error (no response from the server). + * @param bRequestSuccessful True if any response was received from the server (even an + * error response). + * @param eStatusCode HTTP status code returned by the server. + * @param data1 First context value, if one was set. + * @param data2 Second context value, if one was set. + */ +typeset SteamWorksHTTPRequestCompleted +{ + function void (Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode); + function void (Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any data1); + function void (Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, any data1, any data2); +}; + +/** + * Called when the response headers for a streaming request have been received. The number + * of trailing context parameters matches the context values set on the request. + * + * @param hRequest HTTP request handle. + * @param bFailure Always false; headers-received is a success-only notification. A + * failed request is reported through SteamWorksHTTPRequestCompleted. + * @param data1 First context value, if one was set. + * @param data2 Second context value, if one was set. + */ +typeset SteamWorksHTTPHeadersReceived +{ + function void (Handle hRequest, bool bFailure); + function void (Handle hRequest, bool bFailure, any data1); + function void (Handle hRequest, bool bFailure, any data1, any data2); +}; + +/** + * Called when a chunk of data for a streaming request has been received. Pass the offset + * and byte count to SteamWorks_GetHTTPStreamingResponseBodyData to read the chunk. The + * number of trailing context parameters matches the context values set on the request. + * + * @param hRequest HTTP request handle. + * @param bFailure Always false; data-received is a success-only notification. A + * failed request is reported through SteamWorksHTTPRequestCompleted. + * @param offset Offset of this chunk within the response body. + * @param bytesreceived Number of bytes in this chunk. + * @param data1 First context value, if one was set. + * @param data2 Second context value, if one was set. + */ +typeset SteamWorksHTTPDataReceived +{ + function void (Handle hRequest, bool bFailure, int offset, int bytesreceived); + function void (Handle hRequest, bool bFailure, int offset, int bytesreceived, any data1); + function void (Handle hRequest, bool bFailure, int offset, int bytesreceived, any data1, any data2); +}; + +/** + * Called by SteamWorks_GetHTTPResponseBodyCallback with the response body. Use the string + * overload for text bodies or the int[] overload for binary bodies. + * + * @param sData Response body as a string (text overload). + * @param data Response body as a byte array (binary overload). + * @param value The context value passed to SteamWorks_GetHTTPResponseBodyCallback. + * @param datalen Length of the body, in bytes (binary overload). + */ +typeset SteamWorksHTTPBodyCallback +{ + function void (const char[] sData); + function void (const char[] sData, any value); + function void (const int[] data, any value, int datalen); +}; + +/** + * Sets the callbacks fired for a request. Must be called before sending the request. + * The completion callback is used by both regular and streaming requests; the headers and + * data callbacks are only fired for streaming requests. + * + * @param hHandle HTTP request handle. + * @param fCompleted Callback fired when the request completes, or INVALID_FUNCTION. + * @param fHeaders Callback fired when streaming headers arrive, or INVALID_FUNCTION. + * @param fData Callback fired when a streaming data chunk arrives, or INVALID_FUNCTION. + * @param hCalling Handle of the plugin that owns the callbacks, or INVALID_HANDLE for + * the calling plugin. + * @return True on success, false on an invalid handle. + * @error Invalid plugin handle or invalid function. + */ +native bool SteamWorks_SetHTTPCallbacks(Handle hHandle, SteamWorksHTTPRequestCompleted fCompleted = INVALID_FUNCTION, SteamWorksHTTPHeadersReceived fHeaders = INVALID_FUNCTION, SteamWorksHTTPDataReceived fData = INVALID_FUNCTION, Handle hCalling = INVALID_HANDLE); + +/** + * Sends an HTTP request. The result is delivered asynchronously to the completion callback + * set with SteamWorks_SetHTTPCallbacks. + * + * @param hRequest HTTP request handle. + * @return True if the request was sent, false on an invalid handle. + */ +native bool SteamWorks_SendHTTPRequest(Handle hRequest); + +/** + * Sends an HTTP request and streams the response. Headers and data are delivered + * asynchronously to the callbacks set with SteamWorks_SetHTTPCallbacks. + * + * @param hRequest HTTP request handle. + * @return True if the request was sent, false on an invalid handle. + */ +native bool SteamWorks_SendHTTPRequestAndStreamResponse(Handle hRequest); + +/** + * Moves an already-sent request to the tail of the client's request queue. + * + * @param hRequest HTTP request handle. + * @return True on success, false on an invalid handle or if the request has + * not been sent. + */ +native bool SteamWorks_DeferHTTPRequest(Handle hRequest); + +/** + * Moves an already-sent request to the head of the client's request queue. + * + * @param hRequest HTTP request handle. + * @return True on success, false on an invalid handle or if the request has + * not been sent. + */ +native bool SteamWorks_PrioritizeHTTPRequest(Handle hRequest); + +/** + * Checks whether a response header is present and retrieves the size of its value, so a + * correctly-sized buffer can be allocated for SteamWorks_GetHTTPResponseHeaderValue. + * Call from the completion callback. + * + * @param hRequest HTTP request handle. + * @param sHeader Header name. + * @param size Variable to store the header value size in. + * @return True if the header is present, false otherwise. + */ +native bool SteamWorks_GetHTTPResponseHeaderSize(Handle hRequest, const char[] sHeader, int &size); + +/** + * Retrieves a response header value. Call from the completion callback. Use + * SteamWorks_GetHTTPResponseHeaderSize first to size the buffer. + * + * @param hRequest HTTP request handle. + * @param sHeader Header name. + * @param sValue Buffer to store the header value in. + * @param size Maximum length of the buffer. + * @return True on success, false if the header is not present or the buffer + * is too small. + */ +native bool SteamWorks_GetHTTPResponseHeaderValue(Handle hRequest, const char[] sHeader, char[] sValue, int size); + +/** + * Retrieves the size of the response body. Call from the completion callback. + * + * @param hRequest HTTP request handle. + * @param size Variable to store the body size in. + * @return True on success, false on an invalid handle. + */ +native bool SteamWorks_GetHTTPResponseBodySize(Handle hRequest, int &size); + +/** + * Retrieves the response body. Call from the completion callback. Use + * SteamWorks_GetHTTPResponseBodySize first to size the buffer. Not valid for streaming + * responses. + * + * @param hRequest HTTP request handle. + * @param sBody Buffer to store the body in. + * @param length Length of the buffer, which must match the body size. + * @return True on success, false on an invalid handle, a streaming response, + * or an incorrectly-sized buffer. + */ +native bool SteamWorks_GetHTTPResponseBodyData(Handle hRequest, char[] sBody, int length); + +/** + * Retrieves a chunk of a streaming response body. Call from the data-received callback, + * passing the offset and length reported by that callback. + * + * @param hRequest HTTP request handle. + * @param cOffset Offset of the chunk, as provided by the data-received callback. + * @param sBody Buffer to store the chunk in. + * @param length Length of the chunk, as provided by the data-received callback. + * @return True on success, false on an invalid handle, a non-streaming + * response, or a mismatched offset/length. + */ +native bool SteamWorks_GetHTTPStreamingResponseBodyData(Handle hRequest, int cOffset, char[] sBody, int length); + +/** + * Retrieves download progress for the request. This is zero until a response header with a + * content-length has been received; for responses with no content-length it stays zero. + * + * @param hRequest HTTP request handle. + * @param percent Variable to store the progress percentage in. + * @return True on success, false on an invalid handle. + */ +native bool SteamWorks_GetHTTPDownloadProgressPct(Handle hRequest, float &percent); + +/** + * Checks whether the request failed because it timed out (rather than a harder failure). + * + * @param hRequest HTTP request handle. + * @param bWasTimedOut Variable to store the result in. + * @return True on success, false on an invalid handle. + */ +native bool SteamWorks_GetHTTPRequestWasTimedOut(Handle hRequest, bool &bWasTimedOut); + +/** + * Sets a raw body for a POST request. Fails on a GET request or if GET/POST parameters + * were already set. This makes the raw body the entire contents of the POST. + * + * @param hRequest HTTP request handle. + * @param sContentType Value for the Content-Type header. + * @param sBody Raw body data. + * @param bodylen Length of the body, in bytes. + * @return True on success, false on failure. + */ +native bool SteamWorks_SetHTTPRequestRawPostBody(Handle hRequest, const char[] sContentType, const char[] sBody, int bodylen); + +/** + * Sets a raw POST body read from a file (relative to the game directory). Same constraints + * as SteamWorks_SetHTTPRequestRawPostBody. + * + * @param hRequest HTTP request handle. + * @param sContentType Value for the Content-Type header. + * @param sFileName Path to the file, relative to the game directory. + * @return True on success, false on failure (e.g. an empty file). + * @error Unable to open the file for reading. + */ +native bool SteamWorks_SetHTTPRequestRawPostBodyFromFile(Handle hRequest, const char[] sContentType, const char[] sFileName); + +/** + * Retrieves the full response body and passes it to a callback. Useful for bodies larger + * than a single fixed buffer. Call from the completion callback. + * + * @param hRequest HTTP request handle. + * @param fCallback Callback that receives the body. + * @param data Context value passed through to the callback. + * @param hPlugin Handle of the plugin that owns the callback, or INVALID_HANDLE for + * the calling plugin. + * @return True on success, false on an invalid handle or if the body could not + * be retrieved. + * @error Invalid plugin handle or invalid function. + */ +native bool SteamWorks_GetHTTPResponseBodyCallback(Handle hRequest, SteamWorksHTTPBodyCallback fCallback, any data = 0, Handle hPlugin = INVALID_HANDLE); + +/** + * Writes the full response body to a file (relative to the game directory). Call from the + * completion callback. + * + * @param hRequest HTTP request handle. + * @param sFileName Path to the output file, relative to the game directory. + * @return True on success, false on an invalid handle or if the body could not + * be retrieved. + * @error Unable to open the file for writing. + */ +native bool SteamWorks_WriteHTTPResponseBodyToFile(Handle hRequest, const char[] sFileName); + +methodmap SteamWorksHTTPRequest < Handle +{ + /** + * Creates a new HTTP request. + * + * @param method HTTP method to use. + * @param sURL Absolute URL for the request. + * @return A new request handle, or INVALID_HANDLE on failure (including when + * not connected to Steam). + */ + public native SteamWorksHTTPRequest(EHTTPMethod method, const char[] sURL); + + /** + * Sets one or two context values that will be passed back to the request's callbacks. + * These let you associate arbitrary data with a request. + * + * @param data1 First context value. + * @param data2 Second context value. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ + public native bool SetContextValue(any data1, any data2 = 0); + + /** + * Sets a network-activity timeout, in seconds, for the request. Must be called before + * sending. The default is 60 seconds. The timer resets whenever more data is received. + * + * @param timeout Timeout in seconds. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ + public native bool SetNetworkActivityTimeout(int timeout); + + /** + * Sets a request header value. Must be called before sending the request. + * + * @param sName Header name. + * @param sValue Header value. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ + public native bool SetHeaderValue(const char[] sName, const char[] sValue); + + /** + * Sets a GET or POST parameter on the request (which is used depends on the request + * method). Must be called before sending the request. + * + * @param sName Parameter name. + * @param sValue Parameter value. + * @return True on success, false on an invalid handle or if the request was + * already sent. + */ + public native bool SetGetOrPostParameter(const char[] sName, const char[] sValue); + + /** + * Appends extra user-agent info to the request. This does not clobber the normal user + * agent; it is added to the end. + * + * @param sUserAgentInfo Extra user-agent info string. + * @return True on success, false on an invalid handle. + */ + public native bool SetUserAgentInfo(const char[] sUserAgentInfo); + + /** + * Enables or disables verification of SSL/TLS certificates. By default, certificates + * are verified for all HTTPS requests. + * + * @param bRequireVerifiedCertificate True to require a verified certificate, false to disable. + * @return True on success, false on an invalid handle. + */ + public native bool SetRequiresVerifiedCertificate(bool bRequireVerifiedCertificate); + + /** + * Sets an absolute timeout, in milliseconds, on the request. Unlike the network-activity + * timeout, this is a total time limit that does not reset as data arrives. + * + * @param unMilliseconds Total timeout in milliseconds. + * @return True on success, false on an invalid handle. + */ + public native bool SetAbsoluteTimeoutMS(int unMilliseconds); + + /** + * Sets the callbacks fired for a request. Must be called before sending the request. + * The completion callback is used by both regular and streaming requests; the headers + * and data callbacks are only fired for streaming requests. + * + * @param fCompleted Callback fired when the request completes, or INVALID_FUNCTION. + * @param fHeaders Callback fired when streaming headers arrive, or INVALID_FUNCTION. + * @param fData Callback fired when a streaming data chunk arrives, or INVALID_FUNCTION. + * @param hCalling Handle of the plugin that owns the callbacks, or INVALID_HANDLE for + * the calling plugin. + * @return True on success, false on an invalid handle. + * @error Invalid plugin handle or invalid function. + */ + public native bool SetCallbacks(SteamWorksHTTPRequestCompleted fCompleted = INVALID_FUNCTION, SteamWorksHTTPHeadersReceived fHeaders = INVALID_FUNCTION, SteamWorksHTTPDataReceived fData = INVALID_FUNCTION, Handle hCalling = INVALID_HANDLE); + + /** + * Sends an HTTP request. The result is delivered asynchronously to the completion + * callback set with SetCallbacks. + * + * @return True if the request was sent, false on an invalid handle. + */ + public native bool Send(); + + /** + * Sends an HTTP request and streams the response. Headers and data are delivered + * asynchronously to the callbacks set with SetCallbacks. + * + * @return True if the request was sent, false on an invalid handle. + */ + public native bool SendAndStreamResponse(); + + /** + * Moves an already-sent request to the tail of the client's request queue. + * + * @return True on success, false on an invalid handle or if the request has + * not been sent. + */ + public native bool Defer(); + + /** + * Moves an already-sent request to the head of the client's request queue. + * + * @return True on success, false on an invalid handle or if the request has + * not been sent. + */ + public native bool Prioritize(); + + /** + * Checks whether a response header is present and retrieves the size of its value, so a + * correctly-sized buffer can be allocated for GetResponseHeaderValue. Call from the + * completion callback. + * + * @param sHeader Header name. + * @param size Variable to store the header value size in. + * @return True if the header is present, false otherwise. + */ + public native bool GetResponseHeaderSize(const char[] sHeader, int &size); + + /** + * Retrieves a response header value. Call from the completion callback. Use + * GetResponseHeaderSize first to size the buffer. + * + * @param sHeader Header name. + * @param sValue Buffer to store the header value in. + * @param size Maximum length of the buffer. + * @return True on success, false if the header is not present or the buffer + * is too small. + */ + public native bool GetResponseHeaderValue(const char[] sHeader, char[] sValue, int size); + + /** + * Retrieves the size of the response body. Call from the completion callback. + * + * @param size Variable to store the body size in. + * @return True on success, false on an invalid handle. + */ + public native bool GetResponseBodySize(int &size); + + /** + * Retrieves the response body. Call from the completion callback. Use GetResponseBodySize + * first to size the buffer. Not valid for streaming responses. + * + * @param sBody Buffer to store the body in. + * @param length Length of the buffer, which must match the body size. + * @return True on success, false on an invalid handle, a streaming response, + * or an incorrectly-sized buffer. + */ + public native bool GetResponseBodyData(char[] sBody, int length); + + /** + * Retrieves a chunk of a streaming response body. Call from the data-received callback, + * passing the offset and length reported by that callback. + * + * @param cOffset Offset of the chunk, as provided by the data-received callback. + * @param sBody Buffer to store the chunk in. + * @param length Length of the chunk, as provided by the data-received callback. + * @return True on success, false on an invalid handle, a non-streaming + * response, or a mismatched offset/length. + */ + public native bool GetStreamingResponseBodyData(int cOffset, char[] sBody, int length); + + /** + * Retrieves download progress for the request. This is zero until a response header with + * a content-length has been received; for responses with no content-length it stays zero. + * + * @param percent Variable to store the progress percentage in. + * @return True on success, false on an invalid handle. + */ + public native bool GetDownloadProgressPct(float &percent); + + /** + * Checks whether the request failed because it timed out (rather than a harder failure). + * + * @param bWasTimedOut Variable to store the result in. + * @return True on success, false on an invalid handle. + */ + public native bool GetWasTimedOut(bool &bWasTimedOut); + + /** + * Sets a raw body for a POST request. Fails on a GET request or if GET/POST parameters + * were already set. This makes the raw body the entire contents of the POST. + * + * @param sContentType Value for the Content-Type header. + * @param sBody Raw body data. + * @param bodylen Length of the body, in bytes. + * @return True on success, false on failure. + */ + public native bool SetRawPostBody(const char[] sContentType, const char[] sBody, int bodylen); + + /** + * Sets a raw POST body read from a file (relative to the game directory). Same constraints + * as SetRawPostBody. + * + * @param sContentType Value for the Content-Type header. + * @param sFileName Path to the file, relative to the game directory. + * @return True on success, false on failure (e.g. an empty file). + * @error Unable to open the file for reading. + */ + public native bool SetRawPostBodyFromFile(const char[] sContentType, const char[] sFileName); + + /** + * Retrieves the full response body and passes it to a callback. Useful for bodies larger + * than a single fixed buffer. Call from the completion callback. + * + * @param fCallback Callback that receives the body. + * @param data Context value passed through to the callback. + * @param hPlugin Handle of the plugin that owns the callback, or INVALID_HANDLE for + * the calling plugin. + * @return True on success, false on an invalid handle or if the body could not + * be retrieved. + * @error Invalid plugin handle or invalid function. + */ + public native bool GetResponseBodyCallback(SteamWorksHTTPBodyCallback fCallback, any data = 0, Handle hPlugin = INVALID_HANDLE); + + /** + * Writes the full response body to a file (relative to the game directory). Call from the + * completion callback. + * + * @param sFileName Path to the output file, relative to the game directory. + * @return True on success, false on an invalid handle or if the body could not + * be retrieved. + * @error Unable to open the file for writing. + */ + public native bool WriteResponseBodyToFile(const char[] sFileName); +}; + +/** + * Deprecated alias for SteamWorks_OnValidateClient, kept for backwards compatibility. + * Use SteamWorks_OnValidateClient in new code. + * + * @param ownerauthid 32-bit account ID of the account that owns the game license. + * @param authid 32-bit account ID of the validated client. + */ +forward void SW_OnValidateClient(int ownerauthid, int authid); + +/** + * Called when a client has been validated by Steam. For clients playing on a borrowed + * (Family Sharing) license, the owner and client account IDs differ. + * + * @param ownerauthid 32-bit account ID of the account that owns the game license. + * @param authid 32-bit account ID of the validated client. + */ +forward void SteamWorks_OnValidateClient(int ownerauthid, int authid); + +/** + * Called when the server successfully connects (logs on) to Steam. + */ +forward void SteamWorks_SteamServersConnected(); + +/** + * Called when the server fails to connect to Steam. + * + * @param result Result code describing the failure. + */ +forward void SteamWorks_SteamServersConnectFailure(EResult result); + +/** + * Called when the server is disconnected from Steam. + * + * @param result Result code describing the disconnection. + */ +forward void SteamWorks_SteamServersDisconnected(EResult result); + +/** + * Called when the Steam master server has requested that the server restart. Return + * Plugin_Handled or higher to indicate the restart request has been handled. + * + * @return Plugin_Handled or higher to signal the restart was handled, + * Plugin_Continue otherwise. + */ +forward Action SteamWorks_RestartRequested(); + +/** + * Called when the server is about to log on anonymously, giving a plugin the chance to + * supply a Game Server Login Token (GSLT) instead. Write the token into sToken. + * + * @param sToken Buffer to write the login token into. + * @param maxlen Maximum length of the buffer. + */ +forward void SteamWorks_TokenRequested(char[] sToken, int maxlen); + +/** + * Called with the result of a SteamWorks_GetUserGroupStatus / + * SteamWorks_GetUserGroupStatusAuthID request. + * + * @param authid 32-bit account ID of the user. + * @param groupid 32-bit account ID of the group. + * @param isMember True if the user is a member of the group. + * @param isOfficer True if the user is an officer of the group. + */ +forward void SteamWorks_OnClientGroupStatus(int authid, int groupid, bool isMember, bool isOfficer); + +/** + * Called when the game code sends a message to the Game Coordinator, letting a plugin + * observe or override it. Return a non-OK EGCResults value to supersede the send, or + * k_EGCResultOK to let it proceed. + * + * @param unMsgType Message type. + * @param pubData Message payload. + * @param cubData Size of the payload, in bytes. + * @return An EGCResults value to override the send, or k_EGCResultOK to allow it. + */ +forward EGCResults SteamWorks_GCSendMessage(int unMsgType, const char[] pubData, int cubData); + +/** + * Called when a message from the Game Coordinator is available to be retrieved. + * + * @param cubData Size of the available message, in bytes. + */ +forward void SteamWorks_GCMsgAvailable(int cubData); + +/** + * Called when the game code retrieves a message from the Game Coordinator, letting a plugin + * observe or override it. Return a non-OK EGCResults value to supersede the retrieval. + * + * @param punMsgType Message type. + * @param pubDest Message payload. + * @param cubDest Size of the destination buffer, in bytes. + * @param pcubMsgSize Size of the message, in bytes. + * @return An EGCResults value to override the retrieval, or k_EGCResultOK to + * allow it. + */ +forward EGCResults SteamWorks_GCRetrieveMessage(int punMsgType, const char[] pubDest, int cubDest, int pcubMsgSize); + +/** + * Sends a message to the Game Coordinator. + * + * @param unMsgType Message type. + * @param pubData Message payload. + * @param cubData Size of the payload, in bytes. + * @return An EGCResults value; k_EGCResultNotLoggedOn if not connected to Steam. + */ +native EGCResults SteamWorks_SendMessageToGC(int unMsgType, const char[] pubData, int cubData); + +public Extension __ext_SteamWorks = +{ + name = "SteamWorks", + file = "SteamWorks.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public void __ext_SteamWorks_SetNTVOptional() +{ + MarkNativeAsOptional("SteamWorks_IsVACEnabled"); + MarkNativeAsOptional("SteamWorks_GetPublicIP"); + MarkNativeAsOptional("SteamWorks_GetPublicIPCell"); + MarkNativeAsOptional("SteamWorks_IsLoaded"); + MarkNativeAsOptional("SteamWorks_SetGameData"); + MarkNativeAsOptional("SteamWorks_SetGameDescription"); + MarkNativeAsOptional("SteamWorks_IsConnected"); + MarkNativeAsOptional("SteamWorks_SetRule"); + MarkNativeAsOptional("SteamWorks_ClearRules"); + MarkNativeAsOptional("SteamWorks_SetAdvertiseServerActive"); + MarkNativeAsOptional("SteamWorks_ForceHeartbeat"); + MarkNativeAsOptional("SteamWorks_GetUserGroupStatus"); + MarkNativeAsOptional("SteamWorks_GetUserGroupStatusAuthID"); + + MarkNativeAsOptional("SteamWorks_HasLicenseForApp"); + MarkNativeAsOptional("SteamWorks_HasLicenseForAppId"); + MarkNativeAsOptional("SteamWorks_GetClientSteamID"); + + MarkNativeAsOptional("SteamWorks_RequestStatsAuthID"); + MarkNativeAsOptional("SteamWorks_RequestStats"); + MarkNativeAsOptional("SteamWorks_GetStatCell"); + MarkNativeAsOptional("SteamWorks_GetStatAuthIDCell"); + MarkNativeAsOptional("SteamWorks_GetStatFloat"); + MarkNativeAsOptional("SteamWorks_GetStatAuthIDFloat"); + + MarkNativeAsOptional("SteamWorks_SendMessageToGC"); + + MarkNativeAsOptional("SteamWorks_CreateHTTPRequest"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestContextValue"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestNetworkActivityTimeout"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestHeaderValue"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestGetOrPostParameter"); + + MarkNativeAsOptional("SteamWorks_SetHTTPCallbacks"); + MarkNativeAsOptional("SteamWorks_SendHTTPRequest"); + MarkNativeAsOptional("SteamWorks_SendHTTPRequestAndStreamResponse"); + MarkNativeAsOptional("SteamWorks_DeferHTTPRequest"); + MarkNativeAsOptional("SteamWorks_PrioritizeHTTPRequest"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderSize"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderValue"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodySize"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyData"); + MarkNativeAsOptional("SteamWorks_GetHTTPStreamingResponseBodyData"); + MarkNativeAsOptional("SteamWorks_GetHTTPDownloadProgressPct"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestRawPostBody"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestRawPostBodyFromFile"); + + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyCallback"); + MarkNativeAsOptional("SteamWorks_WriteHTTPResponseBodyToFile"); + + MarkNativeAsOptional("SteamWorksHTTPRequest.SteamWorksHTTPRequest"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetContextValue"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetNetworkActivityTimeout"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetHeaderValue"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetGetOrPostParameter"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetUserAgentInfo"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetRequiresVerifiedCertificate"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetAbsoluteTimeoutMS"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetCallbacks"); + MarkNativeAsOptional("SteamWorksHTTPRequest.Send"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SendAndStreamResponse"); + MarkNativeAsOptional("SteamWorksHTTPRequest.Defer"); + MarkNativeAsOptional("SteamWorksHTTPRequest.Prioritize"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetResponseHeaderSize"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetResponseHeaderValue"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetResponseBodySize"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetResponseBodyData"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetStreamingResponseBodyData"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetDownloadProgressPct"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetWasTimedOut"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetRawPostBody"); + MarkNativeAsOptional("SteamWorksHTTPRequest.SetRawPostBodyFromFile"); + MarkNativeAsOptional("SteamWorksHTTPRequest.GetResponseBodyCallback"); + MarkNativeAsOptional("SteamWorksHTTPRequest.WriteResponseBodyToFile"); +} +#endif diff --git a/ext/sourcepawn-client/include/json.inc b/ext/sourcepawn-client/include/json.inc new file mode 100644 index 0000000..a426dd7 --- /dev/null +++ b/ext/sourcepawn-client/include/json.inc @@ -0,0 +1,803 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_included + #endinput +#endif +#define _json_included + +#include +#include +#include +#include +#include +#include +#include + +/** + * Calculates the buffer size required to store an encoded JSON instance. + * + * @param obj Object to encode. + * @param options Bitwise combination of `JSON_ENCODE_*` options. + * @param depth The current depth of the encoder. + * @return The required buffer size. + */ +stock int json_encode_size(JSON_Object obj, int options = JSON_NONE, int depth = 0) +{ + bool pretty_print = (options & JSON_ENCODE_PRETTY) != 0; + + bool is_array = obj.IsArray; + + int size = 1; // for opening bracket + + // used in key iterator + int json_size = obj.Length; + JSON_Object child = null; + bool is_empty = true; + int str_length = 0; + + int key_length = 0; + for (int i = 0; i < json_size; i += 1) { + key_length = is_array ? JSON_INT_BUFFER_SIZE : obj.GetKeySize(i); + char[] key = new char[key_length]; + + if (is_array) { + IntToString(i, key, key_length); + } else { + obj.GetKey(i, key, key_length); + } + + // skip keys that are marked as hidden + if (obj.GetHidden(key)) { + continue; + } + + JSONCellType type = obj.GetType(key); + // skip keys of unknown type + if (type == JSON_Type_Invalid) { + continue; + } + + if (pretty_print) { + size += strlen(JSON_PP_NEWLINE); + size += (depth + 1) * strlen(JSON_PP_INDENT); + } + + if (! is_array) { + // add the size of the key and + 1 for : + size += json_cell_string_size(key) + 1; + + if (pretty_print) { + size += strlen(JSON_PP_AFTER_COLON); + } + } + + switch (type) { + case JSON_Type_String: { + str_length = obj.GetSize(key); + char[] value = new char[str_length]; + obj.GetString(key, value, str_length); + + size += json_cell_string_size(value); + } + case JSON_Type_Int: { + size += JSON_INT_BUFFER_SIZE; + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + size += JSON_INT64_BUFFER_SIZE; + } + #endif + case JSON_Type_Float: { + size += JSON_FLOAT_BUFFER_SIZE; + } + case JSON_Type_Bool: { + size += JSON_BOOL_BUFFER_SIZE; + } + case JSON_Type_Object: { + child = obj.GetObject(key); + size += child != null ? json_encode_size(child, options, depth + 1) : JSON_NULL_BUFFER_SIZE; + } + } + + // increment for comma + size += 1; + + is_empty = false; + } + + if (! is_empty) { + // remove the final comma + size -= 1; + + if (pretty_print) { + size += strlen(JSON_PP_NEWLINE); + size += depth * strlen(JSON_PP_INDENT); + } + } + + size += 2; // closing bracket + NULL + + return size; +} + +/** + * Encodes a JSON instance into its string representation. + * + * @param obj Object to encode. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + * @param options Bitwise combination of `JSON_ENCODE_*` options. + * @param depth The current depth of the encoder. + */ +stock void json_encode( + JSON_Object obj, + char[] output, + int max_size, + int options = JSON_NONE, + int depth = 0 +) +{ + bool pretty_print = (options & JSON_ENCODE_PRETTY) != 0; + + bool is_array = obj.IsArray; + strcopy(output, max_size, is_array ? "[" : "{"); + + // used in key iterator + int json_size = obj.Length; + int builder_size = 0; + int str_length = 1; + JSON_Object child = null; + int cell_length = 0; + bool is_empty = true; + + int key_length = 0; + for (int i = 0; i < json_size; i += 1) { + key_length = is_array ? JSON_INT_BUFFER_SIZE : obj.GetKeySize(i); + char[] key = new char[key_length]; + + if (is_array) { + IntToString(i, key, key_length); + } else { + obj.GetKey(i, key, key_length); + } + + // skip keys that are marked as hidden + if (obj.GetHidden(key)) { + continue; + } + + JSONCellType type = obj.GetType(key); + // skip keys of unknown type + if (type == JSON_Type_Invalid) { + continue; + } + + // determine the length of the char[] needed to represent our cell data + cell_length = 0; + switch (type) { + case JSON_Type_String: { + str_length = obj.GetSize(key); + char[] value = new char[str_length]; + obj.GetString(key, value, str_length); + + cell_length = json_cell_string_size(value); + } + case JSON_Type_Int: { + cell_length = JSON_INT_BUFFER_SIZE; + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + cell_length = JSON_INT64_BUFFER_SIZE; + } + #endif + case JSON_Type_Float: { + cell_length = JSON_FLOAT_BUFFER_SIZE; + } + case JSON_Type_Bool: { + cell_length = JSON_BOOL_BUFFER_SIZE; + } + case JSON_Type_Object: { + child = obj.GetObject(key); + cell_length = child != null ? max_size : JSON_NULL_BUFFER_SIZE; + } + } + + // fit the contents into the cell + char[] cell = new char[cell_length]; + switch (type) { + case JSON_Type_String: { + char[] value = new char[str_length]; + obj.GetString(key, value, str_length); + json_cell_string(value, cell, cell_length); + } + case JSON_Type_Int: { + int value = obj.GetInt(key); + IntToString(value, cell, cell_length); + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + int value[2]; + obj.GetInt64(key, value); + Int64ToString(value, cell, cell_length); + } + #endif + case JSON_Type_Float: { + float value = obj.GetFloat(key); + FloatToString(value, cell, cell_length); + + // trim trailing 0s from float output up until decimal point + int last_char = strlen(cell) - 1; + while (cell[last_char] == '0' && cell[last_char - 1] != '.') { + cell[last_char--] = '\0'; + } + } + case JSON_Type_Bool: { + bool value = obj.GetBool(key); + strcopy(cell, cell_length, value ? "true" : "false"); + } + case JSON_Type_Object: { + if (child != null) { + json_encode(child, cell, cell_length, options, depth + 1); + } else { + strcopy(cell, cell_length, "null"); + } + } + } + + // make the builder fit our key:value + // use previously determined cell length and + 1 for , + builder_size = cell_length + 1; + if (! is_array) { + // get the length of the key and + 1 for : + builder_size += json_cell_string_size(key) + 1; + + if (pretty_print) { + builder_size += strlen(JSON_PP_AFTER_COLON); + } + } + + char[] builder = new char[builder_size]; + strcopy(builder, builder_size, ""); + + // add the key if we're working with an object + if (! is_array) { + json_cell_string(key, builder, builder_size); + StrCat(builder, builder_size, ":"); + + if (pretty_print) { + StrCat(builder, builder_size, JSON_PP_AFTER_COLON); + } + } + + // add the value and a trailing comma + StrCat(builder, builder_size, cell); + StrCat(builder, builder_size, ","); + + // prepare pretty printing then send builder to output afterwards + if (pretty_print) { + StrCat(output, max_size, JSON_PP_NEWLINE); + + for (int j = 0; j < depth + 1; j += 1) { + StrCat(output, max_size, JSON_PP_INDENT); + } + } + + StrCat(output, max_size, builder); + + is_empty = false; + } + + if (! is_empty) { + // remove the final comma + output[strlen(output) - 1] = '\0'; + + if (pretty_print) { + StrCat(output, max_size, JSON_PP_NEWLINE); + + for (int j = 0; j < depth; j += 1) { + StrCat(output, max_size, JSON_PP_INDENT); + } + } + } + + // append closing bracket + StrCat(output, max_size, is_array ? "]" : "}"); +} + +/** + * Decodes a JSON string into a JSON instance. + * + * @param buffer Buffer to decode. + * @param options Bitwise combination of `JSON_DECODE_*` options. + * @param pos Current position of the decoder as bytes + * offset into the buffer. + * @param depth Current nested depth of the decoder. + * @return JSON instance or null if decoding failed becase + * the buffer didn't contain valid JSON. + * @error If the buffer does not contain valid JSON, + * an error will be thrown. + */ +stock JSON_Object json_decode( + const char[] buffer, + int options = JSON_NONE, + int &pos = 0, + int depth = 0 +) +{ + int length = strlen(buffer); + // skip preceding whitespace + if (! json_skip_whitespace(buffer, length, pos)) { + json_set_last_error("buffer ended early at position %d", pos); + + return null; + } + + bool is_array = false; + JSON_Array arr = null; + JSON_Object obj = null; + if (buffer[pos] == '{') { + is_array = false; + obj = new JSON_Object(); + } else if (buffer[pos] == '[') { + is_array = true; + arr = new JSON_Array(); + } else { + json_set_last_error("no object or array found at position %d", pos); + + return null; + } + + bool allow_single_quotes = (options & JSON_DECODE_SINGLE_QUOTES) > 0; + + bool empty_checked = false; + + // while we haven't reached the end of our structure + while ( + (! is_array && buffer[pos] != '}') + || (is_array && buffer[pos] != ']') + ) { + // pos is either an opening structure or comma, so increment past it + pos += 1; + + // skip any whitespace preceding the element + if (! json_skip_whitespace(buffer, length, pos)) { + json_set_last_error("buffer ended early at position %d", pos); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + // if we haven't checked for empty yet and we are at the end + // of an object or array, we can stop here (empty structure) + if (! empty_checked) { + if ( + (! is_array && buffer[pos] == '}') + || (is_array && buffer[pos] == ']') + ) { + break; + } + + empty_checked = true; + } + + int key_length = 1; + if (! is_array) { + // if dealing with an object, look for the key and determine length + if (! json_is_string(buffer[pos], allow_single_quotes)) { + json_set_last_error("expected key string at position %d", pos); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + key_length = json_extract_string_size(buffer, length, pos); + } + + char[] key = new char[key_length]; + + if (! is_array) { + // extract the key from the buffer + json_extract_string(buffer, length, pos, key, key_length, is_array); + + // skip any whitespace following the key + if (! json_skip_whitespace(buffer, length, pos)) { + json_set_last_error("buffer ended early at position %d", pos); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + // ensure that we find a colon + if (buffer[pos++] != ':') { + json_set_last_error( + "expected colon after key at position %d", + pos + ); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + // skip any whitespace following the colon + if (! json_skip_whitespace(buffer, length, pos)) { + json_set_last_error("buffer ended early at position %d", pos); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + } + + int cell_length = 1; + JSONCellType cell_type = JSON_Type_Invalid; + if (buffer[pos] == '{' || buffer[pos] == '[') { + cell_type = JSON_Type_Object; + } else if (json_is_string(buffer[pos], allow_single_quotes)) { + cell_type = JSON_Type_String; + cell_length = json_extract_string_size(buffer, length, pos); + } else { + // in this particular instance, we use JSON_Type_Invalid to + // represent any type that isn't an object or string + cell_length = json_extract_until_end_size( + buffer, + length, + pos, + is_array + ); + } + + if (! is_array && obj.HasKey(key)) { + obj.Remove(key); + } + + char[] cell = new char[cell_length]; + switch (cell_type) { + case JSON_Type_Object: { + // if we are dealing with an object or array, decode recursively + JSON_Object value = json_decode( + buffer, + options, + pos, + depth + 1 + ); + + // decoding failed, error will be logged in json_decode + if (value == null) { + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + if (is_array) { + arr.PushObject(value); + } else { + obj.SetObject(key, value); + } + } + case JSON_Type_String: { + // if we are dealing with a string, attempt to extract it + if (! json_extract_string( + buffer, + length, + pos, + cell, + cell_length, + is_array + )) { + json_set_last_error( + "couldn't extract string at position %d", + pos + ); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + if (is_array) { + arr.PushString(cell); + } else { + obj.SetString(key, cell); + } + } + case JSON_Type_Invalid: { + if (! json_extract_until_end( + buffer, + length, + pos, + cell, + cell_length, + is_array + )) { + json_set_last_error( + "couldn't extract until end at position %d", + pos + ); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + if (strlen(cell) == 0) { + json_set_last_error( + "empty cell encountered at position %d", + pos + ); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + if (json_is_int(cell)) { + int value = StringToInt(cell); + #if SM_INT64_SUPPORTED + if (json_is_int64(cell, value)) { + int values[2]; + StringToInt64(cell, values); + + if (is_array) { + arr.PushInt64(values); + } else { + obj.SetInt64(key, values); + } + } else { + if (is_array) { + arr.PushInt(value); + } else { + obj.SetInt(key, value); + } + } + #else + if (is_array) { + arr.PushInt(value); + } else { + obj.SetInt(key, value); + } + #endif + } else if (json_is_float(cell)) { + float value = StringToFloat(cell); + if (is_array) { + arr.PushFloat(value); + } else { + obj.SetFloat(key, value); + } + } else if (StrEqual(cell, "true") || StrEqual(cell, "false")) { + bool value = StrEqual(cell, "true"); + if (is_array) { + arr.PushBool(value); + } else { + obj.SetBool(key, value); + } + } else if (StrEqual(cell, "null")) { + if (is_array) { + arr.PushObject(null); + } else { + obj.SetObject(key, null); + } + } else { + json_set_last_error( + "unknown type encountered at position %d: %s", + pos, + cell + ); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + } + } + + if (! json_skip_whitespace(buffer, length, pos)) { + json_set_last_error("buffer ended early at position %d", pos); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + } + + // skip remaining whitespace and ensure we're at the end of the buffer + pos += 1; + if (json_skip_whitespace(buffer, length, pos) && depth == 0) { + json_set_last_error( + "unexpected data after structure end at position %d", + pos + ); + json_cleanup_and_delete(obj); + json_cleanup_and_delete(arr); + + return null; + } + + return is_array ? view_as(arr) : obj; +} + +/** + * Encodes the object with the options provided and writes + * the output to the file at the path specified. + * + * @param obj Object to encode/write to file. + * @param path Path of file to write to. + * @param options Options to pass to `json_encode`. + * @return True on success, false otherwise. + */ +stock bool json_write_to_file( + JSON_Object obj, + const char[] path, + int options = JSON_NONE +) +{ + File f = OpenFile(path, "wb"); + if (f == null) { + return false; + } + + int size = json_encode_size(obj, options); + char[] buffer = new char[size]; + json_encode(obj, buffer, size, options); + + bool success = f.WriteString(buffer, false); + delete f; + + return success; +} + +/** + * Reads and decodes the contents of a JSON file. + * + * @param path Path of file to read from. + * @param options Options to pass to `json_decode`. + * @return The decoded object on success, null otherwise. + */ +stock JSON_Object json_read_from_file(const char[] path, int options = JSON_NONE) +{ + File f = OpenFile(path, "rb"); + if (f == null) { + return null; + } + + f.Seek(0, SEEK_END); + int size = f.Position + 1; + char[] buffer = new char[size]; + + f.Seek(0, SEEK_SET); + f.ReadString(buffer, size); + delete f; + + return json_decode(buffer, options); +} + +/** + * Creates a shallow copy of the specified object. + * + * @param obj Object to copy. + * @return A shallow copy of the specified object. + */ +stock JSON_Object json_copy_shallow(JSON_Object obj) +{ + bool isArray = obj.IsArray; + JSON_Object result = isArray + ? view_as(new JSON_Array()) + : new JSON_Object(); + + if (isArray) { + view_as(result).Concat(view_as(obj)); + } else { + result.Merge(obj); + } + + return result; +} + +/** + * Creates a deep copy of the specified object. + * + * @param obj Object to copy. + * @return A deep copy of the specified object. + */ +stock JSON_Object json_copy_deep(JSON_Object obj) +{ + JSON_Object result = json_copy_shallow(obj); + + int length = obj.Length; + int key_length = 0; + for (int i = 0; i < length; i += 1) { + key_length = obj.GetKeySize(i); + char[] key = new char[key_length]; + obj.GetKey(i, key, key_length); + + // only deep copy objects + JSONCellType type = obj.GetType(key); + if (type != JSON_Type_Object) { + continue; + } + + JSON_Object value = obj.GetObject(key); + result.SetObject(key, value != null ? json_copy_deep(value) : null); + } + + return result; +} + +/** + * Recursively cleans up the instance and any instances stored within. + * + * @param obj Object to clean up. + */ +stock void json_cleanup(JSON_Object obj) +{ + if (obj == null) { + return; + } + + int length = obj.Length; + int key_length = 0; + for (int i = 0; i < length; i += 1) { + key_length = obj.GetKeySize(i); + char[] key = new char[key_length]; + obj.GetKey(i, key, key_length); + + // only clean up objects + JSONCellType type = obj.GetType(key); + if (type != JSON_Type_Object) { + continue; + } + + JSON_Object value = obj.GetObject(key); + if (value != null) { + json_cleanup(value); + } + } + + obj.Super.Cleanup(); +} + +/** + * Cleans up an object and sets the passed variable to null. + * + * @param obj Object to clean up. + */ +stock void json_cleanup_and_delete(JSON_Object &obj) +{ + json_cleanup(obj); + obj = null; +} diff --git a/ext/sourcepawn-client/include/json/array.inc b/ext/sourcepawn-client/include/json/array.inc new file mode 100644 index 0000000..5f41162 --- /dev/null +++ b/ext/sourcepawn-client/include/json/array.inc @@ -0,0 +1,955 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_array_included + #endinput +#endif +#define _json_array_included + +#include +#include +#include +#include + +methodmap JSON_Array < JSON_Object +{ + /** + * @section Helpers + */ + + /** + * Views the instance as its superclass to access overridden methods. + */ + property JSON_Object Super + { + public get() + { + return view_as(this); + } + } + + /** + * The enforced type of the array. + */ + property JSONCellType Type + { + public get() + { + return view_as(this.Meta.GetOptionalValue( + JSON_ENFORCE_TYPE_KEY, + JSON_Type_Invalid + )); + } + + public set(JSONCellType value) + { + if (value == JSON_Type_Invalid) { + this.Meta.Remove(JSON_ENFORCE_TYPE_KEY); + } else { + this.Meta.SetValue(JSON_ENFORCE_TYPE_KEY, value); + } + } + } + + /** + * Checks whether the array accepts the type provided. + * + * @param type Type to check for enforcement. + * @return True if the type can be used, false otherwise. + */ + public bool CanUseType(JSONCellType type) + { + return this.Type == JSON_Type_Invalid || this.Type == type; + } + + /** + * Checks whether the object has an index. + * + * @param index Index to check existence of. + * @return True if the index exists, false otherwise. + */ + public bool HasKey(int index) + { + return index >= 0 && index < this.Length; + } + + /** + * @section Metadata Getters + */ + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * @internal + * + * @see MetaStringMap.GetMeta + */ + public any GetMeta(int index, JSONMetaInfo meta, any default_value) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return default_value; + } + + return this.Super.GetMeta(key, meta, default_value); + } + + /** + * Gets the cell type stored at an index. + * + * @param index Index to get value type for. + * @return Value type for index provided, + * or JSON_Type_Invalid if it does not exist. + */ + public JSONCellType GetType(int index) + { + return view_as( + this.GetMeta(index, JSON_Meta_Type, JSON_Type_Invalid) + ); + } + + /** + * Gets the length of the string stored at an index. + * + * @param index Index to get string length for. + * @return Length of string at index provided, + * or -1 if it is not a string/does not exist. + */ + public int GetSize(int index) + { + return view_as(this.GetMeta(index, JSON_Meta_Size, -1)); + } + + /** + * Gets whether the index should be hidden from encoding. + * + * @param index Index to get hidden state for. + * @return Whether or not the index should be hidden. + */ + public bool GetHidden(int index) + { + return view_as(this.GetMeta(index, JSON_Meta_Hidden, false)); + } + + /** + * @section Metadata Setters + */ + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * @internal + * + * @see JSON_Object.SetMeta + */ + public bool SetMeta(int index, JSONMetaInfo meta, any value) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetMeta(key, meta, value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * @internal + * + * @see JSON_Object.RemoveMeta + */ + public bool RemoveMeta(int index, JSONMetaInfo meta) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.RemoveMeta(key, meta); + } + + /** + * Sets whether the index should be hidden from encoding. + * + * @param index Index to set hidden state for. + * @param hidden Whether or not the index should be hidden. + * @return True on success, false otherwise. + */ + public bool SetHidden(int index, bool hidden) + { + return this.SetMeta(index, JSON_Meta_Hidden, hidden); + } + + /** + * @section Getters + */ + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see MetaStringMap.GetValue + */ + public bool GetValue(int index, any &value) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.GetValue(key, value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * @internal + * + * @see MetaStringMap.GetOptionalValue + */ + public any GetOptionalValue(int index, any default_value = -1) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.GetOptionalValue(key, default_value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see MetaStringMap.GetString + */ + public bool GetString(int index, char[] value, int max_size, int &size = 0) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.GetString(key, value, max_size, size); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see MetaStringMap.GetInt + */ + public int GetInt(int index, int default_value = -1) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return default_value; + } + + return this.Super.GetInt(key, default_value); + } + + #if SM_INT64_SUPPORTED + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.GetInt64 + */ + public bool GetInt64(int index, int value[2]) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.GetInt64(key, value); + } + #endif + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see MetaStringMap.GetFloat + */ + public float GetFloat(int index, float default_value = -1.0) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return default_value; + } + + return this.Super.GetFloat(key, default_value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see MetaStringMap.GetBool + */ + public bool GetBool(int index, bool default_value = false) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return default_value; + } + + return this.Super.GetBool(key, default_value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see MetaStringMap.GetObject + */ + public JSON_Object GetObject(int index, JSON_Object default_value = null) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return default_value; + } + + return this.Super.GetObject(key, default_value); + } + + /** + * @section Setters + */ + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.SetString + */ + public bool SetString(int index, const char[] value) + { + if (! this.CanUseType(JSON_Type_String)) { + return false; + } + + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetString(key, value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.SetInt + */ + public bool SetInt(int index, int value) + { + if (! this.CanUseType(JSON_Type_Int)) { + return false; + } + + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetInt(key, value); + } + + #if SM_INT64_SUPPORTED + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.SetInt64 + */ + public bool SetInt64(int index, int value[2]) + { + if (! this.CanUseType(JSON_Type_Int64)) { + return false; + } + + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetInt64(key, value); + } + #endif + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.SetFloat + */ + public bool SetFloat(int index, float value) + { + if (! this.CanUseType(JSON_Type_Float)) { + return false; + } + + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetFloat(key, value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.SetBool + */ + public bool SetBool(int index, bool value) + { + if (! this.CanUseType(JSON_Type_Bool)) { + return false; + } + + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetBool(key, value); + } + + /** + * Converts index to a string ('key') and calls the relevant Super method. + * + * @see JSON_Object.SetObject + */ + public bool SetObject(int index, JSON_Object value) + { + if (! this.CanUseType(JSON_Type_Object)) { + return false; + } + + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + return this.Super.SetObject(key, value); + } + + /** + * @section Pushers + */ + + /** + * Pushes a string to the end of the array. + * + * @param value Value to push. + * @return The element's index on success, -1 otherwise. + */ + public int PushString(const char[] value) + { + int index = this.Length; + if (! this.SetString(index, value)) { + return -1; + } + + return index; + } + + /** + * Pushes an int to the end of the array. + * + * @param value Value to push. + * @return The element's index on success, -1 otherwise. + */ + public int PushInt(int value) + { + int index = this.Length; + if (! this.SetInt(index, value)) { + return -1; + } + + return index; + } + + #if SM_INT64_SUPPORTED + /** + * Pushes an int64 to the end of the array. + * + * @param value Value to push. + * @return The element's index on success, -1 otherwise. + */ + public int PushInt64(int value[2]) + { + int index = this.Length; + if (! this.SetInt64(index, value)) { + return -1; + } + + return index; + } + #endif + + /** + * Pushes a float to the end of the array. + * + * @param value Value to push. + * @return The element's index on success, -1 otherwise. + */ + public int PushFloat(float value) + { + int index = this.Length; + if (! this.SetFloat(index, value)) { + return -1; + } + + return index; + } + + /** + * Pushes a bool to the end of the array. + * + * @param value Value to push. + * @return The element's index on success, -1 otherwise. + */ + public int PushBool(bool value) + { + int index = this.Length; + if (! this.SetBool(index, value)) { + return -1; + } + + return index; + } + + /** + * Pushes a JSON object to the end of the array. + * + * @param value Value to push. + * @return The element's index on success, -1 otherwise. + */ + public int PushObject(JSON_Object value) + { + int index = this.Length; + if (! this.SetObject(index, value)) { + return -1; + } + + return index; + } + + /** + * @section Search Helpers + */ + + /** + * Finds the index of a value in the array. + * + * @param value Value to search for. + * @return The index of the value if it is found, -1 otherwise. + */ + public int IndexOf(any value) + { + any current; + int length = this.Length; + for (int i = 0; i < length; i += 1) { + if (this.GetValue(i, current) && value == current) { + return i; + } + } + + return -1; + } + + /** + * Finds the index of a string in the array. + * + * @param value String to search for. + * @return The index of the string if it is found, -1 otherwise. + */ + public int IndexOfString(const char[] value) + { + int length = this.Length; + for (int i = 0; i < length; i += 1) { + if (this.GetType(i) != JSON_Type_String) { + continue; + } + + int current_size = this.GetSize(i); + char[] current = new char[current_size]; + this.GetString(i, current, current_size); + if (StrEqual(value, current)) { + return i; + } + } + + return -1; + } + + /** + * Determines whether the array contains a value. + * + * @param value Value to search for. + * @return True if the value is found, false otherwise. + */ + public bool Contains(any value) + { + return this.IndexOf(value) != -1; + } + + /** + * Determines whether the array contains a string. + * + * @param value String to search for. + * @return True if the string is found, false otherwise. + */ + public bool ContainsString(const char[] value) + { + return this.IndexOfString(value) != -1; + } + + /** + * @section Misc + */ + + /** + * Removes an index and its related meta-keys from the array, + * and shifts down all following element indices. + * + * @param key Key to remove. + * @return True on success, false if the value was never set. + */ + public bool Remove(int index) + { + char key[JSON_INT_BUFFER_SIZE]; + if (! this.GetKey(index, key, sizeof(key))) { + return false; + } + + int length = this.Length; + + // remove existing value at index + if (! this.Super.Remove(key)) { + return false; + } + + // shift all following elements down + char current_key[JSON_INT_BUFFER_SIZE]; + for (int oldIndex = index + 1; oldIndex < length; oldIndex += 1) { + int newIndex = oldIndex - 1; + JSONCellType type = this.GetType(oldIndex); + + switch (type) { + case JSON_Type_String: { + int str_length = this.GetSize(oldIndex); + char[] str_value = new char[str_length]; + + this.GetString(oldIndex, str_value, str_length); + this.SetString(newIndex, str_value); + } + case JSON_Type_Int: { + this.SetInt(newIndex, this.GetInt(oldIndex)); + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + int value[2]; + this.GetInt64(oldIndex, value); + this.SetInt64(newIndex, value); + } + #endif + case JSON_Type_Float: { + this.SetFloat(newIndex, this.GetFloat(oldIndex)); + } + case JSON_Type_Bool: { + this.SetBool(newIndex, this.GetBool(oldIndex)); + } + case JSON_Type_Object: { + this.SetObject(newIndex, this.GetObject(oldIndex)); + } + } + + this.SetHidden(newIndex, this.GetHidden(oldIndex)); + + if (this.GetKey( + oldIndex, + current_key, + sizeof(current_key) + )) { + this.Super.Remove(current_key); + } + } + + return true; + } + + /** + * Concatenates the entries from the specified array + * on to the end of this array. + * + * @param from Array to concat entries from. + * @return True on success, false otherwise. + * @error If the object being merged is an object or the + * arrays being merged don't have the same strict + * type set, an error will be thrown. + */ + public bool Concat(JSON_Array from) + { + if (! this.IsArray || ! from.IsArray) { + json_set_last_error("attempted to concat using object(s)"); + + return false; + } + + if (this.Type != from.Type) { + json_set_last_error( + "attempted to concat arrays with mismatched strict types" + ); + + return false; + } + + int current_length = this.Length; + int json_size = from.Length; + for (int i = 0; i < json_size; i += 1) { + JSONCellType type = from.GetType(i); + // skip keys of unknown type + if (type == JSON_Type_Invalid) { + continue; + } + + // push value onto array + switch (type) { + case JSON_Type_String: { + int length = from.GetSize(i); + char[] value = new char[length]; + from.GetString(i, value, length); + + this.PushString(value); + } + case JSON_Type_Int: { + this.PushInt(from.GetInt(i)); + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + int value[2]; + from.GetInt64(i, value); + this.PushInt64(value); + } + #endif + case JSON_Type_Float: { + this.PushFloat(from.GetFloat(i)); + } + case JSON_Type_Bool: { + this.PushBool(from.GetBool(i)); + } + case JSON_Type_Object: { + this.PushObject(from.GetObject(i)); + } + } + + this.SetHidden(current_length + i, from.GetHidden(i)); + } + + return true; + } + + /** + * Typed Helpers + */ + + /** + * The length of the longest string in the array. + */ + property int MaxStringLength + { + public get() + { + int max = -1; + int current = -1; + int length = this.Length; + for (int i = 0; i < length; i += 1) { + if (this.GetType(i) != JSON_Type_String) { + continue; + } + + current = this.GetSize(i); + if (current > max) { + max = current; + } + } + + return max; + } + } + + /** + * Sets the array to enforce a specific type. + * This will fail if there are any existing elements + * in the array which are not of the same type. + * + * @param type Type to enforce. + * @return True if the type was enforced successfully, false otherwise. + */ + public bool EnforceType(JSONCellType type) + { + if (type == JSON_Type_Invalid) { + this.Type = type; + + return true; + } + + int length = this.Length; + for (int i = 0; i < length; i += 1) { + if (this.GetType(i) != type) { + return false; + } + } + + this.Type = type; + + return true; + } + + /** + * Imports a native array's values into the instance. + * + * @param type Type of native values. + * @param values Array of values. + * @param size Size of array. + * @return True on success, false otherwise. + */ + public bool ImportValues(JSONCellType type, any[] values, int size) + { + bool success = true; + for (int i = 0; i < size; i += 1) { + switch (type) { + case JSON_Type_Int: { + success = success && this.PushInt(values[i]) > -1; + } + case JSON_Type_Float: { + success = success && this.PushFloat(values[i]) > -1; + } + case JSON_Type_Bool: { + success = success && this.PushBool(values[i]) > -1; + } + case JSON_Type_Object: { + success = success && this.PushObject(values[i]) > -1; + } + } + } + + return success; + } + + /** + * Imports a native array's strings into the instance. + * + * @param strings Array of strings. + * @param size Size of array. + * @return True on success, false otherwise. + */ + public bool ImportStrings(const char[][] strings, int size) + { + bool success = true; + for (int i = 0; i < size; i += 1) { + success = success && this.PushString(strings[i]) > -1; + } + + return success; + } + + /** + * Exports the instance's values into a native array. + * + * @param values Array to export to. + * @param max_size Maximum size of array. + */ + public void ExportValues(any[] values, int max_size) + { + int length = this.Length; + if (length < max_size) { + max_size = length; + } + + for (int i = 0; i < max_size; i += 1) { + this.GetValue(i, values[i]); + } + } + + /** + * Exports the instance's strings into a native array. + * + * @param values Array to export to. + * @param max_size Maximum size of array. + * @param max_string_size Maximum size of array elements. + */ + public void ExportStrings( + char[][] values, + int max_size, + int max_string_size + ) { + int length = this.Length; + if (length < max_size) { + max_size = length; + } + + for (int i = 0; i < max_size; i += 1) { + this.GetString(i, values[i], max_string_size); + } + } + + /** + * json.inc Aliases + */ + + /** @see JSON_Object.ShallowCopy */ + public JSON_Array ShallowCopy() + { + return view_as(this.Super.ShallowCopy()); + } + + /** @see JSON_Object.DeepCopy */ + public JSON_Array DeepCopy() + { + return view_as(this.Super.DeepCopy()); + } + + /** + * @section Constructor + */ + + /** + * Creates a new JSON_Array. + * + * @param type The type to enforce for this array, or + * JSON_Type_Invalid for no enforced type. + * @return A new JSON_Array. + */ + public JSON_Array(JSONCellType type = JSON_Type_Invalid) + { + JSON_Array self = view_as(new JSON_Object()); + self.Meta.SetBool(JSON_ARRAY_KEY, true); + self.EnforceType(type); + + return self; + } +}; diff --git a/ext/sourcepawn-client/include/json/definitions.inc b/ext/sourcepawn-client/include/json/definitions.inc new file mode 100644 index 0000000..eba7b9c --- /dev/null +++ b/ext/sourcepawn-client/include/json/definitions.inc @@ -0,0 +1,180 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_definitions_included + #endinput +#endif +#define _json_definitions_included + +#include +#include + +#define SM_INT64_SUPPORTED SOURCEMOD_V_MAJOR >= 1 \ + && SOURCEMOD_V_MINOR >= 11 \ + && SOURCEMOD_V_REV >= 6861 + +/** + * @section Settings + */ + +/** Used when no options are desired. */ +#define JSON_NONE 0 + +/** + * @section json_encode settings + */ + +/** Should encoded output be pretty printed? */ +#define JSON_ENCODE_PRETTY 1 << 0 + +/** + * @section json_decode settings + */ + +/** Should single quote wrapped strings be accepted during decoding? */ +#define JSON_DECODE_SINGLE_QUOTES 1 << 0 + +/** + * @section json_merge settings + */ + +/** During merge, should existing keys be replaced if they exist in both objects? */ +#define JSON_MERGE_REPLACE 1 << 0 + +/** During merge, should existing objects be cleaned up if they exist in + * both objects? (only applies when JSON_MERGE_REPLACE is also set) */ +#define JSON_MERGE_CLEANUP 1 << 1 + +/** + * @section Pretty Print Constants + * + * Used to determine how pretty printed JSON should be formatted when encoded. + * You can modify these if you prefer your JSON formatted differently. + */ + +char JSON_PP_AFTER_COLON[32] = " "; +char JSON_PP_INDENT[32] = " "; +char JSON_PP_NEWLINE[32] = "\n"; + +/** + * @section Buffer Size Constants + */ + +/** The longest representable integer ("-2147483648") + NULL terminator */ +#define JSON_INT_BUFFER_SIZE 12 + +#if SM_INT64_SUPPORTED +/** The longest representable int64 ("-9223372036854775808") + NULL terminator */ +#define JSON_INT64_BUFFER_SIZE 21 +#endif + +/** You may need to change this if you are working with large floats. */ +#define JSON_FLOAT_BUFFER_SIZE 32 + +/** "true"|"false" + NULL terminator */ +#define JSON_BOOL_BUFFER_SIZE 6 + +/** "null" + NULL terminator */ +#define JSON_NULL_BUFFER_SIZE 5 + +/** + * @section Array/Object Constants + */ + +#define JSON_ARRAY_KEY "is_array" +#define JSON_ENFORCE_TYPE_KEY "enforce_type" + +/** + * Types of cells within a JSON object + */ +enum JSONCellType { + JSON_Type_Invalid = -1, + JSON_Type_String = 0, + JSON_Type_Int, + #if SM_INT64_SUPPORTED + JSON_Type_Int64, + #endif + JSON_Type_Float, + JSON_Type_Bool, + JSON_Type_Object +}; + +/** + * Types of metadata a JSON element can have + */ +enum JSONMetaInfo { + JSON_Meta_Type = 0, + JSON_Meta_Size, + JSON_Meta_Hidden, + JSON_Meta_Index +} + +/** + * An array of all possible meta info values. + */ +JSONMetaInfo JSON_ALL_METADATA[4] = { + JSON_Meta_Type, JSON_Meta_Size, JSON_Meta_Hidden, JSON_Meta_Index +}; + +/** + * Calculates the length required to store a meta key + * for a specified key/metainfo combination. + * @internal + * + * @param key + * @return The length required to store the meta key. + */ +stock int json_meta_key_length(const char[] key) +{ + // %s:%d + return strlen(key) + 1 + JSON_INT_BUFFER_SIZE; +} + +/** + * Formats the key/metainfo combination into a buffer. + * @internal + * + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + * @param key Key to generate metakey for. + * @param meta Meta information to generate metakey for. + */ +stock void json_format_meta_key( + char[] output, + int max_size, + const char[] key, + JSONMetaInfo meta +) +{ + FormatEx(output, max_size, "k|%s:%d", key, view_as(meta)); +} diff --git a/ext/sourcepawn-client/include/json/helpers/decode.inc b/ext/sourcepawn-client/include/json/helpers/decode.inc new file mode 100644 index 0000000..4912b7f --- /dev/null +++ b/ext/sourcepawn-client/include/json/helpers/decode.inc @@ -0,0 +1,567 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_helpers_decode_included + #endinput +#endif +#define _json_helpers_decode_included + +#include +#include + +/** + * @section Determine Buffer Contents + */ + +/** + * Checks whether the character at the beginning of the buffer is whitespace. + * + * @param buffer String buffer of data. + * @return True if the first character in the buffer + * is whitespace, false otherwise. + */ +stock bool json_is_whitespace(const char[] buffer) +{ + return buffer[0] == ' ' + || buffer[0] == '\t' + || buffer[0] == '\r' + || buffer[0] == '\n'; +} + +/** + * Checks whether the character at the beginning + * of the buffer is the start of a string. + * + * @param buffer String buffer of data. + * @param allow_single_quotes Should strings using single quotes be accepted? + * @return True if the first character in the buffer + * is the start of a string, false otherwise. + */ +stock bool json_is_string(const char[] buffer, bool allow_single_quotes = false) +{ + return buffer[0] == '"' || (allow_single_quotes && buffer[0] == '\''); +} + +/** + * Checks whether the buffer provided contains an int. + * + * @param buffer String buffer of data. + * @return True if buffer contains an int, false otherwise. + */ +stock bool json_is_int(const char[] buffer) +{ + int length = strlen(buffer); + if (length == 0) { + return false; + } + + bool starts_with_zero = false; + bool has_digit_gt_zero = false; + + for (int i = 0; i < length; i += 1) { + // allow minus as first character only + if (i == 0 && buffer[i] == '-') { + continue; + } + + if (! IsCharNumeric(buffer[i])) { + return false; + } + + if (buffer[i] == '0') { + if (starts_with_zero) { + // detect repeating leading zeros + return false; + } else if (! has_digit_gt_zero) { + starts_with_zero = true; + } + } else if (starts_with_zero) { + // detect numbers with leading zero + return false; + } else { + has_digit_gt_zero = true; + } + } + + return true; +} + +#if SM_INT64_SUPPORTED +/** + * Checks whether the buffer provided contains an int64, assuming it has + * already been validated as an int and attempted to convert to an int32. + * + * @param buffer String buffer of data. + * @param value Converted int value to compare with. + * @return True if buffer contains an int64, false otherwise. + */ +stock bool json_is_int64(const char[] buffer, int value) +{ + if ( + (value == 0 && ! StrEqual(buffer, "0")) + || (value == -1 && ! StrEqual(buffer, "-1")) + ) { + // failed to produce output of validated int, must be 64bit + return true; + } + + if (buffer[0] != '-' && value < 0) { + // 32-bit unsigned positive int which is incorrectly + // interpreted as a negative signed int by sourcepawn + return true; + } + + if (buffer[0] == '-' && value > 0) { + return true; + } + + return false; +} +#endif + +/** + * Checks whether the buffer provided contains a float. + * + * @param buffer String buffer of data. + * @return True if buffer contains a float, false otherwise. + */ +stock bool json_is_float(const char[] buffer) +{ + int length = strlen(buffer); + if (length == 0) { + return false; + } + + bool starts_with_zero = false; + bool has_digit_gt_zero = false; + bool after_decimal = false; + bool has_digit_after_decimal = false; + bool after_exponent = false; + bool has_digit_after_exponent = false; + + for (int i = 0; i < length; i += 1) { + // allow minus as first character only + if (i == 0 && buffer[i] == '-') { + continue; + } + + // if we haven't encountered a decimal or exponent yet + if (! after_decimal && ! after_exponent) { + if (buffer[i] == '.') { + // if we encounter a decimal before any digits + if (! starts_with_zero && ! has_digit_gt_zero) { + return false; + } + + after_decimal = true; + } else if (buffer[i] == 'e' || buffer[i] == 'E') { + // if we encounter an exponent before any digits + if (! starts_with_zero && ! has_digit_gt_zero) { + return false; + } + + after_exponent = true; + } else if (IsCharNumeric(buffer[i])) { + if (buffer[i] == '0') { + if (starts_with_zero) { + // detect repeating leading zeros + return false; + } else if (! has_digit_gt_zero) { + starts_with_zero = true; + } + } else { + if (starts_with_zero) { + // detect numbers with leading zero + return false; + } + + has_digit_gt_zero = true; + } + } else { + return false; + } + } else if (after_decimal && ! after_exponent) { + // after decimal has been encountered, allow any numerics + if (IsCharNumeric(buffer[i])) { + has_digit_after_decimal = true; + } else if (buffer[i] == 'e' || buffer[i] == 'E') { + if (! has_digit_after_decimal) { + // detect exponents directly after decimal + return false; + } + + after_exponent = true; + } else { + return false; + } + } else if (after_exponent) { + if ( + (buffer[i] == '+' || buffer[i] == '-') + && (buffer[i - 1] == 'e' || buffer[i - 1] == 'E') + ) { + // allow + or - directly after exponent + continue; + } else if (IsCharNumeric(buffer[i])) { + has_digit_after_exponent = true; + } else { + return false; + } + } + } + + // if we have a decimal, there should be digit(s) after it + if (after_decimal && ! has_digit_after_decimal) { + return false; + } + + // if we have an exponent, there should be digit(s) after it + if (after_exponent && ! has_digit_after_exponent) { + return false; + } + + // we should have reached an exponent, decimal, or both + // otherwise this number can be handled by the int parser + return after_decimal || after_exponent; +} + +/** + * Checks whether the character at the beginning of the buffer + * is considered a valid 'end point' for an element, + * such as a colon (indicating the end of a key), + * a comma (indicating the end of an element), + * or the end of an object or array. + * + * @param buffer String buffer of data. + * @param is_array Whether the decoder is processing an array. + * @return True if the first character in the buffer + * is a valid element end point, false otherwise. + */ +stock bool json_is_at_end(const char[] buffer, bool is_array) +{ + return buffer[0] == ',' + || (! is_array && (buffer[0] == ':' || buffer[0] == '}')) + || (is_array && buffer[0] == ']'); +} + +/** + * @section Extract Contents from Buffer + */ + +/** + * Moves the position until it reaches a non-whitespace + * character or the end of the buffer's maximum size. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @return True if pos has not reached the end + * of the buffer, false otherwise. + */ +stock bool json_skip_whitespace(const char[] buffer, int max_size, int &pos) +{ + while (json_is_whitespace(buffer[pos]) && pos < max_size) { + pos += 1; + } + + return pos < max_size; +} + +/** + * Calculates the size of the buffer required to store the next + * JSON cell stored in the provided buffer at the provided position. + * This function is quite forgiving of malformed input and shouldn't be + * relied upon as proof that the input is valid. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @param is_array Whether the decoder is processing an array. + * @return The size of the buffer required to store the cell. + */ +stock int json_extract_until_end_size( + const char[] buffer, + int max_size, + int pos, + bool is_array +) +{ + int length = 1; // for NULL terminator + + // while we haven't hit whitespace, an end point or the end of the buffer + while ( + ! json_is_whitespace(buffer[pos]) + && ! json_is_at_end(buffer[pos], is_array) + && pos < max_size + ) { + pos += 1; + length += 1; + } + + return length; +} + +/** + * Extracts a JSON cell from the buffer until a valid end point is reached. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @param output String buffer to store output. + * @param output_max_size Maximum size of output string buffer. + * @param is_array Whether the decoder is processing an array. + * @return True if pos has not reached the end + * of the buffer, false otherwise. + */ +stock bool json_extract_until_end( + const char[] buffer, + int max_size, + int &pos, + char[] output, + int output_max_size, + bool is_array +) { + strcopy(output, output_max_size, ""); + + // set start to position of first character in cell + int start = pos; + + // while we haven't hit whitespace, an end point or the end of the buffer + while ( + ! json_is_whitespace(buffer[pos]) + && ! json_is_at_end(buffer[pos], is_array) + && pos < max_size + ) { + pos += 1; + } + + // set end to the current position + int end = pos; + + // skip any following whitespace + json_skip_whitespace(buffer, max_size, pos); + + // if we aren't at a valid endpoint, extraction has failed + if (! json_is_at_end(buffer[pos], is_array)) { + return false; + } + + // copy only from start with length end - start + NULL terminator + strcopy(output, end - start + 1, buffer[start]); + + return pos < max_size; +} + +/** + * Calculates the size of the buffer required to store the next + * JSON string stored in the provided buffer at the provided position. + * This function is quite forgiving of malformed input and shouldn't be + * relied upon as proof that the input is valid. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @return The size of the buffer required to store the string. + */ +stock int json_extract_string_size(const char[] buffer, int max_size, int pos) +{ + int length = 1; // for NULL terminator + + // store initial quote + char quote = buffer[pos]; + + // increment past opening quote + pos += 1; + + // while we haven't hit the end of the buffer + int continuous_backslashes = 0; + while (pos < max_size) { + if (buffer[pos] == quote) { + // if we have an even number of preceding backslashes, + // the quote isn't escaped so this is the end of the string + if (continuous_backslashes % 2 == 0) { + break; + } + } + + if (buffer[pos] == '\\') { + continuous_backslashes += 1; + } else { + continuous_backslashes = 0; + } + + // pass over the character as it is part of the string + pos += 1; + length += 1; + } + + return length; +} + +/** + * Extracts a JSON string from the buffer until a valid end point is reached. + * + * @param buffer String buffer of data. + * @param max_size Maximum size of string buffer. + * @param pos Position to increment. + * @param output String buffer to store output. + * @param output_max_size Maximum size of output string buffer. + * @param is_array Whether the decoder is processing an array. + * @return True if pos has not reached the end + * of the buffer, false otherwise. + */ +stock bool json_extract_string( + const char[] buffer, + int max_size, + int &pos, + char[] output, + int output_max_size, + bool is_array +) { + strcopy(output, output_max_size, ""); + + // store initial quote + char quote = buffer[pos]; + + // increment past opening quote + pos += 1; + + // set start to position of first character in string + int start = pos; + + // while we haven't hit the end of the buffer + int continuous_backslashes = 0; + while (pos < max_size) { + // check for unescaped control characters + if ( + buffer[pos] == '\b' + || buffer[pos] == '\f' + || buffer[pos] == '\n' + || buffer[pos] == '\r' + || buffer[pos] == '\t' + ) { + return false; + } + + if (buffer[pos] == quote) { + // if we have an even number of preceding backslashes, + // the quote isn't escaped so this is the end of the string + if (continuous_backslashes % 2 == 0) { + break; + } + } + + if (buffer[pos] == '\\') { + continuous_backslashes += 1; + } else { + if (continuous_backslashes % 2 != 0) { + if (buffer[pos] == 'u') { + if (pos + 4 >= max_size) { + // less than 4 characters left in the buffer + return false; + } + + // ensure next 4 chars are hex and not a high surrogate + for (int i = 0; i < 4; i += 1) { + pos += 1; + + if (! json_char_is_hex(buffer[pos])) { + return false; + } + + if ( + i == 1 + && buffer[pos - 1] == 'D' + && buffer[pos] >= '8' + ) { + // detected a high surrogate value + return false; + } + } + + // jump back to the last hex char so it is safe to continue + pos -= 1; + } else if ( + buffer[pos] != '"' + && buffer[pos] != '\'' + && buffer[pos] != '/' + && buffer[pos] != 'b' + && buffer[pos] != 'f' + && buffer[pos] != 'n' + && buffer[pos] != 'r' + && buffer[pos] != 't' + ) { + // illegal escape detected + return false; + } + } + + continuous_backslashes = 0; + } + + // pass over the character as it is part of the string + pos += 1; + } + + // set end to the current position + int end = pos; + + // increment past closing quote + pos += 1; + + // skip trailing whitespace + if (! json_skip_whitespace(buffer, max_size, pos)) { + return false; + } + + // if we haven't reached an ending character at the end of the cell, + // there is likely junk data not encapsulated by a string + if (! json_is_at_end(buffer[pos], is_array)) { + return false; + } + + // copy only from start with length end - start + NULL terminator + int length = end - start + 1; + strcopy( + output, + length > output_max_size ? output_max_size : length, + buffer[start] + ); + + if (quote == '\'') { + ReplaceString(output, max_size, "\\'", "'"); + } + + json_unescape_string(output, max_size); + + return pos < max_size; +} diff --git a/ext/sourcepawn-client/include/json/helpers/errors.inc b/ext/sourcepawn-client/include/json/helpers/errors.inc new file mode 100644 index 0000000..7c3241c --- /dev/null +++ b/ext/sourcepawn-client/include/json/helpers/errors.inc @@ -0,0 +1,64 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_helpers_errors_included + #endinput +#endif +#define _json_helpers_errors_included + +static char g_jsonLastError[1024] = ""; + +/** + * Stores the error provided as the 'last error' for later access. + * @internal + * + * @param error Error to store. + * @param ... Further arguments to pass to message formatter. + */ +stock void json_set_last_error(const char[] error, any ...) +{ + VFormat(g_jsonLastError, sizeof(g_jsonLastError), error, 2); +} + +/** + * Retrieves the last error encountered and stores it in the buffer provided. + * + * @param buffer String buffer. + * @param max_size Maximum size of string buffer. + * @return True if the error was copied successfuly, + * false otherwise. + */ +stock bool json_get_last_error(char[] buffer, int max_size) +{ + return strcopy(buffer, max_size, g_jsonLastError) > 0; +} diff --git a/ext/sourcepawn-client/include/json/helpers/metastringmap.inc b/ext/sourcepawn-client/include/json/helpers/metastringmap.inc new file mode 100644 index 0000000..b89134a --- /dev/null +++ b/ext/sourcepawn-client/include/json/helpers/metastringmap.inc @@ -0,0 +1,237 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _metastringmap_included + #endinput +#endif +#define _metastringmap_included + +#include +#include + +/** + * A TypedStringMap which contains a nested `Data` `TypedStringMap` property. + * Standard methods and properties have been overridden to run against `Data`, + * but you can access the parent methods/properties using the `Meta` property. + */ +methodmap MetaStringMap < TypedStringMap +{ + /** + * @section Properties + */ + + /** + * Views the instance as its superclass to access overridden methods. + */ + property TypedStringMap Meta + { + public get() + { + return view_as(this); + } + } + + /** + * Gets the nested stringmap where data is stored. + */ + property TypedStringMap Data + { + public get() + { + return view_as(this.Meta.GetHandle("data")); + } + + public set(TypedStringMap value) + { + this.Meta.SetHandle("data", value); + } + } + + /** @see TypedStringMap.Length */ + property int Length { + public get() + { + return this.Data.Length; + } + } + + /** + * @section Getters + */ + + /** @see StringMap.GetValue */ + public bool GetValue(const char[] key, any &value) + { + return this.Data.GetValue(key, value); + } + + /** + * @see TypedStringMap.GetOptionalValue + * @internal + */ + public any GetOptionalValue(const char[] key, any default_value = -1) + { + return this.Data.GetOptionalValue(key, default_value); + } + + /** @see StringMap.GetString */ + public bool GetString( + const char[] key, + char[] value, + int max_size, + int &size = 0 + ) { + return this.Data.GetString(key, value, max_size, size); + } + + /** @see TypedStringMap.GetInt */ + public int GetInt(const char[] key, int default_value = -1) + { + return this.Data.GetInt(key, default_value); + } + + /** @see TypedStringMap.GetFloat */ + public float GetFloat(const char[] key, float default_value = -1.0) + { + return this.Data.GetFloat(key, default_value); + } + + /** @see TypedStringMap.GetBool */ + public bool GetBool(const char[] key, bool default_value = false) + { + return this.Data.GetBool(key, default_value); + } + + /** @see TypedStringMap.GetHandle */ + public Handle GetHandle(const char[] key, Handle default_value = null) + { + return this.Data.GetHandle(key, default_value); + } + + /** + * @section Setters + */ + + /** @see StringMap.SetValue */ + public bool SetValue(const char[] key, any value) + { + return this.Data.SetValue(key, value); + } + + /** @see StringMap.SetString */ + public bool SetString(const char[] key, const char[] value) + { + return this.Data.SetString(key, value); + } + + /** @see TypedStringMap.SetInt */ + public bool SetInt(const char[] key, int value) + { + return this.Data.SetInt(key, value); + } + + /** @see TypedStringMap.SetFloat */ + public bool SetFloat(const char[] key, float value) + { + return this.Data.SetFloat(key, value); + } + + /** @see TypedStringMap.SetBool */ + public bool SetBool(const char[] key, bool value) + { + return this.Data.SetBool(key, value); + } + + /** @see TypedStringMap.SetHandle */ + public bool SetHandle(const char[] key, Handle value) + { + return this.Data.SetHandle(key, value); + } + + /** @see StringMap.Remove */ + public bool Remove(const char[] key) + { + return this.Data.Remove(key); + } + + /** + * @section Misc + */ + + /** @see TypedStringMap.HasKey */ + public bool HasKey(const char[] key) + { + return this.Data.HasKey(key); + } + + /** @see StringMap.Clear */ + public void Clear() + { + TypedStringMap data = this.Data; + data.Clear(); + this.Meta.Clear(); + this.Data = data; + } + + /** + * Deletes the instance's data StringMap as well as the instance itself. + */ + public void Cleanup() + { + delete this.Data; + delete this; + } + + /** @see StringMap.Snapshot */ + public StringMapSnapshot Snapshot() + { + return this.Data.Snapshot(); + } + + /** + * @section Constructor + */ + + /** + * Creates a new MetaStringMap. + * + * @return A new MetaStringMap. + */ + public MetaStringMap() + { + MetaStringMap self = view_as(new TypedStringMap()); + self.Data = new TypedStringMap(); + + return self; + } +}; diff --git a/ext/sourcepawn-client/include/json/helpers/string.inc b/ext/sourcepawn-client/include/json/helpers/string.inc new file mode 100644 index 0000000..e01400c --- /dev/null +++ b/ext/sourcepawn-client/include/json/helpers/string.inc @@ -0,0 +1,247 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_helpers_string_included + #endinput +#endif +#define _json_helpers_string_included + +#include + +/** + * Mapping characters to their escaped form. + */ +char JSON_STRING_NORMAL[][] = { + "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" +}; +char JSON_STRING_ESCAPED[][] = { + "\\\\", "\\\"", "\\/", "\\b", "\\f", "\\n", "\\r", "\\t" +}; + +/** + * Escapes a string in-place in a buffer. + * + * @param buffer String buffer. + * @param max_size Maximum size of string buffer. + */ +stock void json_escape_string(char[] buffer, int max_size) +{ + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); i += 1) { + ReplaceString( + buffer, + max_size, + JSON_STRING_NORMAL[i], + JSON_STRING_ESCAPED[i] + ); + } + + int length = strlen(buffer) + 1; + for (int pos = 0; pos < length && pos < max_size; pos += 1) { + if (buffer[pos] < 0x80) { + // skip standard ascii values + continue; + } + + // consume the ascii bytes of the next utf8 character + int ascii_size; + int utf8 = json_ascii_to_utf8(buffer[pos], length - pos, ascii_size); + if (ascii_size <= 0) { + continue; + } + + // convert the utf8 value to escaped format + char escaped[7]; + FormatEx(escaped, sizeof(escaped), "\\u%04x", utf8); + + // duplicate the consumed byte array + ascii_size += 1; + char[] ascii = new char[ascii_size]; + for (int i = 0; i < ascii_size; i += 1) { + ascii[i] = buffer[pos + i]; + } + ascii[ascii_size - 1] = '\0'; + + // replace bytes with the escaped value + int replacements = ReplaceString(buffer, max_size, ascii, escaped); + + // calculate new string length based on replacements made + length -= replacements * ascii_size - 1; + length += replacements * sizeof(escaped) - 1; + + // skip to the last of the bytes we just replaced + pos += sizeof(escaped) - 2; + } +} + +/** + * Unescapes a string in-place in a buffer. + * + * @param buffer String buffer. + * @param max_size Maximum size of string buffer. + */ +stock void json_unescape_string(char[] buffer, int max_size) +{ + int length = strlen(buffer) + 1; + int continuous_backslashes = 0; + for (int pos = 0; pos < length && pos < max_size; pos += 1) { + if (buffer[pos] == '\\') { + continuous_backslashes += 1; + } else { + if (continuous_backslashes % 2 != 0 && buffer[pos] == 'u') { + // consume the entire escape starting at backslash + pos -= 1; + char escaped[7]; + for (int i = 0; i < 6; i += 1) { + escaped[i] = buffer[pos + i]; + } + escaped[sizeof(escaped) - 1] = '\0'; + + // convert the hex to decimal + int utf8 = StringToInt(escaped[2], 16); + + // convert the utf8 to ascii + int ascii_size = json_utf8_to_ascii_size(utf8) + 1; + char[] ascii = new char[ascii_size]; + int written = json_utf8_to_ascii(utf8, ascii, ascii_size); + + // replace the escaped value with ascii bytes + int replacements = ReplaceString( + buffer, + max_size, + escaped, + ascii, + false + ); + + // calculate new string length based on replacements made + length -= replacements * sizeof(escaped) - 1; + length += replacements * written; + + // skip to the last of the bytes we just replaced + pos += written - 1; + } + + continuous_backslashes = 0; + } + } + + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); i += 1) { + ReplaceString( + buffer, + max_size, + JSON_STRING_ESCAPED[i], + JSON_STRING_NORMAL[i] + ); + } +} + +/** + * Checks whether the provided character is a valid hexadecimal character. + * + * @param c Character to check. + * @return True if c is a hexadecimal character, false otherwise. + */ +stock bool json_char_is_hex(int c) +{ + return ( + (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F') + ); +} + +/** + * Calculates the maximum buffer length required to + * store the JSON cell representation of a string. + * + * @param length The length of the string. + * @return Maximum buffer length. + */ +stock int json_cell_string_size(const char[] input) +{ + int size = 3; // for outside quotes + NULL terminator + + bool foundEscapeTarget = false; + int length = strlen(input); + for (int pos = 0; pos < length; pos += 1) { + foundEscapeTarget = false; + for (int i = 0; i < sizeof(JSON_STRING_NORMAL); i += 1) { + if (input[pos] == JSON_STRING_NORMAL[i][0]) { + size += 2; + foundEscapeTarget = true; + break; + } + } + + if (foundEscapeTarget) { + continue; + } + + if (input[pos] < 0x80) { + size += 1; + continue; + } + + // consume the ascii bytes of the next utf8 character + int ascii_size; + json_ascii_to_utf8(input[pos], length - pos, ascii_size); + if (ascii_size <= 0) { + continue; + } + + pos += ascii_size - 1; + size += 6; // for unicode escape is \uXXXX + } + + return size; +} + +/** + * Generates the JSON cell representation of a string. + * + * @param input Value to generate output for. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + */ +stock void json_cell_string(const char[] input, char[] output, int max_size) +{ + // add input string to output, offset for start/end quotes + strcopy(output[1], max_size - 2, input); + + // escape the output + json_escape_string(output[1], max_size - 2); + + // surround output with quotations + output[0] = '"'; + StrCat(output, max_size, "\""); +} diff --git a/ext/sourcepawn-client/include/json/helpers/typedstringmap.inc b/ext/sourcepawn-client/include/json/helpers/typedstringmap.inc new file mode 100644 index 0000000..07ef8c0 --- /dev/null +++ b/ext/sourcepawn-client/include/json/helpers/typedstringmap.inc @@ -0,0 +1,222 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _typedstringmap_included + #endinput +#endif +#define _typedstringmap_included + +#include + +/** @see StringMap.ContainsKey */ +#define TRIE_SUPPORTS_CONTAINSKEY SOURCEMOD_V_MAJOR >= 1 \ + && SOURCEMOD_V_MINOR >= 11 \ + && SOURCEMOD_V_REV >= 6646 + +/** + * A StringMap with typed getters and setters. + */ +methodmap TypedStringMap < StringMap +{ + /** + * @section Properies + */ + + /** @see StringMap.Size */ + property int Length { + public get() + { + return this.Size; + } + } + + /** + * @section Misc + */ + + /** @see StringMap.ContainsKey */ + public bool HasKey(const char[] key) + { + #if TRIE_SUPPORTS_CONTAINSKEY + return this.ContainsKey(key); + #else + int dummy_int; + char dummy_str[1]; + + return this.GetValue(key, dummy_int) + || this.GetString(key, dummy_str, sizeof(dummy_str)); + #endif + } + + /** + * @section Getters + */ + + // GetValue is implemented natively by StringMap + + /** + * Retrieves the value stored at a key. + * @internal + * + * @param key Key to retrieve value for. + * @param default_value Value to return if the key does not exist. + * @return Value stored at key. + */ + public any GetOptionalValue(const char[] key, any default_value = -1) + { + any value; + return this.GetValue(key, value) ? value : default_value; + } + + // GetString is implemented natively by StringMap + + /** + * Retrieves the int stored at a key. + * + * @param key Key to retrieve int value for. + * @param default_value Value to return if the key does not exist. + * @return Value stored at key. + */ + public int GetInt(const char[] key, int default_value = -1) + { + return view_as(this.GetOptionalValue(key, default_value)); + } + + /** + * Retrieves the float stored at a key. + * + * @param key Key to retrieve float value for. + * @param default_value Value to return if the key does not exist. + * @return Value stored at key. + */ + public float GetFloat(const char[] key, float default_value = -1.0) + { + return view_as(this.GetOptionalValue(key, default_value)); + } + + /** + * Retrieves the bool stored at a key. + * + * @param key Key to retrieve bool value for. + * @param default_value Value to return if the key does not exist. + * @return Value stored at key. + */ + public bool GetBool(const char[] key, bool default_value = false) + { + return view_as(this.GetOptionalValue(key, default_value)); + } + + /** + * Retrieves the handle stored at a key. + * + * @param key Key to retrieve handle value for. + * @param default_value Value to return if the key does not exist. + * @return Value stored at key. + */ + public Handle GetHandle( + const char[] key, + Handle default_value = null + ) { + return view_as(this.GetOptionalValue(key, default_value)); + } + + /** + * @section Setters + */ + + // SetValue is implemented natively by StringMap + + // SetString is implemented natively by StringMap + + /** + * Sets the int stored at a key. + * + * @param key Key to set to int value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetInt(const char[] key, int value) + { + return this.SetValue(key, value); + } + + /** + * Sets the float stored at a key. + * + * @param key Key to set to float value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetFloat(const char[] key, float value) + { + return this.SetValue(key, value); + } + + /** + * Sets the bool stored at a key. + * + * @param key Key to set to bool value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetBool(const char[] key, bool value) + { + return this.SetValue(key, value); + } + + /** + * Sets the handle stored at a key. + * + * @param key Key to set to object value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetHandle(const char[] key, Handle value) + { + return this.SetValue(key, value); + } + + /** + * @section Constructor + */ + + /** + * Creates a new TypedStringMap. + * + * @return A new TypedStringMap. + */ + public TypedStringMap() + { + return view_as(CreateTrie()); + } +}; diff --git a/ext/sourcepawn-client/include/json/helpers/unicode.inc b/ext/sourcepawn-client/include/json/helpers/unicode.inc new file mode 100644 index 0000000..1c4bb43 --- /dev/null +++ b/ext/sourcepawn-client/include/json/helpers/unicode.inc @@ -0,0 +1,185 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_helpers_unicode_included + #endinput +#endif +#define _json_helpers_unicode_included + +// most of the code here is adapted from https://dev.w3.org/XML/encoding.c + +/** + * Calculates how many bytes will be required to store the ASCII + * representation of a UTF-8 character. + * + * @param c The UTF-8 character. + * @return The number of bytes required, or -1 if c is invalid. + */ +stock int json_utf8_to_ascii_size(int c) +{ + if (c < 0 || c > 0x10FFFF) { + return -1; + } + + if (c < 0x80) { + return 1; + } else if (c < 0x800) { + return 2; + } else if (c < 0x10000) { + if (c >= 0xD800 && c <= 0xDFFF) { + // high surrogate + return -1; + } + + return 3; + } + + return 4; +} + +/** + * Converts a UTF-8 character to its ASCII representation. + * + * @param c The UTF-8 character. + * @param output String buffer to store output. + * @param max_size Maximum size of string buffer. + * @return The number of bytes written, or -1 if c is invalid. + */ +stock int json_utf8_to_ascii(int c, char[] output, int max_size) +{ + if (max_size < 1) { + return 0; + } + + if (c < 0 || c > 0x10FFFF) { + return -1; + } + + int size = 0; + if (c < 0x80) { + size = 1; + output[0] = c; + } else if (c < 0x800) { + size = 2; + output[0] = ((c >> 6) & 0x1F) | 0xC0; + } else if (c < 0x10000) { + if (c >= 0xD800 && c <= 0xDFFF) { + // high surrogate + return -1; + } + + size = 3; + output[0] = ((c >> 12) & 0x0F) | 0xE0; + } else { + size = 4; + output[0] = ((c >> 18) & 0x07) | 0xF0; + } + + if (size >= max_size) { + return -1; + } + + // first byte has already been calculated, calculate the rest + int i; + for (i = 1; i < size; i += 1) { + output[i] = ((c >> ((size - i - 1) * 6)) & 0x3F) | 0x80; + } + + return i; +} + +/** + * Converts bytes to their UTF-8 int representation. + * + * @param ascii The ascii/bytes to convert. + * @param max_size Maximum size of ascii. + * @return The UTF-8 int representation. + */ +stock int json_ascii_to_utf8(const char[] ascii, int max_size, int &size) +{ + size = 0; + if (max_size < 1) { + return -1; + } + + int c = 0; + if ((ascii[0] & 0x80) != 0) { + if (max_size < 2) { + return -1; + } + + if ((ascii[1] & 0xC0) != 0x80) { + return -1; + } + + if ((ascii[0] & 0xE0) == 0xE0) { + if (max_size < 3) { + return -1; + } + + if ((ascii[2] & 0xC0) != 0x80) { + return -1; + } + + if ((ascii[0] & 0xF0) == 0xF0) { + if (max_size < 4) { + return -1; + } + + if ((ascii[0] & 0xF8) != 0xF0 || (ascii[3] & 0xC0) != 0x80) { + return -1; + } + + size = 4; + c = (ascii[0] & 0x07) << 18; + } else { + size = 3; + c = (ascii[0] & 0x0F) << 12; + } + } else { + size = 2; + c = (ascii[0] & 0x1F) << 6; + } + } else { + size = 1; + c = ascii[0]; + } + + // first byte has already been calculated, calculate the rest + int i; + for (i = 1; i < size; i += 1) { + c |= (ascii[i] & 0x3F) << ((size - i - 1) * 6); + } + + return c; +} diff --git a/ext/sourcepawn-client/include/json/object.inc b/ext/sourcepawn-client/include/json/object.inc new file mode 100644 index 0000000..71a0603 --- /dev/null +++ b/ext/sourcepawn-client/include/json/object.inc @@ -0,0 +1,724 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * sm-json + * A pure SourcePawn JSON encoder/decoder. + * https://github.com/clugg/sm-json + * + * sm-json (C)2022 James Dickens. (clug) + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#if defined _json_object_included + #endinput +#endif +#define _json_object_included + +#include +#include +#include +#include + +methodmap JSON_Object < MetaStringMap +{ + /** + * @section Properties + */ + + /** + * Views the instance as its superclass to access overridden methods. + */ + property MetaStringMap Super + { + public get() + { + return view_as(this); + } + } + + /** + * Whether the current object is an array. + */ + property bool IsArray { + public get() + { + return this.Meta.GetBool(JSON_ARRAY_KEY); + } + } + + /** + * @section Iteration Helpers + */ + + /** + * @section Metadata Getters + */ + + /** + * Gets the requested meta info for a key. + * @internal + * + * @param key Key to get meta info for. + * @param meta Meta info to get. + * @param default_value Value to return if meta does not exist. + * @return The meta value. + */ + public any GetMeta( + const char[] key, + JSONMetaInfo meta, + any default_value + ) { + int max_size = json_meta_key_length(key); + char[] meta_key = new char[max_size]; + json_format_meta_key(meta_key, max_size, key, meta); + + return this.Meta.GetOptionalValue(meta_key, default_value); + } + + /** + * Gets the cell type stored at a key. + * + * @param key Key to get value type for. + * @return Value type for key provided, + * or JSON_Type_Invalid if it does not exist. + */ + public JSONCellType GetType(const char[] key) + { + return view_as( + this.GetMeta(key, JSON_Meta_Type, JSON_Type_Invalid) + ); + } + + /** + * Gets the size of the string stored at a key. + * + * @param key Key to get buffer size for. + * @return Buffer size for string at key provided, + * or -1 if it is not a string/does not exist. + */ + public int GetSize(const char[] key) + { + return view_as(this.GetMeta(key, JSON_Meta_Size, -1)); + } + + /** + * Gets whether the key should be hidden from encoding. + * + * @param key Key to get hidden state for. + * @return Whether or not the key should be hidden. + */ + public bool GetHidden(const char[] key) + { + return view_as(this.GetMeta(key, JSON_Meta_Hidden, false)); + } + + /** + * Gets the index of a key. + * + * @param key Key to get index of. + * @return Index of the key provided, or -1 if it does not exist. + */ + public int GetIndex(const char[] key) + { + return view_as(this.GetMeta(key, JSON_Meta_Index, -1)); + } + + /** + * Gets the key stored at an index. + * If an array, will convert the index to its string value. + * If an array, will return false if the index is not between [0, length]. + * + * @param index Index of key. + * @param value Buffer to store key at. + * @param max_size Maximum size of value buffer. + * @return True on success, false otherwise. + */ + public bool GetKey(int index, char[] value, int max_size) + { + char[] index_key = new char[JSON_INT_BUFFER_SIZE]; + if (IntToString(index, index_key, JSON_INT_BUFFER_SIZE) == 0) { + return false; + } + + if (this.IsArray) { + // allow access of one past last index for intermediary operations + if (index < 0 || index > this.Length) { + return false; + } + + strcopy(value, max_size, index_key); + + return true; + } + + return this.Meta.GetString(index_key, value, max_size); + } + + /** + * Returns the buffer size required to store the key at the specified index. + * + * @param index Index of key. + * @return Buffer size required to store key. + */ + public int GetKeySize(int index) + { + if (this.IsArray) { + return JSON_INT_BUFFER_SIZE; + } + + int max_size = JSON_INT_BUFFER_SIZE + 4; + char[] index_size_key = new char[max_size]; + FormatEx(index_size_key, max_size, "%d:len", index); + + return this.Meta.GetInt(index_size_key); + } + + /** + * @section Metadata Setters + */ + + /** + * Sets meta info on a key. + * @internal + * + * @param key Key to set meta info for. + * @param meta Meta info to set. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetMeta(const char[] key, JSONMetaInfo meta, any value) + { + int max_size = json_meta_key_length(key); + char[] meta_key = new char[max_size]; + json_format_meta_key(meta_key, max_size, key, meta); + + return this.Meta.SetValue(meta_key, value); + } + + /** + * Removes meta info from a key. + * @internal + * + * @param key Key to remove meta info from. + * @param meta Meta info to remove. + * @return True on success, false otherwise. + */ + public bool RemoveMeta(const char[] key, JSONMetaInfo meta) + { + int max_size = json_meta_key_length(key); + char[] meta_key = new char[max_size]; + json_format_meta_key(meta_key, max_size, key, meta); + + return this.Meta.Remove(meta_key); + } + + /** + * Sets whether the key should be hidden from encoding. + * + * @param key Key to set hidden state for. + * @param hidden Whether or not the key should be hidden. + * @return True on success, false otherwise. + */ + public bool SetHidden(const char[] key, bool hidden) + { + return hidden + ? this.SetMeta(key, JSON_Meta_Hidden, hidden) + : this.RemoveMeta(key, JSON_Meta_Hidden); + } + + /** + * Tracks a key, setting it's type and index where necessary. + * @internal + * + * @param key Key to track. If the key already + * exists, it's index will not be changed. + * @param type Type to set key to. If a valid type is + * provided, the key's type will be updated. + * @param index Index to set key to. If the index is not + * provided (-1), the object length will be used. + * @return True on success, false otherwise. + */ + public bool TrackKey( + const char[] key, + JSONCellType type = JSON_Type_Invalid, + int index = -1 + ) { + // track type if provided + if (type != JSON_Type_Invalid) { + this.SetMeta(key, JSON_Meta_Type, type); + } + + if (this.IsArray) { + return true; + } + + if (index == -1) { + index = this.Length; + + // skip tracking index if we're pushing to end & key already exists + if (this.HasKey(key)) { + if (type != JSON_Type_Invalid && type != JSON_Type_String) { + // remove any existing size + this.RemoveMeta(key, JSON_Meta_Size); + } + + return true; + } + } + + char[] index_key = new char[JSON_INT_BUFFER_SIZE]; + IntToString(index, index_key, JSON_INT_BUFFER_SIZE); + + int max_size = JSON_INT_BUFFER_SIZE + 8; + char[] index_size_key = new char[max_size]; + FormatEx(index_size_key, max_size, "%d:len", index); + + return this.Meta.SetString(index_key, key) + && this.Meta.SetInt(index_size_key, strlen(key) + 1) + && this.SetMeta(key, JSON_Meta_Index, index); + } + + /** + * Untracks a key, cleaning up all it's meta and indexing data. + * @internal + * + * @param key Key to untrack. + * @return True on success, false otherwise. + */ + public bool UntrackKey(const char[] key) + { + int index = this.GetIndex(key); + + for (int i = 0; i < sizeof(JSON_ALL_METADATA); i += 1) { + this.RemoveMeta(key, JSON_ALL_METADATA[i]); + } + + if (this.IsArray) { + return true; + } + + if (index == -1) { + return false; + } + + char[] index_key = new char[JSON_INT_BUFFER_SIZE]; + IntToString(index, index_key, JSON_INT_BUFFER_SIZE); + + int max_size = JSON_INT_BUFFER_SIZE + 8; + char[] index_size_key = new char[max_size]; + FormatEx(index_size_key, max_size, "%d:len", index); + + if (! this.Meta.Remove(index_key) || ! this.Meta.Remove(index_size_key)) { + return false; + } + + int length = this.Length; + int last_index = length - 1; + if (index < last_index) { + for (int i = index + 1; i < length; i += 1) { + int new_key_size = this.GetKeySize(i); + char[] new_key = new char[new_key_size]; + this.GetKey(i, new_key, new_key_size); + + this.TrackKey(new_key, JSON_Type_Invalid, i - 1); + } + + IntToString(last_index, index_key, JSON_INT_BUFFER_SIZE); + FormatEx(index_size_key, max_size, "%d:len", last_index); + + this.Meta.Remove(index_key); + this.Meta.Remove(index_size_key); + } + + return true; + } + + /** + * @section Getters + */ + + #if SM_INT64_SUPPORTED + /** + * Retrieves the int64 stored at a key. + * + * @param key Key to retrieve int64 value for. + * @param value Int buffer to store output. + * @return True on success, false otherwise. + */ + public bool GetInt64(const char[] key, int value[2]) + { + return this.Data.GetArray(key, value, 2); + } + #endif + + /** + * Retrieves the JSON object stored at a key. + * + * @param key Key to retrieve object value for. + * @param default_value Value to return if the key does not exist. + * @return Value stored at key. + */ + public JSON_Object GetObject( + const char[] key, + JSON_Object default_value = null + ) { + return view_as(this.GetHandle(key, default_value)); + } + + /** + * @section Setters + */ + + /** + * Sets the string stored at a key. + * + * @param key Key to set to string value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetString(const char[] key, const char[] value) + { + return this.TrackKey(key, JSON_Type_String) + && this.Super.SetString(key, value) + && this.SetMeta(key, JSON_Meta_Size, strlen(value) + 1); + } + + /** + * Sets the int stored at a key. + * + * @param key Key to set to int value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetInt(const char[] key, int value) + { + return this.TrackKey(key, JSON_Type_Int) + && this.Super.SetInt(key, value); + } + + #if SM_INT64_SUPPORTED + /** + * Sets the int64 stored at a key. + * + * @param key Key to set to int64 value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetInt64(const char[] key, int value[2]) + { + return this.TrackKey(key, JSON_Type_Int64) + && this.Data.SetArray(key, value, 2); + } + #endif + + /** + * Sets the float stored at a key. + * + * @param key Key to set to float value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetFloat(const char[] key, float value) + { + return this.TrackKey(key, JSON_Type_Float) + && this.Super.SetFloat(key, value); + } + + /** + * Sets the bool stored at a key. + * + * @param key Key to set to bool value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetBool(const char[] key, bool value) + { + return this.TrackKey(key, JSON_Type_Bool) + && this.Super.SetBool(key, value); + } + + /** + * Sets the JSON object stored at a key. + * + * @param key Key to set to object value. + * @param value Value to set. + * @return True on success, false otherwise. + */ + public bool SetObject(const char[] key, JSON_Object value) + { + return this.TrackKey(key, JSON_Type_Object) + && this.Super.SetHandle(key, value); + } + + /** + * @section Misc + */ + + /** + * Removes an item from the object by key. + * + * @param key Key of object to remove. + * @return True on success, false if the value was never set. + */ + public bool Remove(const char[] key) + { + return this.UntrackKey(key) && this.Super.Remove(key); + } + + /** + * Renames the key of an existing item in the object. + * + * @param from Existing key to rename. + * @param to New key. + * @param replace Should the 'to' key should be replaced if it exists? + * @return True on success, false otherwise. + */ + public bool Rename( + const char[] from, + const char[] to, + bool replace = true + ) { + JSONCellType type = this.GetType(from); + if (type == JSON_Type_Invalid) { + return false; + } + + if (StrEqual(from, to, true)) { + return true; + } + + bool toExists = this.HasKey(to); + if (toExists) { + if (! replace) { + return false; + } + + this.Remove(to); + } + + switch (type) { + case JSON_Type_String: { + int length = this.GetSize(from); + char[] value = new char[length]; + this.GetString(from, value, length); + this.SetString(to, value); + } + case JSON_Type_Int: { + this.SetInt(to, this.GetInt(from)); + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + int value[2]; + this.GetInt64(from, value); + this.SetInt64(to, value); + } + #endif + case JSON_Type_Float: { + this.SetFloat(to, this.GetFloat(from)); + } + case JSON_Type_Bool: { + this.SetBool(to, this.GetBool(from)); + } + case JSON_Type_Object: { + this.SetObject(to, this.GetObject(from)); + } + } + + this.SetHidden(to, this.GetHidden(from)); + + this.Remove(from); + + return true; + } + + /** + * Merges in the entries from the specified object, + * optionally replacing existing entries with the same key. + * + * @param from Object to merge entries from. + * @param options Bitwise combination of `JSON_MERGE_*` options. + * @return True on success, false otherwise. + * @error If the object being merged is an array, + * an error will be thrown. + */ + public bool Merge(JSON_Object from, int options = JSON_MERGE_REPLACE) + { + if (this.IsArray || from.IsArray) { + json_set_last_error("attempted to merge using an array"); + + return false; + } + + bool replace = (options & JSON_MERGE_REPLACE) > 0; + bool autocleanup = (options & JSON_MERGE_CLEANUP) > 0; + + int json_size = from.Length; + int key_length = 0; + for (int i = 0; i < json_size; i += 1) { + key_length = from.GetKeySize(i); + char[] key = new char[key_length]; + from.GetKey(i, key, key_length); + + // skip already existing keys if we aren't in replace mode + bool key_already_exists = this.HasKey(key); + if (! replace && key_already_exists) { + continue; + } + + JSONCellType type = from.GetType(key); + // skip keys of unknown type + if (type == JSON_Type_Invalid) { + continue; + } + + // merge value onto structure + switch (type) { + case JSON_Type_String: { + int length = from.GetSize(key); + char[] value = new char[length]; + from.GetString(key, value, length); + + this.SetString(key, value); + } + case JSON_Type_Int: { + this.SetInt(key, from.GetInt(key)); + } + #if SM_INT64_SUPPORTED + case JSON_Type_Int64: { + int value[2]; + from.GetInt64(key, value); + this.SetInt64(key, value); + } + #endif + case JSON_Type_Float: { + this.SetFloat(key, from.GetFloat(key)); + } + case JSON_Type_Bool: { + this.SetBool(key, from.GetBool(key)); + } + case JSON_Type_Object: { + JSON_Object value = from.GetObject(key); + + if (autocleanup && key_already_exists) { + JSON_Object existing = this.GetObject(key); + if (existing != value) { + json_cleanup_and_delete(existing); + } + } + + this.SetObject(key, value); + } + } + + this.SetHidden(key, from.GetHidden(key)); + } + + return true; + } + + /** + * @section json.inc Aliases + */ + + /** + * Makes a global call with this + * instance passed as the object. + * + * @see json_encode_size + */ + public int EncodeSize(int options = JSON_NONE) + { + return json_encode_size(this, options); + } + + /** + * Makes a global call with this + * instance passed as the object. + * + * @see json_encode + */ + public void Encode(char[] output, int max_size, int options = JSON_NONE) + { + json_encode(this, output, max_size, options); + } + + /** + * Makes a global call with this + * instance passed as the object. + * + * @see json_write_to_file + */ + public bool WriteToFile(const char[] path, int options = JSON_NONE) + { + return json_write_to_file(this, path, options); + } + + /** + * Makes a global call with this + * instance passed as the object. + * + * @see json_copy_deep + */ + public JSON_Object ShallowCopy() + { + return json_copy_shallow(this); + } + + /** + * Makes a global call with this + * instance passed as the object. + * + * @see json_copy_deep + */ + public JSON_Object DeepCopy() + { + return json_copy_deep(this); + } + + /** + * Makes a global call with this + * instance passed as the object. + * + * @see json_cleanup + */ + public void Cleanup() + { + json_cleanup(this); + } + + /** + * @section Constructor + */ + + /** + * Creates a new JSON_Object. + * + * @return A new JSON_Object. + */ + public JSON_Object() + { + return view_as(new MetaStringMap()); + } +}; diff --git a/ext/sourcepawn-client/include/ripext.inc b/ext/sourcepawn-client/include/ripext.inc new file mode 100644 index 0000000..2e0d258 --- /dev/null +++ b/ext/sourcepawn-client/include/ripext.inc @@ -0,0 +1,26 @@ +#if defined _ripext_included_ + #endinput +#endif +#define _ripext_included_ + +#include +#include + +/** + * Do not edit below this line! + */ +public Extension __ext_rip = +{ + name = "REST in Pawn", + file = "rip.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; \ No newline at end of file diff --git a/ext/sourcepawn-client/include/ripext/http.inc b/ext/sourcepawn-client/include/ripext/http.inc new file mode 100644 index 0000000..51b031f --- /dev/null +++ b/ext/sourcepawn-client/include/ripext/http.inc @@ -0,0 +1,359 @@ +enum HTTPStatus +{ + HTTPStatus_Invalid = 0, + + // 1xx Informational + HTTPStatus_Continue = 100, + HTTPStatus_SwitchingProtocols = 101, + + // 2xx Success + HTTPStatus_OK = 200, + HTTPStatus_Created = 201, + HTTPStatus_Accepted = 202, + HTTPStatus_NonAuthoritativeInformation = 203, + HTTPStatus_NoContent = 204, + HTTPStatus_ResetContent = 205, + HTTPStatus_PartialContent = 206, + + // 3xx Redirection + HTTPStatus_MultipleChoices = 300, + HTTPStatus_MovedPermanently = 301, + HTTPStatus_Found = 302, + HTTPStatus_SeeOther = 303, + HTTPStatus_NotModified = 304, + HTTPStatus_UseProxy = 305, + HTTPStatus_TemporaryRedirect = 307, + HTTPStatus_PermanentRedirect = 308, + + // 4xx Client Error + HTTPStatus_BadRequest = 400, + HTTPStatus_Unauthorized = 401, + HTTPStatus_PaymentRequired = 402, + HTTPStatus_Forbidden = 403, + HTTPStatus_NotFound = 404, + HTTPStatus_MethodNotAllowed = 405, + HTTPStatus_NotAcceptable = 406, + HTTPStatus_ProxyAuthenticationRequired = 407, + HTTPStatus_RequestTimeout = 408, + HTTPStatus_Conflict = 409, + HTTPStatus_Gone = 410, + HTTPStatus_LengthRequired = 411, + HTTPStatus_PreconditionFailed = 412, + HTTPStatus_RequestEntityTooLarge = 413, + HTTPStatus_RequestURITooLong = 414, + HTTPStatus_UnsupportedMediaType = 415, + HTTPStatus_RequestedRangeNotSatisfiable = 416, + HTTPStatus_ExpectationFailed = 417, + HTTPStatus_MisdirectedRequest = 421, + HTTPStatus_TooEarly = 425, + HTTPStatus_UpgradeRequired = 426, + HTTPStatus_PreconditionRequired = 428, + HTTPStatus_TooManyRequests = 429, + HTTPStatus_RequestHeaderFieldsTooLarge = 431, + HTTPStatus_UnavailableForLegalReasons = 451, + + // 5xx Server Error + HTTPStatus_InternalServerError = 500, + HTTPStatus_NotImplemented = 501, + HTTPStatus_BadGateway = 502, + HTTPStatus_ServiceUnavailable = 503, + HTTPStatus_GatewayTimeout = 504, + HTTPStatus_HTTPVersionNotSupported = 505, + HTTPStatus_VariantAlsoNegotiates = 506, + HTTPStatus_NotExtended = 510, + HTTPStatus_NetworkAuthenticationRequired = 511, +}; + +typeset HTTPRequestCallback +{ + function void (HTTPResponse response, any value); + function void (HTTPResponse response, any value, const char[] error); +}; + +typeset HTTPFileCallback +{ + function void (HTTPStatus status, any value); + function void (HTTPStatus status, any value, const char[] error); +}; + +methodmap HTTPRequest < Handle +{ + // Creates an HTTP request. + // + // The Handle is automatically freed when the request is performed. + // Otherwise, the Handle must be freed via delete or CloseHandle(). + // + // @param url URL to the REST API endpoint. + public native HTTPRequest(const char[] url); + + // Appends a parameter to the form data. + // + // The parameter name and value are encoded according to RFC 3986. + // + // @param name Parameter name. + // @param format Formatting rules. + // @param ... Variable number of format parameters. + public native void AppendFormParam(const char[] name, const char[] format, any ...); + + // Appends a query parameter to the URL. + // + // The parameter name and value are encoded according to RFC 3986. + // + // @param name Parameter name. + // @param format Formatting rules. + // @param ... Variable number of format parameters. + public native void AppendQueryParam(const char[] name, const char[] format, any ...); + + // Sets the credentials for HTTP Basic authentication. + // + // @param username Username to use. + // @param password Password to use. + public native void SetBasicAuth(const char[] username, const char[] password); + + // Sets an HTTP header. + // + // @param name Header name. + // @param format Formatting rules. + // @param ... Variable number of format parameters. + public native void SetHeader(const char[] name, const char[] format, any ...); + + // Performs an HTTP GET request. + // + // This function closes the request Handle after completing. + // + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + public native void Get(HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP POST request. + // + // This function closes the request Handle after completing. + // + // @param data JSON data to send. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + public native void Post(JSON data, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP PUT request. + // + // This function closes the request Handle after completing. + // + // @param data JSON data to send. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + public native void Put(JSON data, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP PATCH request. + // + // This function closes the request Handle after completing. + // + // @param data JSON data to send. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + public native void Patch(JSON data, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP DELETE request. + // + // This function closes the request Handle after completing. + // + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + public native void Delete(HTTPRequestCallback callback, any value = 0); + + // Downloads a file. + // + // This function closes the request Handle after completing. + // + // @param path File path to write to. + // @param callback A function to use as a callback when the download has finished. + // @param value Optional value to pass to the callback function. + public native void DownloadFile(const char[] path, HTTPFileCallback callback, any value = 0); + + // Uploads a file. + // + // This function performs an HTTP PUT request. The file contents are sent in the request body. + // This function closes the request Handle after completing. + // + // @param path File path to read from. + // @param callback A function to use as a callback when the upload has finished. + // @param value Optional value to pass to the callback function. + public native void UploadFile(const char[] path, HTTPFileCallback callback, any value = 0); + + // Performs an HTTP POST request with form data. + // + // This function closes the request Handle after completing. + // + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + public native void PostForm(HTTPRequestCallback callback, any value = 0); + + // Connect timeout in seconds. Defaults to 10. + property int ConnectTimeout { + public native get(); + public native set(int connectTimeout); + } + + // Maximum number of redirects to follow. Defaults to 5. + property int MaxRedirects { + public native get(); + public native set(int maxRedirects); + } + + // Maximum download speed in bytes per second. Defaults to unlimited speed. + property int MaxRecvSpeed { + public native get(); + public native set(int maxSpeed); + } + + // Maximum upload speed in bytes per second. Defaults to unlimited speed. + property int MaxSendSpeed { + public native get(); + public native set(int maxSpeed); + } + + // Timeout in seconds. Defaults to 30. + property int Timeout { + public native get(); + public native set(int timeout); + } +} + +methodmap HTTPResponse +{ + // Retrieves an HTTP header from the response. + // + // @param name Header name. + // @param buffer String buffer to store value. + // @param maxlength Maximum length of the string buffer. + // @return True on success, false if the header was not found. + public native bool GetHeader(const char[] name, char[] buffer, int maxlength); + + // Retrieves the JSON data of the response. + // + // @error Invalid JSON response. + property JSON Data { + public native get(); + } + + // Retrieves the HTTP status of the response. + property HTTPStatus Status { + public native get(); + } +}; + +// Deprecated. Use HTTPRequest instead. +methodmap HTTPClient < Handle +{ + // Creates an HTTP client. + // + // The HTTPClient must be freed via delete or CloseHandle(). + // + // @param baseURL Base URL to the REST API. + #pragma deprecated Use HTTPRequest instead. + public native HTTPClient(const char[] baseURL); + + // Sets an HTTP header to be used for all requests. + // + // @param name Header name. + // @param value String value to set. + #pragma deprecated Use HTTPRequest.SetHeader() instead. + public native void SetHeader(const char[] name, const char[] value); + + // Performs an HTTP GET request. + // + // @param endpoint API endpoint to request. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.Get() instead. + public native void Get(const char[] endpoint, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP POST request. + // + // @param endpoint API endpoint to request. + // @param data JSON data to send. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.Post() instead. + public native void Post(const char[] endpoint, JSON data, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP PUT request. + // + // @param endpoint API endpoint to request. + // @param data JSON data to send. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.Put() instead. + public native void Put(const char[] endpoint, JSON data, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP PATCH request. + // + // @param endpoint API endpoint to request. + // @param data JSON data to send. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.Patch() instead. + public native void Patch(const char[] endpoint, JSON data, HTTPRequestCallback callback, any value = 0); + + // Performs an HTTP DELETE request. + // + // @param endpoint API endpoint to request. + // @param callback A function to use as a callback when the request has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.Delete() instead. + public native void Delete(const char[] endpoint, HTTPRequestCallback callback, any value = 0); + + // Downloads a file. + // + // @param endpoint API endpoint to download from. + // @param path File path to write to. + // @param callback A function to use as a callback when the download has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.DownloadFile() instead. + public native void DownloadFile(const char[] endpoint, const char[] path, HTTPFileCallback callback, any value = 0); + + // Uploads a file. + // + // This function performs an HTTP PUT request. The file contents are sent in the request body. + // + // @param endpoint API endpoint to upload to. + // @param path File path to read from. + // @param callback A function to use as a callback when the upload has finished. + // @param value Optional value to pass to the callback function. + #pragma deprecated Use HTTPRequest.UploadFile() instead. + public native void UploadFile(const char[] endpoint, const char[] path, HTTPFileCallback callback, any value = 0); + + // Connect timeout in seconds. Defaults to 10. + #pragma deprecated Use HTTPRequest.ConnectTimeout instead. + property int ConnectTimeout { + public native get(); + public native set(int connectTimeout); + } + + // Follow HTTP 3xx redirects. Defaults to true. + #pragma deprecated Use HTTPRequest.MaxRedirects instead. + property bool FollowLocation { + public native get(); + public native set(bool followLocation); + } + + // Timeout in seconds. Defaults to 30. + #pragma deprecated Use HTTPRequest.Timeout instead. + property int Timeout { + public native get(); + public native set(int timeout); + } + + // Maximum upload speed in bytes per second. Defaults to unlimited speed. + #pragma deprecated Use HTTPRequest.MaxSendSpeed instead. + property int MaxSendSpeed { + public native get(); + public native set(int speed); + } + + // Maximum download speed in bytes per second. Defaults to unlimited speed. + #pragma deprecated Use HTTPRequest.MaxRecvSpeed instead. + property int MaxRecvSpeed { + public native get(); + public native set(int speed); + } +}; \ No newline at end of file diff --git a/ext/sourcepawn-client/include/ripext/json.inc b/ext/sourcepawn-client/include/ripext/json.inc new file mode 100644 index 0000000..a064af8 --- /dev/null +++ b/ext/sourcepawn-client/include/ripext/json.inc @@ -0,0 +1,400 @@ +// Decoding flags +enum +{ + JSON_REJECT_DUPLICATES = 0x1, /**< Error if any JSON object contains duplicate keys */ + JSON_DISABLE_EOF_CHECK = 0x2, /**< Allow extra data after a valid JSON array or object */ + JSON_DECODE_ANY = 0x4, /**< Decode any value */ + JSON_DECODE_INT_AS_REAL = 0x8, /**< Interpret all numbers as floats */ + JSON_ALLOW_NUL = 0x10 /**< Allow \u0000 escape inside string values */ +}; + +// Encoding flags +enum +{ + JSON_COMPACT = 0x20, /**< Compact representation */ + JSON_ENSURE_ASCII = 0x40, /**< Escape all Unicode characters outside the ASCII range */ + JSON_SORT_KEYS = 0x80, /**< Sort object keys */ + JSON_ENCODE_ANY = 0x200, /**< Encode any value */ + JSON_ESCAPE_SLASH = 0x400, /**< Escape / with \/ */ + JSON_EMBED = 0x10000 /**< Omit opening and closing braces of the top-level object */ +}; + +// Maximum indentation +static const int JSON_MAX_INDENT = 0x1F; + +// Pretty-print the result, indenting with n spaces +stock int JSON_INDENT(int n) +{ + return n & JSON_MAX_INDENT; +} + +// Output floats with at most n digits of precision +stock int JSON_REAL_PRECISION(int n) +{ + return (n & 0x1F) << 11; +} + +// Generic type for encoding JSON. +methodmap JSON < Handle +{ + // Writes the JSON string representation to a file. + // + // @param file File to write to. + // @param flags Encoding flags. + // @return True on success, false on failure. + public native bool ToFile(const char[] file, int flags = 0); + + // Retrieves the JSON string representation. + // + // @param buffer String buffer to write to. + // @param maxlength Maximum length of the string buffer. + // @param flags Encoding flags. + // @return True on success, false on failure. + public native bool ToString(char[] buffer, int maxlength, int flags = 0); +}; + +methodmap JSONObject < JSON +{ + // Creates a JSON object. A JSON object maps strings (called "keys") to values. Keys in a + // JSON object are unique. That is, there is at most one entry in the map for a given key. + // + // The JSONObject must be freed via delete or CloseHandle(). + public native JSONObject(); + + // Loads a JSON object from a file. + // + // @param file File to read from. + // @param flags Decoding flags. + // @return Object handle, or null on failure. + // @error Invalid JSON. + public static native JSONObject FromFile(const char[] file, int flags = 0); + + // Loads a JSON object from a string. + // + // @param buffer String buffer to load into the JSON object. + // @param flags Decoding flags. + // @return Object handle, or null on failure. + // @error Invalid JSON. + public static native JSONObject FromString(const char[] buffer, int flags = 0); + + // Retrieves an array or object value from the object. + // + // The JSON must be freed via delete or CloseHandle(). + // + // @param key Key string. + // @return Value read. + // @error Invalid key. + public native JSON Get(const char[] key); + + // Retrieves a boolean value from the object. + // + // @param key Key string. + // @return Value read. + // @error Invalid key. + public native bool GetBool(const char[] key); + + // Retrieves a float value from the object. + // + // @param key Key string. + // @return Value read. + // @error Invalid key. + public native float GetFloat(const char[] key); + + // Retrieves an integer value from the object. + // + // @param key Key string. + // @return Value read. + // @error Invalid key. + public native int GetInt(const char[] key); + + // Retrieves a 64-bit integer value from the object. + // + // @param key Key string. + // @param buffer String buffer to store value. + // @param maxlength Maximum length of the string buffer. + // @return True on success, false if the key was not found. + public native bool GetInt64(const char[] key, char[] buffer, int maxlength); + + // Retrieves a string value from the object. + // + // @param key Key string. + // @param buffer String buffer to store value. + // @param maxlength Maximum length of the string buffer. + // @return True on success. False if the key was not found, or the value is not a string. + public native bool GetString(const char[] key, char[] buffer, int maxlength); + + // Returns whether or not a value in the object is null. + // + // @param key Key string. + // @return True if the value is null, false otherwise. + // @error Invalid key. + public native bool IsNull(const char[] key); + + // Returns whether or not a key exists in the object. + // + // @param key Key string. + // @return True if the key exists, false otherwise. + public native bool HasKey(const char[] key); + + // Sets an array or object value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @param value Value to store at this key. + // @return True on success, false on failure. + public native bool Set(const char[] key, JSON value); + + // Sets a boolean value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @param value Value to store at this key. + // @return True on success, false on failure. + public native bool SetBool(const char[] key, bool value); + + // Sets a float value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @param value Value to store at this key. + // @return True on success, false on failure. + public native bool SetFloat(const char[] key, float value); + + // Sets an integer value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @param value Value to store at this key. + // @return True on success, false on failure. + public native bool SetInt(const char[] key, int value); + + // Sets a 64-bit integer value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @param value Value to store at this key. + // @return True on success, false on failure. + public native bool SetInt64(const char[] key, const char[] value); + + // Sets a null value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @return True on success, false on failure. + public native bool SetNull(const char[] key); + + // Sets a string value in the object, either inserting a new entry or replacing an old one. + // + // @param key Key string. + // @param value Value to store at this key. + // @return True on success, false on failure. + public native bool SetString(const char[] key, const char[] value); + + // Removes an entry from the object. + // + // @param key Key string. + // @return True on success, false if the key was not found. + public native bool Remove(const char[] key); + + // Clears the object of all entries. + // @return True on success, false on failure. + public native bool Clear(); + + // Returns an iterator for the object's keys. See JSONObjectKeys. + public native JSONObjectKeys Keys(); + + // Retrieves the size of the object. + property int Size { + public native get(); + } +}; + +/** + * A JSONObjectKeys is created via JSONObject.Keys(). It allows the keys of an + * object to be iterated. The JSONObjectKeys must be freed with delete or + * CloseHandle(). + */ +methodmap JSONObjectKeys < Handle +{ + // Reads an object key, then advances to the next key if any. + // + // @param buffer String buffer to store key. + // @param maxlength Maximum length of the string buffer. + // @return True on success, false if there are no more keys. + public native bool ReadKey(char[] buffer, int maxlength); +}; + +methodmap JSONArray < JSON +{ + // Creates a JSON array. + // + // The JSONArray must be freed via delete or CloseHandle(). + public native JSONArray(); + + // Loads a JSON array from a file. + // + // @param file File to read from. + // @param flags Decoding flags. + // @return Array handle, or null on failure. + // @error Invalid JSON. + public static native JSONArray FromFile(const char[] file, int flags = 0); + + // Loads a JSON array from a string. + // + // @param buffer String buffer to load into the JSON array. + // @param flags Decoding flags. + // @return Array handle, or null on failure. + // @error Invalid JSON. + public static native JSONArray FromString(const char[] buffer, int flags = 0); + + // Retrieves an array or object value from the array. + // + // The JSON must be freed via delete or CloseHandle(). + // + // @param index Index in the array. + // @return Value read. + // @error Invalid index. + public native JSON Get(int index); + + // Retrieves a boolean value from the array. + // + // @param index Index in the array. + // @return Value read. + // @error Invalid index. + public native bool GetBool(int index); + + // Retrieves a float value from the array. + // + // @param index Index in the array. + // @return Value read. + // @error Invalid index. + public native float GetFloat(int index); + + // Retrieves an integer value from the array. + // + // @param index Index in the array. + // @return Value read. + // @error Invalid index. + public native int GetInt(int index); + + // Retrieves an 64-bit integer value from the array. + // + // @param index Index in the array. + // @param buffer Buffer to copy to. + // @param maxlength Maximum size of the buffer. + // @error Invalid index. + public native void GetInt64(int index, char[] buffer, int maxlength); + + // Retrieves a string value from the array. + // + // @param index Index in the array. + // @param buffer Buffer to copy to. + // @param maxlength Maximum size of the buffer. + // @return True on success, false if the value is not a string. + // @error Invalid index. + public native bool GetString(int index, char[] buffer, int maxlength); + + // Returns whether or not a value in the array is null. + // + // @param index Index in the array. + // @return True if the value is null, false otherwise. + // @error Invalid index. + public native bool IsNull(int index); + + // Sets an array or object value in the array. + // + // @param index Index in the array. + // @param value Value to set. + // @return True on success, false on failure. + public native bool Set(int index, JSON value); + + // Sets a boolean value in the array. + // + // @param index Index in the array. + // @param value Value to set. + // @return True on success, false on failure. + public native bool SetBool(int index, bool value); + + // Sets a float value in the array. + // + // @param index Index in the array. + // @param value Value to set. + // @return True on success, false on failure. + public native bool SetFloat(int index, float value); + + // Sets an integer value in the array. + // + // @param index Index in the array. + // @param value Value to set. + // @return True on success, false on failure. + public native bool SetInt(int index, int value); + + // Sets a 64 bit integer value in the array. + // + // @param index Index in the array. + // @param value 64-bit integer value to set. + // @return True on success, false on failure. + public native bool SetInt64(int index, const char[] value); + + // Sets a null value in the array. + // + // @param index Index in the array. + // @return True on success, false on failure. + public native bool SetNull(int index); + + // Sets a string value in the array. + // + // @param index Index in the array. + // @param value String value to set. + // @return True on success, false on failure. + public native bool SetString(int index, const char[] value); + + // Pushes an array or object value onto the end of the array, adding a new index. + // + // @param value Value to push. + // @return True on success, false on failure. + public native bool Push(JSON value); + + // Pushes a boolean value onto the end of the array, adding a new index. + // + // @param value Value to push. + // @return True on success, false on failure. + public native bool PushBool(bool value); + + // Pushes a float value onto the end of the array, adding a new index. + // + // @param value Value to push. + // @return True on success, false on failure. + public native bool PushFloat(float value); + + // Pushes an integer value onto the end of the array, adding a new index. + // + // @param value Value to push. + // @return True on success, false on failure. + public native bool PushInt(int value); + + // Pushes a 64-bit integer value onto the end of the array, adding a new index. + // + // @param value 64-bit integer value to push. + // @return True on success, false on failure. + public native bool PushInt64(const char[] value); + + // Pushes a null value onto the end of the array, adding a new index. + // @return True on success, false on failure. + public native bool PushNull(); + + // Pushes a string value onto the end of the array, adding a new index. + // + // @param value String value to push. + // @return True on success, false on failure. + public native bool PushString(const char[] value); + + // Removes an entry from the array. + // + // @param index Index in the array to remove. + // @return True on success, false on invalid index. + public native bool Remove(int index); + + // Clears the array of all entries. + // @return True on success, false on failure. + public native bool Clear(); + + // Retrieves the size of the array. + property int Length { + public native get(); + } +}; \ No newline at end of file