Added components

New components + updated
This commit is contained in:
Joelrau 2021-01-07 06:12:05 +02:00
parent e2988d6193
commit 8db47ab865
16 changed files with 1045 additions and 128 deletions

View File

@ -89,6 +89,7 @@ namespace auth
}
const utils::info_string info_string{ std::string{params[2]} };
const auto steam_id = info_string.get("xuid");
const auto challenge = info_string.get("challenge");
@ -104,8 +105,10 @@ namespace auth
const auto xuid = strtoull(steam_id.data(), nullptr, 16);
if (xuid != key.get_hash())
{
network::send(*from, "error", "XUID doesn't match the certificate!", '\n');
return;
// xuid is always 0
//MessageBoxA(nullptr, steam_id.data(), std::to_string(key.get_hash()).data(), 0);
//network::send(*from, "error", "XUID doesn't match the certificate!", '\n');
//return;
}
if (!key.is_valid() || !verify_message(key, challenge, info.signature()))
@ -125,12 +128,11 @@ namespace auth
a.movaps(xmmword_ptr(rsp, 0x20), xmm0);
a.pushad64();
a.mov(rdx, rsi);
a.mov(rdx, rdi);
a.call_aligned(direct_connect);
a.popad64();
a.mov(eax, 0x140442317);
a.jmp(eax);
a.jmp(0x140442317);
});
}
}
@ -165,8 +167,8 @@ namespace auth
utils::hook::jump(0x14053995F, 0x1405399A0);
utils::hook::jump(0x140539E70, 0x140539EB6);
//utils::hook::jump(0x1404421F6, get_direct_connect_stub(), true);
//utils::hook::call(0x140208C54, send_connect_data_stub);
utils::hook::jump(0x1404421F6, get_direct_connect_stub(), true);
utils::hook::call(0x140208C54, send_connect_data_stub);
}
}
};

View File

@ -0,0 +1,133 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace binding
{
namespace
{
std::vector<std::string> custom_binds = {};
utils::hook::detour cl_execute_key_hook;
int key_write_bindings_to_buffer_stub(int /*localClientNum*/, char* buffer, const int buffer_size)
{
auto bytes_used = 0;
const auto buffer_size_align = static_cast<std::int32_t>(buffer_size) - 4;
for (auto key_index = 0; key_index < 256; ++key_index)
{
const auto* const key_button = game::Key_KeynumToString(key_index, 0, 1);
auto value = game::playerKeys->keys[key_index].binding;
if (value && value < 100)
{
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
"bind %s \"%s\"\n", key_button, game::command_whitelist[value]);
if (len < 0)
{
return bytes_used;
}
bytes_used += len;
}
else if (value >= 100)
{
value -= 100;
if (static_cast<size_t>(value) < custom_binds.size() && !custom_binds[value].empty())
{
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
"bind %s \"%s\"\n", key_button, custom_binds[value].data());
if (len < 0)
{
return bytes_used;
}
bytes_used += len;
}
}
}
buffer[bytes_used] = 0;
return bytes_used;
}
int get_binding_for_custom_command(const char* command)
{
auto index = 0;
for (auto& bind : custom_binds)
{
if (bind == command)
{
return index;
}
index++;
}
custom_binds.emplace_back(command);
index = static_cast<unsigned int>(custom_binds.size()) - 1;
return index;
}
int key_get_binding_for_cmd_stub(const char* command)
{
// original binds
for (auto i = 0; i <= 100; i++)
{
if (game::command_whitelist[i] && !strcmp(command, game::command_whitelist[i]))
{
return i;
}
}
// custom binds
return 100 + get_binding_for_custom_command(command);
}
void cl_execute_key_stub(const int local_client_num, unsigned int key, const unsigned int down, const unsigned int time)
{
if (key >= 100)
{
key -= 100;
if (static_cast<size_t>(key) < custom_binds.size() && !custom_binds[key].empty())
{
game::Cbuf_AddText(local_client_num, utils::string::va("%s\n", custom_binds[key].data()));
}
return;
}
cl_execute_key_hook.invoke<void>(local_client_num, key, down, time);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_dedi())
{
return;
}
// write all bindings to config file
utils::hook::call(SELECT_VALUE(0x1, 0x1402081CB), key_write_bindings_to_buffer_stub);
// links a custom command to an index
utils::hook::jump(SELECT_VALUE(0x1402EE5A0, 0x1403AFB50), key_get_binding_for_cmd_stub);
// execute custom binds
cl_execute_key_hook.create(SELECT_VALUE(0x14013B110, 0x140202BE0), &cl_execute_key_stub);
}
};
}
REGISTER_COMPONENT(binding::component)

View File

@ -0,0 +1,79 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "command.hpp"
#include "scheduler.hpp"
#include "game/game.hpp"
#include "party.hpp"
#include <utils/string.hpp>
namespace bots
{
namespace
{
bool can_spawn()
{
// disable spawning for now, causes a crash if more than svs_numclients.
//const auto index = *game::mp::svs_numclients - 1;
//const auto cant = game::mp::svs_clients[index].header.state;
//if (cant)
//{
return false;
//}
//return true;
}
void add_bot()
{
if (!can_spawn())
{
return;
}
auto* bot_ent = game::SV_AddTestClient(0);
if (&bot_ent)
{
game::SV_SpawnTestClient(bot_ent);
}
// SV_BotGetRandomName
/*auto* bot_name = reinterpret_cast<const char* (*)()>(0x1404267E0)();
auto* bot_ent = game::SV_AddBot(bot_name);
if (&bot_ent)
{
game::SV_SpawnTestClient(bot_ent);
}*/
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
command::add("spawnBot", [](const command::params& params)
{
if (!game::SV_Loaded()) return;
auto num_bots = 1;
if (params.size() == 2)
{
num_bots = atoi(params.get(1));
}
for (auto i = 0; i < num_bots; i++)
{
scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i);
}
});
}
};
}
REGISTER_COMPONENT(bots::component)

View File

@ -301,70 +301,6 @@ namespace command
static void add_commands_mp()
{
client_command_hook.create(0x1402E98F0, &client_command);
add("map", [](const command::params& params)
{
if (params.size() != 2)
{
return;
}
auto mapname = params.get(1);
if (!game::SV_MapExists(mapname))
{
printf("Map '%s' doesn't exist.", mapname);
return;
}
auto* current_mapname = game::Dvar_FindVar("mapname");
if (current_mapname && utils::string::to_lower(current_mapname->current.string) == utils::string::to_lower(mapname) && game::SV_Loaded())
{
printf("Restarting map: %s\n", mapname);
command::execute("map_restart", false);
return;
}
printf("Starting map: %s\n", mapname);
//game::SV_StartMap(0, mapname);
game::SV_StartMapForParty(0, mapname, false);
});
add("map_restart", []()
{
if (!game::SV_Loaded())
{
return;
}
*reinterpret_cast<int*>(0x1488692B0) = 1; // sv_map_restart
*reinterpret_cast<int*>(0x1488692B4) = 1; // sv_loadScripts
*reinterpret_cast<int*>(0x1488692B8) = 0; // sv_migrate
reinterpret_cast<void(*)(int)>(0x140437460)(0); // SV_CheckLoadGame
});
add("fast_restart", []()
{
if (game::SV_Loaded())
{
game::SV_FastRestart(0);
}
});
add("clientkick", [](const command::params& params)
{
if (params.size() < 2)
{
printf("usage: clientkick <num>\n");
return;
}
const auto client_num = atoi(params.get(1));
if (client_num < 0 || client_num >= *game::svs_numclients)
{
return;
}
game::SV_KickClientNum(client_num, "EXE_PLAYERKICKED");
});
}
};
}

View File

@ -0,0 +1,187 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game_console.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace dvar_cheats
{
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value)
{
if (dvar && dvar->name == "sv_cheats"s)
{
// if dedi, do not allow internal to change value so servers can allow cheats if they want to
if (game::environment::is_dedi() && source == game::DvarSetSource::DVAR_SOURCE_INTERNAL)
{
value->enabled = dvar->current.enabled;
}
// if sv_cheats was enabled and it changes to disabled, we need to reset all cheat dvars
else if (dvar->current.enabled && !value->enabled)
{
for (auto i = 0; i < *game::dvarCount; ++i)
{
const auto var = game::sortedDvars[i];
if (var && (var->flags & game::DvarFlags::DVAR_FLAG_CHEAT))
{
game::Dvar_Reset(var, game::DvarSetSource::DVAR_SOURCE_INTERNAL);
}
}
}
}
}
bool dvar_flag_checks(const game::dvar_t* dvar, const game::DvarSetSource source)
{
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_WRITE))
{
game_console::print(game_console::con_type_error, "%s is write protected", dvar->name);
return false;
}
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_READ))
{
game_console::print(game_console::con_type_error, "%s is read only", dvar->name);
return false;
}
// only check cheat/replicated values when the source is external
if (source == game::DvarSetSource::DVAR_SOURCE_EXTERNAL)
{
const auto cl_ingame = game::Dvar_FindVar("cl_ingame");
const auto sv_running = game::Dvar_FindVar("sv_running");
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_REPLICATED) && (cl_ingame && cl_ingame->current.enabled) && (
sv_running && !sv_running->current.enabled))
{
game_console::print(game_console::con_type_error, "%s can only be changed by the server", dvar->name);
return false;
}
const auto sv_cheats = game::Dvar_FindVar("sv_cheats");
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_CHEAT) && (sv_cheats && !sv_cheats->current.enabled))
{
game_console::print(game_console::con_type_error, "%s is cheat protected", dvar->name);
return false;
}
}
// pass all the flag checks, allow dvar to be changed
return true;
}
const auto dvar_flag_checks_stub = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto can_set_value = a.newLabel();
const auto zero_source = a.newLabel();
a.pushad64();
a.mov(r8, rdi);
a.mov(edx, esi);
a.mov(rcx, rbx);
a.call_aligned(apply_sv_cheats); //check if we are setting sv_cheats
a.popad64();
a.cmp(esi, 0);
a.jz(zero_source); //if the SetSource is 0 (INTERNAL) ignore flag checks
a.pushad64();
a.mov(edx, esi); //source
a.mov(rcx, rbx); //dvar
a.call_aligned(dvar_flag_checks); //protect read/write/cheat/replicated dvars
a.cmp(al, 1);
a.jz(can_set_value);
// if we get here, we are non-zero source and CANNOT set values
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
a.jmp(0x1404C3D45);
// if we get here, we are non-zero source and CAN set values
a.bind(can_set_value);
a.popad64(); // if I do this before the jz it won't work. for some reason the popad64 is affecting the ZR flag
a.jmp(0x1404C3AAE);
// if we get here, we are zero source and ignore flags
a.bind(zero_source);
a.jmp(0x1404C3AF4);
});
void cg_set_client_dvar_from_server(const int local_client_num, void* cg, const char* dvar_id, const char* value)
{
const auto* dvar = game::Dvar_FindVar(dvar_id);
if (dvar)
{
// If we send as string, it can't be set with source SERVERCMD because the game only allows that source on real server cmd dvars.
// Just use external instead as if it was being set by the console
game::Dvar_SetFromStringByNameFromSource(dvar_id, value, game::DvarSetSource::DVAR_SOURCE_EXTERNAL);
}
else
{
// Not a dvar name, assume it is an id and the game will handle normally
game::CG_SetClientDvarFromServer(local_client_num, cg, dvar_id, value);
}
}
void set_client_dvar_by_string(const int entity_num, const char* value)
{
const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack
const auto* command = utils::string::va("q %s \"%s\"", dvar, value);
game::SV_GameSendServerCommand(entity_num, 1, command);
}
const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a)
{
const auto set_by_string = a.newLabel();
a.pushad64();
// check if we didn't find a network dvar index
a.mov(ecx, dword_ptr(rsp, 0x8C8));
a.cmp(ecx, 0);
a.je(set_by_string);
// we found an index, handle normally
a.popad64();
a.mov(r8d, ptr(rsp, 0x848));
a.lea(r9, ptr(rsp, 0x30));
a.jmp(0x1402E2E57);
// no index, let's send the dvar as a string
a.bind(set_by_string);
a.movzx(ecx, word_ptr(rsp, 0x8C0)); //entity_num
a.lea(rdx, ptr(rsp, 0xB0)); //value
a.call_aligned(set_client_dvar_by_string);
a.popad64();
a.jmp(0x1402E2E7D);
});
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp()) return;
utils::hook::nop(0x1404C3A93, 4); // let our stub handle zero-source sets
utils::hook::jump(0x1404C3A9A, dvar_flag_checks_stub, true); // check extra dvar flags when setting values
utils::hook::nop(0x1402E2E03, 5); // remove error in PlayerCmd_SetClientDvar if setting a non-network dvar
utils::hook::set<uint8_t>(0x1402E2DCF, 0xEB); // don't check flags on the dvars, send any existing dvar instead
utils::hook::jump(0x1402E2E4A, player_cmd_set_client_dvar, true); // send non-network dvars as string
utils::hook::call(0x1401BB782, cg_set_client_dvar_from_server); // check for dvars being sent as string before parsing ids
scheduler::once([]()
{
game::Dvar_RegisterBool("sv_cheats", false, game::DvarFlags::DVAR_FLAG_REPLICATED,
"Allow cheat commands and dvars on this server");
}, scheduler::pipeline::main);
}
};
}
REGISTER_COMPONENT(dvar_cheats::component)

View File

@ -65,21 +65,23 @@ namespace game_console
matches.clear();
}
void print(const std::string& data)
void print_internal(const std::string& data)
{
if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count))
{
con.display_line_offset++;
}
con.output.push_back(data.data());
if (con.output.size() > 512)
{
con.output.pop_front();
}
}
//con.output.push_back(data);
void print(const std::string& data)
{
//print_internal(data.data());
printf("%s\n", data.data());
//if (con.output.size() > 512)
//{
// con.output.pop_front();
//}
}
void toggle_console()
@ -661,11 +663,7 @@ namespace game_console
result = vsnprintf(buffer, 256, format, args);
va_end(args);
std::cout << buffer;
con.output.push_back(buffer);
if (con.output.size() > 512)
{
con.output.pop_front();
}
print_internal(buffer);
return result;
}

View File

@ -0,0 +1,52 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace gameplay
{
namespace
{
void stuck_in_client_stub(void* entity)
{
if (dvars::g_playerEjection->current.enabled)
{
reinterpret_cast<void(*)(void*)>(0x1402DA310)(entity); // StuckInClient
}
}
void cm_transformed_capsule_trace_stub(struct trace_t* results, const float* start, const float* end,
struct Bounds* bounds, struct Bounds* capsule, int contents, const float* origin, const float* angles)
{
if (dvars::g_playerCollision->current.enabled)
{
reinterpret_cast<void(*)
(struct trace_t*, const float*, const float*, struct Bounds*, struct Bounds*, unsigned int, const float*, const float*)>
(0x1403AB1C0)
(results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp()) return;
// Implement player ejection dvar
dvars::g_playerEjection = game::Dvar_RegisterBool("g_playerEjection", true, game::DVAR_FLAG_REPLICATED, "Flag whether player ejection is on or off");
utils::hook::call(0x1402D5E4A, stuck_in_client_stub);
// Implement player collision dvar
dvars::g_playerCollision = game::Dvar_RegisterBool("g_playerCollision", true, game::DVAR_FLAG_REPLICATED, "Flag whether player collision is on or off");
utils::hook::call(0x1404563DA, cm_transformed_capsule_trace_stub); // SV_ClipMoveToEntity
utils::hook::call(0x1401F7F8F, cm_transformed_capsule_trace_stub); // CG_ClipMoveToEntity
}
};
}
REGISTER_COMPONENT(gameplay::component)

View File

@ -0,0 +1,35 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "command.hpp"
#include "game_console.hpp"
#include <utils/hook.hpp>
namespace lui
{
class component final : public component_interface
{
public:
void post_unpack() override
{
if (!game::environment::is_mp()) return;
command::add("lui_open", [](const command::params& params)
{
if (params.size() <= 1)
{
game_console::print(game_console::con_type_info, "usage: lui_open <name>\n");
return;
}
game::LUI_OpenMenu(0, params[1], 1, 0, 0);
});
}
};
}
REGISTER_COMPONENT(lui::component)

View File

@ -36,7 +36,7 @@ namespace network
return true;
}
/*void handle_command_stub(utils::hook::assembler& a)
void handle_command_stub(utils::hook::assembler& a)
{
const auto return_unhandled = a.newLabel();
@ -53,14 +53,13 @@ namespace network
// Command handled
a.popad64();
a.mov(eax, 0x14020AA0E);
a.jmp(eax);
a.mov(al, 1);
a.jmp(0x14020AA10);
a.bind(return_unhandled);
a.popad64();
a.mov(eax, 0x14020A19A);
a.jmp(eax);
}*/
a.jmp(0x14020A19A);
}
int net_compare_base_address(const game::netadr_s* a1, const game::netadr_s* a2)
{
@ -104,6 +103,13 @@ namespace network
get_callbacks()[utils::string::to_lower(command)] = callback;
}
void dw_send_to(const unsigned int size, const char* src, game::netadr_s* a3)
{
sockaddr s = {};
game::NetadrToSockadr(a3, &s);
sendto(*game::query_socket, src, size - 2, 0, &s, 16);
}
void send(const game::netadr_s& address, const std::string& command, const std::string& data, const char separator)
{
std::string packet = "\xFF\xFF\xFF\xFF";
@ -156,34 +162,6 @@ namespace network
return "bad";
}
/*void set_xuid_config_string_stub(utils::hook::assembler& a)
{
const auto return_regular = a.newLabel();
a.mov(rax, ptr(rsp));
a.mov(r9, 0x); // This is the evil one :(
a.cmp(rax, r9);
a.jne(return_regular);
// Do the original work
a.call_aligned(return_regular);
// Jump to success branch
a.mov(rax, 0x);
a.mov(ptr(rsp), rax);
a.ret();
a.bind(return_regular);
a.sub(rsp, 0x38);
a.mov(eax, ptr(rcx));
a.mov(r9d, ptr(rcx, 4));
a.mov(r10, rdx);
a.jmp(0x1404B725D);
}*/
game::dvar_t* register_netport_stub(const char* dvarName, int value, int min, int max, unsigned int flags,
const char* description)
{
@ -207,13 +185,14 @@ namespace network
}
// redirect dw_sendto to raw socket
utils::hook::jump(0x1404D850A, reinterpret_cast<void*>(0x1404D849A));
//utils::hook::jump(0x1404D850A, reinterpret_cast<void*>(0x1404D849A));
utils::hook::call(0x1404D851F, dw_send_to);
// intercept command handling
//utils::hook::jump(0x14020A175, utils::hook::assemble(handle_command_stub), true);
utils::hook::jump(0x14020A175, utils::hook::assemble(handle_command_stub), true);
// handle xuid without secure connection
//utils::hook::jump(0x1404B7250, utils::hook::assemble(set_xuid_config_string_stub), true);
utils::hook::nop(0x14043FFF8, 6);
utils::hook::jump(0x1403DA700, net_compare_address);
utils::hook::jump(0x1403DA750, net_compare_base_address);

View File

@ -0,0 +1,397 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "party.hpp"
#include "command.hpp"
#include "network.hpp"
#include "scheduler.hpp"
//#include "server_list.hpp"
#include "steam/steam.hpp"
#include <utils/string.hpp>
#include <utils/info_string.hpp>
#include <utils/cryptography.hpp>
#include <utils/hook.hpp>
namespace party
{
namespace
{
struct
{
game::netadr_s host{};
std::string challenge{};
} connect_state;
std::string sv_motd;
void perform_game_initialization()
{
// This fixes several crashes and impure client stuff
command::execute("onlinegame 1", true);
command::execute("exec default_xboxlive.cfg", true);
command::execute("xstartprivateparty", true);
command::execute("xblive_privatematch 1", true);
command::execute("startentitlements", true);
}
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype)
{
if (game::environment::is_sp())
{
return;
}
if (game::Live_SyncOnlineDataFlags(0))
{
scheduler::once([=]()
{
connect_to_party(target, mapname, gametype);
}, scheduler::pipeline::main, 1s);
return;
}
perform_game_initialization();
// CL_ConnectFromParty
char session_info[0x100] = {};
reinterpret_cast<void(*)(int, char*, const game::netadr_s*, const char*, const char*)>(0x140209360)(
0, session_info, &target, mapname.data(), gametype.data());
}
std::string get_dvar_string(const std::string& dvar)
{
auto* dvar_value = game::Dvar_FindVar(dvar.data());
if (dvar_value && dvar_value->current.string)
{
return dvar_value->current.string;
}
return {};
}
}
int get_client_count()
{
auto count = 0;
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
{
//if (game::mp::svs_clients[i].header.state >= 3)
//{
// ++count;
//}
}
return count;
}
int get_bot_count()
{
auto count = 0;
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
{
//if (game::mp::svs_clients[i].header.state >= 3 &&
// game::mp::svs_clients[i].testClient != game::TC_NONE)
//{
// ++count;
//}
}
return count;
}
void connect(const game::netadr_s& target)
{
if (game::environment::is_sp())
{
return;
}
command::execute("lui_open popup_acceptinginvite", false);
connect_state.host = target;
connect_state.challenge = utils::cryptography::random::get_challenge();
network::send(target, "getInfo", connect_state.challenge);
}
void start_map(const std::string& mapname)
{
if (game::Live_SyncOnlineDataFlags(0) != 0)
{
scheduler::on_game_initialized([mapname]()
{
command::execute("map " + mapname, false);
}, scheduler::pipeline::main, 1s);
}
else
{
if (!game::environment::is_dedi())
{
perform_game_initialization();
}
if (!game::SV_MapExists(mapname.data()))
{
printf("Map '%s' doesn't exist.", mapname.data());
return;
}
auto* current_mapname = game::Dvar_FindVar("mapname");
if (current_mapname && utils::string::to_lower(current_mapname->current.string) == utils::string::to_lower(mapname) && game::SV_Loaded())
{
printf("Restarting map: %s\n", mapname.data());
command::execute("map_restart", false);
return;
}
// starting map doesn't work, only private match -> start
printf("Starting map: %s\n", mapname.data());
game::SV_StartMapForParty(0, mapname.data(), true);
//game::SV_StartMap(0, mapname.data());
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
if (game::environment::is_sp())
{
return;
}
command::add("map", [](const command::params& argument)
{
if (argument.size() != 2)
{
return;
}
start_map(argument[1]);
});
command::add("map_restart", []()
{
if (!game::SV_Loaded())
{
return;
}
*reinterpret_cast<int*>(0x1488692B0) = 1; // sv_map_restart
*reinterpret_cast<int*>(0x1488692B4) = 1; // sv_loadScripts
*reinterpret_cast<int*>(0x1488692B8) = 0; // sv_migrate
reinterpret_cast<void(*)(int)>(0x140437460)(0); // SV_CheckLoadGame
});
command::add("fast_restart", []()
{
if (game::SV_Loaded())
{
game::SV_FastRestart(0);
}
});
command::add("connect", [](const command::params& argument)
{
if (argument.size() != 2)
{
return;
}
game::netadr_s target{};
if (game::NET_StringToAdr(argument[1], &target))
{
connect(target);
}
});
command::add("reconnect", [](const command::params& params)
{
if (!game::CL_IsCgameInitialized())
{
return;
}
const auto host = connect_state.host;
if (host.type == game::NA_LOOPBACK)
{
command::execute("map_restart", true);
return;
}
if (host.type != game::NA_IP)
{
return;
}
command::execute("disconnect", true);
scheduler::once([host]()
{
connect(host);
}, scheduler::pipeline::async, 2s);
});
command::add("clientkick", [](const command::params& params)
{
if (params.size() < 2)
{
printf("usage: clientkick <num>\n");
return;
}
const auto client_num = atoi(params.get(1));
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
{
return;
}
game::SV_KickClientNum(client_num, "EXE_PLAYERKICKED");
});
scheduler::once([]()
{
game::Dvar_RegisterString("sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE,
"The name to pose as for 'say' commands");
}, scheduler::pipeline::main);
command::add("tell", [](const command::params& params)
{
if (params.size() < 3)
{
return;
}
const auto client_num = atoi(params.get(1));
const auto message = params.join(2);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
game::SV_GameSendServerCommand(client_num, 0,
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
printf("%s -> %i: %s\n", name, client_num, message.data());
});
command::add("tellraw", [](const command::params& params)
{
if (params.size() < 3)
{
return;
}
const auto client_num = atoi(params.get(1));
const auto message = params.join(2);
game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s\"", 84, message.data()));
printf("%i: %s\n", client_num, message.data());
});
command::add("say", [](const command::params& params)
{
if (params.size() < 2)
{
return;
}
const auto message = params.join(1);
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
game::SV_GameSendServerCommand(
-1, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
printf("%s: %s\n", name, message.data());
});
command::add("sayraw", [](const command::params& params)
{
if (params.size() < 2)
{
return;
}
const auto message = params.join(1);
game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s\"", 84, message.data()));
printf("%s\n", message.data());
});
network::on("getInfo", [](const game::netadr_s& target, const std::string_view& data)
{
utils::info_string info{};
info.set("challenge", std::string{data});
info.set("gamename", "S1");
info.set("hostname", get_dvar_string("sv_hostname"));
info.set("gametype", get_dvar_string("g_gametype"));
info.set("sv_motd", get_dvar_string("sv_motd"));
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
info.set("mapname", get_dvar_string("mapname"));
info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1");
info.set("clients", utils::string::va("%i", get_client_count()));
info.set("bots", utils::string::va("%i", get_bot_count()));
info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
info.set("protocol", utils::string::va("%i", PROTOCOL));
info.set("playmode", utils::string::va("%i", game::Com_GetCurrentCoDPlayMode()));
//info.set("shortversion", SHORTVERSION);
//info.set("hc", (Dvar::Var("g_hardcore").get<bool>() ? "1" : "0"));
network::send(target, "infoResponse", info.build(), '\n');
});
network::on("infoResponse", [](const game::netadr_s& target, const std::string_view& data)
{
const utils::info_string info{data};
//server_list::handle_info_response(target, info);
if (connect_state.host != target)
{
return;
}
if (info.get("challenge") != connect_state.challenge)
{
const auto str = "Invalid challenge.";
printf("%s\n", str);
game::Com_Error(game::ERR_DROP, str);
return;
}
const auto playmode = info.get("playmode");
if (playmode.empty())
{
const auto str = "Invalid playmode.";
printf("%s\n", str);
game::Com_Error(game::ERR_DROP, str);
return;
}
const auto mapname = info.get("mapname");
if (mapname.empty())
{
const auto str = "Invalid map.";
printf("%s\n", str);
game::Com_Error(game::ERR_DROP, str);
return;
}
const auto gametype = info.get("gametype");
if (gametype.empty())
{
const auto str = "Invalid gametype.";
printf("%s\n", str);
game::Com_Error(game::ERR_DROP, str);
return;
}
const auto gamename = info.get("gamename");
if (gamename != "S1"s)
{
const auto str = "Invalid gamename.";
printf("%s\n", str);
game::Com_Error(game::ERR_DROP, str);
return;
}
party::sv_motd = info.get("sv_motd");
connect_to_party(target, mapname, gametype);
});
}
};
}
REGISTER_COMPONENT(party::component)

View File

@ -0,0 +1,11 @@
#pragma once
#include "game/game.hpp"
namespace party
{
void connect(const game::netadr_s& target);
void start_map(const std::string& mapname);
int get_client_count();
int get_bot_count();
}

View File

@ -22,6 +22,18 @@ namespace patches
return game::Dvar_FindVar("name")->current.string;
}
utils::hook::detour sv_kick_client_num_hook;
void sv_kick_client_num(const int clientNum, const char* reason)
{
// Don't kick bot to equalize team balance.
if (reason == "EXE_PLAYERKICKED_BOT_BALANCE"s)
{
return;
}
return sv_kick_client_num_hook.invoke<void>(clientNum, reason);
}
utils::hook::detour com_register_dvars_hook;
void com_register_dvars()
@ -96,6 +108,18 @@ namespace patches
{
game::Com_Error(game::ERR_DROP, utils::string::va("MISSING FILE\n%s.ff", fastfiles::get_current_fastfile()));
}
void bsp_sys_error_stub(const char* error, const char* arg1)
{
if (game::environment::is_dedi())
{
game::Sys_Error(error, arg1);
}
else
{
game::Com_Error(game::ERR_DROP, error, arg1);
}
}
}
class component final : public component_interface
@ -149,6 +173,12 @@ namespace patches
{
// Use name dvar
live_get_local_client_name_hook.create(0x1404D47F0, &live_get_local_client_name);
// Patch SV_KickClientNum
sv_kick_client_num_hook.create(0x1404377A0, &sv_kick_client_num);
// patch "Couldn't find the bsp for this map." error to not be fatal in mp
utils::hook::call(0x14026E63B, bsp_sys_error_stub);
}
static void patch_sp()

View File

@ -15,6 +15,9 @@ namespace dvars
game::dvar_t* con_inputDvarInactiveValueColor = nullptr;
game::dvar_t* con_inputCmdMatchColor = nullptr;
game::dvar_t* g_playerEjection = nullptr;
game::dvar_t* g_playerCollision = nullptr;
game::dvar_t* r_fullbright = nullptr;
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain)

View File

@ -15,6 +15,9 @@ namespace dvars
extern game::dvar_t* con_inputDvarInactiveValueColor;
extern game::dvar_t* con_inputCmdMatchColor;
extern game::dvar_t* g_playerCollision;
extern game::dvar_t* g_playerEjection;
extern game::dvar_t* r_fullbright;
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain);

View File

@ -9,6 +9,17 @@ namespace game
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
enum CodPlayMode
{
CODPLAYMODE_NONE = 0x0,
CODPLAYMODE_SP = 0x1,
CODPLAYMODE_CORE = 0x2,
CODPLAYMODE_SURVIVAL = 0x3,
CODPLAYMODE_ZOMBIES = 0x4,
CODPLAYMODE_GOLIATH = 0x5,
CODPLAYMODE_COUNT = 0x6,
};
enum DWOnlineStatus
{
DW_LIVE_DISCONNECTED = 0x0,
@ -876,17 +887,37 @@ namespace game
Material* material;
};
struct client_t
{
};
namespace mp
{
struct gclient_s
{
};
struct EntityState
{
};
struct gentity_s
{
};
struct playerState_s
{
};
struct clientHeader_t
{
};
struct client_t
{
};
}
namespace sp
@ -906,4 +937,10 @@ namespace game
};
}
union playerState_s
{
sp::playerState_s* sp;
mp::playerState_s* mp;
};
}

View File

@ -10,6 +10,7 @@ namespace game
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{ 0x1402F7570, 0x1403CE480 };
WEAK symbol<void()> Com_Frame_Try_Block_Function{ 0x1402F7E10, 0x1403CEF30 };
WEAK symbol<CodPlayMode()> Com_GetCurrentCoDPlayMode{ 0, 0x1404C9690 };
WEAK symbol<void()> Com_Quit_f{ 0x1402F9390, 0x1403D08C0 };
WEAK symbol<void(const char* cmdName, void(), cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{ 0x1402EDDB0, 0x1403AF2C0 };
@ -19,6 +20,7 @@ namespace game
WEAK symbol<void(const char* message)> Conbuf_AppendText{ 0x14038F220, 0x1404D9040 };
WEAK symbol<void(int localClientNum, void (*)(int localClientNum))> Cbuf_AddCall{ 0x1402ED820, 0x1403AECF0 };
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{ 0x1402ED890, 0x1403AED70 };
WEAK symbol<void(int localClientNum, int controllerIndex, const char* buffer,
void(int, int, const char*))> Cbuf_ExecuteBufferInternal{ 0x1402ED9A0, 0x1403AEE80 };
@ -26,6 +28,8 @@ namespace game
WEAK symbol<bool()> CL_IsCgameInitialized{ 0x140136560, 0x1401FD510 };
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{ 0x1400EE500, 0x1401A3050 };
WEAK symbol<void(int localClientNum, /*mp::cg_s**/void* cg, const char* dvar, const char* value)> CG_SetClientDvarFromServer
{ 0, 0x1401BF0A0 };
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), void* inData, bool includeOverride)> DB_EnumXAssets_FastFile{ 0x14017D7C0, 0x14026EC10 };
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{ 0x140151C20, 0x140240DF0 };
@ -33,6 +37,7 @@ namespace game
WEAK symbol<dvar_t* (const char* name)> Dvar_FindVar{ 0x140370860, 0x1404BF8B0 };
WEAK symbol<void(char* buffer, int index)> Dvar_GetCombinedString{ 0x1402FB590, 0x1403D3290 };
WEAK symbol<void(dvar_t* dvar, DvarSetSource source)> Dvar_Reset{ 0x140372950, 0x1404C1DB0 };
WEAK symbol<void(const char* dvar, const char* buffer)> Dvar_SetCommand{ 0x1403730D0, 0x1404C2520 };
WEAK symbol<void(dvar_t* dvar, const char* string)> Dvar_SetString{ 0x140373DE0, 0x1404C3610 };
WEAK symbol<void(const char*, const char*, DvarSetSource)> Dvar_SetFromStringByNameFromSource{ 0x1403737D0, 0x1404C2E40 };
@ -53,10 +58,16 @@ namespace game
WEAK symbol<void()> G_Glass_Update{ 0x14021D540, 0x1402EDEE0 };
WEAK symbol<const char* (int, int, int)> Key_KeynumToString{ 0x14013F380, 0x140207C50 };
WEAK symbol<unsigned int(int)> Live_SyncOnlineDataFlags{ 0x1404459A0, 0x140562830 };
WEAK symbol<void(int clientNum, const char* menu, int a3, int a4, unsigned int a5)> LUI_OpenMenu{ 0, 0x14048E450 };
WEAK symbol<Material* (const char* material)> Material_RegisterHandle{ 0x1404919D0, 0x1405AFBE0 };
WEAK symbol<void(netadr_s*, sockaddr*)> NetadrToSockadr{ 0, 0x1404B6F10 };
WEAK symbol<void(netsrc_t, netadr_s*, const char*)> NET_OutOfBandPrint{ 0, 0x1403DADC0 };
WEAK symbol<void(netsrc_t sock, int length, const void* data, const netadr_s* to)> NET_SendLoopPacket{ 0, 0x1403DAF80 };
WEAK symbol<bool(const char* s, game::netadr_s* a)> NET_StringToAdr{ 0, 0x1403DB070 };
@ -73,19 +84,38 @@ namespace game
WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{ 0x14014FA70, 0x14023CB50 };
WEAK symbol<unsigned int()> Scr_AllocArray{ 0x140317C50, 0x1403F4280 };
WEAK symbol<const float* (const float* v)> Scr_AllocVector{ 0x140317D10, 0x1403F4370 };
WEAK symbol<const char* (int index)> Scr_GetString{ 0x14031C570, 0x1403F8C50 };
WEAK symbol<unsigned int()> Scr_GetInt{ 0x14031C1F0, 0x1403F88D0 };
WEAK symbol<float(int index)> Scr_GetFloat{ 0x14031C090, 0x1403F8820 };
WEAK symbol<int()> Scr_GetNumParam{ 0x14031C2A0, 0x1403F8980 };
WEAK symbol<void()> Scr_ClearOutParams{ 0x14031B7C0, 0x1403F8040 };
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{ 0, 0x1403B0640 };
WEAK symbol<void()> SV_Cmd_EndTokenizedString{ 0, 0x1403B0600 };
WEAK symbol<mp::gentity_s* (const char* name)> SV_AddBot{ 0, 0x140438EC0 };
WEAK symbol<bool(int clientNum)> SV_BotIsBot{ 0, 0x140427300 };
WEAK symbol<mp::gentity_s* (int)> SV_AddTestClient{ 0, 0x140439190 };
WEAK symbol<bool(mp::gentity_s*)> SV_CanSpawnTestClient{ 0, 0x140439460 };
WEAK symbol<int(mp::gentity_s* ent)> SV_SpawnTestClient{ 0, 0x14043C750 };
WEAK symbol<void(mp::gentity_s*)> SV_AddEntity{ 0, 0x1403388B0 };
WEAK symbol<void(netadr_s* from)> SV_DirectConnect{ 0, 0x1404397A0 };
WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{ 0, 0x15121D8E6 };
WEAK symbol<void(int localClientNum)> SV_FastRestart{ 0, 0x1404374E0 };
WEAK symbol<void(int, int, const char*)> SV_GameSendServerCommand{ 0x1403F3A70, 0x14043E120 };
WEAK symbol<const char* (int clientNum)> SV_GetGuid{ 0, 0x14043E1E0 };
WEAK symbol<playerState_s* (int num)> SV_GetPlayerstateForClientNum{ 0x1403F3AB0, 0x14043E260 };
WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{ 0, 0x1404377A0 };
WEAK symbol<bool()> SV_Loaded{ 0x1403F42C0, 0x14043FA50 };
WEAK symbol<bool(const char* map) >SV_MapExists{ 0, 0x140437800 };
WEAK symbol<void(int localClientNum, const char* map)> SV_StartMap{ 0, 0x140438320 };
WEAK symbol<void(int localClientNum, const char* map, bool migrate)> SV_StartMapForParty{ 0, 0x140438490 };
WEAK symbol<void(const char* error, ...)> Sys_Error{ 0x14038C770, 0x1404D6260 };
WEAK symbol<HANDLE(int folder, const char* baseFileName)> Sys_CreateFile{ 0x14037BCA0, 0x1404CC8A0 };
WEAK symbol<bool()> Sys_IsDatabaseReady2{ 0x1402FF980, 0x1403E1840 };
WEAK symbol<bool(int, void const*, const netadr_s*)> Sys_SendPacket{ 0x14038E720, 0x1404D8460 };
@ -105,8 +135,9 @@ namespace game
WEAK symbol<int> dvarCount{ 0x14A7BFF34, 0x14B32AA30 };
WEAK symbol<dvar_t*> sortedDvars{ 0x14A7BFF50, 0x14B32AA50 };
WEAK symbol<int> svs_numclients{ 0, 0x1496C4B0C };
WEAK symbol<client_t> svs_clients{ 0, 0x1496C4B10 };
WEAK symbol<const char*> command_whitelist{ 0x140808EF0, 0x1409B8DC0 };
WEAK symbol<SOCKET> query_socket{ 0, 0x14B5B9180 };
WEAK symbol<void*> DB_XAssetPool{ 0x140804690, 0x1409B40D0 };
WEAK symbol<int> g_poolSize{ 0x140804140, 0x1409B4B90 };
@ -117,6 +148,10 @@ namespace game
namespace mp
{
WEAK symbol<gentity_s> g_entities{ 0, 0x144758C70 };
WEAK symbol<client_t> svs_clients{ 0, 0x1496C4B10 };
WEAK symbol<int> svs_numclients{ 0, 0x1496C4B0C };
WEAK symbol<int> sv_serverId_value{ 0, 0x1488A9A60 };
}
namespace sp