-
Автор темы
- #1
Привет!
Решил сделать для вас перевод статьи с uc. Оригинал -
Перевод кривоват местами, ну нормально...
Предисловие пропустим, сразу к делу:
Для начала, я порекомендую вам скачать/установить эти вещи:
- IDA Professional: Бесплатная версия доступна на сайте HexRays.
- Class Informer: Плагин от Sirmabus'а с открытым исходным кодом, который анализирует дизассемблирование для восстановления классов из RTTI.
- Function String Associate: Плагин от того же Sirmabus'а с открытым исходным кодом, генерирует комментарии для строк, на которые ссылаются функции.
Дополнительно:
- SDK 2013: сохраните локальную копию репозитория.
- Слитый SDK 2007: найдите его, поискав на форумах.
- MinGW MSYS: таким образом Вы можете grep'ать SDK репозиторий:
Вы можете найти эти два плагина в гугле. После скачивания, переместите их в корневой каталог иды, в папку plugins.
Настройка IDA:
Откройте иду, зайдите в корневой каталог CS:GO и найдите client.dll.
Перетащите файл в иду и Вы увидите это:
В данном случае, нам нужна только "Manual load". Включение этой функции вызовет серию подсказок, спрашивающих, хотим ли мы дизассемблировать/разобрать части модуля.
Первая подсказка запрашивает нас базовый адрес для модуля. Вписываем 0, чтобы относительные адреса начинались с нуля.
Следующая серия подсказок спрашивает нас: хотим ли мы анализировать части модуля. Нажмите "yes" для всех, кроме последней:
Нажимайте "No", т.к. у нас нет .pdb файла.
Теперь, иде потребуется некоторое кол-во времени, чтобы разобрать файл. Когда анализ файла закончится, в окне вывода появится сообщение "The initial autoanalysis has been finished.".
Первое, что нам нужно сделать - изменить несколько параметров, чтобы интерфейс показывал нам полезную информацию, скрытую по умолчанию. Выберите 'Option' > 'General' в строке меню. Я показал четыре опции, которые изменил.
1. Это в основном просто предпочтения. Он изменяет адрес в левом поле, когда Вы находитесь в функции. Вместо абсолютных адресов типа `.text:0035235A` Вы будете видеть `sub_7E0CB7+оффсет`.
2. Это добавит 3-х значное значение справа от абсолютного адреса. Он отслеживает размер стека по мере его изменения с помощью функции.
3. Это создаст короткий комментарий определения для всех asm опкодов. Включение этого может быть полезно, если Вы неопытны в asm. Вы заходите выключить это, когда познакомитесь с обычными инструкциями, потому что комментарии загромождают разборку.
4. Это отступ между инструкциями и левым краем. Это чисто предпочтения. Мне нравится небольшое число, потому что оно уменьшает мёртвое пространство.
Определение ClientMode::CreateMove
Теперь мы готовы ревёрсить.
Начнём с запуска Class Informer'a. Выберите 'Edit' > 'Plugins' > 'Class Informer' из строки меню. Если не видите плагина в списке, значит Вы не установили его в правильном каталоге (не забудьте перезапустить иду после установки плагина!). Вы видите всплывающее это окно. Оставьте эти параметры и нажмите 'continue'
Если Вы не читали статью ReadRCE, вы можете это сделать прямо сейчас. Это может показаться странным, но эта информация является важным знанием и основой для плагина Class Informer.
Как только он загрузится, Вы увидите новую вкладку в иде.
Вкладка Class Informer'а (обведено жёлтым)
Vftable: расположение RTTI файла.
Methods: кол-во виртуальных методов, которыми обладает класс.
Flags: `M` означает, что класс имеет множественное наследование.
Type: название класса. Обычно, это будет то же имя класса, что и в SDKs.
Hierarchy: список классов, от которых этот класс наследуется.
Щелчок по любому классу приведёт Вас к его виртуальной таблице методов во вкладке дизассемблирование. Найдите 'ClientMode'. Есть много результатов, потому что 'ClientMode' - подстройка для других классов. Листайте вниз, пока не найдете 'IClientMode':
Вот в чём проблема. Мы видим класс интерфейса ClientMode внизу и несколько производных классов над ним. Как мы определим, какая таблица виртуальных методов класса содержит CreateMove, который мы хотим хукнуть?
grep'ните SDK! Откройте командную строку и перейдите в директорию SDK 2013 с помощью комманды 'cd'. Далее, выполните эту комманду:
grep -lir "::CreateMove" .
Флаг 'l': подавляет вывод контекста строки;
Флаг 'i': игнорирует регистр;
Флаг 'r': рекурсивный поиск (не забывайте о периоде);
":: CreateMove": необходимо для вывода каждого файла, в котором CreateMove определен в классе или пространстве имен.
Вывод выглядит примерно так:
./src/game/client/cdll_client_int.cpp
./src/game/client/clientmode_shared.cpp
./src/game/client/c_baseplayer.cpp
./src/game/client/hl2/c_basehlplayer.cpp
./src/game/client/hl2/hl2_clientmode.cpp
./src/game/client/hltvcamera.cpp
./src/game/client/in_main.cpp
./src/game/client/replay/replaycamera.cpp
./src/game/server/ai_basenpc.cpp
./src/game/shared/weapon_ifmsteadycam.cpp
clientmode_shared.cpp - это, скорее всего, то, что нам нужно, поэтому откройте этот файл. Найдите '::CreateMove', чтобы определить, в каком классе он определен:
ClientModeShared должен быть правильным классом, но во вкладке Class Informer есть 2 строки для ClientModeShared. Это происходит потому, что класс ClientModeShared использует множественное наследование (вспомните флаг M). Мы определяем, что класс с 57 методами является правильным после нахождения определения класса в clientmode_shared.h для анализа структуры наследования. Дважды щелкните эту строку и вы попадете в таблицу виртуальных методов ClientModeShared:
Мы переходим к списку кликабельных указателей функций. Щелчок по любому из желтых слов приведет к переходу к реализации функции. nullsub_X означает, что нет никакой реализации. Это может произойти по нескольким причинам, которые сейчас не важны. Наша цель - определить, какая из этих функций является CreateMove. Плагин Function String Associate значительно облегчит эту задачу. Запустите его из меню плагинов, как Вы сделали с Class Informer'ом. В появившемся окне выберите пункт "continue". Ида теперь должна выглядеть вот так:
Комментарии в правом поле - строки, на которые ссылается ссылка подпрограммы в той же строке. Например, если Вы дважды щелкните 'sub_2694A0`, то увидите строки `LevelInit`, `game_newmap` и 'mapname', используемые в реализации функции. Когда я начинаю ревёрсить vtable, мне нравится идентифицировать несколько функций, поэтому у меня есть ссылка для сравнения с SDK. Давайте посмотрим, сможем ли мы идентифицировать эту функцию по этим трем строкам. Grep'ните SDK - 'game_newmap`:
cd 2013_SDK_src
grep -lir game_newmap
Вывод выглядит примерно так:
./src/game/client/clientmode_shared.cpp
./src/game/client/game_controls/basemodelpanel.cpp
./src/game/client/game_controls/baseviewport.cpp
./src/game/client/game_controls/MapOverview.cpp
./src/game/client/hltvcamera.cpp
./src/game/client/playerspawncache.cpp
./src/game/client/replay/replaycamera.cpp
Мы можем работать с этими результатами. Откройте clientmode_shared.cpp и ищите `game_newmap`; найдено 3 результата. Только один из них встречается в определении метода: ClientModeShared::LevelInit. Перейдите к определению этой функции в IDA, дважды щёлкнув "sub_2694A0". Вы увидите нечто подобное. Я обозначил то, что имеет отношение к делу:
1. IDA объявляет аргументы функции и каждую локальную переменную в начале функции. IDA определила, что эта функция имеет один аргумент (arg_0), который равен 4 байтам (dword ptr 8). Положительное число 8 означает, что эта переменная находится на уровне EBP+8 (помните, что обратный адрес хранится на уровне EBP+4). Обратите внимание, что IDA не всегда может правильно определить количество аргументов или размеры аргументов.
2. Это - ссылка на строку. Эта строка обращается к глобальной переменной, представляющей строку 'LevelInit'. Это так же, как любая глобальная переменная, за исключением IDA (?), автоматически переименовывает строки на основе содержимого. Если бы это не было функцией, эта переменная была бы названа "dword_X".
Если у вас есть hexrays, поместите курсор в тело функции и нажмите "F5" (горячая клавиша декомпиляции по умолчанию). Вы увидите новую вкладку, содержащую код C. Вы можете очистить это, щелкнув правой кнопкой мыши фон и сняв флажок "Show casts". Вот что Вы должны увидеть с комментариями:
1. Определение функции имеет два аргумента. IDA представляет классы C++ в виде структур с явным указателем экземпляра. Если функция является методом класса, то первым аргументом всегда будет указатель на экземпляр класса, указатель "this". `А2' - это первый "реальный" аргумент.
2. Объявления локальных переменных. Они могут быть неточными, если IDA неправильно проанализировала части модуля. Некоторые переменные могут быть дубликатами, с неправильным размером, неправильным типом и т. д. Вы научитесь вовремя замечать и исправлять их.
3. Так выглядит виртуальный вызов при декомпиляции. Вы можете сломать эту линию вот так:
v4 = (*(*v3 + 156))(v3);
(*v3 + 156): v3 - это указатель, который разыменовывается, а затем добавляется к 156. Если v3 является полиморфным объектом, то этот код вычисляет адрес 39-го виртуального метода.
(*v3::method_39)(v3): это вызов функции. Мы разыменовываем указатель на функцию и передаем экземпляр объекта в качестве единственного аргумента. Из-за реализации IDA классов C++ аргумент v3 является явным указателем класса, поэтому эта функция имеет 0 "реальных" аргументов.
v4: результат вызова функции; возвращаемое значение v3::method_39.
4. Всякий раз, когда Вы видите префикс, который является типом, он указывает, что переменная является глобальной переменной (она статична, одноэлементна или объявлена в глобальной области видимости). Префикс 'dword' указывает, что переменная имеет размер 4 байта. В исходном файле это может выглядеть примерно так:
Сравните это изображение с SDK:
Структура почти идентична. Этот немаркированный метод должен быть ClientModeShared::LevelInit. Перейдите на вкладку "IDA View-A", чтобы вернуться к дизассемблированию. Наведите курсор на 'sub_2694A0' и нажмите клавишу 'N`, чтобы присвоить имя этой функции:
Выберите `Yes'. Давайте вернемся к ClientModeShared vtable. Мы теперь используем функцию LevelInit в качестве точки отсчета. Мне нравится добавлять индекс метода к функциям в vtables. Вычислите, чтобы определить, что это 25-й метод для этого класса (0 индексируется). Если Вы знаете python, Вы можете использовать окно вывода в нижней части экрана в качестве интерпретатора python. Напр.
Давайте воспользуемся этой ссылкой. Мы знаем, что ClientModeShared наследуется от интерфейса IClientMode и класса CGameEventListener. Таблица виртуальных методов(vtables), над которой мы работаем, представляет собой сторону IClientMode дерева наследования. Другая таблица виртуальных методов, которую мы нашли на вкладке Class Informer, представляет собой сторону CGameEventListener. Давайте посмотрим на iclientmode.h. CTRL+F 'LevelInit`:
Мы знаем, что виртуальные методы упорядочиваются по их объявлению в исходном файле. Мы можем сделать вывод, что CreateMove должен быть непосредственно над LevelInit. Имейте в виду, что SDK часто датируется(?) и порядок метода может быть не идентичен. Вы должны использовать SDK в качестве общего руководства для поиска справочных функций.
Вернитесь к иде, дважды щелкните по указателю функции непосредственно над "DD offset ClientModeShared_LevelInit". Мы можем проверить, что это CreateMove, сравнивая количество аргументов в дизассемблировании с прототипом CreateMove из SDK. Шестнадцатеричное представление объявляет два аргумента функции, которые соответствуют прототипу CreateMove. Это подаёт надежды. Давайте сравним реализацию ClientModeShared::CreateMove в clientmode_shared.cpp с нашей декомпиляцией этой функции:
Найдите её. Переключитесь на шестнадцатеричный вид и назовите функцию.
Конец.
Вообще, перевод статьи я делал, потому что думал, что тут расскажут как доставать индексы из кс, ну ладно...
Если заметили ошибки, пожалуйста, напишите в тему. Я не силён в иде и ревёрсинге, поэтому что-то мог перевести неправильно.
Решил сделать для вас перевод статьи с uc. Оригинал -
Пожалуйста, авторизуйтесь для просмотра ссылки.
(no ad).Перевод кривоват местами, ну нормально...
Предисловие пропустим, сразу к делу:
Для начала, я порекомендую вам скачать/установить эти вещи:
- IDA Professional: Бесплатная версия доступна на сайте HexRays.
- Class Informer: Плагин от Sirmabus'а с открытым исходным кодом, который анализирует дизассемблирование для восстановления классов из RTTI.
- Function String Associate: Плагин от того же Sirmabus'а с открытым исходным кодом, генерирует комментарии для строк, на которые ссылаются функции.
Дополнительно:
- SDK 2013: сохраните локальную копию репозитория.
- Слитый SDK 2007: найдите его, поискав на форумах.
- MinGW MSYS: таким образом Вы можете grep'ать SDK репозиторий:
Пожалуйста, авторизуйтесь для просмотра ссылки.
.Вы можете найти эти два плагина в гугле. После скачивания, переместите их в корневой каталог иды, в папку plugins.
Настройка IDA:
Откройте иду, зайдите в корневой каталог CS:GO и найдите client.dll.
Перетащите файл в иду и Вы увидите это:
В данном случае, нам нужна только "Manual load". Включение этой функции вызовет серию подсказок, спрашивающих, хотим ли мы дизассемблировать/разобрать части модуля.
Первая подсказка запрашивает нас базовый адрес для модуля. Вписываем 0, чтобы относительные адреса начинались с нуля.
Следующая серия подсказок спрашивает нас: хотим ли мы анализировать части модуля. Нажмите "yes" для всех, кроме последней:
Нажимайте "No", т.к. у нас нет .pdb файла.
Теперь, иде потребуется некоторое кол-во времени, чтобы разобрать файл. Когда анализ файла закончится, в окне вывода появится сообщение "The initial autoanalysis has been finished.".
Первое, что нам нужно сделать - изменить несколько параметров, чтобы интерфейс показывал нам полезную информацию, скрытую по умолчанию. Выберите 'Option' > 'General' в строке меню. Я показал четыре опции, которые изменил.
1. Это в основном просто предпочтения. Он изменяет адрес в левом поле, когда Вы находитесь в функции. Вместо абсолютных адресов типа `.text:0035235A` Вы будете видеть `sub_7E0CB7+оффсет`.
2. Это добавит 3-х значное значение справа от абсолютного адреса. Он отслеживает размер стека по мере его изменения с помощью функции.
3. Это создаст короткий комментарий определения для всех asm опкодов. Включение этого может быть полезно, если Вы неопытны в asm. Вы заходите выключить это, когда познакомитесь с обычными инструкциями, потому что комментарии загромождают разборку.
4. Это отступ между инструкциями и левым краем. Это чисто предпочтения. Мне нравится небольшое число, потому что оно уменьшает мёртвое пространство.
Определение ClientMode::CreateMove
Теперь мы готовы ревёрсить.
Начнём с запуска Class Informer'a. Выберите 'Edit' > 'Plugins' > 'Class Informer' из строки меню. Если не видите плагина в списке, значит Вы не установили его в правильном каталоге (не забудьте перезапустить иду после установки плагина!). Вы видите всплывающее это окно. Оставьте эти параметры и нажмите 'continue'
Если Вы не читали статью ReadRCE, вы можете это сделать прямо сейчас. Это может показаться странным, но эта информация является важным знанием и основой для плагина Class Informer.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Как только он загрузится, Вы увидите новую вкладку в иде.
Вкладка Class Informer'а (обведено жёлтым)
Vftable: расположение RTTI файла.
Methods: кол-во виртуальных методов, которыми обладает класс.
Flags: `M` означает, что класс имеет множественное наследование.
Type: название класса. Обычно, это будет то же имя класса, что и в SDKs.
Hierarchy: список классов, от которых этот класс наследуется.
Щелчок по любому классу приведёт Вас к его виртуальной таблице методов во вкладке дизассемблирование. Найдите 'ClientMode'. Есть много результатов, потому что 'ClientMode' - подстройка для других классов. Листайте вниз, пока не найдете 'IClientMode':
Вот в чём проблема. Мы видим класс интерфейса ClientMode внизу и несколько производных классов над ним. Как мы определим, какая таблица виртуальных методов класса содержит CreateMove, который мы хотим хукнуть?
grep'ните SDK! Откройте командную строку и перейдите в директорию SDK 2013 с помощью комманды 'cd'. Далее, выполните эту комманду:
grep -lir "::CreateMove" .
Флаг 'l': подавляет вывод контекста строки;
Флаг 'i': игнорирует регистр;
Флаг 'r': рекурсивный поиск (не забывайте о периоде);
":: CreateMove": необходимо для вывода каждого файла, в котором CreateMove определен в классе или пространстве имен.
Вывод выглядит примерно так:
./src/game/client/cdll_client_int.cpp
./src/game/client/clientmode_shared.cpp
./src/game/client/c_baseplayer.cpp
./src/game/client/hl2/c_basehlplayer.cpp
./src/game/client/hl2/hl2_clientmode.cpp
./src/game/client/hltvcamera.cpp
./src/game/client/in_main.cpp
./src/game/client/replay/replaycamera.cpp
./src/game/server/ai_basenpc.cpp
./src/game/shared/weapon_ifmsteadycam.cpp
clientmode_shared.cpp - это, скорее всего, то, что нам нужно, поэтому откройте этот файл. Найдите '::CreateMove', чтобы определить, в каком классе он определен:
C++:
bool ClientModeShared::CreateMove( float flInputSampleTime, CUserCmd *cmd )
{
// Let the player override the view.
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if(!pPlayer)
return true;
// Let the player at it
return pPlayer->CreateMove( flInputSampleTime, cmd );
}
Мы переходим к списку кликабельных указателей функций. Щелчок по любому из желтых слов приведет к переходу к реализации функции. nullsub_X означает, что нет никакой реализации. Это может произойти по нескольким причинам, которые сейчас не важны. Наша цель - определить, какая из этих функций является CreateMove. Плагин Function String Associate значительно облегчит эту задачу. Запустите его из меню плагинов, как Вы сделали с Class Informer'ом. В появившемся окне выберите пункт "continue". Ида теперь должна выглядеть вот так:
Комментарии в правом поле - строки, на которые ссылается ссылка подпрограммы в той же строке. Например, если Вы дважды щелкните 'sub_2694A0`, то увидите строки `LevelInit`, `game_newmap` и 'mapname', используемые в реализации функции. Когда я начинаю ревёрсить vtable, мне нравится идентифицировать несколько функций, поэтому у меня есть ссылка для сравнения с SDK. Давайте посмотрим, сможем ли мы идентифицировать эту функцию по этим трем строкам. Grep'ните SDK - 'game_newmap`:
cd 2013_SDK_src
grep -lir game_newmap
Вывод выглядит примерно так:
./src/game/client/clientmode_shared.cpp
./src/game/client/game_controls/basemodelpanel.cpp
./src/game/client/game_controls/baseviewport.cpp
./src/game/client/game_controls/MapOverview.cpp
./src/game/client/hltvcamera.cpp
./src/game/client/playerspawncache.cpp
./src/game/client/replay/replaycamera.cpp
Мы можем работать с этими результатами. Откройте clientmode_shared.cpp и ищите `game_newmap`; найдено 3 результата. Только один из них встречается в определении метода: ClientModeShared::LevelInit. Перейдите к определению этой функции в IDA, дважды щёлкнув "sub_2694A0". Вы увидите нечто подобное. Я обозначил то, что имеет отношение к делу:
1. IDA объявляет аргументы функции и каждую локальную переменную в начале функции. IDA определила, что эта функция имеет один аргумент (arg_0), который равен 4 байтам (dword ptr 8). Положительное число 8 означает, что эта переменная находится на уровне EBP+8 (помните, что обратный адрес хранится на уровне EBP+4). Обратите внимание, что IDA не всегда может правильно определить количество аргументов или размеры аргументов.
2. Это - ссылка на строку. Эта строка обращается к глобальной переменной, представляющей строку 'LevelInit'. Это так же, как любая глобальная переменная, за исключением IDA (?), автоматически переименовывает строки на основе содержимого. Если бы это не было функцией, эта переменная была бы названа "dword_X".
Если у вас есть hexrays, поместите курсор в тело функции и нажмите "F5" (горячая клавиша декомпиляции по умолчанию). Вы увидите новую вкладку, содержащую код C. Вы можете очистить это, щелкнув правой кнопкой мыши фон и сняв флажок "Show casts". Вот что Вы должны увидеть с комментариями:
1. Определение функции имеет два аргумента. IDA представляет классы C++ в виде структур с явным указателем экземпляра. Если функция является методом класса, то первым аргументом всегда будет указатель на экземпляр класса, указатель "this". `А2' - это первый "реальный" аргумент.
2. Объявления локальных переменных. Они могут быть неточными, если IDA неправильно проанализировала части модуля. Некоторые переменные могут быть дубликатами, с неправильным размером, неправильным типом и т. д. Вы научитесь вовремя замечать и исправлять их.
3. Так выглядит виртуальный вызов при декомпиляции. Вы можете сломать эту линию вот так:
v4 = (*(*v3 + 156))(v3);
(*v3 + 156): v3 - это указатель, который разыменовывается, а затем добавляется к 156. Если v3 является полиморфным объектом, то этот код вычисляет адрес 39-го виртуального метода.
(*v3::method_39)(v3): это вызов функции. Мы разыменовываем указатель на функцию и передаем экземпляр объекта в качестве единственного аргумента. Из-за реализации IDA классов C++ аргумент v3 является явным указателем класса, поэтому эта функция имеет 0 "реальных" аргументов.
v4: результат вызова функции; возвращаемое значение v3::method_39.
4. Всякий раз, когда Вы видите префикс, который является типом, он указывает, что переменная является глобальной переменной (она статична, одноэлементна или объявлена в глобальной области видимости). Префикс 'dword' указывает, что переменная имеет размер 4 байта. В исходном файле это может выглядеть примерно так:
C++:
static int g_iState; // объявление вне этой функции
...
if ( g_iState == -1 ) // строка 4
Структура почти идентична. Этот немаркированный метод должен быть ClientModeShared::LevelInit. Перейдите на вкладку "IDA View-A", чтобы вернуться к дизассемблированию. Наведите курсор на 'sub_2694A0' и нажмите клавишу 'N`, чтобы присвоить имя этой функции:
Выберите `Yes'. Давайте вернемся к ClientModeShared vtable. Мы теперь используем функцию LevelInit в качестве точки отсчета. Мне нравится добавлять индекс метода к функциям в vtables. Вычислите, чтобы определить, что это 25-й метод для этого класса (0 индексируется). Если Вы знаете python, Вы можете использовать окно вывода в нижней части экрана в качестве интерпретатора python. Напр.
Давайте воспользуемся этой ссылкой. Мы знаем, что ClientModeShared наследуется от интерфейса IClientMode и класса CGameEventListener. Таблица виртуальных методов(vtables), над которой мы работаем, представляет собой сторону IClientMode дерева наследования. Другая таблица виртуальных методов, которую мы нашли на вкладке Class Informer, представляет собой сторону CGameEventListener. Давайте посмотрим на iclientmode.h. CTRL+F 'LevelInit`:
C++:
virtual vgui::Panel *GetMessagePanel() = 0;
virtual void OverrideMouseInput( float *x, float *y ) = 0;
virtual bool CreateMove( float flInputSampleTime, CUserCmd *cmd ) = 0;
virtual void LevelInit( const char *newmap ) = 0;
virtual void LevelShutdown( void ) = 0;
virtual bool ShouldDrawViewModel( void ) = 0;
virtual bool ShouldDrawCrosshair( void ) = 0;
Вернитесь к иде, дважды щелкните по указателю функции непосредственно над "DD offset ClientModeShared_LevelInit". Мы можем проверить, что это CreateMove, сравнивая количество аргументов в дизассемблировании с прототипом CreateMove из SDK. Шестнадцатеричное представление объявляет два аргумента функции, которые соответствуют прототипу CreateMove. Это подаёт надежды. Давайте сравним реализацию ClientModeShared::CreateMove в clientmode_shared.cpp с нашей декомпиляцией этой функции:
C++:
bool ClientModeShared::CreateMove( float flInputSampleTime, CUserCmd *cmd )
{
// Let the player override the view.
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if(!pPlayer)
return true;
// Let the player at it
return pPlayer->CreateMove( flInputSampleTime, cmd );
}
Найдите её. Переключитесь на шестнадцатеричный вид и назовите функцию.
Конец.
Вообще, перевод статьи я делал, потому что думал, что тут расскажут как доставать индексы из кс, ну ладно...
Если заметили ошибки, пожалуйста, напишите в тему. Я не силён в иде и ревёрсинге, поэтому что-то мог перевести неправильно.
Последнее редактирование: