-
Автор темы
- #1
Более удобный формат на Notion -
Введение
Всем привет! Графический интерфейс в CS:GO достаточно удобен, и уникален, но при разработке читов вам могла прийти идея заменить его на свою реализацию - более удобную, более красивую, да и в общем идеальную! Ведь этого нету у конкурентов, а у вас есть?
Что же такое Panorama?
Предлагаю обратиться к немного душной документации Valve:
Перед тем как приступить к реверс-инжерингу панорамы, нужно ознакомиться с базовыми концепциями нужных нам вещей в V8 ( Ведь в панораме используется именно он ). Предлагаю использовать аналогию с аниме “Блич”:
Isolate - это отдельный "мир" в V8, как Уэко Мундо в Блич. В каждом Isolate есть своя память (хип) и стек, и они изолированы друг от друга. Разные скрипты выполняются в разных Isolate, как разные эпизоды аниме в разных мирах.
По такой же аналогии хип - это пространство Уэко Мундо, где находятся все живые персонажи и локации.
В хип помещаются:
Таким образом, Isolate - это отдельный мир, HandleScope - эпизод в этом мире, хип - пространство всех живых персонажей, а хэндлы - персонажи, "живущие" в этом эпизоде.
Ищем отсылки к Panorama в client.dll
Хендлеры
Если посмотреть в документацию Valve, то можно найти все API методы (
Пример инициализации одного из методов( За пример я взял метод GetMapBSPName с GameStateAPI):
Давайте пройдёмся по каждой строчке псевдо-кода, сначала мы получаем Isolate от v8, после чего входим в скоп, и получаем объект для метода, создаём темплейт с нашим хендлером, и в конечном счёте регистрируем новый метод, и аргументы к нему.
Но вот вопрос: Что же находится в “Panorama::Handlers::GameStateAPI::GetMapBSPName”?
И опять же ничего сложного! Мы получаем Isolate, входим в скоп, проверяем в a + 8, если 0 или меньше, то вызываем ошибку, в ином случае получаем поинтер на обьект с кучи. Идём дальше, стоит глянуть что в “Panorama::AnotherHandlers::GameStateAPI::GetMapBSPName”
Получаем название карты, создаем строку Utf8 JS строку, и возвращаем её. Что в оригинале?
Думаю этот псевдо-код не нуждается в анализе.
Инициализация целой категории
Вот так выглядит функция которая инициализирует нашу категорию, а вот и наша функция которую мы только что разбирали:
Отлично, пора подняться на уровень выше.
Так, что же тут? Регистрируем новый скоп, заполняем CUtlAbstractDelegate, создаем объект, а после чего и глобальный, и закрываем скоп.
Чем выше поднимаемся, тем шире горизонты открываются
Прошлая анализируемая функция является членом виртуальной таблицы:
Как можно увидеть её смещение относительно начала виртуальной таблицы - 0xC байт ( 12 ), запоминаем и идём дальше. Чтобы продвинуться выше нужно воспользоваться отладчиком, я буду использовать
Сработал! Переходим в стэк вызовов, и переходим на предпоследнюю функцию, и вот на функцию выше.
И что же мы тут видим? Тоже самое смещение которые мы запомнили немного ранее. Разбираем важные аспекты:
За гранью видимого: Panorama.dll
Инициализация UI Engine
При вызове конструктора UI Engine как обычно устанавливаются поля значением по умолчанию, также устанавливается HTTP каллбек:
Немного ниже проверяется был ли уже инициализирован Java-Script движок V8, и инкрементирование количества созданных UI движков, также это всё контролируется мютексами, чтобы избежать состояние гонки:
После чего проверяются разные параметры запуска, входит в Isolate, устанавливает Exception Handler, и вызывается инициализацию контекста ( об этом еще поговорим ниже), а также вызывает “RegisterEventTypesWithEngine” и “RegisterPanelFactoriesWithEngine”:
Error Handler при срабатывании, если включена отладка вызывает __debugbreak, в ином случае Raise при этом выводя всю информацию.
Возвращаемся к инициализации контекста, она инициализирует свои методы по $. ( Как JQuery ). Сначала входит в изолейт, регистрирует скоп, а потом уже определяет методы:
В конце конечно происходит глобал референс:
Коротко о разметке
У загруженной разметки ( Layout ) есть своя структура - CLayoutFile, она поддерживает загрузку как с файла, так и с буффера, а также в любой момент можно перезагрузить разметку с буффера.
Предлагаю рассмотреть как раз перезагрузку с буффера:
Входим в скоп профилировщика ( сколько же этих скопов…), очищаем старый набор стилей для этой разметки, после чего чистим другие дочерние структуры, и вызываем заново загрузку с буффера. Сложно? Вроде как нет.
У файлов разметки также есть свой менеджер, который уже контролирует асинхронную загрузку с файлов, подгрузку стилей, конвертирование пути - ведь панорама использует специфический путь основанный на формате веб-ссылок.
Панели, панели, интерфейс из панелей.
Заметки: Стоило уделить больше времени этой главе.
Весь интерфейс как в VGUI построен на панелях, в панораме этот класс называется CUIPanel, и он имеет свою очень большую виртуальную таблицу. Разбирать каждый метод не имеет смысла, поэтому давайте разберём только в общих чертах за что методы отвечают:
У Panorama есть свой движок хэндлинга ввода - удивлены? И я вот тоже нет, ведь это очевидно. Он поддерживает управление с геймпадов, и даже стимдека ( Кто мог сомневаться?) . Конструктор выглядит следующим образом:
Как можно увидеть, оно устанавливает поля, а также вызывает конструкторы для геймпадов и контроллеров. Ниже устанавливает стандартный путь до настроек.
Давайте рассмотрим функцию которая вызывается каждый кадр, она кстати так и называется RunFrame:
Но как же он обрабатывает ввод? Всё просто! Для этого ему приходит “InputMessage”, и он его обрабатывает в InputEvent. Также к другим возможностям:
Устали читать? Я думаю на этом можно закончить, ведь основная цель этой статьи была выполнена, и думаю вы узнали о базовой архитектуре панорамы.
Но почему же в названии так-же написано “Counter-Strike 2”, если тут разбирается только CS:GO? Потому что все детали которые были разобраны тут подходят, и под CS 2, и под Dota 2, да и под другие игры где есть панорама.
Все материалы которые были затронуты, а также декомпилированные разбираемые функции, и DLL которые использовались в статье вы можете найти здесь:
Всем пока! Не забывайте подписываться на мой блог, чтобы узнавать о подобном раньше -
Отдельное спасибо:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Введение
Всем привет! Графический интерфейс в CS:GO достаточно удобен, и уникален, но при разработке читов вам могла прийти идея заменить его на свою реализацию - более удобную, более красивую, да и в общем идеальную! Ведь этого нету у конкурентов, а у вас есть?
Что же такое Panorama?
Предлагаю обратиться к немного душной документации Valve:
Концепции V8 на примере аниме “Блич”Panorama - это новый фреймворк пользовательского интерфейса, разработанный компанией Valve. Он создан под сильным влиянием современного веб-авторинга (HTML5/CSS/JS) и очень похож на него. Он обеспечивает быструю разработку, высокое качество и высокую производительность интерфейсов, а также бесшовную интеграцию с игровым контентом (3D-модели, частицы и т.д.).
Пожалуйста, авторизуйтесь для просмотра ссылки.
Перед тем как приступить к реверс-инжерингу панорамы, нужно ознакомиться с базовыми концепциями нужных нам вещей в V8 ( Ведь в панораме используется именно он ). Предлагаю использовать аналогию с аниме “Блич”:
Isolate - это отдельный "мир" в V8, как Уэко Мундо в Блич. В каждом Isolate есть своя память (хип) и стек, и они изолированы друг от друга. Разные скрипты выполняются в разных Isolate, как разные эпизоды аниме в разных мирах.
По такой же аналогии хип - это пространство Уэко Мундо, где находятся все живые персонажи и локации.
В хип помещаются:
- Объекты JavaScript ( персонажи)
- Функции ( Сценарии эпизода )
- Строки ( Локации )
- и т.д. ( Декорации )
Таким образом, Isolate - это отдельный мир, HandleScope - эпизод в этом мире, хип - пространство всех живых персонажей, а хэндлы - персонажи, "живущие" в этом эпизоде.
Ищем отсылки к Panorama в client.dll
Хендлеры
Если посмотреть в документацию Valve, то можно найти все API методы (
Пожалуйста, авторизуйтесь для просмотра ссылки.
). Они все инициализируются в движке, и там-же имеют свои обёртки.Пример инициализации одного из методов( За пример я взял метод GetMapBSPName с GameStateAPI):
Давайте пройдёмся по каждой строчке псевдо-кода, сначала мы получаем Isolate от v8, после чего входим в скоп, и получаем объект для метода, создаём темплейт с нашим хендлером, и в конечном счёте регистрируем новый метод, и аргументы к нему.
Но вот вопрос: Что же находится в “Panorama::Handlers::GameStateAPI::GetMapBSPName”?
И опять же ничего сложного! Мы получаем Isolate, входим в скоп, проверяем в a + 8, если 0 или меньше, то вызываем ошибку, в ином случае получаем поинтер на обьект с кучи. Идём дальше, стоит глянуть что в “Panorama::AnotherHandlers::GameStateAPI::GetMapBSPName”
Получаем название карты, создаем строку Utf8 JS строку, и возвращаем её. Что в оригинале?
Думаю этот псевдо-код не нуждается в анализе.
Инициализация целой категории
Вот так выглядит функция которая инициализирует нашу категорию, а вот и наша функция которую мы только что разбирали:
Отлично, пора подняться на уровень выше.
Так, что же тут? Регистрируем новый скоп, заполняем CUtlAbstractDelegate, создаем объект, а после чего и глобальный, и закрываем скоп.
Чем выше поднимаемся, тем шире горизонты открываются
Прошлая анализируемая функция является членом виртуальной таблицы:
Как можно увидеть её смещение относительно начала виртуальной таблицы - 0xC байт ( 12 ), запоминаем и идём дальше. Чтобы продвинуться выше нужно воспользоваться отладчиком, я буду использовать
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Ставим брейкпоинт на начало нашей функции, и ждём пока сработает.Сработал! Переходим в стэк вызовов, и переходим на предпоследнюю функцию, и вот на функцию выше.
И что же мы тут видим? Тоже самое смещение которые мы запомнили немного ранее. Разбираем важные аспекты:
- Получает интерфейсы связанные с графическим интерфейсом.
- Проверяем все ли они валидны, если какой-то из них не валидный вызываем ошибку.
- Получаем синглетон объект PanoramaUIEngine
- Если не удачно - опять устанавливаем байт на 1, и вызываем ошибку.
- Итерируем пока (this + 544) не будет достигнута, сразу делаем вывод что это количество категорий
- Получаем указатель на наш объект с массива указателей
- Вызываем инициализацию нужной категории.
За гранью видимого: Panorama.dll
Инициализация UI Engine
При вызове конструктора UI Engine как обычно устанавливаются поля значением по умолчанию, также устанавливается HTTP каллбек:
Немного ниже проверяется был ли уже инициализирован Java-Script движок V8, и инкрементирование количества созданных UI движков, также это всё контролируется мютексами, чтобы избежать состояние гонки:
После чего проверяются разные параметры запуска, входит в Isolate, устанавливает Exception Handler, и вызывается инициализацию контекста ( об этом еще поговорим ниже), а также вызывает “RegisterEventTypesWithEngine” и “RegisterPanelFactoriesWithEngine”:
Error Handler при срабатывании, если включена отладка вызывает __debugbreak, в ином случае Raise при этом выводя всю информацию.
Возвращаемся к инициализации контекста, она инициализирует свои методы по $. ( Как JQuery ). Сначала входит в изолейт, регистрирует скоп, а потом уже определяет методы:
В конце конечно происходит глобал референс:
Коротко о разметке
У загруженной разметки ( Layout ) есть своя структура - CLayoutFile, она поддерживает загрузку как с файла, так и с буффера, а также в любой момент можно перезагрузить разметку с буффера.
Предлагаю рассмотреть как раз перезагрузку с буффера:
Входим в скоп профилировщика ( сколько же этих скопов…), очищаем старый набор стилей для этой разметки, после чего чистим другие дочерние структуры, и вызываем заново загрузку с буффера. Сложно? Вроде как нет.
У файлов разметки также есть свой менеджер, который уже контролирует асинхронную загрузку с файлов, подгрузку стилей, конвертирование пути - ведь панорама использует специфический путь основанный на формате веб-ссылок.
Панели, панели, интерфейс из панелей.
Заметки: Стоило уделить больше времени этой главе.
Весь интерфейс как в VGUI построен на панелях, в панораме этот класс называется CUIPanel, и он имеет свою очень большую виртуальную таблицу. Разбирать каждый метод не имеет смысла, поэтому давайте разберём только в общих чертах за что методы отвечают:
- Установка/Получение атрибутов
- Установка/Получение разных свойств панели.
- Получение родительских/дочерних панелей
- Добавление разметок, стилей, скриптов
- Управление скроллом
- Управление нажатыми клавишами в пределах панели
- и прочее
У Panorama есть свой движок хэндлинга ввода - удивлены? И я вот тоже нет, ведь это очевидно. Он поддерживает управление с геймпадов, и даже стимдека ( Кто мог сомневаться?) . Конструктор выглядит следующим образом:
Как можно увидеть, оно устанавливает поля, а также вызывает конструкторы для геймпадов и контроллеров. Ниже устанавливает стандартный путь до настроек.
Давайте рассмотрим функцию которая вызывается каждый кадр, она кстати так и называется RunFrame:
Но как же он обрабатывает ввод? Всё просто! Для этого ему приходит “InputMessage”, и он его обрабатывает в InputEvent. Также к другим возможностям:
- Он позволяет перезагружать настройки кейбиндов
- Регистрировать новые кейбиндинги
- Устанавливать захват на определенное окно
- Проверять на активность разных клавиш
- Получить тип активного контроллера.
Устали читать? Я думаю на этом можно закончить, ведь основная цель этой статьи была выполнена, и думаю вы узнали о базовой архитектуре панорамы.
Но почему же в названии так-же написано “Counter-Strike 2”, если тут разбирается только CS:GO? Потому что все детали которые были разобраны тут подходят, и под CS 2, и под Dota 2, да и под другие игры где есть панорама.
Все материалы которые были затронуты, а также декомпилированные разбираемые функции, и DLL которые использовались в статье вы можете найти здесь:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Всем пока! Не забывайте подписываться на мой блог, чтобы узнавать о подобном раньше -
Пожалуйста, авторизуйтесь для просмотра ссылки.
Отдельное спасибо:
- Alexander pers0na2 за предварительный просмотр статьи, как обычный читатель
- Colby57. За предварительную оценку статьи
- Team Enterial, за помощь в исследовании
- Всем подписчикам Something Channel