Graf 210 Report post Posted November 27, 2021 Характеристики персонажа(форма в игре) Всем добрового времени суток. Как мы все знаем, в игре есть окно с выводом информации о персонаже. Выглядит оно +- так: В исходниках, по мимо того, что мы привыкли видеть, имеется: То есть возможность вывести шанс "крита", шанс "дропа" и Удачу. Для первых двух, скорей всего, требуются какие-то рассчеты или тому подобного.. А вот с удачей все намного проще. Кто-нибудь может объяснить, как "прохексить Game.exe"(мб в виде мини-гайда), чтобы можно было работать с labLukshow? Или же просто помочь какой-нибудь .dll/hex)) Quote Share this post Link to post Share on other sites
V3ct0r 2,117 Report post Posted November 28, 2021 Привет, @Graf! Чтобы решить эту задачу, проще всего будет написать DLL. Отправь сюда свой Game.exe, покажу на его примере как это сделать. Quote Some useful links / Полезные ссылки Tips for making a topic in 'Questions & Help' / Рекомендации по созданию тем в разделе "Помощь" Server Advertising Section Rules / Правила раздела "Реклама серверов" Available e-mail domains for registration / Допустимые e-mail домены для регистрации User groups / Группы пользователей User ranks / Звания пользователей "Broken" pictures on the forum / "Битые" изображения на форуме Beware of scammers! / Осторожно, мошенники! My developments / Мои разработки Mods for client and server / Моды для клиента и сервера PKOdev.NET website for Tales of Pirates Server / PKOdev.NET веб-обвязка для сервера Пиратии I do not provide any help in private messages and outside the forum. Use 'Questions & Help' section please. Thank you for understanding! Я не оказываю какую-либо помощь в личных сообщениях и вне форума. Пожалуйста, используйте раздел "Пиратия: Помощь". Благодарю за понимание! Share this post Link to post Share on other sites
Graf 210 Report post Posted November 28, 2021 Привет, @V3ct0r Ссылка на Game.exe Quote Share this post Link to post Share on other sites
Graf 210 Report post Posted December 5, 2021 up Quote Share this post Link to post Share on other sites
V3ct0r 2,117 Report post Posted December 7, 2021 On 12/5/2021 at 7:13 PM, Graf said: up Привет, @Graf! Я помню про вопрос, скоро отвечу в виде небольшого гайда. 1 Quote Some useful links / Полезные ссылки Tips for making a topic in 'Questions & Help' / Рекомендации по созданию тем в разделе "Помощь" Server Advertising Section Rules / Правила раздела "Реклама серверов" Available e-mail domains for registration / Допустимые e-mail домены для регистрации User groups / Группы пользователей User ranks / Звания пользователей "Broken" pictures on the forum / "Битые" изображения на форуме Beware of scammers! / Осторожно, мошенники! My developments / Мои разработки Mods for client and server / Моды для клиента и сервера PKOdev.NET website for Tales of Pirates Server / PKOdev.NET веб-обвязка для сервера Пиратии I do not provide any help in private messages and outside the forum. Use 'Questions & Help' section please. Thank you for understanding! Я не оказываю какую-либо помощь в личных сообщениях и вне форума. Пожалуйста, используйте раздел "Пиратия: Помощь". Благодарю за понимание! Share this post Link to post Share on other sites
V3ct0r 2,117 Report post Posted December 17, 2021 Привет, @Graf! Тебе понадобится: 1) Game.exe; 2) OllyDBG; 3) IDA Pro (необязательно, можно пользоваться только OllyDBG); 4) Visual Studio 2019 Community. 1) Поместим на форму с характеристиками (frmState) персонажа 3 текстовые метки: удача (labLukShow), шанс критического удара (labCriticalShow), шанс дропа (labMfShow). preperty.clu: -- Lucky labLukShow = UI_CreateCompent( frmState, LABELEX_TYPE, "labLukShow", 26, 8, 16, 370 ) UI_SetCaption( labLukShow, "L" ) UI_SetTextColor( labLukShow, COLOR_PURPLE ) UI_SetLabelExFont( labLukShow, DEFAULT_FONT, TRUE, COLOR_WHITE ) -- Critical chance labCriticalShow = UI_CreateCompent( frmState, LABELEX_TYPE, "labCriticalShow", 26, 8, 80, 370 ) UI_SetCaption( labCriticalShow, "C" ) UI_SetTextColor( labCriticalShow, COLOR_PURPLE ) UI_SetLabelExFont( labCriticalShow, DEFAULT_FONT, TRUE, COLOR_WHITE ) -- Drop chance labMfShow = UI_CreateCompent( frmState, LABELEX_TYPE, "labMfShow", 26, 8, 144, 370 ) UI_SetCaption( labMfShow, "M" ) UI_SetTextColor( labMfShow, COLOR_PURPLE ) UI_SetLabelExFont( labMfShow, DEFAULT_FONT, TRUE, COLOR_WHITE ) 2) Изучив исходный код клиента, находим функцию void CStateMgr::RefreshStateFrm(), в которой происходит обновление меток с характеристиками персонажей. Далее нам будет необходимо перехватывать её вызов и обновлять наши новые метки. Так же есть функция void CStateMgr::_evtMainShow(CGuiData *pSender), она вызывается при открытии формы характеристик персонажа и её можно использовать для получения указателей на объекты текстовых меток в памяти игры. Параметр pSender это указатель на форму frmState, объекта класса CCompent, у которого есть метод CCompent* CForm::Find(const char* str), этот метод понадобится для получения указателей на метки по их именам, указанных в lua скриптах GUI. Чтобы установить текст меток, нам будет нужен метод void CLabel::SetCaption(const char * str). Наконец, для доступа к параметрам нашего персонажа, нам будет нужен указатель на персонажа, которым управляет игрок. 3) Для поиска адресов в Game.exe будем использовать программу IDA Pro. void CStateMgr::RefreshStateFrm(). Данный метод можно найти по какой-либо уникальной строке, например, "%4.2f%%". Ищем эту строку в IDA Pro и находим её использование только в одной функции по адресу 0x0047F190 - это и есть искомый метод. void CStateMgr::_evtMainShow(CGuiData *pSender). Это обработчик события открытия формы характеристик персонажа, значит он где-то вешается на объект в формы. Проверим в исходном коде клиента игры в методе инициализации формы frmState: bool CStateMgr::Init() { CFormMgr &mgr = CFormMgr::s_Mgr; frmState = _FindForm("frmState"); if( !frmState ) return false; frmState->evtShow = _evtMainShow; labStateName = dynamic_cast<CLabelEx*>(frmState->Find("labStateName")); if( !labStateName ) return Error( g_oLangRec.GetString(45), frmState->GetName(), "labStateName" ); labStateName->SetIsCenter(true); Теперь необходимо найти этот код в Game.exe. Для этого выполняем поиск по строке "frmState". В этот раз нам повезло меньше, найдено 3 использования такой строки: Смотрим первое использование: Рядом так же видно использование строк "btnState", "btnSkill", и "frmSkill". Но в искомом коде таких строк рядом нет. Очевидно, этот адрес нам не подходит. Смотрим второе использование: Строка находится в самом начале функции и рядом есть строка "labStateName". Судя по всему, это наш искомый метод инициализации. Здесь видно, как загружается указатель на форму frmState в регистр EAX, а далее по смещению 0x78, относительно адреса в регистре EAX, помещается какая-то функция. Наверняка, это и есть метод void CStateMgr::_evtMainShow(CGuiData *pSender). Итак, адрес метода void CStateMgr::_evtMainShow(CGuiData *pSender) - 0x0047F7F0. CCompent* CForm::Find(const char* str). Этот метод скорее всего находится в функции инициализации формы, такую мы уже видели: bool CStateMgr::Init(). Просмотрев исходный код, видим следующее: labStateName = dynamic_cast<CLabelEx*>(frmState->Find("labStateName")); if( !labStateName ) return Error( g_oLangRec.GetString(45), frmState->GetName(), "labStateName" ); labStateName->SetIsCenter(true); Это код получения указателя на метку labStateName, нам предстоит сделать то же самое с нашими новыми метками. Идем по адресу 0x0047F9F0 метода bool CStateMgr::Init() в IDA Pro и видим следующий код: .text:0047FA25 push offset aLabstatename ; "labStateName" .text:0047FA2A call dword ptr [eax+48h] Похоже на вызов метода CCompent* CForm::Find(const char* str) для получения указателя на метку labStateName, но его адрес не указан явно. Это говорит о том, что метод виртуальный. Проверим это в исходном коде: virtual CCompent* Find( const char* str ) { return _frmOwn->Find( str ); } Метод действительно является виртуальным. Как же получить его адрес? Как вариант, открыть Game.exe в отладчике, поставить точку останова на адрес 0x0047FA2A и проверить адрес вызываемой функции, но есть проблема - мы не можем запустить клиент через отладчик, а после запуска эта функция уже не вызывается. Есть второй вариант: поставить точку останова на метод void CStateMgr::_evtMainShow(CGuiData *pSender) (его адрес мы уже знаем), получить адрес формы frmState через параметр pSender, прибавить к нему смещение 0x48 и посмотреть адрес искомого метода. Идем в игру и открываем форму характеристик персонажа: На стеке смотрим адрес параметра pSender - 0x109D5568. По этому адресу находится адрес объекта CStateMgr - 0x006089B4. Прибавляем к этому адресу смещение 0x48, получаем адрес 0x006089FC и переходим по данному адресу в окне отображения памяти процесса: Итак, адрес метода CCompent* CForm::Find(const char* str) равен 0x004941F0. void CLabel::SetCaption(const char * str), в исходном коде вызов этого метода встретится в методе void CStateMgr::RefreshStateFrm(), например: if ( labStateEXP) { if (max!=0) sprintf( pszCha , "%4.2f%%" , num*100.0f/max ); else sprintf( pszCha , "0.00%"); labStateEXP->SetCaption( pszCha ); } Снова ищем строку "%4.2f%%" в IDA Pro и переходим по адресу её использования: Очевидно, что вызов неизвестной функции после двух вызовов библиотечной функции sprintf устанавливает текст метки labStateEXP. Функция является виртуальной, поэтому вновь открываем отладчик, ставим точку останова по адресу 0x0047F2A1 и открываем форму характеристик персонажа в игре: Отладчик подсказывает нам, что по адресу [EDX+3C] находится адрес метода void CLabel::SetCaption(const char * str) - 0x0042B1A0. Указатель на персонажа игрока можно посмотреть в том же методе void CStateMgr::RefreshStateFrm(): CForm * f = g_stUIState.frmState; if( !f->GetIsShow() ) return; CCharacter* pCha = g_stUIBoat.GetHuman(); if( !pCha ) return; SGameAttr* pCChaAttr = pCha->getGameAttr(); if (!pCChaAttr ) return; Переходим на начало метода в IDA Pro: Анализ ассемблерного листинга показывает, что указатель на персонажа игрока находится по адресу 0x00668B6C. Мы нашли все необходимые адреса: 0x0047F7F0 void CStateMgr::_evtMainShow(CGuiData *pSender) 0x0047F190 void CStateMgr::RefreshStateFrm() 0x004941F0 CCompent* CForm::Find(const char* str) 0x0042B1A0 void CLabel::SetCaption(const char * str) 0x00668B6C CCharacter* CBoatMgr::_pHuman 4) Все готово для создания DLL. В качестве языка программирования будем использовать C++. Создаем новый проект Dynamic-Link Library (DLL) в Visual Studio 2019 Community. Поскольку мы будем перехватывать вызовы void CStateMgr::_evtMainShow(CGuiData *pSender) и void CStateMgr::RefreshStateFrm(), то нам понадобится библиотека MS Detours, подключаем её к проекту. Записываем код DLL: #include <windows.h> #include <detours.h> #include <cstdio> namespace pkodev { // Addresses of imported functions from Game.exe namespace address { // void CStateMgr::_evtMainShow(CGuiData *pSender) const unsigned int CStateMgr___evtMainShow = 0x0047F7F0; // void CStateMgr::RefreshStateFrm() const unsigned int CStateMgr__RefreshStateFrm = 0x0047F190; // CCompent* CForm::Find( const char* str ) const unsigned int CForm__Find = 0x004941F0; // void CLabel::SetCaption( const char * str ) const unsigned int CLabel__SetCaption = 0x0042B1A0; // CCharacter* CBoatMgr::_pHuman const unsigned int CBoatMgr___pHuman = 0x00668B6C; } namespace pointer { // void CStateMgr::_evtMainShow(CGuiData *pSender) typedef void(__cdecl* CStateMgr___evtMainShow__Ptr)(void*); CStateMgr___evtMainShow__Ptr CStateMgr___evtMainShow = (CStateMgr___evtMainShow__Ptr)(void*)(address::CStateMgr___evtMainShow); // void CStateMgr::RefreshStateFrm() typedef void(__cdecl* CStateMgr__RefreshStateFrm__Ptr)(void*); CStateMgr__RefreshStateFrm__Ptr CStateMgr__RefreshStateFrm = (CStateMgr__RefreshStateFrm__Ptr)(void*)(address::CStateMgr__RefreshStateFrm); // CCompent* CForm::Find( const char* str ) typedef void* (__thiscall* CForm__Find__Ptr)(void*, const char*); CForm__Find__Ptr CForm__Find = (CForm__Find__Ptr)(void*)(address::CForm__Find); // void CLabel::SetCaption( const char * str ) typedef void (__thiscall* CLabel__SetCaption__Ptr)(void*, const char*); CLabel__SetCaption__Ptr CLabel__SetCaption = (CLabel__SetCaption__Ptr)(void*)(address::CLabel__SetCaption); } namespace hook { // void CStateMgr::_evtMainShow(CGuiData *pSender) void __cdecl CStateMgr___evtMainShow(void* pSender); // void CStateMgr::RefreshStateFrm() void __fastcall CStateMgr__RefreshStateFrm(void* This, void* NotUsed); } // Label "labLukShow" void* labLukShow = nullptr; // Label "labCriticalShow" void* labCriticalShow = nullptr; // Label "labMfShow" void* labMfShow = nullptr; } // Entry point BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { // DLL attached to the proccess case DLL_PROCESS_ATTACH: // Enable hooks DetourRestoreAfterWith(); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)pkodev::pointer::CStateMgr___evtMainShow, pkodev::hook::CStateMgr___evtMainShow); DetourAttach(&(PVOID&)pkodev::pointer::CStateMgr__RefreshStateFrm, pkodev::hook::CStateMgr__RefreshStateFrm); DetourTransactionCommit(); break; // DLL detached from the proccess case DLL_PROCESS_DETACH: // Disable hooks DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)pkodev::pointer::CStateMgr___evtMainShow, pkodev::hook::CStateMgr___evtMainShow); DetourDetach(&(PVOID&)pkodev::pointer::CStateMgr__RefreshStateFrm, pkodev::hook::CStateMgr__RefreshStateFrm); DetourTransactionCommit(); break; } return TRUE; } // void CStateMgr::_evtMainShow(CGuiData *pSender) void __cdecl pkodev::hook::CStateMgr___evtMainShow(void* pSender) { // Get pointers to the labels pkodev::labLukShow = pkodev::pointer::CForm__Find(pSender, "labLukShow"); pkodev::labCriticalShow = pkodev::pointer::CForm__Find(pSender, "labCriticalShow"); pkodev::labMfShow = pkodev::pointer::CForm__Find(pSender, "labMfShow"); // Call original CStateMgr___evtMainShow() function pkodev::pointer::CStateMgr___evtMainShow(pSender); } // void CStateMgr::RefreshStateFrm() void __fastcall pkodev::hook::CStateMgr__RefreshStateFrm(void* This, void* NotUsed) { // Call original function CStateMgr::RefreshStateFrm() pkodev::pointer::CStateMgr__RefreshStateFrm(This); // Get pointer to the main character // CCharacter* pCha = g_stUIBoat.GetHuman(); void* pCha = reinterpret_cast<void*>( *reinterpret_cast<unsigned int*>(pkodev::address::CBoatMgr___pHuman) ); if (pCha == nullptr) { // Exit from the hook return; } // Define ATTR constants const unsigned int ATTR_LUK = 30; const unsigned int ATTR_MF = 38; const unsigned int ATTR_CRT = 39; // Get offset to the attribute auto attr_get = [&pCha](unsigned int id) -> unsigned int { return *reinterpret_cast<unsigned int*>( (0xD0 + 4 * id + reinterpret_cast<unsigned int>(pCha)) ); }; // Buffer for labels captions char buf[128]{ 0x00 }; // Show character's ATTR_LUK if (pkodev::labLukShow != nullptr) { sprintf_s(buf, sizeof(buf), "LUK: %d", attr_get(ATTR_LUK)); pkodev::pointer::CLabel__SetCaption(pkodev::labLukShow, buf); } // Show character's ATTR_CRT if (pkodev::labCriticalShow != nullptr) { sprintf_s(buf, sizeof(buf), "CRT: %d", attr_get(ATTR_CRT)); pkodev::pointer::CLabel__SetCaption(pkodev::labCriticalShow, buf); } // Show character's ATTR_MF if (pkodev::labMfShow != nullptr) { sprintf_s(buf, sizeof(buf), "MF: %d", attr_get(ATTR_MF)); pkodev::pointer::CLabel__SetCaption(pkodev::labMfShow, buf); } } 1. В начале определяем найденные нами в пункте 3 адреса; 2. Далее определяем указатели на необходимые функции и методы; 3. Затем определяем функции-перехватчики и глобальные переменные под указатели на текстовые метки; 4. В функции DllMain() записываем код для установки и снятия перехватчиков с помощью библиотеки Detours; 5. Реализуем перехватчик pkodev::hook::CStateMgr___evtMainShow(). В этой функции получаем указатели на объекты новых меток labLukShow, labCriticalShow и labMfShow с помощью метода CCompent* CForm::Find(const char* str); 6. Реализуем перехватчик pkodev::hook::CStateMgr__RefreshStateFrm(). Сначала получаем указатель на персонажа, которым управляет игрок. Далее записываем константы, обозначающие ID требуемых характеристик. Теперь стоит задача получения этих характеристик из памяти игры. В исходном коде клиента для этого используется указатель на объект типа SGameAttr, связанный с персонажем: SGameAttr* pCChaAttr = pCha->getGameAttr(); if (!pCChaAttr ) return; Анализ ассемблерного листинга показывает, что этот указатель находится по смещению 0xD0 относительно указателя на персонажа: .text:0047F1D2 mov ebx, dword_668B6C . . . .text:0047F1E4 add ebx, 0D0h Посмотрим что представляет собой объект SGameAttr в исходном коде: struct SGameAttr { long lAttr[MAX_ATTR_CLIENT]; // MAX_ATTR_CLIENT = 74 . . . }; Это структура, которая содержит только одно поле - массив из 74 целых чисел по 4 байта каждое, индексы элементов которого соответствует ID характеристик персонажа (см. файл AttrType.lua из скриптов GameServer). Из курса программирования мы знаем, что все элементы массива хранятся в памяти друг за другом. Значит, чтобы получить адрес характеристики персонажа с определенным ID, мы можем воспользоваться следующей формулой: 0xD0 + 4 * <ID> + <Адрес персонажа в памяти> Осталось самое простое: вывести требуемые характеристики персонажа в новые текстовые метки с помощью метода void CLabel::SetCaption(const char * str). 5) Компилируем проект и присоединяем DLL библиотеку к процессу игры. В результате мы получим следующее: Надеюсь, объяснил хорошо и ничего не упустил. Отмечу, что я намеренно не останавливался на каких-то базовых понятиях, вроде основ ассемблера и C++, навыков использования отладчика и так далее. В качестве примера я использовал GAME_13X_2, в твоем Game.exe адреса могут быть другими. Если будут вопросы, то пиши в этой теме. Ниже прикладываю проект библиотеки и получившуюся DLL-библиотеку мода. Скачать проект DLL библиотеки для Visual Studio 2019 Community и DLL библиотеку из примера (286 Кб) 1 1 Quote Some useful links / Полезные ссылки Tips for making a topic in 'Questions & Help' / Рекомендации по созданию тем в разделе "Помощь" Server Advertising Section Rules / Правила раздела "Реклама серверов" Available e-mail domains for registration / Допустимые e-mail домены для регистрации User groups / Группы пользователей User ranks / Звания пользователей "Broken" pictures on the forum / "Битые" изображения на форуме Beware of scammers! / Осторожно, мошенники! My developments / Мои разработки Mods for client and server / Моды для клиента и сервера PKOdev.NET website for Tales of Pirates Server / PKOdev.NET веб-обвязка для сервера Пиратии I do not provide any help in private messages and outside the forum. Use 'Questions & Help' section please. Thank you for understanding! Я не оказываю какую-либо помощь в личных сообщениях и вне форума. Пожалуйста, используйте раздел "Пиратия: Помощь". Благодарю за понимание! Share this post Link to post Share on other sites