// // YaPB, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright © YaPB Project Developers . // // SPDX-License-Identifier: MIT // #include int32_t BotSupport::sendTo (int socket, const void *message, size_t length, int flags, const sockaddr *dest, int destLength) { const auto send = [&] (const Twin &msg) -> int32_t { return Socket::sendto (socket, msg.first, msg.second, flags, dest, destLength); }; auto packet = reinterpret_cast (message); constexpr int32_t packetLength = 5; // player replies response if (length > packetLength && memcmp (packet, "\xff\xff\xff\xff", packetLength - 1) == 0) { if (packet[4] == 'D') { QueryBuffer buffer { packet, length, packetLength }; auto count = buffer.read (); for (uint8_t i = 0; i < count; ++i) { buffer.skip (); // number auto name = buffer.readString (); // name buffer.skip (); // score auto ctime = buffer.read (); // override connection time buffer.write (bots.getConnectTime (name, ctime)); } return send (buffer.data ()); } else if (packet[4] == 'I') { QueryBuffer buffer { packet, length, packetLength }; buffer.skip (); // protocol // skip server name, folder, map game for (size_t i = 0; i < 4; ++i) { buffer.skipString (); } buffer.skip (); // steam app id buffer.skip (); // players buffer.skip (); // maxplayers buffer.skip (); // bots buffer.write (0); // zero out bot count return send (buffer.data ()); } else if (packet[4] == 'm') { QueryBuffer buffer { packet, length, packetLength }; buffer.shiftToEnd (); // shift to the end of buffer buffer.write (0); // zero out bot count return send (buffer.data ()); } } return send ({ packet, length }); } void ServerQueryHook::init () { // if previously requested to disable? if (!cv_enable_query_hook.bool_ ()) { if (m_sendToDetour.detoured ()) { disable (); } return; } // do not enable on not dedicated server if (!game.isDedicated ()) { return; } SendToProto *sendToAddress = sendto; // linux workaround with sendto if (!plat.win && !plat.isNonX86 ()) { if (game.elib ()) { auto address = game.elib ().resolve ("sendto"); if (address != nullptr) { sendToAddress = address; } } } m_sendToDetour.initialize ("ws2_32.dll", "sendto", sendToAddress); // enable only on modern games if (!game.is (GameFlags::Legacy) && (plat.nix || plat.win) && !plat.isNonX86 () && !m_sendToDetour.detoured ()) { m_sendToDetour.install (reinterpret_cast (BotSupport::sendTo), true); } } SharedLibrary::Func DynamicLinkerHook::lookup (SharedLibrary::Handle module, const char *function) { static const auto &gamedll = game.lib ().handle (); static const auto &self = m_self.handle (); const auto resolve = [&] (SharedLibrary::Handle handle) { return m_dlsym (handle, function); }; if (entlink.needsBypass () && !strcmp (function, "CreateInterface")) { entlink.setPaused (true); auto ret = resolve (module); entlink.disable (); return ret; } // if requested module is yapb module, put in cache the looked up symbol if (self != module) { return resolve (module); } #if defined (CR_WINDOWS) if (HIWORD (function) == 0) { return resolve (module); } #endif if (m_exports.exists (function)) { return m_exports[function]; } auto botAddr = resolve (self); if (!botAddr) { auto gameAddr = resolve (gamedll); if (gameAddr) { return m_exports[function] = gameAddr; } } else { return m_exports[function] = botAddr; } return nullptr; } bool DynamicLinkerHook::callPlayerFunction (edict_t *ent) { auto callPlayer = [&] () { reinterpret_cast (m_exports["player"]) (&ent->v); }; if (m_exports.exists ("player")) { callPlayer (); return true; } auto playerFunction = game.lib ().resolve ("player"); if (!playerFunction) { logger.error ("Cannot resolve player() function in GameDLL."); return false; } m_exports["player"] = reinterpret_cast (playerFunction); callPlayer (); return true; } bool DynamicLinkerHook::needsBypass () const { return !plat.win && !game.isDedicated (); } void DynamicLinkerHook::initialize () { if (plat.isNonX86 () || game.is (GameFlags::Metamod)) { return; } m_dlsym.initialize ("kernel32.dll", "GetProcAddress", DLSYM_FUNCTION); m_dlsym.install (reinterpret_cast (lookupHandler), true); if (needsBypass ()) { m_dlclose.initialize ("kernel32.dll", "FreeLibrary", DLCLOSE_FUNCTION); m_dlclose.install (reinterpret_cast (closeHandler), true); } m_self.locate (&engfuncs); }