aliast Опубликовано 28 января, 2013 Поделиться Опубликовано 28 января, 2013 И всё-таки никак не могу разобраться как же вызывать внутриигровые функции без вылета из игры... тут как-то всё просто получилось:http://forum.gamehac...__fromsearch__1Имеется игра Cry of Fear, в ней разработчики оставили файл с отладочной инфой hl.pdb, как следствие все функции подписаны, имеется список классов, почти исходники)) пытаюсь сделать квиксэйв, чтобы сохраняться в любом месте игры, а не только возле магнитофонов. Вроде бы кто-то что-то подобное делал для старых версий игры, хочется самому научиться)Функция отвечающая за сохранение находится в файле CryOfFear\cl_dlls\hl.dll, называется (если я ничего не напутал) void __thiscall CBasePlayer__PreSave(CBasePlayer *this, int slot). На входе видим указатель на игрока и номер слота 1-5. Вот её псевдокод:void __thiscall CBasePlayer::PreSave(CBasePlayer *this, int slot){ double v2; // st7@0 edict_s *v3; // eax@2 int v4; // ecx@2 CBasePlayer *thisa; // [sp+4Ch] [bp-4h]@1 thisa = this; if ( ((int (*)(void))this->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0.vfptr->IsAlive)() ) { saveslot = slot; CBasePlayer::ShowVGUIMenu(thisa, 200); thisa->m_iHideHUD |= 8u; thisa->m_iHideHUD |= 1u; thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0.pev->flags |= 0x80u; v3 = ENT(thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0.pev); EMIT_SOUND(v3, 2, "save/save_sequence.wav", 1.0, 0.80000001); v4 = (int)&thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0.pev->velocity; *(_DWORD *)v4 = (_DWORD)g_vecZero.x; *(_DWORD *)(v4 + 4) = LODWORD(g_vecZero.y); *(_DWORD *)(v4 + 8) = LODWORD(g_vecZero.z); CBasePlayer::EnableControl(thisa, 0); UTIL_ProMessage(&thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0, "Lô¬", 2, 0); off_1023950C("difficulty"); if ( 4.0 == v2 ) { --thisa->m_iTapes; if ( !thisa->m_iTapes ) CBasePlayer::HasItemInSlot(thisa, "inventoryitems/cassettetape.txt", 1); } thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0.b_Saving = 1; ((void (__thiscall *)(CBasePlayer *, unsigned int))thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0.vfptr->SetNextThink)( thisa, 0x40400000u); CBaseEntity::ThinkSet( &thisa->baseclass_0.baseclass_0.baseclass_0.baseclass_0.baseclass_0, (void (__thiscall *)(CBaseEntity *))CBasePlayer::EndSave, "EndSave"); }}Пишу как-то так... применительно к версии 1.55[ENABLE]alloc(newmem,1024)createthread(newmem)newmem:pushfpushadpush 1 //slotpush hl.dll+21928C //pCBasePlayercall hl.dll+12E790 //CBasePlayer::PreSave(CBasePlayer *this, int slot)popfpopadret[DISABLE]dealloc(newmem)Вылет, кто бы сомневался... но вот как правильно сохранить все регистры, флаги и стек не понимаю( Ссылка на комментарий Поделиться на другие сайты Поделиться
keng Опубликовано 28 января, 2013 Поделиться Опубликовано 28 января, 2013 А зачем это в отдельный поток заворачивать? Он хотя бы умирает после выполнения инструкций? Как вариант - поставить бряк на адрес вызываемой функции и поглядеть, что пихается в стек при обычном сохранении (у магнитофона, типа). Или ты не те данные (или не так) пихаешь в стек, или напутал с регистрами\потоком. Ссылка на комментарий Поделиться на другие сайты Поделиться
MasterGH Опубликовано 28 января, 2013 Поделиться Опубликовано 28 января, 2013 aliast, скрипт вроде без видимых ошибок. Скорее всего нужно передавать аргументы иначе например через регистры. Или подготовить предшествующие данные до вызова функции как это делает оригинальный код игры.Возможно следует ещё кое-что знать.- Операция сохранения может быть синхронной и асинхронной. Если асинхронной, то может быть поток ожидающий завершения сохранения и закрытие directX-GUI визуазизирующего процесс сохранения.- Код игры может вызвать исключения и правильно обработать, а мы не зная как это сделать получим вылет.- Возможно стоит вызвать функцию сохранения не потоком из CE скриптов, а спровоцировать сохранение в самой игре не создавая никаких потоков. Т.е. надо найти и поправить развилку кода которая даёт сохранить и которая не позволяет этого сделать. Это самый лучший вариант на мой взгляд.Возможно можно взывать функцию потоком из скрипта CE, тогда можно пойти по этому пути. Хотя я уже на писал выше, что возможно проще узнать, какие условия не позволяют сохранить и попробовать их исправить.Этап1 Набраться немного опыта и практики в вызове функций из CEДля начала нужно набраться опыта в вызове функций из CE, чтобы быть уверенным в том, что правильно делаешь по своим представлениям и знаниям. Можно написать консольное приложение на C++ с вызовом функции сохранения данных по определённому вводу с клавиатуры. Скомпилить exe. B нем попробовать эту функцию вызвать с помощью скриптов CE.Этап2 Найти оригинальный код вызова функции внутри игрыЯ бы попробовал в дизассемблере CE перед сохранением перейти на call hl.dll+12E790 и поставить брейкпоинт на выполнение (Execut).Снять копию стека (текстом или скриншотом).Дойти до ret и подняться на вверх перед вызовом call hl.dll+12E790.Этап3 Изучить оригинальный код вызова функции внутри игры и стекИ этот участок вызова функции (из этапа2) внимательно изучить . А именно то как передавать аргументы в функцию.Там может быть неpush 1 //slotpush hl.dll+21928C //pCBasePlayercall hl.dll+12E790 //CBasePlayer::PreSave(CBasePlayer *this, int slot)а типа этого через регистрыmov eax, 1 //slotmov ecx, hl.dll+21928C //pCBasePlayercall hl.dll+12E790 //CBasePlayer::PreSave(CBasePlayer *this, int slot)// и код после тоже надо посмотретьЗатем писать свой скрипт вызова функции и выполнять его по шагам до адреса "вылета из игры". Затем запоминаем этот адрес и снова к нему подбираемся и смотрим в чем дело.Возможно до вызова CBasePlayer::PreSave(CBasePlayer *this, int slot) должны в обязательном порядке вызваны предыдущие функции или подготовлены какие-то данные. Тогда перед сохранением в игре нужно подниматься через пошаговое выоленнеие по коду выше до места кода где происходит сравнение с запуском сохранения (кей-код или что-то в этом роде) и от этого места вглубь до вызова функции полностью изучить листинг дизассемблерного кода со стеком. Под рукой иметь IDA с плагином декомпилятора с запущенным вторым процессом игры.Этап4 Повторение залог найти ошибки и решениеЕсли что-то не получается, то внимательно сравниваем успешное выполнение кода и принудительное не успешное. Возможно придётся писать две истории пошагового выполнения кода начиная с адреса начала сохранения. Запоминать и сравнивать надо всё, что происходит в отладчике. Самое главное здесь это определить- Правильно ли передаются аргументы в функцию- Правильны ли подготовлены данные перед вызовом функции- Соблюдена ли вся последовательность выполнение предшествующего отладочного кода для успешной операции сохранения Ссылка на комментарий Поделиться на другие сайты Поделиться
SnedS91 Опубликовано 28 января, 2013 Поделиться Опубликовано 28 января, 2013 а не проще воспользоваться консолью, написать:bind f5 "save 1"bind f9 "load 1"(f5 - быстрое сохранение в файл 1.sav, f9 - быстрая загрузка файла 1.sav)и будет счастье Хотя поиск функции тоже интересное занятие.. Ссылка на комментарий Поделиться на другие сайты Поделиться
DenkA003 Опубликовано 28 января, 2013 Поделиться Опубликовано 28 января, 2013 а не проще воспользоваться консолью, написать:bind f5 "save 1"bind f9 "load 1"(f5 - быстрое сохранение в файл 1.sav, f9 - быстрая загрузка файла 1.sav)и будет счастье Хотя поиск функции тоже интересное занятие..не вовсех играх есть даные функции...!! Ссылка на комментарий Поделиться на другие сайты Поделиться
aliast Опубликовано 28 января, 2013 Автор Поделиться Опубликовано 28 января, 2013 а не проще воспользоваться консолью, написать:bind f5 "save 1"bind f9 "load 1"Пробовал, не фурычит - разрабы пофиксили этот "чит". В общем-то я тоже добился вызова функции сохранения, появляются надписи "Идёт сохранение", затем "Готово", а сохранения тю-тю скорее всего либо функция не та, либо там идёт доп. проверка на "активатор" сохранения - просто ли вызвали функцию или магнитофон её вызвал? Функцию вроде такую нашёл (void __thiscall CTapeRecorder__TapeUse(CTapeRecorder *this, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)), но пока непонятно как ЕГО вызывать. И можно ли обойтись без этого... Ну а кому интересно вот скрипт на вызов сохранения (толку то...)[ENABLE]alloc(newmem,1024)createthread(newmem)newmem:push ebpmov ebp,espsub esp,44push eaxpush ecxmov eax,5mov ecx, [hl.dll+21928C]call hl.dll+12E790pop ecxpop eaxpop ecxpop eaxmov esp,ebppop ebpret[DISABLE]dealloc(newmem)Upd: ай, я забыл что там ещё номер слота в eax затирается в процессе вызова, в eax оказывается 0, а нулевого слота нету(( я просто тупо занопил строку "hl.dll+12E7B3" и делал сохранение в слот 1. Это можно было бы потом пофиксить, но всё равно не работает, так что неважно... Ссылка на комментарий Поделиться на другие сайты Поделиться
SnedS91 Опубликовано 29 января, 2013 Поделиться Опубликовано 29 января, 2013 Да, действительно.. У меня стояла старая версия и там все работало. В новой версии корректно работает только команда загрузки..К сведению: сохранение состоит из двух файлов, вот к примеру для первого слота файлы cofsave1.sav (файл сохранения) и saveinfo1.cof (описание сохранения - отображается в меню загрузки) Без второго файла просто не будет отображаться в меню.Я это все к тому, что у тебя рабочий скрипт!Варианта 2:1) сделать такой бинд на загрузку: bind f9 "load cofsave1" и загружаться по ф9;2) создать в папке ..Games\Cry of Fear\CryOfFear\SAVE файлик saveinfo1.cof с содержимым, к примеру: Quick saveи загружаться из меню загрузки.Проверено - работает! Ссылка на комментарий Поделиться на другие сайты Поделиться
aliast Опубликовано 29 января, 2013 Автор Поделиться Опубликовано 29 января, 2013 Хмм... про формат сохранений я в курсе. Однако лично у меня мой скрипт не создаёт никаких файлов! Ни .cof, ни .sav. У кого-то создаётся .sav? Очень интересно.. сейчас проверил, даже если сохраняться на магнитофон без читов файл.sav в этой функции не создаётся) надо искать другую(( Ссылка на комментарий Поделиться на другие сайты Поделиться
SnedS91 Опубликовано 29 января, 2013 Поделиться Опубликовано 29 января, 2013 как раз если занопить "hl.dll+12E7B3", то все работает, создается файл cofsave1.sav Ссылка на комментарий Поделиться на другие сайты Поделиться
aliast Опубликовано 29 января, 2013 Автор Поделиться Опубликовано 29 января, 2013 И правда что работает.. вчера почему-то не работало... тогда сейчас разберусь с тем нопом чтобы без него всё работало и придумаю как прикрутить описание .cof, чтобы самому не создаватьИтак, QuickSave в первый слот:[ENABLE]alloc(newmem,1024)createthread(newmem)newmem:push ebpmov ebp,esppush eaxmov eax,1 //номер слотаmov ecx, [hl.dll+21928C] //указатель на игрокаpush ecxcall hl.dll+12E790 //SaveGamepop ecxpop eaxmov esp,ebppop ebprethl.dll+12E7B3: //мешающая фигня, забирающая номер слота из стекаdb 90 90 90[DISABLE]hl.dll+12E7B3: //восстановили занопенную инструкциюmov eax,[ebp+08]dealloc(newmem)Эквивалент консольной команды load cofsave1:[ENABLE]alloc(newmem,1024)createthread(newmem)newmem:push ebpmov ebp,esppush 101d3d48 //строка "load cofsave1" в памятиcall dword ptr [102394C4] //"запускатор" консольных команд в hw.dllmov esp,ebppop ebpret[DISABLE]dealloc(newmem)Такой вопрос: а можно как-то сделать кратковременную активацию скрипта и чтобы он сам скрипт отключал (выполнив то, что в [DISABLE]) ? А то немного криво работает: Нажали F5, сохранились. Захотели опять сохраниться - надо ДВА РАЗА нажать F5, первое нажатие отключает скрипт, второе нажатие запускает...Можно сделать ещё лучше: сохраняться в какой-нибудь слот номер 6 и грузиться забиндив bind f9 "load cofsave6" - тогда в первом слоте может быть магнитофонное сохранение. Но можно ограничиться и первым слотом, главное магнитофоном его не переписать ненароком)PS: а мне понравилось) вот только без отладочной инфы повторить такой финт ушами в другой игре будет ой как сложно Надо тренироваться с Function Hacker'ом. Ссылка на комментарий Поделиться на другие сайты Поделиться
MasterGH Опубликовано 30 января, 2013 Поделиться Опубликовано 30 января, 2013 >>Такой вопрос: а можно как-то сделать кратковременную активацию скрипта и чтобы он сам скрипт отключал (выполнив то, что в [DISABLE])Я думаю есть несколько решений задачи, о которой идёт речь без использования отключения скрипта... Но все их надо проверять на практике и выяснять какой лучше и какой будет работать. Например, при начале потока он может нопить инструкции сам, а после завершения потока он же сам может и возвращать байты. Память можно создавать один раз и не разрушать её. Возможно здесь потребуется создать два АА скрипта. И вообще лучше всё на Lua переписать, так как он более гибкий к автоассемблированию, установке горячих клавиш и т.п.Раз был вопрос именно про отключение АА, то предположительный ответ. Основная фича в том, что из АА-скрипта можно вызвать пользовательскую функцию Lua через LuaCall(название функции), которая найдёт этот скрипт в главной таблице CE (например, по имени) и выключит его после ожидания завершения потока, когда поток запишет 1 перед выходом в зарегистрированную переменную.Как-то так:[ENABLE]LuaCall(Cheat0WaitThreadAndDisable) // вызывает Lua функцию ожидания завершения потокаregisterSymbol(isThreadExecuted)alalloc(newmem,1024)createthread(newmem)newmem:push eaxmov eax,1mov ecx, [hl.dll+21928C]push ecxcall hl.dll+12E790 //SaveGamepop ecxpop eaxmov esp,ebppop ebpmov byte [isThreadExecuted], 01retisThreadExecuted:db 00hl.dll+12E7B3: //мешающая фигня, забирающая номер слота из стекаdb 90 90 90[DISABLE]hl.dll+12E7B3: //восстановили занопенную инструкциюmov eax,[ebp+08]dealloc(newmem)unregisterSymbol(isThreadExecuted)Ну и не законченный Lua кодfunction Cheat0WaitThreadAndDisable()-- Запуск таймера на проверку некоторой переменной, что поток был выполненend-- функция тамера,function OnTimerTick(sender)-- выполняем проверку перменной isThreadExecuted (зарегистрированной)-- если поток выполнен, то найти чит в таблице и выключить его-- разрушить объект таймераend Ссылка на комментарий Поделиться на другие сайты Поделиться
Рекомендуемые сообщения