Add test client functionality

This commit is contained in:
FutureRave 2022-03-10 01:12:16 +00:00
parent 4f691b5f96
commit c0a85c4f33
No known key found for this signature in database
GPG Key ID: E883E2BC9657D955
13 changed files with 600 additions and 51 deletions

View File

@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
name: Build binaries name: Build binaries
runs-on: windows-latest runs-on: windows-2022
strategy: strategy:
matrix: matrix:
configuration: configuration:
@ -35,11 +35,10 @@ jobs:
lfs: false lfs: false
- name: Add msbuild to PATH - name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2 uses: microsoft/setup-msbuild@v1.1
- name: Generate project files - name: Generate project files
#run: tools/premake5 vs2019 --ci-build run: tools/premake5 vs2022
run: tools/premake5 vs2019
- name: Set up problem matching - name: Set up problem matching
uses: ammaraskar/msvc-problem-matcher@master uses: ammaraskar/msvc-problem-matcher@master

View File

@ -1,3 +1,4 @@
[![Build](https://github.com/momo5502/open-iw5/workflows/Build/badge.svg)](https://github.com/momo5502/open-iw5/actions)
![issues](https://img.shields.io/github/issues/momo5502/open-iw5.svg) ![issues](https://img.shields.io/github/issues/momo5502/open-iw5.svg)
![license](https://img.shields.io/github/license/momo5502/open-iw5.svg) ![license](https://img.shields.io/github/license/momo5502/open-iw5.svg)
![forks](https://img.shields.io/github/forks/momo5502/open-iw5.svg) ![forks](https://img.shields.io/github/forks/momo5502/open-iw5.svg)

View File

@ -1,3 +1,3 @@
@echo off @echo off
git submodule update --init --recursive git submodule update --init --recursive
tools\premake5 %* vs2019 tools\premake5 %* vs2022

View File

@ -42,15 +42,14 @@ workspace "open-iw5"
objdir "%{wks.location}/obj" objdir "%{wks.location}/obj"
targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}"
configurations { configurations { "Debug", "Release" }
"Debug",
"Release",
}
architecture "x32" language "C++"
platforms "x32" cppdialect "C++20"
architecture "x86"
platforms "Win32"
buildoptions "/std:c++latest"
systemversion "latest" systemversion "latest"
symbols "On" symbols "On"
staticruntime "On" staticruntime "On"
@ -65,33 +64,22 @@ workspace "open-iw5"
"No64BitChecks" "No64BitChecks"
} }
configuration "windows" filter "platforms:Win*"
defines { defines { "_WINDOWS", "WIN32" }
"_WINDOWS", filter {}
"WIN32",
}
configuration "Release" filter "configurations:Release"
optimize "Full" optimize "Size"
buildoptions "/Os" buildoptions { "/GL" }
linkoptions { "/IGNORE:4702", "/LTCG" }
defines { "NDEBUG" }
flags { "FatalCompileWarnings" }
filter {}
defines { filter "configurations:Debug"
"NDEBUG",
}
flags {
"FatalCompileWarnings",
}
configuration "Debug"
optimize "Debug" optimize "Debug"
defines { "DEBUG", "_DEBUG" }
defines { filter {}
"DEBUG",
"_DEBUG",
}
configuration {}
project "open-iw5" project "open-iw5"
kind "ConsoleApp" kind "ConsoleApp"
@ -103,7 +91,6 @@ workspace "open-iw5"
linkoptions "/IGNORE:4254 /DYNAMICBASE:NO /SAFESEH:NO /LARGEADDRESSAWARE" linkoptions "/IGNORE:4254 /DYNAMICBASE:NO /SAFESEH:NO /LARGEADDRESSAWARE"
linkoptions "/LAST:.main" linkoptions "/LAST:.main"
files { files {
"./src/**.rc", "./src/**.rc",
"./src/**.hpp", "./src/**.hpp",

View File

@ -23,15 +23,32 @@ namespace game
SL_GetStringOfSize_t SL_GetStringOfSize; SL_GetStringOfSize_t SL_GetStringOfSize;
Scr_AddEntityNum_t Scr_AddEntityNum;
Scr_Notify_t Scr_Notify;
Sys_ShowConsole_t Sys_ShowConsole; Sys_ShowConsole_t Sys_ShowConsole;
VM_Notify_t VM_Notify; VM_Notify_t VM_Notify;
BG_NetDataChecksum_t BG_NetDataChecksum;
LiveStorage_GetPersistentDataDefVersion_t LiveStorage_GetPersistentDataDefVersion;
LiveStorage_GetPersistentDataDefFormatChecksum_t LiveStorage_GetPersistentDataDefFormatChecksum;
SV_DirectConnect_t SV_DirectConnect;
SV_ClientEnterWorld_t SV_ClientEnterWorld;
SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString;
SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString;
decltype(longjmp)* _longjmp; decltype(longjmp)* _longjmp;
int* cmd_args; CmdArgs* sv_cmd_args;
int* cmd_argc; CmdArgs* cmd_args;
const char*** cmd_argv;
short* scrVarGlob; short* scrVarGlob;
char** scrMemTreePub; char** scrMemTreePub;
@ -49,6 +66,18 @@ namespace game
scr_classStruct_t* g_classMap; scr_classStruct_t* g_classMap;
int* svs_clientCount;
namespace mp
{
client_t* svs_clients;
}
namespace dedi
{
client_t* svs_clients;
}
void AddRefToValue(VariableValue* value) void AddRefToValue(VariableValue* value)
{ {
if (value->type == SCRIPT_OBJECT) if (value->type == SCRIPT_OBJECT)
@ -280,6 +309,31 @@ namespace game
} }
} }
void scr_add_string_dedi(const char* value)
{
static DWORD func = 0x4F1010;
__asm
{
mov edi, value
call func
retn
}
}
void Scr_AddString(const char* value)
{
if (is_dedi())
{
scr_add_string_dedi(value);
}
else if (is_mp())
{
reinterpret_cast<void(*)(const char*)>
(0x56AC00)(value);
}
}
const char* SL_ConvertToString(const unsigned int stringValue) const char* SL_ConvertToString(const unsigned int stringValue)
{ {
if (!stringValue) return nullptr; if (!stringValue) return nullptr;
@ -292,6 +346,71 @@ namespace game
{ {
return SL_GetStringOfSize(str, user, strlen(str) + 1, 7); return SL_GetStringOfSize(str, user, strlen(str) + 1, 7);
} }
__declspec(naked) void sv_send_client_game_state_mp(mp::client_t* client)
{
static DWORD func = 0x570FC0;
__asm
{
mov esi, client
call func
retn
}
}
void SV_SendClientGameState(mp::client_t* client)
{
if (is_mp())
{
sv_send_client_game_state_mp(client);
}
}
int SV_IsTestClient(int clientNum)
{
assert(clientNum < *svs_clientCount);
if (is_dedi())
{
return dedi::svs_clients[clientNum].bIsTestClient;
}
else if (is_mp())
{
return mp::svs_clients[clientNum].bIsTestClient;
}
return 0;
}
void SV_DropClient(mp::client_t* drop, const char* reason, bool tellThem)
{
if (is_mp())
{
reinterpret_cast<void(*)(mp::client_t*, const char*, bool)>
(0x570980)(drop, reason, tellThem);
}
}
void sv_drop_all_bots_mp()
{
for (auto i = 0; i < *svs_clientCount; i++)
{
if (mp::svs_clients[i].header.state != CS_FREE
&& mp::svs_clients[i].header.netchan.remoteAddress.type == NA_BOT)
{
SV_DropClient(&mp::svs_clients[i], "EXE_TIMEDOUT", 1);
}
}
}
void SV_DropAllBots()
{
if (is_mp())
{
sv_drop_all_bots_mp();
}
}
} }
launcher::mode mode = launcher::mode::none; launcher::mode mode = launcher::mode::none;
@ -344,15 +463,34 @@ namespace game
native::SL_GetStringOfSize = native::SL_GetStringOfSize_t(SELECT_VALUE(0x4E13F0, 0x564650, 0x4E7490)); native::SL_GetStringOfSize = native::SL_GetStringOfSize_t(SELECT_VALUE(0x4E13F0, 0x564650, 0x4E7490));
native::Scr_AddEntityNum = native::Scr_AddEntityNum_t(SELECT_VALUE(0x0, 0x56ABC0, 0x4EA2F0));
native::Scr_Notify = native::Scr_Notify_t(SELECT_VALUE(0x0, 0x52B190, 0x0));
native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590, 0)); native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590, 0));
native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720, 0x4EF450)); native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720, 0x4EF450));
native::BG_NetDataChecksum = native::BG_NetDataChecksum_t(SELECT_VALUE(0x0, 0x41BB20, 0x0));
native::LiveStorage_GetPersistentDataDefVersion = native::LiveStorage_GetPersistentDataDefVersion_t(
SELECT_VALUE(0x0, 0x548D60, 0x4D0390));
native::LiveStorage_GetPersistentDataDefFormatChecksum = native::LiveStorage_GetPersistentDataDefFormatChecksum_t(
SELECT_VALUE(0x0, 0x548D80, 0x4D03D0));
native::SV_DirectConnect = native::SV_DirectConnect_t(SELECT_VALUE(0x0, 0x572750, 0x4F74C0));
native::SV_ClientEnterWorld = native::SV_ClientEnterWorld_t(SELECT_VALUE(0x0, 0x571100, 0x0));
native::SV_Cmd_TokenizeString = native::SV_Cmd_TokenizeString_t(SELECT_VALUE(0x0, 0x545D40, 0x0));
native::SV_Cmd_EndTokenizedString = native::SV_Cmd_EndTokenizedString_t(SELECT_VALUE(0x0, 0x545D70, 0x0));
native::_longjmp = reinterpret_cast<decltype(longjmp)*>(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558)); native::_longjmp = reinterpret_cast<decltype(longjmp)*>(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558));
native::cmd_args = reinterpret_cast<int*>(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8)); native::sv_cmd_args = reinterpret_cast<native::CmdArgs*>(SELECT_VALUE(0x0, 0x1CAA998, 0x1B5E7D8));
native::cmd_argc = reinterpret_cast<int*>(SELECT_VALUE(0x1750794, 0x1C97914, 0x1B4563C)); native::cmd_args = reinterpret_cast<native::CmdArgs*>(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8));
native::cmd_argv = reinterpret_cast<const char***>(SELECT_VALUE(0x17507B4, 0x1C97934, 0x1B4565C));
native::scrVarGlob = reinterpret_cast<short*>(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800)); native::scrVarGlob = reinterpret_cast<short*>(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800));
native::scrMemTreePub = reinterpret_cast<char**>(SELECT_VALUE(0x196FB00, 0x1E32000, 0x1C152A4)); native::scrMemTreePub = reinterpret_cast<char**>(SELECT_VALUE(0x196FB00, 0x1E32000, 0x1C152A4));
@ -371,6 +509,11 @@ namespace game
native::g_classMap = reinterpret_cast<native::scr_classStruct_t*>(SELECT_VALUE(0x92D140, 0x8B4300, 0x7C0408)); native::g_classMap = reinterpret_cast<native::scr_classStruct_t*>(SELECT_VALUE(0x92D140, 0x8B4300, 0x7C0408));
native::svs_clientCount = reinterpret_cast<int*>(SELECT_VALUE(0x0, 0x4B5CF8C, 0x4A12E8C));
native::levelEntityId = reinterpret_cast<unsigned int*>(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C)); native::levelEntityId = reinterpret_cast<unsigned int*>(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C));
native::mp::svs_clients = reinterpret_cast<native::mp::client_t*>(0x4B5CF90);
native::dedi::svs_clients = reinterpret_cast<native::dedi::client_t*>(0x4A12E90);
} }
} }

View File

@ -38,17 +38,43 @@ namespace game
typedef unsigned int (*SL_GetStringOfSize_t)(const char* str, unsigned int user, unsigned int len, int type); typedef unsigned int (*SL_GetStringOfSize_t)(const char* str, unsigned int user, unsigned int len, int type);
extern SL_GetStringOfSize_t SL_GetStringOfSize; extern SL_GetStringOfSize_t SL_GetStringOfSize;
typedef void (*Scr_AddEntityNum_t)(int entnum, unsigned int classnum);
extern Scr_AddEntityNum_t Scr_AddEntityNum;
typedef void (*Scr_Notify_t)(gentity_s* ent, unsigned __int16 stringValue, unsigned int paramcount);
extern Scr_Notify_t Scr_Notify;
typedef void (*Sys_ShowConsole_t)(); typedef void (*Sys_ShowConsole_t)();
extern Sys_ShowConsole_t Sys_ShowConsole; extern Sys_ShowConsole_t Sys_ShowConsole;
typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top); typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top);
extern VM_Notify_t VM_Notify; extern VM_Notify_t VM_Notify;
typedef unsigned int (*BG_NetDataChecksum_t)();
extern BG_NetDataChecksum_t BG_NetDataChecksum;
typedef int (*LiveStorage_GetPersistentDataDefVersion_t)();
extern LiveStorage_GetPersistentDataDefVersion_t LiveStorage_GetPersistentDataDefVersion;
typedef unsigned int (*LiveStorage_GetPersistentDataDefFormatChecksum_t)();
extern LiveStorage_GetPersistentDataDefFormatChecksum_t LiveStorage_GetPersistentDataDefFormatChecksum;
typedef void (*SV_DirectConnect_t)(netadr_s from);
extern SV_DirectConnect_t SV_DirectConnect;
typedef void (*SV_ClientEnterWorld_t)(mp::client_t* client, usercmd_s* cmd);
extern SV_ClientEnterWorld_t SV_ClientEnterWorld;
typedef void (*SV_Cmd_TokenizeString_t)(const char* text_in);
extern SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString;
typedef void (*SV_Cmd_EndTokenizedString_t)();
extern SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString;
extern decltype(longjmp)* _longjmp; extern decltype(longjmp)* _longjmp;
extern int* cmd_args; extern CmdArgs* sv_cmd_args;
extern int* cmd_argc; extern CmdArgs* cmd_args;
extern const char*** cmd_argv;
extern short* scrVarGlob; extern short* scrVarGlob;
extern char** scrMemTreePub; extern char** scrMemTreePub;
@ -66,6 +92,18 @@ namespace game
extern scr_classStruct_t* g_classMap; extern scr_classStruct_t* g_classMap;
extern int* svs_clientCount;
namespace mp
{
extern client_t* svs_clients;
}
namespace dedi
{
extern client_t* svs_clients;
}
void AddRefToValue(VariableValue* value); void AddRefToValue(VariableValue* value);
void Conbuf_AppendText(const char* message); void Conbuf_AppendText(const char* message);
@ -82,9 +120,15 @@ namespace game
scr_call_t Scr_GetFunc(unsigned int index); scr_call_t Scr_GetFunc(unsigned int index);
void Scr_NotifyId(unsigned int id, unsigned int stringValue, unsigned int paramcount); void Scr_NotifyId(unsigned int id, unsigned int stringValue, unsigned int paramcount);
int Scr_SetObjectField(unsigned int classnum, int entnum, int offset); int Scr_SetObjectField(unsigned int classnum, int entnum, int offset);
void Scr_AddString(const char* value);
const char* SL_ConvertToString(unsigned int stringValue); const char* SL_ConvertToString(unsigned int stringValue);
unsigned int SL_GetString(const char* str, unsigned int user); unsigned int SL_GetString(const char* str, unsigned int user);
void SV_SendClientGameState(mp::client_t* client);
int SV_IsTestClient(int clientNum);
void SV_DropClient(mp::client_t* drop, const char* reason, bool tellThem);
void SV_DropAllBots();
} }
bool is_mp(); bool is_mp();

View File

@ -379,6 +379,15 @@ namespace game
int flags; int flags;
}; };
struct CmdArgs
{
int nesting;
int localClientNum[8];
int controllerIndex[8];
int argc[8];
const char** argv[8];
};
struct msg_t struct msg_t
{ {
int overflowed; int overflowed;
@ -546,5 +555,158 @@ namespace game
bool (__cdecl *domainFunc)(dvar_t*, DvarValue); bool (__cdecl *domainFunc)(dvar_t*, DvarValue);
dvar_t* hashNext; dvar_t* hashNext;
}; };
struct usercmd_s
{
int serverTime;
int buttons;
int angles[3];
unsigned int weapon;
unsigned int offHand;
char forwardmove;
char rightmove;
unsigned __int16 airburstMarkDistance;
unsigned __int16 meleeChargeEnt;
unsigned char meleeChargeDist;
char selectedLoc[2];
unsigned char selectedLocAngle;
char remoteControlAngles[2];
char remoteControlMove[3];
};
static_assert(sizeof(usercmd_s) == 0x2C);
struct entityState_s
{
int number;
};
struct gentity_s
{
entityState_s s;
};
enum clientState_t
{
CS_FREE = 0,
CS_ZOMBIE = 1,
CS_RECONNECTING = 2,
CS_CONNECTED = 3,
CS_CLIENTLOADING = 4,
CS_ACTIVE = 5,
};
enum netsrc_t
{
NS_CLIENT1 = 0x0,
NS_CLIENT2 = 0x1,
NS_CLIENT3 = 0x2,
NS_CLIENT4 = 0x3,
NS_MAXCLIENTS = 0x4,
NS_SERVER = 0x4,
NS_PACKET = 0x5,
NS_INVALID_NETSRC = 0x6,
};
enum netadrtype_t
{
NA_BOT = 0x0,
NA_BAD = 0x1,
NA_LOOPBACK = 0x2,
NA_BROADCAST = 0x3,
NA_IP = 0x4,
};
struct netadr_s
{
netadrtype_t type;
unsigned char ip[4];
unsigned __int16 port;
unsigned char ipx[10];
unsigned int addrHandleIndex;
};
static_assert(sizeof(netadr_s) == 24);
struct netProfileInfo_t // Unused
{
unsigned char __pad0[0x5E0];
};
struct netchan_t
{
int outgoingSequence;
netsrc_t sock;
int dropped;
int incomingSequence;
netadr_s remoteAddress;
int qport;
int fragmentSequence;
int fragmentLength;
unsigned char* fragmentBuffer;
int fragmentBufferSize;
int unsentFragments;
int unsentFragmentStart;
int unsentLength;
unsigned char* unsentBuffer;
int unsentBufferSize;
netProfileInfo_t prof;
};
static_assert(sizeof(netchan_t) == 0x630);
struct clientHeader_t
{
clientState_t state;
int sendAsActive;
int deltaMessage;
int rateDealyed;
int hasAckedBaselineData;
int hugeSnapshotSent;
netchan_t netchan;
float predictedOrigin[3];
int predictedOriginServerTime;
int migrationState;
float predictedVehicleOrigin[3];
int predictedVehicleServerTime;
};
static_assert(sizeof(clientHeader_t) == 0x66C);
namespace mp
{
struct client_t
{
clientHeader_t header;
const char* dropReason; // 0x66C
char userinfo[1024]; // 0x670
unsigned char __pad0[0x209B8];
gentity_s* gentity; // 0x21428
unsigned char __pad1[0x20886];
unsigned __int16 scriptId; // 0x41CB2
int bIsTestClient; // 0x41CB4
int serverId; // 0x41CB8
unsigned char __pad2[0x369DC];
};
static_assert(sizeof(mp::client_t) == 0x78698);
}
namespace dedi
{
struct client_t
{
clientHeader_t header;
const char* dropReason;
char userinfo[1024];
unsigned char __pad0[0x4123E];
unsigned __int16 scriptId;
int bIsTestClient; // 0x41CB0
int serverId;
unsigned char __pad1[0x369D8];
};
static_assert(sizeof(dedi::client_t) == 0x78690);
}
} }
} }

View File

@ -95,6 +95,8 @@ int main()
FARPROC entry_point; FARPROC entry_point;
enable_dpi_awareness(); enable_dpi_awareness();
std::srand(uint32_t(time(nullptr)));
{ {
auto premature_shutdown = true; auto premature_shutdown = true;
const auto _ = gsl::finally([&premature_shutdown]() const auto _ = gsl::finally([&premature_shutdown]()

View File

@ -31,21 +31,21 @@ void command::pre_destroy()
void command::dispatcher() void command::dispatcher()
{ {
const auto cmd_index = *game::native::cmd_args; const auto cmd_index = game::native::cmd_args->nesting;
const auto arg_count = game::native::cmd_argc[cmd_index]; const auto arg_count = game::native::cmd_args->argc[cmd_index];
if (arg_count < 1) return; if (arg_count < 1) return;
const auto command = utils::string::to_lower(game::native::cmd_argv[cmd_index][0]); const auto command = utils::string::to_lower(game::native::cmd_args->argv[cmd_index][0]);
const auto handler = callbacks_.find(command); const auto handler = callbacks_.find(command);
if (handler == callbacks_.end()) return; if (handler == callbacks_.end()) return;
std::vector<std::string> arguments; std::vector<std::string> arguments;
arguments.reserve(arg_count); arguments.reserve(arg_count);
for (auto i = 0; i < game::native::cmd_argc[cmd_index]; ++i) for (auto i = 0; i < game::native::cmd_args->argc[cmd_index]; ++i)
{ {
arguments.emplace_back(game::native::cmd_argv[cmd_index][i]); arguments.emplace_back(game::native::cmd_args->argv[cmd_index][i]);
} }
handler->second(arguments); handler->second(arguments);

184
src/module/test_clients.cpp Normal file
View File

@ -0,0 +1,184 @@
#include <std_include.hpp>
#include "test_clients.hpp"
#include "command.hpp"
#include "scheduler.hpp"
#include "utils/hook.hpp"
bool test_clients::can_add()
{
auto i = 0;
while (i < *game::native::svs_clientCount)
{
if (game::native::mp::svs_clients[i].header.state == game::native::clientState_t::CS_FREE)
{
// Free slot was found
break;
}
++i;
}
return i < *game::native::svs_clientCount;
}
game::native::gentity_s* test_clients::sv_add_test_client()
{
if (!test_clients::can_add())
{
return nullptr;
}
static auto bot_port = 0;
char user_info[1024] = {0};
// Most basic string the game will accept. Xuid and Xnaddr can be empty
_snprintf_s(user_info, _TRUNCATE,
"connect bot%d \"\\invited\\0\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\bot%d\\xuid\\%s\\xnaddr\\%s\\natType\\2\\protocol\\%d\\checksum\\%u\\statver\\%d %u\"",
bot_port, bot_port, "", "", 0x507C, game::native::BG_NetDataChecksum(),
game::native::LiveStorage_GetPersistentDataDefVersion(), game::native::LiveStorage_GetPersistentDataDefFormatChecksum());
game::native::netadr_s adr;
std::memset(&adr, 0, sizeof(game::native::netadr_s));
adr.type = game::native::netadrtype_t::NA_BOT;
adr.port = static_cast<std::uint16_t>(bot_port);
++bot_port;
game::native::SV_Cmd_TokenizeString(user_info);
game::native::SV_DirectConnect(adr);
game::native::SV_Cmd_EndTokenizedString();
// Find the bot
auto idx = 0;
while (idx < *game::native::svs_clientCount)
{
if (game::native::mp::svs_clients[idx].header.state == game::native::clientState_t::CS_FREE)
continue;
if (game::native::mp::svs_clients[idx].header.netchan.remoteAddress.type == adr.type
&& game::native::mp::svs_clients[idx].header.netchan.remoteAddress.port == adr.port)
break; // Found them
}
if (idx == *game::native::svs_clientCount)
{
// Failed to add test client
return nullptr;
}
auto client = &game::native::mp::svs_clients[idx];
client->bIsTestClient = 1;
game::native::SV_SendClientGameState(client);
game::native::usercmd_s cmd;
std::memset(&cmd, 0, sizeof(game::native::usercmd_s));
game::native::SV_ClientEnterWorld(client, &cmd);
assert(client->gentity != nullptr);
return client->gentity;
}
void test_clients::spawn()
{
auto* ent = test_clients::sv_add_test_client();
if (ent == nullptr)
return;
game::native::Scr_AddEntityNum(ent->s.number, 0);
scheduler::once([ent]()
{
game::native::Scr_AddString("autoassign");
game::native::Scr_AddString("team_marinesopfor");
game::native::Scr_Notify(ent, static_cast<std::uint16_t>(game::native::SL_GetString("menuresponse", 0)), 2);
scheduler::once([ent]()
{
const auto string = std::format("class{}", std::to_string(std::rand() % 5));
game::native::Scr_AddString(string.data());
game::native::Scr_AddString("changeclass");
game::native::Scr_Notify(ent, static_cast<std::uint16_t>(game::native::SL_GetString("menuresponse", 0)), 2);
});
});
}
void test_clients::scr_shutdown_system_stub(unsigned char sys)
{
game::native::SV_DropAllBots();
reinterpret_cast<void (*)(unsigned char)>(0x569E30)(sys);
}
__declspec(naked) void test_clients::reset_reliable_mp()
{
// client_t ptr is in esi
__asm
{
cmp [esi + 0x41CB4], 1 // bIsTestClient
jnz resume
push eax
mov eax, [esi + 0x20E70] // Move reliableSequence to eax
mov [esi + 0x20E74], eax // Move eax to reliableAcknowledge
resume:
push 0x5CE740 // SV_Netchan_Transmit
retn
}
}
bool test_clients::check_timeouts(const game::native::mp::client_t* cl)
{
return (!cl->bIsTestClient || cl->header.state == game::native::clientState_t::CS_ZOMBIE)
&& cl->header.netchan.remoteAddress.type != game::native::netadrtype_t::NA_LOOPBACK;
}
__declspec(naked) void test_clients::check_timeouts_stub_mp()
{
__asm
{
push eax
pushad
lea esi, [ebx - 0x2146C]
push esi
call test_clients::check_timeouts
add esp, 4
mov [esp + 0x20], eax
popad
pop eax
test al, al
push 0x576DD3
retn
}
}
void test_clients::post_load()
{
if (game::is_mp()) this->patch_mp();
else return; // No sp/dedi bots for now :(
command::add("spawnBot", [](const std::vector<std::string>& params)
{
// Because I am unable to expand the scheduler at the moment
// we only get one bot at the time
test_clients::spawn();
});
}
void test_clients::patch_mp()
{
utils::hook::nop(0x639803, 5); // LiveSteamServer_RunFrame
utils::hook(0x50C147, &test_clients::scr_shutdown_system_stub, HOOK_CALL).install()->quick(); // G_ShutdownGame
utils::hook(0x57BBF9, &test_clients::reset_reliable_mp, HOOK_CALL).install()->quick(); // SV_SendMessageToClient
utils::hook(0x576DCC, &test_clients::check_timeouts_stub_mp, HOOK_JUMP).install()->quick(); // SV_CheckTimeouts
}
REGISTER_MODULE(test_clients);

View File

@ -0,0 +1,25 @@
#pragma once
#include "loader/module_loader.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
class test_clients final : public module
{
public:
void post_load() override;
private:
static void patch_mp();
static bool can_add();
static game::native::gentity_s* sv_add_test_client();
static void spawn();
static void scr_shutdown_system_stub(unsigned char sys);
static void reset_reliable_mp();
static bool check_timeouts(const game::native::mp::client_t* cl);
static void check_timeouts_stub_mp();
};

View File

@ -66,7 +66,9 @@
#pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "ws2_32.lib")
#pragma warning(pop) #pragma warning(pop)
#pragma warning(disable: 4100) #pragma warning(disable: 4100)
#pragma warning(disable: 26812)
#include "resource.hpp" #include "resource.hpp"

Binary file not shown.