Ревёрсер среднего звена
-
Автор темы
- #1
В Доте есть баффы, дебаффы, ауры, пассивки и ещё много чего. Всё это в той или иной степени изменяет состояние героя, можно сказать, модифицирует его. Всё вышеперечисленное — модификаторы, и сегодня мы научимся с их помощью получать нужную информацию.
Итак, начнём. Основа любой сущности — класс, у модификаторов это
Хорошо, класс есть, теперь нужно получить его объекты. У C_DOTA_BaseNPC есть нетвар
У этого ModifierManager на оффсетах 0x10 и 0x30 есть очевидные
Вернёмся к анализу списка. Оказывается, предметы тоже добавляют баффы(ведь должны же они как-то менять статы героя), и этим можно пользоваться, чтобы с точностью определять момент, когда у героя эти предметы появляются/пропадают, следовательно вам не нужно будет каждый раз заново искать их в инвентаре героя или мучиться с реализацией кэширования.
Для реализации нам нужны какие-нибудь callback’и на создание и удаление модификаторов, и они у нас есть. Это деструктор CDOTA_Buff и виртуальная функция
Деструктор вызывается первым call'ом во второй виртуальной функции CDOTA_Buff, IDA-плагин SigMaker подскажет вам очень короткую сигнатуру.
Витейбл класса как раз кстати лежит внутри деструктора, следовательно механизм хуков будет такой:
И уже в OnAdd/OnRemove вы можете сохранить/удалить предмет где-нибудь в своей структуре, если он вам нужен. Соблюдайте осторожность: несмотря на то, что у большинства предметных модификаторов имя это просто “modifier_” + itemName, у той же манты, например, это не так(item_manta - modifier_item_manta_style). Актуальный список названий всех модификаторов в игре есть
Также у некоторых героев во время подготовки каста появляется модификатор, с помощью которого можно отслеживать этот каст(иногда через m_bInAbilityPhase этого не сделаешь). Например, у белки при зарядке ульты появляется
Однако суть именно проверки состояния героя осталась нераскрытой. Не бойтесь, вас не обманули!
Герой может быть в разных состояниях: БКБ, инвиз, стан, сон и т. п. Чтобы не перечислять модификаторы вручную, VALVe добавили C_DOTA_BaseNPC нетвар-флаг
Для работы нам понадобится
Например, у героя под оцепенением флаг будет 2⁰ = 0x1, что соответствует MODIFIER_STATE_ROOTED. Вещь крайне полезная, ибо нажимать какой-нибудь ульт зевса в героя под БКБ было бы пренеприятнейшим конфузом.
Вернёмся в ReClass к ModifierManager. Видите этот второй CUtlVector? Это CUtlVector CUtlVector'ов, так-то! Также на 0x40 у него есть подозрительно массивоподобная структура, и логика этого всего не совсем очевидна
Массив на 0x40 имеет в себе 256 элементов типа uint16_t — это таблица, индекс в которой это ID модифаер-функции, а значение — индекс вектора в вышеупомянутом векторе векторов. ID модифаер-функции при этом является членом
В векторе по индексу находятся все модификаторы, имеющие указанную функцию(например, если получить индекс по MODIFIER_PROPERTY_LIFESTEAL_AMPLIFY_PERCENTAGE, то вектор будет содержать все модифаеры, увеличивающие лайфстил, например, Sange и его производные).
Итак, если сжать это объяснение в код, то выйдет следующее:
Теперь нужно вытащить значение бонуса. Снова вернитесь в ReClass и исследуйте структуру ModifierFunctionListNode. То, что я обозначил как
Как видим, это не иначе как геттер этого значения(например у ноды MODIFIER_PROPERTY_HEALTH_BONUS у модифаера тарраски на оффсете будет 250). Однако он записывает в структур на rdx единицу, дважды подряд копирует r9 и в конце помещает на 0х4 результат. Также оно записывает в [r8 + 90] единицу. Да уж, и зачем так сделали?
Подключите x64dbg и поставьте брейкпоинт на эту функцию, благо вызывается она каждый тик. Структура в RDX имеет размер 0x30, вторая в R8 — 0x100, смысл того, что в них записывается не важен.
В итоге вызов геттера пишется так:
И уже теперь можно вгетать это значение. Вот, например, сумма значений всех модификаторов с указанной функцией:
Спасибо Wolf49406, Liberalist и og за предоставление информации по теме гайда. Опять же, если есть какие-то вопросы — задавайте, заодно и гайд дополним.
Итак, начнём. Основа любой сущности — класс, у модификаторов это
CDOTA_Buff
(по префиксу C можно понять, что это server.dll). В дампе класса из Schema содержится очень много самих по себе полезных нетваров: имя модификатора, время истечения, способность и её использовавший, а также, собственно, на кого модификатор наложен. Ребилд класса выглядит примерно так:
CDOTA_Buff class:
// VClass за авторством Liberalist, можете взять из какого-то из его исходников
// либо сразу из моего :)
#define GETTER(type, name, offset) type name() { return Member<type>(offset); }
// server.dll
// CDOTA_Buff
class CDOTAModifier : public VClass {
public:
GETTER(const char*, GetName, Netvars::CDOTA_Buff::m_name);
GETTER(float, GetDuration, Netvars::CDOTA_Buff::m_flDuration);
GETTER(float, GetDieTime, Netvars::CDOTA_Buff::m_flDieTime);
// Эти два можете тоже переделать, чтобы возвращали энтити
// Лично я до сих пор их нигде не применял, вот и лежат в оригинальном виде
GETTER(ENT_HANDLE, GetCaster, Netvars::CDOTA_Buff::m_hCaster);
GETTER(ENT_HANDLE, GetAbility, Netvars::CDOTA_Buff::m_hAbility);
CDOTABaseNPC* GetOwner() {
return Interfaces::EntitySystem->GetEntity<CDOTABaseNPC>(
H2IDX(
Member<ENT_HANDLE>(Netvars::CDOTA_Buff::m_hParent)
)
);
};
};
m_ModifierManager
, но не спешите смотреть в дамп его класса, там ничего интересного. Заходим в ReClass на нужный оффсет у какого-нибудь героя и видим следующую картину:У этого ModifierManager на оффсетах 0x10 и 0x30 есть очевидные
CUtlVector
’ы(троица размер-массив-вместимость). Нас интересует первый: в нём лежат указатели на модификаторы героя. Т. е. структура классов будет выглядеть так:
C++:
class CDOTAModifierManager : public VClass {
public:
// Returns the original CUtlVector that stores the list
auto GetModifierListRaw() {
return MemberInline<CUtlVector<CDOTAModifier*>>(0x10);
}
auto GetModifierList() {
// CUtlVector
auto vecModifiers = MemberInline<CUtlVector<CDOTAModifier*>>(0x10);
return vecModifiers->AsStdVector(); // этот метод я для удобства вписал в CUtlVector
}
};
class CDOTABaseNPC : public CBaseEntity {
public:
CDOTAModifierManager* GetModifierManager() {
// Inlined into the object instead of a pointer
return MemberInline<CDOTAModifierManager>(Netvars::C_DOTA_BaseNPC::m_ModifierManager);
}
}
Для реализации нам нужны какие-нибудь callback’и на создание и удаление модификаторов, и они у нас есть. Это деструктор CDOTA_Buff и виртуальная функция
OnCreated
(её нам предоставил Wolf49406).Деструктор вызывается первым call'ом во второй виртуальной функции CDOTA_Buff, IDA-плагин SigMaker подскажет вам очень короткую сигнатуру.
OnCreated
имеет индекс 39, выше функции с хрефами "modifier_affects_abilities", "dota_portrait_unit_modifiers_changed":Витейбл класса как раз кстати лежит внутри деструктора, следовательно механизм хуков будет такой:
C++:
#define HOOKFUNC(func) HookFunc(func, &hk##func, &o##func, #func)
typedef void(*OnRemoveModifierFn)(CDOTAModifier*);
typedef void(*OnAddModifierFn)(CDOTAModifier*, int);
inline Signatures::OnRemoveModifierFn oOnRemoveModifier{};
inline Signatures::OnAddModifierFn oOnAddModifier{};
/// Где-то в функции инициализации хуков...
SigScanContext ssctx{ ctx.CurProcHandle, ctx.CurProcId };
auto OnRemoveModifier = ssctx.Scan("4C 8B DC 56 41 57", L"client.dll");
uintptr_t** vtable = OnRemoveModifier.Offset(0xd).GetAbsoluteAddress(3, 7);
uintptr_t* OnAddModifier = vtable[39];
HOOKFUNC(OnAddModifier);
HOOKFUNC(OnRemoveModifier);
///
void Hooks::hkOnAddModifier(CDOTAModifier* modifier, int unk) {
oOnAddModifier(modifier, unk);
}
void Hooks::hkOnRemoveModifier(CDOTAModifier* modifier) {
oOnRemoveModifier(modifier);
}
Пожалуйста, авторизуйтесь для просмотра ссылки.
(и
Пожалуйста, авторизуйтесь для просмотра ссылки.
тоже, да и вообще вся вкладка Scripting очень полезная). Сделайте себе какой-нибудь ассоциативный массив названий предметов и их модификаторов, чтобы не путаться. Если нужен пример, можете посмотреть
Пожалуйста, авторизуйтесь для просмотра ссылки.
, сюда вставлять не буду, выйдет слишком длинно(т. к. механизм комплексный и к тому же зависит от архитектуры вашего чита).Также у некоторых героев во время подготовки каста появляется модификатор, с помощью которого можно отслеживать этот каст(иногда через m_bInAbilityPhase этого не сделаешь). Например, у белки при зарядке ульты появляется
modifier_hoodwink_sharpshooter_windup
:Modifier state
Однако суть именно проверки состояния героя осталась нераскрытой. Не бойтесь, вас не обманули!
Герой может быть в разных состояниях: БКБ, инвиз, стан, сон и т. п. Чтобы не перечислять модификаторы вручную, VALVe добавили C_DOTA_BaseNPC нетвар-флаг
m_nUnitState64
, который и содержит информацию о состояниях героя(не путать с m_nUnitDebuffState
, который отслеживает только дебаффы).Для работы нам понадобится
enum ModifierState
, возьмите его из дампа Схемы(также он лежит в паблике, на том же
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)). Беря во внимание принцип работы флагов(переключение одиночных байтов), понимаем, что числа в энуме — степени двойки. Следовательно проверка на какой-либо стейт выглядит так:
C++:
bool CDOTABaseNPC::HasState(ModifierState state) {
auto unitState = Member<int64_t>(Netvars::C_DOTA_BaseNPC::m_nUnitState64);
return unitState & (1 << (int)state)); // не забываем про битовую арифметику
}
Modifier function
Модификаторы могуть менять разные вещи: статы, мувспид, и даже лайфстил от способностей. Хотелось бы в таком случае получить тип и величину бонуса, и тут Valve, как говорится, got it covered.
Вернёмся в ReClass к ModifierManager. Видите этот второй CUtlVector? Это CUtlVector CUtlVector'ов, так-то! Также на 0x40 у него есть подозрительно массивоподобная структура, и логика этого всего не совсем очевидна
Массив на 0x40 имеет в себе 256 элементов типа uint16_t — это таблица, индекс в которой это ID модифаер-функции, а значение — индекс вектора в вышеупомянутом векторе векторов. ID модифаер-функции при этом является членом
enum modifierfunction
из Schema.В векторе по индексу находятся все модификаторы, имеющие указанную функцию(например, если получить индекс по MODIFIER_PROPERTY_LIFESTEAL_AMPLIFY_PERCENTAGE, то вектор будет содержать все модифаеры, увеличивающие лайфстил, например, Sange и его производные).
Итак, если сжать это объяснение в код, то выйдет следующее:
C++:
// enum ModifierFunction, как я и сказал, возьмёте со Схемы или из паблика
struct ModifierFunctionListNode {
CDOTAModifier* modifier;
void* func;
uintptr_t unk;
};
class CDOTAModifierManager : public VClass {
public:
std::span<uint16_t, 256> GetModifierFunctionMatrix() {
return std::span<uint16_t, 256>(MemberInline<uint16_t>(0x40), 256);
}
auto GetModifierFunctionList() {
return Member<CUtlVector<CUtlVector<ModifierFunctionListNode>>>(0x28);
}
CUtlVector<ModifierFunctionListNode>* GetBuffsByModifierFunction(ModifierFunction id) {
if (const uint16_t idx = GetModifierFunctionMatrix()[id]; idx != MODIFIER_FUNCTION_INVALID) {
auto list = GetModifierFunctionList();
if (idx < list.m_Size)
return &list.at(idx);
}
return nullptr;
}
};
void* func
хранит в себе функцию подобного вида:
code_language.asm6502:
movzx eax,byte ptr ds:[r8+90]
xor r9d,r9d
mov dword ptr ds:[rdx],1
test al,al
mov qword ptr ds:[rdx+8],r9
mov rax,rdx
mov qword ptr ds:[rdx+10],r9
je client.7FFE106D3365
mov dword ptr ds:[rdx+4],r9d
ret
mov byte ptr ds:[r8+90],1
movd xmm0,dword ptr ds:[rcx+113C] < reads the offset in CDOTA_Buff, contains an int
cvtdq2ps xmm0,xmm0 < converts it to float and returns it
movss dword ptr ds:[rdx+4],xmm0
ret
Подключите x64dbg и поставьте брейкпоинт на эту функцию, благо вызывается она каждый тик. Структура в RDX имеет размер 0x30, вторая в R8 — 0x100, смысл того, что в них записывается не важен.
В итоге вызов геттера пишется так:
C++:
struct ModifierFunctionListNode {
struct ReturnBuffer1 {
int successful;
float result;
private:
void* unk1;
void* unk2;
};
struct ReturnBuffer2 {
void* unk[0x100 / 8];
};
CDOTAModifier* modifier;
using GetModifierPropertyValue = float(*)(CDOTAModifier*, ReturnBuffer1*, ReturnBuffer2*, void*);
GetModifierPropertyValue func;
float GetPropertyValue() {
ReturnBuffer1 buf1{};
ReturnBuffer2 buf2{};
return func(modifier, &buf1, &buf2, nullptr);
}
private:
uintptr_t unk;
};
C++:
float GetModifierPropertySum(ModifierFunction id) {
float sum = 0;
auto vec = GetBuffsByModifierFunction(id);
if (!vec)
return sum;
for (auto& node : *vec)
sum += node.GetPropertyValue();
return sum;
}
Последнее редактирование: