Статья Подробный гайд по читам Unity

erLCoder

Пользователь
Сообщения
13
Реакции
1
Сегодня я хочу вам подробно показать, как сделать читы на простенькие Unity(Mono) игры.


  • игра Helltaker (очень простая и понятная игра для примера)
  • dnSpy *тык*
  • прямые руки

После того как все скачали, заходим в папку с игрой и переходим по пути Helltaker_Data/Managed
Мы попали в папку с дллками игры, где хранится весь сурс код, который мы откроем через dnSpy


Здесь нам нужна самая первая дллка Assembly-CSharp.dll обычно в ней хранится все самое важное для нас.

Открываем dnSpy и перекидываем туда эту длл.


открываем namespace "-", после чего мы видим классы игры

Теперь нам нужно создать наш модифицированный класс, с помощью которого мы будем изменять переменные и вызывать нужные нам методы.

Жмем правой кнопкой мыши по любому классу и находим пункт "Добавить класс (C#)". Нам открылось окно для написания кода класса

C#:
using System;
using UnityEngine;

public class Hax : MonoBehaviour
{
 
    private void Awake()
    {
 
    }
 
    private void Start()
    {
 
    }
 
    private void Update()
    {
 
    }
 
    private void OnGUI()
    {
 
    }
 
    private void Initialize()
    {
        Debug.Log("Hack Initialized") // выводим успешное инициализированние класса
    }
}

Обязательно после каждых манипуляций в dnSpy, нужно сохранять работу сочетанием клавиш ctrl+shift+s и нажимаем "OK"

Объясню для чего нужны нам все эти методы, которые мы только что создали в классе

  • Эта функция вызывается при инициализации объекта, до того как он станет активным. Она используется для инициализации переменных или состояния объекта, которые могут быть необходимы до того, как объект будет использован. Например, вы можете использовать Awake() для загрузки данных или настройки ссылок на другие компоненты.

  • Эта функция вызывается после Awake() и только один раз, когда объект становится активным. Она обычно используется для инициализации, которая зависит от других объектов, которые могут быть инициализированы в Awake(). Например, вы можете использовать Start() для начала анимации или установки начальных значений.

  • Эта функция вызывается один раз за кадр и используется для обновления логики игры, которая должна выполняться постоянно, например, обработка ввода пользователя, движение объектов или проверка условий. Если вам нужно, чтобы что-то происходило каждый кадр, вы помещаете это в Update().

  • Эта функция вызывается для отрисовки пользовательского интерфейса (UI) и обработки событий GUI. Она может использоваться для создания кнопок, текстовых полей и других элементов интерфейса.

  • Эта функция не является встроенной в Unity и была вызвана вручную.

Теперь нам нужно сделать "EntryPoint(точку входа)" для нашего класса, чтобы он начал работу.
Для того чтобы это сделать в первую очередь нам нужно задать нашему классу namespace(например "Loader").

Нажимаем правой кнопкой мыши по нашему классу и находим в контекстном меню "Редактировать тип"


Здесь мы находим поле "Пространство имен", где мы зададим любое название, которое нам нравится

После чего у нас есть возможность подключить это пространство в других классах игры, чтобы мы смогли искусственно привязать наш модифицированный класс к объекту на сцене Unity.

Для того чтобы нам найти Entry класс, воспользуемся сочетанием клавиш ctrl+shift+k, где мы напишем "Manager". Возможно вам придется немного поискать этот класс. Жмем два раза левой кнопкой мыши


Почему именно этот класс, вы в свою очередь можете использовать любой который вам нравится, главное чтобы в нем был метод Awake(). В моем случае класс Manager загружается почти сразу и есть в каждой второй игре.


Нажимаем правой кнопкой мыши по пустому месту и открываем "Изменить класс (C#)".
После чего мы можем начинать писать нашу "точку входа".

Первым делом подключим созданное нами пространство имен "Loader"(в моем случае).
Для этого нам нужно дописать в первые строчки кода данную строку
C#:
using Loader;

теперь нам нужно инициализировать наш модифицированный класс "Hax" в классе "Manager" с помощью строчки. Вписываем это обязательно в теле класса после 9 строки, как на скриншоте выше
C#:
private Hax hax;

Сейчас нам нужно искусственно создать GameObject для того, чтобы привязать наш класс к нему и иметь активный класс в коде игры


C#:
    private void Awake()
    {
        if (Manager.instance == null)
        {
            Manager.instance = this;
        }
        else if (Manager.instance != this)
        {
            UnityEngine.Object.Destroy(base.gameObject);
        }
        UnityEngine.Object.DontDestroyOnLoad(base.gameObject);
        Cursor.visible = false;
        this.directory = Directory.GetCurrentDirectory();
        this.menuTxt = File.ReadAllLines(this.directory + "/local/m.json");
        this.dlcTxt = File.ReadAllLines(this.directory + "/localHM/hm_m.json");
        this.VolumeChange(0, PlayerPrefs.GetInt("musicVol", 2));
        this.VolumeChange(1, PlayerPrefs.GetInt("efxVol", 3));
        GameObject gameObject = new GameObject("HaxObject");
        this.hax = gameObject.AddComponent<Hax>();
        this.hax.Initialize();
        gameObject.SetActive(true);
    }

Все на этом этапе мы внедрили наш класс в игру.

Приступим к созданию меню. Переходим снова в наш класс, если трудно найти можно воспользоваться таким же поиском, когда мы искали класс "Manager".

Подготавливаем все переменные и хендлеры нажатий кнопок, чтобы открывать меню на Insert

C#:
private bool isMenuOpen;

C#:
private void Update()
{
    if (Input.GetKeyDown(KeyCode.Insert))
    {
        this.isMenuOpen = !this.isMenuOpen;
    }
}

Напишем наше меню с кнопкой для выхода из игры

C#:
private void OnGUI()
{
    if (this.isMenuOpen)
    {
        GUI.Box(new Rect(10f, 10f, 500f, 350f), "Diveya Menu")
        if (GUI.Button(new Rect(20f, 300f, 160f, 30f), "Exit"))
        {
            this.OnExitButtonCLicked();
        }
    }
   
}

private void OnExitButtonClicked()
{
    Debug.Log("Exit Button Clicked")
    Application.Quit();
}

Теперь можно смело сохранять все модули сочетанием клавиш ctrl+shift+s и заходить в игру проверять проделанную нами работу.


Для просмотра скрытого содержимого вы должны авторизироваться или зарегистрироваться



У нас получилось сделать меню и кнопка Exit работает. Теперь нам нужно изучить немного игру и понять что же можно с этим делать.
Первым делом я бы поискал в dnSpy класс "Player"

Как только мы нашли класс Player сразу же его инициализируем в нашем классе, чтобы мы могли модифицировать класс игрока.

C#:
private Player playerInstance;

private void Awake()
{
    this.playerInstance = UnityEngine.Object.FindObjectOfType<Player>();
    if (this.playerInstance == null)
    {
        Debug.LogError("Player instance not found!");
    }
}
Сразу объясню зачем нам нужны строчки в методе Awake(). Просто так инициализировать класс будет недостаточно, нужно найти объект в сцене Unity, к которому он привязан, для того, чтобы мы могли модифицировать код

Просмотрев методы класса "Player", есть очень нам удобные методы для простой функции "InstantWin", которая будет автоматически выигрывать в уровне, пропуская его.
Это методы Win() и Victory(), но посмотрев на код обеих методов, мы будем использовать метод Win(), запуская ее из корутины, как это делается в методе Victory()(возможно можно и сразу запускать метод Victory())

Напишем кнопку в наше меню для активации функции.

C#:
private void OnGUI()
{
    if (this.isMenuOpen)
    {
        GUI.Box(new Rect(10f, 10f, 500f, 350f), "Diveya Menu");
        if (GUI.Button(new Rect(20f, 40f, 160f, 30f), "InstantWin"))
        {
            base.StartCoroutine(this.playerInstance.Win());
        }
        if (GUI.Button(new Rect(20f, 300f, 160f, 30f), "Exit"))
        {
            this.OnExitButtonClicked();
        }
    }
}

Теперь мы можем пропускать уровни, посредством вызова метода Win()


Для просмотра скрытого содержимого вы должны авторизироваться или зарегистрироваться



Исходя из методов класса игрока, можно заметить интересный метод MinusWill(), который отвечает за убавление количества ходов. Там изменяется переменная will, посредством строчки this.will--;

Мы также можем это поменять

Создаем новую кнопку для нашего меню и модифицируем значение переменной

C#:
private void OnGUI()
{
    if (this.isMenuOpen)
    {
        GUI.Box(new Rect(10f, 10f, 500f, 350f), "Diveya Menu");
        if (GUI.Button(new Rect(20f, 40f, 160f, 30f), "InstantWin"))
        {
            base.StartCoroutine(this.playerInstance.Win());
        }
        if (GUI.Button(new Rect(20f, 80f, 160f, 30f), "InfiniteWills"))
        {
            this.playerInstance.will = 10000;
        }
        if (GUI.Button(new Rect(20f, 300f, 160f, 30f), "Exit"))
        {
            this.OnExitButtonClicked();
        }
    }
}

Теперь по нажатию кнопки у нас будет бесконечное количество ходов.

Просматривая класс игрока, можно заметить метод OnCantMove(), который может нам помочь отключить коллизию для игрока и бегать по всей карте без препятствий, но открыв данный метод ничего интересного мы не найдем пока не перейдем в метод OnKicked(), который там вызывается. Посреди не очень важного для нас кода можно заметить метод AttemptMove(). Этот код уже интереснее и его можно модифицировать, но он все также для нас недостаточно удобен. Внутри этого метода есть следующий метод, который нам очень интересен Move() в классе Moving(). Тут уже есть интересная нам строчка кода, где можно заметить переменную hit, а также переменную blockingLayer. Эту переменную мы можем обратить в 0 и не иметь никаких блокируемых слоев.

C#:
protected bool Move(int xDir, int yDir, out RaycastHit2D hit)
{
    Vector2 vector = base.transform.position;
    Vector2 vector2 = vector + new Vector2((float)xDir, (float)yDir);
    this.boxCollider.enabled = false;
    hit = Physics2D.Linecast(vector, vector2, this.blockingLayer); // <-- эту переменную надо модифицировать
    this.boxCollider.enabled = true;
    if (hit.transform == null)
    {
        base.StartCoroutine(this.SmoothMovement(vector2));
        return true;
    }
    return false;
}

Для того чтобы обратить переменную в ноль, нам нужно инициализировать класс Moving также как мы это делали с классом Player


C#:
private Moving movingClass;

C#:
private void Awake()
{
    this.playerInstance = UnityEngine.Object.FindObjectOfType<Player>();
    if (this.playerInstance == null)
    {
        Debug.LogError("Player instance not found!");
    }
    this.movingClass = UnityEngine.Object.FindObjectOfType<Moving>(); // ищем объект на сцене к которому подключен класс Moving
    if (this.movingClass == null)
    {
        Debug.LogError("Moving instance not found!");
    }
}

C#:
private void OnGUI()
{
    if (this.isMenuOpen)
    {
        GUI.Box(new Rect(10f, 10f, 500f, 350f), "Diveya Menu");
        if (GUI.Button(new Rect(20f, 40f, 160f, 30f), "InstantWin"))
        {
            base.StartCoroutine(this.playerInstance.Win());
        }
        if (GUI.Button(new Rect(20f, 80f, 160f, 30f), "InfiniteWills"))
        {
            this.playerInstance.will = 10000;
        }
        if (GUI.Button(new Rect(20f, 120f, 160f, 30f), "NoCollision"))
        {
            this.movingClass.blockingLayer = 0; // меняем значение всех блокируемых слоев на 0
        }
        if (GUI.Button(new Rect(20f, 300f, 160f, 30f), "Exit"))
        {
            this.OnExitButtonClicked();
        }
    }
}


Для просмотра скрытого содержимого вы должны авторизироваться или зарегистрироваться



Теперь мы можем ходить за картой и делать что захочется

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

Внутри класса Demon у нас есть два метода, которые вызывают разные эффекты и анимации, после чего демон просто пропадает с карты. Для того чтобы вызвать эту функцию Get() надо все также инициализировать наш класс Demon, найти к нему привязанный объект и просто вызвать.

C#:
private Demon demonClass;

C#:
private void Awake()
{
    this.playerInstance = UnityEngine.Object.FindObjectOfType<Player>();
    if (this.playerInstance == null)
    {
        Debug.LogError("Player instance not found!");
    }
    this.movingClass = UnityEngine.Object.FindObjectOfType<Moving>();
    if (this.movingClass == null)
    {
        Debug.LogError("Moving instance not found!");
    }
    this.demonClass = UnityEngine.Object.FindObjectOfType<Demon>();
    if (this.demonClass == null)
    {
        Debug.LogError("Demon instance not found!");
    }
}

C#:
private void OnGUI()
{
    if (this.isMenuOpen)
    {
        GUI.Box(new Rect(10f, 10f, 500f, 350f), "Diveya Menu");
        if (GUI.Button(new Rect(20f, 40f, 160f, 30f), "InstantWin"))
        {
            base.StartCoroutine(this.playerInstance.Win());
        }
        if (GUI.Button(new Rect(20f, 80f, 160f, 30f), "InfiniteWills"))
        {
            this.playerInstance.will = 10000;
        }
        if (GUI.Button(new Rect(20f, 120f, 160f, 30f), "NoCollision"))
        {
            this.movingClass.blockingLayer = 0;
        }
        if (GUI.Button(new Rect(20f, 160f, 160f, 30f), "DestroyDemon"))
        {
            this.demonClass.Get();
        }
        if (GUI.Button(new Rect(20f, 300f, 160f, 30f), "Exit"))
        {
            this.OnExitButtonClicked();
        }
    }
}

И так можно бесконечно продолжать вызывать разные методы и изменять переменные, чтобы упрощать себе игровой опыт.
Всем спасибо, кто читал, кому было интересно и полезна эта информация.
Предоставляю полный код моего класса из dnSpy

C#:
using System;
using UnityEngine;

namespace Loader
{
    // Token: 0x02000056 RID: 86
    public class Hax : MonoBehaviour
    {
        // Token: 0x060001B9 RID: 441
        private void Update()
        {
            this.playerInstance.cheater = false;
            if (Input.GetKeyDown(KeyCode.Insert))
            {
                this.isMenuOpen = !this.isMenuOpen;
            }
        }

        // Token: 0x060001BA RID: 442
        public Hax()
        {
        }

        // Token: 0x060001BB RID: 443
        public void Initialize()
        {
            Debug.Log("Hax initialized");
        }

        // Token: 0x060001BC RID: 444
        private void OnGUI()
        {
            if (this.isMenuOpen)
            {
                GUI.Box(new Rect(10f, 10f, 500f, 350f), "Diveya Menu");
                if (GUI.Button(new Rect(20f, 40f, 160f, 30f), "InstantWin"))
                {
                    base.StartCoroutine(this.playerInstance.Win());
                }
                if (GUI.Button(new Rect(20f, 80f, 160f, 30f), "InfiniteWills"))
                {
                    this.playerInstance.will = 10000;
                }
                if (GUI.Button(new Rect(20f, 120f, 160f, 30f), "NoCollision"))
                {
                    this.movingClass.blockingLayer = 0;
                }
                if (GUI.Button(new Rect(20f, 160f, 160f, 30f), "DestroyDemon"))
                {
                    this.demonClass.Get();
                }
                if (GUI.Button(new Rect(20f, 200f, 160f, 30f), "PowerOfLove"))
                {
                    this.playerInstance.PowerOfLove();
                }
                if (GUI.Button(new Rect(20f, 300f, 160f, 30f), "Exit"))
                {
                    this.OnExitButtonClicked();
                }
            }
        }

        // Token: 0x060001BD RID: 445
        private void OnExitButtonClicked()
        {
            Debug.Log("Exit Button Clicked");
            Application.Quit();
        }

        // Token: 0x060001BE RID: 446
        private void Awake()
        {
            this.playerInstance = UnityEngine.Object.FindObjectOfType<Player>();
            if (this.playerInstance == null)
            {
                Debug.LogError("Player instance not found!");
            }
            this.movingClass = UnityEngine.Object.FindObjectOfType<Moving>();
            if (this.movingClass == null)
            {
                Debug.LogError("Moving instance not found!");
            }
            this.demonClass = UnityEngine.Object.FindObjectOfType<Demon>();
            if (this.demonClass == null)
            {
                Debug.LogError("Demon instance not found!");
            }
        }

        // Token: 0x04000257 RID: 599
        private bool isMenuOpen;

        // Token: 0x04000258 RID: 600
        private Player playerInstance;

        // Token: 0x0400025A RID: 602
        private Moving movingClass;

        // Token: 0x0400025C RID: 604
        private Demon demonClass;
    }
}
 
Последнее редактирование:
Верх Низ