parent
e0eb721364
commit
1ed4f717f6
@ -1,164 +0,0 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace arxan
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour nt_close_hook;
|
||||
utils::hook::detour nt_query_information_process_hook;
|
||||
|
||||
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
||||
const PVOID info,
|
||||
const ULONG info_length, const PULONG ret_length)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.
|
||||
get_original());
|
||||
const auto status = orig(handle, info_class, info, info_length, ret_length);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (info_class == ProcessBasicInformation)
|
||||
{
|
||||
static DWORD explorer_pid = 0;
|
||||
if (!explorer_pid)
|
||||
{
|
||||
auto* const shell_window = GetShellWindow();
|
||||
GetWindowThreadProcessId(shell_window, &explorer_pid);
|
||||
}
|
||||
|
||||
static_cast<PPROCESS_BASIC_INFORMATION>(info)->Reserved3 = PVOID(DWORD64(explorer_pid));
|
||||
}
|
||||
else if (info_class == 30) // ProcessDebugObjectHandle
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
|
||||
return 0xC0000353;
|
||||
}
|
||||
else if (info_class == 7) // ProcessDebugPort
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
}
|
||||
else if (info_class == 31)
|
||||
{
|
||||
*static_cast<ULONG*>(info) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
||||
{
|
||||
char info[16];
|
||||
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
||||
return orig(handle);
|
||||
}
|
||||
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
||||
{
|
||||
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void hide_being_debugged()
|
||||
{
|
||||
auto* const peb = PPEB(__readgsqword(0x60));
|
||||
peb->BeingDebugged = false;
|
||||
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
|
||||
}
|
||||
|
||||
void remove_hardware_breakpoints()
|
||||
{
|
||||
CONTEXT context;
|
||||
ZeroMemory(&context, sizeof(context));
|
||||
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
|
||||
auto* const thread = GetCurrentThread();
|
||||
GetThreadContext(thread, &context);
|
||||
|
||||
context.Dr0 = 0;
|
||||
context.Dr1 = 0;
|
||||
context.Dr2 = 0;
|
||||
context.Dr3 = 0;
|
||||
context.Dr6 = 0;
|
||||
context.Dr7 = 0;
|
||||
|
||||
SetThreadContext(thread, &context);
|
||||
}
|
||||
|
||||
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
||||
{
|
||||
if (!game::environment::is_sp()
|
||||
&& game::dwGetLogOnStatus() == game::DW_LIVE_CONNECTED
|
||||
&& context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetThreadContext(thread, context);
|
||||
}
|
||||
}
|
||||
|
||||
int just_return()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (function == "SetThreadContext")
|
||||
{
|
||||
//return set_thread_context_stub;
|
||||
}
|
||||
|
||||
if (function == "LoadStringA" || function == "LoadStringW")
|
||||
{
|
||||
return just_return;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
hide_being_debugged();
|
||||
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
||||
|
||||
const utils::nt::library ntdll("ntdll.dll");
|
||||
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
||||
nt_query_information_process_hook.create(ntdll.get_proc<void*>("NtQueryInformationProcess"),
|
||||
nt_query_information_process_stub);
|
||||
|
||||
AddVectoredExceptionHandler(1, exception_filter);
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// cba to implement sp, not sure if it's even needed
|
||||
if (game::environment::is_sp()) return;
|
||||
|
||||
// some of arxan crashes
|
||||
utils::hook::nop(0x14CDEFCAA, 6);
|
||||
utils::hook::call(0x1405BCAD1, &just_return);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(arxan::component)
|
@ -42,7 +42,7 @@ namespace auth
|
||||
|
||||
std::string get_protected_data()
|
||||
{
|
||||
std::string input = "X-Labs-H1STEAM-Auth";
|
||||
std::string input = "X-Labs-H1Mod-Auth";
|
||||
|
||||
DATA_BLOB data_in{}, data_out{};
|
||||
data_in.pbData = reinterpret_cast<uint8_t*>(input.data());
|
||||
@ -177,20 +177,21 @@ namespace auth
|
||||
game::SV_DirectConnect(from);
|
||||
}
|
||||
|
||||
// CAN'T FIND
|
||||
//void* get_direct_connect_stub()
|
||||
//{
|
||||
// return utils::hook::assemble([](utils::hook::assembler& a)
|
||||
// {
|
||||
// a.lea(rcx, qword_ptr(rsp, 0x20));
|
||||
// a.movaps(xmmword_ptr(rsp, 0x20), xmm0);
|
||||
// {
|
||||
// a.lea(rcx, qword_ptr(rsp, 0x20));
|
||||
// a.movaps(xmmword_ptr(rsp, 0x20), xmm0);
|
||||
|
||||
// a.pushad64();
|
||||
// a.mov(rdx, rsi);
|
||||
// a.call_aligned(direct_connect);
|
||||
// a.popad64();
|
||||
// a.pushad64();
|
||||
// a.mov(rdx, rsi);
|
||||
// a.call_aligned(direct_connect);
|
||||
// a.popad64();
|
||||
|
||||
// a.jmp(0x140488CE2); // H1MP64(1.4)
|
||||
// });
|
||||
// a.jmp(0x140488CE2); // H1MP64(1.4)
|
||||
// });
|
||||
//}
|
||||
}
|
||||
|
||||
@ -224,8 +225,8 @@ namespace auth
|
||||
utils::hook::jump(0x1D7542_b, 0x1D7587_b); // STEAM MAYBE `1401D7553` ON FIRST
|
||||
utils::hook::jump(0x1D7A82_b, 0x1D7AC8_b); // STEAM
|
||||
|
||||
//utils::hook::jump(0x1401CAE70, get_direct_connect_stub(), true); // STEAM
|
||||
utils::hook::call(0x12D437_b, send_connect_data_stub); // STEAM
|
||||
//utils::hook::jump(0x140488BC1, get_direct_connect_stub(), true); // H1(1.4) can't find
|
||||
utils::hook::call(0x12D437_b, send_connect_data_stub); // H1(1.4)
|
||||
|
||||
// Skip checks for sending connect packet
|
||||
utils::hook::jump(0x1402508FC, 0x140250946);
|
||||
|
138
src/client/component/binding.cpp
Normal file
138
src/client/component/binding.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
#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 get_num_keys()
|
||||
{
|
||||
return SELECT_VALUE(102, 103);
|
||||
}
|
||||
|
||||
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 < get_num_keys())
|
||||
{
|
||||
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 >= get_num_keys())
|
||||
{
|
||||
value -= get_num_keys();
|
||||
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 <= get_num_keys(); i++)
|
||||
{
|
||||
if (game::command_whitelist[i] && !strcmp(command, game::command_whitelist[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// custom binds
|
||||
return get_num_keys() + get_binding_for_custom_command(command);
|
||||
}
|
||||
|
||||
void cl_execute_key_stub(const int local_client_num, int key, const int down, const int time)
|
||||
{
|
||||
if (key >= get_num_keys())
|
||||
{
|
||||
key -= get_num_keys();
|
||||
|
||||
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(0x1401881DB, 0x14025032F), key_write_bindings_to_buffer_stub); // H1(1.4)
|
||||
|
||||
// links a custom command to an index
|
||||
utils::hook::jump(SELECT_VALUE(0x140343C00, 0x1404041E0), key_get_binding_for_cmd_stub); // H1(1.4)
|
||||
|
||||
// execute custom binds
|
||||
cl_execute_key_hook.create(SELECT_VALUE(0x140183C70, 0x14024ACF0), &cl_execute_key_stub); // H1(1.4)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(binding::component)
|
103
src/client/component/bots.cpp
Normal file
103
src/client/component/bots.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "network.hpp"
|
||||
#include "party.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace bots
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool can_add()
|
||||
{
|
||||
if (party::get_client_count() < *game::mp::svs_numclients)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: when scripting comes, fix this to use better notifies
|
||||
void bot_team_join(const int entity_num)
|
||||
{
|
||||
const game::scr_entref_t entref{static_cast<uint16_t>(entity_num), 0};
|
||||
scheduler::once([entref]()
|
||||
{
|
||||
scripting::notify(entref, "luinotifyserver", {"team_select", 2});
|
||||
scheduler::once([entref]()
|
||||
{
|
||||
auto* _class = utils::string::va("class%d", utils::cryptography::random::get_integer() % 5);
|
||||
scripting::notify(entref, "luinotifyserver", {"class_select", _class});
|
||||
}, scheduler::pipeline::server, 2s);
|
||||
}, scheduler::pipeline::server, 2s);
|
||||
}
|
||||
|
||||
void spawn_bot(const int entity_num)
|
||||
{
|
||||
game::SV_SpawnTestClient(&game::mp::g_entities[entity_num]);
|
||||
if (game::Com_GetCurrentCoDPlayMode() == game::CODPLAYMODE_CORE)
|
||||
{
|
||||
bot_team_join(entity_num);
|
||||
}
|
||||
}
|
||||
|
||||
void add_bot()
|
||||
{
|
||||
if (!can_add())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// SV_BotGetRandomName
|
||||
const auto* const bot_name = game::SV_BotGetRandomName();
|
||||
auto* bot_ent = game::SV_AddBot(bot_name);
|
||||
if (bot_ent)
|
||||
{
|
||||
spawn_bot(bot_ent->s.entityNum);
|
||||
}
|
||||
else if (can_add()) // workaround since first bot won't ever spawn
|
||||
{
|
||||
add_bot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() || game::VirtualLobby_Loaded()) return;
|
||||
|
||||
auto num_bots = 1;
|
||||
if (params.size() == 2)
|
||||
{
|
||||
num_bots = atoi(params.get(1));
|
||||
}
|
||||
|
||||
for (auto i = 0; i < (num_bots > *game::mp::svs_numclients ? *game::mp::svs_numclients : num_bots); i++)
|
||||
{
|
||||
scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(bots::component)
|
65
src/client/component/branding.cpp
Normal file
65
src/client/component/branding.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "localized_strings.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "command.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
// fonts/default.otf, fonts/defaultBold.otf, fonts/fira_mono_regular.ttf, fonts/fira_mono_bold.ttf
|
||||
|
||||
namespace branding
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour ui_get_formatted_build_number_hook;
|
||||
|
||||
float color[4] = {0.666f, 0.666f, 0.666f, 0.666f};
|
||||
|
||||
const char* ui_get_formatted_build_number_stub()
|
||||
{
|
||||
const auto* const build_num = ui_get_formatted_build_number_hook.invoke<const char*>();
|
||||
return utils::string::va("%s (%s)", VERSION, build_num);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
localized_strings::override("LUA_MENU_MULTIPLAYER_CAPS", "H1-MOD: MULTIPLAYER\n");
|
||||
localized_strings::override("MENU_MULTIPLAYER_CAPS", "H1-MOD: MULTIPLAYER");
|
||||
}
|
||||
|
||||
dvars::override::set_string("version", utils::string::va("H1-Mod %s", VERSION));
|
||||
|
||||
ui_get_formatted_build_number_hook.create(
|
||||
SELECT_VALUE(0x1403B1C40, 0x1404E74C0), ui_get_formatted_build_number_stub);
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
const auto font = game::R_RegisterFont("fonts/fira_mono_bold.ttf", 20);
|
||||
|
||||
game::R_AddCmdDrawText("H1-Mod: " VERSION, 0x7FFFFFFF, font, 10.f,
|
||||
5.f + static_cast<float>(font->pixelHeight), 1.f, 1.f, 0.0f, color, 0);
|
||||
}, scheduler::pipeline::renderer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(branding::component)
|
182
src/client/component/colors.cpp
Normal file
182
src/client/component/colors.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace colors
|
||||
{
|
||||
struct hsv_color
|
||||
{
|
||||
unsigned char h;
|
||||
unsigned char s;
|
||||
unsigned char v;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
std::vector<DWORD> color_table;
|
||||
|
||||
DWORD hsv_to_rgb(const hsv_color hsv)
|
||||
{
|
||||
DWORD rgb;
|
||||
|
||||
if (hsv.s == 0)
|
||||
{
|
||||
return RGB(hsv.v, hsv.v, hsv.v);
|
||||
}
|
||||
|
||||
// converting to 16 bit to prevent overflow
|
||||
const unsigned int h = hsv.h;
|
||||
const unsigned int s = hsv.s;
|
||||
const unsigned int v = hsv.v;
|
||||
|
||||
const auto region = static_cast<uint8_t>(h / 43);
|
||||
const auto remainder = (h - (region * 43)) * 6;
|
||||
|
||||
const auto p = static_cast<uint8_t>((v * (255 - s)) >> 8);
|
||||
const auto q = static_cast<uint8_t>(
|
||||
(v * (255 - ((s * remainder) >> 8))) >> 8);
|
||||
const auto t = static_cast<uint8_t>(
|
||||
(v * (255 - ((s * (255 - remainder)) >> 8))) >> 8);
|
||||
|
||||
switch (region)
|
||||
{
|
||||
case 0:
|
||||
rgb = RGB(v, t, p);
|
||||
break;
|
||||
case 1:
|
||||
rgb = RGB(q, v, p);
|
||||
break;
|
||||
case 2:
|
||||
rgb = RGB(p, v, t);
|
||||
break;
|
||||
case 3:
|
||||
rgb = RGB(p, q, v);
|
||||
break;
|
||||
case 4:
|
||||
rgb = RGB(t, p, v);
|
||||
break;
|
||||
default:
|
||||
rgb = RGB(v, p, q);
|
||||
break;
|
||||
}
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
int color_index(const char c)
|
||||
{
|
||||
const auto index = c - 48;
|
||||
return index >= 0xC ? 7 : index;
|
||||
}
|
||||
|
||||
char add(const uint8_t r, const uint8_t g, const uint8_t b)
|
||||
{
|
||||
const char index = '0' + static_cast<char>(color_table.size());
|
||||
color_table.push_back(RGB(r, g, b));
|
||||
return index;
|
||||
}
|
||||
|
||||
void com_clean_name_stub(const char* in, char* out, const int out_size)
|
||||
{
|
||||
strncpy_s(out, out_size, in, _TRUNCATE);
|
||||
}
|
||||
|
||||
char* i_clean_str_stub(char* string)
|
||||
{
|
||||
utils::string::strip(string, string, static_cast<int>(strlen(string)) + 1);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
size_t get_client_name_stub(const int local_client_num, const int index, char* buf, const int size,
|
||||
const size_t unk, const size_t unk2)
|
||||
{
|
||||
// CL_GetClientName (CL_GetClientNameAndClantag?)
|
||||
const auto result = utils::hook::invoke<size_t>(0x14025BAA0, local_client_num, index, buf, size, unk, unk2);
|
||||
|
||||
utils::string::strip(buf, buf, size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void rb_lookup_color_stub(const char index, DWORD* color)
|
||||
{
|
||||
*color = RGB(255, 255, 255);
|
||||
|
||||
if (index == '8')
|
||||
{
|
||||
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x14F142FF8, 0x14FE70634)); // H1(1.4)
|
||||
}
|
||||
else if (index == '9')
|
||||
{
|
||||
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x14F142FFC, 0x14FE70638)); // H1(1.4)
|
||||
}
|
||||
else if (index == ':')
|
||||
{
|
||||
*color = hsv_to_rgb({static_cast<uint8_t>((game::Sys_Milliseconds() / 100) % 256), 255, 255});
|
||||
}
|
||||
else if (index == ';')
|
||||
{
|
||||
*color = *reinterpret_cast<DWORD*>(SELECT_VALUE(0x14F143004, 0x14FE70640)); // H1(1.4)
|
||||
}
|
||||
else if (index == '<')
|
||||
{
|
||||
*color = 0xFFFCFF80;
|
||||
}
|
||||
else
|
||||
{
|
||||
*color = color_table[color_index(index)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
// allows colored name in-game
|
||||
utils::hook::jump(0x140503810, com_clean_name_stub); // H1(1.4)
|
||||
|
||||
// don't apply colors to overhead names
|
||||
utils::hook::call(0x1400AB416, get_client_name_stub); // H1(1.4)
|
||||
|
||||
// patch I_CleanStr
|
||||
utils::hook::jump(0x140503D00, i_clean_str_stub); // H1(1.4)
|
||||
}
|
||||
|
||||
// force new colors
|
||||
utils::hook::jump(SELECT_VALUE(0x140524BD0, 0x1406206A0), rb_lookup_color_stub); // H1(1.4)
|
||||
|
||||
// add colors
|
||||
add(0, 0, 0); // 0 - Black
|
||||
add(255, 49, 49); // 1 - Red
|
||||
add(134, 192, 0); // 2 - Green
|
||||
add(255, 173, 34); // 3 - Yellow
|
||||
add(0, 135, 193); // 4 - Blue
|
||||
add(32, 197, 255); // 5 - Light Blue
|
||||
add(151, 80, 221); // 6 - Pink
|
||||
add(255, 255, 255); // 7 - White
|
||||
|
||||
add(0, 0, 0); // 8 - Team color (axis?)
|
||||
add(0, 0, 0); // 9 - Team color (allies?)
|
||||
|
||||
add(0, 0, 0); // 10 - Rainbow (:)
|
||||
add(0, 0, 0);
|
||||
// 11 - Server color (;) - using that color in infostrings (e.g. your name) fails, ';' is an illegal character!
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(colors::component)
|
648
src/client/component/command.cpp
Normal file
648
src/client/component/command.cpp
Normal file
@ -0,0 +1,648 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "game_console.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include "utils/io.hpp"
|
||||
#include <game/dvars.hpp>
|
||||
|
||||
namespace command
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour client_command_hook;
|
||||
|
||||
std::unordered_map<std::string, std::function<void(params&)>> handlers;
|
||||
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
|
||||
|
||||
void main_handler()
|
||||
{
|
||||
params params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (handlers.find(command) != handlers.end())
|
||||
{
|
||||
handlers[command](params);
|
||||
}
|
||||
}
|
||||
|
||||
void client_command(const int client_num, void* a2)
|
||||
{
|
||||
params_sv params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (handlers_sv.find(command) != handlers_sv.end())
|
||||
{
|
||||
handlers_sv[command](client_num, params);
|
||||
}
|
||||
|
||||
client_command_hook.invoke<void>(client_num, a2);
|
||||
}
|
||||
|
||||
// Shamelessly stolen from Quake3
|
||||
// https://github.com/id-Software/Quake-III-Arena/blob/dbe4ddb10315479fc00086f08e25d968b4b43c49/code/qcommon/common.c#L364
|
||||
void parse_command_line()
|
||||
{
|
||||
static auto parsed = false;
|
||||
if (parsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static std::string comand_line_buffer = GetCommandLineA();
|
||||
auto* command_line = comand_line_buffer.data();
|
||||
|
||||
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4); //H1(1.4)
|
||||
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0); //H1(1.4)
|
||||
|
||||
auto inq = false;
|
||||
com_console_lines[0] = command_line;
|
||||
com_num_console_lines = 0;
|
||||
|
||||
while (*command_line)
|
||||
{
|
||||
if (*command_line == '"')
|
||||
{
|
||||
inq = !inq;
|
||||
}
|
||||
// look for a + separating character
|
||||
// if commandLine came from a file, we might have real line seperators
|
||||
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
|
||||
{
|
||||
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
|
||||
{
|
||||
break;
|
||||
}
|
||||
com_console_lines[com_num_console_lines] = command_line + 1;
|
||||
com_num_console_lines++;
|
||||
*command_line = '\0';
|
||||
}
|
||||
command_line++;
|
||||
}
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
void parse_commandline_stub()
|
||||
{
|
||||
parse_command_line();
|
||||
utils::hook::invoke<void>(0x1400D8210);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_command_stub()
|
||||
{
|
||||
const params args;
|
||||
|
||||
if (args.size() <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto dvar = game::Dvar_FindVar(args[0]);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
if (args.size() == 1)
|
||||
{
|
||||
const auto current = game::Dvar_ValueToString(dvar, dvar->current);
|
||||
const auto reset = game::Dvar_ValueToString(dvar, dvar->reset);
|
||||
|
||||
console::info("\"%s\" is: \"%s\" default: \"%s\" hash: 0x%08lX",
|
||||
args[0], current, reset, dvar->hash);
|
||||
|
||||
console::info(" %s\n", dvars::dvar_get_domain(dvar->type, dvar->domain).data());
|
||||
}
|
||||
else
|
||||
{
|
||||
char command[0x1000] = { 0 };
|
||||
game::Dvar_GetCombinedString(command, 1);
|
||||
game::Dvar_SetCommand(dvar->hash, "", command);
|
||||
}
|
||||
|
||||
return dvar;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void read_startup_variable(const std::string& dvar)
|
||||
{
|
||||
// parse the commandline if it's not parsed
|
||||
parse_command_line();
|
||||
|
||||
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4); //H1(1.4)
|
||||
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0); //H1(1.4)
|
||||
|
||||
for (int i = 0; i < com_num_console_lines; i++)
|
||||
{
|
||||
game::Cmd_TokenizeString(com_console_lines[i]);
|
||||
|
||||
// only +set dvar value
|
||||
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
|
||||
{
|
||||
game::Dvar_SetCommand(game::generateHashValue(game::Cmd_Argv(1)), "", game::Cmd_Argv(2));
|
||||
}
|
||||
|
||||
game::Cmd_EndTokenizeString();
|
||||
}
|
||||
}
|
||||
|
||||
params::params()
|
||||
: nesting_(game::cmd_args->nesting)
|
||||
{
|
||||
}
|
||||
|
||||
int params::size() const
|
||||
{
|
||||
return game::cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* params::get(const int index) const
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return game::cmd_args->argv[this->nesting_][index];
|
||||
}
|
||||
|
||||
std::string 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;
|
||||
}
|
||||
|
||||
params_sv::params_sv()
|
||||
: nesting_(game::sv_cmd_args->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;
|
||||
}
|
||||
|
||||
void add_raw(const char* name, void (*callback)())
|
||||
{
|
||||
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
|
||||
}
|
||||
|
||||
void add_test(const char* name, void (*callback)())
|
||||
{
|
||||
static game::cmd_function_s cmd_test;
|
||||
return game::Cmd_AddCommandInternal(name, callback, &cmd_test);
|
||||
}
|
||||
|
||||
void add(const char* name, const std::function<void(const params&)>& callback)
|
||||
{
|
||||
static game::cmd_function_s cmd_test;
|
||||
|
||||
const auto command = utils::string::to_lower(name);
|
||||
|
||||
if (handlers.find(command) == handlers.end())
|
||||
add_raw(name, main_handler);
|
||||
|
||||
handlers[command] = callback;
|
||||
}
|
||||
|
||||
void add(const char* name, const std::function<void()>& callback)
|
||||
{
|
||||
add(name, [callback](const params&)
|
||||
{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback)
|
||||
{
|
||||
// doing this so the sv command would show up in the console
|
||||
add_raw(name, nullptr);
|
||||
|
||||
const auto command = utils::string::to_lower(name);
|
||||
|
||||
if (handlers_sv.find(command) == handlers_sv.end())
|
||||
handlers_sv[command] = std::move(callback);
|
||||
}
|
||||
|
||||
void execute(std::string command, const bool sync)
|
||||
{
|
||||
command += "\n";
|
||||
|
||||
if (sync)
|
||||
{
|
||||
game::Cmd_ExecuteSingleCommand(0, 0, command.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
game::Cbuf_AddText(0, command.data());
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
add_commands_sp();
|
||||
}
|
||||
else
|
||||
{
|
||||
utils::hook::call(0x1400D728F, &parse_commandline_stub); // MWR TEST
|
||||
utils::hook::jump(0x14041D750, dvar_command_stub);
|
||||
|
||||
add_commands_mp();
|
||||
}
|
||||
|
||||
add_commands_generic();
|
||||
}
|
||||
|
||||
private:
|
||||
static void add_commands_generic()
|
||||
{
|
||||
add("quit", game::Quit);
|
||||
//add("quit_hard", utils::nt::raise_hard_exception); /* this command delivers you to a windows blue screen, its quit hard from windows xD */
|
||||
add("crash", []()
|
||||
{
|
||||
*reinterpret_cast<int*>(1) = 0;
|
||||
});
|
||||
|
||||
/*add("consoleList", [](const params& params)
|
||||
{
|
||||
const std::string input = params.get(1);
|
||||
|
||||
std::vector<std::string> matches;
|
||||
game_console::find_matches(input, matches, false);
|
||||
|
||||
for (auto& match : matches)
|
||||
{
|
||||
auto* dvar = game::Dvar_FindVar(match.c_str());
|
||||
if (!dvar)
|
||||
{
|
||||
console::info("[CMD]\t %s\n", match.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::info("[DVAR]\t%s \"%s\"\n", match.c_str(), game::Dvar_ValueToString(dvar, dvar->current, 0));
|
||||
}
|
||||
}
|
||||
|
||||
console::info("Total %i matches\n", matches.size());
|
||||
});*/
|
||||
|
||||
add("commandDump", [](const params& argument)
|
||||
{
|
||||
console::info("================================ COMMAND DUMP =====================================\n");
|
||||
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
std::string filename;
|
||||
if (argument.size() == 2)
|
||||
{
|
||||
filename = "h1-mod/";
|
||||
filename.append(argument[1]);
|
||||
if (!filename.ends_with(".txt"))
|
||||
{
|
||||
filename.append(".txt");
|
||||
}
|
||||
}
|
||||
int i = 0;
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
if (!filename.empty())
|
||||
{
|
||||
const auto line = std::format("{}\r\n", cmd->name);
|
||||
utils::io::write_file(filename, line, i != 0);
|
||||
}
|
||||
console::info("%s\n", cmd->name);
|
||||
i++;
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
console::info("\n%i commands\n", i);
|
||||
console::info("================================ END COMMAND DUMP =================================\n");
|
||||
});
|
||||
|
||||
/*add("listassetpool", [](const params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("listassetpool <poolnumber> [filter]: list all the assets in the specified pool\n");
|
||||
|
||||
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
console::info("%d %s\n", i, game::g_assetNames[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
|
||||
|
||||
if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
|
||||
{
|
||||
console::error("Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("Listing assets in pool %s\n", game::g_assetNames[type]);
|
||||
|
||||
const std::string filter = params.get(2);
|
||||
enum_assets(type, [type, filter](const game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = game::XAsset{type, header};
|
||||
const auto* const asset_name = game::DB_GetXAssetName(&asset);
|
||||
//const auto entry = game::DB_FindXAssetEntry(type, asset_name);
|
||||
//TODO: display which zone the asset is from
|
||||
|
||||
if (!filter.empty() && !game_console::match_compare(filter, asset_name, false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("%s\n", asset_name);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
|
||||
add("vstr", [](const params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("vstr <variablename> : execute a variable command\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* dvarName = params.get(1);
|
||||
const auto* dvar = game::Dvar_FindVar(dvarName);
|
||||
|
||||
if (dvar == nullptr)
|
||||
{
|
||||
console::info("%s doesn't exist\n", dvarName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dvar->type != game::dvar_type::string
|
||||
&& dvar->type != game::dvar_type::enumeration)
|
||||
{
|
||||
console::info("%s is not a string-based dvar\n", dvar->hash);
|
||||
return;
|
||||
}
|
||||
|
||||
execute(dvar->current.string);
|
||||
});*/
|
||||
}
|
||||
|
||||
static void add_commands_sp()
|
||||
{
|
||||
add("god", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].flags ^= 1;
|
||||
game::CG_GameMessage(0, utils::string::va("godmode %s",
|
||||
game::sp::g_entities[0].flags & 1
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("demigod", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].flags ^= 2;
|
||||
game::CG_GameMessage(0, utils::string::va("demigod mode %s",
|
||||
game::sp::g_entities[0].flags & 2
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("notarget", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].flags ^= 4;
|
||||
game::CG_GameMessage(0, utils::string::va("notarget %s",
|
||||
game::sp::g_entities[0].flags & 4
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("noclip", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].client->flags ^= 1;
|
||||
game::CG_GameMessage(0, utils::string::va("noclip %s",
|
||||
game::sp::g_entities[0].client->flags & 1
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add("ufo", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::sp::g_entities[0].client->flags ^= 2;
|
||||
game::CG_GameMessage(0, utils::string::va("ufo %s",
|
||||
game::sp::g_entities[0].client->flags & 2
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
}
|
||||
|
||||
static void add_commands_mp()
|
||||
{
|
||||
//client_command_hook.create(0x1402E98F0, &client_command);
|
||||
|
||||
/*add_sv("god", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
game::mp::g_entities[client_num].flags ^= 1;
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"godmode %s\"",
|
||||
game::mp::g_entities[client_num].flags & 1
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add_sv("demigod", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
game::mp::g_entities[client_num].flags ^= 2;
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"demigod mode %s\"",
|
||||
game::mp::g_entities[client_num].flags & 2
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add_sv("notarget", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
game::mp::g_entities[client_num].flags ^= 4;
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"notarget %s\"",
|
||||
game::mp::g_entities[client_num].flags & 4
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add_sv("noclip", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
game::mp::g_entities[client_num].client->flags ^= 1;
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"noclip %s\"",
|
||||
game::mp::g_entities[client_num].client->flags & 1
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add_sv("ufo", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
game::mp::g_entities[client_num].client->flags ^= 2;
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"ufo %s\"",
|
||||
game::mp::g_entities[client_num].client->flags & 2
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});
|
||||
|
||||
add_sv("give", [](const int client_num, const params_sv& params)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"You did not specify a weapon name\"");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0, 0, 0))
|
||||
{
|
||||
game::G_InitializeAmmo(ps, wp, 0);
|
||||
game::G_SelectWeapon(client_num, wp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_sv("take", [](const int client_num, const params_sv& params)
|
||||
{
|
||||
if (!game::Dvar_FindVar("sv_cheats")->current.enabled)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"Cheats are not enabled on this server\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
"f \"You did not specify a weapon name\"");
|
||||
return;
|
||||
}
|
||||
|
||||
auto ps = game::SV_GetPlayerstateForClientNum(client_num);
|
||||
const auto wp = game::G_GetWeaponForName(params.get(1));
|
||||
if (wp)
|
||||
{
|
||||
game::G_TakePlayerWeapon(ps, wp);
|
||||
}
|
||||
});*/
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(command::component)
|
50
src/client/component/command.hpp
Normal file
50
src/client/component/command.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
namespace command
|
||||
{
|
||||
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_;
|
||||
};
|
||||
|
||||
void read_startup_variable(const std::string& dvar);
|
||||
|
||||
void add_raw(const char* name, void (*callback)());
|
||||
void add(const char* name, const std::function<void(const params&)>& callback);
|
||||
void add(const char* name, const std::function<void()>& callback);
|
||||
|
||||
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback);
|
||||
|
||||
void execute(std::string command, bool sync = false);
|
||||
}
|
299
src/client/component/console.cpp
Normal file
299
src/client/component/console.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
#include <std_include.hpp>
|
||||
#include "console.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
void print(int type, const std::string& data);
|
||||
}
|
||||
|
||||
namespace console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using message_queue = std::queue<std::string>;
|
||||
utils::concurrency::container<message_queue> messages;
|
||||
|
||||
bool native_console()
|
||||
{
|
||||
static const auto flag = utils::flags::has_flag("nativeconsole");
|
||||
return flag;
|
||||
}
|
||||
|
||||
void hide_console()
|
||||
{
|
||||
auto* const con_window = GetConsoleWindow();
|
||||
|
||||
DWORD process;
|
||||
GetWindowThreadProcessId(con_window, &process);
|
||||
|
||||
if (!native_console() && (process == GetCurrentProcessId() || IsDebuggerPresent()))
|
||||
{
|
||||
ShowWindow(con_window, SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
std::string format(va_list* ap, const char* message)
|
||||
{
|
||||
static thread_local char buffer[0x1000];
|
||||
|
||||
const auto count = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), message, *ap);
|
||||
|
||||
if (count < 0) return {};
|
||||
return {buffer, static_cast<size_t>(count)};
|
||||
}
|
||||
|
||||
void dispatch_message(const int type, const std::string& message)
|
||||
{
|
||||
if (native_console())
|
||||
{
|
||||
printf("%s\n", message.data());
|
||||
return;
|
||||
}
|
||||
|
||||
game_console::print(type, message);
|
||||
messages.access([&message](message_queue& msgs)
|
||||
{
|
||||
msgs.emplace(message);
|
||||
});
|
||||
}
|
||||
|
||||
void append_text(const char* text)
|
||||
{
|
||||
dispatch_message(con_type_info, text);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
hide_console();
|
||||
|
||||
if (native_console())
|
||||
{
|
||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)_pipe(this->handles_, 1024, _O_TEXT);
|
||||
(void)_dup2(this->handles_[1], 1);
|
||||
(void)_dup2(this->handles_[1], 2);
|
||||
}
|
||||
}
|
||||
|
||||
void post_start() override
|
||||
{
|
||||
this->terminate_runner_ = false;
|
||||
|
||||
this->console_runner_ = utils::thread::create_named_thread("Console IO", [this]
|
||||
{
|
||||
if (native_console())
|
||||
{
|
||||
this->native_input();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->runner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
this->terminate_runner_ = true;
|
||||
|
||||
printf("\r\n");
|
||||
_flushall();
|
||||
|
||||
if (this->console_runner_.joinable())
|
||||
{
|
||||
this->console_runner_.join();
|
||||
}
|
||||
|
||||
if (this->console_thread_.joinable())
|
||||
{
|
||||
this->console_thread_.join();
|
||||
}
|
||||
|
||||
#ifndef NATIVE_CONSOLE
|
||||
_close(this->handles_[0]);
|
||||
_close(this->handles_[1]);
|
||||
#endif
|
||||
|
||||
messages.access([&](message_queue& msgs)
|
||||
{
|
||||
msgs = {};
|
||||
});
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// Redirect input (]command)
|
||||
utils::hook::jump(SELECT_VALUE(0x1403E34C0, 0x1405141E0), append_text); // H1(1.4)
|
||||
|
||||
this->initialize();
|
||||
}
|
||||
|
||||
private:
|
||||
volatile bool console_initialized_ = false;
|
||||
volatile bool terminate_runner_ = false;
|
||||
|
||||
std::thread console_runner_;
|
||||
std::thread console_thread_;
|
||||
|
||||
int handles_[2]{};
|
||||
|
||||
void initialize()
|
||||
{
|
||||
this->console_thread_ = utils::thread::create_named_thread("Console", [this]()
|
||||
{
|
||||
if (!native_console() && (game::environment::is_dedi() || !utils::flags::has_flag("noconsole")))
|
||||
{
|
||||
game::Sys_ShowConsole();
|
||||
}
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
// Hide that shit
|
||||
ShowWindow(console::get_window(), SW_MINIMIZE);
|
||||
}
|
||||
|
||||
{
|
||||
messages.access([&](message_queue&)
|
||||
{
|
||||
this->console_initialized_ = true;
|
||||
});
|
||||
}
|
||||
|
||||
MSG msg;
|
||||
while (!this->terminate_runner_)
|
||||
{
|
||||
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
if (msg.message == WM_QUIT)
|
||||
{
|
||||
command::execute("quit", false);
|
||||
break;
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->log_messages();
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void log_messages()
|
||||
{
|
||||
/*while*/
|
||||
if (this->console_initialized_ && !messages.get_raw().empty())
|
||||
{
|
||||
std::queue<std::string> message_queue_copy;
|
||||
|
||||
{
|
||||
messages.access([&](message_queue& msgs)
|
||||
{
|
||||
message_queue_copy = std::move(msgs);
|
||||
msgs = {};
|
||||
});
|
||||
}
|
||||
|
||||
while (!message_queue_copy.empty())
|
||||
{
|
||||
log_message(message_queue_copy.front());
|
||||
message_queue_copy.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static void log_message(const std::string& message)
|
||||
{
|
||||
OutputDebugStringA(message.data());
|
||||
game::Conbuf_AppendText(message.data());
|
||||
}
|
||||
|
||||
void runner()
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
while (!this->terminate_runner_ && this->handles_[0])
|
||||
{
|
||||
const auto len = _read(this->handles_[0], buffer, sizeof(buffer));
|
||||
if (len > 0)
|
||||
{
|
||||
dispatch_message(con_type_info, std::string(buffer, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
void native_input()
|
||||
{
|
||||
std::string cmd;
|
||||
|
||||
while (!this->terminate_runner_)
|
||||
{
|
||||
std::getline(std::cin, cmd);
|
||||
command::execute(cmd);
|
||||
}
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
};
|
||||
|
||||
HWND get_window()
|
||||
{
|
||||
return *reinterpret_cast<HWND*>((SELECT_VALUE(0x14CF56C00, 0x14DDFC2D0))); // H1(1.4)
|
||||
}
|
||||
|
||||
void set_title(std::string title)
|
||||
{
|
||||
SetWindowText(get_window(), title.data());
|
||||
}
|
||||
|
||||
void set_size(const int width, const int height)
|
||||
{
|
||||
RECT rect;
|
||||
GetWindowRect(get_window(), &rect);
|
||||
|
||||
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
|
||||
|
||||
auto* const logo_window = *reinterpret_cast<HWND*>(SELECT_VALUE(0x14CF56C10, 0x14DDFC2E0)); // H1(1.4)
|
||||
SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0);
|
||||
}
|
||||
|
||||
void print(const int type, const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
const auto result = format(&ap, fmt);
|
||||
va_end(ap);
|
||||
|
||||
dispatch_message(type, result);
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(console::component)
|
35
src/client/component/console.hpp
Normal file
35
src/client/component/console.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
namespace console
|
||||
{
|
||||
HWND get_window();
|
||||
void set_title(std::string title);
|
||||
void set_size(int width, int height);
|
||||
|
||||
enum console_type
|
||||
{
|
||||
con_type_error = 1,
|
||||
con_type_warning = 3,
|
||||
con_type_info = 7
|
||||
};
|
||||
|
||||
void print(int type, const char* fmt, ...);
|
||||
|
||||
template <typename... Args>
|
||||
void error(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_error, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_warning, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_info, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
333
src/client/component/dedicated.cpp
Normal file
333
src/client/component/dedicated.cpp
Normal file
@ -0,0 +1,333 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "network.hpp"
|
||||
#include "command.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
#include "dvars.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace dedicated
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour gscr_set_dynamic_dvar_hook;
|
||||
utils::hook::detour com_quit_f_hook;
|
||||
|
||||
void init_dedicated_server()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
// R_LoadGraphicsAssets
|
||||
utils::hook::invoke<void>(0x1405DF4B0);
|
||||
}
|
||||
|
||||
void send_heartbeat()
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar("sv_lanOnly");
|
||||
if (dvar && dvar->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::netadr_s target{};
|
||||
if (server_list::get_master_server(target))
|
||||
{
|
||||
network::send(target, "heartbeat", "H1");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string>& get_startup_command_queue()
|
||||
{
|
||||
static std::vector<std::string> startup_command_queue;
|
||||
return startup_command_queue;
|
||||
}
|
||||
|
||||
void execute_startup_command(int client, int /*controllerIndex*/, const char* command)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||
{
|
||||
game::Cbuf_ExecuteBufferInternal(0, 0, command, game::Cmd_ExecuteSingleCommand);
|
||||
}
|
||||
else
|
||||
{
|
||||
get_startup_command_queue().emplace_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_startup_command_queue()
|
||||
{
|
||||
const auto queue = get_startup_command_queue();
|
||||
get_startup_command_queue().clear();
|
||||
|
||||
for (const auto& command : queue)
|
||||
{
|
||||
game::Cbuf_ExecuteBufferInternal(0, 0, command.data(), game::Cmd_ExecuteSingleCommand);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string>& get_console_command_queue()
|
||||
{
|
||||
static std::vector<std::string> console_command_queue;
|
||||
return console_command_queue;
|
||||
}
|
||||
|
||||
void execute_console_command(const int client, const char* command)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 0)
|
||||
{
|
||||
game::Cbuf_AddText(client, command);
|
||||
game::Cbuf_AddText(client, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
get_console_command_queue().emplace_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
void execute_console_command_queue()
|
||||
{
|
||||
const auto queue = get_console_command_queue();
|
||||
get_console_command_queue().clear();
|
||||
|
||||
for (const auto& command : queue)
|
||||
{
|
||||
game::Cbuf_AddText(0, command.data());
|
||||
game::Cbuf_AddText(0, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sync_gpu_stub()
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
game::dvar_t* gscr_set_dynamic_dvar()
|
||||
{
|
||||
/*
|
||||
auto s = game::Scr_GetString(0);
|
||||
auto* dvar = game::Dvar_FindVar(s);
|
||||
|
||||
if (dvar && !strncmp("scr_", dvar->name, 4))
|
||||
{
|
||||
return dvar;
|
||||
}
|
||||
*/
|
||||
|
||||
return gscr_set_dynamic_dvar_hook.invoke<game::dvar_t*>();
|
||||
}
|
||||
|
||||
void kill_server()
|
||||
{
|
||||
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
if (game::mp::svs_clients[i].header.state >= 3)
|
||||
{
|
||||
game::SV_GameSendServerCommand(i, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("r \"%s\"", "EXE_ENDOFGAME"));
|
||||
}
|
||||
}
|
||||
|
||||
com_quit_f_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void sys_error_stub(const char* msg, ...)
|
||||
{
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
|
||||
|
||||
va_end(ap);
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
command::execute("map_rotate");
|
||||
}, scheduler::main, 3s);
|
||||
|
||||
game::Com_Error(game::ERR_DROP, "%s", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void initialize()
|
||||
{
|
||||
command::execute("exec default_xboxlive.cfg", true);
|
||||
command::execute("onlinegame 1", true);
|
||||
command::execute("xblive_privatematch 1", true);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("Starting dedicated server\n");
|
||||
#endif
|
||||
|
||||
// Register dedicated dvar
|
||||
dvars::register_bool("dedicated", true, game::DVAR_FLAG_READ);
|
||||
|
||||
// Add lanonly mode
|
||||
dvars::register_bool("sv_lanOnly", false, game::DVAR_FLAG_NONE);
|
||||
|
||||
// Disable VirtualLobby
|
||||
dvars::override::register_bool("virtualLobbyEnabled", false, game::DVAR_FLAG_READ);
|
||||
|
||||
// Disable r_preloadShaders
|
||||
dvars::override::register_bool("r_preloadShaders", false, game::DVAR_FLAG_READ);
|
||||
|
||||
// Stop crashing from sys_errors
|
||||
utils::hook::jump(0x140511520, sys_error_stub);
|
||||
|
||||
// Hook R_SyncGpu
|
||||
utils::hook::jump(0x1405E12F0, sync_gpu_stub);
|
||||
|
||||
utils::hook::jump(0x140254800, init_dedicated_server);
|
||||
|
||||
// delay startup commands until the initialization is done
|
||||
utils::hook::call(0x1400D72D6, execute_startup_command);
|
||||
|
||||
// delay console commands until the initialization is done
|
||||
utils::hook::call(0x1400D808C, execute_console_command);
|
||||
utils::hook::nop(0x1400D80A4, 5);
|
||||
|
||||
// patch GScr_SetDynamicDvar to behave better
|
||||
gscr_set_dynamic_dvar_hook.create(0x14036B600, &gscr_set_dynamic_dvar);
|
||||
|
||||
utils::hook::nop(0x1404ED90E, 5); // don't load config file
|
||||
utils::hook::nop(0x140403D92, 5); // ^
|
||||
utils::hook::set<uint8_t>(0x1400DC1D0, 0xC3); // don't save config file
|
||||
utils::hook::set<uint8_t>(0x140274710, 0xC3); // disable self-registration
|
||||
utils::hook::set<uint8_t>(0x140515890, 0xC3); // init sound system (1)
|
||||
utils::hook::set<uint8_t>(0x1406574F0, 0xC3); // init sound system (2)
|
||||
utils::hook::set<uint8_t>(0x140620D10, 0xC3); // render thread
|
||||
utils::hook::set<uint8_t>(0x14025B850, 0xC3); // called from Com_Frame, seems to do renderer stuff
|
||||
utils::hook::set<uint8_t>(0x1402507B0, 0xC3); // CL_CheckForResend, which tries to connect to the local server constantly
|
||||
utils::hook::set<uint8_t>(0x1405D5178, 0x00); // r_loadForRenderer default to 0
|
||||
utils::hook::set<uint8_t>(0x14050C2D0, 0xC3); // recommended settings check - TODO: Check hook
|
||||
utils::hook::set<uint8_t>(0x140514C00, 0xC3); // some mixer-related function called on shutdown
|
||||
utils::hook::set<uint8_t>(0x140409830, 0xC3); // dont load ui gametype stuff
|
||||
|
||||
utils::hook::nop(0x140481B06, 6); // unknown check in SV_ExecuteClientMessage
|
||||
utils::hook::nop(0x140480FAC, 4); // allow first slot to be occupied
|
||||
utils::hook::nop(0x14025619B, 2); // properly shut down dedicated servers
|
||||
utils::hook::nop(0x14025615E, 2); // ^
|
||||
utils::hook::nop(0x1402561C0, 5); // don't shutdown renderer
|
||||
|
||||
utils::hook::set<uint8_t>(0x140091840, 0xC3); // something to do with blendShapeVertsView
|
||||
utils::hook::nop(0x140659A0D, 8); // sound thing
|
||||
|
||||
// (COULD NOT FIND IN H1)
|
||||
// utils::hook::set<uint8_t>(0x1404D6960, 0xC3); // cpu detection stuff?
|
||||
utils::hook::set<uint8_t>(0x1405E97F0, 0xC3); // gfx stuff during fastfile loading
|
||||
utils::hook::set<uint8_t>(0x1405E9700, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405E9790, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1402C1180, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405E9750, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405AD5B0, 0xC3); // directx stuff
|
||||
utils::hook::set<uint8_t>(0x1405DB150, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x140625220, 0xC3); // ^ - mutex
|
||||
utils::hook::set<uint8_t>(0x1405DB650, 0xC3); // ^
|
||||
|
||||
utils::hook::set<uint8_t>(0x14008B5F0, 0xC3); // rendering stuff
|
||||
utils::hook::set<uint8_t>(0x1405DB8B0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405DB9C0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405DC050, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405DCBA0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405DD240, 0xC3); // ^
|
||||
|
||||
// shaders
|
||||
utils::hook::set<uint8_t>(0x1400916A0, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x140091610, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x14061ACC0, 0xC3); // ^ - mutex
|
||||
|
||||
utils::hook::set<uint8_t>(0x140516080, 0xC3); // idk
|
||||
utils::hook::set<uint8_t>(0x1405AE5F0, 0xC3); // ^
|
||||
|
||||
utils::hook::set<uint8_t>(0x1405E0B30, 0xC3); // R_Shutdown
|
||||
utils::hook::set<uint8_t>(0x1405AE400, 0xC3); // shutdown stuff
|
||||
utils::hook::set<uint8_t>(0x1405E0C00, 0xC3); // ^
|
||||
utils::hook::set<uint8_t>(0x1405DFE50, 0xC3); // ^
|
||||
|
||||
// utils::hook::set<uint8_t>(0x1404B67E0, 0xC3); // sound crashes (H1 - questionable, function looks way different)
|
||||
|
||||
utils::hook::set<uint8_t>(0x14048B660, 0xC3); // disable host migration
|
||||
|
||||
utils::hook::set<uint8_t>(0x14042B2E0, 0xC3); // render synchronization lock
|
||||
utils::hook::set<uint8_t>(0x14042B210, 0xC3); // render synchronization unlock
|
||||
|
||||
utils::hook::set<uint8_t>(0x140176D2D, 0xEB); // LUI: Unable to start the LUI system due to errors in main.lua
|
||||
|
||||
utils::hook::nop(0x140506ECE, 5); // Disable sound pak file loading
|
||||
utils::hook::nop(0x140506ED6, 2); // ^
|
||||
utils::hook::set<uint8_t>(0x1402C5910, 0xC3); // Disable image pak file loading
|
||||
|
||||
// Reduce min required memory
|
||||
utils::hook::set<uint64_t>(0x14050C717, 0x80000000);
|
||||
|
||||
utils::hook::set(0x1402BF7F0, 0xC3); // some loop
|
||||
utils::hook::set(0x14007E150, 0xC3); // related to shader caching / techsets / fastfiles
|
||||
|
||||
// initialize the game after onlinedataflags is 32 (workaround)
|
||||
scheduler::schedule([=]()
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 32 && game::Sys_IsDatabaseReady2())
|
||||
{
|
||||
scheduler::once([]()
|
||||
{
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("disconnect", true); // 32 -> 0
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return scheduler::cond_end;
|
||||
}
|
||||
|
||||
return scheduler::cond_continue;
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
scheduler::on_game_initialized([]()
|
||||
{
|
||||
initialize();
|
||||
|
||||
console::info("==================================\n");
|
||||
console::info("Server started!\n");
|
||||
console::info("==================================\n");
|
||||
|
||||
// remove disconnect command
|
||||
game::Cmd_RemoveCommand("disconnect");
|
||||
|
||||
execute_startup_command_queue();
|
||||
execute_console_command_queue();
|
||||
|
||||
// Send heartbeat to dpmaster
|
||||
scheduler::once(send_heartbeat, scheduler::pipeline::server);
|
||||
scheduler::loop(send_heartbeat, scheduler::pipeline::server, 10min);
|
||||
command::add("heartbeat", send_heartbeat);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
command::add("killserver", kill_server);
|
||||
com_quit_f_hook.create(0x1400DA640, &kill_server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dedicated::component)
|
65
src/client/component/dedicated_info.cpp
Normal file
65
src/client/component/dedicated_info.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include <std_include.hpp>
|
||||
#include "console.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include <utils\string.hpp>
|
||||
|
||||
namespace dedicated_info
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
auto* sv_running = game::Dvar_FindVar("sv_running");
|
||||
if (!sv_running || !sv_running->current.enabled)
|
||||
{
|
||||
console::set_title("H1-Mod Dedicated Server");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* const sv_hostname = game::Dvar_FindVar("sv_hostname");
|
||||
auto* const sv_maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||
auto* const mapname = game::Dvar_FindVar("mapname");
|
||||
|
||||
auto bot_count = 0;
|
||||
auto client_count = 0;
|
||||
|
||||
for (auto i = 0; i < sv_maxclients->current.integer; i++)
|
||||
{
|
||||
auto* client = &game::mp::svs_clients[i];
|
||||
auto* self = &game::mp::g_entities[i];
|
||||
|
||||
if (client->header.state >= 1 && self && self->client)
|
||||
{
|
||||
client_count++;
|
||||
if (game::SV_BotIsBot(i))
|
||||
{
|
||||
++bot_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string cleaned_hostname;
|
||||
cleaned_hostname.resize(static_cast<int>(strlen(sv_hostname->current.string) + 1));
|
||||
|
||||
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(),
|
||||
static_cast<int>(strlen(sv_hostname->current.string)) + 1);
|
||||
|
||||
console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.data(),
|
||||
mapname->current.string, client_count,
|
||||
sv_maxclients->current.integer, bot_count));
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dedicated_info::component)
|
604
src/client/component/demonware.cpp
Normal file
604
src/client/component/demonware.cpp
Normal file
@ -0,0 +1,604 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/demonware/servers/lobby_server.hpp"
|
||||
#include "game/demonware/servers/auth3_server.hpp"
|
||||
#include "game/demonware/servers/stun_server.hpp"
|
||||
#include "game/demonware/servers/umbrella_server.hpp"
|
||||
#include "game/demonware/server_registry.hpp"
|
||||
#include <game/dvars.hpp>
|
||||
|
||||
#define TCP_BLOCKING true
|
||||
#define UDP_BLOCKING false
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
namespace
|
||||
{
|
||||
volatile bool exit_server;
|
||||
std::thread server_thread;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, bool>> blocking_sockets;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, tcp_server*>> socket_map;
|
||||
server_registry<tcp_server> tcp_servers;
|
||||
server_registry<udp_server> udp_servers;
|
||||
|
||||
tcp_server* find_server(const SOCKET socket)
|
||||
{
|
||||
return socket_map.access<tcp_server*>([&](const std::unordered_map<SOCKET, tcp_server*>& map) -> tcp_server*
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
bool socket_link(const SOCKET socket, const uint32_t address)
|
||||
{
|
||||
auto* server = tcp_servers.find(address);
|
||||
if (!server)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
map[socket] = server;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void socket_unlink(const SOCKET socket)
|
||||
{
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool is_socket_blocking(const SOCKET socket, const bool def)
|
||||
{
|
||||
return blocking_sockets.access<bool>([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
void remove_blocking_socket(const SOCKET socket)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void add_blocking_socket(const SOCKET socket, const bool block)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
map[socket] = block;
|
||||
});
|
||||
}
|
||||
|
||||
void server_main()
|
||||
{
|
||||
exit_server = false;
|
||||
|
||||
while (!exit_server)
|
||||
{
|
||||
tcp_servers.frame();
|
||||
udp_servers.frame();
|
||||
std::this_thread::sleep_for(50ms);
|
||||
}
|
||||
}
|
||||
|
||||
namespace io
|
||||
{
|
||||
int getaddrinfo_stub(const char* name, const char* service,
|
||||
const addrinfo* hints, addrinfo** res)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[ network ]: [getaddrinfo]: \"%s\" \"%s\"\n", name, service);
|
||||
#endif
|
||||
|
||||
base_server* server = tcp_servers.find(name);
|
||||
if (!server)
|
||||
{
|
||||
server = udp_servers.find(name);
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
return getaddrinfo(name, service, hints, res);
|
||||
}
|
||||
|
||||
const auto address = utils::memory::get_allocator()->allocate<sockaddr>();
|
||||
const auto ai = utils::memory::get_allocator()->allocate<addrinfo>();
|
||||
|
||||
auto in_addr = reinterpret_cast<sockaddr_in*>(address);
|
||||
in_addr->sin_addr.s_addr = server->get_address();
|
||||
in_addr->sin_family = AF_INET;
|
||||
|
||||
ai->ai_family = AF_INET;
|
||||
ai->ai_socktype = SOCK_STREAM;
|
||||
ai->ai_addr = address;
|
||||
ai->ai_addrlen = sizeof(sockaddr);
|
||||
ai->ai_next = nullptr;
|
||||
ai->ai_flags = 0;
|
||||
ai->ai_protocol = 0;
|
||||
ai->ai_canonname = const_cast<char*>(name);
|
||||
|
||||
*res = ai;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void freeaddrinfo_stub(addrinfo* ai)
|
||||
{
|
||||
if (!utils::memory::get_allocator()->find(ai))
|
||||
{
|
||||
return freeaddrinfo(ai);
|
||||
}
|
||||
|
||||
utils::memory::get_allocator()->free(ai->ai_addr);
|
||||
utils::memory::get_allocator()->free(ai);
|
||||
}
|
||||
|
||||
int getpeername_stub(const SOCKET s, sockaddr* addr, socklen_t* addrlen)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
auto in_addr = reinterpret_cast<sockaddr_in*>(addr);
|
||||
in_addr->sin_addr.s_addr = server->get_address();
|
||||
in_addr->sin_family = AF_INET;
|
||||
*addrlen = sizeof(sockaddr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getpeername(s, addr, addrlen);
|
||||
}
|
||||
|
||||
int getsockname_stub(const SOCKET s, sockaddr* addr, socklen_t* addrlen)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
auto in_addr = reinterpret_cast<sockaddr_in*>(addr);
|
||||
in_addr->sin_addr.s_addr = server->get_address();
|
||||
in_addr->sin_family = AF_INET;
|
||||
*addrlen = sizeof(sockaddr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getsockname(s, addr, addrlen);
|
||||
}
|
||||
|
||||
hostent* gethostbyname_stub(const char* name)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[ network ]: [gethostbyname]: \"%s\"\n", name);
|
||||
#endif
|
||||
|
||||
base_server* server = tcp_servers.find(name);
|
||||
if (!server)
|
||||
{
|
||||
server = udp_servers.find(name);
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
return gethostbyname(name);
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
static thread_local in_addr address{};
|
||||
address.s_addr = server->get_address();
|
||||
|
||||
static thread_local in_addr* addr_list[2]{};
|
||||
addr_list[0] = &address;
|
||||
addr_list[1] = nullptr;
|
||||
|
||||
static thread_local hostent host{};
|
||||
host.h_name = const_cast<char*>(name);
|
||||
host.h_aliases = nullptr;
|
||||
host.h_addrtype = AF_INET;
|
||||
host.h_length = sizeof(in_addr);
|
||||
host.h_addr_list = reinterpret_cast<char**>(addr_list);
|
||||
|
||||
return &host;
|
||||
}
|
||||
|
||||
int connect_stub(const SOCKET s, const struct sockaddr* addr, const int len)
|
||||
{
|
||||
if (len == sizeof(sockaddr_in))
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(addr);
|
||||
if (socket_link(s, in_addr->sin_addr.s_addr)) return 0;
|
||||
}
|
||||
|
||||
return connect(s, addr, len);
|
||||
}
|
||||
|
||||
int closesocket_stub(const SOCKET s)
|
||||
{
|
||||
remove_blocking_socket(s);
|
||||
socket_unlink(s);
|
||||
|
||||
return closesocket(s);
|
||||
}
|
||||
|
||||
int send_stub(const SOCKET s, const char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
return send(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int recv_stub(const SOCKET s, char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (server->pending_data())
|
||||
{
|
||||
return static_cast<int>(server->handle_output(buf, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return recv(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int sendto_stub(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to,
|
||||
const int tolen)
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(to);
|
||||
auto* server = udp_servers.find(in_addr->sin_addr.s_addr);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len, {s, to, tolen});
|
||||
return len;
|
||||
}
|
||||
|
||||
return sendto(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int recvfrom_stub(const SOCKET s, char* buf, const int len, const int flags, struct sockaddr* from,
|
||||
int* fromlen)
|
||||
{
|
||||
// Not supported yet
|
||||
if (is_socket_blocking(s, UDP_BLOCKING))
|
||||
{
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
size_t result = 0;
|
||||
udp_servers.for_each([&](udp_server& server)
|
||||
{
|
||||
if (server.pending_data(s))
|
||||
{
|
||||
result = server.handle_output(
|
||||
s, buf, static_cast<size_t>(len), from, fromlen);
|
||||
}
|
||||
});
|
||||
|
||||
if (result)
|
||||
{
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
int select_stub(const int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
|
||||
struct timeval* timeout)
|
||||
{
|
||||
if (exit_server)
|
||||
{
|
||||
return select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
}
|
||||
|
||||
auto result = 0;
|
||||
std::vector<SOCKET> read_sockets;
|
||||
std::vector<SOCKET> write_sockets;
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& sockets)
|
||||
{
|
||||
for (auto& s : sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, readfds))
|
||||
{
|
||||
if (s.second->pending_data())
|
||||
{
|
||||
read_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, readfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writefds)
|
||||
{
|
||||
if (FD_ISSET(s.first, writefds))
|
||||
{
|
||||
write_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, writefds);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, exceptfds))
|
||||
{
|
||||
FD_CLR(s.first, exceptfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ((!readfds || readfds->fd_count == 0) && (!writefds || writefds->fd_count == 0))
|
||||
{
|
||||
timeout->tv_sec = 0;
|
||||
timeout->tv_usec = 0;
|
||||
}
|
||||
|
||||
result = select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
if (result < 0) result = 0;
|
||||
|
||||
for (const auto& socket : read_sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
FD_SET(socket, readfds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& socket : write_sockets)
|
||||
{
|
||||
if (writefds)
|
||||
{
|
||||
FD_SET(socket, writefds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int ioctlsocket_stub(const SOCKET s, const long cmd, u_long* argp)
|
||||
{
|
||||
if (static_cast<unsigned long>(cmd) == (FIONBIO))
|
||||
{
|
||||
add_blocking_socket(s, *argp == 0);
|
||||
}
|
||||
|
||||
return ioctlsocket(s, cmd, argp);
|
||||
}
|
||||
|
||||
BOOL internet_get_connected_state_stub(LPDWORD, DWORD)
|
||||
{
|
||||
// Allow offline play
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void bd_logger_stub(char* a1, void* a2, void* a3, void* a4, const char* function, ...)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void a(unsigned int n)
|
||||
{
|
||||
printf("bdAuth: Auth task failed with HTTP code [%u]\n", n);
|
||||
}
|
||||
|
||||
void b(unsigned int n)
|
||||
{
|
||||
printf("bdAuth: Decoded client ticket of unexpected size [%u]\n", n);
|
||||
}
|
||||
|
||||
void c(unsigned int n)
|
||||
{
|
||||
printf("bdAuth: Decoded server ticket of unexpected size [%u]\n", n);
|
||||
}
|
||||
|
||||
void d()
|
||||
{
|
||||
printf("bdAuth: Auth ticket magic number mismatch\n");
|
||||
}
|
||||
|
||||
void e()
|
||||
{
|
||||
printf("bdAuth: Cross Authentication completed\n");
|
||||
}
|
||||
|
||||
void f()
|
||||
{
|
||||
printf("bdAuth: Auth task reply contains invalid data / format\n");
|
||||
}
|
||||
|
||||
void g(unsigned int n)
|
||||
{
|
||||
printf("bdAuth: Auth task returned with error code [%u]\n", n);
|
||||
}
|
||||
|
||||
void h(unsigned int n)
|
||||
{
|
||||
printf("bdAuth: Invalid or No Task ID [%u] in Auth reply\n", n);
|
||||
}
|
||||
|
||||
void i()
|
||||
{
|
||||
printf("bdAuth: Received reply from DemonWare Auth server\n");
|
||||
}
|
||||
|
||||
void l()
|
||||
{
|
||||
printf("bdAuth: Unknown error\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
udp_servers.create<stun_server>("phoenix.stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("phoenix.stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("phoenix.stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("phoenix.stun.au.demonware.net");
|
||||
|
||||
udp_servers.create<stun_server>("stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.au.demonware.net");
|
||||
|
||||
tcp_servers.create<auth3_server>("mwr-pc-steam-auth3.prod.demonware.net");
|
||||
tcp_servers.create<lobby_server>("mwr-pc-steam-lobby.prod.demonware.net");
|
||||
tcp_servers.create<umbrella_server>("prod.umbrella.demonware.net");
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
server_thread = utils::thread::create_named_thread("Demonware", server_main);
|
||||
}
|
||||
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "WS2_32.dll")
|
||||
{
|
||||
if (function == "#3") return io::closesocket_stub;
|
||||
if (function == "#4") return io::connect_stub;
|
||||
if (function == "#5") return io::getpeername_stub;
|
||||
if (function == "#6") return io::getsockname_stub;
|
||||
if (function == "#10") return io::ioctlsocket_stub;
|
||||
if (function == "#16") return io::recv_stub;
|
||||
if (function == "#17") return io::recvfrom_stub;
|
||||
if (function == "#18") return io::select_stub;
|
||||
if (function == "#19") return io::send_stub;
|
||||
if (function == "#20") return io::sendto_stub;
|
||||
if (function == "#52") return io::gethostbyname_stub;
|
||||
if (function == "getaddrinfo") return io::getaddrinfo_stub;
|
||||
if (function == "freeaddrinfo") return io::freeaddrinfo_stub;
|
||||
}
|
||||
|
||||
if (function == "InternetGetConnectedState")
|
||||
{
|
||||
return io::internet_get_connected_state_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
/*
|
||||
mwr has upgraded some networking methods and the gethostbyname import from winsock library is no longer used
|
||||
gethostbyname has been replaced with getaddrinfo
|
||||
btw, still you can't get online..
|
||||
*/
|
||||
utils::hook::jump(SELECT_VALUE(0x140610320, 0x1407400B0), bd_logger_stub); // H1MP64(1.4)
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
utils::hook::set<uint8_t>(0x1405FCA00, 0xC3); // bdAuthSteam H1(1.4)
|
||||
utils::hook::set<uint8_t>(0x140333A00, 0xC3); // dwNet H1(1.4)
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::set<uint8_t>(0x140715039, 0x0); // CURLOPT_SSL_VERIFYPEER H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140715025, 0xAF); // CURLOPT_SSL_VERIFYHOST H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x14095433C, 0x0); // HTTPS -> HTTP [MWR OK][S1X: 0x14088D0E8]
|
||||
|
||||
//HTTPS -> HTTP
|
||||
utils::hook::inject(0x14006DDA9, "http://prod.umbrella.demonware.net/v1.0/"); // ---> [H1MP1.4 - S1X: 0x14003852E]
|
||||
utils::hook::inject(0x14006E11C, "http://prod.umbrella.demonware.net/v1.0/"); // ---> [H1MP1.4 - S1X: 0x14003884F]
|
||||
utils::hook::inject(0x14006E2FB, "http://prod.umbrella.demonware.net/v1.0/"); // ---> [H1MP1.4 - S1X: 0x140038A07]
|
||||
utils::hook::inject(0x14006E9A9, "http://prod.uno.demonware.net/v1.0/");
|
||||
utils::hook::inject(0x14006ED49, "http://prod.uno.demonware.net/v1.0/");
|
||||
utils::hook::inject(0x140728170, "http://%s:%d/auth/");
|
||||
|
||||
utils::hook::set<uint8_t>(0x14047F290, 0xC3); // SV_SendMatchData H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140598990, 0xC3); // Live_CheckForFullDisconnect H1MP64(1.4)
|
||||
|
||||
#ifdef DEBUG
|
||||
// yes
|
||||
utils::hook::call(0x140727BEB, l);
|
||||
utils::hook::call(0x140727AFC, i);
|
||||
utils::hook::call(0x140727E49, h);
|
||||
utils::hook::call(0x140727E30, g);
|
||||
utils::hook::call(0x140727E37, f);
|
||||
utils::hook::call(0x140727DF2, e);
|
||||
utils::hook::call(0x140727DF9, d);
|
||||
utils::hook::call(0x140727CFC, c);
|
||||
utils::hook::call(0x140727C82, b);
|
||||
utils::hook::call(0x140727E6A, a);
|
||||
#endif
|
||||
// Checks X-Signature header or something
|
||||
utils::hook::set(0x140728380, 0xC301B0);
|
||||
// Checks extended_data and extra_data in json object
|
||||
utils::hook::set(0x140728E90, 0xC301B0);
|
||||
// Update check
|
||||
utils::hook::set(0x1403A5390, 0xC301B0);
|
||||
|
||||
// Remove some while loop in demonware that freezes the rendering for a few secs at launch
|
||||
utils::hook::nop(0x14057DBC5, 5);
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
exit_server = true;
|
||||
if (server_thread.joinable())
|
||||
{
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(demonware::component)
|
6
src/client/component/demonware.hpp
Normal file
6
src/client/component/demonware.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
}
|
148
src/client/component/discord.cpp
Normal file
148
src/client/component/discord.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "network.hpp"
|
||||
#include "party.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include <discord_rpc.h>
|
||||
|
||||
namespace discord
|
||||
{
|
||||
namespace
|
||||
{
|
||||
DiscordRichPresence discord_presence;
|
||||
|
||||
void update_discord()
|
||||
{
|
||||
Discord_RunCallbacks();
|
||||
|
||||
if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer";
|
||||
discord_presence.state = "Main Menu";
|
||||
|
||||
auto firingRangeDvar = game::Dvar_FindVar("virtualLobbyInFiringRange");
|
||||
if (firingRangeDvar && firingRangeDvar->current.enabled == 1)
|
||||
{
|
||||
discord_presence.state = "Firing Range";
|
||||
}
|
||||
|
||||
discord_presence.partySize = 0;
|
||||
discord_presence.partyMax = 0;
|
||||
discord_presence.startTimestamp = 0;
|
||||
discord_presence.largeImageKey = game::environment::is_sp() ? "menu_singleplayer" : "menu_multiplayer";
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto map = game::Dvar_FindVar("mapname")->current.string;
|
||||
const auto mapname = game::UI_SafeTranslateString(utils::string::va("PRESENCE_%s%s", (game::environment::is_sp() ? "SP_" : ""), map));
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
const auto gametype = game::UI_GetGameTypeDisplayName(game::Dvar_FindVar("g_gametype")->current.string);
|
||||
|
||||
discord_presence.details = utils::string::va("%s on %s", gametype, mapname);
|
||||
|
||||
char clean_hostname[0x100] = {0};
|
||||
utils::string::strip(game::Dvar_FindVar("sv_hostname")->current.string,
|
||||
clean_hostname, sizeof(clean_hostname));
|
||||
auto max_clients = party::server_client_count();
|
||||
|
||||
// When true, we are in Private Match
|
||||
if (game::SV_Loaded())
|
||||
{
|
||||
strcpy_s(clean_hostname, "Private Match");
|
||||
max_clients = game::Dvar_FindVar("sv_maxclients")->current.integer;
|
||||
}
|
||||
|
||||
discord_presence.partySize = *reinterpret_cast<int*>(0x1429864C4);
|
||||
discord_presence.partyMax = max_clients;
|
||||
discord_presence.state = clean_hostname;
|
||||
discord_presence.largeImageKey = map;
|
||||
}
|
||||
else if (game::environment::is_sp())
|
||||
{
|
||||
discord_presence.state = "";
|
||||
discord_presence.largeImageKey = map;
|
||||
discord_presence.details = mapname;
|
||||
}
|
||||
|
||||
if (!discord_presence.startTimestamp)
|
||||
{
|
||||
discord_presence.startTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
}
|
||||
|
||||
Discord_UpdatePresence(&discord_presence);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DiscordEventHandlers handlers;
|
||||
ZeroMemory(&handlers, sizeof(handlers));
|
||||
handlers.ready = ready;
|
||||
handlers.errored = errored;
|
||||
handlers.disconnected = errored;
|
||||
handlers.joinGame = nullptr;
|
||||
handlers.spectateGame = nullptr;
|
||||
handlers.joinRequest = nullptr;
|
||||
|
||||
Discord_Initialize("947125042930667530", &handlers, 1, nullptr);
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
scheduler::once(update_discord, scheduler::pipeline::async);
|
||||
scheduler::loop(update_discord, scheduler::pipeline::async, 5s);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
if (!initialized_ || game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Discord_Shutdown();
|
||||
}
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
|
||||
static void ready(const DiscordUser* /*request*/)
|
||||
{
|
||||
ZeroMemory(&discord_presence, sizeof(discord_presence));
|
||||
|
||||
discord_presence.instance = 1;
|
||||
|
||||
console::info("Discord: Ready\n");
|
||||
|
||||
Discord_UpdatePresence(&discord_presence);
|
||||
}
|
||||
|
||||
static void errored(const int error_code, const char* message)
|
||||
{
|
||||
console::error("Discord: Error (%i): %s\n", error_code, message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(discord::component)
|
443
src/client/component/dvars.cpp
Normal file
443
src/client/component/dvars.cpp
Normal file
@ -0,0 +1,443 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace dvars
|
||||
{
|
||||
struct dvar_base
|
||||
{
|
||||
unsigned int flags{};
|
||||
};
|
||||
|
||||
struct dvar_bool : dvar_base
|
||||
{
|
||||
bool value{};
|
||||
};
|
||||
|
||||
struct dvar_float : dvar_base
|
||||
{
|
||||
float value{};
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct dvar_vector2 : dvar_base
|
||||
{
|
||||
float x{};
|
||||
float y{};
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct dvar_vector3 : dvar_base
|
||||
{
|
||||
|
||||
float x{};
|
||||
float y{};
|
||||
float z{};
|
||||
float min{};
|
||||
float max{};
|
||||
};
|
||||
|
||||
struct dvar_int : dvar_base
|
||||
{
|
||||
int value{};
|
||||
int min{};
|
||||
int max{};
|
||||
};
|
||||
|
||||
struct dvar_string : dvar_base
|
||||
{
|
||||
std::string value{};
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
T* find_dvar(std::unordered_map<std::string, T>& map, const std::string& name)
|
||||
{
|
||||
auto i = map.find(name);
|
||||
if (i != map.end())
|
||||
{
|
||||
return &i->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* find_dvar(std::unordered_map<std::string, T>& map, const int hash)
|
||||
{
|
||||
for (auto i = map.begin(); i != map.end(); ++i)
|
||||
{
|
||||
if (game::generateHashValue(i->first.data()) == hash)
|
||||
{
|
||||
return &i->second;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool find_dvar(std::unordered_set<std::string>& set, const std::string& name)
|
||||
{
|
||||
return set.find(name) != set.end();
|
||||
}
|
||||
|
||||
|
||||
bool find_dvar(std::unordered_set<std::string>& set, const int hash)
|
||||
{
|
||||
for (auto i = set.begin(); i != set.end(); ++i)
|
||||
{
|
||||
if (game::generateHashValue(i->data()) == hash)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace disable
|
||||
{
|
||||
static std::unordered_set<std::string> set_bool_disables;
|
||||
static std::unordered_set<std::string> set_float_disables;
|
||||
static std::unordered_set<std::string> set_int_disables;
|
||||
static std::unordered_set<std::string> set_string_disables;
|
||||
|
||||
void set_bool(const std::string& name)
|
||||
{
|
||||
set_bool_disables.emplace(name);
|
||||
}
|
||||
|
||||
void set_float(const std::string& name)
|
||||
{
|
||||
set_float_disables.emplace(name);
|
||||
}
|
||||
|
||||
void set_int(const std::string& name)
|
||||
{
|
||||
set_int_disables.emplace(name);
|
||||
}
|
||||
|
||||
void set_string(const std::string& name)
|
||||
{
|
||||
set_string_disables.emplace(name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace override
|
||||
{
|
||||
static std::unordered_map<std::string, dvar_bool> register_bool_overrides;
|
||||
static std::unordered_map<std::string, dvar_float> register_float_overrides;
|
||||
static std::unordered_map<std::string, dvar_int> register_int_overrides;
|
||||
static std::unordered_map<std::string, dvar_string> register_string_overrides;
|
||||
static std::unordered_map<std::string, dvar_vector2> register_vector2_overrides;
|
||||
static std::unordered_map<std::string, dvar_vector3> register_vector3_overrides;
|
||||
|
||||
static std::unordered_map<std::string, bool> set_bool_overrides;
|
||||
static std::unordered_map<std::string, float> set_float_overrides;
|
||||
static std::unordered_map<std::string, int> set_int_overrides;
|
||||
static std::unordered_map<std::string, std::string> set_string_overrides;
|
||||
static std::unordered_map<std::string, std::string> set_from_string_overrides;
|
||||
|
||||
void register_bool(const std::string& name, const bool value, const unsigned int flags)
|
||||
{
|
||||
dvar_bool values;
|
||||
values.value = value;
|
||||
values.flags = flags;
|
||||
register_bool_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_float(const std::string& name, const float value, const float min, const float max,
|
||||
const unsigned int flags)
|
||||
{
|
||||
dvar_float values;
|
||||
values.value = value;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_float_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_int(const std::string& name, const int value, const int min, const int max,
|
||||
const unsigned int flags)
|
||||
{
|
||||
dvar_int values;
|
||||
values.value = value;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_int_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_string(const std::string& name, const std::string& value,
|
||||
const unsigned int flags)
|
||||
{
|
||||
dvar_string values;
|
||||
values.value = value;
|
||||
values.flags = flags;
|
||||
register_string_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_vec2(const std::string& name, float x, float y, float min, float max,
|
||||
const unsigned int flags)
|
||||
{
|
||||
dvar_vector2 values;
|
||||
values.x = x;
|
||||
values.y = y;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_vector2_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void register_vec3(const std::string& name, float x, float y, float z, float min,
|
||||
float max, const unsigned int flags)
|
||||
{
|
||||
dvar_vector3 values;
|
||||
values.x = x;
|
||||
values.y = y;
|
||||
values.z = z;
|
||||
values.min = min;
|
||||
values.max = max;
|
||||
values.flags = flags;
|
||||
register_vector3_overrides[name] = std::move(values);
|
||||
}
|
||||
|
||||
void set_bool(const std::string& name, const bool value)
|
||||
{
|
||||
set_bool_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_float(const std::string& name, const float value)
|
||||
{
|
||||
set_float_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_int(const std::string& name, const int value)
|
||||
{
|
||||
set_int_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_string(const std::string& name, const std::string& value)
|
||||
{
|
||||
set_string_overrides[name] = value;
|
||||
}
|
||||
|
||||
void set_from_string(const std::string& name, const std::string& value)
|
||||
{
|
||||
set_from_string_overrides[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour dvar_register_bool_hook;
|
||||
utils::hook::detour dvar_register_float_hook;
|
||||
utils::hook::detour dvar_register_int_hook;
|
||||
utils::hook::detour dvar_register_string_hook;
|
||||
utils::hook::detour dvar_register_vector2_hook;
|
||||
utils::hook::detour dvar_register_vector3_hook;
|
||||
|
||||
utils::hook::detour dvar_set_bool_hook;
|
||||
utils::hook::detour dvar_set_float_hook;
|
||||
utils::hook::detour dvar_set_int_hook;
|
||||
utils::hook::detour dvar_set_string_hook;
|
||||
utils::hook::detour dvar_set_from_string_hook;
|
||||
|
||||
game::dvar_t* dvar_register_bool(const int hash, const char* name, bool value, unsigned int flags)
|
||||
{
|
||||
auto* var = find_dvar(override::register_bool_overrides, hash);
|
||||
if (var)
|
||||
{
|
||||
value = var->value;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_bool_hook.invoke<game::dvar_t*>(hash, name, value, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_float(const int hash, const char* name, float value, float min, float max, unsigned int flags)
|
||||
{
|
||||
auto* var = find_dvar(override::register_float_overrides, hash);
|
||||
if (var)
|
||||
{
|
||||
value = var->value;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_float_hook.invoke<game::dvar_t*>(hash, name, value, min, max, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_int(const int hash, const char* name, int value, int min, int max, unsigned int flags)
|
||||
{
|
||||
auto* var = find_dvar(override::register_int_overrides, hash);
|
||||
if (var)
|
||||
{
|
||||
value = var->value;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_int_hook.invoke<game::dvar_t*>(hash, name, value, min, max, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_string(const int hash, const char* name, const char* value, unsigned int flags)
|
||||
{
|
||||
auto* var = find_dvar(override::register_string_overrides, hash);
|
||||
if (var)
|
||||
{
|
||||
value = var->value.data();
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_string_hook.invoke<game::dvar_t*>(hash, name, value, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_vector2(const int hash, const char* name, float x, float y, float min, float max,
|
||||
unsigned int flags)
|
||||
{
|
||||
auto* var = find_dvar(override::register_vector2_overrides, hash);
|
||||
if (var)
|
||||
{
|
||||
x = var->x;
|
||||
y = var->y;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_vector2_hook.invoke<game::dvar_t*>(hash, name, x, y, min, max, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_register_vector3(const int hash, const char* name, float x, float y, float z, float min,
|
||||
float max, unsigned int flags)
|
||||
{
|
||||
auto* var = find_dvar(override::register_vector3_overrides, hash);
|
||||
if (var)
|
||||
{
|
||||
x = var->x;
|
||||
y = var->y;
|
||||
z = var->z;
|
||||
min = var->min;
|
||||
max = var->max;
|
||||
flags = var->flags;
|
||||
}
|
||||
|
||||
return dvar_register_vector3_hook.invoke<game::dvar_t*>(hash, name, x, y, z, min, max, flags);
|
||||
}
|
||||
|
||||
void dvar_set_bool(game::dvar_t* dvar, bool boolean)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_bool_disables, dvar->hash);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_bool_overrides, dvar->hash);
|
||||
if (var)
|
||||
{
|
||||
boolean = *var;
|
||||
}
|
||||
|
||||
return dvar_set_bool_hook.invoke<void>(dvar, boolean);
|
||||
}
|
||||
|
||||
void dvar_set_float(game::dvar_t* dvar, float fl)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_float_disables, dvar->hash);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_float_overrides, dvar->hash);
|
||||
if (var)
|
||||
{
|
||||
fl = *var;
|
||||
}
|
||||
|
||||
return dvar_set_float_hook.invoke<void>(dvar, fl);
|
||||
}
|
||||
|
||||
void dvar_set_int(game::dvar_t* dvar, int integer)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_int_disables, dvar->hash);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_int_overrides, dvar->hash);
|
||||
if (var)
|
||||
{
|
||||
integer = *var;
|
||||
}
|
||||
|
||||
return dvar_set_int_hook.invoke<void>(dvar, integer);
|
||||
}
|
||||
|
||||
void dvar_set_string(game::dvar_t* dvar, const char* string)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_string_disables, dvar->hash);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_string_overrides, dvar->hash);
|
||||
if (var)
|
||||
{
|
||||
string = var->data();
|
||||
}
|
||||
|
||||
return dvar_set_string_hook.invoke<void>(dvar, string);
|
||||
}
|
||||
|
||||
void dvar_set_from_string(game::dvar_t* dvar, const char* string, game::DvarSetSource source)
|
||||
{
|
||||
const auto disabled = find_dvar(disable::set_string_disables, dvar->hash);
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* var = find_dvar(override::set_from_string_overrides, dvar->hash);
|
||||
if (var)
|
||||
{
|
||||
string = var->data();
|
||||
}
|
||||
|
||||
return dvar_set_from_string_hook.invoke<void>(dvar, string, source);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
dvar_register_bool_hook.create(SELECT_VALUE(0x1403C47E0, 0x1404FA540), &dvar_register_bool);
|
||||
dvar_register_float_hook.create(SELECT_VALUE(0x1403C4BB0, 0x1404FA910), &dvar_register_float);
|
||||
dvar_register_int_hook.create(SELECT_VALUE(0x1403C4CC0, 0x1404FAA20), &dvar_register_int);
|
||||
dvar_register_string_hook.create(SELECT_VALUE(0x1403C4DA0, 0x1404FAB00), &dvar_register_string);
|
||||
dvar_register_vector2_hook.create(SELECT_VALUE(0x1403C4E80, 0x1404FABE0), &dvar_register_vector2);
|
||||
dvar_register_vector3_hook.create(SELECT_VALUE(0x1403C4FC0, 0x1404FACE0), &dvar_register_vector3);
|
||||
|
||||
dvar_set_bool_hook.create(SELECT_VALUE(0x1403C7020, 0x1404FCDF0), &dvar_set_bool);
|
||||
dvar_set_float_hook.create(SELECT_VALUE(0x1403C7420, 0x1404FD360), &dvar_set_float);
|
||||
dvar_set_int_hook.create(SELECT_VALUE(0x1403C76C0, 0x1404FD5E0), &dvar_set_int);
|
||||
dvar_set_string_hook.create(SELECT_VALUE(0x1403C7900, 0x1404FD8D0), &dvar_set_string);
|
||||
dvar_set_from_string_hook.create(SELECT_VALUE(0x1403C7620, 0x1404FD520), &dvar_set_from_string);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(dvars::component)
|
28
src/client/component/dvars.hpp
Normal file
28
src/client/component/dvars.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace dvars
|
||||
{
|
||||
namespace disable
|
||||
{
|
||||
void set_bool(const std::string& name);
|
||||
void set_float(const std::string& name);
|
||||
void set_int(const std::string& name);
|
||||
void set_string(const std::string& name);
|
||||
}
|
||||
|
||||
namespace override
|
||||
{
|
||||
void register_bool(const std::string& name, bool value, const unsigned int flags);
|
||||
void register_float(const std::string& name, float value, float min, float max, const unsigned int flags);
|
||||
void register_int(const std::string& name, int value, int min, int max, const unsigned int flags);
|
||||
void register_string(const std::string& name, const std::string& value, const unsigned int flags);
|
||||
void register_vec2(const std::string& name, float x, float y, float min, float max, const unsigned int flags);
|
||||
void register_vec3(const std::string& name, float x, float y, float z, float min, float max, const unsigned int flags);
|
||||
|
||||
void set_bool(const std::string& name, bool boolean);
|
||||
void set_float(const std::string& name, float fl);
|
||||
void set_int(const std::string& name, int integer);
|
||||
void set_string(const std::string& name, const std::string& string);
|
||||
void set_from_string(const std::string& name, const std::string& value);
|
||||
}
|
||||
}
|
261
src/client/component/exception.cpp
Normal file
261
src/client/component/exception.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "system_check.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/compression.hpp>
|
||||
|
||||
#include <exception/minidump.hpp>
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
namespace exception
|
||||
{
|
||||
namespace
|
||||
{
|
||||
thread_local struct
|
||||
{
|
||||
DWORD code = 0;
|
||||
PVOID address = nullptr;
|
||||
} exception_data;
|
||||
|
||||
struct
|
||||
{
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> last_recovery{};
|
||||
std::atomic<int> recovery_counts = {0};
|
||||
} recovery_data;
|
||||
|
||||
bool is_game_thread()
|
||||
{
|
||||
static std::vector<int> allowed_threads =
|
||||
{
|
||||
game::THREAD_CONTEXT_MAIN,
|
||||
};
|
||||
|
||||
const auto self_id = GetCurrentThreadId();
|
||||
for (const auto& index : allowed_threads)
|
||||
{
|
||||
if (game::threadIds[index] == self_id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_exception_interval_too_short()
|
||||
{
|
||||
const auto delta = std::chrono::high_resolution_clock::now() - recovery_data.last_recovery;
|
||||
return delta < 1min;
|
||||
}
|
||||
|
||||
bool too_many_exceptions_occured()
|
||||
{
|
||||
return recovery_data.recovery_counts >= 3;
|
||||
}
|
||||
|
||||
volatile bool& is_initialized()
|
||||
{
|
||||
static volatile bool initialized = false;
|
||||
return initialized;
|
||||
}
|
||||
|
||||
bool is_recoverable()
|
||||
{
|
||||
return is_initialized()
|
||||
&& is_game_thread()
|
||||
&& !is_exception_interval_too_short()
|
||||
&& !too_many_exceptions_occured();
|
||||
}
|
||||
|
||||
void show_mouse_cursor()
|
||||
{
|
||||
while (ShowCursor(TRUE) < 0);
|
||||
}
|
||||
|
||||
void display_error_dialog()
|
||||
{
|
||||
std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n"
|
||||
"A minidump has been written.\n\n",
|
||||
exception_data.code, exception_data.address);
|
||||
|
||||
if (!system_check::is_valid())
|
||||
{
|
||||
error_str += "Make sure to get supported game files to avoid such crashes!";
|
||||
}
|
||||
else
|
||||
{
|
||||
error_str += "Make sure to update your graphics card drivers and install operating system updates!";
|
||||
}
|
||||
|
||||
utils::thread::suspend_other_threads();
|
||||
show_mouse_cursor();
|
||||
|
||||
MessageBoxA(nullptr, error_str.data(), "H1-Mod ERROR", MB_ICONERROR);
|
||||
TerminateProcess(GetCurrentProcess(), exception_data.code);
|
||||
}
|
||||
|
||||
void reset_state()
|
||||
{
|
||||
if (dvars::cg_legacyCrashHandling && dvars::cg_legacyCrashHandling->current.enabled)
|
||||
{
|
||||
display_error_dialog();
|
||||
}
|
||||
|
||||
// TODO: Add a limit for dedi restarts
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
utils::nt::relaunch_self();
|
||||
utils::nt::terminate(exception_data.code);
|
||||
}
|
||||
|
||||
if (is_recoverable())
|
||||
{
|
||||
recovery_data.last_recovery = std::chrono::high_resolution_clock::now();
|
||||
++recovery_data.recovery_counts;
|
||||
|
||||
game::Com_Error(game::ERR_DROP, "Fatal error (0x%08X) at 0x%p.\nA minidump has been written.\n\n"
|
||||
"H1-Mod has tried to recover your game, but it might not run stable anymore.\n\n"
|
||||
"Make sure to update your graphics card drivers and install operating system updates!\n"
|
||||
"Closing or restarting Steam might also help.",
|
||||
exception_data.code, exception_data.address);
|
||||
}
|
||||
else
|
||||
{
|
||||
display_error_dialog();
|
||||
}
|
||||
}
|
||||
|
||||
size_t get_reset_state_stub()
|
||||
{
|
||||
static auto* stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.sub(rsp, 0x10);
|
||||
a.or_(rsp, 0x8);
|
||||
a.jmp(reset_state);
|
||||
});
|
||||
|
||||
return reinterpret_cast<size_t>(stub);
|
||||
}
|
||||
|
||||
std::string get_timestamp()
|
||||
{
|
||||
tm ltime{};
|
||||
char timestamp[MAX_PATH] = {0};
|
||||
const auto time = _time64(nullptr);
|
||||
|
||||
_localtime64_s(<ime, &time);
|
||||
strftime(timestamp, sizeof(timestamp) - 1, "%Y-%m-%d-%H-%M-%S", <ime);
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
std::string info{};
|
||||
const auto line = [&info](const std::string& text)
|
||||
{
|
||||
info.append(text);
|
||||
info.append("\r\n");
|
||||
};
|
||||
|
||||
line("H1-Mod Crash Dump");
|
||||
line("");
|
||||
line("Version: "s + VERSION);
|
||||
line("Environment: "s + game::environment::get_string());
|
||||
line("Timestamp: "s + get_timestamp());
|
||||
line("Clean game: "s + (system_check::is_valid() ? "Yes" : "No"));
|
||||
line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode));
|
||||
line(utils::string::va("Address: 0x%llX", exceptioninfo->ExceptionRecord->ExceptionAddress));
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
OSVERSIONINFOEXA version_info;
|
||||
ZeroMemory(&version_info, sizeof(version_info));
|
||||
version_info.dwOSVersionInfoSize = sizeof(version_info);
|
||||
GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&version_info));
|
||||
#pragma warning(pop)
|
||||
|
||||
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
const std::string crash_name = utils::string::va("minidumps/h1-mod-crash-%d-%s.zip",
|
||||
game::environment::get_real_mode(),
|
||||
get_timestamp().data());
|
||||
|
||||
utils::compression::zip::archive zip_file{};
|
||||
zip_file.add("crash.dmp", create_minidump(exceptioninfo));
|
||||
zip_file.add("info.txt", generate_crash_info(exceptioninfo));
|
||||
zip_file.write(crash_name, "H1-Mod Crash Dump");
|
||||
}
|
||||
|
||||
bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
const auto code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP;
|
||||
}
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
if (is_harmless_error(exceptioninfo))
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
write_minidump(exceptioninfo);
|
||||
|
||||
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
|
||||
exceptioninfo->ContextRecord->Rip = get_reset_state_stub();
|
||||
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER)
|
||||
{
|
||||
// Don't register anything here...
|
||||
return &exception_filter;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
SetUnhandledExceptionFilter(exception_filter);
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
SetUnhandledExceptionFilter(exception_filter);
|
||||
utils::hook::jump(SetUnhandledExceptionFilter, set_unhandled_exception_filter_stub, true);
|
||||
|
||||
scheduler::on_game_initialized([]()
|
||||
{
|
||||
is_initialized() = true;
|
||||
});
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
dvars::cg_legacyCrashHandling = dvars::register_bool("cg_legacyCrashHandling",
|
||||
false, game::DVAR_FLAG_SAVED, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(exception::component)
|
49
src/client/component/fastfiles.cpp
Normal file
49
src/client/component/fastfiles.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace fastfiles
|
||||
{
|
||||
static utils::concurrency::container<std::string> current_fastfile;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour db_try_load_x_file_internal_hook;
|
||||
|
||||
void db_try_load_x_file_internal(const char* zone_name, const int flags)
|
||||
{
|
||||
printf("Loading fastfile %s\n", zone_name);
|
||||
current_fastfile.access([&](std::string& fastfile)
|
||||
{
|
||||
fastfile = zone_name;
|
||||
});
|
||||
db_try_load_x_file_internal_hook.invoke<void>(zone_name, flags);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_current_fastfile()
|
||||
{
|
||||
return current_fastfile.access<std::string>([&](std::string& fastfile)
|
||||
{
|
||||
return fastfile;
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
db_try_load_x_file_internal_hook.create(
|
||||
SELECT_VALUE(0x1401CDDD0, 0x1402BFFE0), &db_try_load_x_file_internal);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(fastfiles::component)
|
8
src/client/component/fastfiles.hpp
Normal file
8
src/client/component/fastfiles.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace fastfiles
|
||||
{
|
||||
std::string get_current_fastfile();
|
||||
}
|
94
src/client/component/filesystem.cpp
Normal file
94
src/client/component/filesystem.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "game_module.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool custom_path_registered = false;
|
||||
|
||||
std::string get_binary_directory()
|
||||
{
|
||||
const auto dir = game_module::get_host_module().get_folder();
|
||||
return utils::string::replace(dir, "/", "\\");
|
||||
}
|
||||
|
||||
void register_custom_path_stub(const char* path, const char* dir)
|
||||
{
|
||||
if (!custom_path_registered)
|
||||
{
|
||||
custom_path_registered = true;
|
||||
|
||||
const auto launcher_dir = get_binary_directory();
|
||||
game::FS_AddLocalizedGameDirectory(launcher_dir.data(), "data");
|
||||
}
|
||||
|
||||
game::FS_AddLocalizedGameDirectory(path, dir);
|
||||
}
|
||||
|
||||
void fs_startup_stub(const char* gamename)
|
||||
{
|
||||
custom_path_registered = false;
|
||||
game::FS_Startup(gamename);
|
||||
}
|
||||
}
|
||||
|
||||
file::file(std::string name)
|
||||
: name_(std::move(name))
|
||||
{
|
||||
char* buffer{};
|
||||
const auto size = game::FS_ReadFile(this->name_.data(), &buffer);
|
||||
|
||||
if (size >= 0 && buffer)
|
||||
{
|
||||
this->valid_ = true;
|
||||
this->buffer_.append(buffer, size);
|
||||
game::FS_FreeFile(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
bool file::exists() const
|
||||
{
|
||||
return this->valid_;
|
||||
}
|
||||
|
||||
const std::string& file::get_buffer() const
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
|
||||
const std::string& file::get_name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Set fs_basegame
|
||||
dvars::override::register_string("fs_basegame", "h1-mod", game::DVAR_FLAG_WRITE);
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x1403B76E2, 0x1404ED3E2), fs_startup_stub);
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
utils::hook::call(0x1404ED823, fs_startup_stub);
|
||||
}
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x1403B8D31, 0x1404EE3D0), register_custom_path_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x1403B8D51, 0x1404EE3F0), register_custom_path_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x1403B8D90, 0x1404EE42F), register_custom_path_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(filesystem::component)
|
19
src/client/component/filesystem.hpp
Normal file
19
src/client/component/filesystem.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
class file
|
||||
{
|
||||
public:
|
||||
file(std::string name);
|
||||
|
||||
bool exists() const;
|
||||
const std::string& get_buffer() const;
|
||||
const std::string& get_name() const;
|
||||
|
||||
private:
|
||||
bool valid_ = false;
|
||||
std::string name_;
|
||||
std::string buffer_;
|
||||
};
|
||||
}
|
174
src/client/component/fps.cpp
Normal file
174
src/client/component/fps.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <component/scheduler.hpp>
|
||||
|
||||
namespace fps
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* cg_drawfps;
|
||||
game::dvar_t* cg_drawping;
|
||||
|
||||
float fps_color_good[4] = {0.6f, 1.0f, 0.0f, 1.0f};
|
||||
float fps_color_ok[4] = {1.0f, 0.7f, 0.3f, 1.0f};
|
||||
float fps_color_bad[4] = {1.0f, 0.3f, 0.3f, 1.0f};
|
||||
float ping_color[4] = {1.0f, 1.0f, 1.0f, 0.65f};
|
||||
|
||||
struct cg_perf_data
|
||||
{
|
||||
std::chrono::time_point<std::chrono::steady_clock> perf_start;
|
||||
std::int32_t current_ms{};
|
||||
std::int32_t previous_ms{};
|
||||
std::int32_t frame_ms{};
|
||||
std::int32_t history[32]{};
|
||||
std::int32_t count{};
|
||||
std::int32_t index{};
|
||||
std::int32_t instant{};
|
||||
std::int32_t total{};
|
||||
float average{};
|
||||
float variance{};
|
||||
std::int32_t min{};
|
||||
std::int32_t max{};
|
||||
};
|
||||
|
||||
cg_perf_data cg_perf{};
|
||||
|
||||
void perf_calc_fps(cg_perf_data* data, const std::int32_t value)
|
||||
{
|
||||
data->history[data->index % 32] = value;
|
||||
data->instant = value;
|
||||
data->min = 0x7FFFFFFF;
|
||||
data->max = 0;
|
||||
data->average = 0.0f;
|
||||
data->variance = 0.0f;
|
||||
data->total = 0;
|
||||
|
||||
for (auto i = 0; i < data->count; ++i)
|
||||
{
|
||||
const std::int32_t idx = (data->index - i) % 32;
|
||||
|
||||
if (idx < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
data->total += data->history[idx];
|
||||
|
||||
if (data->min > data->history[idx])
|
||||
{
|
||||
data->min = data->history[idx];
|
||||
}
|
||||
|
||||
if (data->max < data->history[idx])
|
||||
{
|
||||
data->max = data->history[idx];
|
||||
}
|
||||
}
|
||||
|
||||
data->average = static_cast<float>(data->total) / static_cast<float>(data->count);
|
||||
++data->index;
|
||||
}
|
||||
|
||||
void perf_update()
|
||||
{
|
||||
cg_perf.count = 32;
|
||||
|
||||
cg_perf.current_ms = static_cast<std::int32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::high_resolution_clock::now() - cg_perf.perf_start).count());
|
||||
cg_perf.frame_ms = cg_perf.current_ms - cg_perf.previous_ms;
|
||||
cg_perf.previous_ms = cg_perf.current_ms;
|
||||
|
||||
perf_calc_fps(&cg_perf, cg_perf.frame_ms);
|
||||
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x1405487A0, 0x1406575A0)); // H1(1.4)
|
||||
}
|
||||
|
||||
void cg_draw_fps()
|
||||
{
|
||||
if (cg_drawfps->current.integer > 0)
|
||||
{
|
||||
const auto fps = static_cast<std::int32_t>(static_cast<float>(1000.0f / static_cast<float>(cg_perf.
|
||||
average))
|
||||
+ 9.313225746154785e-10);
|
||||
|
||||
const auto font = game::R_RegisterFont("fonts/fira_mono_regular.ttf", 25);
|
||||
const auto fps_string = utils::string::va("%i", fps);
|
||||
|
||||
const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 15.0f) - game::R_TextWidth(
|
||||
fps_string, 0x7FFFFFFF, font);
|
||||
const auto y = font->pixelHeight + 10.f;
|
||||
|
||||
const auto fps_color = fps >= 60 ? fps_color_good : (fps >= 30 ? fps_color_ok : fps_color_bad);
|
||||
game::R_AddCmdDrawText(fps_string, 0x7FFFFFFF, font, x, y, 1.f, 1.f, 0.0f, fps_color, 6);
|
||||
}
|
||||
}
|
||||
|
||||
void cg_draw_ping()
|
||||
{
|
||||
if (cg_drawping->current.integer > 0 && game::CL_IsCgameInitialized() && !game::VirtualLobby_Loaded())
|
||||
{
|
||||
const auto ping = *reinterpret_cast<int*>(0x142D106F0);
|
||||
|
||||
const auto font = game::R_RegisterFont("fonts/consolefont", 20);
|
||||
const auto ping_string = utils::string::va("Ping: %i", ping);
|
||||
|
||||
const auto x = (game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 375.0f) - game::R_TextWidth(
|
||||
ping_string, 0x7FFFFFFF, font);
|
||||
|
||||
const auto y = font->pixelHeight + 15.f;
|
||||
game::R_AddCmdDrawText(ping_string, 0x7FFFFFFF, font, x, y, 1.f, 1.f, 0.0f, ping_color, 6);
|
||||
}
|
||||
}
|
||||
|
||||
game::dvar_t* cg_draw_fps_register_stub(const char* name, const char** _enum, const int value, unsigned int /*flags*/,
|
||||
const char* desc)
|
||||
{
|
||||
cg_drawfps = dvars::register_int("cg_drawFps", 0, 0, 2, game::DVAR_FLAG_SAVED, false);
|
||||
return cg_drawfps;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// fps setup
|
||||
cg_perf.perf_start = std::chrono::high_resolution_clock::now();
|
||||
utils::hook::call(SELECT_VALUE(0x14018D261, 0x14025B747), &perf_update);
|
||||
|
||||
// change cg_drawfps flags to saved
|
||||
utils::hook::call(SELECT_VALUE(0x140139F48, 0x140222A46), &cg_draw_fps_register_stub);
|
||||
|
||||
scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer);
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
cg_drawfps = dvars::register_int("cg_drawFps", 0, 0, 2, game::DVAR_FLAG_SAVED, false);
|
||||
}
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
// fix ping value
|
||||
utils::hook::nop(0x14025AC41, 2);
|
||||
|
||||
cg_drawping = dvars::register_int("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, true);
|
||||
|
||||
scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(fps::component)
|
793
src/client/component/game_console.cpp
Normal file
793
src/client/component/game_console.cpp
Normal file
@ -0,0 +1,793 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game_console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
#define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18)
|
||||
#define material_white game::Material_RegisterHandle("white")
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct console_globals
|
||||
{
|
||||
float x{};
|
||||
float y{};
|
||||
float left_x{};
|
||||
float font_height{};
|
||||
bool may_auto_complete{};
|
||||
char auto_complete_choice[64]{};
|
||||
int info_line_count{};
|
||||
};
|
||||
|
||||
using output_queue = std::deque<std::string>;
|
||||
|
||||
struct ingame_console
|
||||
{
|
||||
char buffer[256]{};
|
||||
int cursor{};
|
||||
int font_height{};
|
||||
int visible_line_count{};
|
||||
int visible_pixel_width{};
|
||||
float screen_min[2]{}; //left & top
|
||||
float screen_max[2]{}; //right & bottom
|
||||
console_globals globals{};
|
||||
bool output_visible{};
|
||||
int display_line_offset{};
|
||||
int line_count{};
|
||||
utils::concurrency::container<output_queue, std::recursive_mutex> output{};
|
||||
};
|
||||
|
||||
ingame_console con{};
|
||||
|
||||
std::int32_t history_index = -1;
|
||||
std::deque<std::string> history{};
|
||||
|
||||
std::string fixed_input{};
|
||||
std::vector<std::string> matches{};
|
||||
|
||||
float color_white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
float color_title[4] = {0.25f, 0.62f, 0.3f, 1.0f};
|
||||
|
||||
void clear()
|
||||
{
|
||||
strncpy_s(con.buffer, "", sizeof(con.buffer));
|
||||
con.cursor = 0;
|
||||
|
||||
fixed_input = "";
|
||||
matches.clear();
|
||||
}
|
||||
|
||||
void print_internal(const std::string& data)
|
||||
{
|
||||
con.output.access([&](output_queue& output)
|
||||
{
|
||||
if (con.visible_line_count > 0
|
||||
&& con.display_line_offset == (output.size() - con.visible_line_count))
|
||||
{
|
||||
con.display_line_offset++;
|
||||
}
|
||||
output.push_back(data);
|
||||
if (output.size() > 512)
|
||||
{
|
||||
output.pop_front();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void toggle_console()
|
||||
{
|
||||
clear();
|
||||
|
||||
con.output_visible = false;
|
||||
*game::keyCatchers ^= 1;
|
||||
}
|
||||
|
||||
void toggle_console_output()
|
||||
{
|
||||
con.output_visible = con.output_visible == 0;
|
||||
}
|
||||
|
||||
void check_resize()
|
||||
{
|
||||
con.screen_min[0] = 6.0f;
|
||||
con.screen_min[1] = 6.0f;
|
||||
con.screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 6.0f;
|
||||
con.screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1] - 6.0f;
|
||||
|
||||
if (console_font)
|
||||
{
|
||||
con.font_height = console_font->pixelHeight;
|
||||
con.visible_line_count = static_cast<int>((con.screen_max[1] - con.screen_min[1] - (con.font_height * 2)
|
||||
) -
|
||||
24.0f) / con.font_height;
|
||||
con.visible_pixel_width = static_cast<int>(((con.screen_max[0] - con.screen_min[0]) - 10.0f) - 18.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
con.font_height = 0;
|
||||
con.visible_line_count = 0;
|
||||
con.visible_pixel_width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_box(const float x, const float y, const float w, const float h, float* color)
|
||||
{
|
||||
game::vec4_t dark_color;
|
||||
|
||||
dark_color[0] = color[0] * 0.5f;
|
||||
dark_color[1] = color[1] * 0.5f;
|
||||
dark_color[2] = color[2] * 0.5f;
|
||||
dark_color[3] = color[3];
|
||||
|
||||
game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0f, 0.0f, 0.0f, 0.0f, color, material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
|
||||
game::R_AddCmdDrawStretchPic((x + w) - 2.0f, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
|
||||
material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, y, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, (y + h) - 2.0f, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
|
||||
material_white);
|
||||
}
|
||||
|
||||
void draw_input_box(const int lines, float* color)
|
||||
{
|
||||
draw_box(
|
||||
con.globals.x - 6.0f,
|
||||
con.globals.y - 6.0f,
|
||||
(con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]),
|
||||
(lines * con.globals.font_height) + 12.0f,
|
||||
color);
|
||||
}
|
||||
|
||||
void draw_input_text_and_over(const char* str, float* color)
|
||||
{
|
||||
game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x,
|
||||
con.globals.y + con.globals.font_height, 1.0f,
|
||||
1.0f, 0.0f, color, 0);
|
||||
con.globals.x = game::R_TextWidth(str, 0, console_font) + con.globals.x + 6.0f;
|
||||
}
|
||||
|
||||
void draw_hint_box(const int lines, float* color, [[maybe_unused]] float offset_x = 0.0f,
|
||||
[[maybe_unused]] float offset_y = 0.0f)
|
||||
{
|
||||
const auto _h = lines * con.globals.font_height + 12.0f;
|
||||
const auto _y = con.globals.y - 3.0f + con.globals.font_height + 12.0f;
|
||||
const auto _w = (con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]);
|
||||
|
||||
draw_box(con.globals.x - 6.0f, _y, _w, _h, color);
|
||||
}
|
||||
|
||||
void draw_hint_text(const int line, const char* text, float* color, const float offset = 0.0f)
|
||||
{
|
||||
const auto _y = con.globals.font_height + con.globals.y + (con.globals.font_height * (line + 1)) + 15.0f;
|
||||
|
||||
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0);
|
||||
}
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact)
|
||||
{
|
||||
if (exact && text == input) return true;
|
||||
if (!exact && text.find(input) != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
|
||||
{
|
||||
input = utils::string::to_lower(input);
|
||||
|
||||
for (const auto& dvar : dvars::dvar_list)
|
||||
{
|
||||
auto name = utils::string::to_lower(dvar);
|
||||
if (game::Dvar_FindVar(name.data()) && match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(dvar);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestions.size() == 0 && game::Dvar_FindVar(input.data()))
|
||||
{
|
||||
suggestions.push_back(input.data());
|
||||
}
|
||||
|
||||
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
std::string name = utils::string::to_lower(cmd->name);
|
||||
|
||||
if (match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(cmd->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cmd = cmd->next;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_input()
|
||||
{
|
||||
con.globals.font_height = static_cast<float>(console_font->pixelHeight);
|
||||
con.globals.x = con.screen_min[0] + 6.0f;
|
||||
con.globals.y = con.screen_min[1] + 6.0f;
|
||||
con.globals.left_x = con.screen_min[0] + 6.0f;
|
||||
|
||||
draw_input_box(1, dvars::con_inputBoxColor->current.vector);
|
||||
draw_input_text_and_over("H1-Mod: " VERSION ">", color_title);
|
||||
|
||||
con.globals.left_x = con.globals.x;
|
||||
con.globals.auto_complete_choice[0] = 0;
|
||||
|
||||
game::R_AddCmdDrawTextWithCursor(con.buffer, 0x7FFFFFFF, console_font, 18, con.globals.x,
|
||||
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0,
|
||||
con.cursor, '|');
|
||||
|
||||
// check if using a prefixed '/' or not
|
||||
const auto input = con.buffer[1] && (con.buffer[0] == '/' || con.buffer[0] == '\\')
|
||||
? std::string(con.buffer).substr(1)
|
||||
: std::string(con.buffer);
|
||||
|
||||
if (!input.length())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (input != fixed_input)
|
||||
{
|
||||
matches.clear();
|
||||
|
||||
if (input.find(" ") != std::string::npos)
|
||||
{
|
||||
find_matches(input.substr(0, input.find(" ")), matches, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
find_matches(input, matches, false);
|
||||
}
|
||||
|
||||
fixed_input = input;
|
||||
}
|
||||
|
||||
con.globals.may_auto_complete = false;
|
||||
if (matches.size() > 24)
|
||||
{
|
||||
draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector);
|
||||
draw_hint_text(0, utils::string::va("%i matches (too many to show here)", matches.size()),
|
||||
dvars::con_inputDvarMatchColor->current.vector);
|
||||
}
|
||||
else if (matches.size() == 1)
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar(matches[0].data());
|
||||
const auto line_count = dvar ? 2 : 1;
|
||||
|
||||
draw_hint_box(line_count, dvars::con_inputHintBoxColor->current.vector);
|
||||
draw_hint_text(0, matches[0].data(), dvar
|
||||
? dvars::con_inputDvarMatchColor->current.vector
|
||||
: dvars::con_inputCmdMatchColor->current.vector);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
|
||||
|
||||
draw_hint_text(0, game::Dvar_ValueToString(dvar, dvar->current),
|
||||
dvars::con_inputDvarValueColor->current.vector, offset);
|
||||
draw_hint_text(1, " default", dvars::con_inputDvarInactiveValueColor->current.vector);
|
||||
draw_hint_text(1, game::Dvar_ValueToString(dvar, dvar->reset),
|
||||
dvars::con_inputDvarInactiveValueColor->current.vector, offset);
|
||||
}
|
||||
|
||||
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
|
||||
con.globals.may_auto_complete = true;
|
||||
}
|
||||
else if (matches.size() > 1)
|
||||
{
|
||||
draw_hint_box(static_cast<int>(matches.size()), dvars::con_inputHintBoxColor->current.vector);
|
||||
|
||||
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
|
||||
|
||||
for (size_t i = 0; i < matches.size(); i++)
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar(matches[i].data());
|
||||
|
||||
draw_hint_text(static_cast<int>(i), matches[i].data(),
|
||||
dvar
|
||||
? dvars::con_inputDvarMatchColor->current.vector
|
||||
: dvars::con_inputCmdMatchColor->current.vector);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
draw_hint_text(static_cast<int>(i), game::Dvar_ValueToString(dvar, dvar->current),
|
||||
dvars::con_inputDvarValueColor->current.vector, offset);
|
||||
}
|
||||
}
|
||||
|
||||
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
|
||||
con.globals.may_auto_complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output)
|
||||
{
|
||||
const auto _x = (x + width) - 10.0f;
|
||||
draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector);
|
||||
|
||||
auto _height = height;
|
||||
if (output.size() > con.visible_line_count)
|
||||
{
|
||||
const auto percentage = static_cast<float>(con.visible_line_count) / output.size();
|
||||
_height *= percentage;
|
||||
|
||||
const auto remainingSpace = height - _height;
|
||||
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (output.size() - con.
|
||||
visible_line_count);
|
||||
|
||||
y = y + (remainingSpace * percentageAbove);
|
||||
}
|
||||
|
||||
draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector);
|
||||
}
|
||||
|
||||
void draw_output_text(const float x, float y, output_queue& output)
|
||||
{
|
||||
const auto offset = output.size() >= con.visible_line_count
|
||||
? 0.0f
|
||||
: (con.font_height * (con.visible_line_count - output.size()));
|
||||
|
||||
for (auto i = 0; i < con.visible_line_count; i++)
|
||||
{
|
||||
y = console_font->pixelHeight + y;
|
||||
|
||||
const auto index = i + con.display_line_offset;
|
||||
if (index >= output.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
game::R_AddCmdDrawText(output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
|
||||
0.0f, color_white, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_output_window()
|
||||
{
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
|
||||
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
|
||||
|
||||
const auto x = con.screen_min[0] + 6.0f;
|
||||
const auto y = (con.screen_min[1] + 32.0f) + 6.0f;
|
||||
const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
|
||||
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
|
||||
|
||||
game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x,
|
||||
((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0);
|
||||
|
||||
draw_output_scrollbar(x, y, width, height, output);
|
||||
draw_output_text(x, y, output);
|
||||
});
|
||||
}
|
||||
|
||||
void draw_console()
|
||||
{
|
||||
check_resize();
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (!(*game::keyCatchers & 1))
|
||||
{
|
||||
con.output_visible = false;
|
||||
}
|
||||
|
||||
if (con.output_visible)
|
||||
{
|
||||
draw_output_window();
|
||||
}
|
||||
|
||||
draw_input();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print_internal(const char* fmt, ...)
|
||||
{
|
||||
char va_buffer[0x200] = {0};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsprintf_s(va_buffer, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
const auto formatted = std::string(va_buffer);
|
||||
const auto lines = utils::string::split(formatted, '\n');
|
||||
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
print_internal(line);
|
||||
}
|
||||
}
|
||||
|
||||
void print(const int type, const std::string& data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto lines = utils::string::split(data, '\n');
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
print_internal(type == console::con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
|
||||
}
|
||||
}
|
||||
|
||||
bool console_char_event(const int local_client_num, const int key)
|
||||
{
|
||||
if (key == game::keyNum_t::K_GRAVE ||
|
||||
key == game::keyNum_t::K_TILDE ||
|
||||
key == '|' ||
|
||||
key == '\\')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key > 127)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (key == game::keyNum_t::K_TAB) // tab (auto complete)
|
||||
{
|
||||
if (con.globals.may_auto_complete)
|
||||
{
|
||||
const auto first_char = con.buffer[0];
|
||||
|
||||
clear();
|
||||
|
||||
if (first_char == '\\' || first_char == '/')
|
||||
{
|
||||
con.buffer[0] = first_char;
|
||||
con.buffer[1] = '\0';
|
||||
}
|
||||
|
||||
strncat_s(con.buffer, con.globals.auto_complete_choice, 64);
|
||||
con.cursor = static_cast<int>(std::string(con.buffer).length());
|
||||
|
||||
if (con.cursor != 254)
|
||||
{
|
||||
con.buffer[con.cursor++] = ' ';
|
||||
con.buffer[con.cursor] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 'v' - 'a' + 1) // paste
|
||||
{
|
||||
const auto clipboard = utils::string::get_clipboard_data();
|
||||
if (clipboard.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < clipboard.length(); i++)
|
||||
{
|
||||
console_char_event(local_client_num, clipboard[i]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == 'c' - 'a' + 1) // clear
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.display_line_offset = 0;
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
output.clear();
|
||||
});
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == 'h' - 'a' + 1) // backspace
|
||||
{
|
||||
if (con.cursor > 0)
|
||||
{
|
||||
memmove(con.buffer + con.cursor - 1, con.buffer + con.cursor,
|
||||
strlen(con.buffer) + 1 - con.cursor);
|
||||
con.cursor--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key < 32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (con.cursor == 256 - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memmove(con.buffer + con.cursor + 1, con.buffer + con.cursor, strlen(con.buffer) + 1 - con.cursor);
|
||||
con.buffer[con.cursor] = static_cast<char>(key);
|
||||
con.cursor++;
|
||||
|
||||
if (con.cursor == strlen(con.buffer) + 1)
|
||||
{
|
||||
con.buffer[con.cursor] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool console_key_event(const int local_client_num, const int key, const int down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_F10)
|
||||
{
|
||||
if (!game::Com_InFrontEnd())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
game::Cmd_ExecuteSingleCommand(local_client_num, 0, "lui_open menu_systemlink_join\n");
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
|
||||
{
|
||||
if (!down)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (game::playerKeys[local_client_num].keys[game::keyNum_t::K_SHIFT].down)
|
||||
{
|
||||
if (!(*game::keyCatchers & 1))
|
||||
toggle_console();
|
||||
|
||||
toggle_console_output();
|
||||
return false;
|
||||
}
|
||||
|
||||
toggle_console();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_UPARROW)
|
||||
{
|
||||
if (++history_index >= history.size())
|
||||
{
|
||||
history_index = static_cast<int>(history.size()) - 1;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
strncpy_s(con.buffer, history.at(history_index).c_str(), 0x100);
|
||||
con.cursor = static_cast<int>(strlen(con.buffer));
|
||||
}
|
||||
}
|
||||
else if (key == game::keyNum_t::K_DOWNARROW)
|
||||
{
|
||||
if (--history_index < -1)
|
||||
{
|
||||
history_index = -1;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
strncpy_s(con.buffer, history.at(history_index).c_str(), 0x100);
|
||||
con.cursor = static_cast<int>(strlen(con.buffer));
|
||||
}
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_RIGHTARROW)
|
||||
{
|
||||
if (con.cursor < strlen(con.buffer))
|
||||
{
|
||||
con.cursor++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_LEFTARROW)
|
||||
{
|
||||
if (con.cursor > 0)
|
||||
{
|
||||
con.cursor--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//scroll through output
|
||||
if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP)
|
||||
{
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
if (output.size() > con.visible_line_count && con.display_line_offset > 0)
|
||||
{
|
||||
con.display_line_offset--;
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN)
|
||||
{
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
if (output.size() > con.visible_line_count
|
||||
&& con.display_line_offset < (output.size() - con.visible_line_count))
|
||||
{
|
||||
con.display_line_offset++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_ENTER)
|
||||
{
|
||||
game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data()));
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
const auto itr = history.begin() + history_index;
|
||||
|
||||
if (*itr == con.buffer)
|
||||
{
|
||||
history.erase(history.begin() + history_index);
|
||||
}
|
||||
}
|
||||
|
||||
history.push_front(con.buffer);
|
||||
|
||||
console::info("]%s\n", con.buffer);
|
||||
|
||||
if (history.size() > 10)
|
||||
{
|
||||
history.erase(history.begin() + 10);
|
||||
}
|
||||
|
||||
history_index = -1;
|
||||
|
||||
clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//scheduler::loop(draw_console, scheduler::pipeline::renderer);
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
scheduler::loop(draw_console, scheduler::pipeline::renderer);
|
||||
|
||||
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize our structs
|
||||
con.cursor = 0;
|
||||
con.visible_line_count = 0;
|
||||
con.output_visible = false;
|
||||
con.display_line_offset = 0;
|
||||
con.line_count = 0;
|
||||
strncpy_s(con.buffer, "", 256);
|
||||
|
||||
con.globals.x = 0.0f;
|
||||
con.globals.y = 0.0f;
|
||||
con.globals.left_x = 0.0f;
|
||||
con.globals.font_height = 0.0f;
|
||||
con.globals.may_auto_complete = false;
|
||||
con.globals.info_line_count = 0;
|
||||
strncpy_s(con.globals.auto_complete_choice, "", 64);
|
||||
|
||||
// add clear command
|
||||
command::add("clear", [&]()
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.display_line_offset = 0;
|
||||
con.output.access([](output_queue& output)
|
||||
{
|
||||
output.clear();
|
||||
});
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
});
|
||||
|
||||
// add our dvars
|
||||
dvars::con_inputBoxColor = dvars::register_vec4("con_inputBoxColor", 0.2f, 0.2f, 0.2f, 0.9f, 0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED,
|
||||
"color of console input box");
|
||||
dvars::con_inputHintBoxColor = dvars::register_vec4("con_inputHintBoxColor", 0.3f, 0.3f, 0.3f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED, "color of console input hint box");
|
||||
dvars::con_outputBarColor = dvars::register_vec4("con_outputBarColor", 0.5f, 0.5f, 0.5f, 0.6f, 0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED,
|
||||
"color of console output bar");
|
||||
dvars::con_outputSliderColor = dvars::register_vec4("con_outputSliderColor", 1.0f, 0.8f, 0.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED, "color of console output slider");
|
||||
dvars::con_outputWindowColor = dvars::register_vec4("con_outputWindowColor", 0.25f, 0.25f, 0.25f, 0.85f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console output window");
|
||||
dvars::con_inputDvarMatchColor = dvars::register_vec4("con_inputDvarMatchColor", 1.0f, 1.0f, 0.8f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar");
|
||||
dvars::con_inputDvarValueColor = dvars::register_vec4("con_inputDvarValueColor", 1.0f, 1.0f, 0.8f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console matched dvar value");
|
||||
dvars::con_inputDvarInactiveValueColor = dvars::register_vec4(
|
||||
"con_inputDvarInactiveValueColor", 0.8f, 0.8f,
|
||||
0.8f, 1.0f, 0.0f, 1.0f, game::DVAR_FLAG_SAVED,
|
||||
"color of console inactive dvar value");
|
||||
dvars::con_inputCmdMatchColor = dvars::register_vec4("con_inputCmdMatchColor", 0.80f, 0.80f, 1.0f, 1.0f,
|
||||
0.0f,
|
||||
1.0f, game::DVAR_FLAG_SAVED, "color of console matched command");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game_console::component)
|
7
src/client/component/game_console.hpp
Normal file
7
src/client/component/game_console.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
bool console_char_event(int local_client_num, int key);
|
||||
bool console_key_event(int local_client_num, int key, int down);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game_module.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
@ -91,7 +90,7 @@ namespace game_module
|
||||
|
||||
utils::nt::library get_game_module()
|
||||
{
|
||||
static utils::nt::library game{HMODULE(game::base_address)};
|
||||
static utils::nt::library game{HMODULE(0x140000000)};
|
||||
return game;
|
||||
}
|
||||
|
||||
|
52
src/client/component/localized_strings.cpp
Normal file
52
src/client/component/localized_strings.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace localized_strings
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour seh_string_ed_get_string_hook;
|
||||
|
||||
using localized_map = std::unordered_map<std::string, std::string>;
|
||||
utils::concurrency::container<localized_map> localized_overrides;
|
||||
|
||||
const char* seh_string_ed_get_string(const char* reference)
|
||||
{
|
||||
return localized_overrides.access<const char*>([&](const localized_map& map)
|
||||
{
|
||||
const auto entry = map.find(reference);
|
||||
if (entry != map.end())
|
||||
{
|
||||
return utils::string::va("%s", entry->second.data());
|
||||
}
|
||||
|
||||
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void override(const std::string& key, const std::string& value)
|
||||
{
|
||||
localized_overrides.access([&](localized_map& map)
|
||||
{
|
||||
map[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Change some localized strings
|
||||
seh_string_ed_get_string_hook.create(SELECT_VALUE(0, 0x585DA0_b), &seh_string_ed_get_string); // H1-STEAM(1.15)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(localized_strings::component)
|
6
src/client/component/localized_strings.hpp
Normal file
6
src/client/component/localized_strings.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace localized_strings
|
||||
{
|
||||
void override(const std::string& key, const std::string& value);
|
||||
}
|
317
src/client/component/logfile.cpp
Normal file
317
src/client/component/logfile.cpp
Normal file
@ -0,0 +1,317 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/lua/value_conversion.hpp"
|
||||
#include "game/scripting/lua/error.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "logfile.hpp"
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_player_killed_hook;
|
||||
utils::hook::detour scr_player_damage_hook;
|
||||
|
||||
std::vector<sol::protected_function> player_killed_callbacks;
|
||||
std::vector<sol::protected_function> player_damage_callbacks;
|
||||
|
||||
utils::hook::detour vm_execute_hook;
|
||||
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
|
||||
bool hook_enabled = true;
|
||||
|
||||
sol::lua_value convert_entity(lua_State* state, const game::mp::gentity_s* ent)
|
||||
{
|
||||
if (!ent)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const scripting::entity player{game::Scr_GetEntityId(ent->s.entityNum, 0)};
|
||||
return scripting::lua::convert(state, player);
|
||||
}
|
||||
|
||||
std::string get_weapon_name(unsigned int weapon, bool isAlternate)
|
||||
{
|
||||
char output[1024] = {0};
|
||||
game::BG_GetWeaponNameComplete(weapon, isAlternate, output, 1024);
|
||||
return output;
|
||||
}
|
||||
|
||||
sol::lua_value convert_vector(lua_State* state, const float* vec)
|
||||
{
|
||||
if (!vec)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto vec_ = scripting::vector(vec);
|
||||
return scripting::lua::convert(state, vec_);
|
||||
}
|
||||
|
||||
std::string convert_mod(const int meansOfDeath)
|
||||
{
|
||||
const auto value = reinterpret_cast<game::scr_string_t**>(0x140FEC3F0)[meansOfDeath];
|
||||
const auto string = game::SL_ConvertToString(*value);
|
||||
return string;
|
||||
}
|
||||
|
||||
void scr_player_killed_stub(game::mp::gentity_s* self, const game::mp::gentity_s* inflictor,
|
||||
game::mp::gentity_s* attacker, int damage, const int meansOfDeath, const unsigned int weapon,
|
||||
const bool isAlternate, const float* vDir, const unsigned int hitLoc, int psTimeOffset, int deathAnimDuration)
|
||||
{
|
||||
{
|
||||
const std::string hitloc = reinterpret_cast<const char**>(0x140FEC4D0)[hitLoc];
|
||||
const auto mod_ = convert_mod(meansOfDeath);
|
||||
|
||||
const auto weapon_ = get_weapon_name(weapon, isAlternate);
|
||||
|
||||
for (const auto& callback : player_killed_callbacks)
|
||||
{
|
||||
const auto state = callback.lua_state();
|
||||
|
||||
const auto self_ = convert_entity(state, self);
|
||||
const auto inflictor_ = convert_entity(state, inflictor);
|
||||
const auto attacker_ = convert_entity(state, attacker);
|
||||
|
||||
const auto dir = convert_vector(state, vDir);
|
||||
|
||||
const auto result = callback(self_, inflictor_, attacker_, damage,
|
||||
mod_, weapon_, dir, hitloc, psTimeOffset, deathAnimDuration);
|
||||
|
||||
scripting::lua::handle_error(result);
|
||||
|
||||
if (result.valid() && result.get_type() == sol::type::number)
|
||||
{
|
||||
damage = result.get<int>();
|
||||
}
|
||||
}
|
||||
|
||||
if (damage == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scr_player_killed_hook.invoke<void>(self, inflictor, attacker, damage, meansOfDeath,
|
||||
weapon, isAlternate, vDir, hitLoc, psTimeOffset, deathAnimDuration);
|
||||
}
|
||||
|
||||
void scr_player_damage_stub(game::mp::gentity_s* self, const game::mp::gentity_s* inflictor,
|
||||
game::mp::gentity_s* attacker, int damage, int dflags, const int meansOfDeath,
|
||||
const unsigned int weapon, const bool isAlternate, const float* vPoint,
|
||||
const float* vDir, const unsigned int hitLoc, const int timeOffset)
|
||||
{
|
||||
{
|
||||
const std::string hitloc = reinterpret_cast<const char**>(0x140FEC4D0)[hitLoc];
|
||||
const auto mod_ = convert_mod(meansOfDeath);
|
||||
|
||||
const auto weapon_ = get_weapon_name(weapon, isAlternate);
|
||||
|
||||
for (const auto& callback : player_damage_callbacks)
|
||||
{
|
||||
const auto state = callback.lua_state();
|
||||
|
||||
const auto self_ = convert_entity(state, self);
|
||||
const auto inflictor_ = convert_entity(state, inflictor);
|
||||
const auto attacker_ = convert_entity(state, attacker);
|
||||
|
||||
const auto point = convert_vector(state, vPoint);
|
||||
const auto dir = convert_vector(state, vDir);
|
||||
|
||||
const auto result = callback(self_, inflictor_, attacker_,
|
||||
damage, dflags, mod_, weapon_, point, dir, hitloc);
|
||||
|
||||
scripting::lua::handle_error(result);
|
||||
|
||||
if (result.valid() && result.get_type() == sol::type::number)
|
||||
{
|
||||
damage = result.get<int>();
|
||||
}
|
||||
}
|
||||
|
||||
if (damage == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scr_player_damage_hook.invoke<void>(self, inflictor, attacker, damage, dflags,
|
||||
meansOfDeath, weapon, isAlternate, vPoint, vDir, hitLoc, timeOffset);
|
||||
}
|
||||
|
||||
void client_command_stub(const int clientNum)
|
||||
{
|
||||
auto self = &game::mp::g_entities[clientNum];
|
||||
char cmd[1024] = {0};
|
||||
|
||||
game::SV_Cmd_ArgvBuffer(0, cmd, 1024);
|
||||
|
||||
if (cmd == "say"s || cmd == "say_team"s)
|
||||
{
|
||||
auto hidden = false;
|
||||
std::string message(game::ConcatArgs(1));
|
||||
|
||||
hidden = message[1] == '/';
|
||||
message.erase(0, hidden ? 2 : 1);
|
||||
|
||||
scheduler::once([cmd, message, self]()
|
||||
{
|
||||
const scripting::entity level{*game::levelEntityId};
|
||||
const scripting::entity player{game::Scr_GetEntityId(self->s.entityNum, 0)};
|
||||
|
||||
scripting::notify(level, cmd, {player, message});
|
||||
scripting::notify(player, cmd, {message});
|
||||
}, scheduler::pipeline::server);
|
||||
|
||||
if (hidden)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ClientCommand
|
||||
return utils::hook::invoke<void>(0x140336000, clientNum);
|
||||
}
|
||||
|
||||
void g_shutdown_game_stub(const int freeScripts)
|
||||
{
|
||||
{
|
||||
const scripting::entity level{*game::levelEntityId};
|
||||
scripting::notify(level, "shutdownGame_called", {1});
|
||||
}
|
||||
|
||||
// G_ShutdownGame
|
||||
return utils::hook::invoke<void>(0x140345A60, freeScripts);
|
||||
}
|
||||
|
||||
unsigned int local_id_to_entity(unsigned int local_id)
|
||||
{
|
||||
const auto variable = game::scr_VarGlob->objectVariableValue[local_id];
|
||||
return variable.u.f.next;
|
||||
}
|
||||
|
||||
bool execute_vm_hook(const char* pos)
|
||||
{
|
||||
if (vm_execute_hooks.find(pos) == vm_execute_hooks.end())
|
||||
{
|
||||
hook_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hook_enabled && pos > reinterpret_cast<char*>(vm_execute_hooks.size()))
|
||||
{
|
||||
hook_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto hook = vm_execute_hooks[pos];
|
||||
const auto state = hook.lua_state();
|
||||
|
||||
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
|
||||
|
||||
std::vector<sol::lua_value> args;
|
||||
|
||||
const auto top = game::scr_function_stack->top;
|
||||
|
||||
for (auto* value = top; value->type != game::SCRIPT_END; --value)
|
||||
{
|
||||
args.push_back(scripting::lua::convert(state, *value));
|
||||
}
|
||||
|
||||
const auto result = hook(self, sol::as_args(args));
|
||||
scripting::lua::handle_error(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vm_execute_stub(utils::hook::assembler& a)
|
||||
{
|
||||
const auto replace = a.newLabel();
|
||||
const auto end = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
|
||||
a.mov(rcx, r14);
|
||||
a.call_aligned(execute_vm_hook);
|
||||
|
||||
a.cmp(al, 0);
|
||||
a.jne(replace);
|
||||
|
||||
a.popad64();
|
||||
a.jmp(end);
|
||||
|
||||
a.bind(end);
|
||||
|
||||
a.movzx(r15d, byte_ptr(r14));
|
||||
a.inc(r14);
|
||||
a.mov(dword_ptr(rbp, 0xA4), r15d);
|
||||
|
||||
a.jmp(SELECT_VALUE(0x140376663, 0x140444653));
|
||||
|
||||
a.bind(replace);
|
||||
|
||||
a.popad64();
|
||||
a.mov(r14, reinterpret_cast<char*>(empty_function));
|
||||
a.jmp(end);
|
||||
}
|
||||
}
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback)
|
||||
{
|
||||
player_damage_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void add_player_killed_callback(const sol::protected_function& callback)
|
||||
{
|
||||
player_killed_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void clear_callbacks()
|
||||
{
|
||||
player_damage_callbacks.clear();
|
||||
player_killed_callbacks.clear();
|
||||
vm_execute_hooks.clear();
|
||||
}
|
||||
|
||||
void enable_vm_execute_hook()
|
||||
{
|
||||
hook_enabled = true;
|
||||
}
|
||||
|
||||
void disable_vm_execute_hook()
|
||||
{
|
||||
hook_enabled = false;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::call(0x14048191D, client_command_stub);
|
||||
|
||||
scr_player_damage_hook.create(0x14037DC50, scr_player_damage_stub);
|
||||
scr_player_killed_hook.create(0x14037DF30, scr_player_killed_stub);
|
||||
|
||||
utils::hook::call(0x140484EC0, g_shutdown_game_stub);
|
||||
utils::hook::call(0x1404853C1, g_shutdown_game_stub);
|
||||
|
||||
utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(logfile::component)
|
13
src/client/component/logfile.hpp
Normal file
13
src/client/component/logfile.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
extern std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback);
|
||||
void add_player_killed_callback(const sol::protected_function& callback);
|
||||
void clear_callbacks();
|
||||
|
||||
void enable_vm_execute_hook();
|
||||
void disable_vm_execute_hook();
|
||||
}
|
58
src/client/component/lui.cpp
Normal file
58
src/client/component/lui.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace lui
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Don't show create cod account popup
|
||||
//utils::hook::set<uint32_t>(0x14017C957, 0); // H1(1.4)
|
||||
|
||||
//#ifdef _DEBUG
|
||||
// Enable development menus (causes issues in sp)
|
||||
//utils::hook::set<uint32_t>(SELECT_VALUE(0x1400B4ABC, 0x1401AB779), 1);
|
||||
//#endif
|
||||
|
||||
command::add("lui_open", [](const command::params& params)
|
||||
{
|
||||
if (params.size() <= 1)
|
||||
{
|
||||
console::info("usage: lui_open <name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
game::LUI_OpenMenu(0, params[1], 0, 0, 0);
|
||||
});
|
||||
|
||||
command::add("lui_open_popup", [](const command::params& params)
|
||||
{
|
||||
if (params.size() <= 1)
|
||||
{
|
||||
console::info("usage: lui_open_popup <name>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
game::LUI_OpenMenu(0, params[1], 1, 0, 0);
|
||||
});
|
||||
|
||||
command::add("runMenuScript", [](const command::params& params)
|
||||
{
|
||||
const auto args_str = params.join(1);
|
||||
const auto* args = args_str.data();
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(lui::component)
|
180
src/client/component/map_rotation.cpp
Normal file
180
src/client/component/map_rotation.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "command.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include "game/game.hpp"
|
||||
#include <game/dvars.hpp>
|
||||
|
||||
namespace map_rotation
|
||||
{
|
||||
DWORD previousPriority;
|
||||
namespace
|
||||
{
|
||||
void set_dvar(const std::string& dvar, const std::string& value)
|
||||
{
|
||||
command::execute(utils::string::va("%s \"%s\"", dvar.data(), value.data()), true);
|
||||
}
|
||||
|
||||
void set_gametype(const std::string& gametype)
|
||||
{
|
||||
set_dvar("g_gametype", gametype);
|
||||
}
|
||||
|
||||
void launch_map(const std::string& mapname)
|
||||
{
|
||||
command::execute(utils::string::va("map %s", mapname.data()), false);
|
||||
}
|
||||
|
||||
void launch_default_map()
|
||||
{
|
||||
auto* mapname = game::Dvar_FindVar("mapname");
|
||||
if (mapname && mapname->current.string && strlen(mapname->current.string) && mapname->current.string !=
|
||||
"mp_vlobby_room"s)
|
||||
{
|
||||
launch_map(mapname->current.string);
|
||||
}
|
||||
else
|
||||
{
|
||||
launch_map("mp_crash");
|
||||
}
|
||||
}
|
||||
|
||||
std::string load_current_map_rotation()
|
||||
{
|
||||
auto* rotation = game::Dvar_FindVar("sv_mapRotationCurrent");
|
||||
if (!strlen(rotation->current.string))
|
||||
{
|
||||
rotation = game::Dvar_FindVar("sv_mapRotation");
|
||||
set_dvar("sv_mapRotationCurrent", rotation->current.string);
|
||||
}
|
||||
|
||||
return rotation->current.string;
|
||||
}
|
||||
|
||||
std::vector<std::string> parse_current_map_rotation()
|
||||
{
|
||||
const auto rotation = load_current_map_rotation();
|
||||
return utils::string::split(rotation, ' ');
|
||||
}
|
||||
|
||||
void store_new_rotation(const std::vector<std::string>& elements, const size_t index)
|
||||
{
|
||||
std::string value{};
|
||||
|
||||
for (auto i = index; i < elements.size(); ++i)
|
||||
{
|
||||
if (i != index)
|
||||
{
|
||||
value.push_back(' ');
|
||||
}
|
||||
|
||||
value.append(elements[i]);
|
||||
}
|
||||
|
||||
set_dvar("sv_mapRotationCurrent", value);
|
||||
}
|
||||
|
||||
void change_process_priority()
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar("sv_autoPriority");
|
||||
if (dvar && dvar->current.enabled)
|
||||
{
|
||||
scheduler::on_game_initialized([]()
|
||||
{
|
||||
//printf("=======================setting OLD priority=======================\n");
|
||||
SetPriorityClass(GetCurrentProcess(), previousPriority);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
|
||||
previousPriority = GetPriorityClass(GetCurrentProcess());
|
||||
//printf("=======================setting NEW priority=======================\n");
|
||||
SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
void perform_map_rotation()
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
||||
{
|
||||
scheduler::on_game_initialized(perform_map_rotation, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto rotation = parse_current_map_rotation();
|
||||
|
||||
for (size_t i = 0; !rotation.empty() && i < (rotation.size() - 1); i += 2)
|
||||
{
|
||||
const auto& key = rotation[i];
|
||||
const auto& value = rotation[i + 1];
|
||||
|
||||
if (key == "gametype")
|
||||
{
|
||||
set_gametype(value);
|
||||
}
|
||||
else if (key == "map")
|
||||
{
|
||||
store_new_rotation(rotation, i + 2);
|
||||
change_process_priority();
|
||||
if (!game::SV_MapExists(value.data()))
|
||||
{
|
||||
printf("map_rotation: '%s' map doesn't exist!\n", value.data());
|
||||
launch_default_map();
|
||||
return;
|
||||
}
|
||||
launch_map(value);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Invalid map rotation key: %s\n", key.data());
|
||||
}
|
||||
}
|
||||
|
||||
launch_default_map();
|
||||
}
|
||||
|
||||
void trigger_map_rotation()
|
||||
{
|
||||
scheduler::schedule([]()
|
||||
{
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
return scheduler::cond_continue;
|
||||
}
|
||||
|
||||
command::execute("map_rotate", false);
|
||||
return scheduler::cond_end;
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
dvars::register_string("sv_mapRotation", "", game::DVAR_FLAG_NONE, true);
|
||||
dvars::register_string("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, true);
|
||||
dvars::register_string("sv_autoPriority", "", game::DVAR_FLAG_NONE, true);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
command::add("map_rotate", &perform_map_rotation);
|
||||
|
||||
// Hook GScr_ExitLevel
|
||||
utils::hook::jump(0x140376630, &trigger_map_rotation); // not sure if working
|
||||
|
||||
previousPriority = GetPriorityClass(GetCurrentProcess());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(map_rotation::component)
|
@ -27,8 +27,15 @@ namespace network
|
||||
const auto cmd_string = utils::string::to_lower(command);
|
||||
auto& callbacks = get_callbacks();
|
||||
const auto handler = callbacks.find(cmd_string);
|
||||
|
||||
if (handler == callbacks.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto offset = cmd_string.size() + 5;
|
||||
if (message->cursize < offset || handler == callbacks.end())
|
||||
|
||||
if (message->cursize <= offset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
630
src/client/component/party.cpp
Normal file
630
src/client/component/party.cpp
Normal file
@ -0,0 +1,630 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "party.hpp"
|
||||
#include "console.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{};
|
||||
bool hostDefined{false};
|
||||
} connect_state;
|
||||
|
||||
std::string sv_motd;
|
||||
int sv_maxclients;
|
||||
|
||||
void perform_game_initialization()
|
||||
{
|
||||
command::execute("onlinegame 1", 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) != 0)
|
||||
{
|
||||
// initialize the game after onlinedataflags is 32 (workaround)
|
||||
if (game::Live_SyncOnlineDataFlags(0) == 32)
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("xstartprivateparty", true);
|
||||
command::execute("disconnect", true); // 32 -> 0
|
||||
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
connect_to_party(target, mapname, gametype);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
perform_game_initialization();
|
||||
|
||||
// exit from virtuallobby
|
||||
utils::hook::invoke<void>(0x140256D40, 1);
|
||||
|
||||
// CL_ConnectFromParty
|
||||
char session_info[0x100] = {};
|
||||
utils::hook::invoke<void>(0x140251560, 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_dvar_int(const std::string& dvar)
|
||||
{
|
||||
auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
||||
if (dvar_value && dvar_value->current.integer)
|
||||
{
|
||||
return dvar_value->current.integer;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool get_dvar_bool(const std::string& dvar)
|
||||
{
|
||||
auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
||||
if (dvar_value && dvar_value->current.enabled)
|
||||
{
|
||||
return dvar_value->current.enabled;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void didyouknow_stub(const char* dvar_name, const char* string)
|
||||
{
|
||||
if (!party::sv_motd.empty())
|
||||
{
|
||||
string = party::sv_motd.data();
|
||||
}
|
||||
|
||||
// This function either does Dvar_SetString or Dvar_RegisterString for the given dvar
|
||||
utils::hook::invoke<void>(0x1404FB210, dvar_name, string);
|
||||
}
|
||||
|
||||
void disconnect_stub()
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
// CL_ForwardCommandToServer
|
||||
utils::hook::invoke<void>(0x140253480, 0, "disconnect");
|
||||
// CL_WritePacket
|
||||
utils::hook::invoke<void>(0x14024DB10, 0);
|
||||
}
|
||||
// CL_Disconnect
|
||||
utils::hook::invoke<void>(0x140252060, 0);
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour cldisconnect_hook;
|
||||
|
||||
void cl_disconnect_stub(int a1)
|
||||
{
|
||||
party::sv_motd.clear();
|
||||
cldisconnect_hook.invoke<void>(a1);
|
||||
}
|
||||
|
||||
const auto drop_reason_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.mov(rdx, rdi);
|
||||
a.mov(ecx, 2);
|
||||
a.jmp(0x140251F78);
|
||||
});
|
||||
|
||||
void menu_error(const std::string& error)
|
||||
{
|
||||
utils::hook::invoke<void>(0x1400DACC0, error.data(), "MENU_NOTICE");
|
||||
utils::hook::set(0x142C1DA98, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int get_client_num_by_name(const std::string& name)
|
||||
{
|
||||
for (auto i = 0; !name.empty() && i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
if (game::mp::g_entities[i].client)
|
||||
{
|
||||
char client_name[16] = {0};
|
||||
strncpy_s(client_name, game::mp::g_entities[i].client->name, sizeof(client_name));
|
||||
game::I_CleanStr(client_name);
|
||||
|
||||
if (client_name == name)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void reset_connect_state()
|
||||
{
|
||||
connect_state = {};
|
||||
}
|
||||
|
||||
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 >= 1)
|
||||
{
|
||||
++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 >= 1 &&
|
||||
game::SV_BotIsBot(i))
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void connect(const game::netadr_s& target)
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
command::execute("lui_open_popup popup_acceptinginvite", false);
|
||||
|
||||
connect_state.host = target;
|
||||
connect_state.challenge = utils::cryptography::random::get_challenge();
|
||||
connect_state.hostDefined = true;
|
||||
|
||||
network::send(target, "getInfo", connect_state.challenge);
|
||||
}
|
||||
|
||||
void start_map(const std::string& mapname)
|
||||
{
|
||||
if (game::Live_SyncOnlineDataFlags(0) > 32)
|
||||
{
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("map " + mapname, false);
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!game::SV_MapExists(mapname.data()))
|
||||
{
|
||||
console::info("Map '%s' doesn't exist.\n", 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() && !game::VirtualLobby_Loaded()))
|
||||
{
|
||||
console::info("Restarting map: %s\n", mapname.data());
|
||||
command::execute("map_restart", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
if (game::SV_Loaded())
|
||||
{
|
||||
const auto* args = "Leave";
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
}
|
||||
|
||||
perform_game_initialization();
|
||||
}
|
||||
|
||||
console::info("Starting map: %s\n", mapname.data());
|
||||
|
||||
auto* gametype = game::Dvar_FindVar("g_gametype");
|
||||
if (gametype && gametype->current.string)
|
||||
{
|
||||
command::execute(utils::string::va("ui_gametype %s", gametype->current.string), true);
|
||||
}
|
||||
command::execute(utils::string::va("ui_mapname %s", mapname.data()), true);
|
||||
|
||||
/*auto* maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||
if (maxclients)
|
||||
{
|
||||
command::execute(utils::string::va("ui_maxclients %i", maxclients->current.integer), true);
|
||||
command::execute(utils::string::va("party_maxplayers %i", maxclients->current.integer), true);
|
||||
}*/
|
||||
|
||||
const auto* args = "StartServer";
|
||||
game::UI_RunMenuScript(0, &args);
|
||||
}
|
||||
}
|
||||
|
||||
int server_client_count()
|
||||
{
|
||||
return party::sv_maxclients;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// hook disconnect command function
|
||||
utils::hook::jump(0x1402521C7, disconnect_stub);
|
||||
|
||||
// detour CL_Disconnect to clear motd
|
||||
cldisconnect_hook.create(0x140252060, cl_disconnect_stub);
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
// show custom drop reason
|
||||
utils::hook::nop(0x140251EFB, 13);
|
||||
utils::hook::jump(0x140251EFB, drop_reason_stub, true);
|
||||
}
|
||||
|
||||
// enable custom kick reason in GScr_KickPlayer
|
||||
utils::hook::set<uint8_t>(0x140376A1D, 0xEB);
|
||||
|
||||
command::add("map", [](const command::params& argument)
|
||||
{
|
||||
if (argument.size() != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start_map(argument[1]);
|
||||
});
|
||||
|
||||
command::add("map_restart", []()
|
||||
{
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
*reinterpret_cast<int*>(0x14A3A91D0) = 1; // sv_map_restart
|
||||
*reinterpret_cast<int*>(0x14A3A91D4) = 1; // sv_loadScripts
|
||||
*reinterpret_cast<int*>(0x14A3A91D8) = 0; // sv_migrate
|
||||
|
||||
utils::hook::invoke<void>(0x14047E7F0); // SV_CheckLoadGame
|
||||
});
|
||||
|
||||
command::add("fast_restart", []()
|
||||
{
|
||||
if (game::SV_Loaded() && !game::VirtualLobby_Loaded())
|
||||
{
|
||||
game::SV_FastRestart(0);
|
||||
}
|
||||
});
|
||||
|
||||
command::add("reconnect", [](const command::params& argument)
|
||||
{
|
||||
if (!connect_state.hostDefined)
|
||||
{
|
||||
console::info("Cannot connect to server.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::CL_IsCgameInitialized())
|
||||
{
|
||||
command::execute("disconnect");
|
||||
command::execute("reconnect");
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(connect_state.host);
|
||||
}
|
||||
});
|
||||
|
||||
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("kickClient", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("usage: kickClient <num>, <reason>(optional)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
if (params.size() > 2)
|
||||
{
|
||||
reason = params.join(2);
|
||||
}
|
||||
if (reason.empty())
|
||||
{
|
||||
reason = "EXE_PLAYERKICKED";
|
||||
}
|
||||
|
||||
const auto client_num = atoi(params.get(1));
|
||||
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([client_num, reason]()
|
||||
{
|
||||
game::SV_KickClientNum(client_num, reason.data());
|
||||
}, scheduler::pipeline::server);
|
||||
});
|
||||
|
||||
command::add("kick", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("usage: kick <name>, <reason>(optional)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
if (params.size() > 2)
|
||||
{
|
||||
reason = params.join(2);
|
||||
}
|
||||
if (reason.empty())
|
||||
{
|
||||
reason = "EXE_PLAYERKICKED";
|
||||
}
|
||||
|
||||
const std::string name = params.get(1);
|
||||
if (name == "all"s)
|
||||
{
|
||||
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
||||
{
|
||||
scheduler::once([i, reason]()
|
||||
{
|
||||
game::SV_KickClientNum(i, reason.data());
|
||||
}, scheduler::pipeline::server);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto client_num = get_client_num_by_name(name);
|
||||
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([client_num, reason]()
|
||||
{
|
||||
game::SV_KickClientNum(client_num, reason.data());
|
||||
}, scheduler::pipeline::server);
|
||||
});
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
const auto hash = game::generateHashValue("sv_sayName");
|
||||
game::Dvar_RegisterString(hash, "sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE);
|
||||
}, 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, game::SV_CMD_CAN_IGNORE,
|
||||
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, game::SV_CMD_CAN_IGNORE,
|
||||
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, game::SV_CMD_CAN_IGNORE, 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, game::SV_CMD_CAN_IGNORE,
|
||||
utils::string::va("%c \"%s\"", 84, message.data()));
|
||||
printf("%s\n", message.data());
|
||||
});
|
||||
|
||||
utils::hook::call(0x1404C6E8D, didyouknow_stub); // allow custom didyouknow based on sv_motd
|
||||
|
||||
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", "H1");
|
||||
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("sv_running", utils::string::va("%i", get_dvar_bool("sv_running") && !game::VirtualLobby_Loaded()));
|
||||
info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
||||
|
||||
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);
|
||||
menu_error(str);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto gamename = info.get("gamename");
|
||||
if (gamename != "H1"s)
|
||||
{
|
||||
const auto str = "Invalid gamename.";
|
||||
printf("%s\n", str);
|
||||
menu_error(str);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto playmode = info.get("playmode");
|
||||
if (game::CodPlayMode(std::atoi(playmode.data())) != game::Com_GetCurrentCoDPlayMode())
|
||||
{
|
||||
const auto str = "Invalid playmode.";
|
||||
printf("%s\n", str);
|
||||
menu_error(str);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sv_running = info.get("sv_running");
|
||||
if (!std::atoi(sv_running.data()))
|
||||
{
|
||||
const auto str = "Server not running.";
|
||||
printf("%s\n", str);
|
||||
menu_error(str);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto mapname = info.get("mapname");
|
||||
if (mapname.empty())
|
||||
{
|
||||
const auto str = "Invalid map.";
|
||||
printf("%s\n", str);
|
||||
menu_error(str);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto gametype = info.get("gametype");
|
||||
if (gametype.empty())
|
||||
{
|
||||
const auto str = "Invalid gametype.";
|
||||
printf("%s\n", str);
|
||||
menu_error(str);
|
||||
return;
|
||||
}
|
||||
|
||||
party::sv_motd = info.get("sv_motd");
|
||||
party::sv_maxclients = std::stoi(info.get("sv_maxclients"));
|
||||
|
||||
connect_to_party(target, mapname, gametype);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(party::component)
|
17
src/client/component/party.hpp
Normal file
17
src/client/component/party.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace party
|
||||
{
|
||||
void reset_connect_state();
|
||||
|
||||
void connect(const game::netadr_s& target);
|
||||
void start_map(const std::string& mapname);
|
||||
|
||||
int server_client_count();
|
||||
|
||||
int get_client_num_by_name(const std::string& name);
|
||||
|
||||
int get_client_count();
|
||||
int get_bot_count();
|
||||
}
|
293
src/client/component/patches.cpp
Normal file
293
src/client/component/patches.cpp
Normal file
@ -0,0 +1,293 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "dvars.hpp"
|
||||
#include "version.h"
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace patches
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char* live_get_local_client_name()
|
||||
{
|
||||
return game::Dvar_FindVar("name")->current.string;
|
||||
}
|
||||
|
||||
utils::hook::detour sv_kick_client_num_hook;
|
||||
|
||||
void sv_kick_client_num(const int client_num, 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>(client_num, reason);
|
||||
}
|
||||
|
||||
std::string get_login_username()
|
||||
{
|
||||
char username[UNLEN + 1];
|
||||
DWORD username_len = UNLEN + 1;
|
||||
if (!GetUserNameA(username, &username_len))
|
||||
{
|
||||
return "Unknown Soldier";
|
||||
}
|
||||
|
||||
return std::string{ username, username_len - 1 };
|
||||
}
|
||||
|
||||
utils::hook::detour com_register_dvars_hook;
|
||||
|
||||
void com_register_dvars_stub()
|
||||
{
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
// Make name save
|
||||
dvars::register_string("name", get_login_username().data(), game::DVAR_FLAG_SAVED, true);
|
||||
|
||||
// Disable data validation error popup
|
||||
dvars::register_int("data_validation_allow_drop", 0, 0, 0, game::DVAR_FLAG_NONE, true);
|
||||
}
|
||||
|
||||
return com_register_dvars_hook.invoke<void>();
|
||||
}
|
||||
|
||||
int is_item_unlocked()
|
||||
{
|
||||
return 0; // 0 == yes
|
||||
}
|
||||
|
||||
void set_client_dvar_from_server_stub(void* a1, void* a2, const char* dvar, const char* value)
|
||||
{
|
||||
if (dvar == "cg_fov"s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// CG_SetClientDvarFromServer
|
||||
utils::hook::invoke<void>(0x140236120, a1, a2, dvar, value);
|
||||
}
|
||||
|
||||
const char* db_read_raw_file_stub(const char* filename, char* buf, const int size)
|
||||
{
|
||||
std::string file_name = filename;
|
||||
if (file_name.find(".cfg") == std::string::npos)
|
||||
{
|
||||
file_name.append(".cfg");
|
||||
}
|
||||
|
||||
const auto file = filesystem::file(file_name);
|
||||
if (file.exists())
|
||||
{
|
||||
snprintf(buf, size, "%s\n", file.get_buffer().data());
|
||||
return buf;
|
||||
}
|
||||
|
||||
// DB_ReadRawFile
|
||||
return utils::hook::invoke<const char*>(SELECT_VALUE(0x1401CD4F0, 0x1402BEF10), filename, buf, size);
|
||||
}
|
||||
|
||||
void bsp_sys_error_stub(const char* error, const char* arg1)
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
game::Sys_Error(error, arg1);
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler::once([]()
|
||||
{
|
||||
command::execute("reconnect");
|
||||
}, scheduler::pipeline::main, 1s);
|
||||
game::Com_Error(game::ERR_DROP, error, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour cmd_lui_notify_server_hook;
|
||||
void cmd_lui_notify_server_stub(game::mp::gentity_s* ent)
|
||||
{
|
||||
command::params_sv params{};
|
||||
const auto menu_id = atoi(params.get(1));
|
||||
const auto client = &game::mp::svs_clients[ent->s.entityNum];
|
||||
|
||||
// 22 => "end_game"
|
||||
if (menu_id == 22 && client->header.remoteAddress.type != game::NA_LOOPBACK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_lui_notify_server_hook.invoke<void>(ent);
|
||||
}
|
||||
|
||||
void sv_execute_client_message_stub(game::mp::client_t* client, game::msg_t* msg)
|
||||
{
|
||||
if (client->reliableAcknowledge < 0)
|
||||
{
|
||||
client->reliableAcknowledge = client->reliableSequence;
|
||||
console::info("Negative reliableAcknowledge from %s - cl->reliableSequence is %i, reliableAcknowledge is %i\n",
|
||||
client->name, client->reliableSequence, client->reliableAcknowledge);
|
||||
network::send(client->header.remoteAddress, "error", "EXE_LOSTRELIABLECOMMANDS", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x140481A00, client, msg);
|
||||
}
|
||||
|
||||
void aim_assist_add_to_target_list(void* a1, void* a2)
|
||||
{
|
||||
if (!dvars::aimassist_enabled->current.enabled)
|
||||
return;
|
||||
|
||||
game::AimAssist_AddToTargetList(a1, a2);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Register dvars
|
||||
com_register_dvars_hook.create(SELECT_VALUE(0x140351B80, 0x1400D9320), &com_register_dvars_stub); // H1(1.4)
|
||||
|
||||
// Unlock fps in main menu
|
||||
utils::hook::set<BYTE>(SELECT_VALUE(0x14018D47B, 0x14025B86B), 0xEB); // H1(1.4)
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
// Fix mouse lag
|
||||
utils::hook::nop(SELECT_VALUE(0x1403E3C05, 0x1404DB1AF), 6);
|
||||
scheduler::loop([]()
|
||||
{
|
||||
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
|
||||
}, scheduler::pipeline::main);
|
||||
}
|
||||
|
||||
// Make cg_fov and cg_fovscale saved dvars
|
||||
dvars::override::register_float("cg_fov", 65.f, 40.f, 200.f, game::DvarFlags::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("cg_fovScale", 1.f, 0.1f, 2.f, game::DvarFlags::DVAR_FLAG_SAVED);
|
||||
|
||||
// Allow kbam input when gamepad is enabled
|
||||
utils::hook::nop(SELECT_VALUE(0x14018797E, 0x14024EF60), 2);
|
||||
utils::hook::nop(SELECT_VALUE(0x1401856DC, 0x14024C6B0), 6);
|
||||
|
||||
// Allow executing custom cfg files with the "exec" command
|
||||
utils::hook::call(SELECT_VALUE(0x140343855, 0x140403E28), db_read_raw_file_stub);
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
patch_mp();
|
||||
}
|
||||
}
|
||||
|
||||
static void patch_mp()
|
||||
{
|
||||
// Use name dvar
|
||||
utils::hook::jump(0x14050FF90, &live_get_local_client_name); // H1(1.4)
|
||||
|
||||
// Patch SV_KickClientNum
|
||||
sv_kick_client_num_hook.create(0x14047ED00, &sv_kick_client_num); // H1(1.4)
|
||||
|
||||
// block changing name in-game
|
||||
utils::hook::set<uint8_t>(0x14047FC90, 0xC3); // H1(1.4)
|
||||
|
||||
// patch "Couldn't find the bsp for this map." error to not be fatal in mp
|
||||
utils::hook::call(0x1402BA26B, bsp_sys_error_stub); // H1(1.4)
|
||||
|
||||
// client side aim assist dvar
|
||||
dvars::aimassist_enabled = dvars::register_bool("aimassist_enabled", true,
|
||||
game::DvarFlags::DVAR_FLAG_SAVED,
|
||||
true);
|
||||
utils::hook::call(0x14009EE9E, aim_assist_add_to_target_list);
|
||||
|
||||
// unlock all items
|
||||
utils::hook::jump(0x140413E60, is_item_unlocked); // LiveStorage_IsItemUnlockedFromTable_LocalClient H1(1.4)
|
||||
utils::hook::jump(0x140413860, is_item_unlocked); // LiveStorage_IsItemUnlockedFromTable H1(1.4)
|
||||
utils::hook::jump(0x140412B70, is_item_unlocked); // idk ( unlocks loot etc ) H1(1.4)
|
||||
|
||||
// isProfanity
|
||||
utils::hook::set(0x1402877D0, 0xC3C033); // MAY BE WRONG H1(1.4)
|
||||
|
||||
// disable emblems
|
||||
dvars::override::register_int("emblems_active", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
utils::hook::set<uint8_t>(0x140479590, 0xC3); // don't register commands
|
||||
|
||||
// disable elite_clan
|
||||
dvars::override::register_int("elite_clan_active", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
utils::hook::set<uint8_t>(0x140585680, 0xC3); // don't register commands H1(1.4)
|
||||
|
||||
// disable codPointStore
|
||||
dvars::override::register_int("codPointStore_enabled", 0, 0, 0, game::DVAR_FLAG_NONE);
|
||||
|
||||
// don't register every replicated dvar as a network dvar
|
||||
utils::hook::nop(0x14039E58E, 5); // dvar_foreach H1(1.4)
|
||||
|
||||
// patch "Server is different version" to show the server client version
|
||||
utils::hook::inject(0x140480952, VERSION); // H1(1.4)
|
||||
|
||||
// prevent servers overriding our fov
|
||||
utils::hook::call(0x14023279E, set_client_dvar_from_server_stub);
|
||||
utils::hook::nop(0x1400DAF69, 5);
|
||||
utils::hook::nop(0x140190C16, 5);
|
||||
utils::hook::set<uint8_t>(0x14021D22A, 0xEB);
|
||||
|
||||
// unlock safeArea_*
|
||||
utils::hook::jump(0x1402624F5, 0x140262503); // H1(1.4)
|
||||
utils::hook::jump(0x14026251C, 0x140262547); // H1(1.4)
|
||||
dvars::override::register_int("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_int("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_int("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_int("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// move chat position on the screen above menu splashes
|
||||
dvars::override::register_vec2("cg_hudChatPosition", 5, 170, 0, 640, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// allow servers to check for new packages more often
|
||||
dvars::override::register_int("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// Massively increate timeouts
|
||||
dvars::override::register_int("cl_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // Seems unused
|
||||
dvars::override::register_int("sv_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // 30 - 0 - 1800
|
||||
dvars::override::register_int("cl_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // Seems unused
|
||||
dvars::override::register_int("sv_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // 60 - 0 - 1800
|
||||
|
||||
dvars::register_int("scr_game_spectatetype", 1, 0, 99, game::DVAR_FLAG_REPLICATED);
|
||||
|
||||
dvars::override::register_int("com_maxfps", 0, 10, 1000, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// Prevent clients from ending the game as non host by sending 'end_game' lui notification
|
||||
// cmd_lui_notify_server_hook.create(0x140335A70, cmd_lui_notify_server_stub); // H1(1.4)
|
||||
|
||||
// Prevent clients from sending invalid reliableAcknowledge
|
||||
// utils::hook::call(0x1404899C6, sv_execute_client_message_stub); // H1(1.4)
|
||||
|
||||
// "fix" for rare 'Out of memory error' error
|
||||
if (utils::flags::has_flag("memoryfix"))
|
||||
{
|
||||
utils::hook::jump(0x140578BE0, malloc);
|
||||
utils::hook::jump(0x140578B00, _aligned_malloc);
|
||||
utils::hook::jump(0x140578C40, free);
|
||||
utils::hook::jump(0x140578D30, realloc);
|
||||
utils::hook::jump(0x140578B60, _aligned_realloc);
|
||||
}
|
||||
|
||||
// Change default hostname and make it replicated
|
||||
dvars::override::register_string("sv_hostname", "^2H1-Mod^7 Default Server", game::DVAR_FLAG_REPLICATED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(patches::component)
|
77
src/client/component/renderer.cpp
Normal file
77
src/client/component/renderer.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace renderer
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour r_init_draw_method_hook;
|
||||
utils::hook::detour r_update_front_end_dvar_options_hook;
|
||||
|
||||
int get_fullbright_technique()
|
||||
{
|
||||
return game::TECHNIQUE_UNLIT;
|
||||
}
|
||||
|
||||
void gfxdrawmethod()
|
||||
{
|
||||
game::gfxDrawMethod->drawScene = game::GFX_DRAW_SCENE_STANDARD;
|
||||
|
||||
game::gfxDrawMethod->baseTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_LIT;
|
||||
game::gfxDrawMethod->emissiveTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : game::TECHNIQUE_EMISSIVE;
|
||||
game::gfxDrawMethod->forceTechType = dvars::r_fullbright->current.enabled ? get_fullbright_technique() : 242;
|
||||
}
|
||||
|
||||
void r_init_draw_method_stub()
|
||||
{
|
||||
gfxdrawmethod();
|
||||
}
|
||||
|
||||
bool r_update_front_end_dvar_options_stub()
|
||||
{
|
||||
if (dvars::r_fullbright->modified)
|
||||
{
|
||||
//game::Dvar_ClearModified(dvars::r_fullbright);
|
||||
game::R_SyncRenderThread();
|
||||
|
||||
gfxdrawmethod();
|
||||
}
|
||||
|
||||
return r_update_front_end_dvar_options_hook.invoke<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi() || !game::environment::is_mp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dvars::r_fullbright = dvars::register_int("r_fullbright", 0, 0, 3, game::DVAR_FLAG_SAVED);
|
||||
|
||||
r_init_draw_method_hook.create(SELECT_VALUE(0x1404BD140, 0x1405C46E0), &r_init_draw_method_stub);
|
||||
r_update_front_end_dvar_options_hook.create(SELECT_VALUE(0x1404F8870, 0x1405FF9E0), &r_update_front_end_dvar_options_stub);
|
||||
|
||||
// use "saved" flags for "r_normalMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x1404CF5CA, 0x1405D460E), game::DVAR_FLAG_SAVED);
|
||||
|
||||
// use "saved" flags for "r_specularMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x1404CF5F5, 0x1405D4639), game::DVAR_FLAG_SAVED);
|
||||
|
||||
// use "saved" flags for "r_specOccMap"
|
||||
utils::hook::set<uint8_t>(SELECT_VALUE(0x1404CF620, 0x1405D4664), game::DVAR_FLAG_SAVED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
REGISTER_COMPONENT(renderer::component)
|
||||
#endif
|
@ -180,9 +180,9 @@ namespace scheduler
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
r_end_frame_hook.create(SELECT_VALUE(0, 0x6A6300_b), scheduler::r_end_frame_stub);
|
||||
g_run_frame_hook.create(SELECT_VALUE(0, 0x417940_b), scheduler::server_frame_stub);
|
||||
main_frame_hook.create(SELECT_VALUE(0, 0x1400D8310), scheduler::main_frame_stub); // I REPEAT, ARXAN IS PAIN
|
||||
r_end_frame_hook.create(SELECT_VALUE(0, 0x6A6300_b), scheduler::r_end_frame_stub); // H1-STEAM(1.15)
|
||||
g_run_frame_hook.create(SELECT_VALUE(0, 0x417940_b), scheduler::server_frame_stub); // H1(1.15)
|
||||
//main_frame_hook.create(SELECT_VALUE(0x1401CE8D0, 0x1400D8310), scheduler::main_frame_stub); can't find
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
|
141
src/client/component/scripting.cpp
Normal file
141
src/client/component/scripting.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/lua/engine.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
utils::hook::detour scr_add_class_field_hook;
|
||||
|
||||
utils::hook::detour scr_set_thread_position_hook;
|
||||
utils::hook::detour process_script_hook;
|
||||
|
||||
std::string current_file;
|
||||
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
|
||||
game::VariableValue* top)
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
const auto* string = game::SL_ConvertToString(string_value);
|
||||
if (string)
|
||||
{
|
||||
event e;
|
||||
e.name = string;
|
||||
e.entity = notify_list_owner_id;
|
||||
|
||||
for (auto* value = top; value->type != game::SCRIPT_END; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
|
||||
if (e.name == "entitydeleted")
|
||||
{
|
||||
scripting::clear_entity_fields(e.entity);
|
||||
}
|
||||
|
||||
lua::engine::notify(e);
|
||||
}
|
||||
}
|
||||
|
||||
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
scr_load_level_hook.invoke<void>();
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
lua::engine::start();
|
||||
}
|
||||
}
|
||||
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
lua::engine::stop();
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
|
||||
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t _name, unsigned int canonicalString, unsigned int offset)
|
||||
{
|
||||
const auto name = game::SL_ConvertToString(_name);
|
||||
|
||||
if (fields_table[classnum].find(name) == fields_table[classnum].end())
|
||||
{
|
||||
fields_table[classnum][name] = offset;
|
||||
}
|
||||
|
||||
scr_add_class_field_hook.invoke<void>(classnum, _name, canonicalString, offset);
|
||||
}
|
||||
|
||||
void process_script_stub(const char* filename)
|
||||
{
|
||||
const auto file_id = atoi(filename);
|
||||
if (file_id)
|
||||
{
|
||||
current_file = scripting::find_token(file_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_file = filename;
|
||||
}
|
||||
|
||||
process_script_hook.invoke<void>(filename);
|
||||
}
|
||||
|
||||
void scr_set_thread_position_stub(unsigned int threadName, const char* codePos)
|
||||
{
|
||||
const auto function_name = scripting::find_token(threadName);
|
||||
script_function_table[current_file][function_name] = codePos;
|
||||
scr_set_thread_position_hook.invoke<void>(threadName, codePos);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vm_notify_hook.create(SELECT_VALUE(0x140379A00, 0x1404479F0), vm_notify_stub);
|
||||
|
||||
scr_add_class_field_hook.create(SELECT_VALUE(0x140370370, 0x14043E2C0), scr_add_class_field_stub);
|
||||
|
||||
scr_set_thread_position_hook.create(SELECT_VALUE(0x14036A180, 0x140437D10), scr_set_thread_position_stub);
|
||||
process_script_hook.create(SELECT_VALUE(0x1403737E0, 0x1404417E0), process_script_stub);
|
||||
|
||||
scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub);
|
||||
g_shutdown_game_hook.create(SELECT_VALUE(0x140277D40, 0x140345A60), g_shutdown_game_stub);
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
lua::engine::run_frame();
|
||||
}, scheduler::pipeline::server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scripting::component)
|
8
src/client/component/scripting.hpp
Normal file
8
src/client/component/scripting.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
}
|
443
src/client/component/server_list.cpp
Normal file
443
src/client/component/server_list.cpp
Normal file
@ -0,0 +1,443 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "network.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "party.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "console.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
namespace server_list
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const int server_limit = 18;
|
||||
|
||||
struct server_info
|
||||
{
|
||||
// gotta add more to this
|
||||
int clients;
|
||||
int max_clients;
|
||||
int bots;
|
||||
int ping;
|
||||
std::string host_name;
|
||||
std::string map_name;
|
||||
std::string game_type;
|
||||
game::CodPlayMode play_mode;
|
||||
char in_game;
|
||||
game::netadr_s address;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
game::netadr_s address{};
|
||||
volatile bool requesting = false;
|
||||
std::unordered_map<game::netadr_s, int> queued_servers{};
|
||||
} master_state;
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<server_info> servers;
|
||||
|
||||
size_t server_list_page = 0;
|
||||
volatile bool update_server_list = false;
|
||||
std::chrono::high_resolution_clock::time_point last_scroll{};
|
||||
|
||||
size_t get_page_count()
|
||||
{
|
||||
const auto count = servers.size() / server_limit;
|
||||
return count + (servers.size() % server_limit > 0);
|
||||
}
|
||||
|
||||
size_t get_page_base_index()
|
||||
{
|
||||
return server_list_page * server_limit;
|
||||
}
|
||||
|
||||
void refresh_server_list()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
servers.clear();
|
||||
master_state.queued_servers.clear();
|
||||
server_list_page = 0;
|
||||
}
|
||||
|
||||
party::reset_connect_state();
|
||||
|
||||
if (get_master_server(master_state.address))
|
||||
{
|
||||
master_state.requesting = true;
|
||||
|
||||
network::send(master_state.address, "getservers", utils::string::va("H1 %i full empty", PROTOCOL));
|
||||
}
|
||||
}
|
||||
|
||||
void join_server(int, int, const int index)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
const auto i = static_cast<size_t>(index) + get_page_base_index();
|
||||
if (i < servers.size())
|
||||
{
|
||||
static auto last_index = ~0ull;
|
||||
if (last_index != i)
|
||||
{
|
||||
last_index = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
console::info("Connecting to (%d - %zu): %s\n", index, i, servers[i].host_name.data());
|
||||
party::connect(servers[i].address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_refresh()
|
||||
{
|
||||
update_server_list = true;
|
||||
}
|
||||
|
||||
int ui_feeder_count()
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
if (update_server_list)
|
||||
{
|
||||
update_server_list = false;
|
||||
return 0;
|
||||
}
|
||||
const auto count = static_cast<int>(servers.size());
|
||||
const auto index = get_page_base_index();
|
||||
const auto diff = count - index;
|
||||
return diff > server_limit ? server_limit : static_cast<int>(diff);
|
||||
}
|
||||
|
||||
const char* ui_feeder_item_text(int /*localClientNum*/, void* /*a2*/, void* /*a3*/, const int index,
|
||||
const int column)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
const auto i = get_page_base_index() + index;
|
||||
|
||||
if (i >= servers.size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (column == 0)
|
||||
{
|
||||
return servers[i].host_name.empty() ? "" : utils::string::va("%s", servers[i].host_name.data());
|
||||
}
|
||||
|
||||
if (column == 1)
|
||||
{
|
||||
return servers[i].map_name.empty() ? "Unknown" : utils::string::va("%s", servers[i].map_name.data());
|
||||
}
|
||||
|
||||
if (column == 2)
|
||||
{
|
||||
return utils::string::va("%d/%d [%d]", servers[i].clients, servers[index].max_clients,
|
||||
servers[i].bots);
|
||||
}
|
||||
|
||||
if (column == 3)
|
||||
{
|
||||
return servers[i].game_type.empty() ? "" : utils::string::va("%s", servers[i].game_type.data());
|
||||
}
|
||||
|
||||
if (column == 4)
|
||||
{
|
||||
return servers[i].game_type.empty() ? "" : utils::string::va("%i", servers[i].ping);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void sort_serverlist()
|
||||
{
|
||||
std::stable_sort(servers.begin(), servers.end(), [](const server_info& a, const server_info& b)
|
||||
{
|
||||
if (a.clients == b.clients)
|
||||
{
|
||||
return a.ping < b.ping;
|
||||
}
|
||||
|
||||
return a.clients > b.clients;
|
||||
});
|
||||
}
|
||||
|
||||
void insert_server(server_info&& server)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
servers.emplace_back(std::move(server));
|
||||
sort_serverlist();
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
void do_frame_work()
|
||||
{
|
||||
auto& queue = master_state.queued_servers;
|
||||
if (queue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
|
||||
size_t queried_servers = 0;
|
||||
const size_t query_limit = 3;
|
||||
|
||||
for (auto i = queue.begin(); i != queue.end();)
|
||||
{
|
||||
if (i->second)
|
||||
{
|
||||
const auto now = game::Sys_Milliseconds();
|
||||
if (now - i->second > 10'000)
|
||||
{
|
||||
i = queue.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (queried_servers++ < query_limit)
|
||||
{
|
||||
i->second = game::Sys_Milliseconds();
|
||||
network::send(i->first, "getInfo", utils::cryptography::random::get_challenge());
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_server_list_open()
|
||||
{
|
||||
return game::Menu_IsMenuOpenAndVisible(0, "menu_systemlink_join");
|
||||
}
|
||||
|
||||
bool is_scrolling_disabled()
|
||||
{
|
||||
return update_server_list || (std::chrono::high_resolution_clock::now() - last_scroll) < 500ms;
|
||||
}
|
||||
|
||||
bool scroll_down()
|
||||
{
|
||||
if (!is_server_list_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_scrolling_disabled() && server_list_page + 1 < get_page_count())
|
||||
{
|
||||
last_scroll = std::chrono::high_resolution_clock::now();
|
||||
++server_list_page;
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool scroll_up()
|
||||
{
|
||||
if (!is_server_list_open())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_scrolling_disabled() && server_list_page > 0)
|
||||
{
|
||||
last_scroll = std::chrono::high_resolution_clock::now();
|
||||
--server_list_page;
|
||||
trigger_refresh();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void resize_host_name(std::string& name)
|
||||
{
|
||||
name = utils::string::split(name, '\n').front();
|
||||
|
||||
game::Font_s* font = game::R_RegisterFont("fonts/default.otf", 18);
|
||||
auto text_size = game::UI_TextWidth(name.data(), 32, font, 1.0f);
|
||||
|
||||
while (text_size > 450)
|
||||
{
|
||||
text_size = game::UI_TextWidth(name.data(), 32, font, 1.0f);
|
||||
name.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour lui_open_menu_hook;
|
||||
|
||||
void lui_open_menu_stub(int controllerIndex, const char* menu, int a3, int a4, unsigned int a5)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
console::info("[LUI] %s\n", menu);
|
||||
#endif
|
||||
|
||||
if (!strcmp(menu, "menu_systemlink_join"))
|
||||
{
|
||||
refresh_server_list();
|
||||
}
|
||||
|
||||
lui_open_menu_hook.invoke<void>(controllerIndex, menu, a3, a4, a5);
|
||||
}
|
||||
}
|
||||
|
||||
bool sl_key_event(const int key, const int down)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_MWHEELUP)
|
||||
{
|
||||
return !scroll_up();
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_MWHEELDOWN)
|
||||
{
|
||||
return !scroll_down();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_master_server(game::netadr_s& address)
|
||||
{
|
||||
return game::NET_StringToAdr("135.148.53.121:20810", &address);
|
||||
// return game::NET_StringToAdr("master.xlabs.dev:20810", &address);
|
||||
}
|
||||
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
|
||||
{
|
||||
// Don't show servers that aren't dedicated!
|
||||
const auto dedicated = std::atoi(info.get("dedicated").data());
|
||||
if (!dedicated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show servers that aren't running!
|
||||
const auto sv_running = std::atoi(info.get("sv_running").data());
|
||||
if (!sv_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle servers of the same playmode!
|
||||
const auto playmode = game::CodPlayMode(std::atoi(info.get("playmode").data()));
|
||||
if (game::Com_GetCurrentCoDPlayMode() != playmode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int start_time{};
|
||||
const auto now = game::Sys_Milliseconds();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
const auto entry = master_state.queued_servers.find(address);
|
||||
|
||||
if (entry == master_state.queued_servers.end() || !entry->second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
start_time = entry->second;
|
||||
master_state.queued_servers.erase(entry);
|
||||
}
|
||||
|
||||
server_info server{};
|
||||
server.address = address;
|
||||
server.host_name = info.get("hostname");
|
||||
server.map_name = game::UI_GetMapDisplayName(info.get("mapname").data());
|
||||
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
|
||||
server.play_mode = playmode;
|
||||
server.clients = atoi(info.get("clients").data());
|
||||
server.max_clients = atoi(info.get("sv_maxclients").data());
|
||||
server.bots = atoi(info.get("bots").data());
|
||||
server.ping = std::min(now - start_time, 999);
|
||||
|
||||
server.in_game = 1;
|
||||
|
||||
resize_host_name(server.host_name);
|
||||
|
||||
insert_server(std::move(server));
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp()) return;
|
||||
|
||||
localized_strings::override("PLATFORM_SYSTEM_LINK_TITLE", "SERVER LIST");
|
||||
|
||||
// hook LUI_OpenMenu to refresh server list for system link menu
|
||||
lui_open_menu_hook.create(game::LUI_OpenMenu, lui_open_menu_stub);
|
||||
|
||||
// replace UI_RunMenuScript call in LUI_CoD_LuaCall_RefreshServerList to our refresh_servers
|
||||
utils::hook::call(0x14018A0C9, &refresh_server_list);
|
||||
utils::hook::call(0x14018A5DE, &join_server);
|
||||
utils::hook::nop(0x14018A5FD, 5);
|
||||
|
||||
// do feeder stuff
|
||||
utils::hook::call(0x14018A199, &ui_feeder_count);
|
||||
utils::hook::call(0x14018A3B1, &ui_feeder_item_text);
|
||||
|
||||
scheduler::loop(do_frame_work, scheduler::pipeline::main);
|
||||
|
||||
network::on("getServersResponse", [](const game::netadr_s& target, const std::string_view& data)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> _(mutex);
|
||||
if (!master_state.requesting || master_state.address != target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
master_state.requesting = false;
|
||||
|
||||
std::optional<size_t> start{};
|
||||
for (size_t i = 0; i + 6 < data.size(); ++i)
|
||||
{
|
||||
if (data[i + 6] == '\\')
|
||||
{
|
||||
start.emplace(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!start.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = start.value(); i + 6 < data.size(); i += 7)
|
||||
{
|
||||
if (data[i + 6] != '\\')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
game::netadr_s address{};
|
||||
address.type = game::NA_IP;
|
||||
address.localNetID = game::NS_CLIENT1;
|
||||
memcpy(&address.ip[0], data.data() + i + 0, 4);
|
||||
memcpy(&address.port, data.data() + i + 4, 2);
|
||||
|
||||
master_state.queued_servers[address] = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(server_list::component)
|
12
src/client/component/server_list.hpp
Normal file
12
src/client/component/server_list.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include <utils/info_string.hpp>
|
||||
|
||||
namespace server_list
|
||||
{
|
||||
bool get_master_server(game::netadr_s& address);
|
||||
void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
|
||||
|
||||
bool sl_key_event(int key, int down);
|
||||
}
|
50
src/client/component/shaders.cpp
Normal file
50
src/client/component/shaders.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "dvars.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace shaders
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* disable_shader_caching = nullptr;
|
||||
|
||||
bool shader_should_show_dialog_stub()
|
||||
{
|
||||
return !disable_shader_caching->current.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto has_flag = utils::flags::has_flag("noshadercaching");
|
||||
|
||||
disable_shader_caching = dvars::register_bool("disable_shader_caching", has_flag, game::DVAR_FLAG_SAVED, true);
|
||||
if (has_flag)
|
||||
{
|
||||
dvars::override::set_bool("disable_shader_caching", 1);
|
||||
dvars::override::set_from_string("disable_shader_caching", "1");
|
||||
}
|
||||
|
||||
utils::hook::jump(0x14007E710, shader_should_show_dialog_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(shaders::component)
|
53
src/client/component/slowmotion.cpp
Normal file
53
src/client/component/slowmotion.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace slowmotion
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void scr_cmd_set_slow_motion()
|
||||
{
|
||||
if (game::Scr_GetNumParam() < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int duration = 1000;
|
||||
float end = 1.0f;
|
||||
const float start = game::Scr_GetFloat(0);
|
||||
|
||||
if (game::Scr_GetNumParam() >= 2)
|
||||
{
|
||||
end = game::Scr_GetFloat(1u);
|
||||
}
|
||||
|
||||
if (game::Scr_GetNumParam() >= 3)
|
||||
{
|
||||
duration = static_cast<int>(game::Scr_GetFloat(2u) * 1000.0f);
|
||||
}
|
||||
|
||||
game::SV_SetConfigstring(10, utils::string::va("%i %i %g %g", *game::mp::gameTime, duration, start, end));
|
||||
game::Com_SetSlowMotion(start, end, duration);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::jump(0x140365480, scr_cmd_set_slow_motion); // H1(1.4)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(slowmotion::component)
|
141
src/client/component/splash.cpp
Normal file
141
src/client/component/splash.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "game_module.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace splash
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_start() override
|
||||
{
|
||||
const utils::nt::library self;
|
||||
image_ = LoadImageA(self, MAKEINTRESOURCE(IMAGE_SPLASH), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->show();
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// Disable native splash screen
|
||||
//utils::hook::nop(SELECT_VALUE(0x1403E192E, 0x1405123E2), 5); // winmain doesn't even exist in 1.15? lmao
|
||||
utils::hook::jump(SELECT_VALUE(0, 0x5BE1D0_b), destroy_stub); // H1-STEAM(1.15)
|
||||
utils::hook::jump(SELECT_VALUE(0, 0x5BE210_b), destroy_stub); // H1-STEAM(1.15)
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
this->destroy();
|
||||
|
||||
MSG msg;
|
||||
while (this->window_ && IsWindow(this->window_))
|
||||
{
|
||||
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
|
||||
this->window_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
HWND window_{};
|
||||
HANDLE image_{};
|
||||
|
||||
static void destroy_stub()
|
||||
{
|
||||
component_loader::get<component>()->destroy();
|
||||
}
|
||||
|
||||
void destroy() const
|
||||
{
|
||||
if (this->window_ && IsWindow(this->window_))
|
||||
{
|
||||
ShowWindow(this->window_, SW_HIDE);
|
||||
DestroyWindow(this->window_);
|
||||
UnregisterClassA("H1 Splash Screen", utils::nt::library{});
|
||||
}
|
||||
}
|
||||
|
||||
void show()
|
||||
{
|
||||
WNDCLASSA wnd_class;
|
||||
|
||||
const auto self = game_module::get_host_module();
|
||||
|
||||
wnd_class.style = CS_DROPSHADOW;
|
||||
wnd_class.cbClsExtra = 0;
|
||||
wnd_class.cbWndExtra = 0;
|
||||
wnd_class.lpszMenuName = nullptr;
|
||||
wnd_class.lpfnWndProc = DefWindowProcA;
|
||||
wnd_class.hInstance = self;
|
||||
wnd_class.hIcon = LoadIconA(self, reinterpret_cast<LPCSTR>(102));
|
||||
wnd_class.hCursor = LoadCursorA(nullptr, IDC_APPSTARTING);
|
||||
wnd_class.hbrBackground = reinterpret_cast<HBRUSH>(6);
|
||||
wnd_class.lpszClassName = "H1 Splash Screen";
|
||||
|
||||
if (RegisterClassA(&wnd_class))
|
||||
{
|
||||
const auto x_pixels = GetSystemMetrics(SM_CXFULLSCREEN);
|
||||
const auto y_pixels = GetSystemMetrics(SM_CYFULLSCREEN);
|
||||
|
||||
if (image_)
|
||||
{
|
||||
this->window_ = CreateWindowExA(WS_EX_APPWINDOW, "H1 Splash Screen", "H1",
|
||||
WS_POPUP | WS_SYSMENU,
|
||||
(x_pixels - 320) / 2, (y_pixels - 100) / 2, 320, 100, nullptr,
|
||||
nullptr,
|
||||
self, nullptr);
|
||||
|
||||
if (this->window_)
|
||||
{
|
||||
auto* const image_window = CreateWindowExA(0, "Static", nullptr, WS_CHILD | WS_VISIBLE | 0xEu,
|
||||
0, 0,
|
||||
320, 100, this->window_, nullptr, self, nullptr);
|
||||
if (image_window)
|
||||
{
|
||||
RECT rect;
|
||||
SendMessageA(image_window, 0x172u, 0, reinterpret_cast<LPARAM>(image_));
|
||||
GetWindowRect(image_window, &rect);
|
||||
|
||||
const int width = rect.right - rect.left;
|
||||
rect.left = (x_pixels - width) / 2;
|
||||
|
||||
const int height = rect.bottom - rect.top;
|
||||
rect.top = (y_pixels - height) / 2;
|
||||
|
||||
rect.right = rect.left + width;
|
||||
rect.bottom = rect.top + height;
|
||||
AdjustWindowRect(&rect, WS_CHILD | WS_VISIBLE | 0xEu, 0);
|
||||
SetWindowPos(this->window_, nullptr, rect.left, rect.top, rect.right - rect.left,
|
||||
rect.bottom - rect.top, SWP_NOZORDER);
|
||||
|
||||
ShowWindow(this->window_, SW_SHOW);
|
||||
UpdateWindow(this->window_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(splash::component)
|
@ -51,28 +51,27 @@ namespace system_check
|
||||
{
|
||||
static std::unordered_map<std::string, std::string> mp_zone_hashes =
|
||||
{
|
||||
{"patch_common_mp.ff", "E45EF5F29D12A5A47F405F89FBBEE479C0A90D02141ABF852D481689514134A1"},
|
||||
{"patch_common_mp.ff", "3F44B0CFB0B8E0FBD9687C2942204AB7F11E66E6E15C73B8B4A5EB5920115A31"},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, std::string> sp_zone_hashes =
|
||||
{
|
||||
// Steam doesn't necessarily deliver this file :(
|
||||
{"patch_common.ff", "1D32A9770F90ED022AA76F4859B4AB178E194A703383E61AC2CE83B1E828B18F"},
|
||||
{"patch_common.ff", "BB0617DD94AF2F511571E7184BBEDE76E64D97E5D0DAFDB457F00717F035EBF0"},
|
||||
};
|
||||
|
||||
|
||||
return verify_hashes(mp_zone_hashes) && (game::environment::is_dedi() || verify_hashes(sp_zone_hashes));
|
||||
}
|
||||
|
||||
//void verify_binary_version()
|
||||
//{
|
||||
// if (*(int*)(uint64_t(GetModuleHandle(NULL)) + 0x4CCD3D) != 1251288)
|
||||
// {
|
||||
// MessageBoxA(0, "UNSUPPORTED VERSION MWR(1.15)", "H1MP-STEAM", MB_ICONWARNING);
|
||||
|
||||
// return;
|
||||
// }
|
||||
//}
|
||||
void verify_binary_version()
|
||||
{
|
||||
const auto value = *reinterpret_cast<DWORD*>(0x140001337);
|
||||
if (value != 0xFFB8006D && value != 0xFFB80080)
|
||||
{
|
||||
throw std::runtime_error("Unsupported Call of Duty: Modern Warfare Remastered version(1.4)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_valid()
|
||||
@ -86,12 +85,12 @@ namespace system_check
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
//verify_binary_version();
|
||||
verify_binary_version();
|
||||
|
||||
if (!is_valid())
|
||||
{
|
||||
MessageBoxA(nullptr, "Your game files are outdated or unsupported.\n"
|
||||
"Please get the latest officially supported Call of Duty: Modern Warfare Remastered 1.15 STEAM files, or you will get random crashes and issues.",
|
||||
"Please get the latest officially supported Call of Duty: Modern Warfare Remastered 1.4 files, or you will get random crashes and issues.",
|
||||
"Invalid game files!", MB_ICONINFORMATION);
|
||||
}
|
||||
}
|
||||
|
60
src/client/component/thread_names.cpp
Normal file
60
src/client/component/thread_names.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
namespace thread_names
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void set_thread_names()
|
||||
{
|
||||
static std::unordered_map<int, std::string> thread_names =
|
||||
{
|
||||
{game::THREAD_CONTEXT_MAIN, "Main"},
|
||||
{game::THREAD_CONTEXT_BACKEND, "Backend"}, // Renderer
|
||||
{game::THREAD_CONTEXT_WORKER0, "Worker0"},
|
||||
{game::THREAD_CONTEXT_WORKER1, "Worker1"},
|
||||
{game::THREAD_CONTEXT_WORKER2, "Worker2"},
|
||||
{game::THREAD_CONTEXT_WORKER3, "Worker3"},
|
||||
{game::THREAD_CONTEXT_WORKER4, "Worker4"},
|
||||
{game::THREAD_CONTEXT_WORKER5, "Worker5"},
|
||||
{game::THREAD_CONTEXT_WORKER6, "Worker6"},
|
||||
{game::THREAD_CONTEXT_WORKER7, "Worker7"},
|
||||
{game::THREAD_CONTEXT_SERVER, "Server"},
|
||||
{game::THREAD_CONTEXT_CINEMATIC, "Cinematic"},
|
||||
{game::THREAD_CONTEXT_DATABASE, "Database"},
|
||||
{game::THREAD_CONTEXT_STREAM, "Stream"},
|
||||
{game::THREAD_CONTEXT_SNDSTREAMPACKETCALLBACK, "Snd stream packet callback"},
|
||||
{game::THREAD_CONTEXT_STATS_WRITE, "Stats write"},
|
||||
};
|
||||
|
||||
for (const auto& thread_name : thread_names)
|
||||
{
|
||||
const auto id = game::threadIds[thread_name.first];
|
||||
if (id)
|
||||
{
|
||||
utils::thread::set_name(id, thread_name.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
set_thread_names();
|
||||
scheduler::once(set_thread_names, scheduler::pipeline::main);
|
||||
scheduler::once(set_thread_names, scheduler::pipeline::renderer);
|
||||
scheduler::once(set_thread_names, scheduler::pipeline::server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(thread_names::component)
|
180
src/client/component/ui_scripting.cpp
Normal file
180
src/client/component/ui_scripting.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include "ui_scripting.hpp"
|
||||
|
||||
#include "game/ui_scripting/lua/engine.hpp"
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
#include "game/ui_scripting/lua/error.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<game::hks::cclosure*, sol::protected_function> converted_functions;
|
||||
|
||||
utils::hook::detour hksi_lual_error_hook;
|
||||
utils::hook::detour hksi_lual_error_hook2;
|
||||
utils::hook::detour hks_start_hook;
|
||||
utils::hook::detour hks_shutdown_hook;
|
||||
utils::hook::detour hks_allocator_hook;
|
||||
utils::hook::detour hks_frame_hook;
|
||||
|
||||
bool error_hook_enabled = false;
|
||||
|
||||
void hksi_lual_error_stub(game::hks::lua_State* s, const char* fmt, ...)
|
||||
{
|
||||
char va_buffer[2048] = {0};
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsprintf_s(va_buffer, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
const auto formatted = std::string(va_buffer);
|
||||
|
||||
if (!error_hook_enabled)
|
||||
{
|
||||
return hksi_lual_error_hook.invoke<void>(s, formatted.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(formatted);
|
||||
}
|
||||
}
|
||||
|
||||
void* hks_start_stub(char a1)
|
||||
{
|
||||
const auto _1 = gsl::finally([]()
|
||||
{
|
||||
ui_scripting::lua::engine::start();
|
||||
});
|
||||
|
||||
return hks_start_hook.invoke<void*>(a1);
|
||||
}
|
||||
|
||||
void hks_shutdown_stub()
|
||||
{
|
||||
ui_scripting::lua::engine::stop();
|
||||
hks_shutdown_hook.invoke<void*>();
|
||||
}
|
||||
|
||||
void* hks_allocator_stub(void* userData, void* oldMemory, unsigned __int64 oldSize, unsigned __int64 newSize)
|
||||
{
|
||||
const auto closure = reinterpret_cast<game::hks::cclosure*>(oldMemory);
|
||||
if (converted_functions.find(closure) != converted_functions.end())
|
||||
{
|
||||
converted_functions.erase(closure);
|
||||
}
|
||||
|
||||
return hks_allocator_hook.invoke<void*>(userData, oldMemory, oldSize, newSize);
|
||||
}
|
||||
|
||||
void hks_frame_stub()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
if (state)
|
||||
{
|
||||
ui_scripting::lua::engine::run_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main_function_handler(game::hks::lua_State* state)
|
||||
{
|
||||
const auto value = state->m_apistack.base[-1];
|
||||
if (value.t != game::hks::TCFUNCTION)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto closure = reinterpret_cast<game::hks::cclosure*>(value.v.cClosure);
|
||||
if (converted_functions.find(closure) == converted_functions.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto function = converted_functions[closure];
|
||||
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
|
||||
const auto arguments = get_return_values(count);
|
||||
const auto s = function.lua_state();
|
||||
|
||||
std::vector<sol::lua_value> converted_args;
|
||||
|
||||
for (const auto& argument : arguments)
|
||||
{
|
||||
converted_args.push_back(lua::convert(s, argument));
|
||||
}
|
||||
|
||||
const auto results = function(sol::as_args(converted_args));
|
||||
lua::handle_error(results);
|
||||
|
||||
for (const auto& result : results)
|
||||
{
|
||||
push_value(lua::convert({s, result}));
|
||||
}
|
||||
|
||||
return results.return_count();
|
||||
}
|
||||
|
||||
void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function)
|
||||
{
|
||||
converted_functions[closure] = function;
|
||||
}
|
||||
|
||||
void clear_converted_functions()
|
||||
{
|
||||
converted_functions.clear();
|
||||
}
|
||||
|
||||
void enable_error_hook()
|
||||
{
|
||||
error_hook_enabled = true;
|
||||
}
|
||||
|
||||
void disable_error_hook()
|
||||
{
|
||||
error_hook_enabled = false;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hks_start_hook.create(SELECT_VALUE(0x1400E4B40, 0x140176A40), hks_start_stub);
|
||||
hks_shutdown_hook.create(SELECT_VALUE(0x1400DD3D0, 0x14016CA80), hks_shutdown_stub);
|
||||
hksi_lual_error_hook.create(SELECT_VALUE(0x1400A5EA0, 0x14012F300), hksi_lual_error_stub);
|
||||
hks_allocator_hook.create(SELECT_VALUE(0x14009B570, 0x14012BAC0), hks_allocator_stub);
|
||||
hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), hks_frame_stub);
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
hksi_lual_error_hook2.create(0x1401366B0, hksi_lual_error_stub);
|
||||
}
|
||||
|
||||
command::add("lui_restart", []()
|
||||
{
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x1400DD3D0, 0x14016CA80));
|
||||
utils::hook::invoke<void>(SELECT_VALUE(0x1400E6170, 0x1401780D0));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(ui_scripting::component)
|
12
src/client/component/ui_scripting.hpp
Normal file
12
src/client/component/ui_scripting.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "game/ui_scripting/lua/value_conversion.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
int main_function_handler(game::hks::lua_State* state);
|
||||
void add_converted_function(game::hks::cclosure* closure, const sol::protected_function& function);
|
||||
void clear_converted_functions();
|
||||
|
||||
void enable_error_hook();
|
||||
void disable_error_hook();
|
||||
}
|
474
src/client/component/updater.cpp
Normal file
474
src/client/component/updater.cpp
Normal file
@ -0,0 +1,474 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "dvars.hpp"
|
||||
#include "updater.hpp"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/http.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#define MASTER "https://master.fed0001.xyz/h1-mod/"
|
||||
|
||||
#define FILES_PATH "files.json"
|
||||
#define FILES_PATH_DEV "files-dev.json"
|
||||
|
||||
#define DATA_PATH "data/"
|
||||
#define DATA_PATH_DEV "data-dev/"
|
||||
|
||||
#define ERR_UPDATE_CHECK_FAIL "Failed to check for updates"
|
||||
#define ERR_DOWNLOAD_FAIL "Failed to download file "
|
||||
#define ERR_WRITE_FAIL "Failed to write file "
|
||||
|
||||
#define BINARY_NAME "h1-mod.exe"
|
||||
|
||||
namespace updater
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* cl_auto_update;
|
||||
bool has_tried_update = false;
|
||||
|
||||
struct status
|
||||
{
|
||||
bool done;
|
||||
bool success;
|
||||
};
|
||||
|
||||
struct file_data
|
||||
{
|
||||
std::string name;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
struct update_data_t
|
||||
{
|
||||
bool restart_required{};
|
||||
bool cancelled{};
|
||||
status check{};
|
||||
status download{};
|
||||
std::string error{};
|
||||
std::string current_file{};
|
||||
std::vector<std::string> required_files{};
|
||||
};
|
||||
|
||||
utils::concurrency::container<update_data_t> update_data;
|
||||
|
||||
std::string get_branch()
|
||||
{
|
||||
return GIT_BRANCH;
|
||||
}
|
||||
|
||||
std::string select(const std::string& main, const std::string& develop)
|
||||
{
|
||||
if (get_branch() == "develop")
|
||||
{
|
||||
return develop;
|
||||
}
|
||||
|
||||
return main;
|
||||
}
|
||||
|
||||
std::string get_data_path()
|
||||
{
|
||||
if (get_branch() == "develop")
|
||||
{
|
||||
return DATA_PATH_DEV;
|
||||
}
|
||||
|
||||
return DATA_PATH;
|
||||
}
|
||||
|
||||
void set_update_check_status(bool done, bool success, const std::string& error = {})
|
||||
{
|
||||
update_data.access([done, success, error](update_data_t& data_)
|
||||
{
|
||||
data_.check.done = done;
|
||||
data_.check.success = success;
|
||||
data_.error = error;
|
||||
});
|
||||
}
|
||||
|
||||
void set_update_download_status(bool done, bool success, const std::string& error = {})
|
||||
{
|
||||
update_data.access([done, success, error](update_data_t& data_)
|
||||
{
|
||||
data_.download.done = done;
|
||||
data_.download.success = success;
|
||||
data_.error = error;
|
||||
});
|
||||
}
|
||||
|
||||
bool check_file(const std::string& name, const std::string& sha)
|
||||
{
|
||||
std::string data;
|
||||
if (!utils::io::read_file(name, &data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (utils::cryptography::sha1::compute(data, true) != sha)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string load_binary_name()
|
||||
{
|
||||
// utils::nt::library self;
|
||||
// return self.get_name();
|
||||
// returns the game's name and not the client's
|
||||
|
||||
return BINARY_NAME;
|
||||
}
|
||||
|
||||
std::string get_binary_name()
|
||||
{
|
||||
static const auto name = load_binary_name();
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string get_time_str()
|
||||
{
|
||||
return utils::string::va("%i", uint32_t(time(nullptr)));
|
||||
}
|
||||
|
||||
std::optional<std::string> download_file(const std::string& name)
|
||||
{
|
||||
return utils::http::get_data(MASTER + select(DATA_PATH, DATA_PATH_DEV) + name + "?" + get_time_str());
|
||||
}
|
||||
|
||||
bool is_update_cancelled()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.cancelled;
|
||||
});
|
||||
}
|
||||
|
||||
bool write_file(const std::string& name, const std::string& data)
|
||||
{
|
||||
if (get_binary_name() == name &&
|
||||
utils::io::file_exists(name) &&
|
||||
!utils::io::move_file(name, name + ".old"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
return utils::io::write_file("update_test/" + name, data);
|
||||
#else
|
||||
return utils::io::write_file(name, data);
|
||||
#endif
|
||||
}
|
||||
|
||||
void delete_old_file()
|
||||
{
|
||||
utils::io::remove_file(get_binary_name() + ".old");
|
||||
}
|
||||
|
||||
void reset_data()
|
||||
{
|
||||
update_data.access([](update_data_t& data_)
|
||||
{
|
||||
data_ = {};
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_mode_flag()
|
||||
{
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
return "-multiplayer";
|
||||
}
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return "-singleplayer";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// workaround
|
||||
void relaunch()
|
||||
{
|
||||
if (!utils::io::file_exists(BINARY_NAME))
|
||||
{
|
||||
utils::nt::terminate(0);
|
||||
return;
|
||||
}
|
||||
|
||||
STARTUPINFOA startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
ZeroMemory(&process_info, sizeof(process_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
|
||||
char current_dir[MAX_PATH];
|
||||
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
|
||||
|
||||
char buf[1024] = {0};
|
||||
const auto command_line = utils::string::va("%s %s", GetCommandLineA(), get_mode_flag().data());
|
||||
strcpy_s(buf, 1024, command_line);
|
||||
|
||||
CreateProcess(BINARY_NAME, buf, nullptr, nullptr, false, NULL, nullptr, current_dir,
|
||||
&startup_info, &process_info);
|
||||
|
||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread);
|
||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE) CloseHandle(process_info.hProcess);
|
||||
|
||||
utils::nt::terminate(0);
|
||||
}
|
||||
|
||||
void set_has_tried_update(bool tried)
|
||||
{
|
||||
has_tried_update = tried;
|
||||
}
|
||||
|
||||
bool get_has_tried_update()
|
||||
{
|
||||
return has_tried_update;
|
||||
}
|
||||
|
||||
bool auto_updates_enabled()
|
||||
{
|
||||
return cl_auto_update->current.enabled;
|
||||
}
|
||||
|
||||
bool is_update_check_done()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.check.done;
|
||||
});
|
||||
}
|
||||
|
||||
bool is_update_download_done()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.download.done;
|
||||
});
|
||||
}
|
||||
|
||||
bool get_update_check_status()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.check.success;
|
||||
});
|
||||
}
|
||||
|
||||
bool get_update_download_status()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.download.success;
|
||||
});
|
||||
}
|
||||
|
||||
bool is_update_available()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.required_files.size() > 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool is_restart_required()
|
||||
{
|
||||
return update_data.access<bool>([](update_data_t& data_)
|
||||
{
|
||||
return data_.restart_required;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_last_error()
|
||||
{
|
||||
return update_data.access<std::string>([](update_data_t& data_)
|
||||
{
|
||||
return data_.error;
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_current_file()
|
||||
{
|
||||
return update_data.access<std::string>([](update_data_t& data_)
|
||||
{
|
||||
return data_.current_file;
|
||||
});
|
||||
}
|
||||
|
||||
void cancel_update()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] Cancelling update\n");
|
||||
#endif
|
||||
|
||||
return update_data.access([](update_data_t& data_)
|
||||
{
|
||||
data_.cancelled = true;
|
||||
});
|
||||
}
|
||||
|
||||
void start_update_check()
|
||||
{
|
||||
cancel_update();
|
||||
reset_data();
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] starting update check\n");
|
||||
#endif
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
const auto files_data = utils::http::get_data(MASTER + select(FILES_PATH, FILES_PATH_DEV) + "?" + get_time_str());
|
||||
|
||||
if (is_update_cancelled())
|
||||
{
|
||||
reset_data();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!files_data.has_value())
|
||||
{
|
||||
set_update_check_status(true, false, ERR_UPDATE_CHECK_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document j;
|
||||
j.Parse(files_data.value().data());
|
||||
|
||||
if (!j.IsArray())
|
||||
{
|
||||
set_update_check_status(true, false, ERR_UPDATE_CHECK_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> required_files;
|
||||
|
||||
const auto files = j.GetArray();
|
||||
for (const auto& file : files)
|
||||
{
|
||||
if (!file.IsArray() || file.Size() != 3 || !file[0].IsString() || !file[2].IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto name = file[0].GetString();
|
||||
const auto sha = file[2].GetString();
|
||||
|
||||
if (!check_file(name, sha))
|
||||
{
|
||||
if (get_binary_name() == name)
|
||||
{
|
||||
update_data.access([](update_data_t& data_)
|
||||
{
|
||||
data_.restart_required = true;
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] need file %s\n", name);
|
||||
#endif
|
||||
|
||||
required_files.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
update_data.access([&required_files](update_data_t& data_)
|
||||
{
|
||||
data_.check.done = true;
|
||||
data_.check.success = true;
|
||||
data_.required_files = required_files;
|
||||
});
|
||||
}, scheduler::pipeline::async);
|
||||
}
|
||||
|
||||
void start_update_download()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] starting update download\n");
|
||||
#endif
|
||||
|
||||
if (!is_update_check_done() || !get_update_check_status() || is_update_cancelled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler::once([]()
|
||||
{
|
||||
const auto required_files = update_data.access<std::vector<std::string>>([](update_data_t& data_)
|
||||
{
|
||||
return data_.required_files;
|
||||
});
|
||||
|
||||
std::vector<file_data> downloads;
|
||||
|
||||
for (const auto& file : required_files)
|
||||
{
|
||||
update_data.access([file](update_data_t& data_)
|
||||
{
|
||||
data_.current_file = file;
|
||||
});
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[Updater] downloading file %s\n", file.data());
|
||||
#endif
|
||||
|
||||
const auto data = download_file(file);
|
||||
|
||||
if (is_update_cancelled())
|
||||
{
|
||||
reset_data();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.has_value())
|
||||
{
|
||||
set_update_download_status(true, false, ERR_DOWNLOAD_FAIL + file);
|
||||
return;
|
||||
}
|
||||
|
||||
downloads.push_back({file, data.value()});
|
||||
}
|
||||
|
||||
for (const auto& download : downloads)
|
||||
{
|
||||
if (!write_file(download.name, download.data))
|
||||
{
|
||||
set_update_download_status(true, false, ERR_WRITE_FAIL + download.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
set_update_download_status(true, true);
|
||||
}, scheduler::pipeline::async);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
delete_old_file();
|
||||
cl_auto_update = dvars::register_bool("cg_auto_update", true, game::DVAR_FLAG_SAVED, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(updater::component)
|
26
src/client/component/updater.hpp
Normal file
26
src/client/component/updater.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
namespace updater
|
||||
{
|
||||
void relaunch();
|
||||
|
||||
void set_has_tried_update(bool tried);
|
||||
bool get_has_tried_update();
|
||||
bool auto_updates_enabled();
|
||||
|
||||
bool is_update_available();
|
||||
bool is_update_check_done();
|
||||
bool get_update_check_status();
|
||||
|
||||
bool is_update_download_done();
|
||||
bool get_update_download_status();
|
||||
|
||||
bool is_restart_required();
|
||||
|
||||
std::string get_last_error();
|
||||
std::string get_current_file();
|
||||
|
||||
void start_update_check();
|
||||
void start_update_download();
|
||||
void cancel_update();
|
||||
}
|
55
src/client/component/videos.cpp
Normal file
55
src/client/component/videos.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace videos
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour playvid_hook;
|
||||
std::unordered_map<std::string, std::string> video_replaces;
|
||||
|
||||
void playvid(const char* name, const int a2, const int a3)
|
||||
{
|
||||
const auto vid = video_replaces.find(name);
|
||||
if (vid != video_replaces.end())
|
||||
{
|
||||
char path[256];
|
||||
game::Sys_BuildAbsPath(path, sizeof(path), game::SF_VIDEO, vid->second.data(), ".bik");
|
||||
|
||||
if (game::Sys_FileExists(path))
|
||||
{
|
||||
name = vid->second.data();
|
||||
}
|
||||
}
|
||||
|
||||
return playvid_hook.invoke<void>(name, a2, a3);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
playvid_hook.create(SELECT_VALUE(0x1404A9D00, 0x1405B0AF0), &playvid); // H1(1.4)
|
||||
|
||||
if (game::environment::is_mp())
|
||||
{
|
||||
video_replaces["menus_bg_comp2"] = "menus_bg_h1mod";
|
||||
video_replaces["mp_menus_bg_options"] = "menus_bg_h1mod_blur";
|
||||
}
|
||||
else if (game::environment::is_sp())
|
||||
{
|
||||
video_replaces["sp_menus_bg_main_menu"] = "menus_bg_h1mod_sp";
|
||||
video_replaces["sp_menus_bg_campaign"] = "menus_bg_h1mod_sp";
|
||||
video_replaces["sp_menus_bg_options"] = "menus_bg_h1mod_sp";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(videos::component)
|
63
src/client/component/virtuallobby.cpp
Normal file
63
src/client/component/virtuallobby.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace virtuallobby
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_t* virtualLobby_fovscale;
|
||||
|
||||
const auto get_fovscale_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto ret = a.newLabel();
|
||||
const auto original = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rax, qword_ptr(0x1425F7210)); // virtualLobbyInFiringRange
|
||||
a.cmp(byte_ptr(rax, 0x10), 1);
|
||||
a.je(original);
|
||||
a.call_aligned(game::VirtualLobby_Loaded);
|
||||
a.cmp(al, 0);
|
||||
a.je(original);
|
||||
|
||||
// virtuallobby
|
||||
a.popad64();
|
||||
a.mov(rax, ptr(reinterpret_cast<int64_t>(&virtualLobby_fovscale)));
|
||||
a.jmp(ret);
|
||||
|
||||
// original
|
||||
a.bind(original);
|
||||
a.popad64();
|
||||
a.mov(rax, qword_ptr(0x1413A8580));
|
||||
a.jmp(ret);
|
||||
|
||||
a.bind(ret);
|
||||
a.mov(rcx, 0x142935000);
|
||||
a.jmp(0x1400B556A);
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
virtualLobby_fovscale = dvars::register_float("virtualLobby_fovScale", 0.7f, 0.0f, 2.0f, game::DVAR_FLAG_SAVED);
|
||||
|
||||
utils::hook::nop(0x1400B555C, 14);
|
||||
utils::hook::jump(0x1400B555C, get_fovscale_stub, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(virtuallobby::component)
|
@ -31,7 +31,7 @@ FARPROC loader::load(const utils::nt::library& library, const std::string& buffe
|
||||
return FARPROC(library.get_ptr() + source.get_relative_entry_point());
|
||||
}
|
||||
|
||||
FARPROC loader::load_library(const std::string& filename, uint64_t* base_address) const
|
||||
FARPROC loader::load_library(const std::string& filename) const
|
||||
{
|
||||
const auto target = utils::nt::library::load(filename);
|
||||
if (!target)
|
||||
@ -40,7 +40,10 @@ FARPROC loader::load_library(const std::string& filename, uint64_t* base_address
|
||||
}
|
||||
|
||||
const auto base = size_t(target.get_ptr());
|
||||
*base_address = base;
|
||||
if(base != 0x140000000)
|
||||
{
|
||||
throw std::runtime_error{utils::string::va("Binary was mapped at 0x%llX (instead of 0x%llX). Something is severely broken :(", base, 0x140000000)};
|
||||
}
|
||||
|
||||
this->load_imports(target, target);
|
||||
this->load_tls(target, target);
|
||||
|
@ -5,7 +5,7 @@ class loader final
|
||||
{
|
||||
public:
|
||||
FARPROC load(const utils::nt::library& library, const std::string& buffer) const;
|
||||
FARPROC load_library(const std::string& filename, uint64_t* base_address) const;
|
||||
FARPROC load_library(const std::string& filename) const;
|
||||
|
||||
void set_import_resolver(const std::function<void*(const std::string&, const std::string&)>& resolver);
|
||||
|
||||
|
164
src/client/resources/ui_scripts/common.lua
Normal file
164
src/client/resources/ui_scripts/common.lua
Normal file
@ -0,0 +1,164 @@
|
||||
menucallbacks = {}
|
||||
originalmenus = {}
|
||||
stack = {}
|
||||
|
||||
LUI.MenuBuilder.m_types_build["generic_waiting_popup_"] = function (menu, event)
|
||||
local oncancel = stack.oncancel
|
||||
local popup = LUI.MenuBuilder.BuildRegisteredType("waiting_popup", {
|
||||
message_text = stack.text,
|
||||
isLiveWithCancel = true,
|
||||
cancel_func = function(...)
|
||||
local args = {...}
|
||||
oncancel()
|
||||
LUI.FlowManager.RequestLeaveMenu(args[1])
|
||||
end
|
||||
})
|
||||
|
||||
local listchildren = popup:getChildById("LUIHorizontalList"):getchildren()
|
||||
local children = listchildren[2]:getchildren()
|
||||
popup.text = children[2]
|
||||
|
||||
stack = {
|
||||
ret = popup
|
||||
}
|
||||
|
||||
return popup
|
||||
end
|
||||
|
||||
LUI.MenuBuilder.m_types_build["generic_yes_no_popup_"] = function()
|
||||
local callback = stack.callback
|
||||
local popup = LUI.MenuBuilder.BuildRegisteredType("generic_yesno_popup", {
|
||||
popup_title = stack.title,
|
||||
message_text = stack.text,
|
||||
yes_action = function()
|
||||
callback(true)
|
||||
end,
|
||||
no_action = function()
|
||||
callback(false)
|
||||
end
|
||||
})
|
||||
|
||||
stack = {
|
||||
ret = popup
|
||||
}
|
||||
|
||||
return popup
|
||||
end
|
||||
|
||||
LUI.MenuBuilder.m_types_build["generic_confirmation_popup_"] = function()
|
||||
local popup = LUI.MenuBuilder.BuildRegisteredType( "generic_confirmation_popup", {
|
||||
cancel_will_close = false,
|
||||
popup_title = stack.title,
|
||||
message_text = stack.text,
|
||||
button_text = stack.buttontext,
|
||||
confirmation_action = stack.callback
|
||||
})
|
||||
|
||||
stack = {
|
||||
ret = popup
|
||||
}
|
||||
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
LUI.onmenuopen = function(name, callback)
|
||||
if (not LUI.MenuBuilder.m_types_build[name]) then
|
||||
return
|
||||
end
|
||||
|
||||
if (not menucallbacks[name]) then
|
||||
menucallbacks[name] = {}
|
||||
end
|
||||
|
||||
table.insert(menucallbacks[name], callback)
|
||||
|
||||
if (not originalmenus[name]) then
|
||||
originalmenus[name] = LUI.MenuBuilder.m_types_build[name]
|
||||
LUI.MenuBuilder.m_types_build[name] = function(...)
|
||||
local args = {...}
|
||||
local menu = originalmenus[name](table.unpack(args))
|
||||
|
||||
for k, v in luiglobals.next, menucallbacks[name] do
|
||||
v(menu, table.unpack(args))
|
||||
end
|
||||
|
||||
return menu
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local addoptionstextinfo = LUI.Options.AddOptionTextInfo
|
||||
LUI.Options.AddOptionTextInfo = function(menu)
|
||||
local result = addoptionstextinfo(menu)
|
||||
menu.optionTextInfo = result
|
||||
return result
|
||||
end
|
||||
|
||||
LUI.addmenubutton = function(name, data)
|
||||
LUI.onmenuopen(name, function(menu)
|
||||
if (not menu.list) then
|
||||
return
|
||||
end
|
||||
|
||||
local button = menu:AddButton(data.text, data.callback, nil, true, nil, {
|
||||
desc_text = data.description
|
||||
})
|
||||
|
||||
local buttonlist = menu:getChildById(menu.type .. "_list")
|
||||
|
||||
if (data.id) then
|
||||
button.id = data.id
|
||||
end
|
||||
|
||||
if (data.index) then
|
||||
buttonlist:removeElement(button)
|
||||
buttonlist:insertElement(button, data.index)
|
||||
end
|
||||
|
||||
local hintbox = menu.optionTextInfo
|
||||
menu:removeElement(hintbox)
|
||||
|
||||
LUI.Options.InitScrollingList(menu.list, nil)
|
||||
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
|
||||
end)
|
||||
end
|
||||
|
||||
LUI.openmenu = function(menu, args)
|
||||
stack = args
|
||||
LUI.FlowManager.RequestAddMenu(nil, menu)
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
LUI.openpopupmenu = function(menu, args)
|
||||
stack = args
|
||||
LUI.FlowManager.RequestPopupMenu(nil, menu)
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
LUI.yesnopopup = function(data)
|
||||
for k, v in luiglobals.next, data do
|
||||
stack[k] = v
|
||||
end
|
||||
LUI.FlowManager.RequestPopupMenu(nil, "generic_yes_no_popup_")
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
LUI.confirmationpopup = function(data)
|
||||
for k, v in luiglobals.next, data do
|
||||
stack[k] = v
|
||||
end
|
||||
LUI.FlowManager.RequestPopupMenu(nil, "generic_confirmation_popup_")
|
||||
return stack.ret
|
||||
end
|
||||
|
||||
function userdata_:getchildren()
|
||||
local children = {}
|
||||
local first = self:getFirstChild()
|
||||
|
||||
while (first) do
|
||||
table.insert(children, first)
|
||||
first = first:getNextSibling()
|
||||
end
|
||||
|
||||
return children
|
||||
end
|
164
src/client/resources/ui_scripts/updater.lua
Normal file
164
src/client/resources/ui_scripts/updater.lua
Normal file
@ -0,0 +1,164 @@
|
||||
updatecancelled = false
|
||||
taskinterval = 100
|
||||
|
||||
updater.cancelupdate()
|
||||
|
||||
function startupdatecheck(popup, autoclose)
|
||||
updatecancelled = false
|
||||
|
||||
local callback = function()
|
||||
if (not updater.getupdatecheckstatus()) then
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("Error: " .. updater.getlasterror())
|
||||
return
|
||||
end
|
||||
|
||||
if (not updater.isupdateavailable()) then
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("No updates available")
|
||||
return
|
||||
end
|
||||
|
||||
LUI.yesnopopup({
|
||||
title = "NOTICE",
|
||||
text = "An update is available, proceed with installation?",
|
||||
callback = function(result)
|
||||
if (result) then
|
||||
startupdatedownload(popup, autoclose)
|
||||
else
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
updater.startupdatecheck()
|
||||
createtask({
|
||||
done = updater.isupdatecheckdone,
|
||||
cancelled = isupdatecancelled,
|
||||
callback = callback,
|
||||
interval = taskinterval
|
||||
})
|
||||
end
|
||||
|
||||
function startupdatedownload(popup, autoclose)
|
||||
updater.startupdatedownload()
|
||||
|
||||
local textupdate = nil
|
||||
local previousfile = nil
|
||||
textupdate = game:oninterval(function()
|
||||
local file = updater.getcurrentfile()
|
||||
if (file == previousfile) then
|
||||
return
|
||||
end
|
||||
|
||||
file = previousfile
|
||||
popup.text:setText("Downloading file " .. updater.getcurrentfile() .. "...")
|
||||
end, 10)
|
||||
|
||||
local callback = function()
|
||||
textupdate:clear()
|
||||
|
||||
if (not updater.getupdatedownloadstatus()) then
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("Error: " .. updater.getlasterror())
|
||||
return
|
||||
end
|
||||
|
||||
popup.text:setText("Update successful")
|
||||
|
||||
if (updater.isrestartrequired()) then
|
||||
LUI.confirmationpopup({
|
||||
title = "RESTART REQUIRED",
|
||||
text = "Update requires restart",
|
||||
buttontext = "RESTART",
|
||||
callback = function()
|
||||
updater.relaunch()
|
||||
end
|
||||
})
|
||||
else
|
||||
if (LUI.mp_menus) then
|
||||
Engine.Exec("lui_restart; lui_open mp_main_menu")
|
||||
else
|
||||
Engine.Exec("lui_restart")
|
||||
end
|
||||
end
|
||||
|
||||
if (autoclose) then
|
||||
LUI.FlowManager.RequestLeaveMenu(popup)
|
||||
end
|
||||
end
|
||||
|
||||
createtask({
|
||||
done = updater.isupdatedownloaddone,
|
||||
cancelled = isupdatecancelled,
|
||||
callback = callback,
|
||||
interval = taskinterval
|
||||
})
|
||||
end
|
||||
|
||||
function updaterpopup(oncancel)
|
||||
return LUI.openpopupmenu("generic_waiting_popup_", {
|
||||
oncancel = oncancel,
|
||||
withcancel = true,
|
||||
text = "Checking for updates..."
|
||||
})
|
||||
end
|
||||
|
||||
function createtask(data)
|
||||
local interval = nil
|
||||
interval = game:oninterval(function()
|
||||
if (data.cancelled()) then
|
||||
interval:clear()
|
||||
return
|
||||
end
|
||||
|
||||
if (data.done()) then
|
||||
interval:clear()
|
||||
data.callback()
|
||||
end
|
||||
end, data.interval)
|
||||
return interval
|
||||
end
|
||||
|
||||
function isupdatecancelled()
|
||||
return updatecancelled
|
||||
end
|
||||
|
||||
function tryupdate(autoclose)
|
||||
updatecancelled = false
|
||||
local popup = updaterpopup(function()
|
||||
updater.cancelupdate()
|
||||
updatecancelled = true
|
||||
end)
|
||||
|
||||
startupdatecheck(popup, autoclose)
|
||||
end
|
||||
|
||||
function tryautoupdate()
|
||||
if (not updater.autoupdatesenabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (not updater.gethastriedupdate()) then
|
||||
game:ontimeout(function()
|
||||
updater.sethastriedupdate(true)
|
||||
tryupdate(true)
|
||||
end, 100)
|
||||
end
|
||||
end
|
||||
|
||||
LUI.onmenuopen("mp_main_menu", tryautoupdate)
|
||||
LUI.onmenuopen("main_lockout", tryautoupdate)
|
Loading…
Reference in New Issue
Block a user