/** * 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; char *NewBuf; char *OldBuf; char g_PlayerReply[1024]; bf_write g_PlayerReplyPacket(g_PlayerReply, 1024); int g_FakePlayers = 0; /** * @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()) { iPlayers++; } } if (iPlayers + g_FakePlayers < 64) iPlayers += g_FakePlayers; // Correct playercount memset(NewBuf, iPlayers, 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) { g_PlayerReplyPacket.Reset(); // Build up the packet as a bitbuffer so we can use the nice helper functions to do the work for us g_PlayerReplyPacket.WriteLong(-1); // FF FF FF FF g_PlayerReplyPacket.WriteByte(68); // 44 g_PlayerReplyPacket.WriteByte(playerhelpers->GetNumPlayers()); // Number of players int iPlayers = 0; for (int index = 0; index <= SM_MAXPLAYERS; index++) { IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(index); if (pPlayer != NULL && pPlayer->IsInGame() && !pPlayer->IsSourceTV()) { g_PlayerReplyPacket.WriteByte(iPlayers); g_PlayerReplyPacket.WriteString(pPlayer->GetName()); IPlayerInfo *pInfo = pPlayer->GetPlayerInfo(); if (pInfo != NULL) g_PlayerReplyPacket.WriteLong(pInfo->GetFragCount()); else g_PlayerReplyPacket.WriteLong(0); INetChannelInfo *pNetInfo = engine->GetPlayerNetInfo(index); if (pNetInfo != NULL) g_PlayerReplyPacket.WriteFloat(pNetInfo->GetTimeConnected()); else g_PlayerReplyPacket.WriteFloat(0.0); iPlayers++; } } return DETOUR_STATIC_CALL(Detour_SendTo)(s, (char *)g_PlayerReplyPacket.GetData(), g_PlayerReplyPacket.GetNumBytesWritten(), flags, to, tolen); } return DETOUR_STATIC_CALL(Detour_SendTo)(s, buf, len, flags, to, tolen); } /** * @brief */ cell_t FakePlayers(IPluginContext *pContext, const cell_t *params) { g_FakePlayers = params[1]; return 1; } /** * @brief */ const sp_nativeinfo_t MyNatives[] = { {"FakePlayers", FakePlayers}, {NULL, NULL}, }; void A2SFixes::SDK_OnAllLoaded() { sharesys->AddNatives(myself, MyNatives); } /** * @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 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); return true; }