fix: update offsets from dump

This commit is contained in:
kedou 2026-08-07 20:42:19 +08:00
parent 4dca333267
commit 52454e23ea
11 changed files with 278 additions and 160 deletions

View File

@ -365,8 +365,8 @@ void CFramework::ScanRotationOffset()
s_cands.clear();
m_scanResult[0] = 0;
// Player 構造 [0x100, 0x700] 内の 8byte アライン済みポインタ
for (uintptr_t mcOff = 0x100; mcOff <= 0x700; mcOff += 0x8)
// Player 构造 [0x20, 0x700] 内的 8byte アライン済みポインタ
for (uintptr_t mcOff = 0x20; mcOff <= 0x700; mcOff += 0x8)
{
uintptr_t ptr = m.Read<uintptr_t>(local.m_address + mcOff);
if (!ptr)
@ -392,7 +392,7 @@ void CFramework::ScanRotationOffset()
if (s_cands.empty())
{
strcpy_s(m_scanResult, "未找到候选,请扩大扫描范围 (0x100-0x700)");
strcpy_s(m_scanResult, "未找到候选,请扩大扫描范围 (0x20-0x700)");
m_scanPhase = 0;
return;
}

View File

@ -102,6 +102,9 @@ private:
}
void HealthBar(float x, float y, float w, float h, int value, int v_max)
{
if (v_max <= 0)
return;
RectFilled(x, y, x + w, y + h, ImColor(0.f, 0.f, 0.f, 0.725f), 0.f, 0);
RectFilled(x, y, x + w, y + ((h / float(v_max)) * (float)value), ImColor(min(510 * (v_max - value) / 100, 255), min(510 * value / 100, 255), 25, 255), 0.0f, 0);
}

View File

@ -234,11 +234,11 @@ void CFramework::RenderMenu()
ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal);
ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal);
// 误改输入框会直接导致 Memory 模式失效, 一键恢复本客户端实测默认值
if (ImGui::Button("重置偏移 (0x1E0/0x3AC)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))
// dump.txt: MovementContext = Player +0x58, Rotation_1 = MovementContext +0x27C
if (ImGui::Button("重置偏移 (0x58/0x27C)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))
{
g.g_AimMcOffset = 0x1E0;
g.g_AimRotOffset = 0x3AC;
g.g_AimMcOffset = 0x58;
g.g_AimRotOffset = 0x27C;
}
if (ImGui::Button("扫描旋转偏移(两步)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f)))

View File

@ -4,83 +4,130 @@ std::vector<int> PlayerBoneList{ BONE_PELVIS, BONE_LEFT_THIGH, BONE_LEFT_FOOT, B
bool CPlayer::Update()
{
// Position
m_vecOrigin = GetBonePosition(BONE_ORIGIN);
if (Vec3_Empty(m_vecOrigin))
return false;
return true;
return !Vec3_Empty(m_vecOrigin);
}
void CPlayer::UpdateStatic()
{
// Health
uintptr_t m_pHealthController = m.Read<uintptr_t>(m_address + offset::Player::pHealthController); // HealthController
uintptr_t m_pHealthBody = m.Read<uintptr_t>(m_pHealthController + 0x68);
m_pBodyController = m.Read<uintptr_t>(m_pHealthBody + 0x18);
m_pHealthController = 0;
m_pProfile = 0;
m_pInfo = 0;
m_pSetting = 0;
m_pWeaponAnimation = 0;
m_iSpawnType = 0;
m_pBoneArray = {};
m_vecBoneList.clear();
// Bone
uintptr_t boneMatrix = m.ReadChain(m_address, { offset::Player::pPlayerBody, 0x30, 0x30, 0x10 });
m_pBoneArray = m.Read<CBoneArray>(boneMatrix + 0x20);
if (!m_address)
return;
// EFT.Player fields from dump.txt.
m_pHealthController = m.Read<uintptr_t>(m_address + offset::Player::pHealthController);
m_pWeaponAnimation = m.Read<uintptr_t>(m_address + offset::Player::pWeaponAnimation);
// Other pointer
m_pProfile = m.Read<uintptr_t>(m_address + offset::Player::pProfile);
m_pInfo = m.Read<uintptr_t>(m_pProfile + 0x40);
m_pSetting = m.Read<uintptr_t>(m_pInfo + 0x50);
if (m_pProfile)
{
m_pInfo = m.Read<uintptr_t>(m_pProfile + offset::Profile::pInfoClass);
if (m_pInfo)
{
m_pSetting = m.Read<uintptr_t>(m_pInfo + offset::InfoClass::profileInfoSettingsClass);
if (m_pSetting)
m_iSpawnType = m.Read<int>(m_pSetting + 0x10); // ProfileInfoSettingsClass.Role
}
}
m.Read<uintptr_t>(m_address + offset::Player::pWeaponAnimation);
// Player data
m_iSpawnType = m.Read<int>(m_pSetting + 0x10); // SpawnType
// PlayerBody +0x30 = SkeletonRootJoint; Skeleton +0x30 = values List;
// List +0x10 = Transform[] data pointer, as described by dump.txt.
const uintptr_t boneMatrix = m.ReadChain(m_address, { offset::Player::pPlayerBody, 0x30, 0x30, 0x10 });
if (boneMatrix)
m_pBoneArray = m.Read<CBoneArray>(boneMatrix + 0x20);
}
void CPlayer::UpdateHealth()
{
float h{ 0 };
float max{ 0 };
m_fHealth = 0.f;
m_fHealthMax = 0.f;
for (int j = 0; j < 7; j++)
if (!m_address)
return;
const uintptr_t healthController = m_pHealthController != 0
? m_pHealthController
: m.Read<uintptr_t>(m_address + offset::Player::pHealthController);
if (!healthController)
return;
// GClass3009<T>.Dictionary_0_1 contains BodyPartState<T> entries.
const uintptr_t bodyParts = m.Read<uintptr_t>(healthController + offset::HealthController::BodyParts);
if (!bodyParts)
return;
const uintptr_t entries = m.Read<uintptr_t>(bodyParts + offset::HealthController::DictionaryEntries);
const int entryCount = m.Read<int>(bodyParts + offset::HealthController::DictionaryCount);
if (!entries || entryCount <= 0 || entryCount > 64)
return;
for (int i = 0; i < entryCount; ++i)
{
uintptr_t body_part = m.Read<uintptr_t>(m_pBodyController + 0x30 + (j * 0x18));
uintptr_t health_container = m.Read<uintptr_t>(body_part + 0x10);
h += m.Read<float>(health_container + 0x10);
max += m.Read<float>(health_container + 0x14);
}
const uintptr_t entry = entries + offset::HealthController::ArrayData
+ static_cast<uintptr_t>(i) * offset::HealthController::EntryStride;
const uintptr_t bodyPartState = m.Read<uintptr_t>(entry + offset::HealthController::EntryValue);
if (!bodyPartState)
continue;
m_fHealth = h;
m_fHealthMax = max;
const uintptr_t healthValue = m.Read<uintptr_t>(bodyPartState + offset::HealthController::BodyPartHealth);
if (!healthValue)
continue;
m_fHealth += m.Read<float>(healthValue + offset::HealthController::HealthValue + offset::HealthController::Current);
m_fHealthMax += m.Read<float>(healthValue + offset::HealthController::HealthValue + offset::HealthController::Maximum);
}
}
void CPlayer::UpdateBone()
{
m_vecHeadOrigin = GetBonePosition(BONE_HEAD);
m_vecNeckOrigin = GetBonePosition(BONE_NECK);
if (g.g_ESP_Skeleton)
{
for (auto& id : PlayerBoneList)
{
Vector3 bone = GetBonePosition(id);
if (Vec3_Empty(bone)) {
m_vecBoneList.clear();
break;
if (!g.g_ESP_Skeleton)
return;
m_vecBoneList.reserve(PlayerBoneList.size());
for (const auto id : PlayerBoneList)
{
const Vector3 bone = GetBonePosition(id);
if (Vec3_Empty(bone))
{
m_vecBoneList.clear();
return;
}
m_vecBoneList.push_back(bone);
}
}
}
bool CPlayer::IsAiming()
{
return m.Read<bool>(m_pWeaponAnimation + offset::WeaponAnimations::isAiming);
return m_pWeaponAnimation != 0 && m.Read<bool>(m_pWeaponAnimation + offset::WeaponAnimations::isAiming);
}
Vector3 CPlayer::GetBonePosition(const int BoneId)
{
uintptr_t TransformPtr = m.Read<uintptr_t>(m_pBoneArray.address[BoneId] + 0x10);
constexpr int kBoneCount = static_cast<int>(sizeof(m_pBoneArray.address) / sizeof(m_pBoneArray.address[0]));
if (BoneId < 0 || BoneId >= kBoneCount)
return {};
return GetTransformPosition(TransformPtr);
const uintptr_t bone = m_pBoneArray.address[BoneId];
if (!bone)
return {};
uintptr_t transform = m.Read<uintptr_t>(bone + 0x10);
if (!transform)
return {};
return GetTransformPosition(transform);
}

View File

@ -13,7 +13,7 @@ public:
uintptr_t m_pProfile{ 0 };
uintptr_t m_pInfo{ 0 };
uintptr_t m_pSetting{ 0 };
uintptr_t m_pBodyController{ 0 };
uintptr_t m_pHealthController{ 0 };
uintptr_t m_pWeaponAnimation{ 0 };
// データ

View File

@ -21,6 +21,9 @@ float GetDistance(Vector3 value1, Vector3 value2)
Vector3 GetTransformPosition(uintptr_t& transform)
{
if (!transform)
return {};
__m128 result{};
const __m128 mulVec0 = { -2.000, 2.000, -2.000, 0.000 };
@ -29,6 +32,9 @@ Vector3 GetTransformPosition(uintptr_t& transform)
TransformAccessReadOnly pTransformAccessReadOnly = m.Read<TransformAccessReadOnly>(transform + 0x38);
unsigned int Index = m.Read<unsigned int>(transform + 0x40);
if (!pTransformAccessReadOnly.pTransformData)
return {};
TransformData transform_data = m.Read<TransformData>(pTransformAccessReadOnly.pTransformData + 0x18);
if (transform_data.pTransformArray && transform_data.pTransformIndices)
@ -96,6 +102,9 @@ bool WorldToScreen(Matrix ViewMatrix, const Vector2& GameSize, Vector3& vIn, Vec
uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const char* objectName)
{
if (!listPtr || !objectName)
return 0;
char ObjectName[128]{};
uintptr_t classNamePtr = 0;
BaseObject activeObject = m.Read<BaseObject>(listPtr);
@ -111,7 +120,8 @@ uintptr_t GetObjectFromList(uintptr_t listPtr, uintptr_t lastObjectPtr, const ch
if (strcmp(ObjectName, objectName) == 0)
return activeObject.object;
Sleep(1);
if (!activeObject.nextObjectLink)
break;
activeObject = m.Read<BaseObject>(activeObject.nextObjectLink);
}
@ -135,13 +145,22 @@ bool CGame::InitAddress()
{
// GameObjectManager
uintptr_t pGOM = m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwGameObjectManager);
if (!pGOM)
{
m_objGameObjectManager = {};
return false;
}
m_objGameObjectManager = m.Read<GameObjectManager>(pGOM);
return m_objGameObjectManager.lastActiveObject != NULL ? true : false;
return m_objGameObjectManager.lastActiveObject != 0;
}
bool CGame::Update()
{
m_pGameWorld = 0;
m_pLocalGameWorld = 0;
// GameWorld
uintptr_t activeNodes = m.Read<uintptr_t>(m_objGameObjectManager.ActiveNodes);
uintptr_t lastActiveNode = m.Read<uintptr_t>(m_objGameObjectManager.LastActiveNode);
@ -161,21 +180,30 @@ bool CGame::Update()
bool CGame::UpdateCamera()
{
Camera all_cameras = m.Read<Camera>(m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwAllCamera));
m_pFpsCamera = 0;
if (all_cameras.count == 0)
uintptr_t all_camera_ptr = m.Read<uintptr_t>(m.m_gBaseAddress + offset::dwAllCamera);
if (!all_camera_ptr)
return false;
Camera all_cameras = m.Read<Camera>(all_camera_ptr);
if (all_cameras.list == 0 || all_cameras.count == 0)
return false;
for (int i = 0; i < all_cameras.count; i++)
{
uintptr_t camera = m.Read<uintptr_t>(all_cameras.list + (i * 0x8));
if (camera != NULL)
if (camera != 0)
{
uintptr_t camera_obj = m.Read<uintptr_t>(camera + 0x30);
if (!camera_obj)
continue;
uintptr_t camera_name_ptr = m.Read<uintptr_t>(camera_obj + 0x60);
if (!camera_name_ptr)
return false;
continue;
char name[64]{};
m.ReadString(camera_name_ptr, name, sizeof(name));
@ -197,9 +225,17 @@ bool CGame::IsInRaid()
Matrix CGame::GetViewMatrix()
{
if (!m_pFpsCamera)
return {};
// ここ省略できるかチェック
uintptr_t dw = m.Read<uintptr_t>(m_pFpsCamera + 0x30);
if (!dw)
return {};
uintptr_t viewmatrix_ptr = m.Read<uintptr_t>(dw + 0x18);
if (!viewmatrix_ptr)
return {};
return m.Read<Matrix>(viewmatrix_ptr + 0x100);
}

View File

@ -1,65 +1,72 @@
#pragma once
#pragma once
// by Unispect and UC
namespace offset
{
// Base
// Base (not present in dump.txt; keep the native UnityPlayer values used by this build)
constexpr auto dwGameObjectManager = 0x1CF93E0;
constexpr auto dwAllCamera = 0x1BF8BC0;
// EFT.GameWorld
// EFT.GameWorld / EFT.ClientLocalGameWorld
constexpr auto ExfilController = 0x30;
constexpr auto LootList = 0x118;
constexpr auto RegisteredPlayers = 0x140;
constexpr auto MainPlayer = 0x1A8;
constexpr auto GrenadeList = 0x200;
// EFT.Player
// EFT.Player (dump.txt: EFT.Player)
namespace Player
{
constexpr auto pPlayerBody = 0xC0;
constexpr auto pMovementContext = 0x1E0; // EXFIL DELTA (Mono 0.16.9) 实测. IL2CPP 0.15.x 是 0x170
constexpr auto pWeaponAnimation = 0x1E0; // IL2CPP 版定义, Mono 版实际是 MovementContext (框架旧代码保留)
constexpr auto pProfile = 0x600;
constexpr auto pPhysics = 0x610;
constexpr auto pHealthController = 0x640;
constexpr auto pPlayerBody = 0xC0; // _playerBody
constexpr auto pMovementContext = 0x58; // <MovementContext>k__BackingField
constexpr auto pWeaponAnimation = 0x1E0; // <ProceduralWeaponAnimation>k__BackingField
constexpr auto pProfile = 0x618; // <Profile>k__BackingField
constexpr auto pPhysics = 0x630; // Physical
constexpr auto pHealthController = 0x660; // _healthController
}
// EFT.MovementContext
// EFT.MovementContext (dump.txt: EFT.MovementContext)
namespace MovementContext
{
// Vector2 (x = yaw, y = pitch) [rad]
// EXFIL DELTA (Mono 0.16.9) 实测: +0x3AC = Rotation_1 (视角旋转)
// +0x3A4 = MovementDirection_1 (移动方向, 勿用!) 依据 Mono.Cecil 字段表
constexpr auto Rotation = 0x3AC;
// Vector2 (x = yaw, y = pitch), in radians.
constexpr auto Rotation = 0x27C; // Rotation_1
}
// EFT.Profile
namespace Profile
{
constexpr auto Id = 0x10; // str
constexpr auto AccountId = 0x18; // str
constexpr auto pInfoClass = 0x40; // pointer
constexpr auto Id = 0x10;
constexpr auto AccountId = 0x18;
constexpr auto pInfoClass = 0x40; // Info
}
// EFT.Profile->InfoClass
// EFT.Profile -> InfoClass
namespace InfoClass
{
constexpr auto Nickname = 0x10; // str
constexpr auto EntryPoint = 0x18; // str
constexpr auto MainProfileNickname = 0x20; // str
constexpr auto MGroupId = 0x28; // str
constexpr auto TeamId = 0x30; // str
constexpr auto profileInfoSettingsClass = 0x50; // pointer
constexpr auto eplayerSide_0 = 0x90; // int
constexpr auto RegistrationDate = 0x94; // int
constexpr auto Type = 0x98; // int
constexpr auto Nickname = 0x10;
constexpr auto EntryPoint = 0x18;
constexpr auto MainProfileNickname = 0x20;
constexpr auto MGroupId = 0x28;
constexpr auto TeamId = 0x30;
constexpr auto profileInfoSettingsClass = 0x48; // ProfileInfoSettingsClass
constexpr auto eplayerSide_0 = 0x88;
constexpr auto RegistrationDate = 0x8C;
constexpr auto Type = 0x90;
}
// EFT.Player->.BasePhysicalClass
namespace Physics
// EFT.HealthSystem.ActiveHealthController / GClass3009<T>
namespace HealthController
{
constexpr auto BodyParts = 0x68; // Dictionary<int, BodyPartState<T>>
constexpr auto DictionaryEntries = 0x18;
constexpr auto DictionaryCount = 0x40;
constexpr auto ArrayData = 0x20;
constexpr auto EntryStride = 0x18;
constexpr auto EntryValue = 0x10;
constexpr auto BodyPartHealth = 0x10; // BodyPartState.Health
constexpr auto HealthValue = 0x10; // HealthValue.Value
constexpr auto Current = 0x00;
constexpr auto Maximum = 0x04;
}
// EFT.Animations.ProceduralWeaponAnimation
@ -74,7 +81,7 @@ namespace offset
{
constexpr auto Stamina = 0x38;
constexpr auto HandsStamina = 0x40;
constexpr auto Oxygen = 0x40;
constexpr auto Oxygen = 0x48;
constexpr auto FallDamageMultiplier = 0xBC;
constexpr auto OxygenRestoreRate = 0xD4;
}

View File

@ -91,12 +91,13 @@ void CFramework::UpdateStaticList() // C6262 :(
std::vector<CItem> list_item{};
std::vector<CExfil> list_exfil{};
const auto GameWorld = tarkov->m_pLocalGameWorld;
tarkov->UpdateCamera();
// ItemList
if (tarkov->Update())
{
const auto GameWorld = tarkov->m_pLocalGameWorld;
// Exfil
if (g.g_ESP_Exfil)
{

View File

@ -53,13 +53,11 @@ struct Globals
float g_RecoilStrength = 4.f; // 每次下拉的鼠标单位(mickeys)
int g_RecoilInterval = 15; // 下拉间隔 [ms]
// Aim Offset (运行时可调, 默认 = 本客户端实测值)
// Aim Offset (运行时可调, 由 F:\desktop\Release\dump.txt 校准)
// Memory 模式自瞄用: Player + g_AimMcOffset -> + g_AimRotOffset (Vector2 yaw/pitch)
// EXFIL DELTA (Mono 0.16.9) 实测: Player+0x1E0 = MovementContext
// +0x3A4 = MovementDirection(移动方向, 非视角!), +0x3AC = Rotation_1(视角旋转, 正解)
// 依据: Mono.Cecil 字段表 + 运行时验证 (0x3A4 读出的俯仰恒 0)
int g_AimMcOffset = 0x1E0;
int g_AimRotOffset = 0x3AC;
// EFT.Player: MovementContext = +0x58; EFT.MovementContext: Rotation_1 = +0x27C
int g_AimMcOffset = 0x58;
int g_AimRotOffset = 0x27C;
// System
bool g_StreamProof = false;

View File

@ -58,19 +58,24 @@ uintptr_t Memory::GetModuleBase(const std::string moduleName)
entry.dwSize = sizeof(MODULEENTRY32);
const auto snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PID);
while (Module32Next(snapShot, &entry))
if (snapShot == INVALID_HANDLE_VALUE)
return 0;
if (Module32First(snapShot, &entry))
{
do
{
if (!moduleName.compare(entry.szModule))
{
const auto base = reinterpret_cast<uintptr_t>(entry.modBaseAddr);
CloseHandle(snapShot);
return reinterpret_cast<uintptr_t>(entry.modBaseAddr);
return base;
}
} while (Module32Next(snapShot, &entry));
}
if (snapShot)
CloseHandle(snapShot);
return reinterpret_cast<uintptr_t>(entry.modBaseAddr);
return 0;
}
PROCESSENTRY32 Memory::GetProcess(const std::string processName)
@ -79,17 +84,22 @@ PROCESSENTRY32 Memory::GetProcess(const std::string processName)
entry.dwSize = sizeof(PROCESSENTRY32);
const auto snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
while (Process32Next(snapShot, &entry))
if (snapShot == INVALID_HANDLE_VALUE)
return PROCESSENTRY32();
if (Process32First(snapShot, &entry))
{
do
{
if (!processName.compare(entry.szExeFile))
{
CloseHandle(snapShot);
return entry;
}
} while (Process32Next(snapShot, &entry));
}
CloseHandle(snapShot);
return PROCESSENTRY32();
}

View File

@ -48,9 +48,17 @@ public:
}
uintptr_t ReadChain(uintptr_t address, const std::vector<uintptr_t>& offsets)
{
uintptr_t result = Read<uintptr_t>(address + offsets.at(0));
for (int i = 1; i < offsets.size(); i++)
result = Read<uintptr_t>(result + offsets.at(i));
if (!address || offsets.empty())
return 0;
uintptr_t result = Read<uintptr_t>(address + offsets.front());
for (std::size_t i = 1; i < offsets.size(); ++i)
{
if (!result)
return 0;
result = Read<uintptr_t>(result + offsets[i]);
}
return result;
}
@ -62,9 +70,17 @@ public:
}
std::string ReadString(uintptr_t address, int length)
{
static char Buffer[256]{};
ReadProcessMemory(pHandle, (void*)(address), Buffer, length, nullptr);
if (!address || length <= 0)
return {};
char Buffer[256]{};
if (length >= static_cast<int>(sizeof(Buffer)))
length = static_cast<int>(sizeof(Buffer)) - 1;
if (!ReadProcessMemory(pHandle, reinterpret_cast<void*>(address), Buffer, length, nullptr))
return {};
Buffer[length] = '\0';
return std::string(Buffer);
};
wchar_t* ReadWString(uintptr_t address, int length)