Add test client functionality
This commit is contained in:
parent
4f691b5f96
commit
c0a85c4f33
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -1,3 +1,3 @@
|
||||
@echo off
|
||||
git submodule update --init --recursive
|
||||
tools\premake5 %* vs2019
|
||||
tools\premake5 %* vs2022
|
||||
|
51
premake5.lua
51
premake5.lua
@ -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,22 @@ workspace "open-iw5"
|
||||
"No64BitChecks"
|
||||
}
|
||||
|
||||
configuration "windows"
|
||||
defines {
|
||||
"_WINDOWS",
|
||||
"WIN32",
|
||||
}
|
||||
filter "platforms:Win*"
|
||||
defines { "_WINDOWS", "WIN32" }
|
||||
filter {}
|
||||
|
||||
configuration "Release"
|
||||
optimize "Full"
|
||||
buildoptions "/Os"
|
||||
filter "configurations:Release"
|
||||
optimize "Size"
|
||||
buildoptions { "/GL" }
|
||||
linkoptions { "/IGNORE:4702", "/LTCG" }
|
||||
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 +91,6 @@ workspace "open-iw5"
|
||||
linkoptions "/IGNORE:4254 /DYNAMICBASE:NO /SAFESEH:NO /LARGEADDRESSAWARE"
|
||||
linkoptions "/LAST:.main"
|
||||
|
||||
|
||||
files {
|
||||
"./src/**.rc",
|
||||
"./src/**.hpp",
|
||||
|
@ -23,15 +23,32 @@ 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;
|
||||
|
||||
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 +66,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)
|
||||
@ -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)
|
||||
{
|
||||
if (!stringValue) return nullptr;
|
||||
@ -292,6 +346,71 @@ 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
|
||||
{
|
||||
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;
|
||||
@ -344,15 +463,34 @@ 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::_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 +509,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);
|
||||
}
|
||||
}
|
||||
|
@ -38,17 +38,43 @@ 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;
|
||||
|
||||
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 +92,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);
|
||||
@ -82,9 +120,15 @@ namespace game
|
||||
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();
|
||||
}
|
||||
|
||||
bool is_mp();
|
||||
|
@ -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,158 @@ 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]()
|
||||
|
@ -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);
|
||||
|
184
src/module/test_clients.cpp
Normal file
184
src/module/test_clients.cpp
Normal 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);
|
25
src/module/test_clients.hpp
Normal file
25
src/module/test_clients.hpp
Normal 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();
|
||||
};
|
@ -66,7 +66,9 @@
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
#pragma warning(disable: 4100)
|
||||
#pragma warning(disable: 26812)
|
||||
|
||||
#include "resource.hpp"
|
||||
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user