2019-09-16 19:28:39 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Copyright (C) 2017 Gian 'myT' Schellenbaum
|
|
|
|
|
|
|
|
This file is part of Challenge Quake 3 (CNQ3).
|
|
|
|
|
|
|
|
Challenge Quake 3 is free software; you can redistribute it
|
|
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
Challenge Quake 3 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 Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
// writing JSON to files without dynamic memory allocation etc for crash reports
|
|
|
|
|
2017-03-14 04:11:12 +00:00
|
|
|
#include "crash.h"
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(_MSC_VER)
|
|
|
|
typedef unsigned __int8 u8;
|
|
|
|
typedef unsigned __int32 u32;
|
|
|
|
#else
|
|
|
|
typedef uint8_t u8;
|
|
|
|
typedef uint32_t u32;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
static qbool UTF8_NextCodePoint(u32* codePoint, u32* byteCount, const char* input)
|
|
|
|
{
|
|
|
|
if (!codePoint || !byteCount || !input || *input == '\0')
|
|
|
|
return qfalse;
|
|
|
|
|
|
|
|
u8 byte0 = (u8)input[0];
|
|
|
|
if (byte0 <= 127) {
|
|
|
|
*codePoint = (u32)byte0;
|
|
|
|
*byteCount = 1;
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Starts with 110?
|
|
|
|
if ((byte0 >> 5) == 6) {
|
|
|
|
const u8 byte1 = (u8)input[1];
|
|
|
|
*codePoint = ((u32)byte1 & 63) | (((u32)byte0 & 31) << 6);
|
|
|
|
*byteCount = 2;
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Starts with 1110?
|
|
|
|
if ((byte0 >> 4) == 14) {
|
|
|
|
const u8 byte1 = (u8)input[1];
|
|
|
|
const u8 byte2 = (u8)input[2];
|
|
|
|
*codePoint = ((u32)byte2 & 63) | (((u32)byte1 & 63) << 6) | (((u32)byte0 & 15) << 12);
|
|
|
|
*byteCount = 3;
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Starts with 11110?
|
|
|
|
if ((byte0 >> 3) == 30) {
|
|
|
|
const u8 byte1 = (u8)input[1];
|
|
|
|
const u8 byte2 = (u8)input[2];
|
|
|
|
const u8 byte3 = (u8)input[3];
|
|
|
|
*codePoint = ((u32)byte3 & 63) | (((u32)byte2 & 63) << 6) | (((u32)byte1 & 63) << 12) | (((u32)byte0 & 7) << 18);
|
|
|
|
*byteCount = 4;
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
u32 CodePoint;
|
|
|
|
char OutputChar;
|
|
|
|
} ShortEscape;
|
|
|
|
|
|
|
|
#define ENTRY(HexValue, Char) { 0x##HexValue, Char }
|
|
|
|
static const ShortEscape ShortEscapeCodePoints[] = {
|
|
|
|
ENTRY(0022, '"'),
|
|
|
|
ENTRY(005C, '\\'),
|
|
|
|
ENTRY(002F, '/'),
|
|
|
|
ENTRY(0008, 'b'),
|
|
|
|
ENTRY(000C, 'f'),
|
|
|
|
ENTRY(000A, 'n'),
|
|
|
|
ENTRY(000D, 'r'),
|
|
|
|
ENTRY(0009, 't')
|
|
|
|
};
|
|
|
|
#undef ENTRY
|
|
|
|
|
|
|
|
static const char HexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
|
|
|
|
|
|
|
static qbool UTF8_NeedsEscaping(u32* newLength, u32 codePoint)
|
|
|
|
{
|
|
|
|
if (!newLength)
|
|
|
|
return qfalse;
|
|
|
|
|
|
|
|
const u32 count = (u32)ARRAY_LEN(ShortEscapeCodePoints);
|
|
|
|
for (u32 i = 0; i < count; i++) {
|
|
|
|
if (codePoint == ShortEscapeCodePoints[i].CodePoint) {
|
|
|
|
*newLength = 2; // Of form: "\A".
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Range: 0x0000 - 0x001F
|
|
|
|
if (codePoint <= 0x001F) {
|
|
|
|
*newLength = 6; // Of form: "\uABCD".
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void UTF8_WriteCodePoint(u32* newLength, char* output, u32 codePoint, const char* input)
|
|
|
|
{
|
|
|
|
if (!newLength || !output || !input)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const u32 count = (u32)ARRAY_LEN(ShortEscapeCodePoints);
|
|
|
|
for (u32 i = 0; i < count; i++) {
|
|
|
|
if (codePoint == ShortEscapeCodePoints[i].CodePoint) {
|
|
|
|
*newLength = 2;
|
|
|
|
output[0] = '\\';
|
|
|
|
output[1] = ShortEscapeCodePoints[i].OutputChar;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Range: 0x0000 - 0x001F
|
|
|
|
if (codePoint <= 0x001F) {
|
|
|
|
*newLength = 6;
|
|
|
|
output[0] = '\\';
|
|
|
|
output[1] = 'u';
|
|
|
|
output[2] = HexDigits[(codePoint >> 12) & 15];
|
|
|
|
output[3] = HexDigits[(codePoint >> 8) & 15];
|
|
|
|
output[4] = HexDigits[(codePoint >> 4) & 15];
|
|
|
|
output[5] = HexDigits[codePoint & 15];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (u32 i = 0; i < *newLength; ++i) {
|
|
|
|
output[i] = input[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 UTF8_LengthForJSON(const char* input)
|
|
|
|
{
|
|
|
|
if (!input)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
const char* s = input;
|
|
|
|
u32 codePoint = 0;
|
|
|
|
u32 byteCount = 0;
|
|
|
|
u32 newLength = 0;
|
|
|
|
while (UTF8_NextCodePoint(&codePoint, &byteCount, s)) {
|
|
|
|
s += byteCount;
|
|
|
|
UTF8_NeedsEscaping(&byteCount, codePoint);
|
|
|
|
newLength += byteCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
return newLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void UTF8_CleanForJSON(char* output, const char* input)
|
|
|
|
{
|
|
|
|
if (!output || !input)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const char* in = input;
|
|
|
|
char* out = output;
|
|
|
|
u32 codePoint = 0;
|
|
|
|
u32 inputByteCount = 0;
|
|
|
|
while (UTF8_NextCodePoint(&codePoint, &inputByteCount, in)) {
|
|
|
|
u32 outputByteCount = inputByteCount;
|
|
|
|
UTF8_WriteCodePoint(&outputByteCount, out, codePoint, in);
|
|
|
|
in += inputByteCount;
|
|
|
|
out += outputByteCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
*out = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
unsigned int itemIndices[16];
|
|
|
|
FILE* file;
|
|
|
|
unsigned int level;
|
|
|
|
} JSONWriter;
|
|
|
|
|
|
|
|
static JSONWriter writer;
|
|
|
|
|
|
|
|
static void JSONW_Write(const char* string)
|
|
|
|
{
|
|
|
|
if (!string)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fwrite(string, strlen(string), 1, writer.file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void JSONW_WriteNewLine()
|
|
|
|
{
|
|
|
|
JSONW_Write("\n");
|
|
|
|
for (u32 i = 0; i < writer.level; i++) {
|
|
|
|
JSONW_Write("\t");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void JSONW_WriteClean(const char* string)
|
|
|
|
{
|
|
|
|
static char buffer[4096];
|
|
|
|
|
|
|
|
if (!string)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const u32 length = UTF8_LengthForJSON(string);
|
|
|
|
if (length >= sizeof(buffer)) {
|
|
|
|
JSONW_Write("bufferSizeTooSmall");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
UTF8_CleanForJSON(buffer, string);
|
|
|
|
JSONW_Write(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_BeginFile(FILE* file)
|
|
|
|
{
|
|
|
|
memset(&writer, 0, sizeof(writer));
|
|
|
|
writer.file = file;
|
|
|
|
|
|
|
|
JSONW_Write("{");
|
|
|
|
++writer.level;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_EndFile()
|
|
|
|
{
|
|
|
|
JSONW_Write("\r\n}");
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_BeginObject()
|
|
|
|
{
|
|
|
|
if (writer.itemIndices[writer.level] > 0)
|
|
|
|
JSONW_Write(",");
|
|
|
|
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("{");
|
|
|
|
++writer.level;
|
|
|
|
writer.itemIndices[writer.level] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_BeginNamedObject(const char* name)
|
|
|
|
{
|
|
|
|
if (!name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (writer.itemIndices[writer.level] > 0)
|
|
|
|
JSONW_Write(",");
|
|
|
|
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("\"");
|
|
|
|
JSONW_WriteClean(name);
|
|
|
|
JSONW_Write("\":");
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("{");
|
|
|
|
++writer.level;
|
|
|
|
writer.itemIndices[writer.level] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_EndObject()
|
|
|
|
{
|
|
|
|
--writer.level;
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("}");
|
|
|
|
++writer.itemIndices[writer.level];
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_BeginArray()
|
|
|
|
{
|
|
|
|
if (writer.itemIndices[writer.level] > 0)
|
|
|
|
JSONW_Write(",");
|
|
|
|
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("[");
|
|
|
|
++writer.level;
|
|
|
|
writer.itemIndices[writer.level] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_BeginNamedArray(const char* name)
|
|
|
|
{
|
|
|
|
if (!name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (writer.itemIndices[writer.level] > 0)
|
|
|
|
JSONW_Write(",");
|
|
|
|
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("\"");
|
|
|
|
JSONW_WriteClean(name);
|
|
|
|
JSONW_Write("\":");
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("[");
|
|
|
|
++writer.level;
|
|
|
|
writer.itemIndices[writer.level] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_EndArray()
|
|
|
|
{
|
|
|
|
--writer.level;
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("]");
|
|
|
|
++writer.itemIndices[writer.level];
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_IntegerValue(const char* name, int number)
|
|
|
|
{
|
|
|
|
if (!name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
JSONW_StringValue(name, "%d", number);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_HexValue(const char* name, uint64_t number)
|
|
|
|
{
|
|
|
|
if (!name)
|
|
|
|
return;
|
|
|
|
|
|
|
|
JSONW_StringValue(name, Q_itohex(number, qtrue, qtrue));
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_BooleanValue(const char* name, qbool value)
|
|
|
|
{
|
|
|
|
JSONW_StringValue(name, value ? "true" : "false");
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_StringValue(const char* name, const char* format, ...)
|
|
|
|
{
|
|
|
|
static char buffer[4096];
|
|
|
|
|
|
|
|
if (!name || !format || *format == '\0')
|
|
|
|
return;
|
|
|
|
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
Q_vsnprintf(buffer, sizeof(buffer), format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if (writer.itemIndices[writer.level] > 0)
|
|
|
|
JSONW_Write(", ");
|
|
|
|
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("\"");
|
|
|
|
JSONW_Write(name);
|
|
|
|
JSONW_Write("\": \"");
|
|
|
|
JSONW_WriteClean(buffer);
|
|
|
|
JSONW_Write("\"");
|
|
|
|
++writer.itemIndices[writer.level];
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_UnnamedHex(uint64_t number)
|
|
|
|
{
|
|
|
|
JSONW_UnnamedString(Q_itohex(number, qtrue, qtrue));
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSONW_UnnamedString(const char* format, ...)
|
|
|
|
{
|
|
|
|
static char buffer[4096];
|
|
|
|
|
|
|
|
if (!format || *format == '\0')
|
|
|
|
return;
|
|
|
|
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
|
|
Q_vsnprintf(buffer, sizeof(buffer), format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if (writer.itemIndices[writer.level] > 0)
|
|
|
|
JSONW_Write(", ");
|
|
|
|
|
|
|
|
JSONW_WriteNewLine();
|
|
|
|
JSONW_Write("\"");
|
|
|
|
JSONW_WriteClean(buffer);
|
|
|
|
JSONW_Write("\"");
|
|
|
|
++writer.itemIndices[writer.level];
|
|
|
|
}
|