developer console

+ added in-game developer console
+ added dvar definitions and helping class
+ list of known game dvars can be found inside variables.cpp
+ adjusted logger component
+ re-enabled com_frame hook
+ some minor code formatting
This commit is contained in:
project-bo4 2023-09-06 05:08:38 -07:00
parent 8b85399de5
commit 2358dcf30b
24 changed files with 8501 additions and 325 deletions

View File

@ -5,10 +5,10 @@
#include <utils/string.hpp>
namespace integrity
namespace arxan
{
namespace
{
{
const std::vector<std::pair<uint8_t*, size_t>>& get_text_sections()
{
static const std::vector<std::pair<uint8_t*, size_t>> text = []
@ -88,7 +88,7 @@ namespace integrity
}
uint32_t adjust_integrity_checksum(const uint64_t return_address, uint8_t* stack_frame,
const uint32_t current_checksum)
const uint32_t current_checksum)
{
const auto handler_address = reverse_b(return_address - 5);
const auto* context = search_handler_context(stack_frame, current_checksum);
@ -96,7 +96,7 @@ namespace integrity
if (!context)
{
MessageBoxA(nullptr, utils::string::va("No frame offset for: %llX", handler_address), "Error",
MB_ICONERROR);
MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), 0xBAD);
return current_checksum;
}
@ -107,8 +107,8 @@ namespace integrity
if (current_checksum != correct_checksum)
{
#ifndef NDEBUG
/*printf("Adjusting checksum (%llX): %X -> %X\n", handler_address,
current_checksum, correct_checksum);*/
/*logger::write(logger::LOG_TYPE_DEBUG, "Adjusting checksum (%llX): %X -> %X\n", handler_address,
current_checksum, correct_checksum);*/
#endif
}
@ -130,38 +130,38 @@ namespace integrity
const auto other_frame_offset = static_cast<uint8_t>(next_inst >> 16);
static const auto stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.push(rax);
{
a.push(rax);
a.mov(rax, qword_ptr(rsp, 8));
a.sub(rax, 2); // Skip the push we inserted
a.mov(rax, qword_ptr(rsp, 8));
a.sub(rax, 2); // Skip the push we inserted
a.push(rax);
a.pushad64();
a.push(rax);
a.pushad64();
a.mov(r8, qword_ptr(rsp, 0x88));
a.mov(rcx, rax);
a.mov(rdx, rbp);
a.call_aligned(adjust_integrity_checksum);
a.mov(r8, qword_ptr(rsp, 0x88));
a.mov(rcx, rax);
a.mov(rdx, rbp);
a.call_aligned(adjust_integrity_checksum);
a.mov(qword_ptr(rsp, 0x80), rax);
a.mov(qword_ptr(rsp, 0x80), rax);
a.popad64();
a.pop(rax);
a.popad64();
a.pop(rax);
a.add(rsp, 8);
a.add(rsp, 8);
a.mov(dword_ptr(rdx, rcx, 4), eax);
a.mov(dword_ptr(rdx, rcx, 4), eax);
a.pop(rax); // return addr
a.xchg(rax, qword_ptr(rsp)); // switch with push
a.pop(rax); // return addr
a.xchg(rax, qword_ptr(rsp)); // switch with push
a.add(dword_ptr(rbp, rax), 0xFFFFFFFF);
a.add(dword_ptr(rbp, rax), 0xFFFFFFFF);
a.mov(rax, dword_ptr(rdx, rcx, 4)); // restore rax
a.mov(rax, dword_ptr(rdx, rcx, 4)); // restore rax
a.ret();
});
a.ret();
});
// push other_frame_offset
utils::hook::set<uint16_t>(game_address, static_cast<uint16_t>(0x6A | (other_frame_offset << 8)));
@ -182,32 +182,32 @@ namespace integrity
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(next_inst_addr + 1));
const auto stub = utils::hook::assemble([jump_target](utils::hook::assembler& a)
{
a.push(rax);
{
a.push(rax);
a.mov(rax, qword_ptr(rsp, 8));
a.push(rax);
a.mov(rax, qword_ptr(rsp, 8));
a.push(rax);
a.pushad64();
a.pushad64();
a.mov(r8, qword_ptr(rsp, 0x88));
a.mov(rcx, rax);
a.mov(rdx, rbp);
a.call_aligned(adjust_integrity_checksum);
a.mov(r8, qword_ptr(rsp, 0x88));
a.mov(rcx, rax);
a.mov(rdx, rbp);
a.call_aligned(adjust_integrity_checksum);
a.mov(qword_ptr(rsp, 0x80), rax);
a.mov(qword_ptr(rsp, 0x80), rax);
a.popad64();
a.pop(rax);
a.popad64();
a.pop(rax);
a.add(rsp, 8);
a.add(rsp, 8);
a.mov(dword_ptr(rdx, rcx, 4), eax);
a.mov(dword_ptr(rdx, rcx, 4), eax);
a.add(rsp, 8);
a.add(rsp, 8);
a.jmp(jump_target);
});
a.jmp(jump_target);
});
utils::hook::call(game_address, stub);
}
@ -303,8 +303,160 @@ namespace integrity
return get_thread_context_hook.invoke<BOOL>(thread_handle, context);
}
utils::hook::detour create_mutex_ex_a_hook;
HANDLE create_mutex_ex_a_stub(const LPSECURITY_ATTRIBUTES attributes, const LPCSTR name, const DWORD flags,
const DWORD access)
{
if (name == "$ IDA trusted_idbs"s || name == "$ IDA registry mutex $"s)
{
return nullptr;
}
return create_mutex_ex_a_hook.invoke<HANDLE>(attributes, name, flags, access);
}
bool remove_evil_keywords_from_string(const UNICODE_STRING& string)
{
static const std::wstring evil_keywords[] =
{
L"IDA",
L"ida",
L"HxD",
L"cheatengine",
L"Cheat Engine",
L"x96dbg",
L"x32dbg",
L"x64dbg",
L"Wireshark",
};
if (!string.Buffer || !string.Length)
{
return false;
}
const std::wstring_view path(string.Buffer, string.Length / sizeof(string.Buffer[0]));
bool modified = false;
for (const auto& keyword : evil_keywords)
{
while (true)
{
const auto pos = path.find(keyword);
if (pos == std::wstring::npos)
{
break;
}
modified = true;
for (size_t i = 0; i < keyword.size(); ++i)
{
string.Buffer[pos + i] = L'a';
}
}
}
return modified;
}
bool remove_evil_keywords_from_string(wchar_t* str, const size_t length)
{
UNICODE_STRING unicode_string{};
unicode_string.Buffer = str;
unicode_string.Length = static_cast<uint16_t>(length);
unicode_string.MaximumLength = unicode_string.Length;
return remove_evil_keywords_from_string(unicode_string);
}
bool remove_evil_keywords_from_string(char* str, const size_t length)
{
std::string_view str_view(str, length);
std::wstring wstr(str_view.begin(), str_view.end());
if (!remove_evil_keywords_from_string(wstr.data(), wstr.size()))
{
return false;
}
const std::string regular_str(wstr.begin(), wstr.end());
memcpy(str, regular_str.data(), length);
return true;
}
int WINAPI get_window_text_a_stub(const HWND wnd, const LPSTR str, const int max_count)
{
std::wstring wstr{};
wstr.resize(max_count);
const auto res = GetWindowTextW(wnd, wstr.data(), max_count);
if (res)
{
remove_evil_keywords_from_string(wstr.data(), res);
const std::string regular_str(wstr.begin(), wstr.end());
memset(str, 0, max_count);
memcpy(str, regular_str.data(), res);
}
return res;
}
utils::hook::detour nt_query_system_information_hook;
NTSTATUS NTAPI nt_query_system_information_stub(const SYSTEM_INFORMATION_CLASS system_information_class,
const PVOID system_information,
const ULONG system_information_length,
const PULONG return_length)
{
const auto status = nt_query_system_information_hook.invoke<NTSTATUS>(
system_information_class, system_information, system_information_length, return_length);
if (NT_SUCCESS(status))
{
if (system_information_class == SystemProcessInformation && !utils::nt::is_shutdown_in_progress())
{
auto addr = static_cast<uint8_t*>(system_information);
while (true)
{
const auto info = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(addr);
remove_evil_keywords_from_string(info->ImageName);
if (!info->NextEntryOffset)
{
break;
}
addr = addr + info->NextEntryOffset;
}
}
}
return status;
}
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)
{
NTSTATUS status = nt_query_information_process_hook.invoke<NTSTATUS>(handle, info_class, info, info_length,
ret_length);
if (NT_SUCCESS(status))
{
if (info_class == ProcessImageFileName || static_cast<int>(info_class) == 43 /*ProcessImageFileNameWin32*/)
{
remove_evil_keywords_from_string(*static_cast<UNICODE_STRING*>(info));
}
}
return status;
}
}
class component final : public component_interface
{
public:
@ -316,6 +468,22 @@ namespace integrity
auto* get_thread_context_func = utils::nt::library("kernelbase.dll").get_proc<void*>("GetThreadContext");
get_thread_context_hook.create(get_thread_context_func, get_thread_context_stub);
create_mutex_ex_a_hook.create(CreateMutexExA, create_mutex_ex_a_stub);
utils::hook::copy(this->window_text_buffer_, GetWindowTextA, sizeof(this->window_text_buffer_));
utils::hook::jump(GetWindowTextA, get_window_text_a_stub, true, true);
utils::hook::move_hook(GetWindowTextA);
const utils::nt::library ntdll("ntdll.dll");
const auto nt_query_information_process = ntdll.get_proc<void*>("NtQueryInformationProcess");
nt_query_information_process_hook.create(nt_query_information_process,
nt_query_information_process_stub);
const auto nt_query_system_information = ntdll.get_proc<void*>("NtQuerySystemInformation");
nt_query_system_information_hook.create(nt_query_system_information, nt_query_system_information_stub);
nt_query_system_information_hook.move();
/*************************************************************************************************************
** TODO : There is some kind of dormant defence mechanism. works so random makes it harder to investigate
@ -335,7 +503,10 @@ namespace integrity
{
return 9999;
}
private:
uint8_t window_text_buffer_[15]{};
};
}
REGISTER_COMPONENT(integrity::component)
REGISTER_COMPONENT(arxan::component)

View File

@ -1,6 +1,6 @@
#include <std_include.hpp>
#include "definitions/game.hpp"
#include "loader/component_loader.hpp"
#include "definitions/t8_engine.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>

View File

@ -1,137 +1,59 @@
#include <std_include.hpp>
#include "definitions/game.hpp"
#include "component/scheduler.hpp"
#include "loader/component_loader.hpp"
#include "definitions/t8_engine.hpp"
#include "scheduler.hpp"
#include <utils/string.hpp>
namespace debugging
{
typedef short(__fastcall* UI_Model_GetModelForController_t)(int controllerIndex);
UI_Model_GetModelForController_t UI_Model_GetModelForController = (UI_Model_GetModelForController_t)0x143AD0200_g;
typedef short(__fastcall* UI_Model_CreateModelFromPath_t)(short parentNodeIndex, const char* path);
UI_Model_CreateModelFromPath_t UI_Model_CreateModelFromPath = (UI_Model_CreateModelFromPath_t)0x143ACFC10_g;
typedef bool(__fastcall* UI_Model_SetString_t)(short nodeIndex, const char* newValue);
UI_Model_SetString_t UI_Model_SetString = (UI_Model_SetString_t)0x143AD18C0_g;
typedef bool(__fastcall* UI_Model_SetInt_t)(short nodeIndex, int newValue);
UI_Model_SetInt_t UI_Model_SetInt = (UI_Model_SetInt_t)0x143AD1820_g;
typedef bool(__fastcall* UI_Model_SetBool_t)(short nodeIndex, bool newValue);
UI_Model_SetBool_t UI_Model_SetBool = (UI_Model_SetBool_t)0x143AD1780_g;
typedef bool(__fastcall* UI_Model_SetReal_t)(short nodeIndex, float newValue);
UI_Model_SetReal_t UI_Model_SetReal = (UI_Model_SetReal_t)0x143AD1870_g;
void LUI_ShowToast(const char* title, const char* desc, const char* icon)
{
short main_model = UI_Model_GetModelForController(0);
short toast_model = UI_Model_CreateModelFromPath(main_model, "FrontendToast");
short sub_model = UI_Model_CreateModelFromPath(toast_model, "state");
UI_Model_SetString(sub_model, "DefaultState");
sub_model = UI_Model_CreateModelFromPath(toast_model, "kicker");
UI_Model_SetString(sub_model, title);
sub_model = UI_Model_CreateModelFromPath(toast_model, "description");
UI_Model_SetString(sub_model, desc);
sub_model = UI_Model_CreateModelFromPath(toast_model, "contentIcon");
UI_Model_SetString(sub_model, icon);
sub_model = UI_Model_CreateModelFromPath(toast_model, "functionIcon");
UI_Model_SetString(sub_model, "blacktransparent");
sub_model = UI_Model_CreateModelFromPath(toast_model, "backgroundId");
UI_Model_SetInt(sub_model, 0);
sub_model = UI_Model_CreateModelFromPath(toast_model, "emblemDecal");
UI_Model_SetReal(sub_model, 0.000000);
sub_model = UI_Model_CreateModelFromPath(toast_model, "notify");
UI_Model_SetBool(sub_model, true);
}
namespace
{
const char* s_connectivityNames[] =
const char* get_connectivity_info_string(int infoBitmask)
{
"user is non-guest", // 0x1
"connected to live", // 0x2
"user has multiplayer privs", // 0x4
"networking initialized", // 0x8
"connected to demonware", // 0x10
"lpc ready", // 0x20
"retrieved ffotd", // 0x40
"retrieved playlists", // 0x80
"publisher variables inited", // 0x100
"ffotd is valid", // 0x200
"user has stats and loadouts", // 0x400
"time is synced", // 0x800
"retrieved geo location", // 0x1000
"dedicated pings done", // 0x2000
"dedicated ping responses ok", // 0x4000
"literally unlisted", // 0x8000
"unknown - lpc related", // 0x10000
"inventory fetched", // 0x20000
"marketing messages received", // 0x40000
"bnet initialized", // 0x80000
"achievements fetched" // 0x100000
};
char connectionInfoString[64];
std::string GetConnectivityInfo()
for (int bitNumber = 0; bitNumber < 21; bitNumber++)
{
if ((1 << bitNumber) & infoBitmask)
{
connectionInfoString[bitNumber * 2] = bitNumber + 0x41;
}
else
{
connectionInfoString[bitNumber * 2] = 0x2D;
}
connectionInfoString[(bitNumber * 2) + 1] = 0x2E;
}
connectionInfoString[42] = NULL;
return utils::string::va("%s", connectionInfoString);
}
void draw_debug_info()
{
int infoBitmask = 0; int requiredMask = 0x1337FA;
game::Live_GetConnectivityInformation(0, &infoBitmask);
bool connected = (requiredMask & infoBitmask) == requiredMask;
std::string result{};
//result.append(std::format("Can play online (controller: {}): {}\n", 0, connected ? "true" : "false"));
void* font = reinterpret_cast<void*>(game::sharedUiInfo->assets.bigFont); if (!font) return;
for (int i = 1; i < 21; ++i)
if (!connected)
{
if (i == 15) continue; // unlisted bit
float color[4] = { 0.8f, 1.0f, 0.3, 0.8f };
const char* v13;
const char* v14;
const char* v15;
sz = get_connectivity_info_string(infoBitmask);
if (((1 << i) & infoBitmask) != 0 || (requiredMask & (1 << i)) == 0)
v13 = "^7";
else
v13 = "^1";
if ((requiredMask & (1 << i)) != 0)
v14 = "required";
else
v14 = "optional";
if (((1 << i) & infoBitmask) != 0)
v15 = "true";
else
v15 = "false";
game::ScreenPlacement* scrPlace = game::ScrPlace_GetView(0);
result.append(std::format("{}{}({}) - {}\n", v13, s_connectivityNames[i], v14, v15));
float offset_x = scrPlace->realViewportSize[0] - 8.0f
- game::UI_TextWidth(0, sz, 0x7FFFFFFF, font, 0.45f);
float offset_y = scrPlace->realViewportSize[1] - 8.0f;
game::R_AddCmdDrawText(sz, 0x7FFFFFFF, font, offset_x, offset_y, 0.45f, 0.45f, 0.0f, color, game::ITEM_TEXTSTYLE_BORDERED);
}
return result;
}
void draw_debug_info()
{
static bool should_draw_debugging_info = false;
if (GetAsyncKeyState(VK_INSERT) & 0x01) should_draw_debugging_info ^= 1;
if (!should_draw_debugging_info) return;
float color[4] = { 0.666f, 0.666f, 0.666f, 1.0f };
game::ScreenPlacement* scrPlace = game::ScrPlace_GetView(0);
void* font = game::UI_GetFontHandle(scrPlace, 0, 1.0f); if (!font) return;
std::string sz = GetConnectivityInfo();
game::R_AddCmdDrawText(sz.data(), 0x7FFFFFFF, font, 18.0f, 1.0f * (game::R_TextHeight(font) * 0.45f) + 4.0f, 0.45f, 0.45f, 0.0f, color, game::ITEM_TEXTSTYLE_BORDERED);
}
void test_key_catcher()
@ -140,20 +62,21 @@ namespace debugging
if ((GetAsyncKeyState(VK_HOME) & 0x01)/* && (static_cast<uint32_t>(time(nullptr)) - last_press_time) > 1*/)
{
last_press_time = static_cast<uint32_t>(time(nullptr));
LUI_ShowToast("Title", "Description", "uie_bookmark");
/* ACTION_PLACE_HOLDER */
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
scheduler::loop(draw_debug_info, scheduler::renderer);
scheduler::loop(test_key_catcher, scheduler::main);
}
};
class component final : public component_interface
{
public:
void post_unpack() override
{
scheduler::loop(draw_debug_info, scheduler::renderer);
scheduler::loop(test_key_catcher, scheduler::main);
}
};
}
REGISTER_COMPONENT(debugging::component)

View File

@ -1,6 +1 @@
#pragma once
namespace debugging
{
void LUI_ShowToast(const char* title, const char* desc, const char* icon = "blacktransparent");
}

View File

@ -0,0 +1,289 @@
#include <std_include.hpp>
#include "dvars.hpp"
#include "scheduler.hpp"
#include "spoofer/spoofcall.hpp"
#include "definitions/variables.hpp"
#include "loader/component_loader.hpp"
#include <utils/string.hpp>
namespace dvars
{
namespace
{
void fetch_dvar_pointers()
{
for (auto& dvar : variables::dvars_record)
{
dvar.pointer = spoofcall::invoke<uintptr_t>(game::Dvar_FindVar, dvar.name.data());
}
}
std::string get_vector_string(const int components, const game::DvarLimits& domain)
{
if (domain.vector.min == -FLT_MAX)
{
if (domain.vector.max == FLT_MAX)
{
return utils::string::va("Domain is any %iD vector", components);
}
else
{
return utils::string::va("Domain is any %iD vector with components %g or smaller", components,
domain.vector.max);
}
}
else if (domain.vector.max == FLT_MAX)
{
return utils::string::va("Domain is any %iD vector with components %g or bigger", components,
domain.vector.min);
}
else
{
return utils::string::va("Domain is any %iD vector with components from %g to %g", components,
domain.vector.min, domain.vector.max);
}
}
}
std::string get_domain_string(const game::dvarType_t type, const game::DvarLimits& domain)
{
std::string str;
switch (type)
{
case game::DVAR_TYPE_BOOL:
return "Domain is 0 or 1"s;
case game::DVAR_TYPE_FLOAT:
if (domain.value.min == -FLT_MAX)
{
if (domain.value.max == FLT_MAX)
{
return "Domain is any number"s;
}
else
{
return utils::string::va("Domain is any number %g or smaller", domain.value.max);
}
}
else if (domain.value.max == FLT_MAX)
{
return utils::string::va("Domain is any number %g or bigger", domain.value.min);
}
else
{
return utils::string::va("Domain is any number from %g to %g", domain.value.min, domain.value.max);
}
case game::DVAR_TYPE_FLOAT_2:
return get_vector_string(2, domain);
case game::DVAR_TYPE_FLOAT_3:
case game::DVAR_TYPE_LINEAR_COLOR_RGB:
case game::DVAR_TYPE_COLOR_XYZ:
case game::DVAR_TYPE_COLOR_LAB:
return get_vector_string(3, domain);
case game::DVAR_TYPE_FLOAT_4:
return get_vector_string(4, domain);
case game::DVAR_TYPE_INT:
if (domain.integer.min == INT_MIN)
{
if (domain.integer.max == INT_MAX)
{
return "Domain is any integer"s;
}
else
{
return utils::string::va("Domain is any integer %i or smaller", domain.integer.max);
}
}
else if (domain.integer.max == INT_MAX)
{
return utils::string::va("Domain is any integer %i or bigger", domain.integer.min);
}
else
{
return utils::string::va("Domain is any integer from %i to %i", domain.integer.min, domain.integer.max);
}
case game::DVAR_TYPE_ENUM:
str = "Domain is one of the following:"s;
for (auto string_index = 0; string_index < domain.enumeration.stringCount; ++string_index)
{
str += utils::string::va("\n %2i: %s", string_index, domain.enumeration.strings[string_index]);
}
return str;
case game::DVAR_TYPE_STRING:
return "Domain is any text"s;
case game::DVAR_TYPE_COLOR:
return "Domain is any 4-component color, in RGBA format"s;
case game::DVAR_TYPE_INT64:
if (domain.integer64.min == _I64_MIN)
{
if (domain.integer64.max == _I64_MAX)
{
return "Domain is any integer"s;
}
else
{
return utils::string::va("Domain is any integer %lli or smaller", domain.integer64.max);
}
}
else if (domain.integer64.max == _I64_MAX)
{
return utils::string::va("Domain is any integer %lli or bigger", domain.integer64.min);
}
else
{
return utils::string::va("Domain is any integer from %lli to %lli", domain.integer64.min, domain.integer64.max);
}
case game::DVAR_TYPE_UINT64:
if (domain.unsignedInt64.min)
{
if (domain.unsignedInt64.max == _UI64_MAX)
{
return utils::string::va("Domain is any unsigned integer %zu or bigger", domain.unsignedInt64.min);
}
else
{
return utils::string::va("Domain is any unsigned integer from %zu to %zu", domain.unsignedInt64.min, domain.unsignedInt64.max);
}
}
else if (domain.unsignedInt64.max == _UI64_MAX)
{
return "Domain is any integer"s;
}
else
{
return utils::string::va("Domain is any integer %zu or smaller", domain.unsignedInt64.max);
}
default:
return utils::string::va("unhandled dvar type '%i'", type);
}
}
std::string get_value_string(const game::dvar_t * dvar, game::DvarValue * value)
{
std::string result = "N/A";
switch (dvar->type)
{
case game::DVAR_TYPE_BOOL:
if (value->naked.enabled)
{
result = "1"s;
}
else
{
result = "0"s;
}
break;
case game::DVAR_TYPE_FLOAT:
result = std::format("{:.2f}", value->naked.value);
break;
case game::DVAR_TYPE_FLOAT_2:
result = std::format("{:.2f} {:.2f}", value->naked.vector[0], value->naked.vector[1]);
break;
case game::DVAR_TYPE_FLOAT_3:
case game::DVAR_TYPE_LINEAR_COLOR_RGB:
case game::DVAR_TYPE_COLOR_XYZ:
case game::DVAR_TYPE_COLOR_LAB:
result = std::format("{:.2f} {:.2f} {:.2f}", value->naked.vector[0], value->naked.vector[1], value->naked.vector[2]);
break;
case game::DVAR_TYPE_FLOAT_4:
result = std::format("{:.2f} {:.2f} {:.2f} {:.2f}", value->naked.vector[0], value->naked.vector[1], value->naked.vector[2], value->naked.vector[3]);
break;
case game::DVAR_TYPE_INT:
result = std::format("{}", value->naked.integer);
break;
case game::DVAR_TYPE_ENUM:
if (dvar->domain.enumeration.stringCount)
{
result = std::string(dvar->domain.enumeration.strings[value->naked.integer]);
}
break;
case game::DVAR_TYPE_STRING:
result = std::string(value->naked.string);
break;
case game::DVAR_TYPE_COLOR:
result = std::format("{:.2f} {:.2f} {:.2f} {:.2f}", (float)value->naked.color[0] * 0.0039215689, (float)value->naked.color[1] * 0.0039215689, (float)value->naked.color[2] * 0.0039215689, (float)value->naked.color[3] * 0.0039215689);
break;
case game::DVAR_TYPE_INT64:
result = std::format("{}", value->naked.integer64);
break;
case game::DVAR_TYPE_UINT64:
result = std::format("{}", value->naked.unsignedInt64);
break;
default:
result = "ERROR:DVAR_TYPE_UNKNOWN"s;
break;
}
return result;
}
game::dvar_t* find_dvar(uint64_t hashRef)
{
if (hashRef == 0) return NULL;
auto it = std::find_if(variables::dvars_record.begin(), variables::dvars_record.end(), [&hashRef](variables::varEntry& i) { return i.fnv1a == hashRef; });
if (it != variables::dvars_record.end() && it->pointer)
{
return reinterpret_cast<game::dvar_t*>(it->pointer);
}
return spoofcall::invoke<game::dvar_t*>(game::Dvar_FindVar_Hash, game::AssetRef(hashRef));
}
game::dvar_t* find_dvar(const char* nameRef)
{
return spoofcall::invoke<game::dvar_t*>(game::Dvar_FindVar, nameRef);
}
game::dvar_t* find_dvar(const std::string& nameRef)
{
auto it = std::find_if(variables::dvars_record.begin(), variables::dvars_record.end(), [&nameRef](variables::varEntry& i) { return utils::string::compare(i.name, nameRef); });
if (it != variables::dvars_record.end() && it->pointer)
{
return reinterpret_cast<game::dvar_t*>(it->pointer);
}
return spoofcall::invoke<game::dvar_t*>(game::Dvar_FindVar, nameRef.data());
}
class component final : public component_interface
{
public:
void post_unpack() override
{
scheduler::once(fetch_dvar_pointers, scheduler::pipeline::main);
}
};
}
REGISTER_COMPONENT(dvars::component)

View File

@ -0,0 +1,13 @@
#pragma once
#include "definitions\game.hpp"
namespace dvars
{
std::string get_value_string(const game::dvar_t* dvar, game::DvarValue* value);
std::string get_domain_string(const game::dvarType_t type, const game::DvarLimits& domain);
game::dvar_t* find_dvar(uint64_t hashRef);
game::dvar_t* find_dvar(const char* nameRef);
game::dvar_t* find_dvar(const std::string& nameRef);
}

View File

@ -1,7 +1,8 @@
#include <std_include.hpp>
#include <utils/hook.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace experimental
{
namespace

View File

@ -0,0 +1,734 @@
#include <std_include.hpp>
#include "game_console.hpp"
#include "definitions/game.hpp"
#include "definitions/variables.hpp"
#include "loader/component_loader.hpp"
#include "component/dvars.hpp"
#include "component/scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/concurrency.hpp>
#define R_DrawTextFont reinterpret_cast<void*>(game::sharedUiInfo->assets.bigFont)
#define R_WhiteMaterial reinterpret_cast<void*>(game::sharedUiInfo->assets.whiteMaterial)
namespace game_console
{
namespace
{
game::vec4_t con_inputBoxColor = { 0.1f, 0.1f, 0.1f, 0.9f };
game::vec4_t con_inputHintBoxColor = { 0.1f, 0.1f, 0.1f, 1.0f };
game::vec4_t con_outputBarColor = { 0.8f, 0.8f, 0.8f, 0.6f };
game::vec4_t con_outputSliderColor = { 0.8f, 0.8f, 0.8f, 1.0f };
game::vec4_t con_outputWindowColor = { 0.15f, 0.15f, 0.15f, 0.85f };
game::vec4_t con_inputWriteDownColor = { 1.0f, 1.0f, 1.0f, 1.0f };
game::vec4_t con_inputDvarMatchColor = { 0.1f, 0.8f, 0.8f, 1.0f };
game::vec4_t con_inputDvarInactiveValueColor = { 0.4f, 0.8f, 0.7f, 1.0f };
game::vec4_t con_inputCmdMatchColor = { 0.9f, 0.6f, 0.2f, 1.0f };
game::vec4_t con_inputDescriptionColor = { 1.0f, 1.0f, 1.0f, 1.0f };
game::vec4_t con_inputAltDescriptionColor = { 0.9f, 0.6f, 0.2f, 1.0f };
game::vec4_t con_inputExtraInfoColor = { 1.0f, 0.5f, 0.5f, 1.0f };
game::vec4_t con_outputVersionStringColor = { 0.92f, 1.0f, 0.65f, 1.0f };
using suggestion_t = variables::varEntry;
using output_queue = std::deque<std::string>;
struct ingame_console
{
char buffer[256]{};
int cursor{};
float font_scale{};
float font_height{};
int max_suggestions{};
int visible_line_count{};
float screen_min[2]{};
float screen_max[2]{};
struct {
float x{}, y{};
} screen_pointer;
bool may_auto_complete{};
char auto_complete_choice[64]{};
bool output_visible{};
int display_line_offset{};
int total_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<suggestion_t> matches{};
void clear_input()
{
strncpy_s(con.buffer, "", sizeof(con.buffer));
con.cursor = 0;
fixed_input = "";
matches.clear();
}
void clear_output()
{
con.total_line_count = 0;
con.display_line_offset = 0;
con.output.access([](output_queue& output)
{
output.clear();
});
history_index = -1;
history.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_input();
con.output_visible = false;
*game::keyCatchers ^= 1;
}
void toggle_console_output()
{
con.output_visible = con.output_visible == 0;
}
bool is_renderer_ready()
{
return (R_DrawTextFont && R_WhiteMaterial);
}
void calculate_window_size()
{
con.screen_min[0] = 6.0f;
con.screen_min[1] = 6.0f;
con.screen_max[0] = game::ScrPlace_GetView(0)->realViewportSize[0] - 6.0f;
con.screen_max[1] = game::ScrPlace_GetView(0)->realViewportSize[1] - 6.0f;
con.font_height = static_cast<float>(game::UI_TextHeight(R_DrawTextFont, con.font_scale));
con.visible_line_count = static_cast<int>((con.screen_max[1] - con.screen_min[1] - (con.font_height * 2)) - 24.0f) / con.font_height;
}
void draw_box(const float x, const float y, const float w, const float h, float* color)
{
game::vec4_t outline_color;
outline_color[0] = color[0] * 0.5f;
outline_color[1] = color[1] * 0.5f;
outline_color[2] = color[2] * 0.5f;
outline_color[3] = color[3];
game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0f, 0.0f, 0.0f, 0.0f, color, R_WhiteMaterial);
game::R_AddCmdDrawStretchPic(x, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, outline_color, R_WhiteMaterial);
game::R_AddCmdDrawStretchPic((x + w) - 2.0f, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, outline_color, R_WhiteMaterial);
game::R_AddCmdDrawStretchPic(x, y, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, outline_color, R_WhiteMaterial);
game::R_AddCmdDrawStretchPic(x, (y + h) - 2.0f, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, outline_color, R_WhiteMaterial);
}
void draw_input_box(const int lines, float* color)
{
draw_box( con.screen_pointer.x - 6.0f, con.screen_pointer.y - 6.0f,
(con.screen_max[0] - con.screen_min[0]) - ((con.screen_pointer.x - 6.0f) - con.screen_min[0]),
(lines * con.font_height) + 12.0f, color);
}
void draw_input_text_and_over(const char* str, float* color)
{
game::R_AddCmdDrawText(str, 0x7FFFFFFF, R_DrawTextFont, con.screen_pointer.x,
con.screen_pointer.y + con.font_height, con.font_scale, con.font_scale, 0.0f, color, 0);
con.screen_pointer.x = game::UI_TextWidth(0, str, 0x7FFFFFFF, R_DrawTextFont, con.font_scale) + con.screen_pointer.x + 6.0f;
}
float 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.font_height + 12.0f;
const auto _y = con.screen_pointer.y - 3.0f + con.font_height + 12.0f + offset_y;
const auto _w = (con.screen_max[0] - con.screen_min[0]) - ((con.screen_pointer.x - 6.0f) - con.screen_min[0]);
draw_box(con.screen_pointer.x - 6.0f, _y, _w, _h, color);
return _h;
}
void draw_hint_text(const int line, const char* text, float* color, const float offset_x = 0.0f, const float offset_y = 0.0f)
{
const auto _y = con.font_height + con.screen_pointer.y + (con.font_height * (line + 1)) + 15.0f + offset_y;
game::R_AddCmdDrawText(text, 0x7FFFFFFF, R_DrawTextFont, con.screen_pointer.x + offset_x, _y, con.font_scale, con.font_scale, 0.0f, color, 0);
}
void find_matches(const std::string& input, std::vector<suggestion_t>& suggestions, bool exact)
{
double required_ratio = exact ? 1.00 : 0.01;
for (const auto& dvar : variables::dvars_record)
{
if (dvars::find_dvar(dvar.fnv1a) && utils::string::match(input, dvar.name) >= required_ratio)
{
suggestions.push_back(dvar);
}
if (exact && suggestions.size() > 1)
{
return;
}
}
if (suggestions.size() == 0 && dvars::find_dvar(input))
{
suggestions.push_back({ input, "", fnv1a::generate_hash(input.data()), reinterpret_cast<uintptr_t>(dvars::find_dvar(input)) });
}
for (const auto& cmd : variables::commands_record)
{
if (utils::string::match(input, cmd.name) >= required_ratio)
{
suggestions.push_back(cmd);
}
if (exact && suggestions.size() > 1)
{
return;
}
}
}
void draw_input()
{
con.screen_pointer.x = con.screen_min[0] + 6.0f;
con.screen_pointer.y = con.screen_min[1] + 6.0f;
draw_input_box(1, con_inputBoxColor);
draw_input_text_and_over("PROJECT-BO4 >", con_inputWriteDownColor);
con.auto_complete_choice[0] = 0;
game::R_AddCmdDrawTextWithCursor(con.buffer, 0x7FFFFFFF, R_DrawTextFont, con.screen_pointer.x, con.screen_pointer.y + con.font_height, con.font_scale, con.font_scale, 0, con_inputWriteDownColor, 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);
if (matches.size() <= con.max_suggestions)
{
std::sort(matches.begin(), matches.end(), [&input](suggestion_t& lhs, suggestion_t& rhs) {
return utils::string::match(input, lhs.name) > utils::string::match(input, rhs.name);
});
}
}
fixed_input = input;
}
con.may_auto_complete = false;
if (matches.size() > con.max_suggestions)
{
draw_hint_box(1, con_inputHintBoxColor);
draw_hint_text(0, utils::string::va("%i matches (too many to show here)", matches.size()), con_inputDvarMatchColor);
}
else if (matches.size() == 1)
{
auto* dvar = dvars::find_dvar(matches[0].fnv1a);
auto line_count = dvar ? 3 : 1;
auto height = draw_hint_box(line_count, con_inputHintBoxColor);
draw_hint_text(0, matches[0].name.data(), dvar ? con_inputDvarMatchColor : con_inputCmdMatchColor);
if (dvar)
{
auto offset_x = (con.screen_max[0] - con.screen_pointer.x) / 4.f;
draw_hint_text(0, dvars::get_value_string(dvar, &dvar->value->current).data(), con_inputDvarMatchColor, offset_x);
draw_hint_text(1, " default", con_inputDvarInactiveValueColor);
draw_hint_text(1, dvars::get_value_string(dvar, &dvar->value->reset).data(), con_inputDvarInactiveValueColor, offset_x);
draw_hint_text(2, matches[0].desc, con_inputDescriptionColor, 0);
auto offset_y = height + 3.f;
auto domain_lines = 1;
if (dvar->type == game::DVAR_TYPE_ENUM)
domain_lines = dvar->domain.enumeration.stringCount + 1;
draw_hint_box(domain_lines, con_inputHintBoxColor, 0, offset_y);
draw_hint_text(0, dvars::get_domain_string(dvar->type, dvar->domain).data(), con_inputAltDescriptionColor, 0, offset_y);
}
else
{
auto offset_x = (con.screen_max[0] - con.screen_pointer.x) / 4.f;
draw_hint_text(0, matches[0].desc, con_inputCmdMatchColor, offset_x);
}
strncpy_s(con.auto_complete_choice, matches[0].name.data(), 64);
con.may_auto_complete = true;
}
else if (matches.size() > 1)
{
draw_hint_box(static_cast<int>(matches.size()), con_inputHintBoxColor);
auto offset_x = (con.screen_max[0] - con.screen_pointer.x) / 4.f;
for (size_t i = 0; i < matches.size(); i++)
{
auto* const dvar = dvars::find_dvar(matches[i].fnv1a);
draw_hint_text(static_cast<int>(i), matches[i].name.data(), dvar ? con_inputDvarMatchColor : con_inputCmdMatchColor);
draw_hint_text(static_cast<int>(i), matches[i].desc, dvar ? con_inputDvarMatchColor : con_inputCmdMatchColor, offset_x * 1.5f);
if (dvar)
{
draw_hint_text(static_cast<int>(i), dvars::get_value_string(dvar, &dvar->value->current).data(), con_inputDvarMatchColor, offset_x);
}
}
strncpy_s(con.auto_complete_choice, matches[0].name.data(), 64);
con.may_auto_complete = true;
}
}
void draw_output_scrollbar(const float x, float y, const float width, const float height, output_queue& output)
{
auto _x = (x + width) - 10.0f;
draw_box(_x, y, 10.0f, height, con_outputBarColor);
auto _height = height;
if (output.size() > con.visible_line_count)
{
auto percentage = static_cast<float>(con.visible_line_count) / output.size();
_height *= percentage;
auto remainingSpace = height - _height;
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, con_outputSliderColor);
}
void draw_output_text(const float x, float y, output_queue& output)
{
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++)
{
auto index = i + con.display_line_offset;
if (index >= output.size())
{
break;
}
game::R_AddCmdDrawText(output.at(index).data(), 0x400, R_DrawTextFont, x, y + offset + ((i + 1) * con.font_height), con.font_scale, con.font_scale, 0.0f, con_inputWriteDownColor, 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, con_outputWindowColor);
auto x = con.screen_min[0] + 6.0f;
auto y = (con.screen_min[1] + 32.0f) + 6.0f;
auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
game::R_AddCmdDrawText(game::version_string.data(), 0x7FFFFFFF, R_DrawTextFont, x, ((height - 16.0f) + y) + con.font_height, con.font_scale, con.font_scale, 0.0f, con_outputVersionStringColor, 0);
draw_output_scrollbar(x, y, width, height, output);
draw_output_text(x, y, output);
});
}
void draw_console()
{
if (!is_renderer_ready()) return;
calculate_window_size();
if (*game::keyCatchers & 1)
{
if (!(*game::keyCatchers & 1))
{
con.output_visible = false;
}
if (con.output_visible)
{
draw_output_window();
}
draw_input();
}
}
}
void print(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 std::string& data)
{
const auto lines = utils::string::split(data, '\n');
for (const auto& line : lines)
{
print_internal(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.may_auto_complete)
{
const auto first_char = con.buffer[0];
clear_input();
if (first_char == '\\' || first_char == '/')
{
con.buffer[0] = first_char;
con.buffer[1] = '\0';
}
strncat_s(con.buffer, con.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_input();
con.total_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_GRAVE || key == game::keyNum_t::K_TILDE)
{
if (!down)
{
return false;
}
const auto shift_down = game::playerKeys[local_client_num].keys[game::keyNum_t::K_LSHIFT].down;
if (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_input();
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_input();
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);
print("]%s\n", con.buffer);
game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data()));
if (history.size() > 10)
{
history.erase(history.begin() + 10);
}
history_index = -1;
clear_input();
}
}
}
return true;
}
utils::hook::detour cl_key_event_hook;
void cl_key_event_stub(int localClientNum, int key, bool down, unsigned int time)
{
if (!game_console::console_key_event(localClientNum, key, down))
{
return;
}
cl_key_event_hook.invoke<void>(localClientNum, key, down, time);
}
utils::hook::detour cl_char_event_hook;
void cl_char_event_stub(const int localClientNum, const int key, bool isRepeated)
{
if (!game_console::console_char_event(localClientNum, key))
{
return;
}
cl_char_event_hook.invoke<void>(localClientNum, key, isRepeated);
}
class component final : public component_interface
{
public:
void post_unpack() override
{
scheduler::loop(draw_console, scheduler::renderer);
cl_key_event_hook.create(0x142839250_g, cl_key_event_stub);
cl_char_event_hook.create(0x142836F80_g, cl_char_event_stub);
// initialize our structs
con.cursor = 0;
con.visible_line_count = 0;
con.output_visible = false;
con.display_line_offset = 0;
con.total_line_count = 0;
strncpy_s(con.buffer, "", 256);
con.screen_pointer.x = 0.0f;
con.screen_pointer.y = 0.0f;
con.may_auto_complete = false;
strncpy_s(con.auto_complete_choice, "", 64);
con.font_scale = 0.38f;
con.max_suggestions = 24;
}
};
}
REGISTER_COMPONENT(game_console::component)

View File

@ -0,0 +1,7 @@
#pragma once
namespace game_console
{
void print(const char* fmt, ...);
void print(const std::string& data);
}

View File

@ -1,10 +1,12 @@
#include <std_include.hpp>
#include "logger.hpp"
#include "game_console.hpp"
#include "loader/component_loader.hpp"
#include <utils/nt.hpp>
#define OUTPUT_DEBUG_API
#define PREPEND_TIMESTAMP
#define OUTPUT_GAME_CONSOLE
namespace logger
{
@ -22,20 +24,25 @@ namespace logger
if (type == LOG_TYPE_DEBUG) return;
#endif // _DEBUG
std::stringstream ss;
ss << "[ " << LogTypeNames[type] << " ] " << str << std::endl;
std::string text = ss.str();
#ifdef OUTPUT_GAME_CONSOLE
game_console::print(text);
#endif // OUTPUT_GAME_CONSOLE
#ifdef OUTPUT_DEBUG_API
OutputDebugStringA(str.c_str());
OutputDebugStringA(text.c_str());
#endif // OUTPUT_DEBUG_API
std::ofstream stream;
stream.open("project-bo4.log", std::ios_base::app);
std::ofstream fs;
fs.open("project-bo4.log", std::ios_base::app);
#ifdef PREPEND_TIMESTAMP
time_t now = time(0);
std::tm* t = std::localtime(&now);
stream << "" << std::put_time(t, "%Y-%m-%d %H:%M:%S") << "\t";
#endif // PREPEND_TIMESTAMP
stream << "[ " << LogTypeNames[type] << " ] " << str << std::endl;
fs << "" << std::put_time(t, "%Y-%m-%d %H:%M:%S") << "\t" << text;
}
void write(const int type, const char* fmt, ...)
@ -51,11 +58,6 @@ namespace logger
write(type, formatted);
}
namespace
{
/* PLACE_HOLDER */
}
class component final : public component_interface
{
public:
@ -65,9 +67,9 @@ namespace logger
utils::io::remove_file("project-bo4.log");
#endif // REMOVE_PREVIOUS_LOG
write(LOG_TYPE_INFO, "=======================================================================================================");
write(LOG_TYPE_INFO, " Project-BO4 Initializing ... %s[0x%llX]", utils::nt::library{}.get_name().c_str(), utils::nt::library{}.get_ptr());
write(LOG_TYPE_INFO, "=======================================================================================================");
write(LOG_TYPE_INFO, "=======================================================================================================");
write(LOG_TYPE_INFO, " Project-BO4 Initializing ... %s[0x%llX]", utils::nt::library{}.get_name().c_str(), utils::nt::library{}.get_ptr());
write(LOG_TYPE_INFO, "=======================================================================================================");
}
void post_unpack() override

View File

@ -1,13 +1,14 @@
#include <std_include.hpp>
#include "platform.hpp"
#include "definitions/game.hpp"
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/identity.hpp>
#include <utils/json_config.hpp>
#include <utils/cryptography.hpp>
#include "WinReg.hpp"
#include "definitions/t8_engine.hpp"
#include <WinReg.hpp>
namespace platform
{

View File

@ -1,7 +1,6 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include "loader/component_loader.hpp"
#include <cassert>
#include <utils/hook.hpp>
@ -166,7 +165,7 @@ namespace scheduler
void post_unpack() override
{
r_end_frame_hook.create(0x14361E260_g, r_end_frame_stub); // R_EndFrame
//main_frame_hook.create(0x14288BAE0_g, main_frame_stub); // Com_Frame
main_frame_hook.create(0x14288BAE0_g, main_frame_stub); // Com_Frame
//g_run_frame_hook.create(0x142D08FC0_g, server_frame_stub); // G_RunFrame
}

View File

@ -1,7 +1,7 @@
#include <std_include.hpp>
#include "splash.hpp"
#include "loader/component_loader.hpp"
#include "splash.hpp"
#include "resource.hpp"
#include <utils/nt.hpp>

View File

@ -1,6 +1,7 @@
#include <std_include.hpp>
#include "definitions\discovery.hpp"
#include "discovery.hpp"
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/signature.hpp>

View File

@ -1,5 +1,5 @@
#include <std_include.hpp>
#include "definitions\t8_engine.hpp"
#include "game.hpp"
#include "loader/component_loader.hpp"
namespace game

View File

@ -0,0 +1,507 @@
#pragma once
#include "definitions\discovery.hpp"
#define WEAK __declspec(selectany)
namespace game
{
//////////////////////////////////////////////////////////////////////////
// VARIABLES //
//////////////////////////////////////////////////////////////////////////
extern std::string version_string;
typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
//////////////////////////////////////////////////////////////////////////
// STRUCTS //
//////////////////////////////////////////////////////////////////////////
struct BO4_AssetRef_t
{
__int64 hash;
__int64 null;
};
inline BO4_AssetRef_t
AssetRef(uint64_t hashRef)
{
BO4_AssetRef_t m128i;
m128i.hash = hashRef;
return m128i;
}
enum keyNum_t
{
K_NONE = 0x00,
K_BUTTON_A = 0x01,
K_BUTTON_B = 0x02,
K_BUTTON_X = 0x03,
K_BUTTON_Y = 0x04,
K_BUTTON_LSHLDR = 0x05,
K_BUTTON_RSHLDR = 0x06,
K_UNK7 = 0x07,
K_UNK8 = 0x08,
K_TAB = 0x09,
K_UNK10 = 0x0A,
K_UNK11 = 0x0B,
K_UNK12 = 0x0C,
K_ENTER = 0x0D,
K_BUTTON_START = 0x0E,
K_BUTTON_BACK = 0x0F,
K_BUTTON_LSTICK = 0x10,
K_BUTTON_RSTICK = 0x11,
K_BUTTON_LTRIG = 0x12,
K_BUTTON_RTRIG = 0x13,
K_UNK20 = 0x14,
K_UNK21 = 0x15,
K_DPAD_UP = 0x16,
K_DPAD_DOWN = 0x17,
K_DPAD_LEFT = 0x18,
K_DPAD_RIGHT = 0x19,
K_UNK26 = 0x1A,
K_ESCAPE = 0x1B,
K_APAD_UP = 0x1C,
K_APAD_DOWN = 0x1D,
K_APAD_LEFT = 0x1E,
K_APAD_RIGHT = 0x1F,
K_SPACE = 0x20,
K_UNK33 = 0x21,
K_UNK34 = 0x22,
K_UNK35 = 0x23,
K_UNK36 = 0x24,
K_UNK37 = 0x25,
K_UNK38 = 0x26,
K_UNK39 = 0x27,
K_UNK40 = 0x28,
K_UNK41 = 0x29,
K_UNK42 = 0x2A,
K_UNK43 = 0x2B,
K_UNK44 = 0x2C,
K_UNK45 = 0x2D,
K_UNK46 = 0x2E,
K_UNK47 = 0x2F,
K_UNK48 = 0x30,
K_UNK49 = 0x31,
K_UNK50 = 0x32,
K_UNK51 = 0x33,
K_UNK52 = 0x34,
K_UNK53 = 0x35,
K_UNK54 = 0x36,
K_UNK55 = 0x37,
K_UNK56 = 0x38,
K_UNK57 = 0x39,
K_UNK58 = 0x3A,
K_SEMICOLON = 0x3B,
K_UNK60 = 0x3C,
K_UNK61 = 0x3D,
K_UNK62 = 0x3E,
K_UNK63 = 0x3F,
K_UNK64 = 0x40,
K_UNK65 = 0x41,
K_UNK66 = 0x42,
K_UNK67 = 0x43,
K_UNK68 = 0x44,
K_UNK69 = 0x45,
K_UNK70 = 0x46,
K_UNK71 = 0x47,
K_UNK72 = 0x48,
K_UNK73 = 0x49,
K_UNK74 = 0x4A,
K_UNK75 = 0x4B,
K_UNK76 = 0x4C,
K_UNK77 = 0x4D,
K_UNK78 = 0x4E,
K_UNK79 = 0x4F,
K_UNK80 = 0x50,
K_UNK81 = 0x51,
K_UNK82 = 0x52,
K_UNK83 = 0x53,
K_UNK84 = 0x54,
K_UNK85 = 0x55,
K_UNK86 = 0x56,
K_UNK87 = 0x57,
K_UNK88 = 0x58,
K_UNK89 = 0x59,
K_UNK90 = 0x5A,
K_UNK91 = 0x5B,
K_UNK92 = 0x5C,
K_UNK93 = 0x5D,
K_UNK94 = 0x5E,
K_UNK95 = 0x5F,
K_GRAVE = 0x60,
K_UNK97 = 0x61,
K_UNK98 = 0x62,
K_UNK99 = 0x63,
K_UNK100 = 0x64,
K_UNK101 = 0x65,
K_UNK102 = 0x66,
K_UNK103 = 0x67,
K_UNK104 = 0x68,
K_UNK105 = 0x69,
K_UNK106 = 0x6A,
K_UNK107 = 0x6B,
K_UNK108 = 0x6C,
K_UNK109 = 0x6D,
K_UNK110 = 0x6E,
K_UNK111 = 0x6F,
K_UNK112 = 0x70,
K_UNK113 = 0x71,
K_UNK114 = 0x72,
K_UNK115 = 0x73,
K_UNK116 = 0x74,
K_UNK117 = 0x75,
K_UNK118 = 0x76,
K_UNK119 = 0x77,
K_UNK120 = 0x78,
K_UNK121 = 0x79,
K_UNK122 = 0x7A,
K_UNK123 = 0x7B,
K_UNK124 = 0x7C,
K_UNK125 = 0x7D,
K_TILDE = 0x7E,
K_BACKSPACE = 0x7F,
K_CAPSLOCK = 0x80,
K_PAUSE = 0x81,
K_PRINTSCREEN = 0x82,
K_SCROLLLOCK = 0x83,
K_UPARROW = 0x84,
K_DOWNARROW = 0x85,
K_LEFTARROW = 0x86,
K_RIGHTARROW = 0x87,
K_LALT = 0x88,
K_RALT = 0x89,
K_LCTRL = 0x8A,
K_RCTRL = 0x8B,
K_LSHIFT = 0x8C,
K_RSHIFT = 0x8D,
K_HIRAGANA = 0x8E,
K_HENKAN = 0x8F,
K_MUHENKAN = 0x90,
K_LWIN = 0x91,
K_RWIN = 0x92,
K_MENU = 0x93,
K_INS = 0x94,
K_DEL = 0x95,
K_PGDN = 0x96,
K_PGUP = 0x97,
K_HOME = 0x98,
K_END = 0x99,
K_F1 = 0x9A,
K_F2 = 0x9B,
K_F3 = 0x9C,
K_F4 = 0x9D,
K_F5 = 0x9E,
K_F6 = 0x9F,
K_F7 = 0xA0,
K_F8 = 0xA1,
K_F9 = 0xA2,
K_F10 = 0xA3,
K_F11 = 0xA4,
K_F12 = 0xA5,
K_UNK166 = 0xA6,
K_UNK167 = 0xA7,
K_UNK168 = 0xA8,
K_KP_HOME = 0xA9,
K_KP_UPARROW = 0xAA,
K_KP_PGUP = 0xAB,
K_KP_LEFTARROW = 0xAC,
K_KP_5 = 0xAD,
K_KP_RIGHTARROW = 0xAE,
K_KP_END = 0xAF,
K_KP_DOWNARROW = 0xB0,
K_KP_PGDN = 0xB1,
K_KP_ENTER = 0xB2,
K_KP_INS = 0xB3,
K_KP_DEL = 0xB4,
K_KP_SLASH = 0xB5,
K_KP_MINUS = 0xB6,
K_KP_PLUS = 0xB7,
K_KP_NUMLOCK = 0xB8,
K_KP_STAR = 0xB9,
K_MOUSE1 = 0xBA,
K_MOUSE2 = 0xBB,
K_MOUSE3 = 0xBC,
K_MOUSE4 = 0xBD,
K_MOUSE5 = 0xBE,
K_MWHEELDOWN = 0xBF,
K_MWHEELUP = 0xC0
};
struct KeyState
{
int down;
int repeats;
int binding;
char pad[20];
}; // size = 32
struct PlayerKeyState
{
bool overstrikeMode;
int anyKeyDown;
KeyState keys[256];
};
struct AssetCache
{
uintptr_t whiteMaterial;
uintptr_t cursor;
uintptr_t blur;
uintptr_t devFont; // TTF
uintptr_t defaultFont; // TTF
uintptr_t bigFont;
uintptr_t smallFont;
uintptr_t consoleFont;
uintptr_t boldFont;
uintptr_t textFont;
uintptr_t extraBigFont;
uintptr_t extraSmallFont;
uintptr_t fxFont;
};
struct sharedUiInfo_t
{
AssetCache assets;
};
struct ScreenPlacement
{
vec2_t scaleVirtualToReal;
vec2_t scaleVirtualToFull;
vec2_t scaleRealToVirtual;
vec2_t virtualViewableMin;
vec2_t virtualViewableMax;
vec2_t virtualTweakableMin;
vec2_t virtualTweakableMax;
vec2_t realViewportBase;
vec2_t realViewportSize;
vec2_t realViewportMid;
vec2_t realViewableMin;
vec2_t realViewableMax;
vec2_t realTweakableMin;
vec2_t realTweakableMax;
vec2_t subScreen;
float hudSplitscreenScale;
};
enum itemTextStyle
{
ITEM_TEXTSTYLE_NORMAL = 0,
ITEM_TEXTSTYLE_SHADOWED = 3,
ITEM_TEXTSTYLE_SHADOWEDMORE = 6,
ITEM_TEXTSTYLE_BORDERED = 7,
ITEM_TEXTSTYLE_BORDEREDMORE = 8,
ITEM_TEXTSTYLE_MONOSPACE = 128,
ITEM_TEXTSTYLE_MONOSPACESHADOWED = 132,
};
enum errorParm
{
ERR_FATAL = 0,
ERR_DROP = 1,
ERR_SERVERDISCONNECT = 2,
ERR_DISCONNECT = 3,
ERR_SCRIPT = 4,
ERR_SCRIPT_DROP = 5,
ERR_LOCALIZATION = 6,
ERR_MAPLOADERRORSUMMARY = 7,
};
enum dvarType_t
{
DVAR_TYPE_INVALID = 0x0,
DVAR_TYPE_BOOL = 0x1,
DVAR_TYPE_FLOAT = 0x2,
DVAR_TYPE_FLOAT_2 = 0x3,
DVAR_TYPE_FLOAT_3 = 0x4,
DVAR_TYPE_FLOAT_4 = 0x5,
DVAR_TYPE_INT = 0x6,
DVAR_TYPE_ENUM = 0x7,
DVAR_TYPE_STRING = 0x8,
DVAR_TYPE_COLOR = 0x9,
DVAR_TYPE_INT64 = 0xA,
DVAR_TYPE_UINT64 = 0xB,
DVAR_TYPE_LINEAR_COLOR_RGB = 0xC,
DVAR_TYPE_COLOR_XYZ = 0xD,
DVAR_TYPE_COLOR_LAB = 0xE,
DVAR_TYPE_SESSIONMODE_BASE_DVAR = 0xF,
DVAR_TYPE_COUNT = 0x10,
};
enum dvarFlags_e
{
DVAR_NONE = 0,
DVAR_ARCHIVE = 1 << 0,
DVAR_USERINFO = 1 << 1,
DVAR_SERVERINFO = 1 << 2,
DVAR_SYSTEMINFO = 1 << 3,
DVAR_LATCH = 1 << 4,
DVAR_ROM = 1 << 5,
DVAR_SAVED = 1 << 6,
DVAR_INIT = 1 << 7,
DVAR_CHEAT = 1 << 8,
//DVAR_UNKNOWN = 1 << 9,
DVAR_EXTERNAL = 1 << 10,
//DVAR_UNKNOWN3x = 1 << 11-13,
DVAR_SESSIONMODE = 1 << 15
};
union DvarLimits
{
struct
{
int stringCount;
const char** strings;
} enumeration;
struct
{
int min;
int max;
} integer;
struct
{
int64_t min;
int64_t max;
} integer64;
struct
{
uint64_t min;
uint64_t max;
} unsignedInt64;
struct
{
float min;
float max;
} value;
struct
{
vec_t min;
vec_t max;
} vector;
};
struct dvar_t;
struct DvarValue
{
union
{
bool enabled;
int integer;
uint32_t unsignedInt;
int64_t integer64;
uint64_t unsignedInt64;
float value;
vec4_t vector;
const char* string;
byte color[4];
const dvar_t* indirect[3];
} naked;
uint64_t encrypted;
};
struct DvarData
{
DvarValue current;
DvarValue latched;
DvarValue reset;
};
struct dvar_t
{
BO4_AssetRef_t name;
char padding_unk1[8];
DvarData* value;
dvarType_t type;
unsigned int flags;
DvarLimits domain;
char padding_unk2[8];
};
//////////////////////////////////////////////////////////////////////////
// SYMBOLS //
//////////////////////////////////////////////////////////////////////////
template <typename T>
class symbol
{
public:
symbol(const size_t address)
: address_(reinterpret_cast<T*>(address))
{
}
T* get() const
{
return address_;
}
operator T* () const
{
return this->get();
}
T* operator->() const
{
return this->get();
}
private:
T* address_;
};
// Main Functions
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{ 0x14288B410_g };
// CMD
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{ 0x143CDE880_g };
// Dvar
WEAK symbol<void* (const char* dvarName)> Dvar_FindVar{ 0x143CEBE40_g };
WEAK symbol<void* (void* dvarHash)> Dvar_FindVar_Hash{ 0x143CEBED0_g };
// Live Functions
WEAK symbol<bool(uint64_t, int*)> Live_GetConnectivityInformation{ 0x1437FA460_g };
// Rendering Functions
WEAK symbol<void(const char* text, int maxChars, void* font, float x, float y, float xScale, float yScale, float rotation, float* color, int style, int cursorPos, char cursor, float padding)> T8_AddBaseDrawTextCmd{ 0x143616B60_g };
WEAK symbol<void(float x, float y, float w, float h, float, float, float, float, float* color, void* material)> R_AddCmdDrawStretchPic{ 0x143616790_g };
WEAK symbol<int(void* font)> R_TextHeight{ 0x1435B2350_g };
WEAK symbol<int(void* font, float scale)> UI_TextHeight{ 0x143CD6560_g };
WEAK symbol<int(int localClientNum, const char* text, int maxChars, void* font)> R_TextWidth{ 0x1435B2530_g };
WEAK symbol<int(int localClientNum, const char* text, int maxChars, void* font, float scale)> UI_TextWidth{ 0x143CD65B0_g };
WEAK symbol<ScreenPlacement* (int localClientNum)> ScrPlace_GetView{ 0x142876E70_g };
WEAK symbol<bool()> Com_IsInGame{ 0x14288FDB0_g };
WEAK symbol<int> keyCatchers{ 0x148A53F84_g };
WEAK symbol<PlayerKeyState> playerKeys{ 0x148A3EF80_g };
WEAK symbol<sharedUiInfo_t> sharedUiInfo{ 0x14F956850_g };
#define R_AddCmdDrawText(TXT, MC, F, X, Y, XS, YS, R, C, S) \
T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, -1, 0, 0)
#define R_AddCmdDrawTextWithCursor(TXT, MC, F, X, Y, XS, YS, R, C, S, CP, CC) \
T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, CP, CC, 0)
#define Com_Error(code, fmt, ...) \
Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
}

View File

@ -1,115 +0,0 @@
#pragma once
#include "definitions\discovery.hpp"
#define WEAK __declspec(selectany)
namespace game
{
extern std::string version_string;
typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
struct T8_Hash_t
{
int64_t value;
int64_t wtf;
};
struct ScreenPlacement
{
vec2_t scaleVirtualToReal;
vec2_t scaleVirtualToFull;
vec2_t scaleRealToVirtual;
vec2_t virtualViewableMin;
vec2_t virtualViewableMax;
vec2_t virtualTweakableMin;
vec2_t virtualTweakableMax;
vec2_t realViewportBase;
vec2_t realViewportSize;
vec2_t realViewportMid;
vec2_t realViewableMin;
vec2_t realViewableMax;
vec2_t realTweakableMin;
vec2_t realTweakableMax;
vec2_t subScreen;
float hudSplitscreenScale;
};
enum itemTextStyle
{
ITEM_TEXTSTYLE_NORMAL = 0,
ITEM_TEXTSTYLE_SHADOWED = 3,
ITEM_TEXTSTYLE_SHADOWEDMORE = 6,
ITEM_TEXTSTYLE_BORDERED = 7,
ITEM_TEXTSTYLE_BORDEREDMORE = 8,
ITEM_TEXTSTYLE_MONOSPACE = 128,
ITEM_TEXTSTYLE_MONOSPACESHADOWED = 132,
};
enum errorParm
{
ERR_FATAL = 0,
ERR_DROP = 1,
ERR_SERVERDISCONNECT = 2,
ERR_DISCONNECT = 3,
ERR_SCRIPT = 4,
ERR_SCRIPT_DROP = 5,
ERR_LOCALIZATION = 6,
ERR_MAPLOADERRORSUMMARY = 7,
};
template <typename T>
class symbol
{
public:
symbol(const size_t address)
: address_(reinterpret_cast<T*>(address))
{
}
T* get() const
{
return address_;
}
operator T* () const
{
return this->get();
}
T* operator->() const
{
return this->get();
}
private:
T* address_;
};
// Main Functions
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{ 0x14288B410_g };
// Live Functions
WEAK symbol<bool(uint64_t, int*)> Live_GetConnectivityInformation{ 0x1437FA460_g };
// Rendering Functions
WEAK symbol<void(const char* text, int maxChars, void* font, float x, float y, float xScale, float yScale, float rotation, float* color, int style, int cursorPos, char cursor, float padding)> T8_AddBaseDrawTextCmd{ 0x143616B60_g };
WEAK symbol<void*(ScreenPlacement* scrPlace, int fontEnum, float scale)> UI_GetFontHandle{ 0x143CD0A30_g };
WEAK symbol<int(void* font)> R_TextHeight{ 0x1435B2350_g }; // [BO4-BNET-2023]
WEAK symbol<ScreenPlacement* (int localClientNum)> ScrPlace_GetView{ 0x142876E70_g };
#define R_AddCmdDrawText(TXT, MC, F, X, Y, XS, YS, R, C, S) \
T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, -1, 0, 0)
#define R_AddCmdDrawTextWithCursor(TXT, MC, F, X, Y, XS, YS, R, C, S, CP, CC) \
T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, CP, CC, 0)
#define Com_Error(code, fmt, ...) \
Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
#pragma once
namespace fnv1a
{
uint64_t generate_hash(const char* string);
}
namespace variables
{
struct varInfo
{
std::string name;
const char* desc;
uint64_t fnv1a;
};
struct varEntry : varInfo
{
uintptr_t pointer = 0;
};
extern std::vector<varEntry> dvars_record;
extern std::vector<varEntry> commands_record;
std::vector<const char*> get_dvars_list();
std::vector<const char*> get_commands_list();
}

View File

@ -1,14 +1,14 @@
#include <std_include.hpp>
#include "objects.hpp"
#include "protobuf_helper.hpp"
#include "resource.hpp"
#include <utils/nt.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/cryptography.hpp>
#include <component/platform.hpp>
#include "protobuf_helper.hpp"
#include "objects.hpp"
#include "resource.hpp"
#include <utils/nt.hpp>
#define PUBLISHER_OBJECTS_ENUMERATE_LPC_DIR

View File

@ -1,9 +1,9 @@
#include <std_include.hpp>
#include "../services.hpp"
#include <picoproto.h>
#include <utils/io.hpp>
#include "../objects.hpp"
#include <picoproto.h>
#include <utils/io.hpp>
#include <utils/cryptography.hpp>
#include <component/platform.hpp>

View File

@ -1,5 +1,6 @@
#include <std_include.hpp>
#include "../services.hpp"
#include <utils/io.hpp>
#include <component/platform.hpp>

View File

@ -0,0 +1,92 @@
#include <std_include.hpp>
#include <utils/hook.hpp>
#include <stack>
namespace spoofcall
{
void* callstack_proxy_addr{ nullptr };
static thread_local const void* address_to_call{};
namespace
{
thread_local std::stack<uint64_t> address_stack{};
const void* get_address_to_call()
{
return address_to_call;
}
void store_address(const uint64_t address)
{
address_stack.push(address);
}
uint64_t get_stored_address()
{
const auto res = address_stack.top();
address_stack.pop();
return res;
}
void callstack_return_stub(utils::hook::assembler& a)
{
a.push(rax);
a.pushad64();
a.call_aligned(get_stored_address);
a.mov(qword_ptr(rsp, 0x80), rax);
a.popad64();
a.add(rsp, 8);
a.jmp(qword_ptr(rsp, -8));
}
uint64_t get_callstack_return_stub()
{
const auto placeholder = 0x1403CF1C6_g;
utils::hook::set<uint8_t>(placeholder - 2, 0xFF); // fakes a call
utils::hook::nop(placeholder, 1);
utils::hook::jump(placeholder + 1, utils::hook::assemble(callstack_return_stub));
return placeholder;
}
void callstack_stub(utils::hook::assembler& a)
{
a.push(rax);
a.pushad64();
a.call_aligned(get_address_to_call);
a.mov(qword_ptr(rsp, 0x80), rax);
a.mov(rcx, qword_ptr(rsp, 0x88));
a.call_aligned(store_address);
a.mov(rax, get_callstack_return_stub());
a.mov(qword_ptr(rsp, 0x88), rax);
a.popad64();
a.add(rsp, 8);
a.jmp(qword_ptr(rsp, -8));
}
}
void* get_spoofcall_proxy(const void* funcAddr)
{
static bool spoofer_initialized = false;
if (!spoofer_initialized)
{
callstack_proxy_addr = utils::hook::assemble(callstack_stub);
}
address_to_call = funcAddr;
return callstack_proxy_addr;
}
}

View File

@ -0,0 +1,18 @@
#pragma once
namespace spoofcall
{
void* get_spoofcall_proxy(const void* funcAddr);
template <typename T, typename... Args>
static T invoke(size_t funcAddr, Args ... args)
{
return static_cast<T(*)(Args ...)>(get_spoofcall_proxy(reinterpret_cast<void*>(funcAddr)))(args...);
}
template <typename T, typename... Args>
static T invoke(void* funcAddr, Args ... args)
{
return static_cast<T(*)(Args ...)>(get_spoofcall_proxy(funcAddr))(args...);
}
}