/** * vim: set ts=4 : * ============================================================================= * SourceMod A2SFixes Extension * 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$ */ #include "extension.h" #include "string.h" #include "sys/socket.h" #include "iplayerinfo.h" #include #include "CDetour/detours.h" #include #define DETOUR_CREATE_MEMBER_ADDRESS(name, address) CDetourManager::CreateDetour(GET_MEMBER_CALLBACK(name), GET_MEMBER_TRAMPOLINE(name), address); #define DETOUR_CREATE_STATIC_ADDRESS(name, address) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), address); #define A2S_INFO_REQUEST "\xFF\xFF\xFF\xFF\x54" #define A2S_INFO_REPLY "\xFF\xFF\xFF\xFF\x49" #define A2S_PLAYER_REQUEST "\xFF\xFF\xFF\xFF\x55" #define A2S_PLAYER_REPLY "\xFF\xFF\xFF\xFF\x44" /** * @file extension.cpp * @brief Implement extension code here. */ A2SFixes g_Interface; SMEXT_LINK(&g_Interface); CDetour *g_pDetour_SendTo = NULL; ConVar g_A2SMaxPlayers("a2s_maxplayers", "-1", FCVAR_NONE, "Maximum Players in the A2S Packet."); ConVar g_A2SIncludeSourceTV("a2s_includesourcetv", "0", FCVAR_NONE, "Include SourceTV in the Player Count and List."); char *NewBuf; char *OldBuf; int g_iFakePlayers = 0; struct PlayerInfo { char Name[32]; long Score; float Time; }; /** * @brief */ DETOUR_DECL_STATIC6(Detour_SendTo, int, int, s, char *, buf, int, len, int, flags, sockaddr *, to, socklen_t*, tolen) { if (memcmp(buf, A2S_INFO_REPLY, 5) == 0) { delete[] NewBuf; NewBuf = new char[len]; // Copy memory memcpy(NewBuf, buf, len); // Store Pointer OldBuf = NewBuf; // Skip header and protocol NewBuf += 5; // Skip server name while(*NewBuf) NewBuf++; NewBuf++; // Skip map name while(*NewBuf) NewBuf++; NewBuf++; // Skip folder while(*NewBuf) NewBuf++; NewBuf++; // Skip game name while(*NewBuf) NewBuf++; NewBuf++; // Skip game id NewBuf += 2; // Calculate playercount int iPlayers = 0; for (int index = 0; index <= SM_MAXPLAYERS; index++) { IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(index); if (pPlayer != NULL && pPlayer->IsConnected() && (!pPlayer->IsSourceTV() || g_A2SIncludeSourceTV.GetInt())) { iPlayers++; } } iPlayers += g_iFakePlayers; // Correct playercount memset(NewBuf, iPlayers, 1); // Set MaxPlayers int iMaxPlayers = g_A2SMaxPlayers.GetInt(); if (iMaxPlayers > 0) { NewBuf++; memset(NewBuf, iMaxPlayers, 1); } // Reset Pointer NewBuf = OldBuf; return DETOUR_STATIC_CALL(Detour_SendTo)(s, NewBuf, len, flags, to, tolen); } if (memcmp(buf, A2S_PLAYER_REPLY, 5) == 0) { int iPlayers = 0; PlayerInfo PlayerList[SM_MAXPLAYERS]; for (int index = 0; index <= SM_MAXPLAYERS; index++) { IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(index); if (pPlayer != NULL && pPlayer->IsConnected() && pPlayer->IsInGame() && (!pPlayer->IsSourceTV() || g_A2SIncludeSourceTV.GetInt())) { memcpy(PlayerList[iPlayers].Name, pPlayer->GetName(), 32); IPlayerInfo *pInfo = pPlayer->GetPlayerInfo(); if (pInfo != NULL) PlayerList[iPlayers].Score = pInfo->GetFragCount(); else PlayerList[iPlayers].Score = 0; INetChannelInfo *pNetInfo = engine->GetPlayerNetInfo(index); if (pNetInfo != NULL) PlayerList[iPlayers].Time = pNetInfo->GetTimeConnected(); else PlayerList[iPlayers].Time = 0.0; iPlayers++; } } char PlayerReply[1024 * 6]; bf_write PlayerReplyPacket(PlayerReply, sizeof(PlayerReply)); PlayerReplyPacket.Reset(); // Build up the packet as a bitbuffer so we can use the nice helper functions to do the work for us PlayerReplyPacket.WriteLong(-1); // FF FF FF FF PlayerReplyPacket.WriteByte(68); // 44 PlayerReplyPacket.WriteByte(iPlayers); // Number of players for (int index = 0; index < iPlayers; index++) { PlayerReplyPacket.WriteByte(index); PlayerReplyPacket.WriteString(PlayerList[index].Name); PlayerReplyPacket.WriteLong(PlayerList[index].Score); PlayerReplyPacket.WriteFloat(PlayerList[index].Time); } return DETOUR_STATIC_CALL(Detour_SendTo)(s, (char *)PlayerReplyPacket.GetData(), PlayerReplyPacket.GetNumBytesWritten(), flags, to, tolen); } return DETOUR_STATIC_CALL(Detour_SendTo)(s, buf, len, flags, to, tolen); } /** * @brief */ cell_t SetFakePlayerCount(IPluginContext *pContext, const cell_t *params) { g_iFakePlayers = params[1]; return 1; } /** * @brief */ const sp_nativeinfo_t g_ExtensionNatives[] = { {"A2S_SetFakePlayerCount", SetFakePlayerCount}, {NULL, NULL}, }; /** * @brief This is called after the initial loading sequence has been processed. * * @param error Error message buffer. * @param maxlength Size of error message buffer. * @param late Whether or not the module was loaded after map load. * @return True to succeed loading, false to fail. */ bool A2SFixes::SDK_OnLoad(char *error, size_t maxlength, bool late) { CDetourManager::Init(g_pSM->GetScriptingEngine(), NULL); g_pDetour_SendTo = DETOUR_CREATE_STATIC_ADDRESS(Detour_SendTo, (void*)sendto); if (g_pDetour_SendTo == NULL) { snprintf(error, maxlength, "Could not create detour for 'sendto'"); SDK_OnUnload(); return false; } g_pDetour_SendTo->EnableDetour(); return true; } /** * @brief This is called right before the extension is unloaded. */ void A2SFixes::SDK_OnUnload() { if (g_pDetour_SendTo != NULL) { g_pDetour_SendTo->Destroy(); g_pDetour_SendTo = NULL; } } /** * @brief This is called once all known extensions have been loaded. * Note: It is is a good idea to add natives here, if any are provided. */ void A2SFixes::SDK_OnAllLoaded() { sharesys->AddNatives(myself, g_ExtensionNatives); sharesys->RegisterLibrary(myself, "A2SFixes"); } /** * @brief Called when Metamod is attached, before the extension version is called. * * @param error Error buffer. * @param maxlength Maximum size of error buffer. * @param late Whether or not Metamod considers this a late load. * @return True to succeed, false to fail. */ bool A2SFixes::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER); GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); ConVar_Register(0, this); return true; } /** * @brief */ bool A2SFixes::RegisterConCommandBase(ConCommandBase *pVar) { /* Always call META_REGCVAR instead of going through the engine. */ return META_REGCVAR(pVar); }