diff --git a/CFramework/Cheat/Aim.cpp b/CFramework/Cheat/Aim.cpp index a605453..ce5db60 100644 --- a/CFramework/Cheat/Aim.cpp +++ b/CFramework/Cheat/Aim.cpp @@ -1,5 +1,7 @@ #include "Framework.h" #include +#include +#include // ============================================================ // Aimbot (自瞄) + Recoil Control (压枪) @@ -102,6 +104,8 @@ void CFramework::AimAll() Vector2 bestScreen{}; uintptr_t bestAddr = 0; + m_diagTotal = 0; m_diagHealth = 0; m_diagSkipped = 0; + for (auto& entity : GetEntityList()) { CPlayer* pEntity = &entity; @@ -109,31 +113,56 @@ void CFramework::AimAll() if (pEntity->m_address == 0 || pEntity->m_address == local.m_address) continue; + m_diagTotal++; + if (!pEntity->Update()) + { + m_diagSkipped++; continue; + } // 距離チェック float dist = GetDistance(local.m_vecOrigin, pEntity->m_vecOrigin); if (dist > g.g_AimMaxDist) + { + m_diagSkipped++; continue; + } // 種別フィルタ (PMC / Scav / Boss) if (!IsValidAimTarget(pEntity->m_iSpawnType)) + { + m_diagSkipped++; continue; + } // 生存チェック (死体はスキップ) - pEntity->UpdateHealth(); - if (pEntity->m_fHealth <= 0.f) - continue; + // ※ Mono版で HealthController の offset が違うと全員死亡扱いになる。 + // g_AimIgnoreHealth で判定をスキップ可能。 + if (!g.g_AimIgnoreHealth) + { + pEntity->UpdateHealth(); + if (pEntity->m_fHealth <= 0.f) + { + m_diagHealth++; + continue; + } + } // 狙うボーン Vector3 aim = GetAimBone(pEntity); if (Vec3_Empty(aim)) + { + m_diagSkipped++; continue; + } Vector2 s{}; if (!WorldToScreen(ViewMatrix, GameSize, aim, s)) + { + m_diagSkipped++; continue; + } float d = sqrtf((s.x - Center.x) * (s.x - Center.x) + (s.y - Center.y) * (s.y - Center.y)); if (d < bestDist) @@ -145,6 +174,9 @@ void CFramework::AimAll() } } + m_diagBestAddr = bestAddr; + m_diagBestDist = bestDist; + if (bestAddr == 0) return; @@ -264,14 +296,15 @@ void CFramework::AimMouse(const Vector2& target, const Vector2& center) } // Memory モード: MovementContext._rotation (Vector2 x=yaw y=pitch) へ書く +// オフセットは g.g_AimMcOffset / g.g_AimRotOffset (运行时可变) を使用 void CFramework::AimMemory(const Vector3& target, const Vector3& localPos) { - uintptr_t mc = m.Read(local.m_address + offset::Player::pMovementContext); + uintptr_t mc = m.Read(local.m_address + (uintptr_t)g.g_AimMcOffset); if (!mc) return; // 現在の回転 [rad] - Vector2 rot = m.Read(mc + offset::MovementContext::Rotation); + Vector2 rot = m.Read(mc + (uintptr_t)g.g_AimRotOffset); // 目標までの方向 Vector3 delta = target - localPos; @@ -298,5 +331,118 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos) ClampAngle(rot.y + dpitch * t) }; - m.Write(mc + offset::MovementContext::Rotation, newRot); + m.Write(mc + (uintptr_t)g.g_AimRotOffset, newRot); +} + +// ============================================================ +// 旋转偏移扫描器 (2 段階) +// Step1: Player 構造内のポインタ候補を列挙 → 各ポインタ先で [-π,π] の +// 隣接 float ペア (rotation らしきもの) を記録 +// Step2: マウスを回した後に再度読み、yaw 変化が最大のペアを報告 +// 見つかったら g.g_AimMcOffset / g.g_AimRotOffset に自動適用 +// ============================================================ +void CFramework::ScanRotationOffset() +{ + struct Cand + { + uintptr_t mcOff; // Player + mcOff -> ポインタ + uintptr_t rotOff; // ポインタ + rotOff -> yaw (float) + float yaw; // Step1 時の yaw 値 + }; + + static std::vector s_cands; + + if (!tarkov->IsInRaid() || local.m_address == 0) + { + strcpy_s(m_scanResult, "In Raid でのみスキャンできます (メニューを閉じて raid に入る)"); + m_scanPhase = 0; + return; + } + + if (m_scanPhase == 0) + { + s_cands.clear(); + m_scanResult[0] = 0; + + // Player 構造 [0x100, 0x700] 内の 8byte アライン済みポインタ + for (uintptr_t mcOff = 0x100; mcOff <= 0x700; mcOff += 0x8) + { + uintptr_t ptr = m.Read(local.m_address + mcOff); + if (!ptr) + continue; + + // ポインタ先 [0x0, 0x500] で rotation らしきペアを探す + for (uintptr_t rotOff = 0x0; rotOff <= 0x500; rotOff += 0x4) + { + float x = m.Read(ptr + rotOff); + float y = m.Read(ptr + rotOff + 4); + + if (fabsf(x) <= kPi && fabsf(y) <= kPi && + (x != 0.f || y != 0.f)) + { + s_cands.push_back({ mcOff, rotOff, x }); + if (s_cands.size() >= 20000) + break; + } + } + if (s_cands.size() >= 20000) + break; + } + + if (s_cands.empty()) + { + strcpy_s(m_scanResult, "候補が見つかりません。範囲を広げて再試行 (0x100-0x700)"); + m_scanPhase = 0; + return; + } + + m_scanPhase = 1; + snprintf(m_scanResult, sizeof(m_scanResult), + "Step1 OK: %zu candidates. マウスを水平に大きく回してから、もう一度ボタンを押す", + s_cands.size()); + } + else + { + // Step2: yaw 変化量が最大の候補を特定 + float bestDelta = -1.f; + Cand best{ 0, 0, 0.f }; + + for (auto& c : s_cands) + { + uintptr_t ptr = m.Read(local.m_address + c.mcOff); + if (!ptr) + continue; + + float x = m.Read(ptr + c.rotOff); + + // 角度の最短差分 + float d = fabsf(x - c.yaw); + if (d > kPi) + d = kTwoPi - d; + + if (d > bestDelta) + { + bestDelta = d; + best = c; + } + } + + m_scanPhase = 0; + + if (bestDelta > 0.3f) + { + g.g_AimMcOffset = (int)best.mcOff; + g.g_AimRotOffset = (int)best.rotOff; + + snprintf(m_scanResult, sizeof(m_scanResult), + "Found! Player+0x%X -> +0x%X (yaw delta %.2f rad). 適用済み。Offset.h にも書いて固定する事。", + (unsigned)best.mcOff, (unsigned)best.rotOff, bestDelta); + } + else + { + snprintf(m_scanResult, sizeof(m_scanResult), + "変化が小さい (max %.2f rad)。マウスを大きく回してもう一度。", + bestDelta); + } + } } diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h index 82490cb..954264b 100644 --- a/CFramework/Cheat/Framework.h +++ b/CFramework/Cheat/Framework.h @@ -80,6 +80,18 @@ private: Vector3 GetAimBone(CPlayer* pEntity); void AimMouse(const Vector2& target, const Vector2& center); void AimMemory(const Vector3& target, const Vector3& localPos); + void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移 + + // 旋转偏移扫描器状态 + int m_scanPhase = 0; // 0 = 待机/Step1, 1 = 等待转动鼠标后 Step2 + char m_scanResult[192]{ 0 }; + + // 自瞄诊断计数(菜单 Aim Status 显示) + int m_diagTotal = 0; // 遍历到的实体数 + int m_diagHealth = 0; // 被健康检查判死跳过的数 + int m_diagSkipped = 0; // 其他原因跳过的数 + uintptr_t m_diagBestAddr = 0; // 最近一帧锁定的目标地址 + float m_diagBestDist = 0.f;// 最近一帧目标到屏幕中心的距离 void DrawLine(ImVec2 a, ImVec2 b, ImColor color, float width) { diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp index 0780c2c..6b65c5f 100644 --- a/CFramework/Cheat/Menu.cpp +++ b/CFramework/Cheat/Menu.cpp @@ -178,29 +178,47 @@ void CFramework::RenderMenu() ImGui::Checkbox("PMC", &g.g_AimTargetPMC); ImGui::Checkbox("Scav", &g.g_AimTargetScav); ImGui::Checkbox("Boss", &g.g_AimTargetBoss); + ImGui::Checkbox("Ignore Health", &g.g_AimIgnoreHealth); ImGui::Checkbox("Draw FOV", &g.g_AimDrawFov); ImGui::Checkbox("Draw Target", &g.g_AimDrawTarget); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // 数据链路状态: 哪一环断了看这里 + ImGui::Text("Aim Status"); + ImGui::Text("InRaid : %s", tarkov->IsInRaid() ? "YES" : "NO"); + ImGui::Text("Local : 0x%llX", (unsigned long long)local.m_address); + ImGui::Text("Entity : %zu", GetEntityList().size()); + ImGui::Text("Scan : %d ent / %d health / %d skip", m_diagTotal, m_diagHealth, m_diagSkipped); + ImGui::Text("Target : %s (d=%.0fpx)", m_diagBestAddr ? "LOCK" : "NONE", m_diagBestDist); + { + bool k0 = (GetAsyncKeyState(g.g_AimKey0) & 0x8000) != 0; + bool k1 = (GetAsyncKeyState(g.g_AimKey1) & 0x8000) != 0; + ImGui::Text("Key : %s", (k0 || k1) ? "HOLD" : "---"); + } + ImGui::EndChild(); ImGui::BeginChild("##C101", ImVec2(ImGui::GetContentRegionAvail()), true); - ImGui::Text("Rot Debug (Memory 検証)"); + ImGui::Text("Offset Debug (Memory)"); ImGui::Separator(); ImGui::Spacing(); if (tarkov->IsInRaid() && local.m_address != 0) { - uintptr_t mc = m.Read(local.m_address + offset::Player::pMovementContext); + uintptr_t mc = m.Read(local.m_address + (uintptr_t)g.g_AimMcOffset); if (mc) { - Vector2 rot = m.Read(mc + offset::MovementContext::Rotation); + Vector2 rot = m.Read(mc + (uintptr_t)g.g_AimRotOffset); ImGui::Text("yaw : %.3f", rot.x); ImGui::Text("pitch : %.3f", rot.y); ImGui::TextDisabled("Move mouse to verify the offset"); } else { - ImGui::TextDisabled("MovementContext null"); + ImGui::TextDisabled("MC null (bad g_AimMcOffset?)"); } } else @@ -208,6 +226,30 @@ void CFramework::RenderMenu() ImGui::TextDisabled("In Raid のみ表示"); } + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + ImGui::Text("Offset (hex)"); + ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Rot", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal); + + // 误改输入框会直接导致 Memory 模式失效, 一键恢复默认 + if (ImGui::Button("Reset MC/Rot (0x170/0x2C0)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f))) + { + g.g_AimMcOffset = 0x170; + g.g_AimRotOffset = 0x2C0; + } + + if (ImGui::Button("Scan Rotation (2-step)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f))) + ScanRotationOffset(); + + if (m_scanPhase == 1) + ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "Now turn the mouse (yaw) then click again"); + + if (m_scanResult[0]) + ImGui::TextWrapped("%s", m_scanResult); + ImGui::EndChild(); break; case 1: // Visual diff --git a/CFramework/Cheat/Visual.cpp b/CFramework/Cheat/Visual.cpp index 5f170ed..2603da2 100644 --- a/CFramework/Cheat/Visual.cpp +++ b/CFramework/Cheat/Visual.cpp @@ -1,4 +1,5 @@ #include "Framework.h" +#include bool InScreen(const Vector2& vIn) { @@ -14,6 +15,20 @@ void CFramework::RenderInfo() std::string InfoText{ "Tarkov PvE | " + std::to_string((int)ImGui::GetIO().Framerate) + "FPS" }; StringEx(ImVec2(8.f, 8.f), ImColor(1.f, 1.f, 1.f, 1.f), ImGui::GetFontSize(), InfoText.c_str()); + // --- 数据链路诊断: ESP/自瞄不工作时看这一行 --- + // Init = GOM 初始化(GameObjectManager offset 是否有效) + // Raid = 是否在局内(GameWorld 是否找到) + // Local = 本地玩家地址(0 说明 MainPlayer offset 失效) + // Ent = 实体列表数量(0 说明 RegisteredPlayers/PlayerBody offset 失效) + char diag[192]{}; + snprintf(diag, sizeof(diag), + "Init:%s Raid:%s Local:0x%llX Ent:%d", + tarkov->InitAddress() ? "OK" : "FAIL", + tarkov->IsInRaid() ? "Y" : "N", + (unsigned long long)local.m_address, + (int)GetEntityList().size()); + StringEx(ImVec2(8.f, 24.f), ImColor(1.f, 1.f, 0.f, 1.f), ImGui::GetFontSize(), diag); + // Crosshair if (g.g_Crosshair && !local.IsAiming()) { diff --git a/CFramework/Framework/Config/Config.h b/CFramework/Framework/Config/Config.h index ce00aa6..c21d703 100644 --- a/CFramework/Framework/Config/Config.h +++ b/CFramework/Framework/Config/Config.h @@ -46,6 +46,7 @@ struct Globals bool g_AimTargetPMC = true; bool g_AimTargetScav = true; bool g_AimTargetBoss = true; + bool g_AimIgnoreHealth = true; // Mono版でHealthController offsetが違うと全員死亡扱いになる。true=判定スキップ bool g_AimDrawFov = true; // 显示 FOV 圈 bool g_AimDrawTarget = true; // 显示目标连线 @@ -54,6 +55,12 @@ struct Globals float g_RecoilStrength = 4.f; // 每次下拉的鼠标单位(mickeys) int g_RecoilInterval = 15; // 下拉间隔 [ms] + // Aim Offset (运行时可调, 默认 = Offset.h 的值) + // Memory 模式自瞄用: Player + g_AimMcOffset -> + g_AimRotOffset (Vector2 yaw/pitch) + // 内置扫描器 [Scan Rotation] 找到后会直接写到这里, 再复制进 Offset.h 固化 + int g_AimMcOffset = 0x170; + int g_AimRotOffset = 0x2C0; + // System bool g_StreamProof = false; bool g_Crosshair = true;