Добро пожаловать на сайт - wlux.net!
FAQ по форуму
1. Все сообщения до группы местный проходят модерацию от 1 минуты до 24 часа
2. Сообщения учитываются в следующих разделах: Читать
3.Что-бы скачать вложение нужно 2 сообщения.
4.Личные переписки не работают на форуме
5. Запрещено: Просить скрытый текст , спам, реклама, скам, ддос, кардинг и другая чернуха, нарушать любые законы РФ/СНГ = бан аккаунта
6. Внимание! Мы не удаляем аккаунты с форума! Будьте внимательны ДО регистрации! Как удалить аккаунт на форуме?!
5.Не понимаю, как и что тут работает у вас?!Как создавать темы, писать сообщения, как получать реакции. Почему не засчитывает сообщения. Все ответы здесь
Репутация:
C_DOTA_MapTree : C_DOTA_BinaryObject
bool m_bInitialized 0x808;
// found by Morphling
setMdl = Address(
tree->GetVFunc(7).ptr // tree = C_DOTA_MapTree
).Offset(0x1c0).GetAbsoluteAddress(1);
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);
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;
}
const char* GetModelName() {
return *GetGameSceneNode()->Member<NormalClass*>(0x200)->Member<const char**>(8);
}
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;
}
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"
};
Репутация:
Отличный рабочий ченджер спасибо большоеПосмотреть вложение 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 сделали механизм проще — переключаешь меш-группу, и вот у дерева исчез ствол. Теперь изучим интересующие нас пункты:
Меш-группы ставятся uint64_t-"масками".
- GetMeshGroupMask — нетвары CSkeletonInstance::m_modelState и CModelState::m_MeshGroupMask(первый хранится прямо в объекте, не поинтером)
- SetMeshGroupMask — может показаться, что без сигнатуры не обойтись, но у CModelState есть интересный коллбек "skeletonMeshGroupMaskChanged", следовательно анализируем его параметры в IDA (осторожно, improper итерация!)
Сохраняем в новую структуру, делаем рестор на основе флага(как и 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 на другую модель
Фулл код .
Репутация:
огромное спасибо за гайд, наконец то с кайфом поиграюПосмотреть вложение 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 сделали механизм проще — переключаешь меш-группу, и вот у дерева исчез ствол. Теперь изучим интересующие нас пункты:
Меш-группы ставятся uint64_t-"масками".
- GetMeshGroupMask — нетвары CSkeletonInstance::m_modelState и CModelState::m_MeshGroupMask(первый хранится прямо в объекте, не поинтером)
- SetMeshGroupMask — может показаться, что без сигнатуры не обойтись, но у CModelState есть интересный коллбек "skeletonMeshGroupMaskChanged", следовательно анализируем его параметры в IDA (осторожно, improper итерация!)
Сохраняем в новую структуру, делаем рестор на основе флага(как и 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 на другую модель
Фулл код .
Репутация:
Спасибо за подробный гайд, не часто найдешь к гайду фулл исходникПосмотреть вложение 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 сделали механизм проще — переключаешь меш-группу, и вот у дерева исчез ствол. Теперь изучим интересующие нас пункты:
Меш-группы ставятся uint64_t-"масками".
- GetMeshGroupMask — нетвары CSkeletonInstance::m_modelState и CModelState::m_MeshGroupMask(первый хранится прямо в объекте, не поинтером)
- SetMeshGroupMask — может показаться, что без сигнатуры не обойтись, но у CModelState есть интересный коллбек "skeletonMeshGroupMaskChanged", следовательно анализируем его параметры в IDA (осторожно, improper итерация!)
Сохраняем в новую структуру, делаем рестор на основе флага(как и 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 на другую модель
Фулл код .
Репутация:
Так ландшафты вроде и есть скины на дерево))вы кстати главное только валв не говорите а то они начнут скины на деревья продавать
Думаю нет, это же вроде бы незначительное изменения файлов игры, хотя за некоторые чейнджеры банилиЗа это не будет никакого наказания?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?