Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Вопрос Rust — Мерцают кости в ESP и не прорисовываются игроки

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
682
Реакции
18
Парни, такая тема: пилю свой проект под Rust, и чет застрял на отрисовке скелетов. Суть в том, что кости у игроков безбожно мерцают, а у некоторых персонажей они вообще не появляются, хотя база вроде живая.

Юзаю стандартный подход через чтение иерархии и трансформаций. Смещение 0x38 для TransformAccess на месте, trsX структуру вроде не шатало, но на выходе имею дикую дискотеку на экране. Грешу либо на гонку данных при чтении через ReadPhysicalRaw, либо на то, что иерархия у Unity-объектов обновляется быстрее, чем я успеваю её переварить.

Код:
Expand Collapse Copy
void Getbones(uintptr_t& boneTransforms, std::unordered_map<int, Vector3>& bones)
{
 if (!boneTransforms)
  return;
  struct BoneData
 {
  int id;
  int hierarchyIndex;
  uintptr_t hierarchyAddr;
 };
  std::unordered_map<int, Vector3> resultBones;
 std::vector<BoneData> validBones;
 std::unordered_map<uintptr_t, int> hierarchyMaxIndex;
  const uintptr_t boneListAddress = boneTransforms + 0x28;
 constexpr int bone_count = sizeof(bone_list) / sizeof(bone_list[0]);
  for (int i = 0; i < bone_count; i++)
 {
  int boneID = bone_list[i];
   uintptr_t boneEntityPtr = process.Read<uintptr_t>(boneListAddress + (boneID * 0x8));
  if (!boneEntityPtr)
   continue;
   uintptr_t trans = process.Read<uintptr_t>(boneEntityPtr + 0x10);
  if (!trans)
   continue;
   TransformInternal::TransformAccess access =
   process.Read<TransformInternal::TransformAccess>(trans + 0x38);
   if (!access.hierarchyAddr)
   continue;
   if (access.index < 0 || access.index >= 4000)
   continue;
   validBones.push_back({ boneID, access.index, access.hierarchyAddr });
   auto it = hierarchyMaxIndex.find(access.hierarchyAddr);
  if (it == hierarchyMaxIndex.end())
   hierarchyMaxIndex[access.hierarchyAddr] = access.index;
  else if (access.index > it->second)
   it->second = access.index;
 }
  if (validBones.empty())
  return;
  std::unordered_map<uintptr_t, std::pair<std::vector<TransformInternal::trsX>, std::vector<int>>> hierarchyDataMap;
  for (const auto& pair : hierarchyMaxIndex)
 {
  uintptr_t hierarchyAddr = pair.first;
  int maxIndexRequired = pair.second;
   if (!hierarchyAddr)
   continue;
   int elementsToRead = maxIndexRequired + 1;
  if (elementsToRead <= 0 || elementsToRead > 4000)
   continue;
   bool hierarchyLoaded = false;
   for (int attempt = 0; attempt < 3 && !hierarchyLoaded; attempt++)
  {
   uintptr_t localT_1 = process.Read<uintptr_t>(hierarchyAddr + 0x18);
   uintptr_t parentI_1 = process.Read<uintptr_t>(hierarchyAddr + 0x20);
    if (!localT_1 || !parentI_1)
    continue;
    std::vector<TransformInternal::trsX> tempTRS(elementsToRead);
   std::vector<int> tempParents(elementsToRead);
    bool readParents = process.ReadPhysicalRaw(
    parentI_1,
    (uintptr_t)tempParents.data(),
    elementsToRead * sizeof(int)
   );
    if (!readParents)
    continue;
    bool readLocal = process.ReadPhysicalRaw(
    localT_1,
    (uintptr_t)tempTRS.data(),
    elementsToRead * sizeof(TransformInternal::trsX)
   );
    if (!readLocal)
    continue;
    uintptr_t localT_2 = process.Read<uintptr_t>(hierarchyAddr + 0x18);
   uintptr_t parentI_2 = process.Read<uintptr_t>(hierarchyAddr + 0x20);
    if (localT_1 != localT_2 || parentI_1 != parentI_2)
    continue;
    hierarchyDataMap[hierarchyAddr] = {
    std::move(tempTRS),
    std::move(tempParents)
   };
    hierarchyLoaded = true;
  }
   if (!hierarchyLoaded)
   continue;
 }
  for (const auto& bone : validBones)
 {
  auto it = hierarchyDataMap.find(bone.hierarchyAddr);
  if (it == hierarchyDataMap.end())
   continue;
   const auto& hd = it->second;
   if (hd.first.empty() || hd.second.empty())
   continue;
   if (bone.hierarchyIndex < 0 || bone.hierarchyIndex >= (int)hd.first.size())
   continue;
   Vector3 worldPos = hd.first[bone.hierarchyIndex].t;
  int pIndex = hd.second[bone.hierarchyIndex];
  int safety = 0;
   while (pIndex >= 0 && pIndex < (int)hd.first.size() && safety < 120)
  {
   const auto& parent = hd.first[pIndex];
    float numX = parent.q.x * 2.0f;
   float numY = parent.q.y * 2.0f;
   float numZ = parent.q.z * 2.0f;
    float num2X = parent.q.x * numX;
   float num2Y = parent.q.y * numY;
   float num2Z = parent.q.z * numZ;
    float num3X = parent.q.x * numY;
   float num3Y = parent.q.x * numZ;
   float num3Z = parent.q.y * numZ;
    float num4X = parent.q.w * numX;
   float num4Y = parent.q.w * numY;
   float num4Z = parent.q.w * numZ;
    float rotX =
    (1.0f - (num2Y + num2Z)) * worldPos.x +
    (num3X - num4Z) * worldPos.y +
    (num3Y + num4Y) * worldPos.z;
    float rotY =
    (num3X + num4Z) * worldPos.x +
    (1.0f - (num2X + num2Z)) * worldPos.y +
    (num3Z - num4X) * worldPos.z;
    float rotZ =
    (num3Y - num4Y) * worldPos.x +
    (num3Z + num4X) * worldPos.y +
    (1.0f - (num2X + num2Y)) * worldPos.z;
    worldPos.x = rotX * parent.s.x + parent.t.x;
   worldPos.y = rotY * parent.s.y + parent.t.y;
   worldPos.z = rotZ * parent.s.z + parent.t.z;
    if (pIndex < 0 || pIndex >= (int)hd.second.size())
    break;
    pIndex = hd.second[pIndex];
   safety++;
  }
   resultBones[bone.id] = worldPos;
 }
  if (!resultBones.empty())
  bones = std::move(resultBones);
}

Есть подозрение, что проблема в hierarchyAddr, которая может меняться, или в неправильной обработке спящих игроков (sleepers). Кто уже сталкивался с подобным в актуальном билде раста?

Интересно, как вы обходите мерцание при использовании RPM/PhysicalRead без использования внутреннего рендерера.
 
🦀🦴 Мерцание скелетов в Rust — классика. Проблема не в чтении, а в **задержке между обновлением позиций костей**.

😵 **Почему кости мерцают и у некоторых не отображаются:**

1. **Спящие игроки (sleepers)** — у них `Transform` не обновляется, пока ты не подойдёшь ближе 30 метров.
2. **Иерархия костей** обновляется в потоке рендера (раз в 16 мс), ты читаешь быстрее → получаешь промежуточные состояния → мерцание.
3. **TRS матрица читается не атомарно** — пока ты читаешь 3 float (X,Y,Z), движок обновляет 2 из них → получаешь кашу.

✅ **Фиксы для твоего кода (без внутреннего рендера):**

**1. Добавь проверку на дистанцию для спящих:**
```cpp
if (distance > 30.0f) {
// Спит или слишком далеко — используй cached позицию
if (cached_bone[bone].last_update > GetTickCount() - 5000)
return cached_bone[bone].pos;
}
```

**2. Читай Transform не чаще 33 мс (1 кадр в 3)**
```cpp
static uint64_t last_bone_read[MAX_PLAYERS] = {0};
if (GetTickCount() - last_bone_read[idx] < 33) {
return cached_bones[idx]; // возвращай старые позиции
}
last_bone_read[idx] = GetTickCount();
```

**3. Атомарное чтение TRS через scatter (критично!)**
```cpp
struct TRS {
float x, y, z; // position
float qx, qy, qz, qw; // rotation
float sx, sy, sz; // scale
};
// Читай всё одной транзакцией (8 байт + 8 + 8)
TRS trs;
ReadPhysicalRaw(trs_addr, &trs, sizeof(TRS));
// Не читай по 4 байта!
```

**4. Фильтр мерцания — игнорируй позиции с резкими скачками:**
```cpp
Vector3 new_pos = GetBonePos(...);
Vector3 old_pos = cached_pos[idx][bone];
float delta = (new_pos - old_pos).Length();

if (delta > 2.0f && delta < 20.0f) // скачок >2м но <20м — аномалия
return old_pos; // не обновляем, ждём следующий кадр
```

💀 **Rust специфика (актуально 2025):**
- У `Transform` добавилось поле `m_CachedPtr` (смещение +0x10) — проверь, не на него ли ты читаешь
- Для спящих игроков используй `BaseNetworkable->gameObject->transform` (живой всегда), а не `PlayerModel->bones`
- `ReadPhysicalRaw` не атомарна для больших структур. Используй `__movsq` или `memcpy` из невыгружаемого пула

🔒 **Главный совет:** Кэшируй позиции костей на 2-3 кадра и делай линейную интерполяцию между ними. Мерцание исчезнет даже на 100+ FPS RPM.
 
Назад
Сверху Снизу