- Статус
- Оффлайн
- Регистрация
- 13 Июл 2018
- Сообщения
- 1,534
- Реакции
- 1,633
Как игра собирает информацию о вас для будущих банов, а вы об этом даже не подозреваете
Всем привет! Решил сделать предновогодний подарок сообществу. Давно не было нормальных глубоких технических статей, так что устраивайтесь поудобнее — будет что почитать в новогодние праздники :)
В этой статье я полностью разберу ingame античит-проверки Counter-Strike 2 — встроенные в клиент защиты (как и классический AntiTamper). Valve внедрила многоуровневую защиту прямо в client.dll, которая работает параллельно с VAC.
В моей статье про AntiTamper (https://www.unknowncheats.me/forum/counter-strike-2-a/695907-analyzing-antitamper-system.html) я дал ясно понять, что игра собирает достаточно информации, чтобы в будущем заблокировать вас. Многие думают, что с ними ничего не произойдет, если они сделают функцию Glow через систему Netvar. Я ясно объяснил, почему это плохо, и привел примеры работы системы сбора данных (https://www.unknowncheats.me/forum/4365681-post6.html).
Введение
Эта статья продолжает анализ античит-систем CS2, фокусируясь на инфраструктуре диагностического сбора данных. Эта система собирает обширную телеметрию о состоянии игрового клиента и отправляет её на серверы Valve для анализа.
Ключевой момент: Это система сбора данных, а не механизм мгновенного детекта. Собранные данные анализируются на стороне сервера для построения профилей игроков с течением времени и корреляции с другими античит-сигналами.
Поиск функций
Диагностическая система обнаруживается через анализ протокола пользовательских сообщений. Ищем CSVCMsg_UserMessage_Setup в IDA и изучаем кросс-референсы для поиска обработчиков сообщений. Сообщения 158-386 (второй параметр с типом EBaseUserMessages) используются для коммуникации с сервером.
Код:
enum class EBaseUserMessages : std::uint32_t
{
UM_AchievementEvent = 0x65,
UM_CloseCaption = 0x66,
// ... (сокращено для краткости)
UM_RequestUtilAction = 0x9d, // 157
UM_UtilActionResponse = 0x9e, // 158
UM_DllStatusResponse = 0x9f, // 159
UM_RequestInventory = 0xa0, // 160
UM_InventoryResponse = 0xa1, // 161
UM_RequestDiagnostic = 0xa2, // 162
UM_DiagnosticResponse = 0xa3, // 163
// ...
};
Глобальное диагностическое состояние: CDllVerificationMonitor
Система поддерживает состояние в глобальной структуре по адресу 0x181CFB790, которая является экземпляром класса CDllVerificationMonitor. Этот класс зарегистрирован как IGameSystem и управляет всем диагностическим сбором данных.
Код:
struct CDllVerificationMonitor {
void* vtable; // +0x00
RTL_CRITICAL_SECTION critical_section; // +0x08
// Коллбэки, разрешенные из экспортов cs2.exe
int (*GetTotalFilesLoaded)(); // +0x18
int (*CountFilesNeedTrustCheck)(); // +0x20
int (*CountFilesCompletedTrustCheck)(); // +0x28
bool (*BSecureAllowed)(char*, int, int); // +0x30
int (*CountItemsToReport)(); // +0x38
// Отслеживание состояния отправки
int last_send_state; // +0x40
bool suppress_flag; // +0x41
DWORD last_send_time; // +0x48
int total_files_cached; // +0x50
int prev_files_loaded; // +0x54
int send_counter; // +0x58
// Хендлы системных DLL для верификации PE timestamp
HMODULE kernel32_dll; // +0x60
HMODULE kernelbase_dll; // +0x68
HMODULE ntdll_dll; // +0x70
HMODULE gameoverlay_dll; // +0x78
// Указатели на функции NT API
void* NtQueryInformationThread; // +0x80
void* callback_1; // +0x88
void* cpuid_callback; // +0x90
void* NtOpenFile; // +0x98
void* LoadLibraryExW; // +0xA0
// Отслеживаемые адреса памяти
void* monitored_addr_1; // +0xA8 - Поле 31
void* monitored_addr_2; // +0xB0 - Поле 32
void* monitored_addr_3; // +0xB8 - Поле 1 (хендл модуля)
void* monitored_addr_4; // +0xC0 - Поле 2 (старт потока)
void* monitored_addr_5; // +0xC8 - Поле 5 (адрес возврата)
void* monitored_addr_6; // +0xD0 - Поле 16 (база аллокации)
// Отслеживаемые значения
DWORD monitored_value_1; // +0xD8 - Поле 3 (ID потока)
DWORD monitored_value_2; // +0xDC - Поле 4 (защита)
// Буфер подписи
char signature_buffer[4096]; // +0xE4 - Поле 17 (дамп памяти)
DWORD signature_size; // +0x10E4 - Поле 18
};
Процесс инициализации
Инициализация системы происходит в виртуальной таблице функций по индексу 27 класса CDllVerificationMonitor:
Код:
// vtable index 27
bool __fastcall CheckDllIntegrity(CDllVerificationMonitor* state)
{
// Вызываем ValidateDiagnosticState для проверки нарушений
bool state_changed = ValidateDiagnosticState(state);
if (state_changed)
{
state->integrity_checked = true;
}
return state_changed;
}
Эта функция вызывает ValidateDiagnosticState, которая инициализирует необходимые данные для телеметрии:
Код:
bool __fastcall ValidateDiagnosticState(CDllVerificationMonitor* state)
{
if (!g_pTime)
InitGlobalTimePID();
LoadSystemDLLHandles(state);
InitializeDiagnosticCallbacks(state);
if (!state->GetTotalFilesLoaded || !state->CountFilesNeedTrustCheck ||
!state->CountFilesCompletedTrustCheck || !state->BSecureAllowed ||
!state->CountItemsToReport)
{
return false;
}
int file_count = state->CountItemsToReport();
state->total_files_cached = file_count;
if (!state->suppress_flag)
{
uint8_t has_violations = state->BSecureAllowed(NULL, 0, 0);
state->suppress_flag = (has_violations == 0);
if (state->suppress_flag)
{
g_bClientIsntAllowedToPlayOnSecureServers = 1;
Source2EngineToClient001->NotifySecurityStateChanged();
}
}
return (state->total_files_cached != state->prev_files_loaded);
}
Глобальная переменная по адресу 0x181CFB788 (g_bClientIsntAllowedToPlayOnSecureServers) — булевый флаг, который контролирует, может ли клиент подключаться к серверам, защищенным VAC. Устанавливается в 1 при обнаружении нарушений или при использовании параметра запуска -insecure.
LoadSystemDLLHandles
Собирает хендлы системных DLL, используемых для верификации PE timestamp:
Код:
void __fastcall LoadSystemDLLHandles(CDllVerificationMonitor* state)
{
if (state->ntdll_dll != NULL)
return;
state->ntdll_dll = GetModuleHandleA("ntdll.dll");
if (state->ntdll_dll)
{
state->NtOpenFile = GetProcAddress(state->ntdll_dll, "NtOpenFile");
state->NtQueryInformationThread = GetProcAddress(state->ntdll_dll, "NtQueryInformationThread");
}
state->kernelbase_dll = GetModuleHandleA("kernelbase.dll");
if (state->kernelbase_dll)
{
state->LoadLibraryExW = GetProcAddress(state->kernelbase_dll, "LoadLibraryExW");
}
state->kernel32_dll = GetModuleHandleA("kernel32.dll");
state->gameoverlay_dll = GetModuleHandleA("gameoverlayrenderer64.dll");
}
InitializeDiagnosticCallbacks
Разрешает экспортируемые функции из cs2.exe:
Код:
void __fastcall InitializeDiagnosticCallbacks(CDllVerificationMonitor* state)
{
if (state->GetTotalFilesLoaded && state->CountFilesNeedTrustCheck &&
state->CountFilesCompletedTrustCheck && state->BSecureAllowed)
{
return;
}
HMODULE cs2_exe = GetModuleHandleA(NULL);
if (!cs2_exe)
return;
state->GetTotalFilesLoaded = (void*)GetProcAddress(cs2_exe, "GetTotalFilesLoaded");
state->CountFilesNeedTrustCheck = (void*)GetProcAddress(cs2_exe, "CountFilesNeedTrustCheck");
state->CountFilesCompletedTrustCheck = (void*)GetProcAddress(cs2_exe, "CountFilesCompletedTrustCheck");
state->BSecureAllowed = (void*)GetProcAddress(cs2_exe, "BSecureAllowed");
state->CountItemsToReport = (void*)GetProcAddress(cs2_exe, "CountItemsToReport");
}
MonitorThreadContext (DllMain)
Вызывается при DLL_THREAD_ATTACH в точке входа:
Код:
__int64 __fastcall EntryPoint(__int64 unused_hinstDLL, int reason_for_call)
{
if (reason_for_call == DLL_THREAD_ATTACH)
MonitorThreadContext(&g_DiagnosticState);
return 1LL;
}
HMODULE __fastcall MonitorThreadContext(CDllVerificationMonitor* monitor)
{
LPCVOID thread_start_address = NULL;
HMODULE thread_module = NULL;
MEMORY_BASIC_INFORMATION mem_info;
DWORD current_thread_id = GetCurrentThreadId();
LoadSystemDLLHandles(monitor);
if (monitor->NtQueryInformationThread)
{
HANDLE current_thread = GetCurrentThread();
NTSTATUS status = monitor->NtQueryInformationThread(
current_thread,
ThreadQuerySetWin32StartAddress,
&thread_start_address,
sizeof(thread_start_address),
NULL);
if (NT_SUCCESS(status))
{
VirtualQuery(thread_start_address, &mem_info, sizeof(mem_info));
GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR)thread_start_address,
&thread_module);
if (mem_info.Protect == PAGE_EXECUTE_READWRITE ||
thread_module == monitor->kernel32_dll ||
thread_module == monitor->kernelbase_dll)
{
monitor->monitored_addr_3 = thread_module;
monitor->monitored_addr_4 = thread_start_address;
monitor->monitored_value_1 = current_thread_id;
monitor->monitored_value_2 = mem_info.Protect;
monitor->monitored_addr_5 = _ReturnAddress();
monitor->monitored_addr_6 = mem_info.AllocationBase;
uint64_t aligned_address = (uint64_t)thread_start_address & 0xFFFFFFFFFFFFF000ULL;
memcpy(monitor->signature_buffer, (void*)aligned_address, 4096);
monitor->send_counter++;
}
}
}
return thread_module;
}
Регистрация обработчиков
В функции инициализации CSource2Client (vtable index 3) регистрируются обработчики через GameEventSystemServerV001->RegisterCallback:
Код:
void __fastcall InitializeClientSystems(CSource2Client* client)
{
// ...
// Регистрация обработчика сообщения 157 (RequestUtilAction)
GameEventSystemServerV001->RegisterCallback(
0, &ReceiveRequestUtilAction, &g_RequestUtilActionCallbackHandle, 0);
// Регистрация обработчика сообщения 160 (RequestInventory)
GameEventSystemServerV001->RegisterCallback(
0, &SendInventoryRequest, &g_RequestInventoryCallbackHandle, 0);
// Регистрация обработчика сообщения 162 (RequestDiagnostic)
GameEventSystemServerV001->RegisterCallback(
0, &ReceiveRequestDiagnostic, &g_RequestDiagnosticCallbackHandle, 0);
// ...
}
SerializeSystemDiagnostics - Основная функция сбора данных
Эта функция является сердцем диагностической системы. Она собирает более 40 различных диагностических полей и отправляет их как сообщение 159. Функция работает в несколько фаз:
Код:
bool __fastcall SerializeSystemDiagnostics(
CDllVerificationMonitor* monitor,
CUserMessage_DllStatus* response,
CUtlBuffer* detail_buffer,
uint8_t flags)
{
// ФАЗА 1: Снимок модулей
InitializeDiagnosticCallbacks(monitor);
CModuleListSnapshot module_snapshot;
uint8_t module_buffer[8200];
CModuleListSnapshot::Constructor(&module_snapshot, module_buffer, 0x2000);
DWORD current_pid = GetCurrentProcessId();
// КРИТИЧНО: Захват ВСЕХ загруженных DLL
if (CModuleListSnapshot::Capture(&module_snapshot, current_pid))
{
const void* module_iter = NULL;
const char* module_name;
void* module_base;
uint64_t module_size;
while (CModuleListSnapshot::GetNextModule(
&module_snapshot,
&module_iter,
&module_name,
&module_base,
&module_size))
{
CModule* module_entry = AllocateModuleEntry(response);
SetModuleBase(module_entry, module_base);
SetModuleName(module_entry, module_name);
SetModuleSize(module_entry, module_size);
}
}
// ФАЗА 2: Подготовка буфера нарушений
bool has_violations = false;
// Резервируем 100 КБ для детального отчета
CUtlBuffer::EnsureCapacity(detail_buffer, 102400);
char* violation_report = CUtlBuffer::GetWritePointer(detail_buffer, 102400);
if (violation_report)
*violation_report = '\0';
// ФАЗА 3: Проверка целостности файлов
if (monitor->GetTotalFilesLoaded &&
monitor->CountFilesNeedTrustCheck &&
monitor->CountFilesCompletedTrustCheck &&
monitor->BSecureAllowed &&
monitor->CountItemsToReport)
{
int total_files = monitor->GetTotalFilesLoaded();
int files_need_check = monitor->CountFilesNeedTrustCheck();
int files_completed = monitor->CountFilesCompletedTrustCheck();
// КЛЮЧЕВАЯ ПРОВЕРКА: BSecureAllowed
if (violation_report)
{
has_violations = monitor->BSecureAllowed(
violation_report,
102400,
flags
);
// Игнорируем нарушения для режима -insecure
if (monitor->suppress_flag)
has_violations = false;
}
// Добавляем отчет о нарушениях в ответ
if (violation_report && *violation_report)
{
response->flags |= 1;
CBufferString report_str;
CBufferString::SetString(&report_str, violation_report);
SetViolationReport(response, &report_str);
}
// Добавляем командную строку
ICommandLine* cmdline = CommandLine();
const char* cmdline_str = cmdline->GetCmdLine();
response->flags |= 2;
CBufferString cmdline_buf;
CBufferString::SetString(&cmdline_buf, cmdline_str);
SetCommandLine(response, &cmdline_buf);
// ФАЗА 4: Базовая статистика
SetDiagnosticField(response, 21, total_files);
SetDiagnosticField(response, 22, files_need_check);
SetDiagnosticField(response, 23, files_completed);
SetDiagnosticField(response, 24, GetCurrentProcessId());
SetDiagnosticField(response, 25, GetCurrentThreadId());
SetDiagnosticField(response, 29, has_violations ? 1 : 0);
// ФАЗА 5: PE Timestamps системных DLL
HMODULE client_dll = NULL;
GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR)&GetPETimestamp,
&client_dll
);
HMODULE cs2_exe = GetModuleHandleA(NULL);
// Поля 8-15, 38-39: Хендлы модулей и timestamp'ы
SetDiagnosticField(response, 8, (uint64_t)client_dll);
SetDiagnosticField(response, 9, (uint64_t)monitor->kernel32_dll);
SetDiagnosticField(response, 10, (uint64_t)cs2_exe);
SetDiagnosticField(response, 14, (uint64_t)monitor->ntdll_dll);
SetDiagnosticField(response, 38, (uint64_t)monitor->gameoverlay_dll);
SetDiagnosticField(response, 11, GetPETimestamp(client_dll));
SetDiagnosticField(response, 12, GetPETimestamp(monitor->kernel32_dll));
SetDiagnosticField(response, 13, GetPETimestamp(cs2_exe));
SetDiagnosticField(response, 15, GetPETimestamp(monitor->ntdll_dll));
SetDiagnosticField(response, 39, GetPETimestamp(monitor->gameoverlay_dll));
// ФАЗА 6: Данные MonitorThreadContext
SetDiagnosticField(response, 1, (uint64_t)monitor->monitored_addr_3);
SetDiagnosticField(response, 2, (uint64_t)monitor->monitored_addr_4);
SetDiagnosticField(response, 3, monitor->monitored_value_1);
SetDiagnosticField(response, 4, monitor->monitored_value_2);
SetDiagnosticField(response, 5, (uint64_t)monitor->monitored_addr_5);
SetDiagnosticField(response, 16, (uint64_t)monitor->monitored_addr_6);
// Поле 17: 4KB дамп памяти по адресу потока
SetDiagnosticFieldBuffer(response, 17,
monitor->signature_buffer, 4096);
SetDiagnosticField(response, 18, monitor->signature_size);
SetDiagnosticField(response, 31, (uint64_t)monitor->monitored_addr_1);
SetDiagnosticField(response, 32, (uint64_t)monitor->monitored_addr_2);
// ФАЗА 7: Данные коллбэков
uint64_t callback_val1 = 0;
uint64_t callback_val2 = 0;
if (monitor->callback_1)
{
monitor->callback_1(&callback_val1, &callback_val2);
}
SetDiagnosticField(response, 35, callback_val1);
SetDiagnosticField(response, 36, callback_val2);
SetDiagnosticField(response, 34, GetCurrentThreadId());
// ФАЗА 8: Состояние приложения
uint32_t app_state = 0;
if (VApplication001)
{
if (VApplication001->IsActive())
app_state |= 1;
if (VApplication001->HasFocus())
app_state |= 2;
}
SetDiagnosticField(response, 33, app_state);
// ФАЗА 9: Системная информация
SetDiagnosticField(response, 27, Plat_GetOSType());
FILETIME system_time;
GetSystemTimeAsFileTime(&system_time);
SetDiagnosticField(response, 28, *(uint64_t*)&system_time);
SetDiagnosticField(response, 30, (uint64_t)monitor->callback_1);
// ФАЗА 10: Обнаружение отладчика
BOOL debugger_present = IsDebuggerPresent();
SetDiagnosticField(response, 37, debugger_present);
// ФАЗА 11: CPUID (обнаружение VM)
// Примечание: Не полностью уверен, но это возможно
__m128i cpuid_data;
memset(&cpuid_data, 0, sizeof(cpuid_data));
cpuid_data.m128i_i32[0] = 148;
if (monitor->cpuid_callback)
{
monitor->cpuid_callback(&cpuid_data);
}
// Упаковываем результат CPUID
uint32_t cpuid_packed =
cpuid_data.m128i_i32[3] |
(cpuid_data.m128i_i32[2] << 16) |
(cpuid_data.m128i_i32[1] << 24);
SetDiagnosticField(response, 40, cpuid_packed);
}
CModuleListSnapshot::Purge(&module_snapshot);
return has_violations;
}
Разбивка по фазам:
Фаза 1: Снимок модулей
Захватывает ВСЕ загруженные DLL с помощью CModuleListSnapshot. Мгновенно обнаруживает любые инжектированные чит-модули.
Фаза 2: Подготовка буфера нарушений
Резервирует 100KB для детальных отчетов о нарушениях от BSecureAllowed.
Фаза 3: Проверка целостности файлов
Вызывает BSecureAllowed для проверки целостности файлов и генерации отчетов о нарушениях.
Фазы 4-11: Диагностические поля
Собирает и отправляет:
- Поля 21-25: Статистика файлов, PID, TID, флаги нарушений
- Поля 8-15, 38-39: PE timestamps критических DLL
- Поля 1-5, 16-18, 31-32: Данные MonitorThreadContext включая 4KB дамп памяти
- Поля 34-36: Данные коллбэков
- Поле 33: Состояние приложения (активно/в фокусе)
- Поля 27-28, 30: Системная информация
- Поле 37: IsDebuggerPresent()
- Поле 40: Результат CPUID для обнаружения VM
Все данные отправляются через сообщение 159 (DiagnosticResponse).
Сообщение 157: Проверка целостности ConVar
Этот обработчик выполняет массовую проверку всех консольных переменных и отправляет детальный отчет. Работает в двух режимах:
Режим 1: Все ConVar
- Обрабатывает имя и флаги (12 байт)
- Быстрая проверка целостности всей конфигурации
Режим 2: FCVAR_REPLICATED ConVar
- Фильтр: 0x4000
- Исключение: 0x10000
- Обрабатывает имя, значение и флаги (16 байт)
- Детальная проверка критических для геймплея переменных
Код:
void __fastcall ReceiveRequestUtilAction(CUserMessageRequestUtilAction_t* request)
{
INetworkClient* net_client = Source2EngineToClient001->GetNetworkClient(0);
if (!net_client)
return;
netadr_t server_addr;
netadr_t::Initialize(&server_addr);
const char* addr_str = net_client->GetServerAddress();
SetupAddressForConnection(&server_addr, addr_str, false);
if (server_addr.type != 2)
{
netadr_t::Clear(&server_addr);
return;
}
netadr_t::Clear(&server_addr);
int max_items_mode1 = request->param1;
int max_items_mode2 = request->param2;
int detail_count_mode1 = request->param3;
int detail_count_mode2_1 = request->param4;
int detail_count_mode2_2 = request->param5;
CUserMessage_UtilMsg_Response response;
CUserMessage_UtilMsg_Response::Constructor(&response);
// РЕЖИМ 1
int mode1_count = 0;
int mode1_total = 0;
uint32_t crc1 = ComputeConVarChecksum(
&response, 0LL, 0LL, 0,
max_items_mode1, &mode1_total, &mode1_count);
response.flags |= 0x83;
response.crc_mode1 = crc1;
response.count_mode1 = mode1_total;
response.tag_mode1 = mode1_count;
AddConVarDetails(&response, 0LL, 0LL,
detail_count_mode1, detail_count_mode2_1, 1);
// РЕЖИМ 2
int mode2_count = 0;
int mode2_total = 0;
uint32_t crc2 = ComputeConVarChecksum(
&response, 0x4000LL, 0x10000LL, 1,
max_items_mode2, &mode2_total, &mode2_count);
response.flags |= 0x10C;
response.crc_mode2 = crc2;
response.count_mode2 = mode2_total;
response.tag_mode2 = mode2_count;
AddConVarDetails(&response, 0x4000LL, 0x10000LL,
detail_count_mode1, detail_count_mode2_2, 2);
response.flags |= 0x10;
response.pe_timestamp = dword_181CFB774;
int os_type = Plat_GetOSType();
response.flags |= 0x20;
response.os_type = os_type;
SendUserMessage(158, &response);
CleanupUserMessage(&response);
}
ComputeConVarChecksum — вычисление хэша CRC32
C++:
uint32_t __fastcall ComputeConVarChecksum(
CUserMessage_UtilMsg_Response* response,
uint64_t filter_flags, // ConVar must have these flags
uint64_t exclude_flags, // ConVar must NOT have these flags
int mode, // 0 or 1
int max_items, // Maximum number of items to process
int* out_total, // Output: total found
int* out_count) // Output: processed
{
if (!VEngineCvar007)
return 0;
// Create a temporary CUtlRBTree for sorting ConVars
CUtlRBTree<ConVar*> tree;
tree.alloc_pool = g_pMemAlloc->Alloc(160000);
tree.capacity = CalculateCapacity(g_pMemAlloc->GetSize(tree.alloc_pool));
tree.root = -1;
tree.count = 0;
tree.free_list = -1;
// Phase 1: Collect all ConVars into the tree
ICvar::Iterator iter;
ConVarHandle first = VEngineCvar007->GetFirstConVar(&iter);
if (first != INVALID_CONVAR_HANDLE)
{
do
{
// Retrieve ConVar
ConVar* cvar = VEngineCvar007->GetConVar(first);
// Insert into sorted tree (sorted by name)
tree.Insert(cvar);
// Move to next ConVar
first = VEngineCvar007->GetNextConVar(&iter, first);
}
while (first != INVALID_CONVAR_HANDLE);
}
// Phase 2: Traverse tree and compute CRC
uint32_t crc32 = 0xFFFFFFFF; // Initial CRC32 value
int processed = 0;
int skip_count = 0;
// Traverse the tree (in-order = alphabetical order)
for (TreeNode* node = tree.GetLeftmost();
node != nullptr;
node = tree.GetNext(node))
{
ConVar* cvar = node->data;
uint64_t cvar_flags = cvar->flags;
// Apply filters
if (filter_flags && (cvar_flags & filter_flags) != filter_flags)
continue;
if (exclude_flags && (cvar_flags & exclude_flags) != 0)
continue;
// Skip the first skip_count items
if (skip_count < max_items)
{
skip_count++;
continue;
}
if (processed >= max_items)
break;
// Compute CRC32 for this ConVar
struct ConVarData {
uint32_t name_hash; // +0x00 - Name hash
uint32_t value_hash; // +0x04 - Value hash (if mode=1)
uint64_t flags; // +0x08 - ConVar flags
};
ConVarData data;
// Hash ConVar name
CBufferString name_buffer;
cvar->GetName(&name_buffer);
const char* name_str = name_buffer.GetString();
uint32_t name_hash = 1171724434; // Initial value
for (const char* p = name_str; *p; p++)
name_hash = *p + 33 * name_hash;
data.name_hash = name_hash;
data.flags = cvar_flags;
// Hash value (only in mode 1)
if (mode == 1)
{
CBufferString value_buffer;
cvar->GetStringValue(-1, &value_buffer);
const char* value_str = value_buffer.GetString();
uint32_t value_hash = 1171724434;
for (const char* p = value_str; *p; p++)
value_hash = *p + 33 * value_hash;
data.value_hash = value_hash;
CBufferString::Purge(&value_buffer);
// CRC32 processes 16 bytes
CRC32_ProcessBuffer(&crc32, &data, 16);
}
else
{
// CRC32 processes 12 bytes (without value_hash)
CRC32_ProcessBuffer(&crc32, &data, 12);
}
processed++;
}
*out_total = processed;
*out_count = tree.count; // Total ConVars in the tree
// Cleanup
tree.Destroy();
return ~crc32; // Finalize CRC32
}
Добавление подробной информации — AddConVarDetails
Эта функция добавляет в ответ подробные записи о конкретных ConVar.
C++:
void __fastcall AddConVarDetails(
CUserMessage_UtilMsg_Response* response,
uint64_t filter_flags,
uint64_t exclude_flags,
int start_index, // Start from this index
int max_details, // Maximum details to add
int detail_type) // 1 or 2
{
if (!VEngineCvar007)
return;
// Create the same tree
CUtlRBTree<ConVar*> tree;
// ... (similar to ComputeConVarChecksum)
int current_index = 0;
int details_added = 0;
for (TreeNode* node = tree.GetLeftmost();
node != nullptr && details_added < max_details;
node = tree.GetNext(node))
{
ConVar* cvar = node->data;
uint64_t cvar_flags = cvar->flags;
// Apply filters
if (filter_flags && (cvar_flags & filter_flags) != filter_flags)
continue;
if (exclude_flags && (cvar_flags & exclude_flags) != 0)
continue;
// Start from start_index
if (current_index < start_index)
{
current_index++;
continue;
}
// Create Detail Entry
CUserMessage_UtilMsg_Response_ItemDetail* detail;
if (response->details_capacity)
{
// There is free space in the pre-allocated array
detail = response->details_buffer[response->details_count];
response->details_count++;
}
else
{
// Allocate a new detail
detail = AllocateDetail(response->details_allocator);
AddDetailToRepeatedField(&response->details, detail);
}
// Fill in detail
detail->flags = 0x0A;
detail->index = current_index;
detail->crc32 = ComputeDetailCRC32(cvar);
// Copy ConVar name
CBufferString name_buffer;
cvar->GetName(&name_buffer);
const char* name_str = name_buffer.GetString();
size_t name_len = strlen(name_str);
// Allocate memory for the name
std::string* name_field = &detail->name;
name_field->reserve(name_len);
name_field->assign(name_str, name_len);
detail->flags |= 0x01; // name field is set
CBufferString::Purge(&name_buffer);
details_added++;
current_index++;
}
// Set flag in response
response->flags |= 0x40;
response->detail_type = detail_type;
// Cleanup
tree.Destroy();
}
Подробности расчета CRC32
C++:
uint32_t ComputeDetailCRC32(ConVar* cvar)
{
uint32_t crc32 = 0xFFFFFFFF;
struct DetailData {
uint32_t name_hash;
uint32_t value_hash;
uint64_t flags;
};
DetailData data;
// Name hash
CBufferString name_buffer;
cvar->GetName(&name_buffer);
const char* name_str = name_buffer.GetString();
data.name_hash = HashString(name_str);
CBufferString::Purge(&name_buffer);
// Flags
data.flags = cvar->flags;
// Value hash
CBufferString value_buffer;
cvar->GetStringValue(-1, &value_buffer);
const char* value_str = value_buffer.GetString();
data.value_hash = HashString(value_str);
CBufferString::Purge(&value_buffer);
// Compute CRC32
CRC32_ProcessBuffer(&crc32, &data, 16);
return ~crc32;
}
Строковая хеш-функция (вариант DJB2)
C++:
uint32_t HashString(const char* str)
{
uint32_t hash = 1171724434;
for (; *str; str++)
hash = *str + 33 * hash;
return hash;
}
Структура ответа
C++:
struct CUserMessage_UtilMsg_Response
{
void* vtable; // +0x00
void* allocator; // +0x08
uint32_t flags; // +0x10
// Mode 1 data
uint32_t crc_mode1; // +0x14 - CRC32 of all ConVars
uint32_t count_mode1; // +0x18 - Number processed
uint32_t tag_mode1; // +0x1C - Total found
// Mode 2 data
uint32_t crc_mode2; // +0x20 - CRC32 with filters
uint32_t count_mode2; // +0x24 - Number processed
uint32_t tag_mode2; // +0x28 - Total found
// Detailed information
RepeatedPtrField<ItemDetail> details; // +0x30 - Array of details
uint32_t detail_type; // +0x48 - Detail type (1 or 2)
// Additional data
uint32_t pe_timestamp; // +0x50 - client.dll PE timestamp
uint32_t os_type; // +0x54 - OS type
// Pre-allocated detail buffer
ItemDetail** details_buffer; // +0x58
uint32_t details_capacity; // +0x60
uint32_t details_count; // +0x64
};
Структура ItemDetail
C++:
struct CUserMessage_UtilMsg_Response_ItemDetail
{
void* vtable; // +0x00
void* allocator; // +0x08
uint32_t flags; // +0x10
std::string name; // +0x18 - ConVar name
uint32_t index; // +0x28 - Index in the list
uint32_t crc32; // +0x2C - CRC32 hash
};
Система создает отсортированное RB-дерево всех ConVars и вычисляет хэши CRC32. Для конкретных ConVars отправляется подробная информация, включая их полное имя, индекс и индивидуальный хэш CRC32.
Последнее редактирование: