2025-07-25 21:45:33 +03:00

413 lines
13 KiB
C++

#pragma once
// used: [ext] json parser implementation
#include <json.hpp>
namespace C::JSON
{
/* @section: [internal] */
// write single variable to buffer
inline void WriteBuffer(nlohmann::json& entry, const VariableObject_t& variable)
{
// write is different for variable types
switch (variable.uTypeHash)
{
case FNV1A::HashConst("bool"):
{
entry = *variable.GetStorage<bool>();
break;
}
case FNV1A::HashConst("int"):
{
entry = *variable.GetStorage<int>();
break;
}
case FNV1A::HashConst("unsigned int"):
{
entry = *variable.GetStorage<unsigned int>();
break;
}
case FNV1A::HashConst("float"):
{
entry = *variable.GetStorage<float>();
break;
}
case FNV1A::HashConst("char[]"):
{
entry = std::string(*variable.GetStorage<char[], false>());
break;
}
case FNV1A::HashConst("bool[]"):
{
const std::size_t nArraySize = variable.nStorageSize / sizeof(bool);
const auto& arrValues = *variable.GetStorage<bool[], false>();
std::vector<bool> vecBools(nArraySize);
for (std::size_t i = 0U; i < nArraySize; i++)
vecBools[i] = arrValues[i];
entry = vecBools;
break;
}
case FNV1A::HashConst("int[]"):
{
const std::size_t nArraySize = variable.nStorageSize / sizeof(int);
const auto& arrValues = *variable.GetStorage<int[], false>();
std::vector<int> vecInts(nArraySize);
for (std::size_t i = 0U; i < nArraySize; i++)
vecInts[i] = arrValues[i];
entry = vecInts;
break;
}
case FNV1A::HashConst("unsigned int[]"):
{
const std::size_t nArraySize = variable.nStorageSize / sizeof(unsigned int);
const auto& arrValues = *variable.GetStorage<unsigned int[], false>();
std::vector<unsigned int> vecUInts(nArraySize);
for (std::size_t i = 0U; i < nArraySize; i++)
vecUInts[i] = arrValues[i];
entry = vecUInts;
break;
}
case FNV1A::HashConst("float[]"):
{
const std::size_t nArraySize = variable.nStorageSize / sizeof(float);
const auto& arrValues = *variable.GetStorage<float[], false>();
std::vector<float> vecFloats(nArraySize);
for (std::size_t i = 0U; i < nArraySize; i++)
vecFloats[i] = arrValues[i];
entry = vecFloats;
break;
}
case FNV1A::HashConst("char[][]"):
{
// @test: very ugh
std::string strValue(variable.nStorageSize, '\0');
CRT::MemoryCopy(strValue.data(), variable.GetStorage<char*[], false>(), variable.nStorageSize);
entry = strValue;
break;
}
default:
{
[[maybe_unused]] bool bFoundUserType = false;
//const std::uint8_t* pVariableStorage = variable.GetStorage<const std::uint8_t, false>();
// lookup for user-defined data type
for (const UserDataType_t& userType : vecUserTypes)
{
if (userType.uTypeHash == variable.uTypeHash)
{
// write size
entry[CS_XOR("size")] = variable.GetSerializationSize();
nlohmann::json members = {};
// write data
// @todo: it would be so fucking neatful if we could rework this to proceed recursive call instead
for (const UserDataMember_t& userMember : userType.vecMembers)
{
nlohmann::json currentMember = {};
currentMember[CS_XOR("name")] = userMember.uNameHash;
currentMember[CS_XOR("type")] = userMember.uTypeHash;
// @todo: call user defined 'to_json' callback | or again, remake to deal with recursive call instead
members.emplace_back(std::move(currentMember));
}
entry[CS_XOR("members")] = members;
bFoundUserType = true;
break;
}
}
CS_ASSERT(bFoundUserType); // value type is not defined
break;
}
}
}
// read single variable from buffer
inline void ReadBuffer(nlohmann::json& entry, VariableObject_t& variable)
{
switch (variable.uTypeHash)
{
case FNV1A::HashConst("bool"):
{
const bool bValue = entry.get<bool>();
variable.SetStorage(&bValue);
break;
}
case FNV1A::HashConst("int"):
{
const int iValue = entry.get<int>();
variable.SetStorage(&iValue);
break;
}
case FNV1A::HashConst("unsigned int"):
{
const unsigned int uValue = entry.get<unsigned int>();
variable.SetStorage(&uValue);
break;
}
case FNV1A::HashConst("float"):
{
const float flValue = entry.get<float>();
variable.SetStorage(&flValue);
break;
}
case FNV1A::HashConst("char[]"):
{
const std::string strValue = entry.get<std::string>();
CS_ASSERT((strValue.size() + 1U) * sizeof(char) <= variable.nStorageSize); // source size is bigger than destination size
variable.SetStorage(strValue.c_str());
break;
}
case FNV1A::HashConst("bool[]"):
{
const auto& vecBools = entry.get<std::vector<bool>>();
CS_ASSERT(vecBools.size() * sizeof(bool) <= variable.nStorageSize); // source size is bigger than destination size
bool* arrValues = *variable.GetStorage<bool*, false>();
for (std::size_t i = 0U; i < vecBools.size(); i++)
arrValues[i] = vecBools[i];
break;
}
case FNV1A::HashConst("int[]"):
{
const auto& vecInts = entry.get<std::vector<int>>();
CS_ASSERT(vecInts.size() * sizeof(int) <= variable.nStorageSize); // source size is bigger than destination size
int* arrValues = *variable.GetStorage<int*, false>();
for (std::size_t i = 0U; i < vecInts.size(); i++)
arrValues[i] = vecInts[i];
break;
}
case FNV1A::HashConst("unsigned int[]"):
{
const auto& vecUInts = entry.get<std::vector<unsigned int>>();
CS_ASSERT(vecUInts.size() * sizeof(unsigned int) <= variable.nStorageSize); // source size is bigger than destination size
unsigned int* arrValues = *variable.GetStorage<unsigned int*, false>();
for (std::size_t i = 0U; i < vecUInts.size(); i++)
arrValues[i] = vecUInts[i];
break;
}
case FNV1A::HashConst("float[]"):
{
const auto& vecFloats = entry.get<std::vector<float>>();
CS_ASSERT(vecFloats.size() * sizeof(float) <= variable.nStorageSize); // source size is bigger than destination size
float* arrValues = *variable.GetStorage<float*, false>();
for (std::size_t i = 0U; i < vecFloats.size(); i++)
arrValues[i] = vecFloats[i];
break;
}
case FNV1A::HashConst("char[][]"):
{
// @test: very ugh
const std::string strValue = entry.get<std::string>();
CS_ASSERT((strValue.size() + 1U) * sizeof(char) <= variable.nStorageSize); // source size is bigger than destination size
variable.SetStorage(strValue.data());
break;
}
default:
{
[[maybe_unused]] bool bFoundUserType = false;
//std::uint8_t* pVariableStorage = variable.GetStorage<std::uint8_t, false>();
// lookup for user-defined data type
for (const UserDataType_t& userType : vecUserTypes)
{
if (userType.uTypeHash == variable.uTypeHash)
{
// read size
const std::size_t nSourceSerializationSize = entry[CS_XOR("size")].get<std::size_t>();
nlohmann::json members = entry[CS_XOR("members")];
// read data
// @todo: it would be so fucking neatful if we could rework this to proceed recursive call instead
// @todo: instead we must loop through all "members" and them look for same in 'vecMembers'
for (const UserDataMember_t& userMember : userType.vecMembers)
{
const FNV1A_t uMemberNameHash = members[CS_XOR("name")].get<FNV1A_t>();
const FNV1A_t uMemberTypeHash = members[CS_XOR("type")].get<FNV1A_t>();
// verify source and destination variable name and data type
if (uMemberNameHash != userMember.uNameHash || uMemberTypeHash != userMember.uTypeHash)
{
// @todo: instead we can skip up to the next matched name and type hashes variable and continue read from there
CS_ASSERT(false); // source and destination structure mismatch
break;
}
// @todo: call user defined 'from_json' callback | or again, remake to deal with recursive call instead
}
bFoundUserType = true;
break;
}
}
CS_ASSERT(bFoundUserType); // value type is not defined
break;
}
}
}
/* @section: main */
inline bool SaveVariable(const wchar_t* wszFilePath, const VariableObject_t& variable)
{
const HANDLE hFileOut = ::CreateFileW(wszFilePath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFileOut == INVALID_HANDLE_VALUE)
return false;
FILE* hFileStream = nullptr;
::_wfopen_s(&hFileStream, wszFilePath, CS_XOR(L"r"));
if (hFileStream == nullptr)
return false;
nlohmann::json root = nlohmann::json::parse(hFileStream);
::fclose(hFileStream);
char szKeyBuffer[CRT::IntegerToString_t<FNV1A_t, 16U>::MaxCount()] = {};
const char* szVariableHash = CRT::IntegerToString(variable.uNameHash, szKeyBuffer, sizeof(szKeyBuffer), 16);
WriteBuffer(root[szVariableHash], variable);
// write re-serialized configuration to file
const std::string strSerialized = root.dump(4);
const BOOL bWritten = ::WriteFile(hFileOut, strSerialized.data(), strSerialized.size(), nullptr, nullptr);
::CloseHandle(hFileOut);
return bWritten;
}
inline bool LoadVariable(const wchar_t* wszFilePath, VariableObject_t& variable)
{
FILE* hFileStream = nullptr;
::_wfopen_s(&hFileStream, wszFilePath, CS_XOR(L"r+"));
if (hFileStream == nullptr)
return false;
nlohmann::json root = nlohmann::json::parse(hFileStream, nullptr, false);
char szHashBuffer[CRT::IntegerToString_t<FNV1A_t, 16U>::MaxCount()] = {};
const char* szVariableHash = CRT::IntegerToString(variable.uNameHash, szHashBuffer, sizeof(szHashBuffer), 16);
if (root.contains(szVariableHash))
ReadBuffer(root[szVariableHash], variable);
else // @todo: we should check for version at first
{
WriteBuffer(root[szVariableHash], variable);
// overwrite version
szVariableHash = CRT::IntegerToString(FNV1A::HashConst("version"), szHashBuffer, sizeof(szHashBuffer), 16);
root[szVariableHash] = CS_VERSION;
// @todo: reserialize and write to file
}
::fclose(hFileStream);
return true;
}
inline bool RemoveVariable(const wchar_t* wszFilePath, const VariableObject_t& variable)
{
// @todo:
return false;
}
inline bool SaveFile(const wchar_t* wszFilePath)
{
const HANDLE hFileOut = ::CreateFileW(wszFilePath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFileOut == INVALID_HANDLE_VALUE)
return false;
char szHashBuffer[_MAX_ULTOSTR_BASE16_COUNT] = {};
const char* szVariableHash = CRT::IntegerToString(FNV1A::HashConst("version"), szHashBuffer, sizeof(szHashBuffer), 16);
nlohmann::json root;
// put current cheat build number
WriteBuffer(root[szVariableHash], VariableObject_t{ FNV1A::HashConst("version"), FNV1A::HashConst("int"), CS_VERSION });
for (const auto& variable : vecVariables)
{
szVariableHash = CRT::IntegerToString(variable.uNameHash, szHashBuffer, sizeof(szHashBuffer), 16);
WriteBuffer(root[szVariableHash], variable);
}
// write serialized configuration to file
const std::string strSerialized = root.dump(4);
const BOOL bWritten = ::WriteFile(hFileOut, strSerialized.data(), strSerialized.size(), nullptr, nullptr);
::CloseHandle(hFileOut);
return bWritten;
}
inline bool LoadFile(const wchar_t* wszFilePath)
{
FILE* hFileStream = nullptr;
::_wfopen_s(&hFileStream, wszFilePath, CS_XOR(L"r+"));
if (hFileStream == nullptr)
return false;
nlohmann::json root = nlohmann::json::parse(hFileStream, nullptr, false);
// @todo: implement version adaptation mechanism like so: if file has variable but src doesn't - remove from file, if src has variable but file doesn't - add it to file + probably with menu notification and ask for this
char szHashBuffer[CRT::IntegerToString_t<FNV1A_t, 16U>::MaxCount()] = {};
const char* szVariableHash = CRT::IntegerToString(FNV1A::HashConst("version"), szHashBuffer, sizeof(szHashBuffer), 16);
// get cheat version at time when configuration has been saved
auto& version = root[szVariableHash];
for (auto& variable : vecVariables)
{
szVariableHash = CRT::IntegerToString(variable.uNameHash, szHashBuffer, sizeof(szHashBuffer), 16);
// check is variable not found
if (!root.contains(szVariableHash))
{
// add variable to save | assert if version matches to current
continue;
}
ReadBuffer(root[szVariableHash], variable);
}
// @todo: check is configuration version older than current cheat version
//if (toml::get<int>(version) != CS_VERSION)
//{
// // manually update version number
// version = CS_VERSION;
//
// // re-serialize toml configuration and write to file
// const std::string strSerialized = toml::format(root);
// if (!::WriteFile(hFileOut, strSerialized.data(), strSerialized.size(), nullptr, nullptr))
// L_PRINT(LOG_WARNING) << CS_XOR("failed to re-serialize configuration file: \"") << wszFileName << CS_XOR("\"");
//
// ::CloseHandle(hFileOut);
//}
::fclose(hFileStream);
return true;
}
}