i hate p2cs
-
Автор темы
- #1
Перед началом хочу выразить огромную благодарность Satoru за помощь с отображением иконок читов в табе т.к я думал что без затрагивания panoramы и js это невозможно.
В данной статье будет использована информация с репозитория
Начнем с предисловия. В данной статье я не буду вдаваться в подробности как вообще работают голосовые пакеты в игре. Однако без понимания этого базиса будет очень сложно объяснить принцип работы. Поэтому рассмотрим краткий пример:
- Любая сетевая игра работает за счёт постоянного обмена данными между клиентом и сервером. В нашем же случае с ксго, игрок(клиент) создаёт новый "голосовой" пакет данных (экземпляр класса CCLCMsg_VoiceData и CSVCMsg_VoiceData), то есть конструирует и заполняет массив данных и в конечном итоге отправляет на сервер (CNetChan::SendNetMsg) другим игрокам. Читы делают это вручную, зачастую используя готовые функции движка игры, формируя свой "голосовой пакет" который ничем не отличается от нативного игрового кроме как будучи заполненным кастомными данными чита. Нам же нужно получить эти данные, расшифровать и сравнить нужные переменные для нашего чит ревилера.
- В добавку к этому всему, не забываем, что у каждого игрока есть разные идентификаторы и в данном случае нам понадобится идентификатор xuid_low (который так же присутствует в классах CCLCMsg_VoiceData и CSVCMsg_VoiceData), но для чего он нужен, мы узнаем немного позже.
Перейдём к написанию самого чит ревилера:
- Как любой нормальный кодер мы сразу сталкиваемся с логическими "проблемами" на которые нам нужно придумать решения, то как мы будем хранить полученные данные, как часто вызывать функции и как сделать так чтобы всё это дело было оптимизировано!
1) Хранение данных:
В моём конкретном случае, на момент написания ревилера, я использовал готовую базу чит опая (airflow) и в моём распоряжении имелась структура esp_player_t и класс c_esp, которые глобально использовались в проекте, поэтому, к счастью, мне не пришлось придумывать велосипед.
Проблема хранения решена.
Наконец перейдём к получению этих самых данных, с помощью которых мы сможем детектить какой чит использует конкретный игрок, для чит ревилера нам нужно получить(перехватить) голосовые данные о которых было сказано в предисловии.
2) Получение "голосовых" данных от сервера
Пишем хук на эту функцию, для получения "голосовых" данных:
Данные получены.
Переходим к их сравнению:
Создаём заголовки
Создаём вспомогательные функции для сравнения полученных нами данных для будущего чит ревилера.
Краткий обзор функций благодаря которым и функционирует наш чит-ревилер:
Функции имеют тип возвращаемого значения - boolean, то-есть если нужный нам чит был найден то возвращаем true!
Рассмотрим первую функцию для обнаружения юзеров скита:
Функция имеет в себе код аллоцирования памяти для загадочного шеллкода, как работает шеллкод я не знаю, но в дальнейшем поинтер на эту память вызывается с параметрами
Остальные примеры обнаружения других читов - более простые, суть заключается в сравнении той инфы которую читы формируют и отправляют на сервер , в этой инфе (голосовые пакеты описанные выше) имеется идентификатор xuid_low и соответственно у каждого чита он разный, чтобы не дампить самому эти значения, методом поиска луашек на чит ревилер, были найдены hex значения которые юзаются для обнаружения различных читов (сравнивая переменную xuid_low из полученной инфы в SVCMsg_VoiceData хуке)
Собираем всё это в кучу.
Основная функция ревилера которую я написал для работы с полученной инфой из SVCMsg_VoiceData хука:
Данные получены, функции написаны, читы других игроков мы видим!
Перейдём к визуальной части:
Увидев разные медии, можно понять что обычно используется три метода отображения чит-ревилера:
1) в табе
2) в килл листе
3) в ESP
В данной статье я покажу лишь один из предложенных методов, первый метод - отображение иконок в табе!
По началу посмотрев большинство луа скриптов, я увидел что везде юзается Panorama APi и JS , я очень расстроился и отказался от этой идеи т.к это много тяжелого кода чтобы такое реализовать, но позже одним опытным имгуи дизайнером и просто хорошим кодером мне было подсказано что можно установить одну штуку игроку:
После чего в табе поменяется иконка, которая хранится в папке с игрой: materials/panorama/images/icons/xp/level%i.png
Заместо %i - число (уровень) который мы вручную прописываем игроку. (как пример: materials/panorama/images/icons/xp/level333.png materials/panorama/images/icons/xp/level334.png)
Ну и сам код установки "уровней" в зависимости от того какой чит использует игрок:
Вот собственно и всё, результат мы видим в табе!
Прошу строго не судить за терминологию и код т.к я не супер профи в кодинге и первый раз писал статью.
Надеюсь новичкам это пригодится для разработки своих читов, удачи!
В данной статье будет использована информация с репозитория
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)Начнем с предисловия. В данной статье я не буду вдаваться в подробности как вообще работают голосовые пакеты в игре. Однако без понимания этого базиса будет очень сложно объяснить принцип работы. Поэтому рассмотрим краткий пример:
- Любая сетевая игра работает за счёт постоянного обмена данными между клиентом и сервером. В нашем же случае с ксго, игрок(клиент) создаёт новый "голосовой" пакет данных (экземпляр класса CCLCMsg_VoiceData и CSVCMsg_VoiceData), то есть конструирует и заполняет массив данных и в конечном итоге отправляет на сервер (CNetChan::SendNetMsg) другим игрокам. Читы делают это вручную, зачастую используя готовые функции движка игры, формируя свой "голосовой пакет" который ничем не отличается от нативного игрового кроме как будучи заполненным кастомными данными чита. Нам же нужно получить эти данные, расшифровать и сравнить нужные переменные для нашего чит ревилера.
- В добавку к этому всему, не забываем, что у каждого игрока есть разные идентификаторы и в данном случае нам понадобится идентификатор xuid_low (который так же присутствует в классах CCLCMsg_VoiceData и CSVCMsg_VoiceData), но для чего он нужен, мы узнаем немного позже.
Перейдём к написанию самого чит ревилера:
- Как любой нормальный кодер мы сразу сталкиваемся с логическими "проблемами" на которые нам нужно придумать решения, то как мы будем хранить полученные данные, как часто вызывать функции и как сделать так чтобы всё это дело было оптимизировано!
1) Хранение данных:
В моём конкретном случае, на момент написания ревилера, я использовал готовую базу чит опая (airflow) и в моём распоряжении имелась структура esp_player_t и класс c_esp, которые глобально использовались в проекте, поэтому, к счастью, мне не пришлось придумывать велосипед.
Проблема хранения решена.
Наконец перейдём к получению этих самых данных, с помощью которых мы сможем детектить какой чит использует конкретный игрок, для чит ревилера нам нужно получить(перехватить) голосовые данные о которых было сказано в предисловии.
2) Получение "голосовых" данных от сервера
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Ставим хук на эту функциюПишем хук на эту функцию, для получения "голосовых" данных:
Код:
bool __fastcall msg_voice_data(void* ecx, void* edx, c_svc_msg_voice_data* message)
{
// получаем оригинал функции
static auto original = hooker::get_original(&msg_voice_data);
// исключаем локального игрока т.к нам нужны данные соперников ну или тиммейтов
if ( !HACKS->local || HACKS->local->index() == message->client + 1)
return original(ecx, edx, message);
// перехватываем нужные нам данные для ревилера
CHEAT_REVEALER->handle_voice(message);
// мы получили что хотели,
// возвращаем оригинал функции чтобы не ломать работоспособность игры
return original(ecx, edx, message);
}
Переходим к их сравнению:
Создаём заголовки
cheat_revealer.hpp:
static unsigned char skeet_shellcode[] =
{
0x81, 0xEC, 0x4C, 0x01, 0x00, 0x00, 0x53, 0x55, 0x56, 0x8B, 0xF1, 0x89, 0x54, 0x24, 0x54, 0x33,
0xDB, 0xC7, 0x44, 0x24, 0x10, 0x68, 0x33, 0x05, 0x97, 0x57, 0xC7, 0x44, 0x24, 0x18, 0x36, 0x06,
0xD4, 0xEA, 0xBF, 0x00, 0x01, 0x00, 0x00, 0x8B, 0x46, 0x10, 0x8B, 0x4E, 0x14, 0x89, 0x44, 0x24,
0x30, 0x8B, 0x46, 0x28, 0x89, 0x44, 0x24, 0x38, 0x8B, 0x46, 0x24, 0x89, 0x44, 0x24, 0x3C, 0x8B,
0x46, 0x2C, 0x89, 0x44, 0x24, 0x40, 0x8B, 0xC3, 0xC7, 0x44, 0x24, 0x1C, 0x4F, 0xC4, 0xA4, 0x3E,
0xC7, 0x44, 0x24, 0x20, 0x85, 0xB2, 0xAC, 0x0F, 0x89, 0x4C, 0x24, 0x34, 0x89, 0x5C, 0x24, 0x28,
0x88, 0x44, 0x04, 0x5C, 0x40, 0x3B, 0xC7, 0x72, 0xF7, 0x8A, 0xF3, 0x8B, 0xF3, 0x8A, 0x54, 0x34,
0x5C, 0x8B, 0xC6, 0x83, 0xE0, 0x0F, 0x8A, 0x44, 0x04, 0x14, 0x02, 0xC2, 0x02, 0xF0, 0x0F, 0xB6,
0xCE, 0x8A, 0x44, 0x0C, 0x5C, 0x88, 0x44, 0x34, 0x5C, 0x46, 0x88, 0x54, 0x0C, 0x5C, 0x3B, 0xF7,
0x72, 0xDB, 0x8A, 0xE3, 0x8B, 0xFB, 0xBD, 0x80, 0x00, 0x00, 0x00, 0x8A, 0xF4, 0xFE, 0xC6, 0x0F,
0xB6, 0xF6, 0x8A, 0x54, 0x34, 0x5C, 0x02, 0xE2, 0x0F, 0xB6, 0xCC, 0x8A, 0x44, 0x0C, 0x5C, 0x88,
0x44, 0x34, 0x5C, 0x88, 0x54, 0x0C, 0x5C, 0x83, 0xED, 0x01, 0x75, 0xE1, 0xFE, 0xC6, 0x0F, 0xB6,
0xF6, 0x8A, 0x54, 0x34, 0x5C, 0x8A, 0xDA, 0x02, 0xDC, 0x0F, 0xB6, 0xCB, 0x8A, 0x44, 0x0C, 0x5C,
0x88, 0x44, 0x34, 0x5C, 0x88, 0x54, 0x0C, 0x5C, 0x8A, 0x44, 0x34, 0x5C, 0x02, 0xC2, 0x0F, 0xB6,
0xC0, 0x8A, 0x44, 0x04, 0x5C, 0x8A, 0xE3, 0x30, 0x44, 0x3C, 0x30, 0x47, 0x83, 0xFF, 0x14, 0x72,
0xCB, 0x33, 0xFF, 0x89, 0x7C, 0x24, 0x2C, 0x8B, 0xEF, 0xC7, 0x44, 0x24, 0x24, 0x0F, 0x00, 0x00,
0x00, 0x8B, 0x7C, 0x24, 0x24, 0xD1, 0xED, 0x89, 0x6C, 0x24, 0x48, 0x0F, 0xB7, 0x4C, 0xAC, 0x32,
0x8B, 0xC1, 0x0F, 0xBF, 0xC9, 0x89, 0x44, 0x24, 0x54, 0x0F, 0xB7, 0x44, 0xAC, 0x34, 0xBD, 0x85,
0x8E, 0xD5, 0x91, 0x8B, 0xD0, 0x89, 0x4C, 0x24, 0x44, 0x8B, 0xD8, 0x89, 0x54, 0x24, 0x4C, 0x8B,
0xC1, 0x0F, 0xB7, 0xF0, 0x2B, 0xDD, 0x24, 0x0F, 0x8B, 0xD5, 0x8A, 0xC8, 0xD1, 0xC2, 0x66, 0xD3,
0xCB, 0x8B, 0xEA, 0x66, 0x8B, 0xC3, 0xD1, 0xC5, 0x66, 0x33, 0xC6, 0x2B, 0xF2, 0x0F, 0xB7, 0xD8,
0x8A, 0xCB, 0x80, 0xE1, 0x0F, 0x66, 0xD3, 0xCE, 0x66, 0x33, 0xF0, 0x0F, 0xB7, 0xCE, 0x0F, 0xB7,
0xC6, 0x83, 0xEF, 0x01, 0x75, 0xCB, 0x8B, 0x7C, 0x24, 0x28, 0x8B, 0xC5, 0x2B, 0xD8, 0x89, 0x6C,
0x24, 0x24, 0x33, 0x5C, 0x24, 0x50, 0x83, 0xC7, 0x02, 0x8B, 0x6C, 0x24, 0x48, 0xD1, 0xC0, 0x2B,
0xC8, 0x89, 0x7C, 0x24, 0x28, 0x8B, 0x44, 0x24, 0x4C, 0x33, 0x4C, 0x24, 0x2C, 0x0F, 0xB7, 0xC0,
0x89, 0x44, 0x24, 0x50, 0x8B, 0x44, 0x24, 0x54, 0x0F, 0xB7, 0xC0, 0x66, 0x89, 0x5C, 0xAC, 0x34,
0x66, 0x89, 0x4C, 0xAC, 0x32, 0x89, 0x44, 0x24, 0x2C, 0x83, 0xFF, 0x09, 0x0F, 0x82, 0x45, 0xFF,
0xFF, 0xFF, 0x8B, 0x44, 0x24, 0x30, 0x8B, 0x4C, 0x24, 0x58, 0xC1, 0xF8, 0x10, 0xC1, 0xF9, 0x10,
0x33, 0xC1, 0xB9, 0x24, 0x24, 0x00, 0x00, 0x5F, 0x5E, 0x66, 0x3B, 0xC1, 0x5D, 0x0F, 0x94, 0xC0,
0x5B, 0x81, 0xC4, 0x4C, 0x01, 0x00, 0x00, 0xC3
};
typedef bool(__fastcall* skeet_shellcode_t)(void*, uint32_t);
class c_cheat_revealer
{
private:
bool is_using_gamesense(c_svc_msg_voice_data* msg, uint32_t xuid_low);
bool is_using_fatality(uint16_t pct);
bool is_using_evolve(uint16_t pct);
bool is_using_onetap(uint16_t pct);
bool is_using_pandora(uint16_t pct);
public:
void handle_voice(c_svc_msg_voice_data* msg);
void update_tab();
};
Краткий обзор функций благодаря которым и функционирует наш чит-ревилер:
Функции имеют тип возвращаемого значения - boolean, то-есть если нужный нам чит был найден то возвращаем true!
Рассмотрим первую функцию для обнаружения юзеров скита:
cheat_revealer.cpp:
bool c_cheat_revealer::is_using_gamesense(c_svc_msg_voice_data* msg, uint32_t xuid_low)
{
// Declare static variables
static unsigned char* shellcode = nullptr;
static skeet_shellcode_t is_using_skeet_fn = nullptr;
// Allocate memory only once
if (!shellcode)
{
shellcode = reinterpret_cast<unsigned char*>(
VirtualAlloc(nullptr, sizeof(skeet_shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
);
// Copy the shellcode to the allocated memory
memcpy(shellcode, skeet_shellcode, sizeof(skeet_shellcode));
// Create a function pointer to the shellcode
is_using_skeet_fn = reinterpret_cast<skeet_shellcode_t>(shellcode);
}
// Call the shellcode
bool result = is_using_skeet_fn(msg, xuid_low);
// Note: We are not freeing the memory to keep it for future calls?
return result;
}
c_svc_msg_voice_data* msg
(голосовая дата полученная клиентом от сервера) и uint32_t xuid_low
(идентификатор игрока). В результате шеллкод возвращает тип boolean, видимо сравнивая эти два параметра с какими то данными скита внутри себя.Остальные примеры обнаружения других читов - более простые, суть заключается в сравнении той инфы которую читы формируют и отправляют на сервер , в этой инфе (голосовые пакеты описанные выше) имеется идентификатор xuid_low и соответственно у каждого чита он разный, чтобы не дампить самому эти значения, методом поиска луашек на чит ревилер, были найдены hex значения которые юзаются для обнаружения различных читов (сравнивая переменную xuid_low из полученной инфы в SVCMsg_VoiceData хуке)
cheat_revealer.cpp:
bool c_cheat_revealer::is_using_fatality(uint16_t pct)
{
if (pct == 0x7FFA || pct == 0x7FFB)
return true;
return false;
}
bool c_cheat_revealer::is_using_evolve(uint16_t pct)
{
if (pct == 0x7FFC || pct == 0x7FFD)
return true;
return false;
}
bool c_cheat_revealer::is_using_onetap(uint16_t pct)
{
if (pct == 0x57FA)
return true;
return false;
}
bool c_cheat_revealer::is_using_pandora(uint16_t pct)
{
if (pct == 0x695B || pct == 0x1B39)
return true;
return false;
}
Основная функция ревилера которую я написал для работы с полученной инфой из SVCMsg_VoiceData хука:
cheat_revealer.cpp:
void c_cheat_revealer::handle_voice(c_svc_msg_voice_data* msg)
{
// исключаем использование при отжатии эксплоитов чтобы обезопасить работоспобность функции
if (EXPLOITS->recharge.start)
return;
// проверяем наличие данных
if (msg->format != 0)
return;
// исключаем локального игрока
if (!HACKS->local || HACKS->engine->get_local_player() == msg->client + 1)
return;
// проверяем наличие данных
if (msg->section_number == 0 && msg->sequence_bytes == 0 && msg->uncompressed_sample_offset == 0)
return;
// Здесь я сталкнулся с небольшой проблемой:
// Проблема выглядела так: при частом аллоцировании и очищении памяти для шеллкода сальватора, у меня происходил краш процесса.
// Таким способом я решил уменьшить частоту использования этого кода чтобы давать памяти время подумать.
// Решение вышло неправильным и не сработало т.к читы в разное время отправляют те самые "голосовые" пакеты и можно легко словить рассинхрон и прозевать момент их получения клиентом и тем самым остаться с голой жопой.
// Ограничения использования этого кода (момент неудачной оптимизации)
//ULONGLONG time = GetTickCount64();
//const bool should_receive = time - CHEAT_REVEALER->last_receive_ack_time >= 500; // miliseconds
// проверяем что идентификатор игрока находится в рабочем диапазоне
int sender_id = msg->client + 1;
if (sender_id >= 0 && sender_id <= 63)
{
// создаем временный пустой массив данных
player_info_t player_info{};
// заполняем временно созданный массив информацией о игроке
if (HACKS->engine->get_player_info(sender_id, &player_info))
{
// получаем поинтер который хранит в себе всякую информацию которую чит использует в ESP и не только..
auto esp_info_sender = ESP->get_esp_player(sender_id);
// здесь я придумал нехитрое решение проблемы оптимизации
// проверку на идентификатор, чтобы постоянно не обновлять одного и того же игрока!
// если единтификатор у игрока поменялся - проверим какой чит он использует.
if (esp_info_sender && esp_info_sender->revealer.m_xuid_low != player_info.xuid_low)
{
// создаём массив и получаем "голосовой" пакет данных
c_voice_communication_data voice_data = msg->get_data();
// проверяем игрока на различные читы
const auto using_skeet = is_using_gamesense(msg, player_info.xuid_low);
const auto using_fatality = is_using_fatality(static_cast<uint16_t>(msg->xuid_low));
const auto using_evolve = is_using_evolve(static_cast<uint16_t>(msg->xuid_low));
const auto using_onetap = is_using_onetap(static_cast<uint16_t>(msg->xuid_low));
const auto using_pandora = is_using_pandora(static_cast<uint16_t>(msg->xuid_low));
// switch case would be better?
if (using_skeet)
{
esp_info_sender->revealer.update(CHEAT_GS, player_info.xuid_low);
#ifdef _DEBUG
printf("receiving | name: %s | xuid_low %d, found skeet user!\n", player_info.name, player_info.xuid_low);
#endif
}
else if (using_fatality)
{
esp_info_sender->revealer.update(CHEAT_FT, player_info.xuid_low);
#ifdef _DEBUG
printf("receiving | name: %s | xuid_low %d, found fatality user!\n", player_info.name, player_info.xuid_low);
#endif
}
else if (using_evolve)
{
esp_info_sender->revealer.update(CHEAT_EVOLVE, player_info.xuid_low);
#ifdef _DEBUG
printf("receiving | name: %s | xuid_low %d, found ev0lve user!\n", player_info.name, player_info.xuid_low);
#endif
}
else if (using_onetap)
{
esp_info_sender->revealer.update(CHEAT_ONETAP, player_info.xuid_low);
#ifdef _DEBUG
printf("receiving | name: %s | xuid_low %d, found ONETAP user!\n", player_info.name, player_info.xuid_low);
#endif
}
else if (using_pandora)
{
esp_info_sender->revealer.update(CHEAT_PANDORA, player_info.xuid_low);
#ifdef _DEBUG
printf("receiving | name: %s | xuid_low %d, found PANDORA user!\n", player_info.name, player_info.xuid_low);
#endif
}
else
{
// https://lua.neverlose.cc/documentation/events#voice_message
// неверлуз и ещё несколько других читов(примо, спирт, никс) используют более мудрённую систему передачи/чтения данных между своими юзерами:
// понять что к чему можно посмотрев в их апи, и увидеть что там используются protobuf'ы с которыми у меня нет опыта работы поэтому нл я задетектить не смог!
// но начав дампить непонятные мне данные из голосового пакета, нашел частое hex число, дурацкая рулетка вообщем.
if ((uint8_t)msg->voice_data == (uint8_t)0x0B282838)
{
esp_info_sender->revealer.update(CHEAT_NL, player_info.xuid_low);
#ifdef _DEBUG
const auto ctx = (c_cs_player*)HACKS->entity_list->get_client_entity(sender_id);
EVENT_LOGS->push_message(tfm::format("[revealer] entity: [%s] | pct: %d [0x%X] [seqb: %d | secn: %d | ucso: %d | xuid_low %d]\n", ctx->get_name(), msg->voice_data, msg->voice_data,
msg->sequence_bytes, msg->section_number, msg->uncompressed_sample_offset, msg->xuid_low));
#endif
}
}
}
}
}
}
Перейдём к визуальной части:
Увидев разные медии, можно понять что обычно используется три метода отображения чит-ревилера:
1) в табе
2) в килл листе
3) в ESP
В данной статье я покажу лишь один из предложенных методов, первый метод - отображение иконок в табе!
По началу посмотрев большинство луа скриптов, я увидел что везде юзается Panorama APi и JS , я очень расстроился и отказался от этой идеи т.к это много тяжелого кода чтобы такое реализовать, но позже одним опытным имгуи дизайнером и просто хорошим кодером мне было подсказано что можно установить одну штуку игроку:
m_nPersonaDataPublicLevel = ("CCSPlayerResource", "m_nPersonaDataPublicLevel");
После чего в табе поменяется иконка, которая хранится в папке с игрой: materials/panorama/images/icons/xp/level%i.png
Заместо %i - число (уровень) который мы вручную прописываем игроку. (как пример: materials/panorama/images/icons/xp/level333.png materials/panorama/images/icons/xp/level334.png)
Ну и сам код установки "уровней" в зависимости от того какой чит использует игрок:
cheat_revealer.cpp:
void c_cheat_revealer::update_tab()
{
if (!g_cfg.misc.cheat_revealer)
return;
// паттерн оффсета player_resource:
// memory::address_t player_resource = memory::get_pattern(client_dll, CXOR("8B 3D ? ? ? ? 85 FF 0F 84 D4 02 00 00"));
const uintptr_t ptr_resource = ** offsets::player_resource.add(0x2).cast< uintptr_t ** >();
LISTENER_ENTITY->for_each_player([&](c_cs_player* player)
{
auto deref = * (std::uintptr_t *)player;
if (deref == NULL || deref == 0x01000100)
return;
//if (player->is_bot())
// return;
auto index = player->index();
auto esp = ESP->get_esp_player(index);
if (ptr_resource)
{
// так как контра уже не будет обновляться и хотелось поскорее протестить ревилер, я в тупую взял нужный мне оффсет для записи m_nPersonaDataPublicLevel
// заранее извиняюсь за тупое различие типов и использование DWORD"a
DWORD m_nPersonaDataPublicLevel = (DWORD)ptr_resource + 0x4dd4 + (index * 4);
if (HACKS->local->index() == index)
{
// локальный игрок, здесь можем установить какую ту свою иконку
}
else
{
switch (esp->revealer.m_cheat)
{
case CHEAT_GS:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 333;
break;
case CHEAT_FT:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 334;
break;
case CHEAT_EVOLVE:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 336;
break;
case CHEAT_ONETAP:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 338;
break;
case CHEAT_PANDORA:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 339;
break;
case CHEAT_NL:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 340;
break;
default:
*(PINT)((DWORD)m_nPersonaDataPublicLevel) = 330; // иконка знака вопроса (т.к чит не найден)
break;
}
}
}
}, false);
}
Прошу строго не судить за терминологию и код т.к я не супер профи в кодинге и первый раз писал статью.
Надеюсь новичкам это пригодится для разработки своих читов, удачи!
Последнее редактирование: