sourcemod/core/HalfLife2.cpp
Oliver John Hitchcock f9ad35badf
Stop EntRefToEntIndex returning garbage if a bad parameter is passed (#1323)
* Stop EntRefToEntIndex returning garbage if a bad parameter is passed

Seen multiple bad usage of this function that works only because whatever was passed in was returned as it wasnt an entity reference.
This code should have worked and would be expected to have returned something invalid but instead the the input was returned which allowed the code to work when really it is bad code.
See for one such case https://discordapp.com/channels/335290997317697536/335290997317697536/736518488314871868

* Update documentation of EntRefToEntIndex

Added the error text saying what shall be returned when a invalid parameter is passed.

* Validate entity index instead of just returning INVALID_EHANDLE_INDEX

Not sure if it needs this much validation but this just mirrors how IsValidEntity works, so the entity index returned should be valid else INVALID_EHANDLE_INDEX is returned.

* EntRefToEntIndex improve doc comments to better represent functionality

---------

Co-authored-by: Kyle Sanderson <kyle.leet@gmail.com>
2024-04-25 23:19:04 +00:00

1593 lines
38 KiB
C++

/**
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2016 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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include "HalfLife2.h"
#include "sourcemod.h"
#include "sourcemm_api.h"
#include "UserMessages.h"
#include "PlayerManager.h"
#include "sm_stringutil.h"
#include <IGameConfigs.h>
#include <compat_wrappers.h>
#include <Logger.h>
#include <amtl/os/am-shared-library.h>
#include "logic_bridge.h"
#include <tier0/mem.h>
#include <bridge/include/ILogger.h>
#if SOURCE_ENGINE == SE_CSGO
#include <cstrike15_usermessages.pb.h>
#elif SOURCE_ENGINE == SE_BLADE
#include <berimbau_usermessages.pb.h>
#elif SOURCE_ENGINE == SE_MCV
#include <vietnam_usermessages.pb.h>
#endif
typedef ICommandLine *(*FakeGetCommandLine)();
#define TIER0_NAME FORMAT_SOURCE_BIN_NAME("tier0")
#define VSTDLIB_NAME FORMAT_SOURCE_BIN_NAME("vstdlib")
CHalfLife2 g_HL2;
ConVar *sv_lan = NULL;
static void *g_EntList = NULL;
static void **g_pEntInfoList = NULL;
static int entInfoOffset = -1;
static int utlVecOffsetOffset = -1;
static CEntInfo *EntInfoArray()
{
if (g_EntList != NULL)
{
return (CEntInfo *)((intp)g_EntList + entInfoOffset);
}
else if (g_pEntInfoList)
{
return *(CEntInfo **)g_pEntInfoList;
}
return NULL;
}
CHalfLife2::CHalfLife2()
{
m_Maps.init();
m_pGetCommandLine = NULL;
}
CHalfLife2::~CHalfLife2()
{
for (NameHashSet<DataTableInfo *>::iterator iter = m_Classes.iter(); !iter.empty(); iter.next())
delete *iter;
for (DataTableMap::iterator iter = m_Maps.iter(); !iter.empty(); iter.next())
delete iter->value;
}
#if SOURCE_ENGINE != SE_DARKMESSIAH
CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL;
#endif
void CHalfLife2::OnSourceModStartup(bool late)
{
#if SOURCE_ENGINE != SE_DARKMESSIAH
if (g_SMAPI->GetSourceEngineBuild() != SOURCE_ENGINE_ORIGINAL && !g_pSharedChangeInfo)
{
g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo();
}
#endif
}
void CHalfLife2::OnSourceModAllInitialized()
{
m_MsgTextMsg = g_UserMsgs.GetMessageIndex("TextMsg");
m_HinTextMsg = g_UserMsgs.GetMessageIndex("HintText");
m_SayTextMsg = g_UserMsgs.GetMessageIndex("SayText");
m_VGUIMenu = g_UserMsgs.GetMessageIndex("VGUIMenu");
sharesys->AddInterface(NULL, this);
}
void CHalfLife2::OnSourceModAllInitialized_Post()
{
InitLogicalEntData();
InitCommandLine();
#if SOURCE_ENGINE == SE_CSGO
m_CSGOBadList.init();
m_CSGOBadList.add("m_iItemDefinitionIndex");
m_CSGOBadList.add("m_iEntityLevel");
m_CSGOBadList.add("m_iItemIDHigh");
m_CSGOBadList.add("m_iItemIDLow");
m_CSGOBadList.add("m_iAccountID");
m_CSGOBadList.add("m_iEntityQuality");
m_CSGOBadList.add("m_bInitialized");
m_CSGOBadList.add("m_szCustomName");
m_CSGOBadList.add("m_iAttributeDefinitionIndex");
m_CSGOBadList.add("m_iRawValue32");
m_CSGOBadList.add("m_iRawInitialValue32");
m_CSGOBadList.add("m_nRefundableCurrency");
m_CSGOBadList.add("m_bSetBonus");
m_CSGOBadList.add("m_OriginalOwnerXuidLow");
m_CSGOBadList.add("m_OriginalOwnerXuidHigh");
m_CSGOBadList.add("m_nFallbackPaintKit");
m_CSGOBadList.add("m_nFallbackSeed");
m_CSGOBadList.add("m_flFallbackWear");
m_CSGOBadList.add("m_nFallbackStatTrak");
m_CSGOBadList.add("m_iCompetitiveRanking");
m_CSGOBadList.add("m_iCompetitiveRankType");
m_CSGOBadList.add("m_nActiveCoinRank");
m_CSGOBadList.add("m_nMusicID");
#endif
g_pGameConf->GetOffset("CSendPropExtra_UtlVector::m_Offset", &utlVecOffsetOffset);
}
ConfigResult CHalfLife2::OnSourceModConfigChanged(const char *key, const char *value,
ConfigSource source, char *error, size_t maxlength)
{
if (strcasecmp(key, "FollowCSGOServerGuidelines") == 0)
{
#if SOURCE_ENGINE == SE_CSGO
if (strcasecmp(value, "no") == 0)
{
m_bFollowCSGOServerGuidelines = false;
return ConfigResult_Accept;
}
else if (strcasecmp(value, "yes") == 0)
{
m_bFollowCSGOServerGuidelines = true;
return ConfigResult_Accept;
}
else
{
ke::SafeStrcpy(error, maxlength, "Invalid value: must be \"yes\" or \"no\"");
return ConfigResult_Reject;
}
#endif
}
return ConfigResult_Ignore;
}
void CHalfLife2::InitLogicalEntData()
{
#if SOURCE_ENGINE == SE_TF2 \
|| SOURCE_ENGINE == SE_DODS \
|| SOURCE_ENGINE == SE_HL2DM \
|| SOURCE_ENGINE == SE_CSS \
|| SOURCE_ENGINE == SE_SDK2013 \
|| SOURCE_ENGINE == SE_BMS \
|| SOURCE_ENGINE == SE_BLADE \
|| SOURCE_ENGINE == SE_NUCLEARDAWN \
|| SOURCE_ENGINE == SE_PVKII
if (g_SMAPI->GetServerFactory(false)("VSERVERTOOLS003", nullptr))
{
g_EntList = servertools->GetEntityList();
}
#endif
char *addr = NULL;
/*
* gEntList and/or g_pEntityList
*
* First try to lookup pointer directly for platforms with symbols.
* If symbols aren't present (Windows or stripped Linux/Mac),
* attempt find via LevelShutdown + offset
*/
if (!g_EntList)
{
if (g_pGameConf->GetMemSig("gEntList", (void **) &addr))
{
#if !defined PLATFORM_WINDOWS
if (!addr)
{
// Key exists so notify if lookup fails, but try other method.
logger->LogError("Failed lookup of gEntList directly - Reverting to lookup via LevelShutdown");
}
else
{
#endif
g_EntList = reinterpret_cast<void *>(addr);
#if !defined PLATFORM_WINDOWS
}
#endif
}
}
if (!g_EntList)
{
if (g_pGameConf->GetMemSig("LevelShutdown", (void **) &addr) && addr)
{
int offset;
if (!g_pGameConf->GetOffset("gEntList", &offset))
{
logger->LogError("Logical Entities not supported by this mod (gEntList) - Reverting to networkable entities only");
return;
}
#ifdef PLATFORM_X86
g_EntList = *reinterpret_cast<void **>(addr + offset);
#elif defined PLATFORM_X64
int32_t varOffset = *reinterpret_cast<int32_t *>(addr + offset);
g_EntList = reinterpret_cast<void *>(addr + offset + sizeof(int32_t) + varOffset);
#endif
}
}
// If we have g_EntList from either of the above methods, make sure we can get the offset from it to EntInfo as well
if (g_EntList && !g_pGameConf->GetOffset("EntInfo", &entInfoOffset))
{
logger->LogError("Logical Entities not supported by this mod (EntInfo) - Reverting to networkable entities only");
g_EntList = NULL;
return;
}
// If we don't have g_EntList or have it but don't know where EntInfo is on it, use fallback.
if (!g_EntList || entInfoOffset == -1)
{
g_pGameConf->GetAddress("EntInfosPtr", (void **)&g_pEntInfoList);
}
if (!g_EntList && !g_pEntInfoList)
{
logger->LogError("Failed lookup of gEntList - Reverting to networkable entities only");
return;
}
}
void CHalfLife2::InitCommandLine()
{
char error[256];
#if SOURCE_ENGINE != SE_DARKMESSIAH
if (g_SMAPI->GetSourceEngineBuild() != SOURCE_ENGINE_ORIGINAL)
{
ke::RefPtr<ke::SharedLib> lib = ke::SharedLib::Open(TIER0_NAME, error, sizeof(error));
if (!lib) {
logger->LogError("Could not load %s: %s", TIER0_NAME, error);
return;
}
m_pGetCommandLine = lib->get<decltype(m_pGetCommandLine)>("CommandLine_Tier0");
/* '_Tier0' dropped on Alien Swarm version */
if (m_pGetCommandLine == NULL)
{
m_pGetCommandLine = lib->get<decltype(m_pGetCommandLine)>("CommandLine");
}
}
else
#endif
{
ke::RefPtr<ke::SharedLib> lib = ke::SharedLib::Open(VSTDLIB_NAME, error, sizeof(error));
if (!lib) {
logger->LogError("Could not load %s: %s", VSTDLIB_NAME, error);
return;
}
m_pGetCommandLine = lib->get<decltype(m_pGetCommandLine)>("CommandLine");
}
if (m_pGetCommandLine == NULL)
{
logger->LogError("Could not locate any command line functionality");
}
}
ICommandLine *CHalfLife2::GetValveCommandLine()
{
if (!m_pGetCommandLine)
return NULL;
return ((FakeGetCommandLine)((FakeGetCommandLine *)m_pGetCommandLine))();
}
#if SOURCE_ENGINE != SE_DARKMESSIAH
IChangeInfoAccessor *CBaseEdict::GetChangeAccessor()
{
return engine->GetChangeAccessor( (const edict_t *)this );
}
#endif
bool UTIL_FindInSendTable(SendTable *pTable,
const char *name,
sm_sendprop_info_t *info,
unsigned int offset)
{
int props = pTable->GetNumProps();
for (int i = 0; i < props; i++)
{
SendProp *prop = pTable->GetProp(i);
// Skip InsideArray props (SendPropArray / SendPropArray2),
// we'll find them later by their containing array.
if (prop->IsInsideArray()) {
continue;
}
const char *pname = prop->GetName();
SendTable *pInnerTable = prop->GetDataTable();
if (pname && strcmp(name, pname) == 0)
{
// get true offset of CUtlVector
if (utlVecOffsetOffset != -1 && prop->GetOffset() == 0 && pInnerTable && pInnerTable->GetNumProps())
{
SendProp *pLengthProxy = pInnerTable->GetProp(0);
const char *ipname = pLengthProxy->GetName();
if (ipname && strcmp(ipname, "lengthproxy") == 0 && pLengthProxy->GetExtraData())
{
info->prop = prop;
info->actual_offset = offset + *reinterpret_cast<size_t *>(reinterpret_cast<intptr_t>(pLengthProxy->GetExtraData()) + utlVecOffsetOffset);
return true;
}
}
info->prop = prop;
info->actual_offset = offset + info->prop->GetOffset();
return true;
}
if (pInnerTable)
{
if (UTIL_FindInSendTable(pInnerTable,
name,
info,
offset + prop->GetOffset())
)
{
return true;
}
}
}
return false;
}
bool UTIL_FindDataMapInfo(datamap_t *pMap, const char *name, sm_datatable_info_t *pDataTable)
{
while (pMap)
{
for (int i = 0; i < pMap->dataNumFields; ++i)
{
if (pMap->dataDesc[i].fieldName == NULL)
{
continue;
}
if (strcmp(name, pMap->dataDesc[i].fieldName) == 0)
{
pDataTable->prop = &(pMap->dataDesc[i]);
pDataTable->actual_offset = GetTypeDescOffs(pDataTable->prop);
return true;
}
if (pMap->dataDesc[i].td == NULL || !UTIL_FindDataMapInfo(pMap->dataDesc[i].td, name, pDataTable))
{
continue;
}
pDataTable->actual_offset += GetTypeDescOffs(&(pMap->dataDesc[i]));
return true;
}
pMap = pMap->baseMap;
}
return false;
}
ServerClass *CHalfLife2::FindServerClass(const char *classname)
{
DataTableInfo *pInfo = _FindServerClass(classname);
if (!pInfo)
return NULL;
return pInfo->sc;
}
DataTableInfo *CHalfLife2::_FindServerClass(const char *classname)
{
DataTableInfo *pInfo = NULL;
if (!m_Classes.retrieve(classname, &pInfo))
{
ServerClass *sc = gamedll->GetAllServerClasses();
while (sc)
{
if (strcmp(classname, sc->GetName()) == 0)
{
pInfo = new DataTableInfo(sc);
m_Classes.insert(classname, pInfo);
break;
}
sc = sc->m_pNext;
}
if (!pInfo)
return NULL;
}
return pInfo;
}
bool CHalfLife2::FindSendPropInfo(const char *classname, const char *offset, sm_sendprop_info_t *info)
{
DataTableInfo *pInfo;
if ((pInfo = _FindServerClass(classname)) == NULL)
{
return false;
}
DataTableInfo::SendPropInfo temp;
if (!pInfo->lookup.retrieve(offset, &temp))
{
bool found = UTIL_FindInSendTable(pInfo->sc->m_pTable, offset, &temp.info, 0);
temp.name = offset;
pInfo->lookup.insert(offset, temp);
if (found)
{
*info = temp.info;
}
return found;
}
*info = temp.info;
return info->prop != nullptr;
}
SendProp *CHalfLife2::FindInSendTable(const char *classname, const char *offset)
{
sm_sendprop_info_t info;
if (!FindSendPropInfo(classname, offset, &info))
{
return NULL;
}
return info.prop;
}
typedescription_t *CHalfLife2::FindInDataMap(datamap_t *pMap, const char *offset)
{
sm_datatable_info_t dt_info;
if (!(this->FindDataMapInfo(pMap, offset, &dt_info)))
{
return NULL;
}
return dt_info.prop;
}
bool CHalfLife2::FindDataMapInfo(datamap_t *pMap, const char *offset, sm_datatable_info_t *pDataTable)
{
DataTableMap::Insert i = m_Maps.findForAdd(pMap);
if (!i.found())
m_Maps.add(i, pMap, new DataMapCache());
DataMapCache *cache = i->value;
DataMapCacheInfo temp;
if (!cache->retrieve(offset, &temp))
{
bool found = UTIL_FindDataMapInfo(pMap, offset, &temp.info);
temp.name = offset;
cache->insert(offset, temp);
if (found)
{
*pDataTable = temp.info;
}
return found;
}
*pDataTable = temp.info;
return pDataTable->prop != nullptr;
}
void CHalfLife2::SetEdictStateChanged(edict_t *pEdict, unsigned short offset)
{
#if SOURCE_ENGINE != SE_DARKMESSIAH
if (g_pSharedChangeInfo != NULL)
{
if (offset)
{
pEdict->StateChanged(offset);
}
else
{
pEdict->StateChanged();
}
}
else
#endif
{
pEdict->m_fStateFlags |= FL_EDICT_CHANGED;
}
}
bool CHalfLife2::TextMsg(int client, int dest, const char *msg)
{
#ifndef USE_PROTOBUF_USERMESSAGES
bf_write *pBitBuf = NULL;
#endif
cell_t players[] = {client};
if (dest == HUD_PRINTTALK)
{
const char *chat_saytext = g_pGameConf->GetKeyValue("ChatSayText");
/* Use SayText user message instead */
if (chat_saytext != NULL && strcmp(chat_saytext, "yes") == 0)
{
char buffer[253];
ke::SafeSprintf(buffer, sizeof(buffer), "%s\1\n", msg);
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV
CCSUsrMsg_SayText *pMsg;
if ((pMsg = (CCSUsrMsg_SayText *)g_UserMsgs.StartProtobufMessage(m_SayTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
pMsg->set_ent_idx(0);
pMsg->set_text(buffer);
pMsg->set_chat(false);
#else
if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_SayTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
pBitBuf->WriteByte(0);
pBitBuf->WriteString(buffer);
pBitBuf->WriteByte(1);
#endif
g_UserMsgs.EndMessage();
return true;
}
}
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV
CCSUsrMsg_TextMsg *pMsg;
if ((pMsg = (CCSUsrMsg_TextMsg *)g_UserMsgs.StartProtobufMessage(m_MsgTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
// Client tries to read all 5 'params' and will crash if less
pMsg->set_msg_dst(dest);
pMsg->add_params(msg);
pMsg->add_params("");
pMsg->add_params("");
pMsg->add_params("");
pMsg->add_params("");
#else
if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_MsgTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
pBitBuf->WriteByte(dest);
pBitBuf->WriteString(msg);
pBitBuf->WriteString("");
pBitBuf->WriteString("");
pBitBuf->WriteString("");
pBitBuf->WriteString("");
#endif
g_UserMsgs.EndMessage();
return true;
}
bool CHalfLife2::HintTextMsg(int client, const char *msg)
{
cell_t players[] = {client};
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV
CCSUsrMsg_HintText *pMsg;
if ((pMsg = (CCSUsrMsg_HintText *)g_UserMsgs.StartProtobufMessage(m_HinTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
pMsg->set_text(msg);
#else
bf_write *pBitBuf = NULL;
if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_HinTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
const char *pre_byte = g_pGameConf->GetKeyValue("HintTextPreByte");
if (pre_byte != NULL && strcmp(pre_byte, "yes") == 0)
{
pBitBuf->WriteByte(1);
}
pBitBuf->WriteString(msg);
#endif
g_UserMsgs.EndMessage();
return true;
}
bool CHalfLife2::HintTextMsg(cell_t *players, int count, const char *msg)
{
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV
CCSUsrMsg_HintText *pMsg;
if ((pMsg = (CCSUsrMsg_HintText *)g_UserMsgs.StartProtobufMessage(m_HinTextMsg, players, count, USERMSG_RELIABLE)) == NULL)
{
return false;
}
pMsg->set_text(msg);
#else
bf_write *pBitBuf = NULL;
if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_HinTextMsg, players, count, USERMSG_RELIABLE)) == NULL)
{
return false;
}
const char *pre_byte = g_pGameConf->GetKeyValue("HintTextPreByte");
if (pre_byte != NULL && strcmp(pre_byte, "yes") == 0)
{
pBitBuf->WriteByte(1);
}
pBitBuf->WriteString(msg);
#endif
g_UserMsgs.EndMessage();
return true;
}
bool CHalfLife2::ShowVGUIMenu(int client, const char *name, KeyValues *data, bool show)
{
KeyValues *SubKey = NULL;
int count = 0;
cell_t players[] = {client};
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV
CCSUsrMsg_VGUIMenu *pMsg;
if ((pMsg = (CCSUsrMsg_VGUIMenu *)g_UserMsgs.StartProtobufMessage(m_VGUIMenu, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
#else
bf_write *pBitBuf = NULL;
if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_VGUIMenu, players, 1, USERMSG_RELIABLE)) == NULL)
{
return false;
}
#endif
if (data)
{
SubKey = data->GetFirstSubKey();
while (SubKey)
{
count++;
SubKey = SubKey->GetNextKey();
}
SubKey = data->GetFirstSubKey();
}
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_BLADE || SOURCE_ENGINE == SE_MCV
pMsg->set_name(name);
pMsg->set_show(show);
while (SubKey)
{
CCSUsrMsg_VGUIMenu_Subkey *key = pMsg->add_subkeys();
key->set_name(SubKey->GetName());
key->set_str(SubKey->GetString());
SubKey = SubKey->GetNextKey();
}
#else
pBitBuf->WriteString(name);
pBitBuf->WriteByte((show) ? 1 : 0);
pBitBuf->WriteByte(count);
while (SubKey)
{
pBitBuf->WriteString(SubKey->GetName());
pBitBuf->WriteString(SubKey->GetString());
SubKey = SubKey->GetNextKey();
}
#endif
g_UserMsgs.EndMessage();
return true;
}
void CHalfLife2::AddToFakeCliCmdQueue(int client, int userid, const char *cmd)
{
DelayedFakeCliCmd *pFake;
if (m_FreeCmds.empty())
{
pFake = new DelayedFakeCliCmd;
} else {
pFake = m_FreeCmds.front();
m_FreeCmds.pop();
}
pFake->client = client;
pFake->userid = userid;
pFake->cmd.assign(cmd);
m_CmdQueue.push(pFake);
}
void CHalfLife2::ProcessFakeCliCmdQueue()
{
while (!m_CmdQueue.empty())
{
DelayedFakeCliCmd *pFake = m_CmdQueue.first();
if (g_Players.GetClientOfUserId(pFake->userid) == pFake->client)
{
CPlayer *pPlayer = g_Players.GetPlayerByIndex(pFake->client);
serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), pFake->cmd.c_str());
}
m_CmdQueue.pop();
m_FreeCmds.push(pFake);
}
}
bool CHalfLife2::IsLANServer()
{
sv_lan = icvar->FindVar("sv_lan");
if (!sv_lan)
{
return false;
}
return (sv_lan->GetInt() != 0);
}
bool CHalfLife2::KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID)
{
if (g_SMAPI->GetSourceEngineBuild() == SOURCE_ENGINE_ORIGINAL)
{
Assert(filesystem);
#ifdef _MSC_VER
Assert(_heapchk() == _HEAPOK);
#endif
FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
if (!f)
return false;
// load file into a null-terminated buffer
int fileSize = filesystem->Size(f);
char *buffer = (char *)MemAllocScratch(fileSize + 1);
Assert(buffer);
filesystem->Read(buffer, fileSize, f); // read into local buffer
buffer[fileSize] = 0; // null terminate file as EOF
filesystem->Close( f ); // close file after reading
bool retOK = kv->LoadFromBuffer( resourceName, buffer, filesystem );
MemFreeScratch();
return retOK;
}
else
{
return kv->LoadFromFile(filesystem, resourceName, pathID);
}
}
void CHalfLife2::PushCommandStack(const ICommandArgs *cmd)
{
CachedCommandInfo info;
info.args = cmd;
#if SOURCE_ENGINE <= SE_DARKMESSIAH
ke::SafeStrcpy(info.cmd, sizeof(info.cmd), cmd->Arg(0));
#endif
m_CommandStack.push(info);
}
const ICommandArgs *CHalfLife2::PeekCommandStack()
{
if (m_CommandStack.empty())
{
return NULL;
}
return m_CommandStack.front().args;
}
void CHalfLife2::PopCommandStack()
{
m_CommandStack.pop();
}
const char *CHalfLife2::CurrentCommandName()
{
#if SOURCE_ENGINE >= SE_ORANGEBOX
return m_CommandStack.front().args->Arg(0);
#else
return m_CommandStack.front().cmd;
#endif
}
void CHalfLife2::AddDelayedKick(int client, int userid, const char *msg)
{
CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
if (!pPlayer || !pPlayer->IsConnected() || pPlayer->IsInKickQueue())
{
return;
}
pPlayer->MarkAsBeingKicked();
DelayedKickInfo kick;
kick.client = client;
kick.userid = userid;
ke::SafeStrcpy(kick.buffer, sizeof(kick.buffer), msg);
m_DelayedKicks.push(kick);
}
void CHalfLife2::ProcessDelayedKicks()
{
while (!m_DelayedKicks.empty())
{
DelayedKickInfo info = m_DelayedKicks.first();
m_DelayedKicks.pop();
CPlayer *player = g_Players.GetPlayerByIndex(info.client);
if (player == NULL || player->GetUserId() != info.userid)
{
continue;
}
player->Kick(info.buffer);
}
}
edict_t *CHalfLife2::EdictOfIndex(int index)
{
return ::PEntityOfEntIndex(index);
}
int CHalfLife2::IndexOfEdict(edict_t *pEnt)
{
return ::IndexOfEdict(pEnt);
}
edict_t *CHalfLife2::GetHandleEntity(CBaseHandle &hndl)
{
if (!hndl.IsValid())
{
return NULL;
}
int index = hndl.GetEntryIndex();
edict_t *pStoredEdict;
CBaseEntity *pStoredEntity;
if (!IndexToAThings(index, &pStoredEntity, &pStoredEdict))
{
return NULL;
}
if (pStoredEdict == NULL || pStoredEntity == NULL)
{
return NULL;
}
IServerEntity *pSE = pStoredEdict->GetIServerEntity();
if (pSE == NULL)
{
return NULL;
}
if (pSE->GetRefEHandle() != hndl)
{
return NULL;
}
return pStoredEdict;
}
void CHalfLife2::SetHandleEntity(CBaseHandle &hndl, edict_t *pEnt)
{
IServerEntity *pEntOther = pEnt->GetIServerEntity();
if (pEntOther == NULL)
{
return;
}
hndl.Set(pEntOther);
}
const char *CHalfLife2::GetCurrentMap()
{
return STRING(gpGlobals->mapname);
}
void CHalfLife2::ServerCommand(const char *buffer)
{
engine->ServerCommand(buffer);
}
cell_t CHalfLife2::EntityToReference(CBaseEntity *pEntity)
{
IServerUnknown *pUnknown = (IServerUnknown *)pEntity;
CBaseHandle hndl = pUnknown->GetRefEHandle();
return (hndl.ToInt() | (1<<31));
}
CBaseEntity *CHalfLife2::ReferenceToEntity(cell_t entRef)
{
if ((unsigned)entRef == INVALID_EHANDLE_INDEX)
{
return NULL;
}
CEntInfo *pInfo = NULL;
if (entRef & (1<<31))
{
/* Proper ent reference */
int hndlValue = entRef & ~(1<<31);
CBaseHandle hndl(hndlValue);
pInfo = LookupEntity(hndl.GetEntryIndex());
if (!pInfo || pInfo->m_SerialNumber != hndl.GetSerialNumber())
{
return NULL;
}
}
else
{
/* Old style index only */
pInfo = LookupEntity(entRef);
}
if (!pInfo)
{
return NULL;
}
IServerUnknown *pUnk = static_cast<IServerUnknown *>(pInfo->m_pEntity);
if (pUnk)
{
return pUnk->GetBaseEntity();
}
return NULL;
}
/**
* Retrieves the CEntInfo pointer from g_EntList for a given entity index
*/
CEntInfo *CHalfLife2::LookupEntity(int entIndex)
{
// Make sure that our index is within the bounds of the global ent array
if (entIndex < 0 || entIndex >= NUM_ENT_ENTRIES)
{
return NULL;
}
CEntInfo *entInfos = EntInfoArray();
if (!entInfos)
{
/* Attempt to use engine interface instead */
static CEntInfo tempInfo;
tempInfo.m_pNext = NULL;
tempInfo.m_pPrev = NULL;
edict_t *pEdict = PEntityOfEntIndex(entIndex);
if (!pEdict)
{
return NULL;
}
IServerUnknown *pUnk = pEdict->GetUnknown();
if (!pUnk)
{
return NULL;
}
tempInfo.m_pEntity = pUnk;
tempInfo.m_SerialNumber = pUnk->GetRefEHandle().GetSerialNumber();
return &tempInfo;
}
return &entInfos[entIndex];
}
/**
SERIAL_MASK = 0x7fff (15 bits)
#define MAX_EDICT_BITS 11
#define NUM_ENT_ENTRY_BITS (MAX_EDICT_BITS + 1)
m_Index = iEntry | (iSerialNumber << NUM_ENT_ENTRY_BITS);
Top 5 bits of a handle are unused.
Bit 31 - Our 'reference' flag indicator
*/
cell_t CHalfLife2::IndexToReference(int entIndex)
{
CBaseEntity *pEnt = ReferenceToEntity(entIndex);
if (!pEnt)
{
return INVALID_EHANDLE_INDEX;
}
return EntityToReference(pEnt);
}
int CHalfLife2::ReferenceToIndex(cell_t entRef)
{
if ((unsigned)entRef == INVALID_EHANDLE_INDEX)
{
return INVALID_EHANDLE_INDEX;
}
if (entRef & (1<<31))
{
/* Proper ent reference */
int hndlValue = entRef & ~(1<<31);
CBaseHandle hndl(hndlValue);
CEntInfo *pInfo = LookupEntity(hndl.GetEntryIndex());
if (pInfo->m_SerialNumber != hndl.GetSerialNumber())
{
return INVALID_EHANDLE_INDEX;
}
return hndl.GetEntryIndex();
}
else
{
CEntInfo *pInfo = LookupEntity(entRef);
if (!pInfo)
{
return INVALID_EHANDLE_INDEX;
}
IServerUnknown *pUnk = static_cast<IServerUnknown *>(pInfo->m_pEntity);
if (!pUnk)
{
return INVALID_EHANDLE_INDEX;
}
CBaseEntity *pEntity = pUnk->GetBaseEntity();
if (!pEntity)
{
return INVALID_EHANDLE_INDEX;
}
return entRef;
}
}
cell_t CHalfLife2::EntityToBCompatRef(CBaseEntity *pEntity)
{
if (pEntity == NULL)
{
return INVALID_EHANDLE_INDEX;
}
IServerUnknown *pUnknown = (IServerUnknown *)pEntity;
CBaseHandle hndl = pUnknown->GetRefEHandle();
if (hndl == INVALID_EHANDLE_INDEX)
{
return INVALID_EHANDLE_INDEX;
}
if (hndl.GetEntryIndex() >= MAX_EDICTS)
{
return (hndl.ToInt() | (1<<31));
}
else
{
return hndl.GetEntryIndex();
}
}
cell_t CHalfLife2::ReferenceToBCompatRef(cell_t entRef)
{
if ((unsigned)entRef == INVALID_EHANDLE_INDEX)
{
return INVALID_EHANDLE_INDEX;
}
int hndlValue = entRef & ~(1<<31);
CBaseHandle hndl(hndlValue);
if (hndl.GetEntryIndex() < MAX_EDICTS)
{
return hndl.GetEntryIndex();
}
return entRef;
}
void *CHalfLife2::GetGlobalEntityList()
{
return g_EntList;
}
int CHalfLife2::GetSendPropOffset(SendProp *prop)
{
return prop->GetOffset();
}
const char *CHalfLife2::GetEntityClassname(edict_t * pEdict)
{
if (pEdict == NULL || pEdict->IsFree())
{
return NULL;
}
IServerUnknown *pUnk = pEdict->GetUnknown();
if (pUnk == NULL)
{
return NULL;
}
CBaseEntity * pEntity = pUnk->GetBaseEntity();
if (pEntity == NULL)
{
return NULL;
}
return GetEntityClassname(pEntity);
}
const char *CHalfLife2::GetEntityClassname(CBaseEntity *pEntity)
{
static int offset = -1;
if (offset == -1)
{
CBaseEntity *pGetterEnt = ReferenceToEntity(0);
if (pGetterEnt == NULL)
{
// If we don't have a world entity yet, we'll have to rely on the given entity
pGetterEnt = pEntity;
}
datamap_t *pMap = GetDataMap(pGetterEnt);
sm_datatable_info_t info;
if (!FindDataMapInfo(pMap, "m_iClassname", &info))
{
return NULL;
}
offset = info.actual_offset;
}
return *(const char **)(((unsigned char *)pEntity) + offset);
}
SMFindMapResult CHalfLife2::FindMap(char *pMapName, size_t nMapNameMax)
{
return this->FindMap(pMapName, pMapName, nMapNameMax);
}
#ifdef PLATFORM_WINDOWS
bool CheckReservedFilename(const char *in, const char *reservedName)
{
size_t nameLen = strlen(reservedName);
for (size_t i = 0; i < nameLen; ++i)
{
if (reservedName[i] != tolower(in[i]))
{
return false;
}
}
if (in[nameLen] == '\0' || in[nameLen] == '.')
{
return true;
}
return false;
}
bool IsWindowsReservedDeviceName(const char *pMapname)
{
static const char * const reservedDeviceNames[] = {
"con", "prn", "aux", "clock$", "nul", "com1",
"com2", "com3", "com4", "com5", "com6", "com7",
"com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
};
size_t reservedCount = sizeof(reservedDeviceNames) / sizeof(reservedDeviceNames[0]);
for (size_t i = 0; i < reservedCount; ++i)
{
if (CheckReservedFilename(pMapname, reservedDeviceNames[i]))
{
return true;
}
}
return false;
}
#endif
#if SOURCE_ENGINE >= SE_LEFT4DEAD && defined PLATFORM_WINDOWS && SOURCE_ENGINE != SE_MOCK
// This frees memory allocated by the game using the game's CRT on Windows,
// avoiding a crash due to heap corruption (issue #910).
template< class T, class I >
class CUtlMemoryGlobalMalloc : public CUtlMemory< T, I >
{
typedef CUtlMemory< T, I > BaseClass;
public:
using BaseClass::BaseClass;
void Purge()
{
if (!IsExternallyAllocated())
{
if (m_pMemory)
{
UTLMEMORY_TRACK_FREE();
g_pMemAlloc->Free((void*)m_pMemory);
m_pMemory = 0;
}
m_nAllocationCount = 0;
}
BaseClass::Purge();
}
};
void CHalfLife2::FreeUtlVectorUtlString(CUtlVector<CUtlString, CUtlMemoryGlobalMalloc<CUtlString>> &vec)
{
CUtlMemoryGlobalMalloc<unsigned char> *pMemory;
FOR_EACH_VEC(vec, i)
{
pMemory = (CUtlMemoryGlobalMalloc<unsigned char> *) &vec[i].m_Storage.m_Memory;
pMemory->Purge();
vec[i].m_Storage.SetLength(0);
}
}
#endif
SMFindMapResult CHalfLife2::FindMap(const char *pMapName, char *pFoundMap, size_t nMapNameMax)
{
/* We need to ensure user input does not contain reserved device names on windows */
#ifdef PLATFORM_WINDOWS
if (IsWindowsReservedDeviceName(pMapName))
{
return SMFindMapResult::NotFound;
}
#endif
ke::SafeStrcpy(pFoundMap, nMapNameMax, pMapName);
#if SOURCE_ENGINE >= SE_LEFT4DEAD
static char mapNameTmp[PLATFORM_MAX_PATH];
g_SourceMod.Format(mapNameTmp, sizeof(mapNameTmp), "maps%c%s.bsp", PLATFORM_SEP_CHAR, pMapName);
if (filesystem->FileExists(mapNameTmp, "GAME"))
{
// If this is already an exact match, don't attempt to autocomplete it further (de_dust -> de_dust2).
// ... but still check that map file is actually valid.
// We check FileExists first to avoid console message about IsMapValid with invalid map.
return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found;
}
static ConCommand *pHelperCmd = g_pCVar->FindCommand("changelevel");
// This shouldn't happen.
if (!pHelperCmd || !pHelperCmd->CanAutoComplete())
{
return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found;
}
static size_t helperCmdLen = strlen(pHelperCmd->GetName());
#ifdef PLATFORM_WINDOWS
CUtlVector<CUtlString, CUtlMemoryGlobalMalloc<CUtlString>> results;
pHelperCmd->AutoCompleteSuggest(pMapName, *(CUtlVector<CUtlString, CUtlMemory<CUtlString>>*)&results);
#else
CUtlVector<CUtlString> results;
pHelperCmd->AutoCompleteSuggest(pMapName, results);
#endif
if (results.Count() == 0)
return SMFindMapResult::NotFound;
// Results come back as you'd see in autocomplete. (ie. "changelevel fullmapnamehere"),
// so skip ahead to start of map path/name
// Like the engine, we're only going to deal with the first match.
bool bExactMatch = Q_strcmp(pMapName, &results[0][helperCmdLen + 1]) == 0;
if (bExactMatch)
{
#ifdef PLATFORM_WINDOWS
FreeUtlVectorUtlString(results);
#endif
return SMFindMapResult::Found;
}
else
{
ke::SafeStrcpy(pFoundMap, nMapNameMax, &results[0][helperCmdLen + 1]);
#ifdef PLATFORM_WINDOWS
FreeUtlVectorUtlString(results);
#endif
return SMFindMapResult::FuzzyMatch;
}
#elif SOURCE_ENGINE == SE_TF2 || SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_DODS || SOURCE_ENGINE == SE_HL2DM \
|| SOURCE_ENGINE == SE_SDK2013 || SOURCE_ENGINE == SE_BMS || SOURCE_ENGINE == SE_PVKII
static IVEngineServer *engine23 = (IVEngineServer *)(g_SMAPI->GetEngineFactory()("VEngineServer023", nullptr));
if (engine23)
{
static char szTemp[PLATFORM_MAX_PATH];
if (pFoundMap == NULL)
{
ke::SafeStrcpy(szTemp, SM_ARRAYSIZE(szTemp), pMapName);
pFoundMap = szTemp;
nMapNameMax = 0;
}
return static_cast<SMFindMapResult>(engine->FindMap(pFoundMap, static_cast<int>(nMapNameMax)));
}
else
{
static IVEngineServer *engine21 = (IVEngineServer *)(g_SMAPI->GetEngineFactory()("VEngineServer021", nullptr));
return engine21->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found;
}
#else
return engine->IsMapValid(pMapName) == 0 ? SMFindMapResult::NotFound : SMFindMapResult::Found;
#endif
}
bool CHalfLife2::GetMapDisplayName(const char *pMapName, char *pDisplayname, size_t nMapNameMax)
{
SMFindMapResult result = FindMap(pMapName, pDisplayname, nMapNameMax);
if (result == SMFindMapResult::NotFound)
return false;
char *pPos;
if ((pPos = strrchr(pDisplayname, '/')) != NULL || (pPos = strrchr(pDisplayname, '\\')) != NULL)
ke::SafeStrcpy(pDisplayname, nMapNameMax, &pPos[1]);
if ((pPos = strstr(pDisplayname, ".ugc")) != NULL)
pPos[0] = '\0';
return true;
}
bool CHalfLife2::IsMapValid(const char *map)
{
if (!map || !map[0])
return false;
return FindMap(map) != SMFindMapResult::NotFound;
}
#if SOURCE_ENGINE < SE_ORANGEBOX
class VKeyValuesSS_Helper {};
static bool VKeyValuesSS(CBaseEntity* pThisPtr, const char *pszKey, const char *pszValue, int offset)
{
void** this_ptr = *reinterpret_cast<void***>(&pThisPtr);
void** vtable = *reinterpret_cast<void***>(pThisPtr);
void* vfunc = vtable[offset];
union
{
bool (VKeyValuesSS_Helper::* mfpnew)(const char *, const char *);
#ifndef PLATFORM_POSIX
void* addr;
} u;
u.addr = vfunc;
#else
struct
{
void* addr;
intptr_t adjustor;
} s;
} u;
u.s.addr = vfunc;
u.s.adjustor = 0;
#endif
return (bool)(reinterpret_cast<VKeyValuesSS_Helper*>(this_ptr)->*u.mfpnew)(pszKey, pszValue);
}
#endif
string_t CHalfLife2::AllocPooledString(const char *pszValue)
{
// This is admittedly a giant hack, but it's a relatively safe method for
// inserting a string into the game's string pool that isn't likely to break.
//
// We find the first valid ent (should always be worldspawn), save off it's
// current targetname string_t, set it to our string to insert via SetKeyValue,
// read back the new targetname value, restore the old value, and return the new one.
#if SOURCE_ENGINE < SE_ORANGEBOX
CBaseEntity* pEntity = nullptr;
for (int i = 0; i < gpGlobals->maxEntities; ++i)
{
pEntity = ReferenceToEntity(i);
if (pEntity)
{
break;
}
}
if (!pEntity)
{
logger->LogError("Failed to locate a valid entity for AllocPooledString.");
return NULL_STRING;
}
#else
CBaseEntity *pEntity = ((IServerUnknown *) servertools->FirstEntity())->GetBaseEntity();
#endif
auto *pDataMap = GetDataMap(pEntity);
assert(pDataMap);
static int iNameOffset = -1;
if (iNameOffset == -1)
{
sm_datatable_info_t info;
bool found = FindDataMapInfo(pDataMap, "m_iName", &info);
assert(found);
iNameOffset = info.actual_offset;
}
string_t* pProp = (string_t*)((intp)pEntity + iNameOffset);
string_t backup = *pProp;
#if SOURCE_ENGINE < SE_ORANGEBOX
static int iFuncOffset;
if (!g_pGameConf->GetOffset("DispatchKeyValue", &iFuncOffset) || !iFuncOffset)
{
logger->LogError("Failed to locate DispatchKeyValue in core gamedata. AllocPooledString unsupported.");
return NULL_STRING;
}
VKeyValuesSS(pEntity, "targetname", pszValue, iFuncOffset);
#else
servertools->SetKeyValue(pEntity, "targetname", pszValue);
#endif
string_t newString = *pProp;
*pProp = backup;
return newString;
}
bool CHalfLife2::GetServerSteam3Id(char *pszOut, size_t len) const
{
CSteamID sid((uint64)GetServerSteamId64());
switch (sid.GetEAccountType())
{
case k_EAccountTypeAnonGameServer:
ke::SafeSprintf(pszOut, len, "[A:%u:%u:%u]", sid.GetEUniverse(), sid.GetAccountID(), sid.GetUnAccountInstance());
break;
case k_EAccountTypeGameServer:
ke::SafeSprintf(pszOut, len, "[G:%u:%u]", sid.GetEUniverse(), sid.GetAccountID());
break;
case k_EAccountTypeInvalid:
ke::SafeSprintf(pszOut, len, "[I:%u:%u]", sid.GetEUniverse(), sid.GetAccountID());
break;
default:
return false;
}
return true;
}
#if defined( PLATFORM_WINDOWS )
#define STEAM_LIB_PREFIX
#define STEAM_LIB_SUFFIX
#elif defined( PLATFORM_LINUX )
#define STEAM_LIB_PREFIX "lib"
#define STEAM_LIB_SUFFIX ".so"
#elif defined( PLATFORM_APPLE )
#define STEAM_LIB_PREFIX "lib"
#define STEAM_LIB_SUFFIX ".dylib"
#endif
uint64_t CHalfLife2::GetServerSteamId64() const
{
#if SOURCE_ENGINE == SE_BLADE \
|| SOURCE_ENGINE == SE_BMS \
|| SOURCE_ENGINE == SE_CSGO \
|| SOURCE_ENGINE == SE_CSS \
|| SOURCE_ENGINE == SE_DODS \
|| SOURCE_ENGINE == SE_EYE \
|| SOURCE_ENGINE == SE_HL2DM \
|| SOURCE_ENGINE == SE_INSURGENCY \
|| SOURCE_ENGINE == SE_DOI \
|| SOURCE_ENGINE == SE_SDK2013 \
|| SOURCE_ENGINE == SE_ALIENSWARM \
|| SOURCE_ENGINE == SE_TF2 \
|| SOURCE_ENGINE == SE_PVKII \
|| SOURCE_ENGINE == SE_MCV
const CSteamID *sid = engine->GetGameServerSteamID();
if (sid)
{
return sid->ConvertToUint64();
}
#else
typedef uint64_t(* GetServerSteamIdFn)(void);
static GetServerSteamIdFn fn = nullptr;
if (!fn)
{
ke::SharedLib steam_api(STEAM_LIB_PREFIX "steam_api" STEAM_LIB_SUFFIX);
if (steam_api.valid())
{
fn = (GetServerSteamIdFn)steam_api.lookup("SteamGameServer_GetSteamID");
}
}
if (fn)
{
return fn();
}
#endif
return 1ULL;
}