Вопрос Получение сущности локального игрока по индексу

Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2021
Сообщения
13
Реакции[?]
3
Поинты[?]
0
Привет!

Не могу получить указатель на сущность локального игрока из списка. Делаю следующим образом (тестирую в игре с ботами, OS Windows x64):

1) Посредством вызова EngineClient::GetLocalPlayer() получаю индекс сущности. Всегда возвращает индекс 0.

2) Пробую получить по этому индексу указатель на сущность используя алгоритм из
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Код функции:
C++:
    CBaseEntity* GetBaseEntity(int index)
    {
        if (index <= -1 || index >= MAX_TOTAL_ENTITIES)
            return nullptr;

        int listToUse = (index / MAX_ENTITIES_IN_LIST);
        if (!m_pEntityList[listToUse]) {
            printf("Tried to Use an EntityList does that not exist yet! List #%d", listToUse);
            return nullptr;
        }
        if (m_pEntityList[listToUse]->m_pIdentities[index % MAX_ENTITIES_IN_LIST].entity) {
            return m_pEntityList[listToUse]->m_pIdentities[index % MAX_ENTITIES_IN_LIST].entity;
        }
        else {
            return nullptr;
        }
    }
3) CEntityIdentity найти удается но CEntityIdentity::pEntity у неё равно NULL, следовательно и функция приведенная выше возвращает NULL.

Если посмотрим в отладчик на GameEntitySystem видим следующее:
SC_1.png
Указатели на сущности начинаются со смещения 0x18. По алгоритму: 0 (LocalPlayerIndex) / MAX_ENTITIES_IN_LIST = 0, т.е. берем первый указатель в массиве. Смотрим, что там находится:
SC_2.png
Очевидно что это CEntityIdentity, т.к. по смещению 0x14 у нас -1 и по смещения 0x50 и 0x58 у на валидные указатели на соседние элементы списка (m_pPrev и m_pNext). По алгоритму: 0 (LocalPlayerIndex) % MAX_ENTITIES_IN_LIST = 0, т.е. берем именно этот элемент. Но как видим на скрине, указатель по смещению 0x0 у нас NULL. Там должен быть указатель на сущность, а его нет. Собственно вот в чем загвоздка.
SC_3.png
Не могу понять почему так. Как мне получить локального игрока по индексу?

Спасибо.
 
Последнее редактирование:
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
(локального игрока кстати в главном меню не существует. и кстати индекс 0 это как правило world entity)
ентити система:
void* VMT;//+0
void* UNK;//+8
EntityList* Lists[64];//+16 or +0x10
Снимок3.PNG
ентити листы:
EntityIdentity identities[512];
Снимок4.PNG
Снимок5.PNG

ентити айдентити найдешь где-нибудь сам, там указатель на сущность, имя(0x20) и т.д.

сначала движок получаешь через фактори(Source2EngineToClient001),
потом 22 функцию из вмт вызываешь, возвращает (да бог знает что она возвращает, это в данном контексте не важно)(пусть будет void), принимает int* - указатель на переменную куда она будет записывать индекс локального игрока(ну или референс), и int - сплит скрин слот(сплитскрин есть в играх типа портал 2. в доте его нет но в движке присутствует)(передавай 0, то есть первый слот сплитскрина)
вызываешь(первым параметром this, то есть сам движок):
((void (*)(void***, int&, int)) * (engine_vmt + 22))(engine, iLP, 0);//__cdecl/__fastcall без разницы на x64 на винде
и у тебя в iLP лежит индекс локального игрока.


находишь энтити систему, например с хрефа Format: ent_find <substring>\n
берешь листы(+0x10), находишь тот который тебе нужен путем целочисленного деления индекса сущности на 512,
считываешь его,
там будет 512 идентити сущностей. умножаешь 0x78(размер ентити айдентити) на остаток деления индекса на 512,
получаешь айдентити нужной сущности.
чекни:(тут хуйня но так чисто в образовательных целях можешь посмотреть. надо все эти говногайды переписать потом когда-нибудь)

proof-of-concept
C++:
#include "pch.h"
#include <thread>
auto __stdcall DllMain(HMODULE,DWORD rsn,LPVOID)
{
    if (rsn == DLL_PROCESS_ATTACH)
    {
        std::thread t([]() {
            __try {//супер пупер жоские поинтер арифметики, захотелось повыебываться, все оффсеты которые я там прибавляю умножай на размер типа данных на который указывает указатель(char = 1, void** = 8)
//RVA юзаю вместо сиги чисто ради теста чтобы получить хреф на ентити систему. юзай сигу.
                auto engine = ((void*** (*)(const char*, int)
                    )GetProcAddress(GetModuleHandleW(L"engine2.dll"), "CreateInterface"))
                    ("Source2EngineToClient001", 0);
                auto engine_vmt = *engine;
                int iLP;
                ((void(*)(void***, int&, int)) * (engine_vmt + 22))(engine, iLP, 0);

                auto xref = (char*)GetModuleHandleW(L"client.dll") + 0x33c7bf;
                auto ent_sys = *(char**)(xref + 7 + *(int*)(xref + 3));

                auto lists = (const char***)(ent_sys + 16);

                auto list = *(lists + (iLP >> 9));
                auto identity = list + 15 * (iLP & 0x1ff);
                ((void(*)(const char*, ...)
                    )GetProcAddress(GetModuleHandleW(L"tier0.dll"), "Msg"))("Ent name: %s\n", *(identity + 4));
            }
            __except (1) {
                __try {
                    ((void(*)(const char*, ...)
                        )GetProcAddress(GetModuleHandleW(L"tier0.dll"), "Msg"))("Failed to get entity name!\n");
                }
                __except (1) {}
            }
            });
        t.detach();
    }
    return 1;
}
в главном меню:(так как игрока не существует в главном меню)

в демке:


и кстати игрок и герой это разные вещи.(пореверси CScriptBindingPR_Players::GetPlayerHeroEntityIndex , либо можешь героя найти по m_hOwnerEntity или как там нетвар так вот он у героя равен CHandle(айди сущности и серийник запакованные в int)(
Пожалуйста, авторизуйтесь для просмотра ссылки.
) игрока)
Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2021
Сообщения
13
Реакции[?]
3
Поинты[?]
0
Liberalist, спасибо огромное! Мне здорово помог твой ответ. Исправил пару косяков и все заработало. А ты можешь подробней рассказать о списках? Я не понимаю почему их несколько. По какому принципу система размещает сущности (а точнее их CEntityIdentity) по этим спискам?
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
512*64 = 32768 ентити айдентити. чтобы столько места впустую не тратить, игра листы по мере надобности создает. один лист как минимум содержит 512 * sizeof EntityIdentity байт, нафига стока места впустую тратить и заранее аллоцировать? как только сущностей становится больше 512, игра создает второй лист. как только более 1024, третий и т.д. чтобы сразу 64 листа не создавать и кучу места впустую не тратить, как только надо - создает новый лист. а заранее смысла нет это делать. в списке все сущности по айди отсортированы - первая вторая третья четвертая(опять же по мере создания им выдаются айдишники. кто первый создался у того и первый айдишник. кто второй у того второй айди и тд). айди сущности и ее место в листе(ну точнее не в листе а во всей системе) это одно и то же. у сущности есть CHANDLE - это микс айдишника и сериала. (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)
C++:
class CHANDLE {
public:
    int handle;
    short Index() {
        return handle & 0x7fff;//битовая маска это MAX_ENTITIES - 1. в доте макс сущностей 512*64. то есть 32768 - 1 = 32767 = 0x7fff
    }
};
class CENTITYIDENTITY {//не обновлял это старая структура мб пара оффсетов поменялась хз
public:
    Entity* entity;
    AnyPointer baseinfo;
    CHANDLE handle;
private:
    int unk;
public:
    cc name;
    cc designer_name;
    char pad[8];
    char flags[4];
    short Index() {
        return handle.Index();
    }
    cc Name() {
        return name;
    }
    cc Designername() {
        return designer_name;
    }
};
 
Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2021
Сообщения
13
Реакции[?]
3
Поинты[?]
0
Ясно, спасибо! А вот касательно полей CEntityIdentity: m_pPrev и m_pNext. Тут уже ведь будет какая-то группировка? Т.е. если я получу CEntityIdentity локального игрока и пройдусь влево (m_pPrev) и вправо (m_pNext) я ведь не получу ВСЕХ сущностей в игре. Верно?
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
Ясно, спасибо! А вот касательно полей CEntityIdentity: m_pPrev и m_pNext. Тут уже ведь будет какая-то группировка? Т.е. если я получу CEntityIdentity локального игрока и пройдусь влево (m_pPrev) и вправо (m_pNext) я ведь не получу ВСЕХ сущностей в игре. Верно?
нет не получишь. юзай qword:[EntSystem + 0x210](это самая "первая" сущность для некста) и по ней уже некст проходись. а лучше юзай итерацию по листам(по индексам от 0 до dword:[EntSys+0x2080]) а не через некст. через некст медленнее я проверял. есть еще итерация через хуки OnAddEntity и OnRemoveEntity(чек dylib)
 
Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2021
Сообщения
13
Реакции[?]
3
Поинты[?]
0
А вот ещё встречный вопрос. Какие же это будут сущности если я все же пройдусь по m_pPrev и m_pNext от CEntityIdentity локального игрока? Другими словами, по какому признаку происходит эта связка?
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
А вот ещё встречный вопрос. Какие же это будут сущности если я все же пройдусь по m_pPrev и m_pNext от CEntityIdentity локального игрока? Другими словами, по какому признаку происходит эта связка?
бог знает, наверно по мере создания. никогда не задавался этим вопросом. есть же указатель на "первую" сущность - вот с нее и начинай через некст итерировать.
 
Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2021
Сообщения
13
Реакции[?]
3
Поинты[?]
0
нет не получишь. юзай qword:[EntSystem + 0x210](это самая "первая" сущность для некста) и по ней уже некст проходись
Потестил. На самом деле получу. Если уйти влево до упора (m_pPrev == NULL) от локального игрока, то как раз и окажемся на "первой" сущности (EntSystem + 0x210). Т.е. можно без лишнего оффсета обойтись. Еще заметил что при итерации по индексу сущностей получается меньше. Например нет деревьев.
и кстати игрок и герой это разные вещи.(пореверси CScriptBindingPR_Players::GetPlayerHeroEntityIndex , либо можешь героя найти по m_hOwnerEntity или как там нетвар так вот он у героя равен CHandle(айди сущности и серийник запакованные в int)(
Пожалуйста, авторизуйтесь для просмотра ссылки.
) игрока)
Как я понимаю нужно при итерации сущностей проверять их m_hOwnerEntity. Если LocalPlayerIndex == (m_hOwnerEntity & 0x7FFF), то нашли. Я так попробовал и получил npc_dota_courier хотя у меня в игре (на момент теста) был выбран герой. Подскажи пожалуйста, что я делаю не так?
 
Последнее редактирование:
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
Потестил. На самом деле получу. Если уйти влево до упора (m_pPrev == NULL) от локального игрока, то как раз и окажемся на "первой" сущности (EntSystem + 0x210). Т.е. можно без лишнего оффсета обойтись. Еще заметил что при итерации по индексу сущностей получается меньше. Например нет деревьев.

Как я понимаю нужно при итерации сущностей проверять их m_hOwnerEntity. Если LocalPlayerIndex == (m_hOwnerEntity & 0x7FFF), то нашли. Я так попробовал и получил npc_dota_courier хотя у меня в игре (на момент теста) был выбран герой. Подскажи пожалуйста, что я делаю не так?
щас бы уходить влево до упора пробегая через миллион сущностей вместо того чтобы считать 8 байтиков. бесполезная нагрузка компуктера. не делай так.

лучше чекай CHANDLE сразу целиком а не айдишник. m_hOwnerEntity == player_chandle(хендл статичный, живет пока сущность не удалится, поэтому во время матча он не будет меняться, можно просто в переменную сунуть чтобы не считывать каждый раз)(опять же чтобы не &0x7fff каждый m_hOwnerEntity, бесполезная нагрузка). у героя много сущностей не все герои - курьер же тоже твой. и все твои крипы прирученные доминатором и тд тоже все твои. чекай класс сущности на предмет того герой это или нет.
тут два варианта - один простой другой сложный.
простой(в дебагере там потыкай на ячейки разберешься):
C++:
const char* getBaseClass(ui ent) {
    if (
        *(ui*)(ent + 0x10) == NULL//identity
        ||
        *(ui*)(*(ui*)(ent + 0x10) + 0x8) == NULL//base class
        ) return 0;
    return ***(const char****)(//еще 2 раза считываешь с бейз класса и получаешь строку
        *(ui*)(ent + 0x10) + 0x8
        );
}
bool OfBaseClass(ui ent, cc _class) {
    return !strcmp(getBaseClass(ent), _class);
}
bool IsHero(ui ent) {
    return OfBaseClass(ent, "C_DOTA_BaseNPC_Hero");
}
второй:
C++:
class CENTITYIDENTITY {
public:
    Entity* entity;
    AnyPointer baseinfo;
    CHANDLE handle;
private:
    int unk;
public:
    ...
    bool IsHero() {
        return ClassInfo() == HEROClassInfo;
    }
    u64 ClassInfo() {
        return *baseinfo;
    }
};
...
class entitySystem : public VirtualClass {
public:
    static Function GetClassInfoByName;
    AnyPointer GetInfoForClass(cc classname) {
        return GetClassInfoByName(this, classname, 0ull);
    }
};
...
    entitySystem::GetClassInfoByName = FindPattern("client.dll",
        "ну тут очень старая сига кароче",
        "getclassinfobyname not found");
...
HEROClassInfo = *EntitySystem->GetInfoForClass("C_DOTA_BaseNPC_Hero");
чтобы найти эту функцию ищешь хреф "Attempted to create unknown entity classname", чуть выше отматываешь и вот твоя функция.
Снимок2.PNG
Снимок.PNG
C++:
#include "pch.h"
#include <thread>
auto __stdcall DllMain(HMODULE, DWORD rsn, LPVOID)
{
    if (rsn == DLL_PROCESS_ATTACH)
    {
        std::thread t([]() {
            __try {
                auto engine = ((void*** (*)(const char*, int)
                    )GetProcAddress(GetModuleHandleW(L"engine2.dll"), "CreateInterface"))
                    ("Source2EngineToClient001", 0);
                auto engine_vmt = *engine;
                int iLP;
                ((void(*)(void***, int&, int)) * (engine_vmt + 22))(engine, iLP, 0);

                auto xref = (char*)GetModuleHandleW(L"client.dll") + 0x33c7bf;
                auto ent_sys = *(char**)(xref + 7 + *(int*)(xref + 3));

                auto lists = (const char****)(ent_sys + 16);

                auto list = *(lists + (iLP >> 9));
                auto identity = list + 15 * (iLP & 0x1ff);

                auto cmsg = ((void(*)(const char*, ...)
                    )GetProcAddress(GetModuleHandleW(L"tier0.dll"), "Msg"));

                cmsg("Ent name: %s\n", *(identity + 4));

                auto cgibn = (void** (*)(char*, const char*, int))
                    ((char*)GetModuleHandleW(L"client.dll") + 0x1c784c0);
                auto hero_base = *cgibn(ent_sys, "C_DOTA_BaseNPC_Hero", 0);
                cmsg("Hero baseclass: 0x%p\n", hero_base);
               
                auto highest = *(int*)(ent_sys + 0x2080);
                cmsg("Highest ent index: %d\n",highest);
                auto last_rem = highest & 0x1ff;
                auto last_list = highest >> 9;
                for (auto iList = 0; iList < last_list; iList++) {
                    auto pList = *(lists + iList);
                    cmsg("List: %d\n", iList);
                    for (auto iEnt = 0; iEnt < 512; iEnt++) {
                        auto entIdent = pList + 15 * iEnt;
                        auto cBase_1 = *(entIdent + 1);
                        if (cBase_1) {
                            auto cBase_2 = *cBase_1;
                            if (cBase_2 == hero_base)
                                cmsg("A hero here: 0x%p named %s\n", entIdent, *(entIdent + 4));
                        }
                    }
                }
                auto pLastList = *(lists + last_list);
                cmsg("List: last: %d\n", last_list);
                for (auto iEnt = 0; iEnt < last_rem; iEnt++) {
                    auto entIdent = pLastList + 15 * iEnt;
                    auto cBase_1 = *(entIdent + 1);
                    if (cBase_1) {
                        auto cBase_2 = *cBase_1;
                        if (cBase_2 == hero_base)
                            cmsg("A hero here: 0x%p named %s\n", entIdent, *(entIdent + 4));
                    }
                }
            }
            __except (1) {
                __try {
                    ((void(*)(const char*, ...)
                        )GetProcAddress(GetModuleHandleW(L"tier0.dll"), "Msg"))("Failed to get entity name!\n");
                }
                __except (1) {}
            }
            });
        t.detach();
    }
    return 1;
}
выдает в демке(наспавнил антимагов xD и еще кучу дерьма)

Ent name: player
Hero baseclass: 0x000001E5DA373980
Highest ent index: 2388
List: 0
A hero here: 0x000001E5FEE15118 named npc_dota_hero_phantom_assassin
A hero here: 0x000001E5FEE18A48 named npc_dota_hero_antimage
A hero here: 0x000001E5FEE19D08 named npc_dota_hero_antimage
A hero here: 0x000001E5FEE1B928 named npc_dota_hero_antimage
A hero here: 0x000001E5FEE1D6B0 named npc_dota_hero_antimage
List: 1
List: 2
List: 3
List: last: 4
 
Начинающий
Статус
Оффлайн
Регистрация
21 Июн 2021
Сообщения
13
Реакции[?]
3
Поинты[?]
0
щас бы уходить влево до упора пробегая через миллион сущностей вместо того чтобы считать 8 байтиков. бесполезная нагрузка компуктера. не делай так.
Но ведь можно сначала влево сходить (запомнив указатель откуда начали) а потом направо. Вхолостую ходить по списку не нужно. И обернуть это дело в какую-нибуть обертку. Типа FindFirst() FindNext(). Вообще я уже на итерации по индексу остановился. Разбираюсь из интереса.

------------------------------------------------------

За остальное спасибо, буду разбиратся. Но вроде проще по C_DOTA_BaseNPC::m_iUnitType героя определять. Я по этому полю крипов нахожу.
 
Участник
Статус
Оффлайн
Регистрация
23 Май 2019
Сообщения
779
Реакции[?]
331
Поинты[?]
63K
Но ведь можно сначала влево сходить (запомнив указатель откуда начали) а потом направо. Вхолостую ходить по списку не нужно. И обернуть это дело в какую-нибуть обертку. Типа FindFirst() FindNext(). Вообще я уже на итерации по индексу остановился. Разбираюсь из интереса.

------------------------------------------------------

За остальное спасибо, буду разбиратся. Но вроде проще по C_DOTA_BaseNPC::m_iUnitType героя определять. Я по этому полю крипов нахожу.
ну можешь как угодно находить, если работает - юзай. просто тебе привел примеры. лучше вообще массивом итерировать(реально +фпс по сравнению с некст я сам проверял). а еще лучше OnAdd/OnRemoveEntity
 
Сверху Снизу