From c7b391de2e7477e72dc42c11b7550555dfbf33a4 Mon Sep 17 00:00:00 2001 From: SNMetamorph <25657591+SNMetamorph@users.noreply.github.com> Date: Sat, 4 Nov 2023 00:04:16 +0400 Subject: [PATCH] Library: heavily refactored build info code --- sources/library/build_info.cpp | 55 ++++++++++++++++---- sources/library/build_info.h | 4 +- sources/library/build_info_entry.h | 6 +-- sources/library/build_info_impl.cpp | 81 +++++++++++++++++------------ sources/library/build_info_impl.h | 19 ++++--- sources/library/client_module.cpp | 26 ++++----- sources/library/server_module.cpp | 16 +++--- 7 files changed, 133 insertions(+), 74 deletions(-) diff --git a/sources/library/build_info.cpp b/sources/library/build_info.cpp index a5284f0..c8703be 100644 --- a/sources/library/build_info.cpp +++ b/sources/library/build_info.cpp @@ -31,27 +31,49 @@ CBuildInfo::~CBuildInfo() void CBuildInfo::Initialize(const SysUtils::ModuleInfo &engineModule) { std::vector fileContents; - if (m_pImpl->LoadBuildInfoFile(fileContents)) { - m_pImpl->ParseBuildInfo(fileContents); + if (m_pImpl->LoadBuildInfoFile(fileContents)) + { + auto error = m_pImpl->ParseBuildInfo(fileContents); + if (error.has_value()) { + m_pImpl->m_initErrorMessage = error.value(); + return; + } } else { - EXCEPT("failed to load build info file"); + m_pImpl->m_initErrorMessage = "failed to load build info file"; + return; } if (!m_pImpl->FindBuildNumberFunc(engineModule)) { if (!m_pImpl->ApproxBuildNumber(engineModule)) { - EXCEPT("failed to approximate engine build number"); + m_pImpl->m_initErrorMessage = "failed to approximate engine build number"; + return; } } - m_pImpl->m_iActualEntryIndex = m_pImpl->FindActualInfoEntry(); - if (m_pImpl->m_iActualEntryIndex < 0) { - EXCEPT("no one build info entries parsed"); + + auto entryIndex = m_pImpl->FindActualInfoEntry(); + if (!entryIndex.has_value()) { + m_pImpl->m_initErrorMessage = "not found matching build info entry"; + return; } + m_pImpl->m_iActualEntryIndex = entryIndex; } void *CBuildInfo::FindFunctionAddress(CBuildInfo::FunctionType funcType, void *startAddr, void *endAddr) const { - CMemoryPattern funcPattern = m_pImpl->m_InfoEntries[m_pImpl->m_iActualEntryIndex].GetFunctionPattern(funcType); + if (!m_pImpl->m_iActualEntryIndex.has_value()) { + return nullptr; + } + + const CBuildInfo::Entry *entry; + if (m_pImpl->m_infoEntryGameSpecific) { + entry = m_pImpl->m_GameInfoEntries.data() + m_pImpl->m_iActualEntryIndex.value(); + } + else { + entry = m_pImpl->m_EngineInfoEntries.data() + m_pImpl->m_iActualEntryIndex.value(); + } + + CMemoryPattern funcPattern = entry->GetFunctionPattern(funcType); if (!endAddr) endAddr = (uint8_t *)startAddr + funcPattern.GetLength(); @@ -62,7 +84,20 @@ void *CBuildInfo::FindFunctionAddress(CBuildInfo::FunctionType funcType, void *s ); } -const CBuildInfo::Entry &CBuildInfo::GetInfoEntry() const +const std::string &CBuildInfo::GetInitErrorDescription() const +{ + return m_pImpl->m_initErrorMessage; +} + +const CBuildInfo::Entry *CBuildInfo::GetInfoEntry() const { - return m_pImpl->m_InfoEntries[m_pImpl->m_iActualEntryIndex]; + if (!m_pImpl->m_iActualEntryIndex.has_value()) { + return nullptr; + } + if (m_pImpl->m_infoEntryGameSpecific) { + return m_pImpl->m_GameInfoEntries.data() + m_pImpl->m_iActualEntryIndex.value(); + } + else { + return m_pImpl->m_EngineInfoEntries.data() + m_pImpl->m_iActualEntryIndex.value(); + } } diff --git a/sources/library/build_info.h b/sources/library/build_info.h index 899ec6f..8fd870f 100644 --- a/sources/library/build_info.h +++ b/sources/library/build_info.h @@ -15,6 +15,7 @@ GNU General Public License for more details. #pragma once #include "sys_utils.h" #include +#include class CBuildInfo { @@ -35,7 +36,8 @@ class CBuildInfo ~CBuildInfo(); void Initialize(const SysUtils::ModuleInfo &engineModule); void *FindFunctionAddress(FunctionType funcType, void *startAddr, void *endAddr = nullptr) const; - const Entry &GetInfoEntry() const; + const std::string &GetInitErrorDescription() const; + const Entry *GetInfoEntry() const; private: class Impl; diff --git a/sources/library/build_info_entry.h b/sources/library/build_info_entry.h index ddc0c1a..10aba33 100644 --- a/sources/library/build_info_entry.h +++ b/sources/library/build_info_entry.h @@ -23,8 +23,8 @@ class CBuildInfo::Entry public: Entry() {}; bool Validate() const; - inline int GetBuildNumber() const { return m_iBuildNumber; } - inline void SetBuildNumber(int value) { m_iBuildNumber = value; } + inline uint32_t GetBuildNumber() const { return m_iBuildNumber; } + inline void SetBuildNumber(uint32_t value) { m_iBuildNumber = value; } inline bool HasClientEngfuncsOffset() const { return m_iClientEngfuncsOffset != 0; } inline bool HasServerEngfuncsOffset() const { return m_iServerEngfuncsOffset != 0; } inline uint64_t GetClientEngfuncsOffset() const { return m_iClientEngfuncsOffset; } @@ -44,7 +44,7 @@ class CBuildInfo::Entry } private: - int m_iBuildNumber = 0; + uint32_t m_iBuildNumber = 0; std::string m_szGameProcessName; uint64_t m_iClientEngfuncsOffset = 0x0; uint64_t m_iServerEngfuncsOffset = 0x0; diff --git a/sources/library/build_info_impl.cpp b/sources/library/build_info_impl.cpp index 4df5794..93137fe 100644 --- a/sources/library/build_info_impl.cpp +++ b/sources/library/build_info_impl.cpp @@ -20,14 +20,14 @@ GNU General Public License for more details. #include #include -int CBuildInfo::Impl::GetBuildNumber() const +std::optional CBuildInfo::Impl::GetBuildNumber() const { if (m_pfnGetBuildNumber) return m_pfnGetBuildNumber(); else if (m_iBuildNumber) return m_iBuildNumber; else - return 0; + return std::nullopt; } int CBuildInfo::Impl::DateToBuildNumber(const char *date) const @@ -125,19 +125,19 @@ bool CBuildInfo::Impl::LoadBuildInfoFile(std::vector &fileContents) } } -void CBuildInfo::Impl::ParseBuildInfo(std::vector &fileContents) +std::optional CBuildInfo::Impl::ParseBuildInfo(std::vector &fileContents) { rapidjson::Document doc; doc.Parse(reinterpret_cast(fileContents.data())); if (!doc.IsObject()) { - EXCEPT("JSON: build info document root is not object"); + return "JSON: build info document root is not object"; } if (!doc.HasMember("build_number_signatures")) { - EXCEPT("JSON: build info document hasn't member build_number_signatures"); + return "JSON: build info document hasn't member build_number_signatures"; } if (!doc.HasMember("engine_builds_info")) { - EXCEPT("JSON: build info document hasn't member engine_builds_info"); + return "JSON: build info document hasn't member engine_builds_info"; } const rapidjson::Value &buildSignatures = doc["build_number_signatures"]; @@ -149,8 +149,11 @@ void CBuildInfo::Impl::ParseBuildInfo(std::vector &fileContents) for (size_t i = 0; i < engineBuilds.Size(); ++i) { CBuildInfo::Entry infoEntry; - ParseBuildInfoEntry(infoEntry, engineBuilds[i]); - m_InfoEntries.push_back(infoEntry); + auto error = ParseBuildInfoEntry(infoEntry, engineBuilds[i]); + if (error.has_value()) { + return error; + } + m_EngineInfoEntries.push_back(infoEntry); } if (doc.HasMember("game_specific_builds_info")) @@ -162,18 +165,22 @@ void CBuildInfo::Impl::ParseBuildInfo(std::vector &fileContents) const rapidjson::Value &entryObject = gameSpecificBuilds[i]; if (entryObject.HasMember("process_name")) { - ParseBuildInfoEntry(infoEntry, entryObject); + auto error = ParseBuildInfoEntry(infoEntry, entryObject); + if (error.has_value()) { + return error; + } infoEntry.SetGameProcessName(entryObject["process_name"].GetString()); - m_InfoEntries.push_back(infoEntry); + m_GameInfoEntries.push_back(infoEntry); } else { - EXCEPT("JSON: parsed game specific build info without process name"); + return "JSON: parsed game specific build info without process name"; } } } + return std::nullopt; } -void CBuildInfo::Impl::ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject) +std::optional CBuildInfo::Impl::ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject) { if (jsonObject.HasMember("number")) { destEntry.SetBuildNumber(jsonObject["number"].GetInt()); @@ -198,8 +205,10 @@ void CBuildInfo::Impl::ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const r } if (!destEntry.Validate()) { - EXCEPT("JSON: parsed empty build info entry, check if signatures/offsets are set"); + return "JSON: parsed empty build info entry, check if signatures/offsets are set"; } + + return std::nullopt; } bool CBuildInfo::Impl::ApproxBuildNumber(const SysUtils::ModuleInfo &engineModule) @@ -253,42 +262,46 @@ bool CBuildInfo::Impl::FindBuildNumberFunc(const SysUtils::ModuleInfo &engineMod return false; } -int CBuildInfo::Impl::FindActualInfoEntry() +std::optional CBuildInfo::Impl::FindActualInfoEntry() { - if (m_InfoEntries.size() < 1) { - return -1; + auto buildNumberOpt = GetBuildNumber(); + const size_t totalEntriesCount = m_EngineInfoEntries.size() + m_GameInfoEntries.size(); + if (totalEntriesCount < 1 || !buildNumberOpt.has_value()) { + return std::nullopt; } else { - std::string processName; - int currBuildNumber = GetBuildNumber(); - const int lastEntryIndex = m_InfoEntries.size() - 1; - int actualEntryIndex = lastEntryIndex; - // first check among game-specific builds + std::string processName; SysUtils::GetModuleFilename(SysUtils::GetCurrentProcessModule(), processName); - for (size_t i = 0; i < m_InfoEntries.size(); ++i) + for (size_t i = 0; i < m_GameInfoEntries.size(); ++i) { - const CBuildInfo::Entry &buildInfo = m_InfoEntries[i]; + const CBuildInfo::Entry &buildInfo = m_GameInfoEntries[i]; const std::string &targetName = buildInfo.GetGameProcessName(); - if (targetName.length() > 0 && targetName.compare(processName) == 0) { + if (targetName.compare(processName) == 0) { + m_infoEntryGameSpecific = true; return i; } } // and then among engine builds - std::sort(m_InfoEntries.begin(), m_InfoEntries.end()); - for (int i = 0; i < lastEntryIndex; ++i) + if (m_EngineInfoEntries.size() < 1) { + return std::nullopt; + } + + uint32_t currBuildNumber = buildNumberOpt.value(); + const size_t lastEntryIndex = m_EngineInfoEntries.size() - 1; + std::sort(m_EngineInfoEntries.begin(), m_EngineInfoEntries.end()); + + for (size_t i = 0; i < lastEntryIndex; ++i) { - const CBuildInfo::Entry &buildInfo = m_InfoEntries[i]; - const CBuildInfo::Entry &nextBuildInfo = m_InfoEntries[i + 1]; - const int nextBuildNumber = nextBuildInfo.GetBuildNumber(); - if (nextBuildNumber > currBuildNumber) // valid only if build info entries sorted ascending - { - actualEntryIndex = i; - break; + const CBuildInfo::Entry &buildInfo = m_EngineInfoEntries[i]; + const CBuildInfo::Entry &nextBuildInfo = m_EngineInfoEntries[i + 1]; + const uint32_t nextBuildNumber = nextBuildInfo.GetBuildNumber(); + if (nextBuildNumber > currBuildNumber) { // valid only if build info entries sorted ascending + return i; } } - return actualEntryIndex; + return lastEntryIndex; // if can't find something matching, then try just use latest available entry } } diff --git a/sources/library/build_info_impl.h b/sources/library/build_info_impl.h index aff4298..ed8fd87 100644 --- a/sources/library/build_info_impl.h +++ b/sources/library/build_info_impl.h @@ -17,27 +17,32 @@ GNU General Public License for more details. #include "memory_pattern.h" #include #include +#include #include +#include class CBuildInfo::Impl { public: typedef int(__cdecl *pfnGetBuildNumber_t)(); - int GetBuildNumber() const; + std::optional GetBuildNumber() const; int DateToBuildNumber(const char *date) const; const char *FindDateString(uint8_t *startAddr, int maxLen) const; bool LoadBuildInfoFile(std::vector &fileContents); - void ParseBuildInfo(std::vector &fileContents); - void ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject); + std::optional ParseBuildInfo(std::vector &fileContents); + std::optional ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject); bool ApproxBuildNumber(const SysUtils::ModuleInfo &engineModule); bool FindBuildNumberFunc(const SysUtils::ModuleInfo &engineModule); - int FindActualInfoEntry(); + std::optional FindActualInfoEntry(); - int m_iBuildNumber = -1; - int m_iActualEntryIndex = -1; + uint32_t m_iBuildNumber = -1; + bool m_infoEntryGameSpecific = false; pfnGetBuildNumber_t m_pfnGetBuildNumber = nullptr; - std::vector m_InfoEntries; + std::string m_initErrorMessage; + std::optional m_iActualEntryIndex = std::nullopt; + std::vector m_GameInfoEntries; + std::vector m_EngineInfoEntries; std::vector m_BuildNumberSignatures; }; diff --git a/sources/library/client_module.cpp b/sources/library/client_module.cpp index d9e298c..7372f69 100644 --- a/sources/library/client_module.cpp +++ b/sources/library/client_module.cpp @@ -39,23 +39,25 @@ bool CClientModule::FindHandle() bool CClientModule::FindEngfuncs(const CBuildInfo &buildInfo) { - uint8_t *moduleEndAddr; - uint8_t *moduleAddr; uint8_t *pfnSPR_Load; uint8_t *pfnSPR_Frames; - const CBuildInfo::Entry &buildInfoEntry = buildInfo.GetInfoEntry(); - - moduleAddr = g_EngineModule.GetAddress(); - moduleEndAddr = moduleAddr + g_EngineModule.GetSize(); - - // obtain address directly without searching - if (buildInfoEntry.HasClientEngfuncsOffset()) { - g_pClientEngfuncs = (cl_enginefunc_t *)(moduleAddr + buildInfoEntry.GetClientEngfuncsOffset()); - return true; - } + uint8_t *moduleAddr = g_EngineModule.GetAddress(); + uint8_t *moduleEndAddr = moduleAddr + g_EngineModule.GetSize(); + const CBuildInfo::Entry *buildInfoEntry = buildInfo.GetInfoEntry(); if (!g_EngineModule.GetFunctionsFromAPI(&pfnSPR_Load, &pfnSPR_Frames)) { + if (!buildInfoEntry) { + std::string errorMsg; + Utils::Snprintf(errorMsg, "build info parsing error: %s\n", buildInfo.GetInitErrorDescription().c_str()); + EXCEPT(errorMsg); + } + // obtain address directly without searching + if (buildInfoEntry->HasClientEngfuncsOffset()) { + g_pClientEngfuncs = (cl_enginefunc_t *)(moduleAddr + buildInfoEntry->GetClientEngfuncsOffset()); + return true; + } + pfnSPR_Load = static_cast(buildInfo.FindFunctionAddress( CBuildInfo::FunctionType::SPR_Load, moduleAddr, moduleEndAddr )); diff --git a/sources/library/server_module.cpp b/sources/library/server_module.cpp index 04c0bf1..5d7e2eb 100644 --- a/sources/library/server_module.cpp +++ b/sources/library/server_module.cpp @@ -44,16 +44,18 @@ bool CServerModule::FindEngfuncs(const CBuildInfo &buildInfo) uint8_t *probeAddr; uint8_t *coincidenceAddr; uint8_t *scanStartAddr; - uint8_t *moduleEndAddr; - uint8_t *moduleAddr; const size_t pointerSize = sizeof(void *); - moduleAddr = g_EngineModule.GetAddress(); - moduleEndAddr = moduleAddr + g_EngineModule.GetSize(); + uint8_t *moduleAddr = g_EngineModule.GetAddress(); + uint8_t *moduleEndAddr = moduleAddr + g_EngineModule.GetSize(); + const CBuildInfo::Entry *buildInfoEntry = buildInfo.GetInfoEntry(); - const CBuildInfo::Entry &buildInfoEntry = buildInfo.GetInfoEntry(); - if (buildInfoEntry.HasServerEngfuncsOffset()) { - g_pServerEngfuncs = (enginefuncs_t *)(moduleAddr + buildInfoEntry.GetServerEngfuncsOffset()); + if (!buildInfoEntry) { + return false; + } + + if (buildInfoEntry->HasServerEngfuncsOffset()) { + g_pServerEngfuncs = (enginefuncs_t *)(moduleAddr + buildInfoEntry->GetServerEngfuncsOffset()); return true; }