Добро пожаловать! Игровой форум WLUX.NET - Игры, Читы, Скрипты, Статьи, Софт, Курсы.

Присоединяйтесь к нам сейчас, чтобы получить доступ ко всем нашим функциям. После регистрации и входа в систему вы сможете создавать темы, публиковать ответы на существующие темы, повышать репутацию участников, скачивать вложения и файлы и многое, многое другое. Так чего же вы ждете?

Добро пожаловать гость!

Приветствуем вас на нашем форуме! Мы очень рады вас видеть и с большим удовольствием поделимся всей информацией нашего форума!

Мы уважаем своих пользователей и так же ждем от вас приятного общения.

Система наград, ежедневное пополнения тем!

Общайся, получай награды.

Статьи, гайды, софт

У нас вы можете скачать бесплатно читы для игр. Полезные гайды на любые темы и схемы заработка. Есть раздел халявы!

FAQ по форуму

Не понимаю, как и что тут работает у вас?!Как создавать темы, писать сообщения, как получать реакции. Почему не засчитывает сообщения. Все ответы здесь

  • Добро пожаловать на сайт - wlux.net!

    FAQ по форуму

    1. Все сообщения до группы местный проходят модерацию от 1 минуты до 24 часа

    2. Сообщения учитываються в следующих разделах: Читать

    3.Что-бы скачать вложение нужно 2 сообщения.

    4.Личные переписки работают только с Администрацией форума

    5. Запрещено: Просить скрытый текст , спам, реклама, скам, ддос, кардинг и другая чернуха, нарушать любые законы РФ/СНГ = бан аккаунта

    6. Внимание! Мы не удаляем аккаунты с форума! Будьте внимательны ДО регистрации! Как удалить аккаунт на форуме?!

    5.Не понимаю, как и что тут работает у вас?!Как создавать темы, писать сообщения, как получать реакции. Почему не засчитывает сообщения. Все ответы здесь

Исходник Гайд Меняем деревья (tree changer) в Dota 2 на любые - Скины tree changer для Dota 2

wlux.net Оффлайн

wlux.net

Где волчьи уши, там волчьи зубы.
Команда форума
LV
7
 
20.06.2022
23 845
218
36
Награды
10
Пол
Муж.

Репутация:

  • Автор темы
  • Администратор
  • Модератор
  • Команда форума
  • #1
Меняем деревья.png

Итак, наша задача состоит в том, чтобы поменять всем деревьям модель. Для начала нужно найти список всех деревьев, и это не настолько очевидно. В EntitySystem такого нет, а поинтеры на 0x110 и далее не являются стабильным решением. Поглядим на класс дерева в Схеме:

Код:
C_DOTA_MapTree : C_DOTA_BinaryObject
    bool m_bInitialized 0x808;

Ага, BinaryObject. В доте есть статическая геймсистема CDOTA_BinaryObjectSystem, в которой, ожидаемо, на 0x18 лежит CUtlVector<C_DOTA_MapTree*>. Этот список работает таким же образом, как и entity list, поэтому там будут и nullptrы. Отлично, деревья наши.

Теперь нужно изменить им модель и масштаб. Для первого нам нужен SetModel. Находим по хрефам,
wu6wbck.png
но не спешим брать по сигнатуре, можно и без неё:

C++:
// found by Morphling
setMdl = Address(
    tree->GetVFunc(7).ptr // tree = C_DOTA_MapTree
).Offset(0x1c0).GetAbsoluteAddress(1);

Помимо this этот метод принимает путь к VPK-файлу модели(например "models/props_tree/ti7/ggbranch.vmdl"). Вызывать его необходимо в главном потоке доты, иначе будете ловить регулярные краши. Мой совет: вызывайте проверку на свой bool-флаг в чём-то типа RunFrame/FrameStageNotify, сам флаг сможете ставить где угодно. Сами модельки ищем в VPK-папке models/props_tree/

Иногда также необходимо выставить масштаб, чтобы видимый размер модельки соответствовал реальному(иначе известные всем пеньки будут микроскопическими). Для этого ищем метод CGameSceneNode::SetLocalScale через удобный конвар
uVHzSjq.png
и видим, казалось бы, монструозную функцию:

Код:
if ( a2 != *(float *)(this + 0xC4) )
  {
    isHierarchyTypeZero = *(_BYTE *)(this + 0xEC) == 0;
    v4 = 4;
    *(float *)(this + 0xC4) = a2;
    if ( isHierarchyTypeZero )
    {
       *куча mov'ов*
    }
    ownerIdentity = *(_QWORD *)(*(_QWORD *)(this + 0x30) + 16i64);
    if ( (*(_DWORD *)(ownerIdentity + 48) & 0x800) != 0 )
    {
      v11 = (*(_DWORD *)(ownerIdentity + 48) & 0x40) != 0;
    }
    else
    {
      v12 = 0x7FFF;
      if ( ownerIdentity )
      {
        v13 = *(_DWORD *)(ownerIdentity + 16);
        if ( v13 != -1 )
          v12 = v13 & 0x7FFF;
      }
      v11 = v12 < 0x4000;
    }
    if ( !v11 )
    {
      m_pOwner = *(_QWORD *)(this + 0x30);
      globals = (float *)g_pGlobalVars;
      if ( !*((_BYTE *)g_pGlobalVars + 61) && !*((_BYTE *)g_pGlobalVars + 60) )
      {
        v16 = (void (__fastcall *)(__int64))*((_QWORD *)g_pGlobalVars + 4);
        if ( v16 )
        {
          v16(1i64);
          m_pOwner = *(_QWORD *)(this + 48);
        }
      }
      sub_180235560(m_pOwner, globals[11]);
    }
    (*(void (__fastcall **)(__int64, _QWORD))(*(_QWORD *)this + 80i64))(this, v4);

Но не спешите брать её сигнатуру! На самом деле, единственное, что нам нужно — это первая и последняя строчки. Проанализировав функцию динамически, увидите, что первый и последний if у деревьев не выполняется. К тому же конструкция последнего является некой inline-функцией, сообщающей игре о проблемах с синхронизацией, такую же можете увидеть в GetGameTime. Вот и остаётся условие на изменённый нетвар CGameSceneNode::m_flScale и вызов его виртуальной функции. В итоге код будет примерно такой:

C++:
struct TreeModelInfo {
    const char* modelName;
    float scale;
};

// Вызываем где-то у себя
void Modules::M_TreeChanger::QueueModelUpdate(TreeModelInfo mdlInfo) {
    queuedModel = mdlInfo;
    needsUpdate = true; // тот самый bool-флаг
}

void Modules::M_TreeChanger::SetTreeModel(CBaseEntity* tree, const TreeModelInfo& mdl) {
    static Function setMdl = nullptr;

    if (!setMdl.ptr)
        setMdl = Address(tree->GetVFunc(7).ptr).Offset(0x1c0).GetAbsoluteAddress(1);

    setMdl(tree, mdl.modelName);
    if (tree->ModelScale() != mdl.scale) {
        tree->ModelScale() = mdl.scale;
        tree->Member<VClass*>(Netvars::C_BaseEntity::m_pGameSceneNode)->CallVFunc<10>(4);
    }
}

// Вызываем в RunFrame/FrameStageNotify
void Modules::M_TreeChanger::UpdateTreeModels() {
    if (!needsUpdate)
        return;

    if (needsUpdate) {
        auto trees = GameSystems::BinaryObjectSystem->GetTrees();
        for (auto tree : trees) {
            if (!tree)
                continue;
    
            SetTreeModel(tree, queuedModel);
        }
    }

    needsUpdate = false;
}

Поставим-ка models/props_tree/frostivus_tree.vmdl с масштабом 0.85:

dt2.png

Прекрасно. Но это только начало! Ведь ваш $упер-$офт должен также уметь вернуть оригинальные деревья прямо в игре(и не пугайтесь из-за того, что современные p2c этого не могут, это не наша забота). Итак, приступим.
Для начала нужно сохранить оригинальную модель дерева. Гетнуть это не составляет труда, заходим в ReClass и внимательно наблюдаем

lAYrDp4.png

оффсет m_hModel у CModelState. Это CStrongHandle, имя модели у которого находится в двух местах. Также можете напрямую взять по нетвару m_ModelName, вариаций масса! Оформляем нашему C_BaseEntity геттер:

C++:
const char* GetModelName() {
    return *GetGameSceneNode()->Member<NormalClass*>(0x200)->Member<const char**>(8);
}

Сохраняем в структуру на выбор, делаем функцию восстановления через тот же SetModel, нажимаем в своём списке деревьев Default и... куда они пропали? Деревья резко стали невидимыми, но не теряем надежду. Заходим в VRF и смотрим на любую дефолтную модельку дерева(я взял пальму с одного из ландшафтов):

dt3.png

Тоже ничего. Заметили новый пункт "Mesh Groups" в меню слева? Нажмите на пустые чекбоксы и увидите дерево. Меш-группы это как бы "submodels", то есть части одной модельки. Когда вы съедаете дерево, оно не исчезает, оно становится пеньком. Делать отдельную модельку на ствол и пень было бы крайне затратно, поэтому Valve сделали механизм проще — переключаешь меш-группу, и вот у дерева исчез ствол. Теперь изучим интересующие нас пункты:
  • GetMeshGroupMask — нетвары CSkeletonInstance::m_modelState и CModelState::m_MeshGroupMask(первый хранится прямо в объекте, не поинтером)
  • SetMeshGroupMask — может показаться, что без сигнатуры не обойтись, но у CModelState есть интересный коллбек "skeletonMeshGroupMaskChanged", следовательно анализируем его параметры в IDA

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    (осторожно, improper итерация!)
Меш-группы ставятся uint64_t-"масками".

Сохраняем в новую структуру, делаем рестор на основе флага(как и Update), пихаем туда SetMeshGroupMask. Получаем следующее:

C++:
struct TreeModelInfo {
    const char* modelName;
    float scale;
};
struct SavedModelInfo : public TreeModelInfo{
    uint64_t meshGroupMask;
};

std::map<CBaseEntity*, SavedModelInfo> originalTrees;

TreeModelInfo queuedModel;

void QueueModelUpdate(TreeModelInfo mdlInfo) {
    queuedModel = mdlInfo;
    needsUpdate = true;
}
void QueueModelRestore() {
    needsRestore = true;
}

void Modules::M_TreeChanger::SetTreeModel(CBaseEntity* tree, const TreeModelInfo& mdl) {
    static Function setMdl = nullptr;

    if (!setMdl.ptr)
        setMdl = Address(tree->GetVFunc(7).ptr).Offset(0x1c0).GetAbsoluteAddress(1);

    setMdl(tree, mdl.modelName);
    if (tree->ModelScale() != mdl.scale) {
        tree->ModelScale() = mdl.scale;
        tree->Member<VClass*>(Netvars::C_BaseEntity::m_pGameSceneNode)->CallVFunc<10>(4);
    }
}

void Modules::M_TreeChanger::RestoreTreeModels() {
static void(*skeletonMeshGroupMaskChanged)(CBaseEntity::CModelState * mdl, CBaseEntity * owner, uint64_t * mask) = nullptr;

    if (!skeletonMeshGroupMaskChanged)
        for (const auto& data : Interfaces::NetworkMessages->GetNetvarCallbacks())
            if (IsValidReadPtr(data.m_szCallbackName) && std::string_view(data.m_szCallbackName) == "skeletonMeshGroupMaskChanged") {
                skeletonMeshGroupMaskChanged = (decltype(skeletonMeshGroupMaskChanged))data.m_CallbackFn;
                break;
            }

    auto trees = GameSystems::BinaryObjectSystem->GetTrees();

    for (auto& [tree, mdlInfo] : originalTrees) {
        if (!IsValidReadPtr(tree))
            continue;

        SetTreeModel(tree, mdlInfo);
        skeletonMeshGroupMaskChanged(tree->GetGameSceneNode()->GetModelState(), tree, &mdlInfo.meshGroupMask);
        tree->SetColor({ 255,255,255,255 });
    }

    originalTrees.clear();
}

void Modules::M_TreeChanger::UpdateTreeModels() {
    if (!needsUpdate && !needsRestore)
        return;

    if (needsUpdate) {
        auto trees = GameSystems::BinaryObjectSystem->GetTrees();
        bool shouldSaveOriginalTrees = originalTrees.empty();
        for (auto tree : trees) {
            if (!tree)
                continue;

            if (shouldSaveOriginalTrees)
                originalTrees[tree] = { tree->GetModelName(), tree->ModelScale(), tree->GetGameSceneNode()->GetModelState()->GetMeshGroupMask() };

            SetTreeModel(tree, queuedModel);
        }
    }
    else if (needsRestore)
        RestoreTreeModels();

    needsUpdate = false;
    needsRestore = false;
}

И вот теперь при вызове RestoreTreeModels(в мейн треде) вы увидите родные деревья. Поздравляю, Tree Changer готов!

BONUS:
Короткий, но ёмкий датасет моделей и скейлов:

C++:
inline TreeModelList[] = {
{ "models/props_tree/newbloom_tree.vmdl", 1.0f },
{ "models/props_tree/mango_tree.vmdl", 1.0f },
{ "maps/journey_assets/props/trees/journey_armandpine/journey_armandpine_02_stump.vmdl", 4.5f },
{ "models/props_tree/frostivus_tree.vmdl", 0.85f },
{ "models/props_tree/ti7/ggbranch.vmdl", 1.0f },
{"models/props_structures/crystal003_refract.vmdl", 1},
{"models/props_structures/pumpkin001.vmdl", 1.08},
{"models/props_structures/pumpkin003.vmdl", 3},
{"models/props_diretide/pumpkin_head.vmdl", 3},
{"models/props_gameplay/pumpkin_bucket.vmdl", 1},

};

inline const char* TreeNameList[] = {
"Default",
"New Bloom",
"Mango",
"Stumps",
"Frostivus",
"GG Branch",
"Crystal",
"Pumpkins #1",
"Pumpkins #2",
"Pumpkins #3",
"Pumpkin Buckets"
};

models/props_tree/ti7/ggbranch.vmdl у вас будет бледно-белая, и это нормально. В зависимости от стадии седьмого инта она была разного цвета, я её помню жёлтой, изначально она была зелёная. Фиксится это выставлением m_clrRender на желаемый цвет и вызовом OnColorChanged, как это делают, например, при покраске иллюзий. Не забывайте возвращать его к { 255, 255, 255, 255 }, если меняетесь с GG branch на другую модель

Фулл код

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

.
 
G Оффлайн

Ghhf

Участник
LV
2
 
19.08.2024
15
0
15
Награды
2
24

Репутация:

За это не будет никакого наказания?
 
O Оффлайн

opxlop

Участник
LV
2
 
24.09.2024
2
0
11
Награды
2
31

Репутация:

Актуально, а за не за банят?
 
H Оффлайн

Hemiw

Участник
LV
2
 
24.09.2024
17
0
10
Награды
2
39

Репутация:

Сейчас посмотрим, что да как
 
K Оффлайн

Kanerx

Участник
LV
0
 
02.10.2024
5
0
6
19

Репутация:

Сейчас посмотрим, что да как
 
B Оффлайн

Berqqw

Местный
Участник
LV
2
 
17.10.2024
44
0
22
Награды
4
24

Репутация:

Сейчас скачаем посмотрим. Спасибо)))
 
M Оффлайн

mokleva

Местный
Участник
LV
2
 
16.10.2024
37
0
15
Награды
3
28

Репутация:

Так вот откуда берутся все эти мастера доджа в деревьях xd
 
shyne666 Оффлайн

shyne666

Участник
LV
0
 
18.11.2024
5
0
7
21

Репутация:

овер тяжело но очень понятно объяснгил
 
A Оффлайн

Axmed1928

Участник
LV
2
 
20.11.2024
16
0
10
Награды
2
22

Репутация:

ну вообще прикольная тема, спасибо
 
T Оффлайн

trerere

Участник
LV
0
 
30.11.2024
1
0
6
Награды
1
20

Репутация:

Слишком тяжко для моей бошки
 
Shipper Оффлайн

Shipper

Участник
LV
0
 
01.12.2024
2
0
7
24

Репутация:

Не плохо, работает вроде
 
O Оффлайн

Overlord87

Участник
LV
0
 
02.12.2024
10
0
7
Награды
1
25

Репутация:

Посмотреть вложение 9950

Итак, наша задача состоит в том, чтобы поменять всем деревьям модель. Для начала нужно найти список всех деревьев, и это не настолько очевидно. В EntitySystem такого нет, а поинтеры на 0x110 и далее не являются стабильным решением. Поглядим на класс дерева в Схеме:

Код:
C_DOTA_MapTree : C_DOTA_BinaryObject
    bool m_bInitialized 0x808;

Ага, BinaryObject. В доте есть статическая геймсистема CDOTA_BinaryObjectSystem, в которой, ожидаемо, на 0x18 лежит CUtlVector<C_DOTA_MapTree*>. Этот список работает таким же образом, как и entity list, поэтому там будут и nullptrы. Отлично, деревья наши.

Теперь нужно изменить им модель и масштаб. Для первого нам нужен SetModel. Находим по хрефам,
Посмотреть вложение 9951
но не спешим брать по сигнатуре, можно и без неё:

C++:
// found by Morphling
setMdl = Address(
    tree->GetVFunc(7).ptr // tree = C_DOTA_MapTree
).Offset(0x1c0).GetAbsoluteAddress(1);

Помимо this этот метод принимает путь к VPK-файлу модели(например "models/props_tree/ti7/ggbranch.vmdl"). Вызывать его необходимо в главном потоке доты, иначе будете ловить регулярные краши. Мой совет: вызывайте проверку на свой bool-флаг в чём-то типа RunFrame/FrameStageNotify, сам флаг сможете ставить где угодно. Сами модельки ищем в VPK-папке models/props_tree/

Иногда также необходимо выставить масштаб, чтобы видимый размер модельки соответствовал реальному(иначе известные всем пеньки будут микроскопическими). Для этого ищем метод CGameSceneNode::SetLocalScale через удобный конвар
Посмотреть вложение 9952
и видим, казалось бы, монструозную функцию:

Код:
if ( a2 != *(float *)(this + 0xC4) )
  {
    isHierarchyTypeZero = *(_BYTE *)(this + 0xEC) == 0;
    v4 = 4;
    *(float *)(this + 0xC4) = a2;
    if ( isHierarchyTypeZero )
    {
       *куча mov'ов*
    }
    ownerIdentity = *(_QWORD *)(*(_QWORD *)(this + 0x30) + 16i64);
    if ( (*(_DWORD *)(ownerIdentity + 48) & 0x800) != 0 )
    {
      v11 = (*(_DWORD *)(ownerIdentity + 48) & 0x40) != 0;
    }
    else
    {
      v12 = 0x7FFF;
      if ( ownerIdentity )
      {
        v13 = *(_DWORD *)(ownerIdentity + 16);
        if ( v13 != -1 )
          v12 = v13 & 0x7FFF;
      }
      v11 = v12 < 0x4000;
    }
    if ( !v11 )
    {
      m_pOwner = *(_QWORD *)(this + 0x30);
      globals = (float *)g_pGlobalVars;
      if ( !*((_BYTE *)g_pGlobalVars + 61) && !*((_BYTE *)g_pGlobalVars + 60) )
      {
        v16 = (void (__fastcall *)(__int64))*((_QWORD *)g_pGlobalVars + 4);
        if ( v16 )
        {
          v16(1i64);
          m_pOwner = *(_QWORD *)(this + 48);
        }
      }
      sub_180235560(m_pOwner, globals[11]);
    }
    (*(void (__fastcall **)(__int64, _QWORD))(*(_QWORD *)this + 80i64))(this, v4);

Но не спешите брать её сигнатуру! На самом деле, единственное, что нам нужно — это первая и последняя строчки. Проанализировав функцию динамически, увидите, что первый и последний if у деревьев не выполняется. К тому же конструкция последнего является некой inline-функцией, сообщающей игре о проблемах с синхронизацией, такую же можете увидеть в GetGameTime. Вот и остаётся условие на изменённый нетвар CGameSceneNode::m_flScale и вызов его виртуальной функции. В итоге код будет примерно такой:

C++:
struct TreeModelInfo {
    const char* modelName;
    float scale;
};

// Вызываем где-то у себя
void Modules::M_TreeChanger::QueueModelUpdate(TreeModelInfo mdlInfo) {
    queuedModel = mdlInfo;
    needsUpdate = true; // тот самый bool-флаг
}

void Modules::M_TreeChanger::SetTreeModel(CBaseEntity* tree, const TreeModelInfo& mdl) {
    static Function setMdl = nullptr;

    if (!setMdl.ptr)
        setMdl = Address(tree->GetVFunc(7).ptr).Offset(0x1c0).GetAbsoluteAddress(1);

    setMdl(tree, mdl.modelName);
    if (tree->ModelScale() != mdl.scale) {
        tree->ModelScale() = mdl.scale;
        tree->Member<VClass*>(Netvars::C_BaseEntity::m_pGameSceneNode)->CallVFunc<10>(4);
    }
}

// Вызываем в RunFrame/FrameStageNotify
void Modules::M_TreeChanger::UpdateTreeModels() {
    if (!needsUpdate)
        return;

    if (needsUpdate) {
        auto trees = GameSystems::BinaryObjectSystem->GetTrees();
        for (auto tree : trees) {
            if (!tree)
                continue;
   
            SetTreeModel(tree, queuedModel);
        }
    }

    needsUpdate = false;
}

Поставим-ка models/props_tree/frostivus_tree.vmdl с масштабом 0.85:

Посмотреть вложение 9953

Прекрасно. Но это только начало! Ведь ваш $упер-$офт должен также уметь вернуть оригинальные деревья прямо в игре(и не пугайтесь из-за того, что современные p2c этого не могут, это не наша забота). Итак, приступим.
Для начала нужно сохранить оригинальную модель дерева. Гетнуть это не составляет труда, заходим в ReClass и внимательно наблюдаем

Посмотреть вложение 9954

оффсет m_hModel у CModelState. Это CStrongHandle, имя модели у которого находится в двух местах. Также можете напрямую взять по нетвару m_ModelName, вариаций масса! Оформляем нашему C_BaseEntity геттер:

C++:
const char* GetModelName() {
    return *GetGameSceneNode()->Member<NormalClass*>(0x200)->Member<const char**>(8);
}

Сохраняем в структуру на выбор, делаем функцию восстановления через тот же SetModel, нажимаем в своём списке деревьев Default и... куда они пропали? Деревья резко стали невидимыми, но не теряем надежду. Заходим в VRF и смотрим на любую дефолтную модельку дерева(я взял пальму с одного из ландшафтов):

Посмотреть вложение 9955

Тоже ничего. Заметили новый пункт "Mesh Groups" в меню слева? Нажмите на пустые чекбоксы и увидите дерево. Меш-группы это как бы "submodels", то есть части одной модельки. Когда вы съедаете дерево, оно не исчезает, оно становится пеньком. Делать отдельную модельку на ствол и пень было бы крайне затратно, поэтому Valve сделали механизм проще — переключаешь меш-группу, и вот у дерева исчез ствол. Теперь изучим интересующие нас пункты:
  • GetMeshGroupMask — нетвары CSkeletonInstance::m_modelState и CModelState::m_MeshGroupMask(первый хранится прямо в объекте, не поинтером)
  • SetMeshGroupMask — может показаться, что без сигнатуры не обойтись, но у CModelState есть интересный коллбек "skeletonMeshGroupMaskChanged", следовательно анализируем его параметры в IDA

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    (осторожно, improper итерация!)
Меш-группы ставятся uint64_t-"масками".

Сохраняем в новую структуру, делаем рестор на основе флага(как и Update), пихаем туда SetMeshGroupMask. Получаем следующее:

C++:
struct TreeModelInfo {
    const char* modelName;
    float scale;
};
struct SavedModelInfo : public TreeModelInfo{
    uint64_t meshGroupMask;
};

std::map<CBaseEntity*, SavedModelInfo> originalTrees;

TreeModelInfo queuedModel;

void QueueModelUpdate(TreeModelInfo mdlInfo) {
    queuedModel = mdlInfo;
    needsUpdate = true;
}
void QueueModelRestore() {
    needsRestore = true;
}

void Modules::M_TreeChanger::SetTreeModel(CBaseEntity* tree, const TreeModelInfo& mdl) {
    static Function setMdl = nullptr;

    if (!setMdl.ptr)
        setMdl = Address(tree->GetVFunc(7).ptr).Offset(0x1c0).GetAbsoluteAddress(1);

    setMdl(tree, mdl.modelName);
    if (tree->ModelScale() != mdl.scale) {
        tree->ModelScale() = mdl.scale;
        tree->Member<VClass*>(Netvars::C_BaseEntity::m_pGameSceneNode)->CallVFunc<10>(4);
    }
}

void Modules::M_TreeChanger::RestoreTreeModels() {
static void(*skeletonMeshGroupMaskChanged)(CBaseEntity::CModelState * mdl, CBaseEntity * owner, uint64_t * mask) = nullptr;

    if (!skeletonMeshGroupMaskChanged)
        for (const auto& data : Interfaces::NetworkMessages->GetNetvarCallbacks())
            if (IsValidReadPtr(data.m_szCallbackName) && std::string_view(data.m_szCallbackName) == "skeletonMeshGroupMaskChanged") {
                skeletonMeshGroupMaskChanged = (decltype(skeletonMeshGroupMaskChanged))data.m_CallbackFn;
                break;
            }

    auto trees = GameSystems::BinaryObjectSystem->GetTrees();

    for (auto& [tree, mdlInfo] : originalTrees) {
        if (!IsValidReadPtr(tree))
            continue;

        SetTreeModel(tree, mdlInfo);
        skeletonMeshGroupMaskChanged(tree->GetGameSceneNode()->GetModelState(), tree, &mdlInfo.meshGroupMask);
        tree->SetColor({ 255,255,255,255 });
    }

    originalTrees.clear();
}

void Modules::M_TreeChanger::UpdateTreeModels() {
    if (!needsUpdate && !needsRestore)
        return;

    if (needsUpdate) {
        auto trees = GameSystems::BinaryObjectSystem->GetTrees();
        bool shouldSaveOriginalTrees = originalTrees.empty();
        for (auto tree : trees) {
            if (!tree)
                continue;

            if (shouldSaveOriginalTrees)
                originalTrees[tree] = { tree->GetModelName(), tree->ModelScale(), tree->GetGameSceneNode()->GetModelState()->GetMeshGroupMask() };

            SetTreeModel(tree, queuedModel);
        }
    }
    else if (needsRestore)
        RestoreTreeModels();

    needsUpdate = false;
    needsRestore = false;
}

И вот теперь при вызове RestoreTreeModels(в мейн треде) вы увидите родные деревья. Поздравляю, Tree Changer готов!

BONUS:
Короткий, но ёмкий датасет моделей и скейлов:

C++:
inline TreeModelList[] = {
{ "models/props_tree/newbloom_tree.vmdl", 1.0f },
{ "models/props_tree/mango_tree.vmdl", 1.0f },
{ "maps/journey_assets/props/trees/journey_armandpine/journey_armandpine_02_stump.vmdl", 4.5f },
{ "models/props_tree/frostivus_tree.vmdl", 0.85f },
{ "models/props_tree/ti7/ggbranch.vmdl", 1.0f },
{"models/props_structures/crystal003_refract.vmdl", 1},
{"models/props_structures/pumpkin001.vmdl", 1.08},
{"models/props_structures/pumpkin003.vmdl", 3},
{"models/props_diretide/pumpkin_head.vmdl", 3},
{"models/props_gameplay/pumpkin_bucket.vmdl", 1},

};

inline const char* TreeNameList[] = {
"Default",
"New Bloom",
"Mango",
"Stumps",
"Frostivus",
"GG Branch",
"Crystal",
"Pumpkins #1",
"Pumpkins #2",
"Pumpkins #3",
"Pumpkin Buckets"
};

models/props_tree/ti7/ggbranch.vmdl у вас будет бледно-белая, и это нормально. В зависимости от стадии седьмого инта она была разного цвета, я её помню жёлтой, изначально она была зелёная. Фиксится это выставлением m_clrRender на желаемый цвет и вызовом OnColorChanged, как это делают, например, при покраске иллюзий. Не забывайте возвращать его к { 255, 255, 255, 255 }, если меняетесь с GG branch на другую модель

Фулл код

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

.
Отличный рабочий ченджер спасибо большое
 
R Оффлайн

Ruslan_Ivanov

Участник
LV
3
 
01.10.2023
12
0
25
Награды
3
24

Репутация:

Статья 23 года, подскажите эта шняга сейчас работает? Там столько патчей вроде вольво дропнули уже
 
H Оффлайн

hxxkan

Участник
LV
2
 
15.02.2024
3
0
24
Награды
2
28

Репутация:

Посмотреть вложение 9950

Итак, наша задача состоит в том, чтобы поменять всем деревьям модель. Для начала нужно найти список всех деревьев, и это не настолько очевидно. В EntitySystem такого нет, а поинтеры на 0x110 и далее не являются стабильным решением. Поглядим на класс дерева в Схеме:

Код:
C_DOTA_MapTree : C_DOTA_BinaryObject
    bool m_bInitialized 0x808;

Ага, BinaryObject. В доте есть статическая геймсистема CDOTA_BinaryObjectSystem, в которой, ожидаемо, на 0x18 лежит CUtlVector<C_DOTA_MapTree*>. Этот список работает таким же образом, как и entity list, поэтому там будут и nullptrы. Отлично, деревья наши.

Теперь нужно изменить им модель и масштаб. Для первого нам нужен SetModel. Находим по хрефам,
Посмотреть вложение 9951
но не спешим брать по сигнатуре, можно и без неё:

C++:
// found by Morphling
setMdl = Address(
    tree->GetVFunc(7).ptr // tree = C_DOTA_MapTree
).Offset(0x1c0).GetAbsoluteAddress(1);

Помимо this этот метод принимает путь к VPK-файлу модели(например "models/props_tree/ti7/ggbranch.vmdl"). Вызывать его необходимо в главном потоке доты, иначе будете ловить регулярные краши. Мой совет: вызывайте проверку на свой bool-флаг в чём-то типа RunFrame/FrameStageNotify, сам флаг сможете ставить где угодно. Сами модельки ищем в VPK-папке models/props_tree/

Иногда также необходимо выставить масштаб, чтобы видимый размер модельки соответствовал реальному(иначе известные всем пеньки будут микроскопическими). Для этого ищем метод CGameSceneNode::SetLocalScale через удобный конвар
Посмотреть вложение 9952
и видим, казалось бы, монструозную функцию:

Код:
if ( a2 != *(float *)(this + 0xC4) )
  {
    isHierarchyTypeZero = *(_BYTE *)(this + 0xEC) == 0;
    v4 = 4;
    *(float *)(this + 0xC4) = a2;
    if ( isHierarchyTypeZero )
    {
       *куча mov'ов*
    }
    ownerIdentity = *(_QWORD *)(*(_QWORD *)(this + 0x30) + 16i64);
    if ( (*(_DWORD *)(ownerIdentity + 48) & 0x800) != 0 )
    {
      v11 = (*(_DWORD *)(ownerIdentity + 48) & 0x40) != 0;
    }
    else
    {
      v12 = 0x7FFF;
      if ( ownerIdentity )
      {
        v13 = *(_DWORD *)(ownerIdentity + 16);
        if ( v13 != -1 )
          v12 = v13 & 0x7FFF;
      }
      v11 = v12 < 0x4000;
    }
    if ( !v11 )
    {
      m_pOwner = *(_QWORD *)(this + 0x30);
      globals = (float *)g_pGlobalVars;
      if ( !*((_BYTE *)g_pGlobalVars + 61) && !*((_BYTE *)g_pGlobalVars + 60) )
      {
        v16 = (void (__fastcall *)(__int64))*((_QWORD *)g_pGlobalVars + 4);
        if ( v16 )
        {
          v16(1i64);
          m_pOwner = *(_QWORD *)(this + 48);
        }
      }
      sub_180235560(m_pOwner, globals[11]);
    }
    (*(void (__fastcall **)(__int64, _QWORD))(*(_QWORD *)this + 80i64))(this, v4);

Но не спешите брать её сигнатуру! На самом деле, единственное, что нам нужно — это первая и последняя строчки. Проанализировав функцию динамически, увидите, что первый и последний if у деревьев не выполняется. К тому же конструкция последнего является некой inline-функцией, сообщающей игре о проблемах с синхронизацией, такую же можете увидеть в GetGameTime. Вот и остаётся условие на изменённый нетвар CGameSceneNode::m_flScale и вызов его виртуальной функции. В итоге код будет примерно такой:

C++:
struct TreeModelInfo {
    const char* modelName;
    float scale;
};

// Вызываем где-то у себя
void Modules::M_TreeChanger::QueueModelUpdate(TreeModelInfo mdlInfo) {
    queuedModel = mdlInfo;
    needsUpdate = true; // тот самый bool-флаг
}

void Modules::M_TreeChanger::SetTreeModel(CBaseEntity* tree, const TreeModelInfo& mdl) {
    static Function setMdl = nullptr;

    if (!setMdl.ptr)
        setMdl = Address(tree->GetVFunc(7).ptr).Offset(0x1c0).GetAbsoluteAddress(1);

    setMdl(tree, mdl.modelName);
    if (tree->ModelScale() != mdl.scale) {
        tree->ModelScale() = mdl.scale;
        tree->Member<VClass*>(Netvars::C_BaseEntity::m_pGameSceneNode)->CallVFunc<10>(4);
    }
}

// Вызываем в RunFrame/FrameStageNotify
void Modules::M_TreeChanger::UpdateTreeModels() {
    if (!needsUpdate)
        return;

    if (needsUpdate) {
        auto trees = GameSystems::BinaryObjectSystem->GetTrees();
        for (auto tree : trees) {
            if (!tree)
                continue;
   
            SetTreeModel(tree, queuedModel);
        }
    }

    needsUpdate = false;
}

Поставим-ка models/props_tree/frostivus_tree.vmdl с масштабом 0.85:

Посмотреть вложение 9953

Прекрасно. Но это только начало! Ведь ваш $упер-$офт должен также уметь вернуть оригинальные деревья прямо в игре(и не пугайтесь из-за того, что современные p2c этого не могут, это не наша забота). Итак, приступим.
Для начала нужно сохранить оригинальную модель дерева. Гетнуть это не составляет труда, заходим в ReClass и внимательно наблюдаем

Посмотреть вложение 9954

оффсет m_hModel у CModelState. Это CStrongHandle, имя модели у которого находится в двух местах. Также можете напрямую взять по нетвару m_ModelName, вариаций масса! Оформляем нашему C_BaseEntity геттер:

C++:
const char* GetModelName() {
    return *GetGameSceneNode()->Member<NormalClass*>(0x200)->Member<const char**>(8);
}

Сохраняем в структуру на выбор, делаем функцию восстановления через тот же SetModel, нажимаем в своём списке деревьев Default и... куда они пропали? Деревья резко стали невидимыми, но не теряем надежду. Заходим в VRF и смотрим на любую дефолтную модельку дерева(я взял пальму с одного из ландшафтов):

Посмотреть вложение 9955

Тоже ничего. Заметили новый пункт "Mesh Groups" в меню слева? Нажмите на пустые чекбоксы и увидите дерево. Меш-группы это как бы "submodels", то есть части одной модельки. Когда вы съедаете дерево, оно не исчезает, оно становится пеньком. Делать отдельную модельку на ствол и пень было бы крайне затратно, поэтому Valve сделали механизм проще — переключаешь меш-группу, и вот у дерева исчез ствол. Теперь изучим интересующие нас пункты:
  • GetMeshGroupMask — нетвары CSkeletonInstance::m_modelState и CModelState::m_MeshGroupMask(первый хранится прямо в объекте, не поинтером)
  • SetMeshGroupMask — может показаться, что без сигнатуры не обойтись, но у CModelState есть интересный коллбек "skeletonMeshGroupMaskChanged", следовательно анализируем его параметры в IDA

    Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

    (осторожно, improper итерация!)
Меш-группы ставятся uint64_t-"масками".

Сохраняем в новую структуру, делаем рестор на основе флага(как и Update), пихаем туда SetMeshGroupMask. Получаем следующее:

C++:
struct TreeModelInfo {
    const char* modelName;
    float scale;
};
struct SavedModelInfo : public TreeModelInfo{
    uint64_t meshGroupMask;
};

std::map<CBaseEntity*, SavedModelInfo> originalTrees;

TreeModelInfo queuedModel;

void QueueModelUpdate(TreeModelInfo mdlInfo) {
    queuedModel = mdlInfo;
    needsUpdate = true;
}
void QueueModelRestore() {
    needsRestore = true;
}

void Modules::M_TreeChanger::SetTreeModel(CBaseEntity* tree, const TreeModelInfo& mdl) {
    static Function setMdl = nullptr;

    if (!setMdl.ptr)
        setMdl = Address(tree->GetVFunc(7).ptr).Offset(0x1c0).GetAbsoluteAddress(1);

    setMdl(tree, mdl.modelName);
    if (tree->ModelScale() != mdl.scale) {
        tree->ModelScale() = mdl.scale;
        tree->Member<VClass*>(Netvars::C_BaseEntity::m_pGameSceneNode)->CallVFunc<10>(4);
    }
}

void Modules::M_TreeChanger::RestoreTreeModels() {
static void(*skeletonMeshGroupMaskChanged)(CBaseEntity::CModelState * mdl, CBaseEntity * owner, uint64_t * mask) = nullptr;

    if (!skeletonMeshGroupMaskChanged)
        for (const auto& data : Interfaces::NetworkMessages->GetNetvarCallbacks())
            if (IsValidReadPtr(data.m_szCallbackName) && std::string_view(data.m_szCallbackName) == "skeletonMeshGroupMaskChanged") {
                skeletonMeshGroupMaskChanged = (decltype(skeletonMeshGroupMaskChanged))data.m_CallbackFn;
                break;
            }

    auto trees = GameSystems::BinaryObjectSystem->GetTrees();

    for (auto& [tree, mdlInfo] : originalTrees) {
        if (!IsValidReadPtr(tree))
            continue;

        SetTreeModel(tree, mdlInfo);
        skeletonMeshGroupMaskChanged(tree->GetGameSceneNode()->GetModelState(), tree, &mdlInfo.meshGroupMask);
        tree->SetColor({ 255,255,255,255 });
    }

    originalTrees.clear();
}

void Modules::M_TreeChanger::UpdateTreeModels() {
    if (!needsUpdate && !needsRestore)
        return;

    if (needsUpdate) {
        auto trees = GameSystems::BinaryObjectSystem->GetTrees();
        bool shouldSaveOriginalTrees = originalTrees.empty();
        for (auto tree : trees) {
            if (!tree)
                continue;

            if (shouldSaveOriginalTrees)
                originalTrees[tree] = { tree->GetModelName(), tree->ModelScale(), tree->GetGameSceneNode()->GetModelState()->GetMeshGroupMask() };

            SetTreeModel(tree, queuedModel);
        }
    }
    else if (needsRestore)
        RestoreTreeModels();

    needsUpdate = false;
    needsRestore = false;
}

И вот теперь при вызове RestoreTreeModels(в мейн треде) вы увидите родные деревья. Поздравляю, Tree Changer готов!

BONUS:
Короткий, но ёмкий датасет моделей и скейлов:

C++:
inline TreeModelList[] = {
{ "models/props_tree/newbloom_tree.vmdl", 1.0f },
{ "models/props_tree/mango_tree.vmdl", 1.0f },
{ "maps/journey_assets/props/trees/journey_armandpine/journey_armandpine_02_stump.vmdl", 4.5f },
{ "models/props_tree/frostivus_tree.vmdl", 0.85f },
{ "models/props_tree/ti7/ggbranch.vmdl", 1.0f },
{"models/props_structures/crystal003_refract.vmdl", 1},
{"models/props_structures/pumpkin001.vmdl", 1.08},
{"models/props_structures/pumpkin003.vmdl", 3},
{"models/props_diretide/pumpkin_head.vmdl", 3},
{"models/props_gameplay/pumpkin_bucket.vmdl", 1},

};

inline const char* TreeNameList[] = {
"Default",
"New Bloom",
"Mango",
"Stumps",
"Frostivus",
"GG Branch",
"Crystal",
"Pumpkins #1",
"Pumpkins #2",
"Pumpkins #3",
"Pumpkin Buckets"
};

models/props_tree/ti7/ggbranch.vmdl у вас будет бледно-белая, и это нормально. В зависимости от стадии седьмого инта она была разного цвета, я её помню жёлтой, изначально она была зелёная. Фиксится это выставлением m_clrRender на желаемый цвет и вызовом OnColorChanged, как это делают, например, при покраске иллюзий. Не забывайте возвращать его к { 255, 255, 255, 255 }, если меняетесь с GG branch на другую модель

Фулл код

Пожалуйста, войдите или зерегистрируйтесь, чтобы увидеть скрытый текст.

.
огромное спасибо за гайд, наконец то с кайфом поиграю
 

Поиск по форуму

shape1
shape2
shape3
shape4
shape7
shape8
Верх