Skip to content

Commit

Permalink
feat(game/five): Add natives to operate with drawables through collec…
Browse files Browse the repository at this point in the history
…tions

Added analogues to natives like SET_PED_COMPONENT_VARIATION that use
collection name and local (relative to the start of collection) index
instead of the global index.

With the old natives indexes would often shift after Title update which
would make migration to the newer game build harder. With the collection
relative indexing this would not be a problem as the local indexes
remain stable.

The new natives basically provide interface to the base game
functionality that already existed but was unavailable before.
  • Loading branch information
Nobelium-cfx committed Aug 23, 2024
1 parent 09c04d4 commit 7dad6da
Show file tree
Hide file tree
Showing 21 changed files with 901 additions and 0 deletions.
389 changes: 389 additions & 0 deletions code/components/extra-natives-five/src/PedCollectionsNatives.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,389 @@
#include <StdInc.h>

#include <atArray.h>
#include <EntitySystem.h>
#include <Hooking.h>
#include <Local.h>
#include <ScriptEngine.h>
#include <Utils.h>

class CPedPropInfo
{
};

class CPedVariationInfo
{
};

class CPedVariationInfoCollection
{
public:
atArray<CPedVariationInfo*> m_infos;
// There are more fields after m_infos, but we don't care about them.
};

static uint32_t g_collectionInfoHashOffset;
static uint32_t g_dynamicEntityArchetypeOffset;
static uint32_t g_pedModelInfoVarInfoCollectionOffset;
static uint32_t g_variationInfoPropInfoOffset;

static hook::cdecl_stub<int(CPedVariationInfoCollection*, int, uint32_t, uint32_t)> g_GetGlobalDrawableIndex([]()
{
return hook::get_pattern("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 56 41 57 48 83 EC ? 0F B7 41 ? 33 F6 45 8B F1");
});

static hook::cdecl_stub<int(CPedVariationInfoCollection*, int, uint32_t, uint32_t)> g_GetGlobalPropIndex([]()
{
return hook::get_pattern("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 56 41 57 48 83 EC ? 33 FF 45 8B F1");
});

static hook::cdecl_stub<int(CPedVariationInfoCollection*, uint32_t, uint32_t)> g_GetDlcDrawableIdx([]()
{
return hook::get_call(hook::get_pattern("E8 ? ? ? ? 44 8B C3 48 8B 5D"));
});

static hook::cdecl_stub<int(CPedVariationInfoCollection*, uint32_t, uint32_t)> g_GetDlcPropIdx([]()
{
return hook::get_call(hook::get_pattern("E8 ? ? ? ? 45 33 F6 44 8B E8 44 38 75"));
});

static hook::cdecl_stub<uint8_t(CPedVariationInfo*, uint32_t)> g_GetMaxNumDrawables([]()
{
return hook::get_pattern("48 89 5C 24 ? 57 48 83 EC ? 8B DA 48 8B F9 E8 ? ? ? ? 48 85 C0 74 ? 8B D3");
});

static hook::cdecl_stub<uint8_t(CPedPropInfo*, uint32_t)> g_GetMaxNumProps([]()
{
return hook::get_pattern("48 89 5C 24 ? 0F B7 41 ? 45 33 C0 45 8B C8 45 8B D0 8B D8");
});

static hook::cdecl_stub<CPedVariationInfo*(CPedVariationInfoCollection*, uint32_t, uint32_t)> g_GetVariationInfoFromDrawableIdx([]()
{
return hook::get_pattern("48 8B C4 48 89 58 ? 48 89 68 ? 48 89 70 ? 48 89 78 ? 41 56 48 83 EC ? 33 DB 41 8B F0 8B EA 48 8B F9 66 3B 59 ? 73 ? 48 8B 0F");
});

static hook::cdecl_stub<CPedVariationInfo*(CPedVariationInfoCollection*, uint32_t, uint32_t)> g_GetVariationInfoFromPropIdx([]()
{
return hook::get_pattern("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 41 54 41 55 41 56 41 57 48 83 EC ? 0F B7 41 ? 33 DB");
});

static hook::cdecl_stub<const char*(CPedVariationInfo*)> g_GetCollectionName([]()
{
return hook::get_pattern("8B 51 ? 85 D2 75 ? 33 C0");
});

static const char* GetCollectionName(CPedVariationInfo* info)
{
const char* collectionName = g_GetCollectionName(info);
if (collectionName == nullptr)
{
// g_GetCollectionName returns nullptr for empty string which corresponds to the base game collection.
return "";
}
else
{
return collectionName;
}
}

static uint32_t GetCollectionHash(CPedVariationInfo* info)
{
return *(uint32_t*)((uintptr_t)info + g_collectionInfoHashOffset);
};

static CPedVariationInfoCollection* GetPedVariationInfoCollection(uint32_t pedId)
{
auto ped = rage::fwScriptGuid::GetBaseFromGuid(pedId);
if (!ped || !ped->IsOfType<CPed>())
{
return nullptr;
}

auto pedModelInfo = *(void**)((uintptr_t)ped + g_dynamicEntityArchetypeOffset);
return *(CPedVariationInfoCollection**)((uintptr_t)pedModelInfo + g_pedModelInfoVarInfoCollectionOffset);
}

static CPedVariationInfo* GetVariationInfoFromCollection(CPedVariationInfoCollection* self, const char* collectionName)
{
uint32_t collectionNameHash = HashString(collectionName);
for (int i = 0; i < self->m_infos.GetCount(); i++)
{
auto variationInfo = self->m_infos[i];
if (variationInfo && GetCollectionHash(variationInfo) == collectionNameHash)
{
return variationInfo;
}
}
return nullptr;
}

static int GetGlobalIndex(uint32_t pedId, int slotId, const char* collectionName, int localIndex, hook::cdecl_stub<int(CPedVariationInfoCollection*, int, uint32_t, uint32_t)> indexGetter)
{
auto variationInfoCollection = GetPedVariationInfoCollection(pedId);
if (!variationInfoCollection)
{
return -1;
}

auto variationInfo = GetVariationInfoFromCollection(variationInfoCollection, collectionName);
// Collection does not exist.
if (!variationInfo)
{
return -1;
}

uint32_t collectionNameHash = HashString(collectionName);
return indexGetter(variationInfoCollection, localIndex, slotId, collectionNameHash);
}

template<typename T>
static int VariadicGetArgument(fx::ScriptContext& context, fx::ScriptContextBuffer& newContext, int index)
{
newContext.Push(context.GetArgument<T>(index));
return index + 1;
}

template<typename T, typename... AdditionalArguments>
static void RedirectNativeCallWithGlobalIndex(fx::ScriptContext& context, hook::cdecl_stub<int(CPedVariationInfoCollection*, int, uint32_t, uint32_t)> indexGetter, uint64_t nativeIdentifier)
{
uint32_t pedId = context.GetArgument<uint32_t>(0);
int componentId = context.GetArgument<int>(1);
int globalIndex = GetGlobalIndex(pedId, componentId, context.CheckArgument<const char*>(2), context.GetArgument<int>(3), indexGetter);
if (globalIndex == -1)
{
if constexpr (!std::is_same<T, void>::value)
{
context.SetResult<T>(T());
}
return;
}

fx::ScriptContextBuffer newContext;
newContext.Push(pedId);
newContext.Push(componentId);
newContext.Push(globalIndex);
int index = 4;
((index = VariadicGetArgument<AdditionalArguments>(context, newContext, index)), ...);

fx::ScriptEngine::CallNativeHandler(nativeIdentifier, newContext);
if constexpr (!std::is_same<T, void>::value)
{
context.SetResult<T>(newContext.GetResult<T>());
}
}

static HookFunction hookFunction([]()
{
g_collectionInfoHashOffset = *hook::get_pattern<uint8_t>("8B 51 ? 85 D2 75 ? 33 C0", 2);
g_dynamicEntityArchetypeOffset = *hook::get_pattern<uint8_t>("4C 8B 61 ? 48 8B F9 4D 63 E9", 3);
g_pedModelInfoVarInfoCollectionOffset = *hook::get_pattern<uint32_t>("49 8B 8C 24 ? ? ? ? 49 8B E8", 4);
g_variationInfoPropInfoOffset = *hook::get_pattern<uint8_t>("48 83 C1 ? E8 ? ? ? ? 48 8D 7F", 3);

// Natives to iterate over all collections.
fx::ScriptEngine::RegisterNativeHandler("GET_PED_COLLECTIONS_COUNT", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
context.SetResult<int>(variationInfoCollection->m_infos.GetCount());
}
else
{
context.SetResult<int>(0);
}
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_COLLECTION_NAME", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
int index = context.GetArgument<int>(1);
if (variationInfoCollection && index >= 0 && index < variationInfoCollection->m_infos.GetCount())
{
auto variationInfo = variationInfoCollection->m_infos[context.GetArgument<int>(1)];
context.SetResult<const char*>(GetCollectionName(variationInfo));
}
else
{
context.SetResult<const char*>(nullptr);
}
});

// Natives to convert between global drawable/prop indices and local collections drawable/prop indices.
fx::ScriptEngine::RegisterNativeHandler("GET_PED_COLLECTION_NAME_FROM_DRAWABLE", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
auto variationInfo = g_GetVariationInfoFromDrawableIdx(variationInfoCollection, context.GetArgument<int>(1), context.GetArgument<int>(2));
if (variationInfo)
{
context.SetResult<const char*>(GetCollectionName(variationInfo));
return;
}
}
context.SetResult<const char*>(nullptr);
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_COLLECTION_NAME_FROM_PROP", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
auto variationInfo = g_GetVariationInfoFromPropIdx(variationInfoCollection, context.GetArgument<int>(1), context.GetArgument<int>(2));
if (variationInfo)
{
context.SetResult<const char*>(GetCollectionName(variationInfo));
return;
}
}
context.SetResult<const char*>(nullptr);
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_COLLECTION_LOCAL_INDEX_FROM_DRAWABLE", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
context.SetResult<int>(g_GetDlcDrawableIdx(variationInfoCollection, context.GetArgument<int>(1), context.GetArgument<int>(2)));
}
else
{
context.SetResult<int>(-1);
}
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_COLLECTION_LOCAL_INDEX_FROM_PROP", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
context.SetResult<int>(g_GetDlcPropIdx(variationInfoCollection, context.GetArgument<int>(1), context.GetArgument<int>(2)));
}
else
{
context.SetResult<int>(-1);
}
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_DRAWABLE_GLOBAL_INDEX_FROM_COLLECTION", [](fx::ScriptContext& context)
{
context.SetResult<int>(GetGlobalIndex(context.GetArgument<uint32_t>(0), context.GetArgument<int>(1), context.CheckArgument<const char*>(2), context.GetArgument<int>(3), g_GetGlobalDrawableIndex));
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_PROP_GLOBAL_INDEX_FROM_COLLECTION", [](fx::ScriptContext& context)
{
context.SetResult<int>(GetGlobalIndex(context.GetArgument<uint32_t>(0), context.GetArgument<int>(1), context.CheckArgument<const char*>(2), context.GetArgument<int>(3), g_GetGlobalPropIndex));
});

// Natives to set component/prop variation using collections.
fx::ScriptEngine::RegisterNativeHandler("SET_PED_COLLECTION_COMPONENT_VARIATION", [](fx::ScriptContext& context)
{
// Call SET_PED_COMPONENT_VARIATION using the globalPropIndex
RedirectNativeCallWithGlobalIndex<void, int, int>(context, g_GetGlobalDrawableIndex, 0x262B14F48D29DE80);
});
fx::ScriptEngine::RegisterNativeHandler("SET_PED_COLLECTION_PROP_INDEX", [](fx::ScriptContext& context)
{
// Call SET_PED_PROP_INDEX using the globalPropIndex
RedirectNativeCallWithGlobalIndex<void, int, bool>(context, g_GetGlobalPropIndex, 0x93376B65A266EB5F);
});
fx::ScriptEngine::RegisterNativeHandler("SET_PED_COLLECTION_PRELOAD_VARIATION_DATA", [](fx::ScriptContext& context)
{
// Call SET_PED_PRELOAD_VARIATION_DATA using the globalPropIndex
RedirectNativeCallWithGlobalIndex<void, int>(context, g_GetGlobalDrawableIndex, 0x39D55A620FCB6A3A);
});
fx::ScriptEngine::RegisterNativeHandler("SET_PED_COLLECTION_PRELOAD_PROP_DATA", [](fx::ScriptContext& context)
{
// Call SET_PED_PROP_INDEX using the globalPropIndex
RedirectNativeCallWithGlobalIndex<void, int>(context, g_GetGlobalPropIndex, 0x2B16A3BFF1FBCE49);
});

// Natives to get component/prop variation using collections.
fx::ScriptEngine::RegisterNativeHandler("GET_NUMBER_OF_PED_COLLECTION_DRAWABLE_VARIATIONS", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
auto variationInfo = GetVariationInfoFromCollection(variationInfoCollection, context.CheckArgument<const char*>(2));
if (variationInfo)
{
context.SetResult<int>(static_cast<int>(g_GetMaxNumDrawables(variationInfo, context.GetArgument<int>(1))));
return;
}
}
context.SetResult<int>(0);
});
fx::ScriptEngine::RegisterNativeHandler("GET_NUMBER_OF_PED_COLLECTION_PROP_DRAWABLE_VARIATIONS", [](fx::ScriptContext& context)
{
auto variationInfoCollection = GetPedVariationInfoCollection(context.GetArgument<uint32_t>(0));
if (variationInfoCollection)
{
auto variationInfo = GetVariationInfoFromCollection(variationInfoCollection, context.CheckArgument<const char*>(2));
if (variationInfo)
{
auto propInfo = (CPedPropInfo*)((uintptr_t)variationInfo + g_variationInfoPropInfoOffset);
context.SetResult<int>(static_cast<int>(g_GetMaxNumProps(propInfo, context.GetArgument<int>(1))));
return;
}
}
context.SetResult<int>(0);
});
fx::ScriptEngine::RegisterNativeHandler("GET_NUMBER_OF_PED_COLLECTION_TEXTURE_VARIATIONS", [](fx::ScriptContext& context)
{
// Call GET_NUMBER_OF_PED_TEXTURE_VARIATIONS using the globalPropIndex
RedirectNativeCallWithGlobalIndex<int>(context, g_GetGlobalDrawableIndex, 0x8F7156A3142A6BAD);
});
fx::ScriptEngine::RegisterNativeHandler("GET_NUMBER_OF_PED_COLLECTION_PROP_TEXTURE_VARIATIONS", [](fx::ScriptContext& context)
{
// Call GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS using the globalPropIndex
RedirectNativeCallWithGlobalIndex<int>(context, g_GetGlobalPropIndex, 0xA6E7F1CEB523E171);
});
fx::ScriptEngine::RegisterNativeHandler("IS_PED_COLLECTION_COMPONENT_VARIATION_VALID", [](fx::ScriptContext& context)
{
// Call IS_PED_COMPONENT_VARIATION_VALID using the globalPropIndex
RedirectNativeCallWithGlobalIndex<bool, int>(context, g_GetGlobalDrawableIndex, 0xE825F6B6CEA7671D);
});
fx::ScriptEngine::RegisterNativeHandler("IS_PED_COLLECTION_COMPONENT_VARIATION_GEN9_EXCLUSIVE", [](fx::ScriptContext& context)
{
// Call IS_PED_COMPONENT_VARIATION_GEN9_EXCLUSIVE using the globalPropIndex
RedirectNativeCallWithGlobalIndex<bool>(context, g_GetGlobalDrawableIndex, 0xC767B581);
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_DRAWABLE_VARIATION_COLLECTION_LOCAL_INDEX", [](fx::ScriptContext& context)
{
uint32_t pedId = context.GetArgument<uint32_t>(0);
int componentId = context.GetArgument<int>(1);
auto variationInfoCollection = GetPedVariationInfoCollection(pedId);
if (!variationInfoCollection)
{
context.SetResult<int>(-1);
return;
}

fx::ScriptContextBuffer newContext;
newContext.Push(pedId);
newContext.Push(componentId);
// Call GET_PED_DRAWABLE_VARIATION to get global drawable index.
fx::ScriptEngine::CallNativeHandler(0x67F3780DD425D4FC, newContext);
int globalDrawableIndex = newContext.GetResult<int>();
context.SetResult<int>(g_GetDlcDrawableIdx(variationInfoCollection, componentId, globalDrawableIndex));
});
fx::ScriptEngine::RegisterNativeHandler("GET_PED_DRAWABLE_VARIATION_COLLECTION_NAME", [](fx::ScriptContext& context)
{
uint32_t pedId = context.GetArgument<uint32_t>(0);
int componentId = context.GetArgument<int>(1);
auto variationInfoCollection = GetPedVariationInfoCollection(pedId);
if (!variationInfoCollection)
{
context.SetResult<const char*>(nullptr);
return;
}

fx::ScriptContextBuffer newContext;
newContext.Push(pedId);
newContext.Push(componentId);
// Call GET_PED_DRAWABLE_VARIATION to get global drawable index.
fx::ScriptEngine::CallNativeHandler(0x67F3780DD425D4FC, newContext);
int globalDrawableIndex = newContext.GetResult<int>();
auto variationInfo = g_GetVariationInfoFromDrawableIdx(variationInfoCollection, componentId, globalDrawableIndex);
if (!variationInfo)
{
context.SetResult<const char*>(nullptr);
return;
}

context.SetResult<const char*>(GetCollectionName(variationInfo));
});
});
Loading

6 comments on commit 7dad6da

@utkuali
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nobelium-cfx hey, do these new component and prop natives work? I have tested a lot and it seems it's not working properly for props

for example:

RegisterCommand("getP", (s, args, r) => {
    const ped = PlayerPedId()
    const gender = GetEntityModel(ped) === GetHashKey("mp_m_freemode_01") ? "male" : "female"

    for (let i = 0; i < 12; i++) {
        const collection = GetPedCollectionNameFromProp(ped, i)
        const drawid = GetPedCollectionLocalIndexFromProp(ped, i)
        const textid = GetPedPropTextureIndex(ped, i)
        console.log(`PROPID[${i}] = ${collection}-${gender === "male" ? "M" : "F"}_P_${i}_${drawid}_${textid}`)
    }
})

outputs when I don't wear anything:
PROPID[0] = -F_P_0_0_-1
PROPID[1] = -F_P_1_0_-1
PROPID[2] = mp_f_luxe_01-F_P_2_0_-1
PROPID[3] = null-F_P_3_0_-1
PROPID[4] = null-F_P_4_0_-1
PROPID[5] = null-F_P_5_0_-1
PROPID[6] = Female_freemode_business-F_P_6_0_-1
PROPID[7] = mp_f_luxe_02-F_P_7_0_-1
PROPID[8] = null-F_P_8_0_-1
PROPID[9] = null-F_P_9_0_-1
PROPID[10] = null-F_P_10_0_-1
PROPID[11] = null-F_P_11_0_-1

outputs when I wear this hat:
PROPID[0] = -F_P_0_0_20
PROPID[1] = -F_P_1_0_-1
PROPID[2] = mp_f_luxe_01-F_P_2_0_-1
PROPID[3] = null-F_P_3_0_-1
PROPID[4] = null-F_P_4_0_-1
PROPID[5] = null-F_P_5_0_-1
PROPID[6] = Female_freemode_business-F_P_6_0_-1
PROPID[7] = mp_f_luxe_02-F_P_7_0_-1
PROPID[8] = null-F_P_8_0_-1
PROPID[9] = null-F_P_9_0_-1
PROPID[10] = null-F_P_10_0_-1
PROPID[11] = null-F_P_11_0_-1

weird that only textid changes and no collection name outputs. Info about hat:
and if I'm not mistaken collection name should be "mp_f_gunrunning_01"

"NameHash": "DLC_MP_GR_F_PHEAD_3_20",
"AnchorPoint": "ANCHOR_HEAD",
"ComponentId": 0,
"DrawableId": 105,
"TextureId": 20,
"RelativeCollectionDrawableId": 3,

thanks a lot ❤

@AvarianKnight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be helpful to include the game version you were using too

@utkuali
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure. b3285 (Beta) onesync infinity

@utkuali
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey just an update, it seems that I've made a mistake on the custom data side. "DrawableId" field is global index not the local collection index. "RelativeCollectionDrawableId" is the correct localindex. Everything works fine when I used local index instead of global index in the code.

@Nobelium-cfx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was in a process of testing if I could reproduce the error :). Happy to hear everything works now! Let me know if you need any other help with this.

@utkuali
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the fast answer, there seems the be problem again, but I will make sure it's not my mistake like before. I will comment here if I can't figure it out

Please sign in to comment.