Skip to content

Commit

Permalink
Library: heavily refactored build info code
Browse files Browse the repository at this point in the history
  • Loading branch information
SNMetamorph committed Nov 3, 2023
1 parent 6d6fb49 commit c7b391d
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 74 deletions.
55 changes: 45 additions & 10 deletions sources/library/build_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,49 @@ CBuildInfo::~CBuildInfo()
void CBuildInfo::Initialize(const SysUtils::ModuleInfo &engineModule)
{
std::vector<uint8_t> 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();

Expand All @@ -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();
}
}
4 changes: 3 additions & 1 deletion sources/library/build_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ GNU General Public License for more details.
#pragma once
#include "sys_utils.h"
#include <memory>
#include <string>

class CBuildInfo
{
Expand All @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions sources/library/build_info_entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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;
Expand Down
81 changes: 47 additions & 34 deletions sources/library/build_info_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ GNU General Public License for more details.
#include <fstream>
#include <filesystem>

int CBuildInfo::Impl::GetBuildNumber() const
std::optional<uint32_t> 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
Expand Down Expand Up @@ -125,19 +125,19 @@ bool CBuildInfo::Impl::LoadBuildInfoFile(std::vector<uint8_t> &fileContents)
}
}

void CBuildInfo::Impl::ParseBuildInfo(std::vector<uint8_t> &fileContents)
std::optional<std::string> CBuildInfo::Impl::ParseBuildInfo(std::vector<uint8_t> &fileContents)
{
rapidjson::Document doc;
doc.Parse(reinterpret_cast<const char *>(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"];
Expand All @@ -149,8 +149,11 @@ void CBuildInfo::Impl::ParseBuildInfo(std::vector<uint8_t> &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"))
Expand All @@ -162,18 +165,22 @@ void CBuildInfo::Impl::ParseBuildInfo(std::vector<uint8_t> &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<std::string> CBuildInfo::Impl::ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject)
{
if (jsonObject.HasMember("number")) {
destEntry.SetBuildNumber(jsonObject["number"].GetInt());
Expand All @@ -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)
Expand Down Expand Up @@ -253,42 +262,46 @@ bool CBuildInfo::Impl::FindBuildNumberFunc(const SysUtils::ModuleInfo &engineMod
return false;
}

int CBuildInfo::Impl::FindActualInfoEntry()
std::optional<size_t> 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
}
}
19 changes: 12 additions & 7 deletions sources/library/build_info_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,32 @@ GNU General Public License for more details.
#include "memory_pattern.h"
#include <rapidjson/rapidjson.h>
#include <rapidjson/document.h>
#include <string>
#include <vector>
#include <optional>

class CBuildInfo::Impl
{
public:
typedef int(__cdecl *pfnGetBuildNumber_t)();

int GetBuildNumber() const;
std::optional<uint32_t> GetBuildNumber() const;
int DateToBuildNumber(const char *date) const;
const char *FindDateString(uint8_t *startAddr, int maxLen) const;

bool LoadBuildInfoFile(std::vector<uint8_t> &fileContents);
void ParseBuildInfo(std::vector<uint8_t> &fileContents);
void ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject);
std::optional<std::string> ParseBuildInfo(std::vector<uint8_t> &fileContents);
std::optional<std::string> ParseBuildInfoEntry(CBuildInfo::Entry &destEntry, const rapidjson::Value &jsonObject);
bool ApproxBuildNumber(const SysUtils::ModuleInfo &engineModule);
bool FindBuildNumberFunc(const SysUtils::ModuleInfo &engineModule);
int FindActualInfoEntry();
std::optional<size_t> 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<CBuildInfo::Entry> m_InfoEntries;
std::string m_initErrorMessage;
std::optional<size_t> m_iActualEntryIndex = std::nullopt;
std::vector<CBuildInfo::Entry> m_GameInfoEntries;
std::vector<CBuildInfo::Entry> m_EngineInfoEntries;
std::vector<CMemoryPattern> m_BuildNumberSignatures;
};
26 changes: 14 additions & 12 deletions sources/library/client_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t *>(buildInfo.FindFunctionAddress(
CBuildInfo::FunctionType::SPR_Load, moduleAddr, moduleEndAddr
));
Expand Down
16 changes: 9 additions & 7 deletions sources/library/server_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit c7b391d

Please sign in to comment.