Merge pull request #35 from diamante0018/master

Testclients
This commit is contained in:
Maurice Heumann 2022-03-10 19:07:03 +01:00 committed by GitHub
commit 2bb1fcdcf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 690 additions and 50 deletions

View File

@ -11,7 +11,7 @@ on:
jobs:
build:
name: Build binaries
runs-on: windows-latest
runs-on: windows-2022
strategy:
matrix:
configuration:
@ -35,11 +35,10 @@ jobs:
lfs: false
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
uses: microsoft/setup-msbuild@v1.1
- name: Generate project files
#run: tools/premake5 vs2019 --ci-build
run: tools/premake5 vs2019
run: tools/premake5 vs2022
- name: Set up problem matching
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)
![license](https://img.shields.io/github/license/momo5502/open-iw5.svg)
![forks](https://img.shields.io/github/forks/momo5502/open-iw5.svg)

View File

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

View File

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

View File

@ -23,15 +23,34 @@ namespace game
SL_GetStringOfSize_t SL_GetStringOfSize;
Scr_AddEntityNum_t Scr_AddEntityNum;
Scr_Notify_t Scr_Notify;
Sys_ShowConsole_t Sys_ShowConsole;
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;
XUIDToString_t XUIDToString;
decltype(longjmp)* _longjmp;
int* cmd_args;
int* cmd_argc;
const char*** cmd_argv;
CmdArgs* sv_cmd_args;
CmdArgs* cmd_args;
short* scrVarGlob;
char** scrMemTreePub;
@ -49,6 +68,18 @@ namespace game
scr_classStruct_t* g_classMap;
int* svs_clientCount;
namespace mp
{
client_t* svs_clients;
}
namespace dedi
{
client_t* svs_clients;
}
void AddRefToValue(VariableValue* value)
{
if (value->type == SCRIPT_OBJECT)
@ -154,6 +185,31 @@ namespace game
return scrMemTreeGlob + 12 * size_t(MT_AllocIndex(numBytes, type));
}
__declspec(naked) dvar_t* dvar_find_malleable_var(const char* dvarName)
{
static DWORD func = 0x531320;
__asm
{
mov edi, dvarName
call func
retn
}
}
dvar_t* Dvar_FindVar(const char* dvarName)
{
if (is_dedi())
{
return dvar_find_malleable_var(dvarName);
}
else
{
return reinterpret_cast<dvar_t*(*)(const char*)>
(SELECT_VALUE(0x539550, 0x5BDCC0, 0x0))(dvarName);
}
}
const float* Scr_AllocVector(const float* v)
{
const auto mem = static_cast<DWORD*>(MT_Alloc(16, 2));
@ -280,6 +336,31 @@ namespace game
}
}
__declspec(naked) 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)
{
if (!stringValue) return nullptr;
@ -292,6 +373,85 @@ namespace game
{
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
{
pushad
mov esi, [esp + 0x20 + 0x4]
call func
popad
ret
}
}
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", true);
}
}
}
void SV_DropAllBots()
{
if (is_mp())
{
sv_drop_all_bots_mp();
}
}
int GetProtocolVersion()
{
return 0x507C;
}
void NetAdr_SetType(netadr_s* addr, netadrtype_t type)
{
addr->type = type;
}
}
launcher::mode mode = launcher::mode::none;
@ -344,15 +504,36 @@ namespace game
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::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::XUIDToString = native::XUIDToString_t(SELECT_VALUE(0x4FAA30, 0x55CC20, 0x0));
native::_longjmp = reinterpret_cast<decltype(longjmp)*>(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558));
native::cmd_args = reinterpret_cast<int*>(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8));
native::cmd_argc = reinterpret_cast<int*>(SELECT_VALUE(0x1750794, 0x1C97914, 0x1B4563C));
native::cmd_argv = reinterpret_cast<const char***>(SELECT_VALUE(0x17507B4, 0x1C97934, 0x1B4565C));
native::sv_cmd_args = reinterpret_cast<native::CmdArgs*>(SELECT_VALUE(0x0, 0x1CAA998, 0x1B5E7D8));
native::cmd_args = reinterpret_cast<native::CmdArgs*>(SELECT_VALUE(0x1750750, 0x1C978D0, 0x1B455F8));
native::scrVarGlob = reinterpret_cast<short*>(SELECT_VALUE(0x19AFC80, 0x1E72180, 0x1D3C800));
native::scrMemTreePub = reinterpret_cast<char**>(SELECT_VALUE(0x196FB00, 0x1E32000, 0x1C152A4));
@ -371,6 +552,11 @@ namespace game
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::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,46 @@ namespace game
typedef unsigned int (*SL_GetStringOfSize_t)(const char* str, unsigned int user, unsigned int len, int type);
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)();
extern Sys_ShowConsole_t Sys_ShowConsole;
typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top);
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;
typedef void (*XUIDToString_t)(const unsigned __int64* xuid, char* str);
extern XUIDToString_t XUIDToString;
extern decltype(longjmp)* _longjmp;
extern int* cmd_args;
extern int* cmd_argc;
extern const char*** cmd_argv;
extern CmdArgs* sv_cmd_args;
extern CmdArgs* cmd_args;
extern short* scrVarGlob;
extern char** scrMemTreePub;
@ -66,6 +95,18 @@ namespace game
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 Conbuf_AppendText(const char* message);
@ -76,15 +117,27 @@ namespace game
void* MT_Alloc(int numBytes, int type);
dvar_t* Dvar_FindVar(const char* dvarName);
const float* Scr_AllocVector(const float* v);
void Scr_ClearOutParams();
scr_entref_t Scr_GetEntityIdRef(unsigned int id);
scr_call_t Scr_GetFunc(unsigned int index);
void Scr_NotifyId(unsigned int id, unsigned int stringValue, unsigned int paramcount);
int Scr_SetObjectField(unsigned int classnum, int entnum, int offset);
void Scr_AddString(const char* value);
const char* SL_ConvertToString(unsigned int stringValue);
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();
int GetProtocolVersion();
void NetAdr_SetType(netadr_s* addr, netadrtype_t type);
}
bool is_mp();

View File

@ -379,6 +379,15 @@ namespace game
int flags;
};
struct CmdArgs
{
int nesting;
int localClientNum[8];
int controllerIndex[8];
int argc[8];
const char** argv[8];
};
struct msg_t
{
int overflowed;
@ -546,5 +555,198 @@ namespace game
bool (__cdecl *domainFunc)(dvar_t*, DvarValue);
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 playerState_s
{
unsigned char __pad0[0x4EC];
unsigned int perks[0x2];
unsigned int perkSlots[0x9];
unsigned char __pad1[0x2DE8];
};
struct gclient_s
{
playerState_s ps;
unsigned char __pad0[0x2CC];
int flags;
unsigned char __pad1[0x3B0];
};
static_assert(sizeof(gclient_s) == 0x3980);
struct entityState_s
{
int number;
};
struct EntHandle
{
unsigned __int16 number;
unsigned __int16 infoIndex;
};
struct gentity_s
{
entityState_s s;
unsigned char __pad0[0x154];
gclient_s* client; // 0x158
unsigned char __pad1[0x28];
int flags;
int eventTime;
int clipmask;
int processedFrame;
EntHandle parent;
int nextthink;
int health;
int maxHealth;
int damage;
int count;
unsigned char __pad2[0xc8];
};
static_assert(sizeof(gentity_s) == 0x274);
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;
enable_dpi_awareness();
std::srand(uint32_t(time(nullptr)));
{
auto premature_shutdown = true;
const auto _ = gsl::finally([&premature_shutdown]()

View File

@ -31,21 +31,21 @@ void command::pre_destroy()
void command::dispatcher()
{
const auto cmd_index = *game::native::cmd_args;
const auto arg_count = game::native::cmd_argc[cmd_index];
const auto cmd_index = game::native::cmd_args->nesting;
const auto arg_count = game::native::cmd_args->argc[cmd_index];
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);
if (handler == callbacks_.end()) return;
std::vector<std::string> arguments;
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);

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

@ -0,0 +1,183 @@
#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;
}
assert(game::native::Dvar_FindVar("sv_running")->current.enabled);
assert(game::native::Dvar_FindVar("sv_maxclients")->current.integer <= *game::native::svs_clientCount);
static auto bot_port = 0;
char user_info[1024] = {0};
char xuid[32] = {0};
const std::uint64_t random_xuid = std::rand();
game::native::XUIDToString(&random_xuid, xuid);
// Most basic string the game will accept.
_snprintf_s(user_info, _TRUNCATE,
"connect bot%d \"\\invited\\0\\cg_predictItems\\1\\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\\qport\\%d\"",
bot_port, bot_port, xuid, "", game::native::GetProtocolVersion(), game::native::BG_NetDataChecksum(),
game::native::LiveStorage_GetPersistentDataDefVersion(), game::native::LiveStorage_GetPersistentDataDefFormatChecksum(), bot_port);
game::native::netadr_s adr;
std::memset(&adr, 0, sizeof(game::native::netadr_s));
game::native::SV_Cmd_TokenizeString(user_info);
adr.port = static_cast<std::uint16_t>(bot_port++);
game::native::NetAdr_SetType(&adr, game::native::netadrtype_t::NA_BOT);
game::native::SV_DirectConnect(adr);
game::native::SV_Cmd_EndTokenizedString();
// Find the bot
auto idx = 1;
for (idx = 0; idx < *game::native::svs_clientCount; idx++)
{
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);
}
void test_clients::scr_shutdown_system_mp_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
mov eax, [esi + 0x20E70] // Move reliableSequence to eax
mov [esi + 0x20E74], eax // Move eax to reliableAcknowledge
resume:
push 0x5CE740 // Sys_Milliseconds
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", []([[maybe_unused]] 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::nop(0x5CD65A, 5); // Do not crash for silly fatal error
utils::hook::set<BYTE>(0x572879, 0xEB); // Skip checks in SV_DirectConnect
utils::hook::set<BYTE>(0x5728D4, 0xEB);
utils::hook::set<BYTE>(0x57293D, 0xEB);
utils::hook(0x50C147, &test_clients::scr_shutdown_system_mp_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_mp_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 warning(pop)
#pragma warning(disable: 4100)
#pragma warning(disable: 26812)
#include "resource.hpp"

View File

@ -34,6 +34,7 @@ namespace utils::flags
if (!parsed)
{
parse_flags(enabled_flags);
parsed = true;
}
for (const auto& entry : enabled_flags)

Binary file not shown.