Перейти к содержанию

Вызов функций (Cry of fear)


Рекомендуемые сообщения

И всё-таки никак не могу разобраться как же вызывать внутриигровые функции без вылета из игры... тут как-то всё просто получилось:

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:
pushf
pushad
push 1 //slot
push hl.dll+21928C //pCBasePlayer
call hl.dll+12E790 //CBasePlayer::PreSave(CBasePlayer *this, int slot)
popf
popad
ret

[DISABLE]
dealloc(newmem)

Вылет, кто бы сомневался... но вот как правильно сохранить все регистры, флаги и стек не понимаю(

Ссылка на комментарий
Поделиться на другие сайты

А зачем это в отдельный поток заворачивать? Он хотя бы умирает после выполнения инструкций? Как вариант - поставить бряк на адрес вызываемой функции и поглядеть, что пихается в стек при обычном сохранении (у магнитофона, типа). Или ты не те данные (или не так) пихаешь в стек, или напутал с регистрами\потоком.

Ссылка на комментарий
Поделиться на другие сайты

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 //slot

push hl.dll+21928C //pCBasePlayer

call hl.dll+12E790 //CBasePlayer::PreSave(CBasePlayer *this, int slot)

а типа этого через регистры

mov eax, 1 //slot

mov ecx, hl.dll+21928C //pCBasePlayer

call hl.dll+12E790 //CBasePlayer::PreSave(CBasePlayer *this, int slot)

// и код после тоже надо посмотреть

Затем писать свой скрипт вызова функции и выполнять его по шагам до адреса "вылета из игры". Затем запоминаем этот адрес и снова к нему подбираемся и смотрим в чем дело.

Возможно до вызова CBasePlayer::PreSave(CBasePlayer *this, int slot) должны в обязательном порядке вызваны предыдущие функции или подготовлены какие-то данные. Тогда перед сохранением в игре нужно подниматься через пошаговое выоленнеие по коду выше до места кода где происходит сравнение с запуском сохранения (кей-код или что-то в этом роде) и от этого места вглубь до вызова функции полностью изучить листинг дизассемблерного кода со стеком. Под рукой иметь IDA с плагином декомпилятора с запущенным вторым процессом игры.

Этап4 Повторение залог найти ошибки и решение

Если что-то не получается, то внимательно сравниваем успешное выполнение кода и принудительное не успешное. Возможно придётся писать две истории пошагового выполнения кода начиная с адреса начала сохранения. Запоминать и сравнивать надо всё, что происходит в отладчике. Самое главное здесь это определить

- Правильно ли передаются аргументы в функцию

- Правильны ли подготовлены данные перед вызовом функции

- Соблюдена ли вся последовательность выполнение предшествующего отладочного кода для успешной операции сохранения

Ссылка на комментарий
Поделиться на другие сайты

а не проще воспользоваться консолью, написать:


bind f5 "save 1"
bind f9 "load 1"

(f5 - быстрое сохранение в файл 1.sav, f9 - быстрая загрузка файла 1.sav)

и будет счастье ^_^

Хотя поиск функции тоже интересное занятие..

Ссылка на комментарий
Поделиться на другие сайты

а не проще воспользоваться консолью, написать:


bind f5 "save 1"
bind f9 "load 1"

(f5 - быстрое сохранение в файл 1.sav, f9 - быстрая загрузка файла 1.sav)

и будет счастье ^_^

Хотя поиск функции тоже интересное занятие..

не вовсех играх есть даные функции...!!

Ссылка на комментарий
Поделиться на другие сайты

а не проще воспользоваться консолью, написать:


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 ebp
mov ebp,esp
sub esp,44
push eax
push ecx
mov eax,5
mov ecx, [hl.dll+21928C]
call hl.dll+12E790
pop ecx
pop eax
pop ecx
pop eax
mov esp,ebp
pop ebp
ret

[DISABLE]
dealloc(newmem)

Upd: ай, я забыл что там ещё номер слота в eax затирается в процессе вызова, в eax оказывается 0, а нулевого слота нету(( я просто тупо занопил строку "hl.dll+12E7B3" и делал сохранение в слот 1. Это можно было бы потом пофиксить, но всё равно не работает, так что неважно...

Ссылка на комментарий
Поделиться на другие сайты

Да, действительно.. У меня стояла старая версия и там все работало. В новой версии корректно работает только команда загрузки..

К сведению: сохранение состоит из двух файлов, вот к примеру для первого слота файлы cofsave1.sav (файл сохранения) и saveinfo1.cof (описание сохранения - отображается в меню загрузки) Без второго файла просто не будет отображаться в меню.

Я это все к тому, что у тебя рабочий скрипт!

Варианта 2:

1) сделать такой бинд на загрузку:


bind f9 "load cofsave1"

и загружаться по ф9;

2) создать в папке ..Games\Cry of Fear\CryOfFear\SAVE файлик saveinfo1.cof с содержимым, к примеру:


Quick save

и загружаться из меню загрузки.

Проверено - работает!

Ссылка на комментарий
Поделиться на другие сайты

Хмм... про формат сохранений я в курсе. Однако лично у меня мой скрипт не создаёт никаких файлов! Ни .cof, ни .sav. У кого-то создаётся .sav? Очень интересно.. сейчас проверил, даже если сохраняться на магнитофон без читов файл.sav в этой функции не создаётся) надо искать другую((

Ссылка на комментарий
Поделиться на другие сайты

И правда что работает.. вчера почему-то не работало... тогда сейчас разберусь с тем нопом чтобы без него всё работало и придумаю как прикрутить описание .cof, чтобы самому не создавать

Итак, QuickSave в первый слот:


[ENABLE]

alloc(newmem,1024)
createthread(newmem)

newmem:
push ebp
mov ebp,esp
push eax
mov eax,1 //номер слота
mov ecx, [hl.dll+21928C] //указатель на игрока
push ecx
call hl.dll+12E790 //SaveGame
pop ecx
pop eax

mov esp,ebp
pop ebp
ret

hl.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 ebp
mov ebp,esp
push 101d3d48 //строка "load cofsave1" в памяти
call dword ptr [102394C4] //"запускатор" консольных команд в hw.dll
mov esp,ebp
pop ebp
ret

[DISABLE]
dealloc(newmem)

Такой вопрос: а можно как-то сделать кратковременную активацию скрипта и чтобы он сам скрипт отключал (выполнив то, что в [DISABLE]) ? А то немного криво работает: Нажали F5, сохранились. Захотели опять сохраниться - надо ДВА РАЗА нажать F5, первое нажатие отключает скрипт, второе нажатие запускает...

Можно сделать ещё лучше: сохраняться в какой-нибудь слот номер 6 и грузиться забиндив bind f9 "load cofsave6" - тогда в первом слоте может быть магнитофонное сохранение. Но можно ограничиться и первым слотом, главное магнитофоном его не переписать ненароком)

PS: а мне понравилось) вот только без отладочной инфы повторить такой финт ушами в другой игре будет ой как сложно :( Надо тренироваться с Function Hacker'ом.

Ссылка на комментарий
Поделиться на другие сайты

>>Такой вопрос: а можно как-то сделать кратковременную активацию скрипта и чтобы он сам скрипт отключал (выполнив то, что в [DISABLE])

Я думаю есть несколько решений задачи, о которой идёт речь без использования отключения скрипта... Но все их надо проверять на практике и выяснять какой лучше и какой будет работать. Например, при начале потока он может нопить инструкции сам, а после завершения потока он же сам может и возвращать байты. Память можно создавать один раз и не разрушать её. Возможно здесь потребуется создать два АА скрипта. И вообще лучше всё на Lua переписать, так как он более гибкий к автоассемблированию, установке горячих клавиш и т.п.

Раз был вопрос именно про отключение АА, то предположительный ответ. Основная фича в том, что из АА-скрипта можно вызвать пользовательскую функцию Lua через LuaCall(название функции), которая найдёт этот скрипт в главной таблице CE (например, по имени) и выключит его после ожидания завершения потока, когда поток запишет 1 перед выходом в зарегистрированную переменную.

Как-то так:

[ENABLE]
LuaCall(Cheat0WaitThreadAndDisable) // вызывает Lua функцию ожидания завершения потока
registerSymbol(isThreadExecuted)
alalloc(newmem,1024)
createthread(newmem)
newmem:
push eax
mov eax,1
mov ecx, [hl.dll+21928C]
push ecx
call hl.dll+12E790 //SaveGame
pop ecx
pop eax
mov esp,ebp
pop ebp
mov byte [isThreadExecuted], 01
ret
isThreadExecuted:
db 00

hl.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

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.