mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-06 11:53:21 +00:00
Add json include
This commit is contained in:
parent
d23503099b
commit
1932d6f02b
11 changed files with 4532 additions and 0 deletions
813
scripting/include/json.inc
Normal file
813
scripting/include/json.inc
Normal file
|
@ -0,0 +1,813 @@
|
||||||
|
/**
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _json_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _json_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <json/definitions>
|
||||||
|
#include <json/helpers/decode>
|
||||||
|
#include <json/helpers/errors>
|
||||||
|
#include <json/helpers/string>
|
||||||
|
#include <json/object>
|
||||||
|
#include <json/array>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
is_array
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
is_array
|
||||||
|
);
|
||||||
|
} 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<JSON_Object>(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<JSON_Object>(new JSON_Array())
|
||||||
|
: new JSON_Object();
|
||||||
|
|
||||||
|
if (isArray) {
|
||||||
|
view_as<JSON_Array>(result).Concat(view_as<JSON_Array>(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;
|
||||||
|
}
|
955
scripting/include/json/array.inc
Normal file
955
scripting/include/json/array.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _json_array_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _json_array_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <json/definitions>
|
||||||
|
#include <json/helpers/errors>
|
||||||
|
#include <json/object>
|
||||||
|
|
||||||
|
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<JSON_Object>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The enforced type of the array.
|
||||||
|
*/
|
||||||
|
property JSONCellType Type
|
||||||
|
{
|
||||||
|
public get()
|
||||||
|
{
|
||||||
|
return view_as<JSONCellType>(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<JSONCellType>(
|
||||||
|
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<int>(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<bool>(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<JSON_Array>(this.Super.ShallowCopy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @see JSON_Object.DeepCopy */
|
||||||
|
public JSON_Array DeepCopy()
|
||||||
|
{
|
||||||
|
return view_as<JSON_Array>(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<JSON_Array>(new JSON_Object());
|
||||||
|
self.Meta.SetBool(JSON_ARRAY_KEY, true);
|
||||||
|
self.EnforceType(type);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
};
|
180
scripting/include/json/definitions.inc
Normal file
180
scripting/include/json/definitions.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _json_definitions_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _json_definitions_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <json/helpers/string>
|
||||||
|
|
||||||
|
#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<int>(meta));
|
||||||
|
}
|
573
scripting/include/json/helpers/decode.inc
Normal file
573
scripting/include/json/helpers/decode.inc
Normal file
|
@ -0,0 +1,573 @@
|
||||||
|
/**
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _json_helpers_decode_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _json_helpers_decode_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <json/helpers/string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
* @param is_array Whether the decoder is processing an array.
|
||||||
|
* @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,
|
||||||
|
bool is_array
|
||||||
|
)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
64
scripting/include/json/helpers/errors.inc
Normal file
64
scripting/include/json/helpers/errors.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
237
scripting/include/json/helpers/metastringmap.inc
Normal file
237
scripting/include/json/helpers/metastringmap.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _metastringmap_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _metastringmap_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <json/helpers/typedstringmap>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<TypedStringMap>(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the nested stringmap where data is stored.
|
||||||
|
*/
|
||||||
|
property TypedStringMap Data
|
||||||
|
{
|
||||||
|
public get()
|
||||||
|
{
|
||||||
|
return view_as<TypedStringMap>(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<MetaStringMap>(new TypedStringMap());
|
||||||
|
self.Data = new TypedStringMap();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
};
|
247
scripting/include/json/helpers/string.inc
Normal file
247
scripting/include/json/helpers/string.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _json_helpers_string_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _json_helpers_string_included
|
||||||
|
|
||||||
|
#include <json/helpers/unicode>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, "\"");
|
||||||
|
}
|
222
scripting/include/json/helpers/typedstringmap.inc
Normal file
222
scripting/include/json/helpers/typedstringmap.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _typedstringmap_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _typedstringmap_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/** @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<int>(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<float>(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<bool>(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<Handle>(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<TypedStringMap>(CreateTrie());
|
||||||
|
}
|
||||||
|
};
|
185
scripting/include/json/helpers/unicode.inc
Normal file
185
scripting/include/json/helpers/unicode.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
724
scripting/include/json/object.inc
Normal file
724
scripting/include/json/object.inc
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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 <http://www.sourcemod.net/license.php>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined _json_object_included
|
||||||
|
#endinput
|
||||||
|
#endif
|
||||||
|
#define _json_object_included
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <json/definitions>
|
||||||
|
#include <json/helpers/errors>
|
||||||
|
#include <json/helpers/metastringmap>
|
||||||
|
|
||||||
|
methodmap JSON_Object < MetaStringMap
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @section Properties
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Views the instance as its superclass to access overridden methods.
|
||||||
|
*/
|
||||||
|
property MetaStringMap Super
|
||||||
|
{
|
||||||
|
public get()
|
||||||
|
{
|
||||||
|
return view_as<MetaStringMap>(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<JSONCellType>(
|
||||||
|
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<int>(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<bool>(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<int>(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<JSON_Object>(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<JSON_Object>(new MetaStringMap());
|
||||||
|
}
|
||||||
|
};
|
332
scripting/l4d2_randomizer.sp
Normal file
332
scripting/l4d2_randomizer.sp
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
#pragma semicolon 1
|
||||||
|
#pragma newdecls required
|
||||||
|
|
||||||
|
//#define DEBUG
|
||||||
|
|
||||||
|
#define PLUGIN_VERSION "1.0"
|
||||||
|
#define DEBUG_SCENE_PARSE 1
|
||||||
|
|
||||||
|
#include <sourcemod>
|
||||||
|
#include <sdktools>
|
||||||
|
//#include <sdkhooks>
|
||||||
|
#include <profiler>
|
||||||
|
#include <json>
|
||||||
|
#include <jutils>
|
||||||
|
#define ENT_PROP_NAME "l4d2_randomizer"
|
||||||
|
#include <gamemodes/ents>
|
||||||
|
|
||||||
|
public Plugin myinfo =
|
||||||
|
{
|
||||||
|
name = "L4D2 Randomizer",
|
||||||
|
author = "jackzmc",
|
||||||
|
description = "",
|
||||||
|
version = PLUGIN_VERSION,
|
||||||
|
url = "https://github.com/Jackzmc/sourcemod-plugins"
|
||||||
|
};
|
||||||
|
|
||||||
|
ConVar cvarEnabled;
|
||||||
|
|
||||||
|
public void OnPluginStart() {
|
||||||
|
EngineVersion g_Game = GetEngineVersion();
|
||||||
|
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
|
||||||
|
SetFailState("This plugin is for L4D/L4D2 only.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS);
|
||||||
|
RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC);
|
||||||
|
|
||||||
|
cvarEnabled = CreateConVar("sm_randomizer_enabled", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnMapEnd() {
|
||||||
|
DeleteCustomEnts();
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetLookingEntity(int client, TraceEntityFilter filter) {
|
||||||
|
static float pos[3], ang[3];
|
||||||
|
GetClientEyePosition(client, pos);
|
||||||
|
GetClientEyeAngles(client, ang);
|
||||||
|
TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client);
|
||||||
|
if(TR_DidHit()) {
|
||||||
|
return TR_GetEntityIndex();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Command_CycleRandom(int client, int args) {
|
||||||
|
DeleteCustomEnts();
|
||||||
|
char map[64];
|
||||||
|
GetCurrentMap(map, sizeof(map));
|
||||||
|
LoadMap(map);
|
||||||
|
ReplyToCommand(client, "Done.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action Command_ExportEnt(int client, int args) {
|
||||||
|
int entity = GetLookingEntity(client, Filter_IgnorePlayer);
|
||||||
|
if(entity > 0) {
|
||||||
|
float origin[3];
|
||||||
|
float angles[3];
|
||||||
|
float size[3];
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_angRotation", angles);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size);
|
||||||
|
|
||||||
|
char model[64];
|
||||||
|
GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||||
|
|
||||||
|
ReplyToCommand(client, "{");
|
||||||
|
ReplyToCommand(client, "\t\"model\": \"%s\",", model);
|
||||||
|
ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]);
|
||||||
|
ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f],", angles[0], angles[1], angles[2]);
|
||||||
|
ReplyToCommand(client, "\t\"size\": [%.2f, %.2f, %.2f]", size[0], size[1], size[2]);
|
||||||
|
ReplyToCommand(client, "}");
|
||||||
|
} else {
|
||||||
|
PrintCenterText(client, "No entity found");
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnMapStart() {
|
||||||
|
if(cvarEnabled.BoolValue) {
|
||||||
|
char map[64];
|
||||||
|
GetCurrentMap(map, sizeof(map));
|
||||||
|
LoadMap(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAX_SCENE_NAME_LENGTH 32
|
||||||
|
enum struct SceneData {
|
||||||
|
char name[MAX_SCENE_NAME_LENGTH];
|
||||||
|
float chance;
|
||||||
|
ArrayList exclusions;
|
||||||
|
ArrayList variants;
|
||||||
|
|
||||||
|
void Cleanup() {
|
||||||
|
delete this.exclusions;
|
||||||
|
SceneVariantData choice;
|
||||||
|
for(int i = 0; i < this.variants.Length; i++) {
|
||||||
|
this.variants.GetArray(i, choice);
|
||||||
|
choice.Cleanup();
|
||||||
|
}
|
||||||
|
delete this.variants;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum struct SceneVariantData {
|
||||||
|
int weight;
|
||||||
|
ArrayList entities;
|
||||||
|
|
||||||
|
void Cleanup() {
|
||||||
|
delete this.entities;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum struct VariantEntityData {
|
||||||
|
char type[16];
|
||||||
|
char model[64];
|
||||||
|
float origin[3];
|
||||||
|
float angles[3];
|
||||||
|
float scale[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList scenes;
|
||||||
|
|
||||||
|
// Parses (mapname).json and runs chances
|
||||||
|
public bool LoadMap(const char[] map) {
|
||||||
|
|
||||||
|
char filePath[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map);
|
||||||
|
if(!FileExists(filePath)) {
|
||||||
|
Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[65536];
|
||||||
|
File file = OpenFile(filePath, "r");
|
||||||
|
if(file == null) {
|
||||||
|
LogError("[Randomizer] Could not open map config file (data/randomizer/%s.json)", map);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.ReadString(buffer, sizeof(buffer));
|
||||||
|
JSON_Object data = json_decode(buffer);
|
||||||
|
if(data == null) {
|
||||||
|
json_get_last_error(buffer, sizeof(buffer));
|
||||||
|
LogError("[Randomizer] Could not parse map config file (data/randomizer/%s.json): %s", map, buffer);
|
||||||
|
delete file;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Cleanup();
|
||||||
|
scenes = new ArrayList(sizeof(SceneData));
|
||||||
|
|
||||||
|
Profiler profiler = new Profiler();
|
||||||
|
profiler.Start();
|
||||||
|
|
||||||
|
int length = data.Length;
|
||||||
|
char key[32];
|
||||||
|
for (int i = 0; i < length; i += 1) {
|
||||||
|
data.GetKey(i, key, sizeof(key));
|
||||||
|
if(data.GetType(key) != JSON_Type_Object) continue;
|
||||||
|
|
||||||
|
JSON_Object scene = data.GetObject(key);
|
||||||
|
// Parses scene data and inserts to scenes
|
||||||
|
loadGroup(key, scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
profiler.Stop();
|
||||||
|
Log("Loaded %d scenes in %.1f seconds", scenes.Length, profiler.Time);
|
||||||
|
profiler.Start();
|
||||||
|
|
||||||
|
processGroups();
|
||||||
|
|
||||||
|
profiler.Stop();
|
||||||
|
Log("Done processing in %.1f seconds", scenes.Length, profiler.Time);
|
||||||
|
|
||||||
|
json_cleanup_and_delete(data);
|
||||||
|
|
||||||
|
delete profiler;
|
||||||
|
delete file;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadGroup(const char key[MAX_SCENE_NAME_LENGTH], JSON_Object sceneData) {
|
||||||
|
SceneData scene;
|
||||||
|
scene.name = key;
|
||||||
|
scene.chance = sceneData.GetFloat("chance");
|
||||||
|
if(scene.chance < 0.0 || scene.chance > 1.0) {
|
||||||
|
LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scene.exclusions = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH));
|
||||||
|
JSON_Array exclusions = view_as<JSON_Array>(sceneData.GetObject("exclusions"));
|
||||||
|
if(exclusions != null) {
|
||||||
|
char id[MAX_SCENE_NAME_LENGTH];
|
||||||
|
for(int i = 0; i < exclusions.Length; i ++) {
|
||||||
|
exclusions.GetString(i, id, sizeof(id));
|
||||||
|
scene.exclusions.PushString(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scene.variants = new ArrayList(sizeof(SceneVariantData));
|
||||||
|
JSON_Array variants = view_as<JSON_Array>(sceneData.GetObject("variants"));
|
||||||
|
for(int i = 0; i < variants.Length; i++) {
|
||||||
|
// Parses choice and loads to scene.choices
|
||||||
|
loadChoice(scene, variants.GetObject(i));
|
||||||
|
}
|
||||||
|
scenes.PushArray(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadChoice(SceneData scene, JSON_Object choiceData) {
|
||||||
|
SceneVariantData choice;
|
||||||
|
choice.weight = choiceData.GetInt("weight", 1);
|
||||||
|
choice.entities = new ArrayList(sizeof(VariantEntityData));
|
||||||
|
JSON_Array entities = view_as<JSON_Array>(choiceData.GetObject("entities"));
|
||||||
|
for(int i = 0; i < entities.Length; i++) {
|
||||||
|
// Parses entities and loads to choice.entities
|
||||||
|
loadChoiceEntity(choice, entities.GetObject(i));
|
||||||
|
}
|
||||||
|
scene.variants.PushArray(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadChoiceEntity(SceneVariantData choice, JSON_Object entityData) {
|
||||||
|
VariantEntityData entity;
|
||||||
|
entityData.GetString("model", entity.model, sizeof(entity.model));
|
||||||
|
if(!entityData.GetString("type", entity.type, sizeof(entity.type))) {
|
||||||
|
entity.type = "prop_dynamic";
|
||||||
|
}
|
||||||
|
GetVector(entityData, "origin", entity.origin);
|
||||||
|
GetVector(entityData, "angles", entity.angles);
|
||||||
|
GetVector(entityData, "scale", entity.scale);
|
||||||
|
choice.entities.PushArray(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetVector(JSON_Object obj, const char[] key, float out[3]) {
|
||||||
|
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
|
||||||
|
if(vecArray != null) {
|
||||||
|
out[0] = vecArray.GetFloat(0);
|
||||||
|
out[1] = vecArray.GetFloat(1);
|
||||||
|
out[2] = vecArray.GetFloat(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processGroups() {
|
||||||
|
SceneData scene;
|
||||||
|
for(int i = 0; i < scenes.Length; i++) {
|
||||||
|
scenes.GetArray(i, scene);
|
||||||
|
// TODO: Exclusions
|
||||||
|
if(GetURandomFloat() < scene.chance) {
|
||||||
|
selectScene(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectScene(SceneData scene) {
|
||||||
|
// TODO: Weight
|
||||||
|
if(scene.variants.Length == 0) {
|
||||||
|
LogError("Warn: No variants were found for scene \"%s\"", scene.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Debug("Selected scene: \"%s\"", scene.name);
|
||||||
|
|
||||||
|
ArrayList choices = new ArrayList();
|
||||||
|
SceneVariantData choice;
|
||||||
|
// Weighted random: Push N times dependent on weight
|
||||||
|
for(int i = 0; i < scene.variants.Length; i++) {
|
||||||
|
scene.variants.GetArray(i, choice);
|
||||||
|
for(int c = 0; c < choice.weight; c++) {
|
||||||
|
choices.Push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
int index = GetURandomInt() % choices.Length;
|
||||||
|
index = choices.Get(index);
|
||||||
|
delete choices;
|
||||||
|
Debug("Selected variant: #%d", index);
|
||||||
|
scene.variants.GetArray(index, choice);
|
||||||
|
spawnVariant(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawnVariant(SceneVariantData choice) {
|
||||||
|
VariantEntityData entity;
|
||||||
|
// Weighted random: Push N times dependent on weight
|
||||||
|
for(int i = 0; i < choice.entities.Length; i++) {
|
||||||
|
choice.entities.GetArray(i, entity);
|
||||||
|
spawnEntity(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawnEntity(VariantEntityData entity) {
|
||||||
|
Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]);
|
||||||
|
PrecacheModel(entity.model);
|
||||||
|
CreateProp(entity.type, entity.model, entity.origin, entity.angles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Debug(const char[] format, any ...) {
|
||||||
|
#if defined DEBUG_SCENE_PARSE
|
||||||
|
char buffer[192];
|
||||||
|
|
||||||
|
VFormat(buffer, sizeof(buffer), format, 2);
|
||||||
|
|
||||||
|
PrintToServer("[Randomizer::Debug] %s", buffer);
|
||||||
|
PrintToConsoleAll("[Randomizer::Debug] %s", buffer);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log(const char[] format, any ...) {
|
||||||
|
char buffer[192];
|
||||||
|
|
||||||
|
VFormat(buffer, sizeof(buffer), format, 2);
|
||||||
|
|
||||||
|
PrintToServer("[Randomizer] %s", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cleanup() {
|
||||||
|
if(scenes != null) {
|
||||||
|
SceneData scene;
|
||||||
|
for(int i = 0; i < scenes.Length; i++) {
|
||||||
|
scenes.GetArray(i, scene);
|
||||||
|
scene.Cleanup();
|
||||||
|
}
|
||||||
|
delete scenes;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue