Гайд Продолжение. Всё о модификаторах

Ревёрсер среднего звена
Пользователь
Статус
Оффлайн
Регистрация
24 Ноя 2022
Сообщения
303
Реакции[?]
108
Поинты[?]
57K
В Доте есть баффы, дебаффы, ауры, пассивки и ещё много чего. Всё это в той или иной степени изменяет состояние героя, можно сказать, модифицирует его. Всё вышеперечисленное — модификаторы, и сегодня мы научимся с их помощью получать нужную информацию.

Итак, начнём. Основа любой сущности — класс, у модификаторов это 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)
        )
        );
    };
};
Хорошо, класс есть, теперь нужно получить его объекты. У C_DOTA_BaseNPC есть нетвар m_ModifierManager, но не спешите смотреть в дамп его класса, там ничего интересного. Заходим в ReClass на нужный оффсет у какого-нибудь героя и видим следующую картину:

Screenshot_540.png

У этого 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":

Screenshot_544.png

Витейбл класса как раз кстати лежит внутри деструктора, следовательно механизм хуков будет такой:
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);
}
И уже в OnAdd/OnRemove вы можете сохранить/удалить предмет где-нибудь в своей структуре, если он вам нужен. Соблюдайте осторожность: несмотря на то, что у большинства предметных модификаторов имя это просто “modifier_” + itemName, у той же манты, например, это не так(item_manta - modifier_item_manta_style). Актуальный список названий всех модификаторов в игре есть
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
тоже, да и вообще вся вкладка Scripting очень полезная). Сделайте себе какой-нибудь ассоциативный массив названий предметов и их модификаторов, чтобы не путаться. Если нужен пример, можете посмотреть
Пожалуйста, авторизуйтесь для просмотра ссылки.
, сюда вставлять не буду, выйдет слишком длинно(т. к. механизм комплексный и к тому же зависит от архитектуры вашего чита).
Также у некоторых героев во время подготовки каста появляется модификатор, с помощью которого можно отслеживать этот каст(иногда через m_bInAbilityPhase этого не сделаешь). Например, у белки при зарядке ульты появляется modifier_hoodwink_sharpshooter_windup:

belka.png

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)); // не забываем про битовую арифметику
}
Например, у героя под оцепенением флаг будет 2⁰ = 0x1, что соответствует MODIFIER_STATE_ROOTED. Вещь крайне полезная, ибо нажимать какой-нибудь ульт зевса в героя под БКБ было бы пренеприятнейшим конфузом.

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;
    }
};
Теперь нужно вытащить значение бонуса. Снова вернитесь в ReClass и исследуйте структуру ModifierFunctionListNode. То, что я обозначил как 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
Как видим, это не иначе как геттер этого значения(например у ноды MODIFIER_PROPERTY_HEALTH_BONUS у модифаера тарраски на оффсете будет 250). Однако он записывает в структур на rdx единицу, дважды подряд копирует r9 и в конце помещает на 0х4 результат. Также оно записывает в [r8 + 90] единицу. Да уж, и зачем так сделали?
Подключите 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;
}
Спасибо Wolf49406, Liberalist и og за предоставление информации по теме гайда. Опять же, если есть какие-то вопросы — задавайте, заодно и гайд дополним.
 
Последнее редактирование:
Участник
Статус
Онлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
функа на 39 индексе у всех вмтх(которые наследуют от сдота_бафф) одинаковая(то есть ее никто не оверрайдит)(перепроверь сам в иде(возьми CDOTA_Buff и какой-нибудь конкретный бафф и сравни что там одинаковые функции лежат на индексе), у меня старая дота), поэтому можешь просто взять ее из вмт у CDOTA_Buff, хреф на которую есть в деструкторе/конструкторе
+ в доте есть модифаерфункции(всякие бонусы к статам и тд) они тоже в модифаерменеджере
там есть вектор векторов модифаерфункций
+ там есть сетка на 256 индексов(индекс юинт16) которая нужна для сопоставления айди модифаерфункции и индекса в векторе(0xFFFF значит нету такой модифаерфункции)
C++:
    CUtlVector<ModifierFunction>* GetModifierFunction(modifierfunction id) const noexcept
    {
        if (const auto index = modifierfunction_indices[id];
            index != modifierfunction::MODIFIER_FUNCTION_INVALID)
        {
            if(index < modifierfunctions.size())
                return &modifierfunctions.at(index);
        }
        return nullptr;
    }
 
Последнее редактирование:
Ревёрсер среднего звена
Пользователь
Статус
Оффлайн
Регистрация
24 Ноя 2022
Сообщения
303
Реакции[?]
108
Поинты[?]
57K
функа на 39 индексе у всех вмтх(которые наследуют от сдота_бафф) одинаковая(то есть ее никто не оверрайдит)(перепроверь сам в иде(возьми CDOTA_Buff и какой-нибудь конкретный бафф и сравни что там одинаковые функции лежат на индексе), у меня старая дота), поэтому можешь просто взять ее из вмт у CDOTA_Buff, хреф на которую есть в деструкторе/конструкторе
Реально, не заметил что он там был. Тогда можно хукнуть деструктор и по пути взять из него OnCreated
+ в доте есть модифаерфункции(всякие бонусы к статам и тд) они тоже в модифаерменеджере
там есть вектор векторов модифаерфункций
+ там есть сетка на 256 индексов(индекс юинт16) которая нужна для сопоставления айди модифаерфункции и индекса в векторе(0xFFFF значит нету такой модифаерфункции)
Типа можно получить информацию о том, что модификатор меняет? И где в таком случае брать ID функции?
 
Участник
Статус
Онлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
Реально, не заметил что он там был. Тогда можно хукнуть деструктор и по пути взять из него OnCreated

Типа можно получить информацию о том, что модификатор меняет? И где в таком случае брать ID функции?
server.dll -> enum modifierfunction
модифаерфункции это всякие дополнительные каст рейнджи(их несколько может быть, условно +50 рейнджи потом еще +100 и тд, там вектор) и тд, пореверси
 
Ревёрсер среднего звена
Пользователь
Статус
Оффлайн
Регистрация
24 Ноя 2022
Сообщения
303
Реакции[?]
108
Поинты[?]
57K
модифаерфункции это всякие дополнительные каст рейнджи(их несколько может быть, условно +50 рейнджи потом еще +100 и тд, там вектор) и тд
Так, ну структуру я разобрал
C++:
struct ModifierFunctionListNode {
    CDOTAModifier* modifier;
    void* func;
private:
    uintptr_t unk; // 128
};

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;
}
Я так понимаю точно узнать значение бонуса(+50/+100/+225) нельзя, оно просто группирует модифаеры по функциям
 
🤡
Пользователь
Статус
Оффлайн
Регистрация
28 Апр 2014
Сообщения
127
Реакции[?]
163
Поинты[?]
21K
Так, ну структуру я разобрал
C++:
struct ModifierFunctionListNode {
    CDOTAModifier* modifier;
    void* func;
private:
    uintptr_t unk; // 128
};

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;
}
Я так понимаю точно узнать значение бонуса(+50/+100/+225) нельзя, оно просто группирует модифаеры по функциям
Нет. можно узнать, там есть список функций который можно вызывать, чтобы точно узнать
 
Ревёрсер среднего звена
Пользователь
Статус
Оффлайн
Регистрация
24 Ноя 2022
Сообщения
303
Реакции[?]
108
Поинты[?]
57K
Нет. можно узнать, там есть список функций который можно вызывать, чтобы точно узнать
А, вот зачем эти функции. Весьма запутанную структуру сделали. Тогда сейчас включу это в гайд
 
Сверху Снизу