Ревёрсер среднего звена
-
Автор темы
- #1
Итак, вы хотите стать на тёмную сторону Доты. Сделать не чит, но ченджер... Что ж, сегодня я обучу вас азам этого искусства
В доте мета-вещами занимается публичный интерфейс CGCClient(GC — game coordinator). Стим-инвентарь, дота плюс, информация о профиле, даже присоединение к игре — всем этим заведует он. В этом гайде процесс будет описан на примере дота плюса. Итак, к делу:
GCClient берём из client.dll через интерфейс DOTA_GC_CLIENT. Алгоритм обновления информации об объекте GC-системы включает в себя три шага: найти, изменить, уведомить. Пойдём по порядку:
В GC всё построено на SharedObject-ах, которыми представлены, например, те же предметы в инвентаре. Нам нужен объект дотаплюса, он называется CDOTAGameAccountPlus. Первым делом ищем по хрефу «CDOTAGameAccountPlus», и... бинго!
Функция явно имеет в себе код нахождения объекта с нуля, это то, что нужно. Запишем её себе как FindGameAccountPlus. Переходим из IDA в x64dbg и смотрим в ReClass первый из qword-ов. Это CGCClientSharedObjectCache, главный контейнер в GC-системе. Далее в IDA смотрим, что за sub_XXXXXX:
Наблюдаем шедевр ручной обфускации от Valve, но много задумываться над бит-шифтами не стоит. Счётчик idx берётся с 0x10, а поинтер с 0x18, там у SO-кэша
В нашем случае typeID равен 2012(0x7dc), и становится понятно, почему цикл идёт с конца списка: именно там находится тайп-кэш с дотаплюсом. Возвращаемся в FindGameAccountPlus, дальше оно просто достаёт из CUtlVector’а у тайп-кэша на 0x10 последний элемент(в нашем случае вообще единственный).
Вас может потянуть на взятие сигнатуры этой функции, чтобы достать указатель на CGCClientSharedObjectCache, но этого делать не нужно, к концу гайда у вас всё будет.
Переходим к следующему шагу алгоритма: изменение данных. Некоторые SharedObjectы, на самом деле, являются лишь определённого рода обёртками над протобафами. Заметили, что после витейбла CDOTAGameAccountPlus сразу идёт CSODOTAGameAccountPlus? Это оно и есть. Получить его можно либо оффсетом, либо, как истинный джентльмен, вызвав vfunc 9 под названием GetPObject(который получает его по оффсету). Вполне логично будет предположить, что если в доте есть протобаф, то он определён в отслеживаемых прото-файлах. Находится CSOGameAccountPlus в
Также в функции, вызывающей FindGameAccountPlus можно найти консольную команду
Прекрасно, теперь мы можем включить дотаплюс следующим образом:
Но само по себе это ничего не изменит. Доту нужно оповестить об изменении, и тут мы переходим к последней стадии алгоритма. У GCClient есть массив листенеров, через которые оповещается GC-система. За обновления SO отвечает метод DispatchSOUpdated, можете посмотреть хрефы в дилибах. Я не буду вас мучать: «Unable to create object of type %d\n», с меньшим оффсетом от функции, пятый с конца call.
Видим тут простейшую итерацию по CUtlVector<ISharedObjectListener*> на 0x270 с вызовом виртуальной функции по индексу 1 с теми же параметрами(называется, как ни странно, SOUpdated). Параметры нам неизвестны, но и это не беда. Тут нам понадобятся
Тут нам нужны два из трёх:
Теперь наша задача — просто ребилднуть цикл из DispatchSOUpdated, и готово. Оповестится, на самом, только один из листенеров(инвентарь реагирует только на предметы). Full code:
Не забудьте, что вызывать апдейт нужно ТОЛЬКО в главном потоке игры! Это сделано для синхронизации. Вставьте куда-нибудь в хук RunFrame и будет вам счастье. Выставленный вами статус игра сохранит по выходу, поэтому рекомендую добавить проверку на уже включённый дота плюс, чтобы лишний раз плашку на весь экран не показывать.
Данный гайд был непосредственно проспонсирован Liberalist(https://yougame.biz/threads/242900/#post-2518552) и og(https://yougame.biz/threads/250228/post-2624629), спасибо нашим меценатам! Также не забывайте, что даже если я здесь что-то забыл, в
В доте мета-вещами занимается публичный интерфейс CGCClient(GC — game coordinator). Стим-инвентарь, дота плюс, информация о профиле, даже присоединение к игре — всем этим заведует он. В этом гайде процесс будет описан на примере дота плюса. Итак, к делу:
GCClient берём из client.dll через интерфейс DOTA_GC_CLIENT. Алгоритм обновления информации об объекте GC-системы включает в себя три шага: найти, изменить, уведомить. Пойдём по порядку:
В GC всё построено на SharedObject-ах, которыми представлены, например, те же предметы в инвентаре. Нам нужен объект дотаплюса, он называется CDOTAGameAccountPlus. Первым делом ищем по хрефу «CDOTAGameAccountPlus», и... бинго!
Функция явно имеет в себе код нахождения объекта с нуля, это то, что нужно. Запишем её себе как FindGameAccountPlus. Переходим из IDA в x64dbg и смотрим в ReClass первый из qword-ов. Это CGCClientSharedObjectCache, главный контейнер в GC-системе. Далее в IDA смотрим, что за sub_XXXXXX:
Наблюдаем шедевр ручной обфускации от Valve, но много задумываться над бит-шифтами не стоит. Счётчик idx берётся с 0x10, а поинтер с 0x18, там у SO-кэша
CUtlVector<CGCClientSharedObjectTypeCache*>
. В цикле проверяется оффсет тайп-кэша на 0x28, и если это значение равно typeID, то возвращает этот TypeCache. Значение берётся из enum'а EEconTypeID
, другие значения из которого можно восстановить по хрефу "Create(X)", где X это имя econ-класса(например, CEconItem).
Пожалуйста, авторизуйтесь для просмотра ссылки.
.В нашем случае typeID равен 2012(0x7dc), и становится понятно, почему цикл идёт с конца списка: именно там находится тайп-кэш с дотаплюсом. Возвращаемся в FindGameAccountPlus, дальше оно просто достаёт из CUtlVector’а у тайп-кэша на 0x10 последний элемент(в нашем случае вообще единственный).
Пожалуйста, авторизуйтесь для просмотра ссылки.
. На этом этапе у вас должно быть что-то вроде:
C++:
enum EEconTypeID // тут ещё есть полезные значения, вписанные dotakoder'ом
{
k_EEconTypeItem = 1,
k_CEconGameAccountClient = 7,
k_CDOTAGameAccountClient = 0x7D2,
k_CDOTAParty = 0x7D3,
k_CDOTAGameHeroFavorites = 0x7D7,
k_ECDOTAMapLocationState = 0x7D8,
k_ECDOTAPlayerChallenge = 0x7DA,
k_CDOTAGameAccountPlus = 0x7DC
};
class CGCClientSharedObjectTypeCache : public VClass {
public
GETTER(EEconTypeID, GetEconTypeID, 0x28);
};
class CGCClientSharedObjectCache : public VClass {
public:
GETTER(CUtlVector<CGCClientSharedObjectTypeCache*>, GetTypeCacheList, 0x10);
};
Переходим к следующему шагу алгоритма: изменение данных. Некоторые SharedObjectы, на самом деле, являются лишь определённого рода обёртками над протобафами. Заметили, что после витейбла CDOTAGameAccountPlus сразу идёт CSODOTAGameAccountPlus? Это оно и есть. Получить его можно либо оффсетом, либо, как истинный джентльмен, вызвав vfunc 9 под названием GetPObject(который получает его по оффсету). Вполне логично будет предположить, что если в доте есть протобаф, то он определён в отслеживаемых прото-файлах. Находится CSOGameAccountPlus в
Пожалуйста, авторизуйтесь для просмотра ссылки.
.Также в функции, вызывающей FindGameAccountPlus можно найти консольную команду
dota_game_account_plus_debug
, которая выводит значения полей протобафа.Прекрасно, теперь мы можем включить дотаплюс следующим образом:
C++:
class CProtobufSharedObjectBase : public VClass {
public:
google::protobuf::Message* GetPObject() {
return CallVFunc<9, google::protobuf::Message*>();
}
};
class CGCClientSharedObjectTypeCache : public VClass {
public:
CProtobufSharedObjectBase* GetProtobufSO() {
return *Member< CProtobufSharedObjectBase**>(0x10);
}
GETTER(uint32_t, GetEconTypeID, 0x28);
};
...
auto objCache = ???; // wait for it, wait for it...
for (auto& typeCache : objCache->GetTypeCacheList()) {
if (typeCache->GetEconTypeID() != EEconTypeID::k_CDOTAGameAccountPlus)
continue;
auto message = (CDOTAGameAccountPlus*)typeCache->GetProtobufSO()->GetPObject();
message->set_plus_flags(0);
message->set_plus_status(1);
}
Видим тут простейшую итерацию по CUtlVector<ISharedObjectListener*> на 0x270 с вызовом виртуальной функции по индексу 1 с теми же параметрами(называется, как ни странно, SOUpdated). Параметры нам неизвестны, но и это не беда. Тут нам понадобятся
Пожалуйста, авторизуйтесь для просмотра ссылки.
(ведь econ-система в вальв-играх однородная):SOUpdated( const CSteamID & steamIDOwner, const GCSDK::CSharedObject *pObject, GCSDK::ESOCacheEvent eEvent )
Тут нам нужны два из трёх:
CSteamID
— хранится на 0x28 у SOCache(vfunc 2 GetOwner, не менялась аж с 2018), возьмите не по значению, а по указателю(т. к. в сурсе ссылка)ESOCacheEvent
— полностью пастим этот enum себе, нас интересуетeSOCacheEvent_Incremental
.
Теперь наша задача — просто ребилднуть цикл из DispatchSOUpdated, и готово. Оповестится, на самом, только один из листенеров(инвентарь реагирует только на предметы). Full code:
C++:
using SOID_t = uint64_t;
enum EEconTypeID
{
k_EEconTypeItem = 1,
k_CEconGameAccountClient = 7,
k_CDOTAGameAccountClient = 0x7D2,
k_CDOTAParty = 0x7D3,
k_CDOTAGameHeroFavorites = 0x7D7,
k_ECDOTAMapLocationState = 0x7D8,
k_ECDOTAPlayerChallenge = 0x7DA,
k_CDOTAGameAccountPlus = 0x7DC
};
enum ESOCacheEvent
{
eSOCacheEvent_None = 0,
eSOCacheEvent_Subscribed = 1,
eSOCacheEvent_Unsubscribed = 2,
eSOCacheEvent_Resubscribed = 3,
eSOCacheEvent_Incremental = 4,
eSOCacheEvent_ListenerAdded = 5,
eSOCacheEvent_ListenerRemoved = 6,
};
class CGCClientSharedObjectCache : public VClass {
public:
GETTER(CUtlVector<CGCClientSharedObjectTypeCache*>, GetTypeCacheList, 0x10);
SOID_t* GetOwner() {
return MemberInline<SOID_t>(0x28);
}
};
class ISharedObjectListener : public VClass {
public:
void DispatchUpdate(SOID_t* soid, void* sharedObj, ESOCacheEvent ev) {
CallVFunc<1>(soid, sharedObj, ev);
}
GETTER(CGCClientSharedObjectCache*, GetSOCache, 0xA0);
};
class CProtobufSharedObjectBase : public VClass {
public:
google::protobuf::Message* GetPObject() {
return CallVFunc<9, google::protobuf::Message*>();
}
};
class CGCClientSharedObjectTypeCache : public VClass {
public:
CProtobufSharedObjectBase* GetProtobufSO() {
return *Member< CProtobufSharedObjectBase**>(0x10);
}
GETTER(uint32_t, GetEconTypeID, 0x28);
};
class CGCClient : public VClass {
public:
GETTER(CUtlVector<ISharedObjectListener*>, GetSOListeners, 0x270);
void DispatchSOUpdated(SOID_t* soid, void* sharedObj, ESOCacheEvent ev) {
auto listeners = GetSOListeners();
for (auto& listener : listeners)
listener->DispatchUpdate(soid, sharedObj, ev);
};
};
...
void UpdateDotaPlusStatus() {
auto inventory = Interfaces::GCClient->GetSOListeners()[1];
auto objCache = inventory->GetSOCache();
for (auto& typeCache : objCache->GetTypeCacheList()) {
if (typeCache->GetEconTypeID() != k_CDOTAGameAccountPlus)
continue;
auto message = (CDOTAGameAccountPlus*)typeCache->GetProtobufSO()->GetPObject();
message->set_plus_flags(!Config::UnlockDotaPlus);
message->set_plus_status(Config::UnlockDotaPlus);
Interfaces::GCClient->DispatchSOUpdated(objCache->GetOwner(), typeCache->GetProtobufSO(), eSOCacheEvent_Incremental);
}
}
Данный гайд был непосредственно проспонсирован Liberalist(https://yougame.biz/threads/242900/#post-2518552) и og(https://yougame.biz/threads/250228/post-2624629), спасибо нашим меценатам! Также не забывайте, что даже если я здесь что-то забыл, в
Пожалуйста, авторизуйтесь для просмотра ссылки.
всё есть.
Последнее редактирование: