1. Архитектура Rust DevBlog и фундаментальные принципы движка Unity
Архитектура Rust DevBlog и фундаментальные принципы движка Unity
Представьте, что вы пытаетесь взломать сейф, не зная, как устроены его ригели и кодовые диски. Вы можете бесконечно перебирать комбинации, но одно понимание механики замка сокращает время работы в десятки раз. В мире разработки внешнего ПО для Rust DevBlog (версий игры периода 2013–2018 годов) таким «замком» является движок Unity. Именно его архитектурные особенности определяют, как данные об игроках хранятся в оперативной памяти, как рассчитывается отдача оружия и почему поиск координат противника всегда начинается с одной и той же структуры данных.
Философия Unity: Entity-Component System в контексте Rust
Rust DevBlog базируется на классической для Unity архитектуре «Сущность-Компонент» (Entity-Component). В отличие от чистого объектно-ориентированного программирования, где логика и данные часто жестко связаны в иерархии классов, Unity разделяет их.
Базовым строительным блоком здесь является GameObject. Сам по себе он пуст — это лишь контейнер, «крючок» в игровом мире. Все значимое поведение (физика, отрисовка, логика здоровья) добавляется к нему через компоненты (Component). Для разработчика внешнего софта это означает, что если мы нашли в памяти объект игрока, то его координаты, инвентарь и состояние здоровья не лежат в одной сплошной структуре. Они разнесены по разным компонентам, ссылки на которые хранятся в массивах внутри GameObject.
В Rust DevBlog эта концепция доведена до абсолюта. Игрок — это BasePlayer, который является компонентом, прикрепленным к GameObject. Но рядом с ним на том же объекте могут висеть компоненты PlayerInventory, PlayerModel или PlayerTick.
> Важно понимать иерархию: GameObject содержит список компонентов. Чтобы изменить отдачу, вам нужно найти компонент оружия. Чтобы увидеть игрока сквозь стену, нужно найти компонент трансформации (Transform) и извлечь из него матрицу позиционирования.
Жизненный цикл памяти: Managed vs Unmanaged
Rust написан на языке C#, который работает в среде Mono (в старых версиях) или IL2CPP (в более новых). DevBlog-версии преимущественно использовали Mono. Это критический нюанс для реверс-инжиниринга, так как Mono подразумевает наличие управляемой памяти (Managed Memory) и сборщика мусора (Garbage Collector).
Когда мы работаем с внешним процессом через WinAPI (ReadProcessMemory), мы сталкиваемся с двумя типами данных:
GameObjectManager, структуры Transform и рендерер. Они находятся в адресном пространстве UnityPlayer.dll. Эти данные стабильны, их структура не меняется от запуска к запуску, а смещения (offsets) внутри них диктуются версией самого движка Unity.BasePlayer, Item, BuildingPrivilege. Они живут в «куче» Mono. Доступ к ним осуществляется через mono.dll (или mono-2.0-bdwgc.dll).Для разработки ESP нам жизненно необходимо понимать, как эти два мира связаны. Unity использует концепцию «оберток». У каждого управляемого объекта в C# есть указатель на его нативную часть в C++. Например, поле m_CachedPtr в классе UnityEngine.Object — это прямой адрес структуры в нативной памяти движка. Если вы нашли BasePlayer в C#, вы можете через этот указатель выйти на GameObject в нативном коде, и наоборот.
Глобальный корень: GameObjectManager (GOM)
Любое исследование памяти Rust начинается с поиска «точки входа». В Unity такой точкой является GameObjectManager. Это внутренняя структура движка, которая хранит списки всех активных объектов в текущей сцене.
Структура GOM в версиях Unity, на которых работал Rust DevBlog, представляет собой набор связанных списков (Linked Lists). Объекты в них разделены по категориям:
Для внешнего чита GOM — это «телефонная книга». Проходя по этому списку, мы можем отфильтровать объекты по имени или тегу. Если имя объекта содержит "player.prefab" или "scientist", мы понимаем, что нашли сущность, которую нужно отрисовать в ESP.
Математически проход по GOM можно представить как итерацию по узлам, где каждый узел содержит указатель на данные объекта и указатель на следующий узел. Если обозначить базовый адрес GOM как , то адрес первого активного объекта будет находиться по определенному смещению:
Где квадратные скобки означают операцию разыменования указателя (чтение памяти).
Система координат и компонент Transform
Как только мы нашли объект игрока через GOM, нам нужно узнать, где он находится в трехмерном пространстве. В Unity за это отвечает компонент Transform.
В старых версиях Rust данные трансформации хранятся в иерархической структуре. Важно различать локальные координаты (относительно родительского объекта) и мировые координаты (относительно центра карты). Для ESP нам всегда нужны мировые координаты.
Однако Unity оптимизирует хранение координат. Вместо того чтобы просто хранить вектор Vector3(x, y, z), движок часто использует матрицы или упакованные структуры в TransformInternal. В контексте Rust DevBlog доступ к позиции часто идет через цепочку:
GameObject -> ComponentList -> Transform -> VisualData -> Position.
Стоит учитывать, что Rust — это игра с огромным открытым миром. Для оптимизации точности вычислений на больших расстояниях Unity использует числа с плавающей запятой (float). Из-за особенностей стандарта IEEE 754, при удалении от центра координат () на десятки километров, точность float падает. Это приводило к «дрожанию» моделей в ранних версиях игры, что также нужно учитывать при написании сглаживания (smoothing) для Aimbot.
Сетевая архитектура и синхронизация (Lidgren и RakNet)
Rust — многопользовательская игра, и то, что мы видим на экране, является лишь локальной копией данных, присланных сервером. В эпоху DevBlog Rust сменил несколько сетевых библиотек (от Lidgren до RakNet).
Для разработчика внешнего ПО это создает важное ограничение: состояние «записи». Если мы изменим координаты своего игрока в памяти процесса, мы переместимся лишь визуально (client-side). Сервер, обладающий авторитарностью, немедленно вернет нас назад или отключит за нарушение протокола (SpeedHack/FlyHack check).
Однако не все данные проверяются сервером с одинаковой строгостью. Например:
HeldEntity позволяло полностью убрать подброс ствола без немедленного бана от сервера.IsSprinting или IsGrounded часто можно манипулировать для активации функций типа "Spider-man" (лазание по стенам), так как античит тех лет не всегда корректно валидировал вектор движения по вертикали.Иерархия классов в Rust: От BaseNetworkable до BasePlayer
Чтобы эффективно читать данные, нужно понимать «генеалогию» классов Rust. Почти все объекты в игре наследуются от базового класса BaseNetworkable. Это позволяет серверу идентифицировать их по уникальному ID (net.ID).
Иерархия выглядит примерно так:
Когда мы ищем смещение для здоровья игрока, мы ищем его не в BasePlayer, а в BaseCombatEntity. Поскольку BasePlayer наследует BaseCombatEntity, поле здоровья будет находиться по одному и тому же смещению для игрока, медведя или деревянной стены. Это унифицирует разработку: один и тот же код может отображать HP как противников, так и построек.
Механика Raycasting и видимость объектов
Одной из самых сложных задач для ESP является проверка видимости (Visible Check). Игрок может находиться за стеной, и рисовать на нем яркий бокс — значит перегружать интерфейс лишней информацией.
В Unity проверка видимости реализована через систему Raycast (пускание лучей). Нативный движок просчитывает пересечение луча от камеры до объекта. Внешнему читу сложно вызвать нативную функцию LineOfSight напрямую, так как это требует внедрения кода (инъекции).
Внешние решения обычно идут двумя путями:
MeshRenderer есть поле isVisible. Оно становится true, если объект попал в пирамиду видимости (Frustum) камеры. Минус — это не учитывает стены, только поле зрения.В старых версиях Rust часто использовался упрощенный подход: если игрок находится в определенном радиусе и его модель обновляется (поле lastVisibleTime), он считается видимым.
Взаимодействие с WinAPI для доступа к памяти
Поскольку мы разрабатываем внешнее ПО, наш инструмент — это библиотека kernel32.dll. Основной цикл работы программы выглядит так:
FindWindow или CreateToolhelp32Snapshot для поиска процесса RustClient.exe.OpenProcess с флагами PROCESS_VM_READ (для ESP) и PROCESS_VM_WRITE (для NoRecoil).EnumProcessModules для нахождения базовых адресов библиотек UnityPlayer.dll и mono.dll.Особенность работы с Unity через WinAPI заключается в многоуровневых указателях. Чтобы добраться до позиции игрока, вам придется выполнить цепочку чтений:
BaseAddress -> StaticClass -> StaticInstance -> PlayerList -> PlayerObject -> Transform -> Position.
Каждое звено этой цепи — это вызов ReadProcessMemory. На больших списках (например, 100 игроков) количество вызовов WinAPI может достигать тысяч в секунду, что создает нагрузку на CPU. Оптимизация заключается в «пакетном» чтении: мы читаем не по 4 байта, а сразу весь блок данных (структуру целиком) за один вызов.
Особенности сборки DevBlog: Почему старое проще?
Современный Rust защищен мощными античитами (EAC) и использует технологию IL2CPP, которая превращает понятный C#-код в запутанный C++, лишая нас имен классов и методов. DevBlog-версии — это «золотой век» для обучения.
Assembly-CSharp.dll тех лет названия полей вроде public float recoilPitchMin открыты. Достаточно открыть файл в декомпиляторе (dnSpy), чтобы увидеть точное смещение.Архитектурный парадокс: Камера и проекция
Последний важный элемент архитектуры Unity, который мы затронем — это Camera. Чтобы превратить 3D-координаты игрока в памяти в 2D-координаты на вашем мониторе (для рисования бокса), нужно понимать, как работает матрица вида и проекции (View-Projection Matrix).
В Unity камера — это тоже GameObject с компонентом Camera. Она хранит матрицу размерностью . Эта матрица описывает:
Умножая вектор позиции игрока на эту матрицу, мы получаем координаты в так называемом «пространстве отсечения» (Clip Space), которые затем легко переводятся в пиксели экрана. Поиск этой матрицы в памяти UnityPlayer.dll — критическая задача, без которой невозможен ни ESP, ни Aimbot. В Rust DevBlog камера обычно доступна через статический указатель MainCamera, который можно найти, зная сигнатуру (уникальную последовательность байт) в коде движка.
Работа с Rust тех лет — это работа с «чистым» Unity. Понимая, как движок управляет объектами, как он разделяет нативную и управляемую память и как строит иерархию сущностей, вы закладываете фундамент, который позволит вам не просто копировать чужие смещения, а самостоятельно находить их после любого обновления игры.