Как правильно сделать call в своём скрипте?
-
Откопал в игре код:
StartNightImmediately::UsePower - cmp dword ptr [050310B8],01 StartNightImmediately::UsePower+7- jne StartNightImmediately::UsePower+A StartNightImmediately::UsePower+9- ret StartNightImmediately::UsePower+A- xor ecx,ecx StartNightImmediately::UsePower+C- xor edx,edx StartNightImmediately::UsePower+E- call Terraria.Main::SkipToTime StartNightImmediately::UsePower+13- ret
Есть ещё аналогичные вызовы дня, полдня, вечера... все отличаются только регистрами ECX и EDX перед CALL
Есть способ какой-то в свой скрипт добавить вызов этого метода?
Пока пробовал только такой вариант (символ debug_flag для одноразового срабатывания):
cmp byte ptr [debug_flag],0 je @f mov byte ptr [debug_flag],0 push ecx push edx xor ecx,ecx xor edx,edx call Terraria.Main::SkipToTime pop edx pop ecx @@:
Но игра вылетает
-
В качестве некого заключения данной темы хочу предложить кусочек своего скрипта, который демонстрирует как передачу параметров в функцию, так и получение от неё результата в случаях, если оные передаются через регистр.
// начало скрипта (flgRain выведен в таблицу и назначена горячая клавиша на установку значения в 1) cmp byte ptr [flgRain],0 // проверяем не было ли команды на смену дождя je endRain // если нет - завершаем скрипт mov byte ptr [flgRain],0 // если была, то сначала сбрасываем флаг обратно (нам не нужно беспрерывно менять дождь туда-сюда, нужно выполнить код лишь единожды) pushad // сохраняем все регистры в стек call Terraria.Main::get_IsItRaining // проверяем идёт ли сейчас дождь (результат вернётся в регистр EAX) test eax,eax // 0 - не идёт, 1 - идёт je @f // прыжок к секции начала дождя (пропустить остановку дождя) если EAX==0 call Terraria.Main::StopRain // останавливаем дождь popad // восстанавливаем регистры из стека jmp endRain // выход из скрипта (не нужно запускать дождь) @@: call Terraria.Main::StartRain // запустить дождь popad // восстановить регистры из стека endRain:
За идею использования инструкций pushad/popad хочу поблагодарить пользователя "youneuoy" с Discord канала GamehackLab[RU].
-
Вариант с передачей в стек не отличается результатом
cmp byte ptr [debug_flag],0 je @f mov byte ptr [debug_flag],0 push 00000000 push 00000000 call Terraria.Main::SkipToTime @@:
-
@paracetamol попробуй ret после call добавить - нет возврата из функции, поэтому вылет.
-
@garik66 Я не совсем понимаю.
ret - это возврат из текущей функции к коду, который её вызвал. Место где я делаю call - это не функция. Просто место кода, где я делаю всякие свои штуки.
Намереваюсь вместе с ними при некоторых условиях (взведён debug_flag) сделать так, чтобы отработала функция, находящаяся по адресу после call.
Функция которую я хочу вызвать - SkipToTimeTerraria.Main::SkipToTime - push ebp Terraria.Main::SkipToTime+1- mov ebp,esp Terraria.Main::SkipToTime+3- push edi Terraria.Main::SkipToTime+4- push esi Terraria.Main::SkipToTime+5- sub esp,08 Terraria.Main::SkipToTime+8- xor eax,eax Terraria.Main::SkipToTime+A- mov [ebp-0C],eax Terraria.Main::SkipToTime+D- mov edi,ecx Terraria.Main::SkipToTime+F- movzx esi,dl Terraria.Main::SkipToTime+12- movzx eax,byte ptr [058C128D] Terraria.Main::SkipToTime+19- cmp esi,eax Terraria.Main::SkipToTime+1B- je Terraria.Main::SkipToTime+4E Terraria.Main::SkipToTime+1D- call dword ptr [07F45530] Terraria.Main::SkipToTime+23- mov [ebp-0C],eax Terraria.Main::SkipToTime+26- cmp byte ptr [058C128D],00 Terraria.Main::SkipToTime+2D- je Terraria.Main::SkipToTime+3A Terraria.Main::SkipToTime+2F- lea ecx,[ebp-0C] Terraria.Main::SkipToTime+32- call dword ptr [07F4553C] Terraria.Main::SkipToTime+38- jmp Terraria.Main::SkipToTime+43 Terraria.Main::SkipToTime+3A- lea ecx,[ebp-0C] Terraria.Main::SkipToTime+3D- call dword ptr [07F45548] Terraria.Main::SkipToTime+43- movzx eax,byte ptr [058C128D] Terraria.Main::SkipToTime+4A- cmp esi,eax Terraria.Main::SkipToTime+4C- jne Terraria.Main::SkipToTime+1D Terraria.Main::SkipToTime+4E- mov [ebp-10],edi Terraria.Main::SkipToTime+51- fild dword ptr [ebp-10] Terraria.Main::SkipToTime+54- fstp qword ptr [058C0CE8] Terraria.Main::SkipToTime+5A- cmp dword ptr [058C10B8],02 Terraria.Main::SkipToTime+61- jne Terraria.Main::SkipToTime+82 Terraria.Main::SkipToTime+63- push -01 Terraria.Main::SkipToTime+65- push 00 Terraria.Main::SkipToTime+67- push 00 Terraria.Main::SkipToTime+69- push 00 Terraria.Main::SkipToTime+6B- push 00 Terraria.Main::SkipToTime+6D- push 00 Terraria.Main::SkipToTime+6F- push 00 Terraria.Main::SkipToTime+71- push 00 Terraria.Main::SkipToTime+73- push 00 Terraria.Main::SkipToTime+75- mov ecx,00000007 Terraria.Main::SkipToTime+7A- lea edx,[ecx-08] Terraria.Main::SkipToTime+7D- call Terraria.NetMessage::TrySendData Terraria.Main::SkipToTime+82- lea esp,[ebp-08] Terraria.Main::SkipToTime+85- pop esi Terraria.Main::SkipToTime+86- pop edi Terraria.Main::SkipToTime+87- pop ebp Terraria.Main::SkipToTime+88- ret
ret в ней есть к слову
Чтобы понять как правильно её вызывать (у неё есть параметры)
Я проводил искал код её вызывающий и нашёл 4 похожих друг на друга класса
Каждый из которых отличается в коде только формированием регистров ECX и EDX (гипотеза моя в том, что ECX - первый параметр, а EDX - второй)
Не мудрствуя лукаво решил просто сделать вызов так, как это делает один из этих классов своим методом UsePower:StartNightImmediately::UsePower - cmp dword ptr [058C10B8],01 StartNightImmediately::UsePower+7- jne StartNightImmediately::UsePower+A StartNightImmediately::UsePower+9- ret StartNightImmediately::UsePower+A- xor ecx,ecx StartNightImmediately::UsePower+C- xor edx,edx StartNightImmediately::UsePower+E- call Terraria.Main::SkipToTime StartNightImmediately::UsePower+13- ret StartNightImmediately::UsePower+14- add [eax],al
Т.е. такие 2 xor, затем call
Полагаю мой провал связан с тем, что помимо формирования корректного состояния регистров мне ещё нужно было озаботиться тем, чтобы также правильно сформировать стек. Всё дело в стеке? Может кто подсказать как изучить стек в момент перед вызовом call'а и как его повторить в своём коде?
-
Я нашёл решение и теперь у меня получилось успешно вызвать функцию - эффект от неё применился и никаких крашей!
Рассказываю и читающему, если ему вдруг тоже интересно
Как я и предполагал ранее, функция не читает ничего из стека. Передача параметров происходит исключительно через регистры. Для нас это значит следующее: не нужно никакой мороки с изучением и воспроизводством состояния стека. Просто забиваем требуемые параметры в регистры и делаем call.
Причины предыдущих неудач
Игра крашилась у меня не из-за того, что я что-то не так передавал, а из-за того, что вызываемая функция (SkipToTime, см.выше код) почему то:- уничтожает значения регистров edi и esi
Terraria.Main::SkipToTime+3- push edi Terraria.Main::SkipToTime+4- push esi Terraria.Main::SkipToTime+5- sub esp,08
- сразу за этим безобразием без бэкапов обнуляет eax
Terraria.Main::SkipToTime+8- xor eax,eax
Ну и соответственно после отработки функции и возвращения в исходный код начинали твориться "чудеса" из-за сбоя состояния регистров.
Найденное решение:
- бэкап всех регистров общего назначения, а также edi и esi (понятное дело ebp и esp бэкапить не нужно)
push eax push ebx push ecx push edx push edi push esi
- далее заполняем регистры необходимыми значениями (в моём случае это заполненные нулями ecx и edx)
xor ecx,ecx xor edx,edx
- собственно сам вызов
call Terraria.Main::SkipToTime
- ну и восстанавливаем состояние регистров (естественно в обратном бэкапу порядке)
pop esi pop edi pop edx pop ecx pop ebx pop eax
Всё.
В Террарии наступила ночь, часики перемотались на 7:30 вечера, игра не вылетела, я счастлив, мир прекрасен!
-
@paracetamol Ты прошёл по пути самурая, и пришёл к решению самостоятельно. Это круто! От себя добавлю - если бы ты сразу попробовал выяснить, какой тип вызовов в этой игре используется, возможно, твой путь к правильному решению был бы намного короче
-
@StoneWeaver Спасибо за приятные слова!
"попробовал выяснить, какой тип вызовов в этой игре используется" - дело в том, что я не знал вообще, что типы вызовов бывают разными. Просто где-то на просторах ютуба наткнулся на ролик обучающий и там рассказчик как раз упомянул, что параметры могут передаваться либо через регистры, либо через стек. Вот эта фраза и тригернула у меня цепочку рассуждений "у меня точно используются регистры при передаче, а что если стек тут вообще не при чём и зря я на нём зациклился" и далее и далее к конечной идее "бэкапим вообще все, регистры меняем только то что нужно, вызываем функцию, восстанавливаем все регистры".Всё верно пишешь - начать нужно было не с экспериментов, а с внимательного изучения кода внутри вызываемой функции и сравнения её с кодом той, которая её вызывает. Подобный анализ даёт понять как передаются параметры в функцию и как вызывающая сторона получает результат (в случае его наличия).
-
В качестве некого заключения данной темы хочу предложить кусочек своего скрипта, который демонстрирует как передачу параметров в функцию, так и получение от неё результата в случаях, если оные передаются через регистр.
// начало скрипта (flgRain выведен в таблицу и назначена горячая клавиша на установку значения в 1) cmp byte ptr [flgRain],0 // проверяем не было ли команды на смену дождя je endRain // если нет - завершаем скрипт mov byte ptr [flgRain],0 // если была, то сначала сбрасываем флаг обратно (нам не нужно беспрерывно менять дождь туда-сюда, нужно выполнить код лишь единожды) pushad // сохраняем все регистры в стек call Terraria.Main::get_IsItRaining // проверяем идёт ли сейчас дождь (результат вернётся в регистр EAX) test eax,eax // 0 - не идёт, 1 - идёт je @f // прыжок к секции начала дождя (пропустить остановку дождя) если EAX==0 call Terraria.Main::StopRain // останавливаем дождь popad // восстанавливаем регистры из стека jmp endRain // выход из скрипта (не нужно запускать дождь) @@: call Terraria.Main::StartRain // запустить дождь popad // восстановить регистры из стека endRain:
За идею использования инструкций pushad/popad хочу поблагодарить пользователя "youneuoy" с Discord канала GamehackLab[RU].
-
-