// decl — CBoneBitList + CIKContext, faithful to cs sdk
// public/bone_setup.h. added so the bone rebuild is self-contained.
using matrix3x4a_t = matrix3x4_t; // SDK: 16-byte-aligned matrix3x4_t
template <int NUM_BITS>
class CBitVec { // 256 bits -> m_Ints[8] = 32 bytes
public:
inline void Set(int b) { m_Ints[b >> 5] |= (1u << (b & 31)); }
inline bool IsBitSet(int b) { return (m_Ints[b >> 5] & (1u << (b & 31))) != 0; }
inline void ClearAll() { std::memset(m_Ints, 0, sizeof(m_Ints)); }
int m_Ints[(NUM_BITS + 31) >> 5];
};
class CBoneBitList : public CBitVec<MAXSTUDIOBONES> { // bone_setup.h:37
public:
CBoneBitList() { ClearAll(); }
inline void MarkBone(int i) { Set(i); }
inline bool IsBoneMarked(int i) { return IsBitSet(i); }
};
// IK rule payloads are reached through CUtlVector pointers, so a fwd decl suffices for size.
struct ikcontextikrule_t;
class CIKTarget { // bone_setup.h:127 — drives sizeof(CIKContext) (~340B each, x12 inline)
public:
int chain, type;
struct { char* pAttachmentName; Vector pos; Quaternion q; } offset;
struct { Vector pos; Quaternion q; } ideal;
struct { float latched, release, height, floor, radius, flTime, flWeight;
Vector pos; Quaternion q; bool onWorld; } est;
struct { float hipToFoot, hipToKnee, kneeToFoot;
Vector hip, closest, knee, farthest, lowest; } trace;
struct { bool bNeedsLatch, bHasLatch; float influence; int iFramecounter, owner;
Vector absOrigin; QAngle absAngles; Vector pos; Quaternion q;
Vector deltaPos; Quaternion deltaQ; Vector debouncePos; Quaternion debounceQ; } latched;
struct { float flTime, flErrorTime, ramp; bool bInError; } error;
};
class CIKContext { // bone_setup.h:275 — mercury stack-allocates one (v339, ~4208B)
public:
CUtlVectorFixed<CIKTarget, 12> m_target; // 12 CIKTargets inline (the fat member)
private:
CStudioHdr const* m_pStudioHdr;
CUtlVector<CUtlVector<ikcontextikrule_t>> m_ikChainRule;
CUtlVector<ikcontextikrule_t> m_ikLock;
matrix3x4a_t m_rootxform;
int m_iFramecounter;
float m_flTime;
int m_boneMask;
};
// Bone masks: BONE_USED_BY_HITBOX 0x100 | ATTACHMENT 0x200 | BONE_MERGE 0x80000 | BY_ANYTHING 0x7FF00.
// mercury: ik.Init mask = 0x300 (HITBOX|ATTACHMENT); StandardBlendingRules mask = 0xFFF00 (0x7FF00|0x80000). [C]
constexpr int IK_INIT_MASK = 0x300;
constexpr int BLEND_MASK = 0xFFF00;
constexpr int MAXSTUDIOBONES = 256; // mercury sizes pos[]/q[] to 256 [C]
// C_BaseAnimating::m_pIk == entity + 0x2660. [C] StandardBlendingRules reads [this+0x2660]
// (client.dll+0x19AAA4). == m_nForceBone(0x267C) - 0x1C. NB: SetupBones shows +0x265C ONLY because it is an
// IClientRenderable INTERFACE method (this = main+4 thunk) — DO NOT use 0x265C from the main base.
constexpr int m_pIk = 0x2660;
constexpr int m_nForceBone = 0x267C; // [C] RecvProp
// mercury sub_36AF9410 = fatality setupbones pasted
bool BuildBones_Mercury(Player* target, matrix3x4a_t* out, LagRecord* rec) {
CStudioHdr* hdr = target->GetModelPtr();
if (!hdr || hdr->numbones() > MAXSTUDIOBONES) return false;
// sigs: same-build client.dll (PE ts 0x5AA06961), reloc-wildcarded, each unique in .text.
using SBR_t = void(__thiscall*)(void*, CStudioHdr*, Vector*, QuaternionAligned*, float, int); // StandardBlendingRules
using Init_t = void(__thiscall*)(void*, const CStudioHdr*, const QAngle&, const Vector&, float, int, int); // CIKContext::Init
using Bone_t = void(__thiscall*)(void*, Vector*, Quaternion*, matrix3x4a_t*, CBoneBitList&); // UpdateTargets/SolveDependencies
static auto StandardBlendingRules = g_memory.pattern_scan("client.dll",
"55 8B EC 83 E4 F0 B8 F8 10 00 00 E8 ? ? ? ? 56 8B 75 08").as<SBR_t>(); // +0x19A950
static auto CIKContext_Init = g_memory.pattern_scan("client.dll",
"55 8B EC 83 EC 08 8B 45 08 56 57 8B F9 8D 8F FC 0F 00 00 89 7D F8").as<Init_t>(); // +0x5E9580
static auto CIKContext_UpdateTargets = g_memory.pattern_scan("client.dll",
"55 8B EC 83 E4 F0 81 EC 18 01 00 00 33 D2 89 4C 24 40 56 57").as<Bone_t>(); // +0x5EA3B0
static auto CIKContext_SolveDependencies = g_memory.pattern_scan("client.dll",
"55 8B EC 83 E4 F0 81 EC A8 05 00 00 8B 81 F8 0F 00 00 56 57").as<Bone_t>(); // +0x5EBA10
alignas(16) Vector pos[MAXSTUDIOBONES];
alignas(16) QuaternionAligned q [MAXSTUDIOBONES];
CBoneBitList computed; // uint8_t[0x100] bone-computed bitlist
alignas(16) uint8_t ik_storage[sizeof(CIKContext)]; // stack CIKContext (mercury v339)
CIKContext* ik = reinterpret_cast<CIKContext*>(ik_storage);
new (ik) CIKContext();
const float time = rec->m_anim_time; // mercury: anim time
target->AddEffect(EF_NOINTERP);
// 1) IK init:
CIKContext_Init(ik, hdr, rec->m_abs_ang, rec->m_pred_origin,
time, g_csgo.m_globals->m_framecount, IK_INIT_MASK); // +0x300
*reinterpret_cast<CIKContext**>((uintptr_t)target + m_pIk) = ik; // player->m_pIk = &ik (0x2660)
// 2) blend, then IK solve (mercury calls the real client.dll fns through the resolved ptrs):
StandardBlendingRules(target, hdr, pos, q, time, BLEND_MASK); // 0xFFF00
CIKContext_UpdateTargets(ik, pos, q, out, computed);
CIKContext_SolveDependencies(ik, pos, q, out, computed);
// 3) inline StudioBuildMatrices(BuildTransformations) (mercury 00AA9410.c:347-416+): reversed chain + parent concat.
int chain[MAXSTUDIOBONES]; int n = hdr->numbones();
for (int i = 0; i < n; ++i) chain[n - i - 1] = i;
matrix3x4_t rotation = math::angle_matrix(rec->m_abs_ang, rec->m_pred_origin);
for (int j = n - 1; j >= 0; --j) {
int i = chain[j], parent = hdr->boneParent(i);
if (!(hdr->boneFlags(i) & BLEND_MASK)) continue;
matrix3x4_t bm = math::quaternion_matrix(q[i], pos[i]);
out[i] = (parent == -1) ? math::concat_transforms(rotation, bm)
: math::concat_transforms(out[parent], bm);
}
target->RemoveEffect(EF_NOINTERP);
rec->m_setup = true; // bones now cached in rec->m_bones
return true;
}