#pragma once // used: [ext] json parser implementation #include 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(); break; } case FNV1A::HashConst("int"): { entry = *variable.GetStorage(); break; } case FNV1A::HashConst("unsigned int"): { entry = *variable.GetStorage(); break; } case FNV1A::HashConst("float"): { entry = *variable.GetStorage(); break; } case FNV1A::HashConst("char[]"): { entry = std::string(*variable.GetStorage()); break; } case FNV1A::HashConst("bool[]"): { const std::size_t nArraySize = variable.nStorageSize / sizeof(bool); const auto& arrValues = *variable.GetStorage(); std::vector 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(); std::vector 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(); std::vector 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(); std::vector 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(), variable.nStorageSize); entry = strValue; break; } default: { [[maybe_unused]] bool bFoundUserType = false; //const std::uint8_t* pVariableStorage = variable.GetStorage(); // 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(); variable.SetStorage(&bValue); break; } case FNV1A::HashConst("int"): { const int iValue = entry.get(); variable.SetStorage(&iValue); break; } case FNV1A::HashConst("unsigned int"): { const unsigned int uValue = entry.get(); variable.SetStorage(&uValue); break; } case FNV1A::HashConst("float"): { const float flValue = entry.get(); variable.SetStorage(&flValue); break; } case FNV1A::HashConst("char[]"): { const std::string strValue = entry.get(); 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>(); CS_ASSERT(vecBools.size() * sizeof(bool) <= variable.nStorageSize); // source size is bigger than destination size bool* arrValues = *variable.GetStorage(); for (std::size_t i = 0U; i < vecBools.size(); i++) arrValues[i] = vecBools[i]; break; } case FNV1A::HashConst("int[]"): { const auto& vecInts = entry.get>(); CS_ASSERT(vecInts.size() * sizeof(int) <= variable.nStorageSize); // source size is bigger than destination size int* arrValues = *variable.GetStorage(); 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>(); CS_ASSERT(vecUInts.size() * sizeof(unsigned int) <= variable.nStorageSize); // source size is bigger than destination size unsigned int* arrValues = *variable.GetStorage(); for (std::size_t i = 0U; i < vecUInts.size(); i++) arrValues[i] = vecUInts[i]; break; } case FNV1A::HashConst("float[]"): { const auto& vecFloats = entry.get>(); CS_ASSERT(vecFloats.size() * sizeof(float) <= variable.nStorageSize); // source size is bigger than destination size float* arrValues = *variable.GetStorage(); 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(); 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(); // 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(); 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(); const FNV1A_t uMemberTypeHash = members[CS_XOR("type")].get(); // 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::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::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::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(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; } }