Всем привет.
Спасибо Vasabist, который поднял тему RPG Maker. Он попросил сделать возможность ускорения персонажа при нажатии клавиши Shift.
Мы как-то давно с другом создавали игры на RPG Maker XP и я даже писал какие-то скрипты, расширяющие возможности персонажа.
Тогда уже был VX, но он нам не нравился из-за мелких моделей персонажей. В общем увидел тему, нахлынуло
Сами игры на RPG Maker XP - по сути большой набор Ruby-скриптов, которые выполняются движком RPG Maker с помощью Ruby-интерпретатора.
Сначала я предложил Vasabist использовать фильтры, так как повторяется ситуация с lua-играми - один код работает со множеством адресов.
Но потом вспомнил, что RPG Maker есть такой класс объектов как Events, который так же позволяет выполнить произвольный код при срабатывании определенных условий. Решил по-исследовать - думал выйти на функцию eval, которой скорее всего и выполняется пользовательский код.
Но всё оказалось даже проще, движок RPG Maker XP предоставляет функцию RGSSEval, которая выполняет произвольный код переданный в аргументах с доступом к глобальным переменным игры.
Собственно на RGSSEval выйти оказалось очень просто. Я скачал RPG Maker XP, создал на карте Event и попросил при взаимодействии с ним вывести строку "Hello, 1234444567":
После запустил игру и попробовал найти данную строку. И она нашлась! Ставим бряк на access и активируем Event. И в памяти мы видим следующее:
Да, ребят, это оказался memcpy. Но, это не главное. Главное что мы видим смещения адресов как RGSS104E.regex_error_code_to_str+52018, а это значит в движке RGSS104E присутствуют публичные функции, которые он любезно предоставляет нам. И открыв Memory View - View - Enumerate DLL's and Symbols мы видим RGSSEval:
Осталось понять как её использовать. В целом на это довольно просто выйти, жмем два раза на функцию данном окне или просто прыгаем на неё и выполняем Memory View - Tools - Dissect Code. В появившемся окне жмем старт и видим всех, кто использует RGSSEval:
Двойным нажатием на один из (Call) под RGSSEval прыгаем на место использования и видим, что у данной функции один аргумент (один push с выполняемой строкой перед вызовом) и она не двигает за собой стек (add esp,8 - восемь из-за того, чтобы не двигать стек два раза):
Собственно, можем копировать эти call-ы в наш AutoAssembler и пробовать вызывать. Для тестирования я набросал следующий скрипт:
Запускаем и видим следующее:
Ура, функция работает как мы и ожидали. Проверить доступные глобальные переменные мы можем через print global_variables, либо обратившись к какой-нибудь из известных нам (или пока только мне..) переменных напрямую, например print $game_player. Можем творить!
Собственно весь код игры на Ruby можно увидеть в меню Tools - Script Editor в RPG Maker. И скорее всего код в вашем текущем проекте будет совпадать в большом количестве игр на данном движке. Поэтому можем попытаться написать обертки для стандартных функций для выполнения наших хитрых задач:
Для начала рекомендую пробовать написать обертку в своем проекте, а после внедрять её в Cheat Engine (еще лучше для начала прочитать краткий мануал по Ruby, но я так рвался закончить код что пропустил этот пункт). И сразу оговорюсь, обертки над классами будут работать только до того, как эти классы станут объектами. Поэтому наш будущий чит можно будет активировать лишь раз - до начала игры. Это поправимо, Ruby позволяет определять функции у инстансов на лету, но код становится менее лаконичным, поэтому я пошел по простому пути.
Собственно для того, чтобы ускорить нашего персонажа мы должны воздействовать на атрибут move_speed класса Game_Player. Он является приватным, поэтому для взаимодействия с ним лучше сделать его видимым для всех. Опять же нам повезло и у Game_Player есть метод update, в котором мы сможем проверять нажата ли клавиша Shift, но если бы его не было, пришлось бы менять move_speed из вне.
Приступим. Проще всего будет исправить Game_Player создав класс Game_Player унаследовав его от оригинала:
class Game_Player < Game_Player
Для изменения move_speed нам понадобится сохранять его оригинальное значение и назначать скорость бега, выделим для этого две переменные - walk_speed и run_speed. Ну и заодно сделаем move_speed публичным:
attr_accessor :move_speed
attr_accessor :walk_speed
attr_accessor :run_speed
Добавляем функции переключения скоростей и простую проверку на то, бежит ли персонаж:
def start_run
@move_speed = @run_speed
end
def start_walk
@move_speed = @walk_speed
end
def running?
return (@move_speed == @run_speed)
end
Осталось установить начальные значения переменным и добавить проверку на нажатый SHIFT. Для этого обернем функции initialize и update класса Game_Player с помощью функции alias:
alias orig_initialize initialize
def initialize
orig_initialize
@walk_speed = @move_speed
@run_speed = 5
end
Видите? Мы попросили обозначить родительский initialize как orig_initialize и вызывали его в нашей функции initialize.
Скорость бега я установил на 5 (оригинальная скорость 4). Если увеличивать больше - персонаж просто летает и им неудобно управлять.
Тоже самое сделаем с update. На нажатый SHIFT проверить очень просто - движок предоставляет объект Input, который знает нажата требуемая клавиша или нет (её я нашел в оригинальных скриптах игры):
def update
if Input.press?(Input::SHIFT)
if !running?
start_run
end
else
if running?
start_walk
end
end
orig_update
end
Собственно и всё - скрипт готов. Если вы добавите его в оригинальные скрипты проекта, при нажатии клавиши SHIFT персонаж будет идти быстрее.
Осталось просто заменить наш print "Hello, World" на получившийся скрипт.
Выглядеть это будет следующим образом - каждую строку обрамляем в db 'xxxxx', где xxxx - строчка с кодом. В конце добавляем #13 #10 (каждое число через пробел) - это обычный Enter в конце строки (\n\r - если так привычнее). Я использовал для этого Sublime Text и его мультикурсоры, но можно воспользоваться простой заменой. Ну и не забываем 0 в конце, как конец текста. В итоге мы получим:
Теперь просто заменяем db 'print "Hello, world!"' на получившийся код и можем запускать в главном меню игры
Итоговый скрипт:
Итоговый скрипт на Ruby:
Видео работы скрипта:
Итого (или рубрика "О проблемах"):
Скрипт прекрасно работает если включать его в меню, до запуска игры. Но не получится загрузить уже существующие сохранения, так как оно выполняет через дампы и нашему коду там просто не откуда взяться С этим можно бороться подключая код на-лету, но это уже в следующей серии.
Так же скрипт ломает будущие сохранения - игра не сможет загрузиться, если чит не был включен. Увы, это частая проблема игр использующих моды, а мы фактически этим и занимались.
Вот и всё, народ )