From 4b2ba10300f878470187854786f2d16fbe43c0bd Mon Sep 17 00:00:00 2001 From: Arkshine Date: Tue, 23 Jun 2015 19:39:01 +0200 Subject: [PATCH] Gameconfig: Add game configs parser --- amxmodx/AMBuilder | 2 + amxmodx/CGameConfigs.cpp | 894 ++++++++++++++++++++++ amxmodx/CGameConfigs.h | 167 ++++ amxmodx/CLibrarySys.cpp | 26 + amxmodx/CLibrarySys.h | 11 + amxmodx/msvc12/amxmodx_mm.vcxproj | 4 + amxmodx/msvc12/amxmodx_mm.vcxproj.filters | 18 +- public/IGameConfigs.h | 139 ++++ public/memtools/MemoryUtils.cpp | 9 +- 9 files changed, 1266 insertions(+), 4 deletions(-) create mode 100644 amxmodx/CGameConfigs.cpp create mode 100644 amxmodx/CGameConfigs.h create mode 100644 public/IGameConfigs.h diff --git a/amxmodx/AMBuilder b/amxmodx/AMBuilder index 6f1bd3b0..53075490 100644 --- a/amxmodx/AMBuilder +++ b/amxmodx/AMBuilder @@ -91,9 +91,11 @@ binary.sources = [ 'textparse.cpp', 'CvarManager.cpp', 'cvars.cpp', + '../public/memtools/MemoryUtils.cpp', '../public/memtools/CDetour/detours.cpp', '../public/memtools/CDetour/asm/asm.c', 'CLibrarySys.cpp', + 'CGameConfigs.cpp', ] if builder.target_platform == 'windows': diff --git a/amxmodx/CGameConfigs.cpp b/amxmodx/CGameConfigs.cpp new file mode 100644 index 00000000..2b0e9421 --- /dev/null +++ b/amxmodx/CGameConfigs.cpp @@ -0,0 +1,894 @@ +// vim: set ts=4 sw=4 tw=99 noet: +// +// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). +// Copyright (C) The AMX Mod X Development Team. +// +// This software is licensed under the GNU General Public License, version 3 or higher. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://alliedmods.net/amxmodx-license + +#include "CGameConfigs.h" +#include +#include + +CGameConfigManager ConfigManager; +static CGameMasterReader MasterReader; + +// +// GAME CONFIG +// + +enum +{ + PSTATE_NONE, + PSTATE_GAMES, + PSTATE_GAMEDEFS, + PSTATE_GAMEDEFS_OFFSETS, + PSTATE_GAMEDEFS_OFFSETS_OFFSET, + PSTATE_GAMEDEFS_KEYS, + PSTATE_GAMEDEFS_SUPPORTED, + PSTATE_GAMEDEFS_SIGNATURES, + PSTATE_GAMEDEFS_SIGNATURES_SIG, + PSTATE_GAMEDEFS_CUSTOM, + PSTATE_GAMEDEFS_ADDRESSES, + PSTATE_GAMEDEFS_ADDRESSES_ADDRESS, + PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ, +}; + +struct TempSigInfo +{ + void Reset() + { + library[0] = '\0'; + signature[0] = '\0'; + } + + char signature[1024]; + char library[64]; + +} TempSig; + +static char ParseEngine[32]; + +static bool DoesGameMatch(const char *value) +{ + return g_mod_name.compare(value) == 0; +} + +static bool DoesEngineMatch(const char* value) +{ + return strcmp(ParseEngine, value) == 0; +} + +CGameConfig::CGameConfig(const char *path) +{ + strncopy(m_File, path, sizeof(m_File)); + strncopy(ParseEngine, IS_DEDICATED_SERVER() ? "engine_ds" : "engine_ls", sizeof(ParseEngine)); + + m_CustomLevel = 0; + m_CustomHandler = nullptr; +} + +CGameConfig::~CGameConfig() +{ + ConfigManager.RemoveCachedConfig(this); +} + +SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name) +{ + if (m_IgnoreLevel) + { + m_IgnoreLevel++; + return SMCResult_Continue; + } + + switch (m_ParseState) + { + case PSTATE_NONE: + { + if (strcmp(name, "Games") == 0) + { + m_ParseState = PSTATE_GAMES; + } + else + { + m_IgnoreLevel++; + } + break; + } + case PSTATE_GAMES: + { + if (strcmp(name, "*") == 0 || strcmp(name, "#default") == 0 || DoesGameMatch(name)) + { + m_ShouldBeReadingDefault = true; + strncopy(m_Game, name, sizeof(m_Game)); + + m_ParseState = PSTATE_GAMEDEFS; + } + else + { + m_IgnoreLevel++; + } + break; + } + case PSTATE_GAMEDEFS: + { + if (strcmp(name, "Offsets") == 0) + { + m_ParseState = PSTATE_GAMEDEFS_OFFSETS; + } + else if (strcmp(name, "Keys") == 0) + { + m_ParseState = PSTATE_GAMEDEFS_KEYS; + } + else if (strcmp(name, "#supported") == 0 && strcmp(m_Game, "#default") == 0) + { + m_ParseState = PSTATE_GAMEDEFS_SUPPORTED; + + m_ShouldBeReadingDefault = false; + m_HadGame = false; + m_MatchedGame = false; + m_HadEngine = false; + m_MatchedEngine = false; + } + else if (strcmp(name, "Signatures") == 0) + { + m_ParseState = PSTATE_GAMEDEFS_SIGNATURES; + } + else if (strcmp(name, "Addresses") == 0) + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES; + } + else + { + if (ConfigManager.m_customHandlers.retrieve(name, &m_CustomHandler)) + { + m_CustomLevel = 0; + m_ParseState = PSTATE_GAMEDEFS_CUSTOM; + m_CustomHandler->ReadSMC_ParseStart(); + break; + } + + m_IgnoreLevel++; + } + break; + } + case PSTATE_GAMEDEFS_OFFSETS: + { + m_Class[0] = '\0'; + + strncopy(m_Offset, name, sizeof(m_Offset)); + + m_ParseState = PSTATE_GAMEDEFS_OFFSETS_OFFSET; + m_MatchedPlatform = false; + break; + } + case PSTATE_GAMEDEFS_SIGNATURES: + { + strncopy(m_Offset, name, sizeof(m_Offset)); + TempSig.Reset(); + + m_ParseState = PSTATE_GAMEDEFS_SIGNATURES_SIG; + m_MatchedPlatform = false; + break; + } + case PSTATE_GAMEDEFS_CUSTOM: + { + m_CustomLevel++; + return m_CustomHandler->ReadSMC_NewSection(states, name); + break; + } + case PSTATE_GAMEDEFS_ADDRESSES: + { + m_Address[0] = '\0'; + m_AddressSignature[0] = '\0'; + m_AddressReadCount = 0; + + strncopy(m_Address, name, sizeof(m_Address)); + + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS; + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS: + { + if (g_LibSys.DoesPlatformMatch(name)) + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ; + } + else + { + if (strcmp(name, PLATFORM_LINUX_NAME) != 0 && strcmp(name, PLATFORM_WINDOWNS_NAME) != 0 && strcmp(name, PLATFORM_MAC_NAME) != 0) + { + AMXXLOG_Error("Error while parsing Address section for \"%s\" (%s):", m_Address, m_CurrentPath); + AMXXLOG_Error("Unrecognized platform \"%s\"", name); + } + + m_IgnoreLevel = 1; + } + break; + } + default: + { + m_IgnoreLevel++; + break; + } + } + + return SMCResult_Continue; +} + +SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) +{ + if (m_IgnoreLevel) + { + return SMCResult_Continue; + } + + switch (m_ParseState) + { + case PSTATE_GAMEDEFS_OFFSETS_OFFSET: + { + if (strcmp(key, "class") == 0) + { + strncopy(m_Class, value, sizeof(m_Class)); + } + else if (g_LibSys.IsPlatformCompatible(key, &m_MatchedPlatform)) + { + if (m_Class[0]) + { + auto ic = m_OffsetsByClass.findForAdd(m_Class); + + if (ic.found()) + { + ic->value->list.replace(m_Offset, atoi(value)); + } + else if (m_OffsetsByClass.add(ic, m_Class)) + { + ic->value = new OffsetClass; + ic->value->list.insert(m_Offset, atoi(value)); + } + } + else + { + m_Offsets.replace(m_Offset, atoi(value)); + } + } + break; + } + case PSTATE_GAMEDEFS_KEYS: + { + ke::AString vstr(value); + m_Keys.replace(key, ke::Move(vstr)); + break; + } + case PSTATE_GAMEDEFS_SUPPORTED: + { + if (strcmp(key, "game") == 0) + { + m_HadGame = true; + + if (DoesGameMatch(value)) + { + m_MatchedGame = true; + } + + if ((!m_HadEngine && m_MatchedGame) || (m_MatchedEngine && m_MatchedGame)) + { + m_ShouldBeReadingDefault = true; + } + } + else if (strcmp(key, "engine") == 0) + { + m_HadEngine = true; + + if (DoesEngineMatch(value)) + { + m_MatchedEngine = true; + } + + if ((!m_HadGame && m_MatchedEngine) || (m_MatchedGame && m_MatchedEngine)) + { + m_ShouldBeReadingDefault = true; + } + } + break; + } + case PSTATE_GAMEDEFS_SIGNATURES_SIG: + { + if (g_LibSys.IsPlatformCompatible(key, &m_MatchedPlatform)) + { + strncopy(TempSig.signature, value, sizeof(TempSig.signature)); + } + else if (strcmp(key, "library") == 0) + { + strncopy(TempSig.library, value, sizeof(TempSig.library)); + } + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS: + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ: + { + if (strcmp(key, "read") == 0) + { + int limit = sizeof(m_AddressRead) / sizeof(m_AddressRead[0]); + + if (m_AddressReadCount < limit) + { + m_AddressRead[m_AddressReadCount] = atoi(value); + m_AddressReadCount++; + } + else + { + AMXXLOG_Error("[SM] Error parsing Address \"%s\", does not support more than %d read offsets (gameconf \"%s\")", + m_Address, limit, m_CurrentPath); + } + } + else if (strcmp(key, "signature") == 0) + { + strncopy(m_AddressSignature, value, sizeof(m_AddressSignature)); + } + break; + } + case PSTATE_GAMEDEFS_CUSTOM: + { + return m_CustomHandler->ReadSMC_KeyValue(states, key, value); + } + } + + return SMCResult_Continue; +} + +SMCResult CGameConfig::ReadSMC_LeavingSection(const SMCStates *states) +{ + if (m_IgnoreLevel) + { + m_IgnoreLevel--; + return SMCResult_Continue; + } + + if (m_CustomLevel) + { + m_CustomLevel--; + m_CustomHandler->ReadSMC_LeavingSection(states); + return SMCResult_Continue; + } + + switch (m_ParseState) + { + case PSTATE_GAMES: + { + m_ParseState = PSTATE_NONE; + break; + } + case PSTATE_GAMEDEFS: + { + m_ParseState = PSTATE_GAMES; + break; + } + case PSTATE_GAMEDEFS_CUSTOM: + { + m_ParseState = PSTATE_GAMEDEFS; + m_CustomHandler->ReadSMC_ParseEnd(false, false); + break; + } + case PSTATE_GAMEDEFS_KEYS: + case PSTATE_GAMEDEFS_OFFSETS: + { + m_ParseState = PSTATE_GAMEDEFS; + break; + } + case PSTATE_GAMEDEFS_OFFSETS_OFFSET: + { + m_ParseState = PSTATE_GAMEDEFS_OFFSETS; + break; + } + case PSTATE_GAMEDEFS_SUPPORTED: + { + if (!m_ShouldBeReadingDefault) + { + m_IgnoreLevel = 1; + m_ParseState = PSTATE_GAMES; + } + else + { + m_ParseState = PSTATE_GAMEDEFS; + } + break; + } + case PSTATE_GAMEDEFS_SIGNATURES: + { + m_ParseState = PSTATE_GAMEDEFS; + break; + } + case PSTATE_GAMEDEFS_SIGNATURES_SIG: + { + if (TempSig.library[0] == '\0') + { + strncopy(TempSig.library, "server", sizeof(TempSig.library)); + } + + void *addressInBase = nullptr; + + if (strcmp(TempSig.library, "server") == 0) + { + addressInBase = reinterpret_cast(MDLL_Spawn); + } + else if (strcmp(TempSig.library, "engine") == 0) + { + addressInBase = reinterpret_cast(gpGlobals); + } + + void *finalAddress = nullptr; + + if (!addressInBase) + { + AMXXLOG_Error("Unrecognized library \"%s\" (gameconf \"%s\")", TempSig.library, m_CurrentPath); + } + else if (TempSig.signature[0]) + { + if (TempSig.signature[0] == '@') + { +#if defined PLATFORM_WINDOWS + MEMORY_BASIC_INFORMATION mem; + + if (VirtualQuery(addressInBase, &mem, sizeof(mem))) + { + finalAddress = g_MemUtils.ResolveSymbol(mem.AllocationBase, &TempSig.signature[1]); + } + else + { + AMXXLOG_Error("Unable to find library \"%s\" in memory (gameconf \"%s\")", TempSig.library, m_File); + } + +#elif defined PLATFORM_POSIX + Dl_info info; + + if (dladdr(addressInBase, &info) != 0) + { + void *handle = dlopen(info.dli_fname, RTLD_NOW); + + if (handle) + { + finalAddress = g_MemUtils.ResolveSymbol(handle, &TempSig.signature[1]); + dlclose(handle); + } + else + { + AMXXLOG_Error("Unable to load library \"%s\" (gameconf \"%s\")", TempSig.library, m_File); + } + } + else + { + AMXXLOG_Error("Unable to find library \"%s\" in memory (gameconf \"%s\")", TempSig.library, m_File); + } +#endif + } + + if (!finalAddress) + { + finalAddress = g_MemUtils.DecodeAndFindPattern(addressInBase, TempSig.signature); + } + + m_Sigs.replace(m_Offset, finalAddress); + } + + m_ParseState = PSTATE_GAMEDEFS_SIGNATURES; + break; + } + case PSTATE_GAMEDEFS_ADDRESSES: + { + m_ParseState = PSTATE_GAMEDEFS; + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS: + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES; + + if (m_Address[0] != '\0' && m_AddressSignature[0] != '\0') + { + AddressConf addrConf(m_AddressSignature, sizeof(m_AddressSignature), m_AddressReadCount, m_AddressRead); + m_Addresses.replace(m_Address, addrConf); + } + + break; + } + case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ: + { + m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS; + break; + } + } + + return SMCResult_Continue; +} + +bool CGameConfig::Reparse(char *error, size_t maxlength) +{ + m_Offsets.clear(); + m_OffsetsByClass.clear(); + m_Keys.clear(); + m_Addresses.clear(); + + char path[PLATFORM_MAX_PATH]; + const char *dataDir = get_localinfo("amxx_datadir", "addons/amxmodx/data"); + + build_pathname_r(path, sizeof(path), "%s/gamedata/%s/master.games.txt", dataDir, m_File); + + if (!g_LibSys.PathExists(path)) + { + return false; + } + + SMCError err; + SMCStates state = { 0, 0 }; + + ke::Vector fileList; + MasterReader.m_FileList = &fileList; + + err = textparsers->ParseSMCFile(path, &MasterReader, &state, error, maxlength); + + if (err != SMCError_Okay) + { + const char *msg = textparsers->GetSMCErrorString(err); + + AMXXLOG_Error("Error parsing master gameconf file \"%s\":", path); + AMXXLOG_Error("Error %d on line %d, col %d: %s", err, state.line, state.col, msg ? msg : "Unknown error"); + + return false; + } + + for (size_t i = 0; i < fileList.length(); ++i) + { + g_LibSys.PathFormat(path, sizeof(path), "%s/%s", m_File, fileList[i].chars()); + + if (!EnterFile(path, error, maxlength)) + { + return false; + } + } + + build_pathname_r(path, sizeof(path), "%s/gamedata/%s/custom", dataDir, m_File); + CDirectory *customDir = g_LibSys.OpenDirectory(path); + + if (!customDir) + { + return true; + } + + while (customDir->MoreFiles()) + { + if (!customDir->IsEntryFile()) + { + customDir->NextEntry(); + continue; + } + + const char *currentFile = customDir->GetEntryName(); + + size_t length = strlen(currentFile); + + if (length > 4 && strcmp(¤tFile[length - 4], ".txt") != 0) + { + customDir->NextEntry(); + continue; + } + + g_LibSys.PathFormat(path, sizeof(path), "%s/custom/%s", m_File, currentFile); + + if (!EnterFile(path, error, maxlength)) + { + g_LibSys.CloseDirectory(customDir); + return false; + } + + customDir->NextEntry(); + } + + g_LibSys.CloseDirectory(customDir); + + return true; +} + +bool CGameConfig::EnterFile(const char *file, char *error, size_t maxlength) +{ + char path[PLATFORM_MAX_PATH]; + build_pathname_r(path, sizeof(path), "%s/gamedata/%s", get_localinfo("amxx_datadir", "addons/amxmodx/data"), file); + + strncopy(m_CurrentPath, path, sizeof(m_CurrentPath)); + + m_IgnoreLevel = 0; + m_ShouldBeReadingDefault = true; + m_ParseState = PSTATE_NONE; + + SMCError err; + SMCStates state = { 0, 0 }; + + if ((err = textparsers->ParseSMCFile(m_CurrentPath, this, &state, error, maxlength)) != SMCError_Okay) + { + const char *msg = textparsers->GetSMCErrorString(err); + + AMXXLOG_Error("Error parsing gameconfig file \"%s\":", m_CurrentPath); + AMXXLOG_Error("Error %d on line %d, col %d: %s", err, state.line, state.col, msg ? msg : "Unknown error"); + + if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM) + { + m_CustomHandler->ReadSMC_ParseEnd(true, true); + m_CustomHandler = nullptr; + m_CustomLevel = 0; + } + + return false; + } + + return true; +} + +bool CGameConfig::GetOffset(const char *key, int *value) +{ + return m_Offsets.retrieve(key, value); +} + +bool CGameConfig::GetOffsetByClass(const char *classname, const char *key, int *value) +{ + auto r = m_OffsetsByClass.find(classname); + + if (!r.found()) + { + return false; + } + + return r->value->list.retrieve(key, value); +} + +const char *CGameConfig::GetKeyValue(const char *key) +{ + auto r = m_Keys.find(key); + + if (!r.found()) + { + return nullptr; + } + + return r->value.chars(); +} + +//memory addresses below 0x10000 are automatically considered invalid for dereferencing +#define VALID_MINIMUM_MEMORY_ADDRESS 0x10000 + +bool CGameConfig::GetAddress(const char *key, void **retaddr) +{ + auto r = m_Addresses.find(key); + + if (!r.found()) + { + *retaddr = nullptr; + return false; + } + + AddressConf &addrConf = r->value; + + void *address; + + if (!GetMemSig(addrConf.m_SignatureName, &address)) + { + *retaddr = nullptr; + return false; + } + + for (size_t i = 0; i < addrConf.m_ReadCount; ++i) + { + int offset = addrConf.m_ReadBytes[i]; + + if (!address || reinterpret_cast(address) < VALID_MINIMUM_MEMORY_ADDRESS) + { + *retaddr = nullptr; + return false; + } + address = *(reinterpret_cast(reinterpret_cast(address) + offset)); + } + + *retaddr = address; + + return true; +} + +CGameConfig::AddressConf::AddressConf(const char *sigName, size_t sigLength, size_t readCount, int *read) +{ + size_t limit = sizeof(m_ReadBytes) / sizeof(m_ReadBytes[0]); + size_t readLimit = (readCount <= limit) ? readCount : limit; + + strncopy(m_SignatureName, sigName, sizeof(m_SignatureName)); + + m_ReadCount = readLimit; + memcpy(&this->m_ReadBytes[0], read, sizeof(this->m_ReadBytes[0]) * readLimit); +} + +bool CGameConfig::GetMemSig(const char *key, void **addr) +{ + return m_Sigs.retrieve(key, addr); +} + + +// +// CONFIG MASTER READER +// + +#define MSTATE_NONE 0 +#define MSTATE_MAIN 1 +#define MSTATE_FILE 2 + +void CGameMasterReader::ReadSMC_ParseStart() +{ + m_State = MSTATE_NONE; + m_IgnoreLevel = 0; +} + +SMCResult CGameMasterReader::ReadSMC_NewSection(const SMCStates *states, const char *name) +{ + if (m_IgnoreLevel) + { + return SMCResult_Continue; + } + + if (m_State == MSTATE_NONE) + { + if (strcmp(name, "Game Master") == 0) + { + m_State = MSTATE_MAIN; + } + else + { + m_IgnoreLevel++; + } + } + else if (m_State == MSTATE_MAIN) + { + strncopy(m_CurrentPath, name, sizeof(m_CurrentPath)); + + m_HadEngine = false; + m_MatchedEngine = false; + m_HadGame = false; + m_MatchedGame = false; + + m_State = MSTATE_FILE; + } + else if (m_State == MSTATE_FILE) + { + m_IgnoreLevel++; + } + + return SMCResult_Continue; +} + +SMCResult CGameMasterReader::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) +{ + if (m_IgnoreLevel || m_State != MSTATE_FILE) + { + return SMCResult_Continue; + } + + if (strcmp(key, "engine") == 0) + { + m_HadEngine = true; + + if (DoesEngineMatch(value)) + { + m_MatchedEngine = true; + } + } + else if (strcmp(key, "game") == 0) + { + m_HadGame = true; + + if (DoesGameMatch(value)) + { + m_MatchedGame = true; + } + } + + return SMCResult_Continue; +} + +SMCResult CGameMasterReader::ReadSMC_LeavingSection(const SMCStates *states) +{ + if (m_IgnoreLevel) + { + m_IgnoreLevel--; + return SMCResult_Continue; + } + + if (m_State == MSTATE_FILE) + { + // The four success conditions: + // 1. Needed nothing. + // 2. Needed game only. + // 3. Needed engine only. + // 4. Needed both engine and game. + // Final result is minimized via k-map. + + if ((!m_HadEngine && !m_HadGame) || + (!m_HadEngine && m_MatchedGame) || + (!m_HadGame && m_MatchedEngine) || + (m_MatchedEngine && m_MatchedEngine)) + { + m_FileList->append(m_CurrentPath); + } + + m_State = MSTATE_MAIN; + } + else if (m_State == MSTATE_MAIN) + { + m_State = MSTATE_NONE; + } + + return SMCResult_Continue; +} + + +// +// CONFIG MANAGER +// + +CGameConfigManager::CGameConfigManager() +{ +} + +CGameConfigManager::~CGameConfigManager() +{ +} + +bool CGameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **config, char *error, size_t maxlength) +{ + CGameConfig *configFromCache; + + if (m_Lookup.retrieve(file, &configFromCache)) + { + configFromCache->AddRef(); + *config = configFromCache; + + return true; + } + + configFromCache = new CGameConfig(file); + configFromCache->AddRef(); + + bool returnValue = returnValue = configFromCache->Reparse(error, maxlength); + + m_Lookup.insert(file, configFromCache); + *config = configFromCache; + + return returnValue; +} + +void CGameConfigManager::CloseGameConfigFile(IGameConfig *config) +{ + CGameConfig *currentConfig = static_cast(config); + currentConfig->Release(); +} + +void CGameConfigManager::AddUserConfigHook(const char *sectionName, ITextListener_SMC *listener) +{ + m_customHandlers.insert(sectionName, listener); +} + +void CGameConfigManager::RemoveUserConfigHook(const char *sectionName, ITextListener_SMC *listener) +{ + ITextListener_SMC *listenerFromCache; + + if (!m_customHandlers.retrieve(sectionName, &listenerFromCache)) + { + return; + } + + if (listenerFromCache != listener) + { + return; + } + + m_customHandlers.remove(sectionName); +} + +void CGameConfigManager::RemoveCachedConfig(CGameConfig *config) +{ + m_Lookup.remove(config->m_File); +} diff --git a/amxmodx/CGameConfigs.h b/amxmodx/CGameConfigs.h new file mode 100644 index 00000000..01ab72db --- /dev/null +++ b/amxmodx/CGameConfigs.h @@ -0,0 +1,167 @@ +// vim: set ts=4 sw=4 tw=99 noet: +// +// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). +// Copyright (C) The AMX Mod X Development Team. +// +// This software is licensed under the GNU General Public License, version 3 or higher. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://alliedmods.net/amxmodx-license + +#ifndef _INCLUDE_GAMECONFIG_H_ +#define _INCLUDE_GAMECONFIG_H_ + +#include +#include "CLibrarySys.h" +#include +#include +#include +#include +#include + +class CGameConfig + : + public ITextListener_SMC, + public IGameConfig, + public ke::Refcounted +{ + friend class CGameConfigManager; + + public: + + CGameConfig(const char *file); + ~CGameConfig(); + + public: + + bool Reparse(char *error, size_t maxlength); + bool EnterFile(const char *file, char *error, size_t maxlength); + + public: // ITextListener_SMC + + SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name); + SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value); + SMCResult ReadSMC_LeavingSection(const SMCStates *states); + + public: // IGameConfig + + const char* GetKeyValue(const char *key); + bool GetOffset(const char *key, int *value); + bool GetOffsetByClass(const char *classname, const char *key, int *value); + bool GetMemSig(const char *key, void **addr); + bool GetAddress(const char *key, void **addr); + + public: // NameHashSet + + static inline bool matches(const char *key, const CGameConfig *value) + { + return strcmp(key, value->m_File) == 0; + } + + private: + + struct OffsetClass + { + StringHashMap list; + }; + + typedef StringHashMap> OffsetClassMap; + + char m_File[PLATFORM_MAX_PATH]; + char m_CurrentPath[PLATFORM_MAX_PATH]; + + StringHashMap m_Offsets; + OffsetClassMap m_OffsetsByClass; + StringHashMap m_Keys; + StringHashMap m_Sigs; + + int m_ParseState; + unsigned int m_IgnoreLevel; + + char m_Class[64]; + char m_Offset[64]; + char m_Game[256]; + + bool m_ShouldBeReadingDefault; + bool m_HadGame; + bool m_MatchedGame; + bool m_HadEngine; + bool m_MatchedEngine; + bool m_MatchedPlatform; + + unsigned int m_CustomLevel; + ITextListener_SMC* m_CustomHandler; + + struct AddressConf + { + char m_SignatureName[64]; + size_t m_ReadCount; + int m_ReadBytes[8]; + + AddressConf(const char *sigName, size_t sigLength, size_t readCount, int *read); + AddressConf() {} + }; + + char m_Address[64]; + char m_AddressSignature[64]; + int m_AddressReadCount; + int m_AddressRead[8]; + StringHashMap m_Addresses; + + char m_pEngine[64]; +}; + +class CGameMasterReader : public ITextListener_SMC +{ + public: + + void ReadSMC_ParseStart(); + + SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name); + SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value); + SMCResult ReadSMC_LeavingSection(const SMCStates *states); + + public: + + ke::Vector* m_FileList; + + unsigned int m_State; + unsigned int m_IgnoreLevel; + + char m_CurrentPath[PLATFORM_MAX_PATH]; + + bool m_HadEngine; + bool m_MatchedEngine; + bool m_HadGame; + bool m_MatchedGame; +}; + +class CGameConfigManager : public IGameConfigManager +{ + public: + + CGameConfigManager(); + ~CGameConfigManager(); + + public: // IGameConfigManager + + bool LoadGameConfigFile(const char *file, IGameConfig **pConfig, char *error, size_t maxlength); + void CloseGameConfigFile(IGameConfig *cfg); + void AddUserConfigHook(const char *sectionname, ITextListener_SMC *listener); + void RemoveUserConfigHook(const char *sectionname, ITextListener_SMC *listener); + + public: + + void RemoveCachedConfig(CGameConfig *config); + + private: + + NameHashSet m_Lookup; + + public: + + StringHashMap m_customHandlers; +}; + +extern CGameConfigManager ConfigManager; + +#endif // _INCLUDE_GAMECONFIG_H_ diff --git a/amxmodx/CLibrarySys.cpp b/amxmodx/CLibrarySys.cpp index 689b789d..fca84a3d 100644 --- a/amxmodx/CLibrarySys.cpp +++ b/amxmodx/CLibrarySys.cpp @@ -530,3 +530,29 @@ bool LibrarySystem::FileTime(const char* path, FileTimeType type, time_t* pTime) return true; } + +bool LibrarySystem::DoesPlatformMatch(const char *platform) +{ + return strcmp(platform, PLATFORM_NAME) == 0; +} + +bool LibrarySystem::IsPlatformCompatible(const char *platform, bool *hadPrimaryMatch) +{ + if (DoesPlatformMatch(platform)) + { +#if defined PLATFORM_COMPAT_ALT + *hadPrimaryMatch = true; +#endif + return true; + } + +#if defined PLATFORM_COMPAT_ALT + /* If entry hasn't been found for the primary platform name, check for compatible alternate */ + if (!*hadPrimaryMatch) + { + return strcmp(platform, PLATFORM_COMPAT_ALT) == 0; + } +#endif + + return false; +} \ No newline at end of file diff --git a/amxmodx/CLibrarySys.h b/amxmodx/CLibrarySys.h index d57063b4..935cac6c 100644 --- a/amxmodx/CLibrarySys.h +++ b/amxmodx/CLibrarySys.h @@ -14,6 +14,9 @@ #include // Interface (HLSDK) #include // AutoPtr +#define PLATFORM_WINDOWNS_NAME "windows" +#define PLATFORM_LINUX_NAME "linux" +#define PLATFORM_MAC_NAME "mac" #if defined(WIN32) # ifndef PLATFORM_WINDOWS # define PLATFORM_WINDOWS 1 @@ -25,6 +28,7 @@ # include # include # define PLATFORM_LIB_EXT "dll" +# define PLATFORM_NAME PLATFORM_WINDOWNS_NAME # define PLATFORM_SEP_CHAR '\\' # define PLATFORM_SEP_ALTCHAR '/' # define PLATFORM_EXTERN_C extern "C" __declspec(dllexport) @@ -32,9 +36,13 @@ # if defined(__linux__) # define PLATFORM_LINUX 1 # define PLATFORM_LIB_EXT "so" +# define PLATFORM_NAME PLATFORM_LINUX_NAME +# define PLATFORM_COMPAT_ALT PLATFORM_MAC_NAME # elif defined(__APPLE__) # define PLATFORM_APPLE 1 # define PLATFORM_LIB_EXT "dylib" +# define PLATFORM_NAME PLATFORM_MAC_NAME +# define PLATFORM_COMPAT_ALT PLATFORM_LINUX_NAME # endif # ifndef PLATFORM_POSIX # define PLATFORM_POSIX 1 @@ -153,6 +161,9 @@ class LibrarySystem bool FileTime(const char* path, FileTimeType type, time_t* pTime); void GetLoaderError(char* buffer, size_t maxlength); + + bool DoesPlatformMatch(const char* platform); + bool IsPlatformCompatible(const char *platform, bool *hadPrimaryMatch); }; extern LibrarySystem g_LibSys; diff --git a/amxmodx/msvc12/amxmodx_mm.vcxproj b/amxmodx/msvc12/amxmodx_mm.vcxproj index 4cdb7d2d..bd63e4a9 100644 --- a/amxmodx/msvc12/amxmodx_mm.vcxproj +++ b/amxmodx/msvc12/amxmodx_mm.vcxproj @@ -150,6 +150,7 @@ + $(IntDir)hashing\ $(IntDir)hashing\ @@ -255,6 +256,7 @@ + @@ -307,6 +309,7 @@ + @@ -338,6 +341,7 @@ + diff --git a/amxmodx/msvc12/amxmodx_mm.vcxproj.filters b/amxmodx/msvc12/amxmodx_mm.vcxproj.filters index 7270a8b4..ef5c2f69 100644 --- a/amxmodx/msvc12/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc12/amxmodx_mm.vcxproj.filters @@ -277,11 +277,17 @@ Third Party\Hashing\hashers - Source Files - + Source Files + Source Files + + Source Files + + + Memtools + @@ -478,13 +484,19 @@ Third Party\Hashing\hashers - + Header Files Header Files + + Header Files + + + Memtools + diff --git a/public/IGameConfigs.h b/public/IGameConfigs.h new file mode 100644 index 00000000..1360e371 --- /dev/null +++ b/public/IGameConfigs.h @@ -0,0 +1,139 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_IGAMECONFIG_H_ +#define _INCLUDE_IGAMECONFIG_H_ + +#include + +/** + * @brief Describes a game private data config file + */ +class IGameConfig +{ +public: + /** + * @brief Returns an offset value. + * + * @param key Key to retrieve from the offset section. + * @param value Pointer to store the offset value in. + * @return True if found, false otherwise. + */ + virtual bool GetOffset(const char *key, int *value) = 0; + + /** + * @brief Returns an offset value from given class. + * + * @param classname class name to match from the offset section. + * @param key Key to retrieve from the offset section. + * @param value Pointer to store the offset value in. + * @return True if found, false otherwise. + */ + virtual bool GetOffsetByClass(const char *classname, const char *key, int *value) = 0; + + /** + * @brief Returns the value of a key from the "Keys" section. + * + * @param key Key to retrieve from the Keys section. + * @return String containing the value, or NULL if not found. + */ + virtual const char *GetKeyValue(const char *key) = 0; + + /** + * @brief Retrieves a cached memory signature. + * + * @param key Name of the signature. + * @param addr Pointer to store the memory address in. + * (NULL is copied if signature is not found in binary). + * @return True if the section exists and key for current + * platform was found, false otherwise. + */ + virtual bool GetMemSig(const char *key, void **addr) = 0; + + /** + * @brief Retrieves the value of an address from the "Address" section. + * + * @param key Key to retrieve from the Address section. + * @param addr Pointer to store the memory address. + * @return True on success, false on failure. + */ + virtual bool GetAddress(const char *key, void **addr) = 0; +}; + +/** + * @brief Manages game config files + */ +class IGameConfigManager +{ +public: + /** + * @brief Loads or finds an already loaded game config file. + * + * @param file File to load. The path must be relative to the + * 'gamedata' folder and the extension should be + * omitted. + * @param pConfig Pointer to store the game config pointer. Pointer + * will be valid even on failure. + * @param error Optional error message buffer. + * @param maxlength Maximum length of the error buffer. + * @return True on success, false if the file failed. + */ + virtual bool LoadGameConfigFile(const char *file, IGameConfig **pConfig, char *error, size_t maxlength) = 0; + + /** + * @brief Closes an IGameConfig pointer. Since a file can be loaded + * more than once, the file will not actually be removed from memory + * until it is closed once for each call to LoadGameConfigfile(). + * + * @param cfg Pointer to the IGameConfig to close. + */ + virtual void CloseGameConfigFile(IGameConfig *cfg) = 0; + + /** + * @brief Adds a custom gamedata section hook. + * + * @param sectionname Section name to hook. + * @param listener Listener callback. + * @noreturn + */ + virtual void AddUserConfigHook(const char *sectionname, ITextListener_SMC *listener) = 0; + + /** + * @brief Removes a custom gamedata section hook. + * + * @param sectionname Section name to unhook. + * @param listener Listener callback. + * @noreturn + */ + virtual void RemoveUserConfigHook(const char *sectionname, ITextListener_SMC *listener) = 0; +}; + +#endif //_INCLUDE_IGAMECONFIG_H_ diff --git a/public/memtools/MemoryUtils.cpp b/public/memtools/MemoryUtils.cpp index 65e38636..99ec9a94 100644 --- a/public/memtools/MemoryUtils.cpp +++ b/public/memtools/MemoryUtils.cpp @@ -28,7 +28,7 @@ */ #include "MemoryUtils.h" -#include "amxxmodule.h" +#include // sscanf #if defined(__linux__) #include @@ -159,6 +159,13 @@ void *MemoryUtils::ResolveSymbol(void *handle, const char *symbol) #elif defined(__linux__) + void *addr = dlsym(handle, symbol); + + if (addr) + { + return addr; + } + struct link_map *dlmap; struct stat dlstat; int dlfile;