Skip to content

Instantly share code, notes, and snippets.

@ikt32
Last active August 16, 2022 09:40
Show Gist options
  • Save ikt32/466c59df7aff69d2ac788fa38e669300 to your computer and use it in GitHub Desktop.
Save ikt32/466c59df7aff69d2ac788fa38e669300 to your computer and use it in GitHub Desktop.
Get unsafe struct from GTA Native function
using System;
using System.Runtime.InteropServices;
using GTA;
using GTA.Native;
// shvdn2
public static class SHVNative
{
// These are borrowed from ScriptHookVDotNet's
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeInit@@YAX_K@Z")]
static extern void NativeInit(ulong hash);
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativePush64@@YAX_K@Z")]
static extern void NativePush64(ulong val);
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeCall@@YAPEA_KXZ")]
static extern unsafe ulong* NativeCall();
// These are from ScriptHookV's nativeCaller.h
static unsafe void nativePush<T>(T val) where T: unmanaged
{
ulong val64 = 0;
*(T*)(&val64) = val;
NativePush64(val64);
}
public static unsafe R invoke<R>(ulong hash) where R : unmanaged
{
NativeInit(hash);
return *(R*)(NativeCall());
}
// Apparently this works, but might be less efficient than C++'s variadic things
public static unsafe R invoke<R>(ulong hash, params dynamic[] args)
where R : unmanaged
{
NativeInit(hash);
foreach(var arg in args)
nativePush(arg);
return *(R*)(NativeCall());
}
}
public class HeadBlendDataTest : Script
{
[StructLayout(LayoutKind.Explicit, Size = 80)]
public struct HeadBlendData
{
[FieldOffset(0)]
public int ShapeFirst;
[FieldOffset(8)]
public int ShapeSecond;
[FieldOffset(16)]
public int ShapeThird;
[FieldOffset(24)]
public int SkinFirst;
[FieldOffset(32)]
public int SkinSecond;
[FieldOffset(40)]
public int SkinThird;
[FieldOffset(48)]
public float ShapeMix;
[FieldOffset(56)]
public float SkinMix;
[FieldOffset(64)]
public float ThirdMix;
}
public HeadBlendDataTest()
{
Tick += OnTick;
lastPed = Game.Player.Character;
}
private Ped lastPed;
void OnTick(object sender, EventArgs e)
{
// just a sanity check for invoke
ShowText(0.5f, 0.00f, SHVNative.invoke<int>((ulong)Hash.GET_GAME_TIMER).ToString());
Ped playerPed = Game.Player.Character;
if (playerPed != lastPed)
{
Function.Call(Hash.SET_PED_HEAD_BLEND_DATA, playerPed, 1, 2, 3, 4, 5, 6, 1.0f, 2.0f, 3.0f);
}
var hbd = GetHeadBlendData(playerPed);
ShowText(0.5f, 0.250f, hbd.ShapeFirst.ToString());
ShowText(0.5f, 0.275f, hbd.ShapeSecond.ToString());
ShowText(0.5f, 0.300f, hbd.ShapeThird.ToString());
ShowText(0.5f, 0.325f, hbd.SkinFirst.ToString());
ShowText(0.5f, 0.350f, hbd.SkinSecond.ToString());
ShowText(0.5f, 0.375f, hbd.SkinThird.ToString());
ShowText(0.5f, 0.400f, hbd.ShapeMix.ToString());
ShowText(0.5f, 0.425f, hbd.SkinMix.ToString());
ShowText(0.5f, 0.450f, hbd.ThirdMix.ToString());
}
public unsafe HeadBlendData GetHeadBlendData(Ped ped)
{
HeadBlendData hbd = new HeadBlendData() {ShapeFirst = -1};
HeadBlendData* pHbd = &hbd;
bool result = SHVNative.invoke<bool>((ulong)Hash._GET_PED_HEAD_BLEND_DATA, ped.Handle, (IntPtr)pHbd);
// result handling might be useful
return *pHbd;
}
void ShowText(float x, float y, string text, float size = 0.2f)
{
Function.Call(Hash.SET_TEXT_FONT, 0);
Function.Call(Hash.SET_TEXT_SCALE, size, size);
Function.Call(Hash.SET_TEXT_COLOUR, 255, 255, 255, 255);
Function.Call(Hash.SET_TEXT_WRAP, 0.0, 1.0);
Function.Call(Hash.SET_TEXT_CENTRE, 0);
Function.Call(Hash.SET_TEXT_OUTLINE, true);
Function.Call(Hash._SET_TEXT_ENTRY, "STRING");
Function.Call(Hash._ADD_TEXT_COMPONENT_STRING, text);
Function.Call(Hash._DRAW_TEXT, x, y);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment