Refactor command & add client commands

This commit is contained in:
FutureRave 2022-03-10 19:50:36 +00:00
parent 800795b3a4
commit 7b3ba9d3d4
No known key found for this signature in database
GPG Key ID: E883E2BC9657D955
8 changed files with 525 additions and 42 deletions

View File

@ -7,6 +7,8 @@ namespace game
{
Cmd_AddCommand_t Cmd_AddCommand;
Cmd_RemoveCommand_t Cmd_RemoveCommand;
Com_Error_t Com_Error;
DB_LoadXAssets_t DB_LoadXAssets;
@ -45,6 +47,10 @@ namespace game
SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString;
SV_GameSendServerCommand_t SV_GameSendServerCommand;
SV_SendServerCommand_t SV_SendServerCommand;
XUIDToString_t XUIDToString;
decltype(longjmp)* _longjmp;
@ -70,6 +76,8 @@ namespace game
int* svs_clientCount;
gentity_s* g_entities;
namespace mp
{
client_t* svs_clients;
@ -386,7 +394,7 @@ namespace game
call func
popad
ret
retn
}
}
@ -443,6 +451,30 @@ namespace game
}
}
__declspec(naked) void client_command_dedi(int clientNum)
{
static DWORD func = 0x47EA40;
__asm
{
mov edi, clientNum
call func
retn
}
}
void ClientCommand(int clientNum)
{
if (is_dedi())
{
client_command_dedi(clientNum);
}
else if (is_mp())
{
reinterpret_cast<void(*)(int)>(0x502CB0)(clientNum);
}
}
int GetProtocolVersion()
{
return 0x507C;
@ -452,6 +484,61 @@ namespace game
{
addr->type = type;
}
__declspec(naked) void cbuf_add_text_dedi(LocalClientNum_t localClientNum, const char* text)
{
static DWORD func = 0x4CB5D0;
__asm
{
mov eax, text
push localClientNum
call func
add esp, 4h
retn
}
}
void Cbuf_AddText(LocalClientNum_t localClientNum, const char* text)
{
if (is_dedi())
{
cbuf_add_text_dedi(localClientNum, text);
}
else
{
reinterpret_cast<void(*)(LocalClientNum_t, const char*)>
(SELECT_VALUE(0x457C90, 0x545680, 0x0))(localClientNum, text);
}
}
__declspec(naked) void teleport_player_dedi(gentity_s* player, float* origin, float* angles)
{
static DWORD func = 0x48B840;
__asm
{
mov eax, player
mov ecx, origin
push angles
call func
add esp, 4h
retn
}
}
void TeleportPlayer(gentity_s* player, float* origin, float* angles)
{
if (is_dedi())
{
teleport_player_dedi(player, origin, angles);
}
else if (is_mp())
{
reinterpret_cast<void(*)(gentity_s*, float*, float*)>
(0x50D840)(player, origin, angles);
}
}
}
launcher::mode mode = launcher::mode::none;
@ -487,6 +574,8 @@ namespace game
native::Cmd_AddCommand = native::Cmd_AddCommand_t(SELECT_VALUE(0x558820, 0x545DF0, 0));
native::Cmd_RemoveCommand = native::Cmd_RemoveCommand_t(SELECT_VALUE(0x443A30, 0x545E20, 0x4CC060));
native::Com_Error = native::Com_Error_t(SELECT_VALUE(0x425540, 0x555450, 0x4D93F0));
native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770));
@ -528,6 +617,10 @@ namespace game
native::SV_Cmd_EndTokenizedString = native::SV_Cmd_EndTokenizedString_t(SELECT_VALUE(0x0, 0x545D70, 0x0));
native::SV_GameSendServerCommand = native::SV_GameSendServerCommand_t(SELECT_VALUE(0x0, 0x573220, 0x0));
native::SV_SendServerCommand = native::SV_SendServerCommand_t(SELECT_VALUE(0x0, 0x575DE0, 0x4FD5A0));
native::XUIDToString = native::XUIDToString_t(SELECT_VALUE(0x4FAA30, 0x55CC20, 0x0));
native::_longjmp = reinterpret_cast<decltype(longjmp)*>(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558));
@ -558,5 +651,7 @@ namespace game
native::mp::svs_clients = reinterpret_cast<native::mp::client_t*>(0x4B5CF90);
native::dedi::svs_clients = reinterpret_cast<native::dedi::client_t*>(0x4A12E90);
native::g_entities = reinterpret_cast<native::gentity_s*>(SELECT_VALUE(0, 0x1A66E28, 0x191B900));
}
}

View File

@ -14,6 +14,9 @@ namespace game
typedef void (*Cmd_AddCommand_t)(const char* cmdName, void (*function)(), cmd_function_t* allocedCmd);
extern Cmd_AddCommand_t Cmd_AddCommand;
typedef void (*Cmd_RemoveCommand_t)(const char* cmdName);
extern Cmd_RemoveCommand_t Cmd_RemoveCommand;
typedef void (*Com_Error_t)(int code, const char* fmt, ...);
extern Com_Error_t Com_Error;
@ -71,11 +74,18 @@ namespace game
typedef void (*SV_Cmd_EndTokenizedString_t)();
extern SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString;
typedef void (*SV_GameSendServerCommand_t)(int clientNum, svscmd_type type, const char* text);
extern SV_GameSendServerCommand_t SV_GameSendServerCommand;
typedef void (*SV_SendServerCommand_t)(dedi::client_t* cl, svscmd_type type, const char* fmt, ...);
extern SV_SendServerCommand_t SV_SendServerCommand;
typedef void (*XUIDToString_t)(const unsigned __int64* xuid, char* str);
extern XUIDToString_t XUIDToString;
extern decltype(longjmp)* _longjmp;
constexpr auto CMD_MAX_NESTING = 8;
extern CmdArgs* sv_cmd_args;
extern CmdArgs* cmd_args;
@ -97,6 +107,8 @@ namespace game
extern int* svs_clientCount;
extern gentity_s* g_entities;
namespace mp
{
extern client_t* svs_clients;
@ -135,9 +147,15 @@ namespace game
void SV_DropClient(mp::client_t* drop, const char* reason, bool tellThem);
void SV_DropAllBots();
void ClientCommand(int clientNum);
int GetProtocolVersion();
void NetAdr_SetType(netadr_s* addr, netadrtype_t type);
void Cbuf_AddText(LocalClientNum_t localClientNum, const char* text);
void TeleportPlayer(gentity_s* player, float* origin, float* angles);
}
bool is_mp();

View File

@ -4,6 +4,11 @@ namespace game
{
namespace native
{
typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
enum bdLobbyErrorCode : uint32_t
{
BD_NO_ERROR = 0x0,
@ -369,6 +374,23 @@ namespace game
};
#pragma pack(pop)
enum svscmd_type
{
SV_CMD_CAN_IGNORE,
SV_CMD_RELIABLE,
};
enum LocalClientNum_t
{
LOCAL_CLIENT_0 = 0,
LOCAL_CLIENT_1 = 1,
LOCAL_CLIENT_2 = 2,
LOCAL_CLIENT_3 = 3,
LOCAL_CLIENT_LAST = 3,
LOCAL_CLIENT_COUNT = 4,
LOCAL_CLIENT_INVALID = -1,
};
struct cmd_function_t
{
cmd_function_t* next;
@ -594,6 +616,28 @@ namespace game
static_assert(sizeof(gclient_s) == 0x3980);
enum entityFlag
{
FL_GODMODE = 0x1,
FL_DEMI_GODMODE = 0x2,
FL_NOTARGET = 0x4,
FL_NO_KNOCKBACK = 0x8,
FL_NO_RADIUS_DAMAGE = 0x10,
FL_SUPPORTS_LINKTO = 0x1000,
FL_NO_AUTO_ANIM_UPDATE = 0x2000,
FL_GRENADE_TOUCH_DAMAGE = 0x4000,
FL_STABLE_MISSILES = 0x20000,
FL_REPEAT_ANIM_UPDATE = 0x40000,
FL_VEHICLE_TARGET = 0x80000,
FL_GROUND_ENT = 0x100000,
FL_CURSOR_HINT = 0x200000,
FL_MISSILE_ATTRACTOR = 0x800000,
FL_WEAPON_BEING_GRABBED = 0x1000000,
FL_DELETE = 0x2000000,
FL_BOUNCE = 0x4000000,
FL_MOVER_SLIDE = 0x8000000
};
struct entityState_s
{
int number;

View File

@ -0,0 +1,142 @@
#include <std_include.hpp>
#include "loader/module_loader.hpp"
#include "command.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "utils/string.hpp"
class client_command final : public module
{
public:
void post_load() override
{
if (game::is_mp())
{
add_mp_client_commands();
}
}
private:
static void send_msg_to_client(int client_num, game::native::svscmd_type type, const char* string)
{
if (game::is_dedi())
{
game::native::SV_SendServerCommand(&game::native::dedi::svs_clients[client_num],
type, string);
}
else if (game::is_mp())
{
game::native::SV_GameSendServerCommand(client_num, type, string);
}
}
// I know this is supposed to check sv_cheats but it's not even a registered dvar so why bother
static bool cheats_ok(game::native::gentity_s* ent)
{
if (ent->health < 1)
{
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65));
return false;
}
return true;
}
static void add_mp_client_commands()
{
command::add_sv("noclip", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params)
{
if (!cheats_ok(ent))
return;
ent->client->flags ^= 1;
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"%s\"", 0x65, (ent->client->flags & 1) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
});
command::add_sv("ufo", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params)
{
if (!cheats_ok(ent))
return;
ent->client->flags ^= 2;
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"%s\"", 0x65, (ent->client->flags & 2) ? "GAME_UFOON" : "GAME_UFOOFF"));
});
command::add_sv("god", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params)
{
if (!cheats_ok(ent))
return;
ent->flags = game::native::FL_GODMODE;
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"%s\"", 0x65, (ent->flags & game::native::FL_GODMODE)
? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
});
command::add_sv("demigod", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params)
{
if (!cheats_ok(ent))
return;
ent->flags = game::native::FL_DEMI_GODMODE;
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"%s\"", 0x65, (ent->flags & game::native::FL_DEMI_GODMODE)
? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
});
command::add_sv("notarget", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params)
{
if (!cheats_ok(ent))
return;
ent->flags = game::native::FL_NOTARGET;
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"%s\"", 0x65, (ent->flags & game::native::FL_NOTARGET)
? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
});
command::add_sv("setviewpos", [](game::native::gentity_s* ent, [[maybe_unused]] const command::params_sv& params)
{
if (!cheats_ok(ent))
return;
game::native::vec3_t origin, angles{0.f, 0.f, 0.f};
if (params.size() < 4 || params.size() > 6)
{
send_msg_to_client(ent->s.number, game::native::SV_CMD_CAN_IGNORE,
utils::string::va("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\"", 0x65));
return;
}
for (auto i = 0; i < 3; i++)
{
origin[i] = std::strtof(params.get(i + 1), nullptr);
}
if (params.size() > 4)
{
angles[1] = std::strtof(params.get(4), nullptr); // Yaw
}
if (params.size() == 6u)
{
angles[0] = std::strtof(params.get(5), nullptr); // Pitch
}
game::native::TeleportPlayer(ent, origin, angles);
});
}
};
REGISTER_MODULE(client_command);

View File

@ -1,54 +1,188 @@
#include <std_include.hpp>
#include "command.hpp"
#include "utils/string.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "scheduler.hpp"
#include "utils/hook.hpp"
utils::memory::allocator command::allocator_;
std::mutex command::mutex_;
std::unordered_map<std::string, std::function<void(const std::vector<std::string>&)>> command::callbacks_;
std::unordered_map<std::string, std::function<void(const command::params&)>> command::handlers;
std::unordered_map<std::string, std::function<void(game::native::gentity_s*, command::params_sv&)>> command::handlers_sv;
void command::add(const std::string& name, const std::function<void(const std::vector<std::string>&)>& callback)
command::params::params()
: nesting_(game::native::cmd_args->nesting)
{
std::lock_guard _(mutex_);
callbacks_[utils::string::to_lower(name)] = callback;
assert(game::native::cmd_args->nesting < game::native::CMD_MAX_NESTING);
}
const auto cmd_name = allocator_.duplicate_string(name);
const auto cmd_function = allocator_.allocate<game::native::cmd_function_t>();
int command::params::size() const
{
return game::native::cmd_args->argc[this->nesting_];
}
game::native::Cmd_AddCommand(cmd_name, dispatcher, cmd_function);
const char* command::params::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::native::cmd_args->argv[this->nesting_][index];
}
std::string command::params::join(const int index) const
{
std::string result;
for (auto i = index; i < this->size(); i++)
{
if (i > index) result.append(" ");
result.append(this->get(i));
}
return result;
}
command::params_sv::params_sv()
: nesting_(game::native::sv_cmd_args->nesting)
{
assert(game::native::sv_cmd_args->nesting < game::native::CMD_MAX_NESTING);
}
int command::params_sv::size() const
{
return game::native::sv_cmd_args->argc[this->nesting_];
}
const char* command::params_sv::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::native::sv_cmd_args->argv[this->nesting_][index];
}
std::string command::params_sv::join(const int index) const
{
std::string result;
for (auto i = index; i < this->size(); i++)
{
if (i > index) result.append(" ");
result.append(this->get(i));
}
return result;
}
void command::add_raw(const char* name, void (*callback)())
{
game::native::Cmd_AddCommand(name, callback, command::allocator_.allocate<game::native::cmd_function_t>());
}
void command::add(const char* name, const std::function<void(const command::params&)>& callback)
{
const auto command = utils::string::to_lower(name);
if (handlers.find(command) == handlers.end())
{
add_raw(name, main_handler);
}
handlers[command] = callback;
}
void command::add(const char* name, const std::function<void()>& callback)
{
add(name, [callback](const command::params&)
{
callback();
});
}
void command::add_sv(const char* name, std::function<void(game::native::gentity_s*, const command::params_sv&)> callback)
{
// Since the console is not usable there is no point in calling add_raw
const auto command = utils::string::to_lower(name);
if (handlers_sv.find(command) == handlers_sv.end())
{
handlers_sv[command] = callback;
}
}
void command::main_handler()
{
params params;
const auto command = utils::string::to_lower(params[0]);
const auto got = handlers.find(command);
if (got != handlers.end())
{
got->second(params);
}
}
void command::client_command_stub(int client_num)
{
const auto entity = &game::native::g_entities[client_num];
if (entity->client == nullptr)
{
return;
}
params_sv params;
const auto command = utils::string::to_lower(params[0]);
const auto got = handlers_sv.find(command);
if (got != handlers_sv.end())
{
got->second(entity, params);
}
game::native::ClientCommand(client_num);
}
__declspec(naked) void command::client_command_dedi_stub()
{
__asm
{
pushad
push edi
call client_command_stub
add esp, 4h
popad
retn
}
}
void command::post_load()
{
if (game::is_mp())
{
utils::hook(0x57192A, &command::client_command_stub, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommand
}
else if (game::is_dedi())
{
utils::hook(0x4F96B5, &command::client_command_dedi_stub, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommand
}
}
void command::pre_destroy()
{
std::lock_guard _(mutex_);
if (!callbacks_.empty())
for (const auto& [key, val] : command::handlers)
{
callbacks_.clear();
}
}
void command::dispatcher()
{
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_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_args->argc[cmd_index]; ++i)
{
arguments.emplace_back(game::native::cmd_args->argv[cmd_index][i]);
handlers.erase(key);
game::native::Cmd_RemoveCommand(key.data());
}
handler->second(arguments);
command::allocator_.clear();
}
REGISTER_MODULE(command);

View File

@ -1,18 +1,68 @@
#pragma once
#include "loader/module_loader.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "utils/memory.hpp"
class command final : public module
{
public:
static void add(const std::string& name, const std::function<void(const std::vector<std::string>&)>& callback);
class params
{
public:
params();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
const char* operator[](const int index) const
{
return this->get(index);
}
private:
int nesting_;
};
class params_sv
{
public:
params_sv();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
const char* operator[](const int index) const
{
return this->get(index);
}
private:
int nesting_;
};
static void add(const char* name, const std::function<void(const params&)>& callback);
static void add(const char* name, const std::function<void()>& callback);
static void add_sv(const char* name, std::function<void(game::native::gentity_s*, const params_sv&)> callback);
void post_load() override;
void pre_destroy() override;
private:
static utils::memory::allocator allocator_;
static std::mutex mutex_;
static std::unordered_map<std::string, std::function<void(const std::vector<std::string>&)>> callbacks_;
static void dispatcher();
static std::unordered_map<std::string, std::function<void(const params&)>> handlers;
static std::unordered_map<std::string, std::function<void(game::native::gentity_s*, params_sv&)>> handlers_sv;
static void main_handler();
static void client_command_stub(int client_num);
static void client_command_dedi_stub();
static void add_raw(const char* name, void (*callback)());
};

View File

@ -16,7 +16,7 @@ public:
if (game::is_mp())
{
// Set dvar limit
static const auto cg_fov_limit = 90.0f;
static const auto cg_fov_limit = 120.0f;
utils::hook::set(0x455148, &cg_fov_limit);
// Prevent value change via internal scripts

View File

@ -158,7 +158,7 @@ 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)
command::add("spawnBot", []()
{
// Because I am unable to expand the scheduler at the moment
// we only get one bot at the time