руководства
November 7, 2022

Создание зумхака для Banished

(вольный перевод руководства от Asbra, оригинал которого можно найти здесь)

Привет!

В этом руководстве мы создадим зумхак для игры Banished. По умолчанию возможности зума в игре довольно ограничены: мы можем немного приближать и наклонять камеру, но некоторым игрокам - вроде меня - хотелось бы подобраться поближе к земле и хорошенько рассмотреть то, что там происходит.

То, чему вы научитесь, пользуясь этим руководством, может быть применено и к другим играм, прежде всего стратегическим. Итак, как же создаётся зумхак?

Для начала запустим игру, а вместе с ней - нашего верного друга, Cheat Engine. В большинстве подобных ситуаций нам потребуется переменная с плавающей точкой (float), отвечающая за степень приближения/наклона камеры - приступим к её поиску.

Для начала ищем неизвестную переменную (Unknown initial value) типа float. Далее разворачиваем игру и либо приближаем, либо отдаляем камеру, затем переключаемся на Cheat Engine и ищем значение, которое, соответственно, уменьшилось (Decreased value) либо увеличилось (Increased value). Повторяем эту процедуру несколько раз: приближаем - ищем уменьшившееся значение - отдаляем - ищем увеличившееся (в некоторых играх дела обстоят противоположным образом, что можно выяснить опытным путём). Время от времени не меняем степень приближения и просто двигаем камеру или делаем что-то ещё на смоё усмотрение, после чего ищем не изменившееся значение (Unchanged value). Так мы избавимся от лишних значений, не имеющих отношения к зуму.

Со временем вы наткнётесь на значение, равное 20 при максимальном приближении и 130 при максимальном удалении. Щёлкните на нём правой кнопкой в Cheat Engine и выберите "Find out what writes to this address", затем максимально приблизьте камеру в игре и продолжайте нажимать на кнопку приближения. После этого сделайте то же самое с отдалением камеры. В Cheat Engine вы обнаружите две инструкции, возвращающие значение зума к максимуму/минимуму, когда вы пытаетесь добиться большего или меньшего значения.

Разбираем код

Вот ASM-дамп этого участка:

Application-steam-x64.exe+ED970 - F3 0F10 0D 6CED0700 - movss xmm1,[Application-steam-x64.exe+16C6E4]
Application-steam-x64.exe+ED978 - 85 DB - test ebx,ebx
Application-steam-x64.exe+ED97A - 74 3C - je Application-steam-x64.exe+ED9B8
Application-steam-x64.exe+ED97C - F3 0F10 47 3C - movss xmm0,[rdi+3C]
Application-steam-x64.exe+ED981 - 7E 0A - jle Application-steam-x64.exe+ED98D
Application-steam-x64.exe+ED983 - F3 0F5C 05 2DED0700 - subss xmm0,[Application-steam-x64.exe+16C6B8]
Application-steam-x64.exe+ED98B - EB 08 - jmp Application-steam-x64.exe+ED995
Application-steam-x64.exe+ED98D - F3 0F58 05 23ED0700 - addss xmm0,[Application-steam-x64.exe+16C6B8]
Application-steam-x64.exe+ED995 - 0F2F C8 - comiss xmm1,xmm0
Application-steam-x64.exe+ED998 - F3 0F11 47 3C - movss [rdi+3C],xmm0
Application-steam-x64.exe+ED99D - 76 09 - jna Application-steam-x64.exe+ED9A8
Application-steam-x64.exe+ED99F - C7 47 3C 0000A041 - mov [rdi+3C],41A00000
Application-steam-x64.exe+ED9A6 - EB 10 - jmp Application-steam-x64.exe+ED9B8
Application-steam-x64.exe+ED9A8 - 0F2F 05 89ED0700 - comiss xmm0,[Application-steam-x64.exe+16C738]
Application-steam-x64.exe+ED9AF - 76 07 - jna Application-steam-x64.exe+ED9B8
Application-steam-x64.exe+ED9B1 - C7 47 3C 00000243 - mov [rdi+3C],43020000
Application-steam-x64.exe+ED9B8 - F3 0F10 57 44 - movss xmm2,[rdi+44]

Посмотрим, что именно здесь происходит!

movss xmm1,[Application-steam-x64.exe+16C6E4] - 20.0 float

Загружает минимальное значение зума в регистр xmm1

movss xmm0,[rdi+3C]

Загружает текущее значение зума в регистр xmm0

subss xmm0,[Application-steam-x64.exe+16C6B8]

Уменьшает значение зума на 5.0 - так мы приближаем камеру

addss xmm0,[Application-steam-x64.exe+16C6B8]

Увеличивает значение зума на 5.0 - так мы её отдаляем

comiss xmm1,xmm0

Сравнивает текущее значение зума с минимальным

movss [rdi+3C],xmm0

Устанавливает новое значение зума

jna Application-steam-x64.exe+ED9A8

JNA (перейти, если не больше)

Если текущее значение зума больше минимально возможного, переходит дальше; если нет, запускает следующую инструкцию:

mov [rdi+3C],41A00000
jmp Application-steam-x64.exe+ED9B8

Устанавливает значение зума на минимум (20) и переходит дальше.

0xA041 в формате float - это 20

comiss xmm0,[Application-steam-x64.exe+16C738]

Сравнивает текущее значение зума с максимальным (130)

jna Application-steam-x64.exe+ED9B8

Если текущее значение зума не больше максимально возможного, переходит дальше; если нет, запускает следующую инструкцию:

mov [rdi+3C],43020000

Устанавливает значение зума на максимум (130)

0x0243 в формате float - это 130

Здесь мы можем заметить, что зум-фактор находится в 0x3C некого класса. Это может быть хорошим началом для реверс-инжиниринга этого класса (вероятно, задающего параметры камеры - её позицию, угол обзора и так далее), если вас это интересует.

Итак, как же нам заполучить возможность приближать или отдалять камеру без ограничений? Есть несколько возможных решений. Рассмотрим два из них.

Первый метод для неограниченного зума

Во-первых, мы могли бы избавиться от двух операций, возвращающих значение к максимальному/минимальному - то есть, вот этих двух:

mov [rdi+3C],41A00000
mov [rdi+3C],43020000

Помещаем вместо них NOP, команду, предписывающую ничего не делать (‘Replace with code that does nothing’ в Cheat Engine) - и проблема решена.

Второй метод для неограниченного зума

Куда интереснее было бы изменить один из переходов, чтобы полностью избежать проверок значения зума.

76 09 - jna Application-steam-x64.exe+ED9A8
C7 47 3C 0000A041 - mov [rdi+3C],41A00000
EB 10 - jmp Application-steam-x64.exe+ED9B8
0F2F 05 89ED0700 - comiss xmm0,[Application-steam-x64.exe+16C738]
76 07 - jna Application-steam-x64.exe+ED9B8
C7 47 3C 00000243 - mov [rdi+3C],43020000
F3 0F10 57 44 - movss xmm2,[rdi+44]

Если мы изменим JNA в первой строке на JMP и сделаем точкой перехода последнюю строку, мы избавимся от проверок. Сейчас байты инструкции JNA выглядят как 76 09. 76 - это, собственно, JNA (типа short). Инструкция JMP (short) будет выглядеть как EB, так что мы можем изменить байты на EB 09. Получается следующая инструкция перехода:

EB 09 - jmp Application-steam-x64.exe+ED9A8

Она, однако, переносит нас прямиком к следующей проверке - придётся поменять и дистанцию. Второй байт инструкции - 09. Чтобы переместиться к movss, нам нужно добавить дополнительные 0x10 - так что мы меняем 09 на 19.

Теперь инструкция выглядит как EB 19 вместо исходных 76 09:

EB 19 - jmp Application-steam-x64.exe+ED9B8

Зумхак готов! Если мы хотим сохранить его, следует:

  1. откатить изменения в строке, если они были (правый клик -> Restore with original code)
  2. выделив строку, выбрать Tools -> Auto Assemble (или нажать Ctrl+A)
  3. в открывшемся окне нажать Template -> Full Injection
  4. заменить исходную строку на нужную нам в появившемся коде
  5. сохранить получившийся скрипт в своей таблице (File -> Assign to current cheat table)
  6. при желании назначить горячую клавишу для включения / отключения скрипта (Set/Change Hotkeys при правом клике на скрипт в таблице)
  7. сохранить таблицу

Изменяем наклон камеры

А теперь небольшой бонус; если мы прокрутим код немного наверх, мы можем обнаружить вот этот участок:

FF 15 F3BD0500 - call qword ptr [Application-steam-x64.exe+149730]
84 C0 - test al,al
74 12 - je Application-steam-x64.exe+ED953
F3 0F10 47 44 - movss xmm0,[rdi+44]
F3 0F5C 05 BEEC0700 - subss xmm0,[Application-steam-x64.exe+16C60C]
F3 0F11 47 44 - movss [rdi+44],xmm0
44 0F2F 5F 44 - comiss xmm11,[rdi+44]
76 04 - jna Application-steam-x64.exe+ED95E
44 89 67 44 - mov [rdi+44],r12d
F3 0F10 47 44 - movss xmm0,[rdi+44]
41 0F2F C4 - comiss xmm0,xmm4
76 07 - jna Application-steam-x64.exe+ED970
C7 47 44 0000803F - mov [rdi+44],3F800000
F3 0F10 0D 6CED0700 - movss xmm1,[Application-steam-x64.exe+16C6E4]
85 DB - test ebx,ebx
74 3C - je Application-steam-x64.exe+ED9B8
F3 0F10 47 3C - movss xmm0,[rdi+3C]
7E 0A - jle Application-steam-x64.exe+ED98D
F3 0F5C 05 2DED0700 - subss xmm0,[Application-steam-x64.exe+16C6B8]

Здесь игра накладывает ограничения на наклон камеры.

Предлагаю вам самостоятельно разобраться в том, как это работает, и попробовать снять это ограничение, основываясь на том, что мы сделали с зумом ;)

Теперь мы, наконец, можем заглянуть в эти маленькие пикселизованные лица!