Гайд [External] Создание нетвар менеджера

Участник
Статус
Оффлайн
Регистрация
15 Янв 2021
Сообщения
499
Реакции[?]
310
Поинты[?]
100K
Доброго времени суток, господа югеймовцы. Так как раздел в РУ сегменте достаточно пустует решил разбавить его постом о неплохом варианте получения и создвания нетвар менеджера для CS 2. Да и тем более новая кс -- новый уровень пастерства.

Стоит начать с того, как это в основном делается в интернале. ( Данный код для своей базы брал
Пожалуйста, авторизуйтесь для просмотра ссылки.
)
C++:
auto pModuleScope = g_pInterfaces->m_pSchemaSystem->FindTypeScopeForModule("client.dll");

if (!pModuleScope)
    return;

const auto pElements = std::make_unique_for_overwrite< UtlTSHashHandle_t[] >(pModuleScope->GetBindingsTable().Count());
const auto nElements = pModuleScope->GetBindingsTable().GetElements(0, pModuleScope->GetBindingsTable().Count(), pElements.get());

if (!nElements)
    return;

for (int nElementIndex = 0; nElementIndex < nElements; nElementIndex++)
{
    const UtlTSHashHandle_t hElement = pElements[nElementIndex];

    if (!hElement)
        continue;

    SDK::CSchemaClassBindingBase* const pClassBinding = pModuleScope->GetBindingsTable().Element(hElement);

    if (!pClassBinding)
        continue;

    if (!pClassBinding->GetNumFields())
        continue;

    std::unordered_map< std::size_t, std::size_t > umapFields;

    for (int nFieldIndex = 0; nFieldIndex < pClassBinding->GetNumFields(); nFieldIndex++)
    {
        SDK::CSchemaField* const pSchemaField = &pClassBinding->GetFields()[nFieldIndex];

        if (!pSchemaField)
            continue;

        umapFields.try_emplace(FNV1A::Hash(pSchemaField->m_pszFieldName), pSchemaField->m_nOffset);
    }

    m_umapSchemas.try_emplace(FNV1A::Hash(pClassBinding->GetName()), umapFields);
}
* Если брать pModuleScope для каждого модуля, то мы будем получать один тот же список постоянно, потому я понял, что в этом нет смысла. Если я что-то упустил, поправьте.

В общем-то, всё невероятно просто, практически как в движке 1-го сурса, лишь с маленькими изменениями и упрощениями.
Как же дела обстоят с созданием такой же системы но для External софта ? Глянем в иду. FindTypeScopeForModule имеет 13 вирутальный индекс в интерфейсе CSchemaSystem.
C++:
__int64 __fastcall FindTypeScopeForModule(_QWORD *this_ptr, const char *pszModuleName)
{
  unsigned __int16 v4; // [rsp+30h] [rbp+8h] BYREF
  // Как я понял, здесь находим индекс нашего названия в таблице.
  CUtlSymbolTable::Find(this_ptr + 53, &v4, pszModuleName);
  // Если не найден, то вызываем некотурую функцию по 11 вирт. индексу
  if ( v4 == 0xFFFF )
    return (*(__int64 (__fastcall **)(_QWORD *))(*this_ptr + 88i64))(this_ptr);
  // Или же возвращаем как раз таки наш ScopeModule
  else
    return *(_QWORD *)(this_ptr[51] + 8i64 * v4);
}

Что же нам это даёт ? Что всё это сложно и хочется пойти лучше погамать в игрушки. Что наш pScopeModule лежит в массиве массивов по индексу, что мы гетнули из функции CUtlSymbolTable::Find.
Для большей наглядности к последующему коду, я бы хотел приложить вырезку из асм кода, с которым понимание будет полегче. Это label, которые вызывается если наш индекс не равен 0xFFFF, то есть найден.
Код:
    loc_18000E246:
         movzx   ecx, ax ; Сохраняем наш индекс в регистр rcx
         mov     rax, [rbx+198h] ; RBX - указатель на наш интерфейс, прибавляем 0x198 ( 51 * 8, где 8 размер типа int ).
         mov     rax, [rax+rcx*8]  ; Из полученного элемента массива получаем ещё один, где индекс это наш индекс модуля из списка.
         add     rsp, 20h ; Выравниваем стек и выходим из функции
         pop     rbx
         retn

Итак, с теорией практически покончено. Обращаясь ещё раз к нашей исследуемой функции замечу, что этот самый индекс модуля, мы не знаем, но благо я продебажил данную функцию за вас и выяснил, что индекс равняется 15 ( 0xF ). Так что примем это к сведению и начнём писать код.
C++:
// Получение базового адреса нашего интерфейса
auto ptrToBaseInterface = g_pMemorySystem->GetModule(HASH("schemasystem.dll")) + schema_offset;

// Получаем наш массив с pScopeModule для каждого модуля
auto ptrToListElement = g_pMemorySystem->RPM<std::uintptr_t>(ptrToBaseInterface + 0x198);

// Получаем уже финальный pScopeModule благодаря той логике что изложил выше ( я кастую именно в поинтер, чтобы облегчить RPM и использоваться указатель this )
auto ptrToScope = g_pMemorySystem->RPM<CSchemaSystemTypeScope*>(ptrToListElement + (0xF * 8));

// Ну и собсна сама таблица
auto pTable = ptrToScope->GetBindingsTable();
auto pElements = std::make_unique_for_overwrite< UtlTSHashHandle_t[] >(pTable.Count());
const auto nElements = pTable.GetElements(0, pTable.Count(), pElements.get());

С получением элементов не всё так просто, а именно с функцией GetElements, но я это сделал и особо зацикливаться не хочу. Исходники будут, не переживайте :)
Далее идёт уже достаточно простая практика получения самих таблиц с нетварами. Единственное отличие от интернала, что мы будем юзать RPM для таких целей.
C++:
// Список содержащий название таблицы и к нему список нетваров
std::unordered_map< std::uint64_t, std::unordered_map< std::uint64_t, std::uint32_t >  > umapTableOffsets;

for (int nElementIndex = 0; nElementIndex < nElements; nElementIndex++)
{
    const UtlTSHashHandle_t hElement = pElements[nElementIndex];

    if (!hElement)
        continue;

    CSchemaClassBindingBase* const pClassBinding = pTable.Element(hElement);

    if (!pClassBinding)
        continue;
    
    if (!pClassBinding->GetNumFields())
        continue;

    printf("%s ( %i ) :\n", pClassBinding->GetName().c_str(), pClassBinding->GetNumFields());

    CSchemaField* pSchemaField = &pClassBinding->GetFields()[0];
    
    std::unordered_map< std::uint64_t, std::uint32_t > umapOffsets;
    for (int nFieldIndex = 0; nFieldIndex < pClassBinding->GetNumFields(); nFieldIndex++)
    {
        if (!pSchemaField)
            continue;

        auto name = pSchemaField->GetName();

        umapOffsets.try_emplace(HASH(pSchemaField->GetName()), pSchemaField->GetOffset());

        printf("\t%s = 0x%p\n", pSchemaField->GetName().c_str(), pSchemaField->GetOffset());
        
        // Когда я изначально делал, вполне работал вариант с 21 строчки, но сегодня всё по какой-то причине сломалось xD
        // Так что просто прибавляю к настоящему адресу 0x20. Это будет адрес след. элемента в списке.
        pSchemaField = reinterpret_cast<CSchemaField*>(std::uintptr_t(pSchemaField) + 0x20);
    }

    umapTableOffsets.try_emplace(HASH(pClassBinding->GetName()), umapOffsets);
}

Ну и пример финальный пример использования например так:
C++:
auto uiHealthOffset = umapTableOffsets[HASH("C_BaseEntity")][HASH("m_iHealth")];

printf("C_BaseEntity->m_iHealth offset is 0x%p\n", uiHealthOffset);

Ну и самое вкуснямбовое сами сурсы:
Пожалуйста, авторизуйтесь для просмотра ссылки.

Не самые красивые реализации, хоть я и чуть подправил всё же. Для ознакомления пойдёт.
 
Сверху Снизу