500 lines
11 KiB
C++
500 lines
11 KiB
C++
// Copyright © 2008-2020 Pioneer Developers. See AUTHORS.txt for details
|
|
// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
|
|
|
|
#ifndef _USE_MATH_DEFINES
|
|
#define _USE_MATH_DEFINES
|
|
#endif
|
|
|
|
#include "JsonUtils.h"
|
|
#include "FileSystem.h"
|
|
#include "GZipFormat.h"
|
|
#include "base64/base64.hpp"
|
|
#include "utils.h"
|
|
#include <cmath>
|
|
|
|
extern "C" {
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wattributes"
|
|
#endif
|
|
#include "miniz/miniz.h"
|
|
#ifdef __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
}
|
|
|
|
namespace {
|
|
uint32_t pack4char(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d)
|
|
{
|
|
return ((a << 24) | (b << 16) | (c << 8) | d);
|
|
}
|
|
|
|
void unpack4char(const uint32_t packed, uint8_t &a, uint8_t &b, uint8_t &c, uint8_t &d)
|
|
{
|
|
a = ((packed >> 24) & 0xff);
|
|
b = ((packed >> 16) & 0xff);
|
|
c = ((packed >> 8) & 0xff);
|
|
d = (packed & 0xff);
|
|
}
|
|
|
|
static const vector3f zeroVector3f(0.0f);
|
|
static const vector3d zeroVector3d(0.0);
|
|
static const Quaternionf identityQuaternionf(1.0f, 0.0f, 0.0f, 0.0f);
|
|
static const Quaterniond identityQuaterniond(1.0, 0.0, 0.0, 0.0);
|
|
} // namespace
|
|
|
|
namespace JsonUtils {
|
|
Json LoadJson(RefCountedPtr<FileSystem::FileData> fd)
|
|
{
|
|
if (!fd) return Json();
|
|
|
|
Json out;
|
|
|
|
try {
|
|
out = Json::parse(std::string(fd->GetData(), fd->GetSize()));
|
|
} catch (Json::parse_error &e) {
|
|
Output("error in JSON file '%s': %s\n", fd->GetInfo().GetPath().c_str(), e.what());
|
|
return nullptr;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
Json LoadJsonFile(const std::string &filename, FileSystem::FileSource &source)
|
|
{
|
|
return LoadJson(source.ReadFile(filename));
|
|
}
|
|
|
|
Json LoadJsonDataFile(const std::string &filename, bool with_merge)
|
|
{
|
|
Json out = LoadJsonFile(filename, FileSystem::gameDataFiles);
|
|
if (out.is_null() || !with_merge) return out;
|
|
|
|
for (auto info : FileSystem::gameDataFiles.LookupAll(filename + ".patch")) {
|
|
Json patch = LoadJson(info.Read());
|
|
if (!patch.is_null()) out.merge_patch(patch);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
Json LoadJsonSaveFile(const std::string &filename, FileSystem::FileSource &source)
|
|
{
|
|
auto file = source.ReadFile(filename);
|
|
if (!file) return nullptr;
|
|
const auto file_data = std::string(file->GetData(), file->GetSize());
|
|
const unsigned char *dataPtr = reinterpret_cast<const unsigned char *>(&file_data[0]);
|
|
try {
|
|
std::string plain_data;
|
|
if (gzip::IsGZipFormat(dataPtr, file_data.size())) {
|
|
plain_data = gzip::DecompressDeflateOrGZip(dataPtr, file_data.size());
|
|
} else {
|
|
plain_data = file_data;
|
|
}
|
|
|
|
Json rootNode;
|
|
try {
|
|
// Allow loading files in JSON format as well as CBOR
|
|
if (plain_data[0] == '{')
|
|
return Json::parse(plain_data);
|
|
else
|
|
return Json::from_cbor(plain_data);
|
|
} catch (Json::parse_error &e) {
|
|
Output("error in JSON file '%s': %s\n", file->GetInfo().GetPath().c_str(), e.what());
|
|
return nullptr;
|
|
}
|
|
} catch (gzip::DecompressionFailedException) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
} // namespace JsonUtils
|
|
|
|
#define USE_STRING_VERSIONS
|
|
|
|
void VectorToJson(Json &jsonObj, const vector3f &vec)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (vec == zeroVector3f)
|
|
return; // don't store zero vector
|
|
|
|
char str[128];
|
|
Vector3fToStr(vec, str, 128);
|
|
jsonObj = str; // Add vector array to supplied object.
|
|
#else
|
|
// Create JSON array to contain vector data.
|
|
jsonObj = Json::array({ vec.x, vec.y, vec.z });
|
|
#endif
|
|
}
|
|
|
|
void VectorToJson(Json &jsonObj, const vector3d &vec)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (vec == zeroVector3d)
|
|
return; // don't store zero vector
|
|
char str[128];
|
|
Vector3dToStr(vec, str, 128);
|
|
jsonObj = str; // Add vector array to supplied object.
|
|
#else
|
|
// Create JSON array to contain vector data.
|
|
jsonObj = Json::array({ vec.x, vec.y, vec.z });
|
|
#endif
|
|
}
|
|
|
|
void QuaternionToJson(Json &jsonObj, const Quaternionf &quat)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
if (memcmp(&quat, &identityQuaternionf, sizeof(Quaternionf)) == 0)
|
|
return;
|
|
jsonObj = Json::array({ quat.w, quat.x, quat.y, quat.z });
|
|
}
|
|
|
|
void QuaternionToJson(Json &jsonObj, const Quaterniond &quat)
|
|
{
|
|
PROFILE_SCOPED()
|
|
if (memcmp(&quat, &identityQuaterniond, sizeof(Quaterniond)) == 0)
|
|
return;
|
|
|
|
jsonObj = Json::array({ quat.w, quat.x, quat.y, quat.z });
|
|
}
|
|
|
|
void MatrixToJson(Json &jsonObj, const matrix3x3f &mat)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!memcmp(&matrix3x3fIdentity, &mat, sizeof(matrix3x3f))) return;
|
|
char str[512];
|
|
Matrix3x3fToStr(mat, str, 512);
|
|
jsonObj = str;
|
|
#else
|
|
jsonObj = Json::array({ mat[0], mat[1], mat[2],
|
|
mat[3], mat[4], mat[5],
|
|
mat[6], mat[7], mat[8] });
|
|
#endif
|
|
}
|
|
|
|
void MatrixToJson(Json &jsonObj, const matrix3x3d &mat)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!memcmp(&matrix3x3dIdentity, &mat, sizeof(matrix3x3d))) return;
|
|
char str[512];
|
|
Matrix3x3dToStr(mat, str, 512);
|
|
jsonObj = str;
|
|
#else
|
|
jsonObj = Json::array({ mat[0], mat[1], mat[2],
|
|
mat[3], mat[4], mat[5],
|
|
mat[6], mat[7], mat[8] });
|
|
#endif
|
|
}
|
|
|
|
void MatrixToJson(Json &jsonObj, const matrix4x4f &mat)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!memcmp(&matrix4x4fIdentity, &mat, sizeof(matrix4x4f))) return;
|
|
char str[512];
|
|
Matrix4x4fToStr(mat, str, 512);
|
|
jsonObj = str;
|
|
#else
|
|
jsonObj = Json::array({
|
|
mat[0],
|
|
mat[1],
|
|
mat[2],
|
|
mat[3],
|
|
mat[4],
|
|
mat[5],
|
|
mat[6],
|
|
mat[7],
|
|
mat[8],
|
|
mat[9],
|
|
mat[10],
|
|
mat[11],
|
|
mat[12],
|
|
mat[13],
|
|
mat[14],
|
|
mat[15],
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void MatrixToJson(Json &jsonObj, const matrix4x4d &mat)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!memcmp(&matrix4x4dIdentity, &mat, sizeof(matrix4x4d))) return;
|
|
char str[512];
|
|
Matrix4x4dToStr(mat, str, 512);
|
|
jsonObj = str;
|
|
#else
|
|
jsonObj = Json::array({
|
|
mat[0],
|
|
mat[1],
|
|
mat[2],
|
|
mat[3],
|
|
mat[4],
|
|
mat[5],
|
|
mat[6],
|
|
mat[7],
|
|
mat[8],
|
|
mat[9],
|
|
mat[10],
|
|
mat[11],
|
|
mat[12],
|
|
mat[13],
|
|
mat[14],
|
|
mat[15],
|
|
});
|
|
#endif
|
|
}
|
|
|
|
void ColorToJson(Json &jsonObj, const Color3ub &col)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
jsonObj[0] = col.r;
|
|
jsonObj[1] = col.g;
|
|
jsonObj[2] = col.b;
|
|
}
|
|
|
|
void ColorToJson(Json &jsonObj, const Color4ub &col)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
jsonObj[0] = col.r;
|
|
jsonObj[1] = col.g;
|
|
jsonObj[2] = col.b;
|
|
jsonObj[3] = col.a;
|
|
}
|
|
|
|
void BinStrToJson(Json &jsonObj, const std::string &binStr)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
// compress in memory, write to open file
|
|
size_t outSize = 0;
|
|
void *pCompressedData = tdefl_compress_mem_to_heap(binStr.data(), binStr.length(), &outSize, 128);
|
|
assert(pCompressedData); // can we fail to compress?
|
|
if (pCompressedData) {
|
|
// We encode as base64 to avoid generating invalid UTF-8 data, which breaks the JSON standard.
|
|
// Prealloc a string for the encoded data.
|
|
std::string encodedData = std::string(Base64::EncodedLength(outSize), '\0');
|
|
// Use C++11's contiguous std::string implementation to great effect.
|
|
if (Base64::Encode(static_cast<const char *>(pCompressedData), outSize, &encodedData[0], encodedData.size())) {
|
|
// Store everything in a string.
|
|
jsonObj = std::move(encodedData);
|
|
}
|
|
// release the compressed data
|
|
mz_free(pCompressedData);
|
|
}
|
|
}
|
|
|
|
void JsonToVector(vector3f *pVec, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!jsonObj.is_string()) {
|
|
*pVec = vector3f(0.0f);
|
|
return;
|
|
}
|
|
std::string vecStr = jsonObj;
|
|
StrToVector3f(vecStr.c_str(), *pVec);
|
|
#else
|
|
pVec->x = jsonObj[0];
|
|
pVec->y = jsonObj[1];
|
|
pVec->z = jsonObj[2];
|
|
#endif
|
|
}
|
|
|
|
void JsonToVector(vector3d *pVec, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!jsonObj.is_string()) {
|
|
*pVec = vector3d(0.0);
|
|
return;
|
|
}
|
|
std::string vecStr = jsonObj;
|
|
StrToVector3d(vecStr.c_str(), *pVec);
|
|
#else
|
|
pVec->x = jsonObj[0];
|
|
pVec->y = jsonObj[1];
|
|
pVec->z = jsonObj[2];
|
|
#endif
|
|
}
|
|
|
|
void JsonToQuaternion(Quaternionf *pQuat, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
if (!jsonObj.is_array()) {
|
|
*pQuat = identityQuaternionf;
|
|
return;
|
|
}
|
|
|
|
pQuat->w = jsonObj[0];
|
|
pQuat->x = jsonObj[1];
|
|
pQuat->y = jsonObj[2];
|
|
pQuat->z = jsonObj[3];
|
|
}
|
|
|
|
void JsonToQuaternion(Quaterniond *pQuat, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
if (!jsonObj.is_array()) {
|
|
*pQuat = identityQuaterniond;
|
|
return;
|
|
}
|
|
|
|
pQuat->w = jsonObj[0];
|
|
pQuat->x = jsonObj[1];
|
|
pQuat->y = jsonObj[2];
|
|
pQuat->z = jsonObj[3];
|
|
}
|
|
|
|
void JsonToMatrix(matrix3x3f *pMat, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!jsonObj.is_string()) {
|
|
*pMat = matrix3x3fIdentity;
|
|
return;
|
|
}
|
|
std::string matStr = jsonObj;
|
|
StrToMatrix3x3f(matStr.c_str(), *pMat);
|
|
#else
|
|
(*pMat)[0] = jsonObj[0];
|
|
(*pMat)[1] = jsonObj[1];
|
|
(*pMat)[2] = jsonObj[2];
|
|
(*pMat)[3] = jsonObj[3];
|
|
(*pMat)[4] = jsonObj[4];
|
|
(*pMat)[5] = jsonObj[5];
|
|
(*pMat)[6] = jsonObj[6];
|
|
(*pMat)[7] = jsonObj[7];
|
|
(*pMat)[8] = jsonObj[8];
|
|
#endif
|
|
}
|
|
|
|
void JsonToMatrix(matrix3x3d *pMat, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!jsonObj.is_string()) {
|
|
*pMat = matrix3x3dIdentity;
|
|
return;
|
|
}
|
|
std::string matStr = jsonObj;
|
|
StrToMatrix3x3d(matStr.c_str(), *pMat);
|
|
#else
|
|
(*pMat)[0] = jsonObj[0];
|
|
(*pMat)[1] = jsonObj[1];
|
|
(*pMat)[2] = jsonObj[2];
|
|
(*pMat)[3] = jsonObj[3];
|
|
(*pMat)[4] = jsonObj[4];
|
|
(*pMat)[5] = jsonObj[5];
|
|
(*pMat)[6] = jsonObj[6];
|
|
(*pMat)[7] = jsonObj[7];
|
|
(*pMat)[8] = jsonObj[8];
|
|
#endif
|
|
}
|
|
|
|
void JsonToMatrix(matrix4x4f *pMat, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!jsonObj.is_string()) {
|
|
*pMat = matrix4x4fIdentity;
|
|
return;
|
|
}
|
|
std::string matStr = jsonObj;
|
|
StrToMatrix4x4f(matStr.c_str(), *pMat);
|
|
#else
|
|
(*pMat)[0] = jsonObj[0];
|
|
(*pMat)[1] = jsonObj[1];
|
|
(*pMat)[2] = jsonObj[2];
|
|
(*pMat)[3] = jsonObj[3];
|
|
(*pMat)[4] = jsonObj[4];
|
|
(*pMat)[5] = jsonObj[5];
|
|
(*pMat)[6] = jsonObj[6];
|
|
(*pMat)[7] = jsonObj[7];
|
|
(*pMat)[8] = jsonObj[8];
|
|
(*pMat)[9] = jsonObj[9];
|
|
(*pMat)[10] = jsonObj[10];
|
|
(*pMat)[11] = jsonObj[11];
|
|
(*pMat)[12] = jsonObj[12];
|
|
(*pMat)[13] = jsonObj[13];
|
|
(*pMat)[14] = jsonObj[14];
|
|
(*pMat)[15] = jsonObj[15];
|
|
#endif
|
|
}
|
|
|
|
void JsonToMatrix(matrix4x4d *pMat, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
#ifdef USE_STRING_VERSIONS
|
|
if (!jsonObj.is_string()) {
|
|
*pMat = matrix4x4dIdentity;
|
|
return;
|
|
}
|
|
std::string matStr = jsonObj;
|
|
StrToMatrix4x4d(matStr.c_str(), *pMat);
|
|
#else
|
|
(*pMat)[0] = jsonObj[0];
|
|
(*pMat)[1] = jsonObj[1];
|
|
(*pMat)[2] = jsonObj[2];
|
|
(*pMat)[3] = jsonObj[3];
|
|
(*pMat)[4] = jsonObj[4];
|
|
(*pMat)[5] = jsonObj[5];
|
|
(*pMat)[6] = jsonObj[6];
|
|
(*pMat)[7] = jsonObj[7];
|
|
(*pMat)[8] = jsonObj[8];
|
|
(*pMat)[9] = jsonObj[9];
|
|
(*pMat)[10] = jsonObj[10];
|
|
(*pMat)[11] = jsonObj[11];
|
|
(*pMat)[12] = jsonObj[12];
|
|
(*pMat)[13] = jsonObj[13];
|
|
(*pMat)[14] = jsonObj[14];
|
|
(*pMat)[15] = jsonObj[15];
|
|
#endif
|
|
}
|
|
|
|
void JsonToColor(Color3ub *pCol, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
pCol->r = jsonObj[0];
|
|
pCol->g = jsonObj[1];
|
|
pCol->b = jsonObj[2];
|
|
}
|
|
|
|
void JsonToColor(Color4ub *pCol, const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
pCol->r = jsonObj[0];
|
|
pCol->g = jsonObj[1];
|
|
pCol->b = jsonObj[2];
|
|
pCol->a = jsonObj[3];
|
|
}
|
|
|
|
std::string JsonToBinStr(const Json &jsonObj)
|
|
{
|
|
PROFILE_SCOPED()
|
|
|
|
// Decode the base64 string into raw binary data.
|
|
std::string binStr;
|
|
if (!Base64::Decode(jsonObj, &binStr)) return binStr;
|
|
|
|
size_t outSize = 0;
|
|
void *pDecompressedData = tinfl_decompress_mem_to_heap(binStr.c_str(), binStr.size(), &outSize, 0);
|
|
binStr.clear();
|
|
assert(pDecompressedData);
|
|
if (pDecompressedData) {
|
|
binStr = std::string((char *)pDecompressedData, outSize);
|
|
mz_free(pDecompressedData);
|
|
}
|
|
|
|
return binStr;
|
|
}
|