kal
This commit is contained in:
@@ -0,0 +1,487 @@
|
||||
#pragma once
|
||||
|
||||
namespace C::BIN
|
||||
{
|
||||
/* @section: [internal] */
|
||||
/// write single variable to the buffer
|
||||
/// @returns: number of written bytes
|
||||
CS_INLINE std::size_t WriteBuffer(std::uint8_t* pBuffer, const VariableObject_t& variable)
|
||||
{
|
||||
std::uint8_t* pBufferCurrent = pBuffer;
|
||||
|
||||
*reinterpret_cast<FNV1A_t*>(pBufferCurrent) = variable.uNameHash;
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
*reinterpret_cast<FNV1A_t*>(pBufferCurrent) = variable.uTypeHash;
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
switch (variable.uTypeHash)
|
||||
{
|
||||
case FNV1A::HashConst("bool"):
|
||||
case FNV1A::HashConst("int"):
|
||||
case FNV1A::HashConst("unsigned int"):
|
||||
case FNV1A::HashConst("float"):
|
||||
case FNV1A::HashConst("Color_t"):
|
||||
case FNV1A::HashConst("char[]"):
|
||||
{
|
||||
CRT::MemoryCopy(pBufferCurrent, variable.GetStorage<const std::uint8_t, false>(), variable.nStorageSize);
|
||||
pBufferCurrent += variable.nStorageSize;
|
||||
break;
|
||||
}
|
||||
case FNV1A::HashConst("bool[]"):
|
||||
case FNV1A::HashConst("int[]"):
|
||||
case FNV1A::HashConst("unsigned int[]"):
|
||||
case FNV1A::HashConst("float[]"):
|
||||
case FNV1A::HashConst("char[][]"):
|
||||
{
|
||||
// write size
|
||||
*reinterpret_cast<std::size_t*>(pBufferCurrent) = variable.nStorageSize;
|
||||
pBufferCurrent += sizeof(std::size_t);
|
||||
|
||||
// write data
|
||||
CRT::MemoryCopy(pBufferCurrent, variable.GetStorage<const std::uint8_t, false>(), variable.nStorageSize);
|
||||
pBufferCurrent += variable.nStorageSize;
|
||||
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
|
||||
*reinterpret_cast<std::size_t*>(pBufferCurrent) = variable.GetSerializationSize();
|
||||
pBufferCurrent += sizeof(std::size_t);
|
||||
|
||||
// write data
|
||||
// @test: it would be so fucking neatful if we could rework this to proceed recursive call instead
|
||||
for (const UserDataMember_t& userMember : userType.vecMembers)
|
||||
{
|
||||
*reinterpret_cast<FNV1A_t*>(pBufferCurrent) = userMember.uNameHash;
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
*reinterpret_cast<FNV1A_t*>(pBufferCurrent) = userMember.uTypeHash;
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
CRT::MemoryCopy(pBufferCurrent, pVariableStorage + userMember.uBaseOffset, userMember.nDataSize);
|
||||
pBufferCurrent += userMember.nDataSize;
|
||||
}
|
||||
|
||||
bFoundUserType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CS_ASSERT(bFoundUserType); // value type is not defined
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t nWriteCount = (pBufferCurrent - pBuffer);
|
||||
CS_ASSERT(nWriteCount == sizeof(FNV1A_t[2]) + variable.GetSerializationSize()); // count of actually written bytes mismatch to serialization size
|
||||
return nWriteCount;
|
||||
}
|
||||
|
||||
/// read single variable from the buffer
|
||||
/// @returns: number of read bytes
|
||||
CS_INLINE std::size_t ReadBuffer(std::uint8_t* pBuffer, VariableObject_t& variable)
|
||||
{
|
||||
std::uint8_t* pBufferCurrent = pBuffer;
|
||||
|
||||
// @todo: instead of overwriting those, check are them same? or alternatively, make caller of this method do not initialize variable
|
||||
variable.uNameHash = *reinterpret_cast<FNV1A_t*>(pBufferCurrent);
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
variable.uTypeHash = *reinterpret_cast<FNV1A_t*>(pBufferCurrent);
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
switch (variable.uTypeHash)
|
||||
{
|
||||
case FNV1A::HashConst("char[]"):
|
||||
CS_ASSERT((CRT::StringLength(reinterpret_cast<const char*>(pBufferCurrent)) + 1U) * sizeof(char) <= variable.nStorageSize); // string length mismatched
|
||||
[[fallthrough]];
|
||||
case FNV1A::HashConst("bool"):
|
||||
case FNV1A::HashConst("int"):
|
||||
case FNV1A::HashConst("unsigned int"):
|
||||
case FNV1A::HashConst("float"):
|
||||
case FNV1A::HashConst("Color_t"):
|
||||
{
|
||||
variable.SetStorage(pBufferCurrent);
|
||||
pBufferCurrent += variable.nStorageSize;
|
||||
break;
|
||||
}
|
||||
case FNV1A::HashConst("bool[]"):
|
||||
case FNV1A::HashConst("int[]"):
|
||||
case FNV1A::HashConst("unsigned int[]"):
|
||||
case FNV1A::HashConst("float[]"):
|
||||
case FNV1A::HashConst("char[][]"):
|
||||
{
|
||||
// read size
|
||||
[[maybe_unused]] const std::size_t nSourceStorageSize = *reinterpret_cast<std::size_t*>(pBufferCurrent);
|
||||
pBufferCurrent += sizeof(std::size_t);
|
||||
|
||||
// read data
|
||||
CS_ASSERT(nSourceStorageSize <= variable.nStorageSize); // source size is bigger than destination size
|
||||
variable.SetStorage(pBufferCurrent);
|
||||
pBufferCurrent += nSourceStorageSize;
|
||||
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 = *reinterpret_cast<std::size_t*>(pBufferCurrent);
|
||||
pBufferCurrent += sizeof(std::size_t);
|
||||
|
||||
const std::uint8_t* pBufferFirstMember = pBufferCurrent;
|
||||
|
||||
// read data
|
||||
// @test: it would be so fucking neatful if we could rework this to proceed recursive call instead
|
||||
for (const UserDataMember_t& userMember : userType.vecMembers)
|
||||
{
|
||||
const FNV1A_t uMemberNameHash = *reinterpret_cast<FNV1A_t*>(pBufferCurrent);
|
||||
pBufferCurrent += sizeof(FNV1A_t);
|
||||
|
||||
const FNV1A_t uMemberTypeHash = *reinterpret_cast<FNV1A_t*>(pBufferCurrent);
|
||||
pBufferCurrent += sizeof(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
|
||||
|
||||
// skip rest of the variables due to the impossibility of determining their size
|
||||
pBufferCurrent += nSourceSerializationSize - (pBufferCurrent - pBufferFirstMember);
|
||||
break;
|
||||
}
|
||||
|
||||
CRT::MemoryCopy(pVariableStorage + userMember.uBaseOffset, pBufferCurrent, userMember.nDataSize);
|
||||
pBufferCurrent += userMember.nDataSize;
|
||||
}
|
||||
|
||||
bFoundUserType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CS_ASSERT(bFoundUserType); // value type is not defined
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t nReadCount = (pBufferCurrent - pBuffer);
|
||||
CS_ASSERT(nReadCount == sizeof(FNV1A_t[2]) + variable.GetSerializationSize()); // count of actually read bytes mismatch to serialization size
|
||||
return nReadCount;
|
||||
}
|
||||
|
||||
/// search for single variable in the buffer
|
||||
/// @returns: pointer to the buffer at position of found variable on success, null otherwise
|
||||
CS_INLINE std::uint8_t* FindBuffer(const std::uint8_t* pBufferStart, const std::size_t nBufferSize, const VariableObject_t& variable)
|
||||
{
|
||||
// convert hashes to bytes
|
||||
const FNV1A_t uNameTypeHash[2] = { variable.uNameHash, variable.uTypeHash };
|
||||
|
||||
// @todo: do we need to always go from the start of file and parse contents until needed variable due to binary format? if so then rework it and do not store type hash (currently it just used to have more explicit search)
|
||||
#ifdef CS_PARANOID
|
||||
const std::vector<std::uint8_t*> vecVariableHeaders = MEM::FindPatternAllOccurrencesEx(pBufferStart, nBufferSize, reinterpret_cast<const std::uint8_t*>(uNameTypeHash), sizeof(FNV1A_t[2]));
|
||||
|
||||
if (!vecVariableHeaders.empty())
|
||||
{
|
||||
// notify user about multiple variables with same name
|
||||
// @note: this also could happen due to FNV1A hash collision, open an issue on github if you experiencing this
|
||||
if (vecVariableHeaders.size() > 1U)
|
||||
L_PRINT(LOG_WARNING) << CS_XOR("found more than one variable that matches [NAME: ") << L::AddFlags(LOG_MODE_INT_SHOWBASE | LOG_MODE_INT_FORMAT_HEX) << variable.uNameHash << CS_XOR(", TYPE: ") << variable.uTypeHash << CS_XOR("] hashes");
|
||||
|
||||
// return first found occurrence
|
||||
return vecVariableHeaders[0];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
#else
|
||||
return MEM::FindPatternEx(pBufferStart, nBufferSize, reinterpret_cast<const std::uint8_t*>(uNameTypeHash), sizeof(FNV1A_t[2]));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* @section: main */
|
||||
CS_INLINE bool SaveVariable(const wchar_t* wszFilePath, const VariableObject_t& variable)
|
||||
{
|
||||
const HANDLE hFileInOut = ::CreateFileW(wszFilePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFileInOut == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
DWORD dwFileSize;
|
||||
if (dwFileSize = ::GetFileSize(hFileInOut, nullptr); dwFileSize == INVALID_FILE_SIZE)
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t* pBuffer = static_cast<std::uint8_t*>(MEM::HeapAlloc(dwFileSize));
|
||||
if (!::ReadFile(hFileInOut, pBuffer, dwFileSize, nullptr, nullptr))
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableObject_t version = { FNV1A::HashConst("version"), FNV1A::HashConst("int"), CS_VERSION };
|
||||
ReadBuffer(pBuffer, version);
|
||||
|
||||
BOOL bWritten = FALSE;
|
||||
|
||||
// check have we found a variable
|
||||
if (std::uint8_t* pVariableData = FindBuffer(pBuffer, dwFileSize, variable); pVariableData != nullptr)
|
||||
{
|
||||
const std::size_t nOverwriteBytesCount = WriteBuffer(pVariableData, variable);
|
||||
|
||||
// overwrite variable in the file
|
||||
if (::SetFilePointer(hFileInOut, pVariableData - pBuffer, nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER)
|
||||
bWritten = ::WriteFile(hFileInOut, pVariableData, nOverwriteBytesCount, nullptr, nullptr);
|
||||
}
|
||||
// or we need to create new
|
||||
else
|
||||
{
|
||||
// write missing variable to the end of file
|
||||
std::uint8_t* pTemporaryBuffer = static_cast<std::uint8_t*>(MEM_STACKALLOC(sizeof(FNV1A_t[2]) + variable.GetSerializationSize()));
|
||||
const std::size_t nWriteBytesCount = WriteBuffer(pTemporaryBuffer, variable);
|
||||
|
||||
bWritten = ::WriteFile(hFileInOut, pTemporaryBuffer, nWriteBytesCount, nullptr, nullptr);
|
||||
MEM_STACKFREE(pTemporaryBuffer);
|
||||
}
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return bWritten;
|
||||
}
|
||||
|
||||
CS_INLINE bool LoadVariable(const wchar_t* wszFilePath, VariableObject_t& variable)
|
||||
{
|
||||
const HANDLE hFileInOut = ::CreateFileW(wszFilePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFileInOut == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
DWORD dwFileSize;
|
||||
if (dwFileSize = ::GetFileSize(hFileInOut, nullptr); dwFileSize == INVALID_FILE_SIZE)
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t* pBuffer = static_cast<std::uint8_t*>(MEM::HeapAlloc(dwFileSize));
|
||||
if (!::ReadFile(hFileInOut, pBuffer, dwFileSize, nullptr, nullptr))
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableObject_t version = { FNV1A::HashConst("version"), FNV1A::HashConst("int"), CS_VERSION };
|
||||
ReadBuffer(pBuffer, version);
|
||||
|
||||
// search a variable in the file
|
||||
std::uint8_t* pVariableData = FindBuffer(pBuffer, dwFileSize, variable);
|
||||
const bool bFoundVariable = (pVariableData != nullptr);
|
||||
|
||||
// check have we found the variable
|
||||
if (bFoundVariable)
|
||||
ReadBuffer(pVariableData, variable);
|
||||
// otherwise serialize it
|
||||
else
|
||||
{
|
||||
if (*version.GetStorage<int>() < CS_VERSION)
|
||||
{
|
||||
// write missing variable to the end of file
|
||||
std::uint8_t* pTemporaryBuffer = static_cast<std::uint8_t*>(MEM_STACKALLOC(sizeof(FNV1A_t[2]) + variable.GetSerializationSize()));
|
||||
const std::size_t nWriteBytesCount = WriteBuffer(pTemporaryBuffer, variable);
|
||||
|
||||
::WriteFile(hFileInOut, pTemporaryBuffer, nWriteBytesCount, nullptr, nullptr);
|
||||
MEM_STACKFREE(pTemporaryBuffer);
|
||||
|
||||
// overwrite version
|
||||
if (::SetFilePointer(hFileInOut, sizeof(FNV1A_t[2]), nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER)
|
||||
{
|
||||
constexpr int iLastVersion = CS_VERSION;
|
||||
::WriteFile(hFileInOut, &iLastVersion, version.GetSerializationSize(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
CS_ASSERT(false); // version of configuration is greater than cheat, or version is same but configuration missing variable, consider update 'CS_VERSION' cheat version
|
||||
}
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return bFoundVariable;
|
||||
}
|
||||
|
||||
CS_INLINE bool RemoveVariable(const wchar_t* wszFilePath, const VariableObject_t& variable)
|
||||
{
|
||||
const HANDLE hFileInOut = ::CreateFileW(wszFilePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFileInOut == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
DWORD dwFileSize;
|
||||
if (dwFileSize = ::GetFileSize(hFileInOut, nullptr); dwFileSize == INVALID_FILE_SIZE)
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t* pBuffer = static_cast<std::uint8_t*>(MEM::HeapAlloc(dwFileSize));
|
||||
if (!::ReadFile(hFileInOut, pBuffer, dwFileSize, nullptr, nullptr))
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return false;
|
||||
}
|
||||
const std::uint8_t* pBufferEnd = pBuffer + dwFileSize;
|
||||
|
||||
VariableObject_t version = { FNV1A::HashConst("version"), FNV1A::HashConst("int"), CS_VERSION };
|
||||
ReadBuffer(pBuffer, version);
|
||||
|
||||
// search a variable in the file
|
||||
std::uint8_t* pVariableData = FindBuffer(pBuffer, dwFileSize, variable);
|
||||
bool bRemovedVariable = false;
|
||||
|
||||
// check have we found the variable
|
||||
if (pVariableData != nullptr)
|
||||
{
|
||||
const std::size_t nVariableDataLength = sizeof(FNV1A_t[2]) + variable.GetSerializationSize();
|
||||
const std::uint8_t* pVariableDataEnd = pVariableData + nVariableDataLength;
|
||||
|
||||
// shift bytes left with overlapping data of variable to remove
|
||||
CRT::MemoryMove(pVariableData, pVariableDataEnd, pBufferEnd - pVariableDataEnd);
|
||||
|
||||
// truncate file size
|
||||
if (::SetFilePointer(hFileInOut, -static_cast<LONG>(nVariableDataLength), nullptr, FILE_END) != INVALID_SET_FILE_POINTER)
|
||||
bRemovedVariable = true;
|
||||
}
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return bRemovedVariable;
|
||||
}
|
||||
|
||||
CS_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;
|
||||
|
||||
VariableObject_t version = { FNV1A::HashConst("version"), FNV1A::HashConst("int"), CS_VERSION };
|
||||
|
||||
// pre-calculate buffer size for all variables to avoid reallocation
|
||||
std::size_t nBufferSize = sizeof(FNV1A_t[2]) + version.GetSerializationSize();
|
||||
for (const auto& variable : vecVariables)
|
||||
nBufferSize += sizeof(FNV1A_t[2]) + variable.GetSerializationSize();
|
||||
|
||||
// since we know final size, reserve disk space for it
|
||||
if (::SetFilePointer(hFileOut, nBufferSize, nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER)
|
||||
{
|
||||
::SetEndOfFile(hFileOut);
|
||||
|
||||
if (::SetFilePointer(hFileOut, 0L, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t* pBuffer = static_cast<std::uint8_t*>(MEM::HeapAlloc(nBufferSize));
|
||||
const std::uint8_t* pBufferEnd = pBuffer + nBufferSize;
|
||||
|
||||
std::uint8_t* pCurrentBuffer = pBuffer;
|
||||
|
||||
// put current cheat build number
|
||||
pCurrentBuffer += WriteBuffer(pBuffer, version);
|
||||
|
||||
for (auto& variable : vecVariables)
|
||||
{
|
||||
CS_ASSERT(pCurrentBuffer <= pBufferEnd); // allocated buffer can't hold more variables
|
||||
pCurrentBuffer += WriteBuffer(pCurrentBuffer, variable);
|
||||
}
|
||||
|
||||
// write serialized configuration to file
|
||||
const BOOL bWritten = ::WriteFile(hFileOut, pBuffer, nBufferSize, nullptr, nullptr);
|
||||
::CloseHandle(hFileOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return bWritten;
|
||||
}
|
||||
|
||||
CS_INLINE bool LoadFile(const wchar_t* wszFilePath)
|
||||
{
|
||||
const HANDLE hFileInOut = ::CreateFileW(wszFilePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFileInOut == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
DWORD dwFileSize;
|
||||
if (dwFileSize = ::GetFileSize(hFileInOut, nullptr); dwFileSize == INVALID_FILE_SIZE)
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t* pBuffer = static_cast<std::uint8_t*>(MEM::HeapAlloc(dwFileSize));
|
||||
if (!::ReadFile(hFileInOut, pBuffer, dwFileSize, nullptr, nullptr))
|
||||
{
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableObject_t version = { FNV1A::HashConst("version"), FNV1A::HashConst("int"), CS_VERSION };
|
||||
ReadBuffer(pBuffer, version);
|
||||
|
||||
for (auto& variable : vecVariables)
|
||||
{
|
||||
std::uint8_t* pVariableData = FindBuffer(pBuffer, dwFileSize, variable);
|
||||
|
||||
// check is variable not found
|
||||
if (pVariableData == nullptr)
|
||||
{
|
||||
if (*version.GetStorage<int>() < CS_VERSION)
|
||||
{
|
||||
// write missing variable to the end of file
|
||||
if (::SetFilePointer(hFileInOut, 0L, nullptr, FILE_END) != INVALID_SET_FILE_POINTER)
|
||||
{
|
||||
std::uint8_t* pTemporaryBuffer = static_cast<std::uint8_t*>(MEM_STACKALLOC(sizeof(FNV1A_t[2]) + variable.GetSerializationSize()));
|
||||
const std::size_t nWriteBytesCount = WriteBuffer(pTemporaryBuffer, variable);
|
||||
|
||||
::WriteFile(hFileInOut, pTemporaryBuffer, nWriteBytesCount, nullptr, nullptr);
|
||||
MEM_STACKFREE(pTemporaryBuffer);
|
||||
|
||||
// overwrite version
|
||||
if (::SetFilePointer(hFileInOut, sizeof(FNV1A_t[2]), nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER)
|
||||
{
|
||||
constexpr int iLastVersion = CS_VERSION;
|
||||
::WriteFile(hFileInOut, &iLastVersion, version.GetSerializationSize(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
CS_ASSERT(false); // version of configuration is greater than cheat, or version is same but configuration missing variable, consider update 'CS_VERSION' cheat version
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ReadBuffer(pVariableData, variable);
|
||||
}
|
||||
::CloseHandle(hFileInOut);
|
||||
|
||||
MEM::HeapFree(pBuffer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user