413 lines
13 KiB
C++
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;
|
|
}
|
|
}
|