Cоздаём мод меню на андроид игру
3. разбираться в указателях и адресной арифметике
если вы соответствуете каждому из вышеперечисленных критериев, можете приступать к прочтению статьи.
что такое мод меню?
В нашем случае мод меню это модификация игры, последствием которой является появление плавающего окна с множеством функций (меню), действие и комбинацию которых мы определяем в коде.
Оффсеты - в нашем случае это адреса функций, или смещения относительно указателя на объект класса.
Хук - перехват, т.е. "хукнуть функцию" это значит перехватить какую-либо функцию.
В нашем случае во время хука при вызове функции будет сначала вызываться наша функция, внутри которой мы делаем что хотим, после чего вызываем исходную функцию.
object->Update() - вызывается Update.
object->Update() - вызывается my_update c нашим кодом, после чего вызывается Update.
Подготовка рабочего пространства
Для того, чтобы работать с мод меню, необходимо иметь два приложения:
1. Apktool M (для декомпиляции и сборки приложений)
2. AIDE CMODs (для сборки нашего мод меню)
первое приложение будет работать прямо из коробки, найти его вы можете в плей маркете или на просторах интернета.
Со вторым приложением придётся попотеть, устанавливаем его.
из коробки компилировать нативный С++ код оно откажется. необходимо установить NDK (Native Development Kit).
NDK вы найдёте на просторах интернета, ибо я манал в рот заливать всё это на вирустотал для публикации на форуме (у меня не безлимитный интернет!).
1. копируем полный путь до архива (в моём случае это "/storage/emulated/0/Download/ndk_arm64.tar.gz") и переходим в AIDE CMODs.
2. при каждом новом запуске AIDE CMODs предложит скачать и установить свой NDK. отклоняем, у нас он уже скачан.
3. Нажимаем на три точки в правом верхнем углу.
6. Нажимаем на вкладку "Build & Run":
7. Ищем пункт "Manage native code support", нажимаем туда.
p.s. на этом моменте можем активировать пункт "Parallel build", это позволит слегка уменьшить время сборки меню.
8. Перед нами откроется меню, куда необходимо ввести полный путь до NDK, затем нажать на "Install".
"Пространство" для разработки мода установлено. Теперь мы можем и творить, и вытворять. переходим к следующему шагу.
За основу возьмём мод меню от LGLTeam.
Скачиваем архив, распаковываем его в любую удобную вам директорию.
так как мы будем работать с 32 битными библиотеками (дампить их), нам потребуется вырезать поддержку 64 битного хука из нашего проекта для работоспособности меню:
1. Вот тут удаляем все фильтры, кроме 'armeabi-v7a':
Заходим в AIDE CMODs, открываем папку с меню, затем заходим в папку "app".
Появится кнопка "Open android project", нажимаем её.
Переходим по пути "/src/main/jni", открываем файл "Main.cpp". здесь располагается наш код.
Рекомендую удалить функции-пустышки, которые служат лишь примером использования.
Теперь мы можем писать свой код, а так-же собирать наше меню в apk.
Существует множество способов добычи оффсетов, но я покажу вам самый простой.
Для примера я возьму игру "Extreme Car Driving Simulator".
2. Переименовываем формат в zip
3. Распаковываем полученный файл.
Нас встречает множество файлов и папок, из которых нам нужны лишь несколько.
4. Заходим в папку "lib", после чего заходим в папку "armeabi-v7a".
5. Заходим на сайт https://il2cppdumper.com/the-dumper-tool, нажимаем "select executable", и выбираем libil2cpp.so.
6. Теперь необходимо загрузить метадату. этот файл располагается по пути "\assets\bin\Data\Managed\Metadata\global-metadata.bat", загружаем его.
7. Снимаем галочку с "Configurations", чтобы настроить результат.
8. Снимаем галочки с 10 и 12 пунктов, они нам не понадобятся.
9. Нажимаем на большую кнопку "dump".
Так-же при наличии пк, всё это можно провернуть при помощи консольной утилиты, которую можно скачать с официального репозитория:
Запускаем её, выбираем необходимые файлы, после чего в директории с утилитой появится файл "dump.cs".
Полученный файл содержит дампы всех классов и функций.
Среди них ищем интересные, мне попался такой класс:
public class VehicleController : MonoBehaviour // TypeDefIndex: 2366 { // Fields public float maxSpeedForward; // 0x20 public float maxSpeedReverse; // 0x24 public static VehicleController current; // 0x4 private Transform m_transform; // 0xE8 protected Rigidbody m_rigidbody; // 0xEC private float m_speed; // 0x108 private float m_speedAngle; // 0x10C public bool grounded; // 0x138 public bool fullGrounded; // 0x139 // Methods // RVA: 0xA31FB8 Offset: 0xA31FB8 VA: 0xA31FB8 private void Update() { } // RVA: 0xA33514 Offset: 0xA33514 VA: 0xA33514 Slot: 4 protected virtual void FixedUpdate() { } // RVA: 0xA37920 Offset: 0xA37920 VA: 0xA37920 public void .ctor() { } }
У класса, (в частности наследуемого от MonoBehavior) существуют как минимум несколько функций из этого списка:
1. Update() - вызывается при каждом новом кадре.
2. FixedUpdate() - вызывается N раз в секунду.
3. Start() - вызывается до первого кадра или физики объекта.
4. Awake() - вызывается в момент загрузки сцены.
5. .ctor() - конструктор класса, вызывается при создании объекта.
Рядом с этими функциями мы можем видеть их оффсеты (адреса):
Адрес функции Update() - 0xA31FB8.
Оффсеты необходимы, чтобы хукнуть функцию, об этом поговорим чуть ниже.
Вкратце, весь код состоит из трёх основных частей.
Это переменные, отвечающие за состояния вашей функции: "включена" ли она, а так-же иные параметры, связанные с вашей функции.
bool fly_enabled = false; float fly_speed = 5.f;
Переменная fly_enabled отвечает за то, "включена" ваша функция, или нет.
Переменная fly_speed это один из возможных параметров вашей функции.
Важно: для каждой вашей функции используем отдельные переменные, чтобы не поломать логику чита.
После того, как мы получили оффсет необходимой функции, её необходимо хукнуть.
Функция Update() из кода игры в языке C++ будет иметь следующее определение:
void old_update(void* this); void new_update(void* this);
Где this - указатель на объект текущего класса.
Он используется для того, чтобы поменять значение у любого из полей нашего класса (так-же потребуются оффсеты).
old_update() - исходная функция, которая будет вызываться внутри функции new_update().
Если вы внимательно читали начало статьи, то вы поймёте, что new_update() это и есть my_update().
Допустим, оффсет поля float speed - 0x12F.
В коде игры функция, которая меняет значение поля класса, будет выглядеть как-то так:
void Update() { this.speed = 2; // дальше какая-то логика }
В коде нашего мода эта функция будет выглядеть чуть иначе. Сначала мы должны сконструировать указатель на необходимый адрес:
После чего с помощью оператора разыменовывания указателя получаем доступ к полю, и меняем его значение:
(float*)((uint64_t)this + 0x12F) = fly_speed;
Так-же не забываем про исходную функцию (old_update()), которую необходимо вызвать после всех наших манипуляций.
// исходная функция void old_update(void* this); // новая функция void new_update(void* this) { // делаем наши дела if (fly_enabled) { (float*)((uint64_t)this + 0x12F) = fly_speed; } // не забываем вызывать исходную функцию old_update(this); }
Осталось хукнуть две полученные функции. Делается это буквально в конце всего кода:
В нашем случае синтаксис будет следующим:
HOOK_LIB("libil2cpp.so", "0x12345", new_update, old_update);
"libil2cpp.so" - название библиотеки, которую мы хукаем.
"0x12345" - оффсет нашей функции. про оффсеты смотри выше.
new_update - наша функция, которая будет вызываться при вызове object->Update().
old_update - исходная функция, которую мы вызываем из new_update.
Данную манипуляцию необходимо проделывать со всеми вашими функциями. важно - названия функций НЕ должны повторяться!
Да-да. В меню присутствуют функции, которые видит пользователь. определяются они вот здесь:
Когда пользователь нажимает на ту или иную кнопку, вызывается функция Changes().
Внутри этой функции есть свич для обработки полученного ввода:
https://github.com/LGLTeam/Android-Mod-Menu/blob/master/app/src/main/jni/Main.cpp#L211
Именно в этой части кода мы определяем значение переменной fly_enabled (и других переменных-состояний).
Таким образом пишем реализацию всех наших функциий, которые будут присутствовать в мод меню.
Написать мы можем почти что угодно, всё ограничено лишь вашей фантазией (и скиллом).
После написания кода необходимо собрать мод меню. Сделать это можно, нажав на соответствующую кнопку. В таком случае AIDE CMODs предложит нам установить apk. Устанавливаем его, затем вытягиваем его любым удобным способом.
Когда мы получим apk с модом, потребуется разобрать его при помощи Apktool M. Точно таким-же образом разбираем apk игры.
Заходим в папку "lib" и удаляем папку "arm64-v8a".
Заходим в папку "armeabi-v7a", затем из нашего мода достаём библиотеки и вставляем их в нашу папку. Создаём папку "smali_classesX" (сортируем папки по имени, если последняя папка "smali_classes2" оканчивается на число 2, значит вместо X пишем число 3). Кидаем в эту папку содержимое папки "smali_classes" нашего мода.
Открываем файл манифеста, листаем до кода с разрешениями, и кидаем туда следующее:
Листаем до сервисов (обычно почти в самом конце файла), и пихаем туда следующее:
- <service android:name="com.android.support.Launcher" android:enabled="true" android:exported="false" android:stopWithTask="true" />
Далее заходим по пути "smali_classesN/com/unity3d/player" и открываем файл "UnityPlayerActivity.smali".
Это метод, отвечающий за создание главного активити игры. После .locals 2 добавляем наш код:
Готово. Осталось лишь собрать apk игры, установить его, и наслаждаться проделанной работой.