fixes for iw4m, gsc, notifes and more

This commit is contained in:
Diavolo 2023-01-09 16:53:51 +01:00
parent e7de6af00a
commit 549064fa64
13 changed files with 467 additions and 6 deletions

View File

@ -0,0 +1,60 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include "command.hpp"
#include "client_command.hpp"
namespace chat
{
namespace
{
void cmd_say_f(game::gentity_s* ent, const command::params_sv& params)
{
if (params.size() < 2)
{
return;
}
auto mode = 0;
if (std::strcmp(params[0], "say_team") == 0)
{
mode = 1;
}
auto p = params.join(1);
game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, p.data() + 1); // Skip special char
game::Scr_Notify_Canon(ent, game::Scr_CanonHash(params[0]), 1);
game::G_Say(ent, nullptr, mode, p.data());
}
void cmd_chat_f(game::gentity_s* ent, const command::params_sv& params)
{
auto p = params.join(1);
// Not a mistake! + 2 is necessary for the GSC script to receive only the actual chat text
game::Scr_AddString(game::SCRIPTINSTANCE_SERVER, p.data() + 2);
game::Scr_Notify_Canon(ent, game::Scr_CanonHash(params[0]), 1);
utils::hook::invoke<void>(0x140298E70, ent, p.data());
}
}
class component final : public server_component
{
public:
void post_unpack() override
{
client_command::add("say", cmd_say_f);
client_command::add("say_team", cmd_say_f);
client_command::add("chat", cmd_chat_f);
}
};
}
REGISTER_COMPONENT(chat::component)

View File

@ -0,0 +1,57 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "command.hpp"
#include "client_command.hpp"
namespace client_command
{
namespace
{
std::unordered_map<std::string, callback> handlers;
void client_command_stub(const int client_num)
{
const auto ent = &game::g_entities[client_num];
if (ent->client == nullptr)
{
// Client is not fully in game
return;
}
const command::params_sv params;
const auto command = utils::string::to_lower(params.get(0));
if (const auto got = handlers.find(command); got != handlers.end())
{
got->second(ent, params);
return;
}
utils::hook::invoke<void>(0x140295C40, client_num);
}
}
void add(const std::string& name, const callback& cmd)
{
const auto command = utils::string::to_lower(name);
handlers[command] = cmd;
}
class component final : public server_component
{
public:
void post_unpack() override
{
utils::hook::call(0x14052F81B_g, client_command_stub);
}
};
}
REGISTER_COMPONENT(client_command::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace client_command
{
using callback = std::function<void(game::gentity_s* ent, const command::params_sv& params)>;
void add(const std::string& name, const callback& cmd);
}

View File

@ -18,6 +18,12 @@ namespace command
return command_map;
}
std::unordered_map<std::string, sv_command_param_function>& get_sv_command_map()
{
static std::unordered_map<std::string, sv_command_param_function> command_map{};
return command_map;
}
void execute_custom_command()
{
const params params{};
@ -31,6 +37,19 @@ namespace command
}
}
void execute_custom_sv_command()
{
const params_sv params{};
const auto command = utils::string::to_lower(params[0]);
auto& map = get_sv_command_map();
const auto entry = map.find(command);
if (entry != map.end())
{
entry->second(params);
}
}
game::CmdArgs* get_cmd_args()
{
return game::Sys_GetTLS()->cmdArgs;
@ -67,6 +86,40 @@ namespace command
return get_cmd_args()->argc[this->nesting_];
}
params_sv::params_sv()
: nesting_(game::sv_cmd_args->nesting)
{
assert(this->nesting_ < game::CMD_MAX_NESTING);
}
int params_sv::size() const
{
return game::sv_cmd_args->argc[this->nesting_];
}
const char* params_sv::get(const int index) const
{
if (index >= this->size())
{
return "";
}
return game::sv_cmd_args->argv[this->nesting_][index];
}
std::string 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;
}
const char* params::get(const int index) const
{
if (index >= this->size())
@ -118,6 +171,27 @@ namespace command
game::Cmd_AddCommandInternal(cmd_string, execute_custom_command, cmd_function);
cmd_function->autoComplete = 1;
}
void add_sv(const std::string& command, sv_command_param_function function)
{
auto lower_command = utils::string::to_lower(command);
auto& map = get_sv_command_map();
const auto is_registered = map.contains(lower_command);
map[std::move(lower_command)] = std::move(function);
if (is_registered)
{
return;
}
auto& allocator = *utils::memory::get_allocator();
const auto* cmd_string = allocator.duplicate_string(command);
game::Cmd_AddCommandInternal(cmd_string, game::Cbuf_AddServerText_f, allocator.allocate<game::cmd_function_s>());
game::Cmd_AddServerCommandInternal(cmd_string, execute_custom_sv_command, allocator.allocate<game::cmd_function_s>());
}
}
REGISTER_COMPONENT(command::component)

View File

@ -7,11 +7,29 @@ namespace command
public:
params();
int size() const;
const char* get(int index) const;
std::string join(int index) const;
[[nodiscard]] int size() const;
[[nodiscard]] const char* get(int index) const;
[[nodiscard]] std::string join(int index) const;
const char* operator[](const int index) const
[[nodiscard]] const char* operator[](const int index) const
{
return this->get(index); //
}
private:
int nesting_;
};
class params_sv
{
public:
params_sv();
[[nodiscard]] int size() const;
[[nodiscard]] const char* get(int index) const;
[[nodiscard]] std::string join(int index) const;
[[nodiscard]] const char* operator[](const int index) const
{
return this->get(index); //
}
@ -22,7 +40,10 @@ namespace command
using command_function = std::function<void()>;
using command_param_function = std::function<void(const params&)>;
using sv_command_param_function = std::function<void(const params_sv&)>;
void add(const std::string& command, command_function function);
void add(const std::string& command, command_param_function function);
void add_sv(const std::string& command, sv_command_param_function function);
}

View File

@ -0,0 +1,51 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "command.hpp"
#include "console_command.hpp"
namespace console_command
{
namespace
{
utils::hook::detour console_command_hook;
std::unordered_map<std::string, callback> handlers;
int console_command_stub()
{
const command::params params;
const auto command = utils::string::to_lower(params.get(0));
if (const auto got = handlers.find(command); got != handlers.end())
{
got->second(params);
return 1;
}
return console_command_hook.invoke<int>();
}
}
void add_console(const std::string& name, const callback& cmd)
{
const auto command = utils::string::to_lower(name);
handlers[command] = cmd;
}
class component final : public server_component
{
public:
void post_unpack() override
{
console_command_hook.create(0x1402FF8C0_g, &console_command_stub);
}
};
}
REGISTER_COMPONENT(console_command::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace console_command
{
using callback = std::function<void(const command::params& params)>;
void add_console(const std::string& name, const callback& cmd);
}

View File

@ -9,6 +9,10 @@ namespace dedicated
{
namespace
{
void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt, [[maybe_unused]] int c, char* text)
{
game::SV_SendServerCommand(cl_0, type, "%c \"GAME_SERVER\x15: %s\"", 79, text);
}
}
struct component final : server_component
@ -18,6 +22,9 @@ namespace dedicated
// Ignore "bad stats"
utils::hook::set<uint8_t>(0x14052D523_g, 0xEB);
utils::hook::nop(0x14052D4E4_g, 2);
// Fix tell command for IW4M
utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub);
}
};
}

View File

@ -0,0 +1,71 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/io.hpp>
namespace game_log
{
namespace
{
void g_scr_log_print()
{
char string[1024]{};
std::size_t i_string_len = 0;
const auto i_num_parms = game::Scr_GetNumParam(game::SCRIPTINSTANCE_SERVER);
for (std::uint32_t i = 0; i < i_num_parms; ++i)
{
const auto* psz_token = game::Scr_GetString(game::SCRIPTINSTANCE_SERVER, i);
const auto i_token_len = std::strlen(psz_token);
i_string_len += i_token_len;
if (i_string_len >= sizeof(string))
{
// Do not overflow the buffer
break;
}
strncat_s(string, psz_token, _TRUNCATE);
}
game::G_LogPrintf("%s", string);
}
void g_log_printf_stub(const char* fmt, ...)
{
char va_buffer[0x400] = { 0 };
va_list ap;
va_start(ap, fmt);
vsprintf_s(va_buffer, fmt, ap);
va_end(ap);
const auto* file = "games_mp.log";
const auto time = *game::level_time / 1000;
utils::io::write_file(file, utils::string::va("%3i:%i%i %s",
time / 60,
time % 60 / 10,
time % 60 % 10,
va_buffer
), true);
}
}
class component final : public server_component
{
public:
void post_unpack() override
{
// Fix format string vulnerability & make it work
utils::hook::jump(0x1402D9300_g, g_scr_log_print);
utils::hook::jump(0x1402A7BB0_g, g_log_printf_stub);
}
};
}
REGISTER_COMPONENT(game_log::component)

View File

@ -122,6 +122,11 @@ namespace network
{
return length + (socket_byte_missing() ? 1 : 0);
}
void con_restricted_execute_buf_stub(int local_clientNum, game::ControllerIndex_t controller_index, const char* buffer)
{
game::Cbuf_ExecuteBuffer(local_clientNum, controller_index, buffer);
}
}
void on(const std::string& command, const callback& callback)
@ -228,6 +233,9 @@ namespace network
utils::hook::set<uint8_t>(game::select(0x14224E90D, 0x1405315F9), 0xEB); // don't kick clients without dw handle
// Remove restrictions for rcon commands
utils::hook::call(0x140538D5C_g, con_restricted_execute_buf_stub); // SVC_RemoteCommand
// TODO: Fix that
scheduler::once(create_ip_socket, scheduler::main);
}

View File

@ -870,6 +870,48 @@ namespace game
JoinResult joinResult;
};
typedef uint32_t ScrVarCanonicalName_t;
enum svscmd_type
{
SV_CMD_CAN_IGNORE_0 = 0x0,
SV_CMD_RELIABLE_0 = 0x1,
};
struct client_s
{
};
enum scriptInstance_t
{
SCRIPTINSTANCE_SERVER = 0x0,
SCRIPTINSTANCE_CLIENT = 0x1,
SCRIPT_INSTANCE_MAX = 0x2,
};
struct gclient_s
{
char __pad0[0x8C];
float velocity[3];
char __pad1[59504];
char flags;
};
struct EntityState
{
int number;
};
struct gentity_s
{
EntityState s;
unsigned char __pad0[0x24C];
gclient_s* client;
unsigned char __pad1[0x2A0];
};
static_assert(sizeof(gentity_s) == 0x4F8);
#ifdef __cplusplus
}
#endif

View File

@ -8,4 +8,35 @@ namespace game
{
return eModes(*reinterpret_cast<uint32_t*>(game::select(0x1568EF7F4, 0x14948DB04)) << 28 >> 28);
}
bool I_islower(int c)
{
return c >= 'a' && c <= 'z';
}
bool I_isupper(int c)
{
return c >= 'A' && c <= 'Z';
}
unsigned int Scr_CanonHash(const char* str)
{
#define FNV_OFFSET 0x4B9ACE2F
#define FNV_PRIME 16777619
const auto* s = str;
const int first_char = I_islower(*s) ? static_cast<unsigned char>(*s) : tolower(static_cast<unsigned char>(*str));
unsigned int hash = FNV_PRIME * (first_char ^ FNV_OFFSET);
while (*s)
{
int acc = I_islower(*++s)
? static_cast<unsigned char>(*s)
: std::tolower(static_cast<unsigned char>(*s));
hash = FNV_PRIME * (acc ^ hash);
}
return hash;
}
}

View File

@ -13,6 +13,10 @@ namespace game
int numPrivateSlots, const char* mapname, const char* gametype)> CL_ConnectFromLobby
{0x14134C570};
// Game
WEAK symbol<void(gentity_s* ent, gentity_s* target, int mode, const char* chatText)> G_Say{0x0, 0x140299170};
WEAK symbol<void(const char* fmt, ...)> G_LogPrintf{0x0, 0x1402A7BB0};
// Com
WEAK symbol<void(int channel, unsigned int label, const char* fmt, ...)> Com_Printf{0x1421499C0, 0x140505630};
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{0x1420F8BD0};
@ -22,9 +26,14 @@ namespace game
};
WEAK symbol<void(uint32_t localClientNum, const char* text)> Cbuf_AddText{0x1420EC8B0, 0x1404F75B0};
WEAK symbol<void(int localClientNum, ControllerIndex_t controllerIndex, const char* buffer)> Cbuf_ExecuteBuffer{0x0, 0x1404F78D0};
WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{
0x1420ED530, 0x1404F8210
};
WEAK symbol<void()> Cbuf_AddServerText_f{0x0, 0x1407DB4C0};
WEAK symbol<void(const char* cmdName, xcommand_t function, cmd_function_s* allocedCmd)> Cmd_AddServerCommandInternal{
0x0, 0x1404F8280
};
WEAK symbol<void(uint32_t localClientNum, ControllerIndex_t controllerIndex, const char* text,
bool fromRemoteConsol)> Cmd_ExecuteSingleCommand{
0x1420EDC20
@ -64,6 +73,12 @@ namespace game
0x1422C7F60
};
// Scr
WEAK symbol<void(scriptInstance_t inst, const char* value)> Scr_AddString{0x0, 0x14016F320};
WEAK symbol<const char* (scriptInstance_t inst, unsigned int index)> Scr_GetString{0x0, 0x140171490};
WEAK symbol<void(gentity_s* ent, ScrVarCanonicalName_t stringValue, unsigned int paramcount)> Scr_Notify_Canon{0x0, 0x1402F5FF0};
WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x140171320};
WEAK symbol<void(uint64_t id, bool cancelAll)> Cinematic_StopPlayback{0x1412BEA70};
// Rendering
@ -72,13 +87,18 @@ namespace game
0x141CD98D0
};
// Rendering
// SV
WEAK symbol<void*()> SV_AddTestClient{0x1422499A0, 0x14052E3E0};
WEAK symbol<void(client_s* cl_0, svscmd_type type, const char* fmt, ...)> SV_SendServerCommand{0x0, 0x140537F10};
// Variables
WEAK symbol<cmd_function_s> cmd_functions{0x15689FF58, 0x14946F860};
WEAK symbol<CmdArgs> sv_cmd_args{0x15689CE30};
WEAK symbol<CmdArgs> sv_cmd_args{0x0, 0x15689CE30};
WEAK symbol<gentity_s> g_entities{0x0, 0x1471031B0};
WEAK symbol<int> level_time{0x0, 0x1474FDC94};
WEAK symbol<SOCKET> ip_socket{0x157E77818, 0x14A640988};
@ -101,4 +121,9 @@ namespace game
// Re-implementations
eModes Com_SessionMode_GetMode();
bool I_islower(int c);
bool I_isupper(int c);
unsigned int Scr_CanonHash(const char* str);
}