diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md
new file mode 100644
index 000000000..1ef184b0c
--- /dev/null
+++ b/CONTRIBUTE.md
@@ -0,0 +1,64 @@
+# How to Contribute
+
+## Implementing [cpp-sdk](https://github.com/altmp/cpp-sdk) Changes
+
+To implement a new `cpp-sdk` change or add a missing implementation, follow these steps:
+
+1. **Create a New Branch**
+ - Start by creating a new branch based on the [dev](https://github.com/altmp/coreclr-module/tree/dev) branch.
+ - In the submodule `runtime`, create another branch also based on the [dev](https://github.com/altmp/coreclr-module-runtime/tree/dev) branch.
+
+2. **Update the cpp-sdk Version**
+ - If required, update the `cpp-sdk` version in the `runtime` branch.
+
+3. **Add the cpp-sdk Method**
+ - Add the new `cpp-sdk` method to the appropriate classes.
+ For example, to implement [IPlayer.GetSocialClubId()](https://github.com/altmp/cpp-sdk/blob/30b5e35ab7081f7e8ff7ac2bc0568aa7cf38e6be/objects/IPlayer.h#L90C20-L90C31), update the following runtime classes:
+ - [player.h](https://github.com/altmp/coreclr-module-runtime/blob/dev/c-api/entities/player.h)
+ - [player.cpp](https://github.com/altmp/coreclr-module-runtime/blob/dev/c-api/entities/player.cpp)
+
+4. **Run the CApi Generator**
+ - Run the [CApi Generator](https://github.com/altmp/coreclr-module/blob/dev/api/AltV.Net.CApi.Generator/Program.cs) with the `runtime/c-api` folder as the working directory.
+
+5. **Push Changes**
+ - Push all changes to the `runtime` branch.
+
+6. **Implement the Method in the C# Module**
+ - Use the `runtime` branch to implement the method within the C# module.
+ For the example above, add the `SocialClubId` as a getter to the `IPlayer` interface.
+
+7. **Add the Getter Implementation**
+ - Implement the getter in the `Player` class to invoke the unsafe runtime call `Core.Library.*`.
+
+8. **Extend the AsyncPlayer Class**
+ - Add the method to the `AsyncPlayer` class, ensuring it calls the parent method from the `Player` class.
+
+9. **Push Changes**
+ - Push all changes to the `module` branch.
+
+10. **Testing**
+ - Open powershell window, change directory to root module folder. Run `gen_local_win_env.ps1` and wait everything downloaded.
+ - To test the changes, use the `windows-build.bat` file in the `runtime/server` folder to generate a `coreclr-module.dll`.
+ - Place the generated DLL in the server's `modules` folder.
+ - Build the module project and copy the new module DLLs to the C# resource folder and the project directory of the resource.
+ ```
+
+
+ lib\AltV.Net.dll
+
+
+ lib\AltV.Net.Async.dll
+
+
+ lib\AltV.Net.Interactions.dll
+
+
+ PreserveNewest
+
+
+ ```
+ - Test your changes thoroughly.
+ - **Clientside cannot be tested yet**
+
+11. **Submit Pull Requests**
+ - Create separate pull requests for both the `module` branch and the `runtime` branch.
diff --git a/api/AltV.Net.Async/AltAsync.RegisterEvents.cs b/api/AltV.Net.Async/AltAsync.RegisterEvents.cs
index a9b9eb01e..d72d844fe 100644
--- a/api/AltV.Net.Async/AltAsync.RegisterEvents.cs
+++ b/api/AltV.Net.Async/AltAsync.RegisterEvents.cs
@@ -355,11 +355,11 @@ public static void RegisterEvents(object target)
new[]
{
typeof(IPlayer), typeof(IEntity), typeof(uint), typeof(ushort),
- typeof(Position), typeof(BodyPart)
+ typeof(Position), typeof(BodyPart), typeof(IEntity)
}, isAsync: true);
if (scriptFunction == null) return;
OnWeaponDamage +=
- (player, targetEntity, weapon, damage, shotOffset, damageOffset) =>
+ (player, targetEntity, weapon, damage, shotOffset, damageOffset, sourceEntity) =>
{
var currScriptFunction = scriptFunction.Clone();
currScriptFunction.Set(player);
@@ -368,6 +368,7 @@ public static void RegisterEvents(object target)
currScriptFunction.Set(damage);
currScriptFunction.Set(shotOffset);
currScriptFunction.Set(damageOffset);
+ currScriptFunction.Set(sourceEntity);
return currScriptFunction.CallAsync();
};
break;
diff --git a/api/AltV.Net.Async/AsyncCore.cs b/api/AltV.Net.Async/AsyncCore.cs
index 2dbacf858..0e87d688e 100644
--- a/api/AltV.Net.Async/AsyncCore.cs
+++ b/api/AltV.Net.Async/AsyncCore.cs
@@ -295,14 +295,14 @@ await ExplosionAsyncEventHandler.CallAsync(@delegate =>
public override void OnWeaponDamageEvent(IntPtr eventPointer, IPlayer sourcePlayer, IEntity targetEntity,
uint weapon, ushort damage,
- Position shotOffset, BodyPart bodyPart)
+ Position shotOffset, BodyPart bodyPart, IEntity sourceEntity)
{
- base.OnWeaponDamageEvent(eventPointer, sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart);
+ base.OnWeaponDamageEvent(eventPointer, sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart, sourceEntity);
if (!WeaponDamageAsyncEventHandler.HasEvents()) return;
Task.Run(async () =>
{
await WeaponDamageAsyncEventHandler.CallAsync(@delegate =>
- @delegate(sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart));
+ @delegate(sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart, sourceEntity));
});
}
diff --git a/api/AltV.Net.Async/Elements/Entities/AsyncConnectionInfo.cs b/api/AltV.Net.Async/Elements/Entities/AsyncConnectionInfo.cs
index b5e732b6f..a10907eb7 100644
--- a/api/AltV.Net.Async/Elements/Entities/AsyncConnectionInfo.cs
+++ b/api/AltV.Net.Async/Elements/Entities/AsyncConnectionInfo.cs
@@ -63,6 +63,19 @@ public ulong HardwareIdExHash
}
}
}
+
+ public string HardwareId3
+ {
+ get
+ {
+ lock (ConnectionInfo)
+ {
+ if (!AsyncContext.CheckIfExistsOrCachedNullable(ConnectionInfo)) return default;
+ return ConnectionInfo.HardwareId3;
+ }
+ }
+ }
+
public string AuthToken
{
get
@@ -74,6 +87,7 @@ public string AuthToken
}
}
}
+
public bool IsDebug
{
get
diff --git a/api/AltV.Net.Async/Elements/Entities/AsyncPlayer.cs b/api/AltV.Net.Async/Elements/Entities/AsyncPlayer.cs
index 1ef1cef3e..c5ef79853 100644
--- a/api/AltV.Net.Async/Elements/Entities/AsyncPlayer.cs
+++ b/api/AltV.Net.Async/Elements/Entities/AsyncPlayer.cs
@@ -100,6 +100,18 @@ public ulong HardwareIdExHash
}
}
+ public string HardwareId3
+ {
+ get
+ {
+ lock (Player)
+ {
+ if (!AsyncContext.CheckIfExistsOrCachedNullable(Player)) return default;
+ return Player.HardwareId3;
+ }
+ }
+ }
+
public string AuthToken
{
get
diff --git a/api/AltV.Net.Async/Events/Events.cs b/api/AltV.Net.Async/Events/Events.cs
index 6aa2a772d..ec7819aff 100644
--- a/api/AltV.Net.Async/Events/Events.cs
+++ b/api/AltV.Net.Async/Events/Events.cs
@@ -59,7 +59,7 @@ public delegate Task ExplosionAsyncDelegate(IPlayer player, ExplosionType explos
uint explosionFx, IEntity target);
public delegate Task WeaponDamageAsyncDelegate(IPlayer player, IEntity target, uint weapon, ushort damage,
- Position shotOffset, BodyPart bodyPart);
+ Position shotOffset, BodyPart bodyPart, IEntity sourceEntity);
public delegate Task VehicleDestroyAsyncDelegate(IVehicle vehicle);
diff --git a/api/AltV.Net.CApi/Libraries/ClientLibrary.cs b/api/AltV.Net.CApi/Libraries/ClientLibrary.cs
index db25d09a9..8ab31f13c 100644
--- a/api/AltV.Net.CApi/Libraries/ClientLibrary.cs
+++ b/api/AltV.Net.CApi/Libraries/ClientLibrary.cs
@@ -939,7 +939,7 @@ public unsafe interface IClientLibrary
public unsafe class ClientLibrary : IClientLibrary
{
- public readonly uint Methods = 1810;
+ public readonly uint Methods = 1812;
public delegate* unmanaged[Cdecl] Audio_AddOutput { get; }
public delegate* unmanaged[Cdecl] Audio_GetBaseObject { get; }
public delegate* unmanaged[Cdecl] Audio_GetCurrentTime { get; }
@@ -3721,7 +3721,7 @@ private IntPtr GetUnmanagedPtr(IDictionary funcTable, ulong ha
public ClientLibrary(Dictionary funcTable)
{
if (!funcTable.TryGetValue(0, out var capiHash)) Outdated = true;
- else if (capiHash == IntPtr.Zero || *(ulong*)capiHash != 7720423717087039049UL) Outdated = true;
+ else if (capiHash == IntPtr.Zero || *(ulong*)capiHash != 992064889353150126UL) Outdated = true;
Audio_AddOutput = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 9914412815391408844UL, Audio_AddOutputFallback);
Audio_GetBaseObject = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 6330360502401226894UL, Audio_GetBaseObjectFallback);
Audio_GetCurrentTime = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 2944324482134975819UL, Audio_GetCurrentTimeFallback);
diff --git a/api/AltV.Net.CApi/Libraries/ServerLibrary.cs b/api/AltV.Net.CApi/Libraries/ServerLibrary.cs
index f17944671..bc685519d 100644
--- a/api/AltV.Net.CApi/Libraries/ServerLibrary.cs
+++ b/api/AltV.Net.CApi/Libraries/ServerLibrary.cs
@@ -35,6 +35,7 @@ public unsafe interface IServerLibrary
public delegate* unmanaged[Cdecl] ConnectionInfo_GetCloudAuthResult { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetCloudID { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetDiscordUserID { get; }
+ public delegate* unmanaged[Cdecl] ConnectionInfo_GetHwid3 { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetHwIdExHash { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetHwIdHash { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetID { get; }
@@ -194,6 +195,7 @@ public unsafe interface IServerLibrary
public delegate* unmanaged[Cdecl] Player_GetHeadBlendData { get; }
public delegate* unmanaged[Cdecl] Player_GetHeadBlendPaletteColor { get; }
public delegate* unmanaged[Cdecl] Player_GetHeadOverlay { get; }
+ public delegate* unmanaged[Cdecl] Player_GetHwid3 { get; }
public delegate* unmanaged[Cdecl] Player_GetHwidExHash { get; }
public delegate* unmanaged[Cdecl] Player_GetHwidHash { get; }
public delegate* unmanaged[Cdecl] Player_GetInteriorLocation { get; }
@@ -488,7 +490,7 @@ public unsafe interface IServerLibrary
public unsafe class ServerLibrary : IServerLibrary
{
- public readonly uint Methods = 1810;
+ public readonly uint Methods = 1812;
public delegate* unmanaged[Cdecl] BaseObject_DeleteSyncedMetaData { get; }
public delegate* unmanaged[Cdecl] BaseObject_SetMultipleSyncedMetaData { get; }
public delegate* unmanaged[Cdecl] BaseObject_SetSyncedMetaData { get; }
@@ -513,6 +515,7 @@ public unsafe class ServerLibrary : IServerLibrary
public delegate* unmanaged[Cdecl] ConnectionInfo_GetCloudAuthResult { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetCloudID { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetDiscordUserID { get; }
+ public delegate* unmanaged[Cdecl] ConnectionInfo_GetHwid3 { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetHwIdExHash { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetHwIdHash { get; }
public delegate* unmanaged[Cdecl] ConnectionInfo_GetID { get; }
@@ -672,6 +675,7 @@ public unsafe class ServerLibrary : IServerLibrary
public delegate* unmanaged[Cdecl] Player_GetHeadBlendData { get; }
public delegate* unmanaged[Cdecl] Player_GetHeadBlendPaletteColor { get; }
public delegate* unmanaged[Cdecl] Player_GetHeadOverlay { get; }
+ public delegate* unmanaged[Cdecl] Player_GetHwid3 { get; }
public delegate* unmanaged[Cdecl] Player_GetHwidExHash { get; }
public delegate* unmanaged[Cdecl] Player_GetHwidHash { get; }
public delegate* unmanaged[Cdecl] Player_GetInteriorLocation { get; }
@@ -1010,6 +1014,8 @@ public unsafe class ServerLibrary : IServerLibrary
private static nint ConnectionInfo_GetCloudIDFallback(IntPtr _connectionInfo, int* _size) => throw new Exceptions.OutdatedSdkException("ConnectionInfo_GetCloudID", "ConnectionInfo_GetCloudID SDK method is outdated. Please update your module nuget");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate long ConnectionInfo_GetDiscordUserIDDelegate(IntPtr _connectionInfo);
private static long ConnectionInfo_GetDiscordUserIDFallback(IntPtr _connectionInfo) => throw new Exceptions.OutdatedSdkException("ConnectionInfo_GetDiscordUserID", "ConnectionInfo_GetDiscordUserID SDK method is outdated. Please update your module nuget");
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate nint ConnectionInfo_GetHwid3Delegate(IntPtr _connectionInfo, int* _size);
+ private static nint ConnectionInfo_GetHwid3Fallback(IntPtr _connectionInfo, int* _size) => throw new Exceptions.OutdatedSdkException("ConnectionInfo_GetHwid3", "ConnectionInfo_GetHwid3 SDK method is outdated. Please update your module nuget");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong ConnectionInfo_GetHwIdExHashDelegate(IntPtr _connectionInfo);
private static ulong ConnectionInfo_GetHwIdExHashFallback(IntPtr _connectionInfo) => throw new Exceptions.OutdatedSdkException("ConnectionInfo_GetHwIdExHash", "ConnectionInfo_GetHwIdExHash SDK method is outdated. Please update your module nuget");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong ConnectionInfo_GetHwIdHashDelegate(IntPtr _connectionInfo);
@@ -1328,6 +1334,8 @@ public unsafe class ServerLibrary : IServerLibrary
private static void Player_GetHeadBlendPaletteColorFallback(nint _player, byte _id, Rgba* _headBlendPaletteColor) => throw new Exceptions.OutdatedSdkException("Player_GetHeadBlendPaletteColor", "Player_GetHeadBlendPaletteColor SDK method is outdated. Please update your module nuget");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void Player_GetHeadOverlayDelegate(nint _player, byte _overlayID, HeadOverlay* _headOverlay);
private static void Player_GetHeadOverlayFallback(nint _player, byte _overlayID, HeadOverlay* _headOverlay) => throw new Exceptions.OutdatedSdkException("Player_GetHeadOverlay", "Player_GetHeadOverlay SDK method is outdated. Please update your module nuget");
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate nint Player_GetHwid3Delegate(nint _player, int* _size);
+ private static nint Player_GetHwid3Fallback(nint _player, int* _size) => throw new Exceptions.OutdatedSdkException("Player_GetHwid3", "Player_GetHwid3 SDK method is outdated. Please update your module nuget");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong Player_GetHwidExHashDelegate(nint _player);
private static ulong Player_GetHwidExHashFallback(nint _player) => throw new Exceptions.OutdatedSdkException("Player_GetHwidExHash", "Player_GetHwidExHash SDK method is outdated. Please update your module nuget");
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong Player_GetHwidHashDelegate(nint _player);
@@ -1917,7 +1925,7 @@ private IntPtr GetUnmanagedPtr(IDictionary funcTable, ulong ha
public ServerLibrary(Dictionary funcTable)
{
if (!funcTable.TryGetValue(0, out var capiHash)) Outdated = true;
- else if (capiHash == IntPtr.Zero || *(ulong*)capiHash != 7720423717087039049UL) Outdated = true;
+ else if (capiHash == IntPtr.Zero || *(ulong*)capiHash != 992064889353150126UL) Outdated = true;
BaseObject_DeleteSyncedMetaData = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 8228424877092269355UL, BaseObject_DeleteSyncedMetaDataFallback);
BaseObject_SetMultipleSyncedMetaData = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 1390762125822890831UL, BaseObject_SetMultipleSyncedMetaDataFallback);
BaseObject_SetSyncedMetaData = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 8002999088966424231UL, BaseObject_SetSyncedMetaDataFallback);
@@ -1942,6 +1950,7 @@ public ServerLibrary(Dictionary funcTable)
ConnectionInfo_GetCloudAuthResult = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 7415605567391116903UL, ConnectionInfo_GetCloudAuthResultFallback);
ConnectionInfo_GetCloudID = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 7998061229071288348UL, ConnectionInfo_GetCloudIDFallback);
ConnectionInfo_GetDiscordUserID = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 4175744399917476392UL, ConnectionInfo_GetDiscordUserIDFallback);
+ ConnectionInfo_GetHwid3 = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 3230557606089997547UL, ConnectionInfo_GetHwid3Fallback);
ConnectionInfo_GetHwIdExHash = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 3151831504154255688UL, ConnectionInfo_GetHwIdExHashFallback);
ConnectionInfo_GetHwIdHash = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 11409383581668438027UL, ConnectionInfo_GetHwIdHashFallback);
ConnectionInfo_GetID = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 8080268107975854795UL, ConnectionInfo_GetIDFallback);
@@ -2101,6 +2110,7 @@ public ServerLibrary(Dictionary funcTable)
Player_GetHeadBlendData = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 12996031514192232278UL, Player_GetHeadBlendDataFallback);
Player_GetHeadBlendPaletteColor = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 6875264309357036667UL, Player_GetHeadBlendPaletteColorFallback);
Player_GetHeadOverlay = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 18242810182906526031UL, Player_GetHeadOverlayFallback);
+ Player_GetHwid3 = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 13686597780873033455UL, Player_GetHwid3Fallback);
Player_GetHwidExHash = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 424368865670330442UL, Player_GetHwidExHashFallback);
Player_GetHwidHash = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 9546723288515311389UL, Player_GetHwidHashFallback);
Player_GetInteriorLocation = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 16961931856292652951UL, Player_GetInteriorLocationFallback);
diff --git a/api/AltV.Net.CApi/Libraries/SharedLibrary.cs b/api/AltV.Net.CApi/Libraries/SharedLibrary.cs
index f42949f30..dfa70d522 100644
--- a/api/AltV.Net.CApi/Libraries/SharedLibrary.cs
+++ b/api/AltV.Net.CApi/Libraries/SharedLibrary.cs
@@ -426,7 +426,7 @@ public unsafe interface ISharedLibrary
public unsafe class SharedLibrary : ISharedLibrary
{
- public readonly uint Methods = 1810;
+ public readonly uint Methods = 1812;
public delegate* unmanaged[Cdecl] Audio_GetID { get; }
public delegate* unmanaged[Cdecl] AudioAttachedOutput_GetID { get; }
public delegate* unmanaged[Cdecl] AudioFilter_GetID { get; }
@@ -1669,7 +1669,7 @@ private IntPtr GetUnmanagedPtr(IDictionary funcTable, ulong ha
public SharedLibrary(Dictionary funcTable)
{
if (!funcTable.TryGetValue(0, out var capiHash)) Outdated = true;
- else if (capiHash == IntPtr.Zero || *(ulong*)capiHash != 7720423717087039049UL) Outdated = true;
+ else if (capiHash == IntPtr.Zero || *(ulong*)capiHash != 992064889353150126UL) Outdated = true;
Audio_GetID = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 4464042055475980737UL, Audio_GetIDFallback);
AudioAttachedOutput_GetID = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 17725794901805112189UL, AudioAttachedOutput_GetIDFallback);
AudioFilter_GetID = (delegate* unmanaged[Cdecl]) GetUnmanagedPtr(funcTable, 8824535635529306325UL, AudioFilter_GetIDFallback);
diff --git a/api/AltV.Net.CApi/Native/AltV.Resource.cs b/api/AltV.Net.CApi/Native/AltV.Resource.cs
index f3cf4dac4..c62f994ba 100644
--- a/api/AltV.Net.CApi/Native/AltV.Resource.cs
+++ b/api/AltV.Net.CApi/Native/AltV.Resource.cs
@@ -73,7 +73,8 @@ internal delegate void ExplosionDelegate(IntPtr eventPointer, IntPtr playerPoint
Position position, uint explosionFx, IntPtr targetEntityPointer, BaseObjectType targetEntityType);
internal delegate void WeaponDamageDelegate(IntPtr eventPointer, IntPtr playerPointer, IntPtr entityPointer,
- BaseObjectType entityType, uint weapon, ushort damage, Position shotOffset, BodyPart bodyPart);
+ BaseObjectType entityType, uint weapon, ushort damage, Position shotOffset, BodyPart bodyPart, IntPtr sourceEntityPointer,
+ BaseObjectType sourceEntityType);
internal delegate void FireDelegate(IntPtr eventPointer, IntPtr playerPointer,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
diff --git a/api/AltV.Net.Example/SampleResource.cs b/api/AltV.Net.Example/SampleResource.cs
index 54445719d..3269fbadd 100644
--- a/api/AltV.Net.Example/SampleResource.cs
+++ b/api/AltV.Net.Example/SampleResource.cs
@@ -24,7 +24,7 @@ public override void OnStart()
long currentTraceSize = 0;
AltTrace.OnTraceFileSizeChange += size => { currentTraceSize = size; };
- Alt.OnWeaponDamage += (player, target, weapon, damage, offset, part) =>
+ Alt.OnWeaponDamage += (player, target, weapon, damage, offset, part, sourceEntity) =>
{
//Do Something
return false;
diff --git a/api/AltV.Net.Shared/Enums/PedModel.cs b/api/AltV.Net.Shared/Enums/PedModel.cs
index 8a1e46bb5..efbcdda12 100644
--- a/api/AltV.Net.Shared/Enums/PedModel.cs
+++ b/api/AltV.Net.Shared/Enums/PedModel.cs
@@ -917,6 +917,16 @@ public enum PedModel : uint
Zombie01GMM = 3323689041, // 0xC61B7851
BountyTarget05IG = 3690927337, // 0xDBFF14E9
JenetteECSB = 4095622650, // 0xF41E3DFA
- Maude02CSB = 1928962749 // 0x72F9A2BD
+ Maude02CSB = 1928962749, // 0x72F9A2BD
+ JodiMarshallIG = 1043886488, // 0x3E387198
+ GuadalopeIG = 2630588041, // 0x9CCB9689
+ HelmsmanPavel02IG = 2162565007, // 0x80E61F8F
+ HelmsmanPavel02CSB = 1591881099, // 0x5EE22D8B
+ CarClub02AFY = 238774192, // 0xE3B67B0
+ CarClub02AMY = 2790225070, // 0xA64F74AE
+ ArmsManufac01IG = 999842350, // 0x3B98622E
+ Oscar02IG = 1042644412, // 0x3E257DBC
+ Oscar02CSB = 3265229602, // 0xC29F7322
+ JodiMarshallCSB = 2447125353 // 0x91DC2B69
}
}
diff --git a/api/AltV.Net.Shared/Enums/VehicleModel.cs b/api/AltV.Net.Shared/Enums/VehicleModel.cs
index bad96869b..b2d152c75 100644
--- a/api/AltV.Net.Shared/Enums/VehicleModel.cs
+++ b/api/AltV.Net.Shared/Enums/VehicleModel.cs
@@ -863,6 +863,24 @@ public enum VehicleModel : uint
eurosX32 = 3295372994, // [0xC46B66C2] Euros X32 (CAR)
pipistrello = 4071505793, // [0xF2AE3F81] Pipistrello (CAR)
driftvorschlag = 4151380270, // [0xF771092E] Vorschlaghammer (CAR)
- dominator10 = 1579902654 // [0x5E2B66BE] Dominator FX (CAR)
+ dominator10 = 1579902654, // [0x5E2B66BE] Dominator FX (CAR)
+ driftjester3 = 3932276298, // [0xEA61C64A] Jester Classic (CAR)
+ driftcheburek = 2828274931, // [0xA8940CF3] Cheburek (CAR)
+ youga5 = 2266063097, // [0x871160F9] Youga Custom (CAR)
+ freightcar3 = 2420957787, // [0x904CE25B] Freight Train (TRAIN)
+ uranus = 1534326199, // [0x5B73F5B7] Uranus LozSpeed (CAR)
+ banshee3 = 3634959571, // [0xD8A914D3] Banshee GTS (CAR)
+ duster2 = 84351789, // [0x5071B2D] Duster 300-H (PLANE)
+ titan2 = 858355070, // [0x3329757E] Titan 250 D (PLANE)
+ driftfuto2 = 3005741670, // [0xB327FA66] Futo (CAR)
+ chavosv6 = 1992041063, // [0x76BC2267] Chavos V6 (CAR)
+ jester5 = 1484920335, // [0x5882160F] Jester RR Widebody (CAR)
+ polcaracara = 2346018232, // [0x8BD565B8] Caracara Pursuit (CAR)
+ polfaction2 = 1891140410, // [0x70B8833A] Outreach Faction (CAR)
+ polterminus = 2973836112, // [0xB1412350] Terminus Patrol (CAR)
+ firebolt = 3321950518, // [0xC600F136] Firebolt ASP (CAR)
+ polcoquette4 = 2042703219, // [0x79C12D73] Coquette D10 Pursuit (CAR)
+ coquette6 = 127317925, // [0x796B7A5] Coquette D5 (CAR)
+ cargobob5 = 3942284983 // [0xEAFA7EB7] DH-7 Iron Mule (HELI)
}
}
diff --git a/api/AltV.Net/Alt.RegisterEvents.cs b/api/AltV.Net/Alt.RegisterEvents.cs
index 7d213ed42..0abfb7997 100644
--- a/api/AltV.Net/Alt.RegisterEvents.cs
+++ b/api/AltV.Net/Alt.RegisterEvents.cs
@@ -350,11 +350,11 @@ public static void RegisterEvents(object target)
new[]
{
typeof(IPlayer), typeof(IEntity), typeof(uint), typeof(ushort),
- typeof(Position), typeof(BodyPart)
+ typeof(Position), typeof(BodyPart), typeof(IEntity)
}, new[] {typeof(WeaponDamageResponse)});
if (scriptFunction == null) return;
OnWeaponDamage +=
- (player, targetEntity, weapon, damage, shotOffset, damageOffset) =>
+ (player, targetEntity, weapon, damage, shotOffset, damageOffset, sourceEntity) =>
{
scriptFunction.Set(player);
scriptFunction.Set(targetEntity);
@@ -362,6 +362,7 @@ public static void RegisterEvents(object target)
scriptFunction.Set(damage);
scriptFunction.Set(shotOffset);
scriptFunction.Set(damageOffset);
+ scriptFunction.Set(sourceEntity);
if (scriptFunction.Call() is WeaponDamageResponse response)
{
return response;
diff --git a/api/AltV.Net/Core.Events.cs b/api/AltV.Net/Core.Events.cs
index bce46bd84..f8b736e1f 100644
--- a/api/AltV.Net/Core.Events.cs
+++ b/api/AltV.Net/Core.Events.cs
@@ -523,7 +523,8 @@ public virtual void OnExplosionEvent(IntPtr eventPointer, IPlayer sourcePlayer,
public void OnWeaponDamage(IntPtr eventPointer, IntPtr playerPointer, IntPtr entityPointer,
BaseObjectType entityType, uint weapon,
- ushort damage, Position shotOffset, BodyPart bodyPart)
+ ushort damage, Position shotOffset, BodyPart bodyPart,
+ IntPtr sourceEntityPointer, BaseObjectType sourceEntityType)
{
var sourcePlayer = PoolManager.Player.Get(playerPointer);
if (sourcePlayer == null)
@@ -534,25 +535,76 @@ public void OnWeaponDamage(IntPtr eventPointer, IntPtr playerPointer, IntPtr ent
}
var targetEntity = (IEntity)PoolManager.Get(entityPointer, entityType);
+
+
+ var sourceEntity = (IEntity)PoolManager.Get(sourceEntityPointer, sourceEntityType);
+
+ OnWeaponDamageEvent(eventPointer, sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart, sourceEntity);
+ }
+
+ ///
+ /// Handles the weapon damage event triggered in the game.
+ ///
+ ///
+ /// This method iterates through all registered event handlers for weapon damage events,
+ /// determines whether the event should be canceled, and updates the damage value if modified
+ /// by any handler.
+ ///
+ /// If any handler indicates cancellation, the event is marked as canceled.
+ /// If a damage override is provided, it updates the damage value.
+ ///
+ public virtual void OnWeaponDamageEvent(IntPtr eventPointer, IPlayer sourcePlayer, IEntity targetEntity,
+ uint weapon, ushort damage, Position shotOffset, BodyPart bodyPart, IEntity sourceEntity)
+ {
+ try
+ {
+ var (shouldCancel, weaponDamage) =
+ ProcessWeaponDamageEvents(sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart, sourceEntity);
- OnWeaponDamageEvent(eventPointer, sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart);
+ if (weaponDamage.HasValue)
+ {
+ unsafe
+ {
+ Alt.CoreImpl.Library.Server.Event_WeaponDamageEvent_SetDamageValue(eventPointer,
+ weaponDamage.Value);
+ }
+ }
+
+ if (shouldCancel)
+ {
+ unsafe
+ {
+ Alt.CoreImpl.Library.Shared.Event_Cancel(eventPointer);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Alt.Log($"Unhandled exception in {nameof(OnWeaponDamageEvent)}: {ex}");
+ }
}
- public virtual void OnWeaponDamageEvent(IntPtr eventPointer, IPlayer sourcePlayer, IEntity targetEntity,
- uint weapon, ushort damage,
- Position shotOffset, BodyPart bodyPart)
+ ///
+ /// Processes weapon damage events by invoking all registered event handlers and aggregating results.
+ ///
+ ///
+ /// A tuple containing a flag indicating if the event should be canceled and the aggregated weapon damage value.
+ ///
+ private (bool shouldCancel, uint? weaponDamage) ProcessWeaponDamageEvents(IPlayer sourcePlayer,
+ IEntity targetEntity, uint weapon, ushort damage, Position shotOffset, BodyPart bodyPart, IEntity sourceEntity)
{
+ var shouldCancel = false;
uint? weaponDamage = null;
- var cancel = false;
+
foreach (var @delegate in WeaponDamageEventHandler.GetEvents())
{
try
{
- var result = @delegate(sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart);
+ var result = @delegate(sourcePlayer, targetEntity, weapon, damage, shotOffset, bodyPart, sourceEntity);
- if (!result.notCancel)
+ if (result.Cancel)
{
- cancel = true;
+ shouldCancel = true;
}
if (result.Damage.HasValue)
@@ -560,31 +612,17 @@ public virtual void OnWeaponDamageEvent(IntPtr eventPointer, IPlayer sourcePlaye
weaponDamage ??= result.Damage.Value;
}
}
- catch (TargetInvocationException exception)
+ catch (TargetInvocationException tex)
{
- Alt.Log("exception at event:" + "OnWeaponDamageEvent" + ":" + exception.InnerException);
+ Alt.Log($"TargetInvocationException in {nameof(ProcessWeaponDamageEvents)} handler: {tex.InnerException}");
}
- catch (Exception exception)
+ catch (Exception ex)
{
- Alt.Log("exception at event:" + "OnWeaponDamageEvent" + ":" + exception);
+ Alt.Log($"Exception in {nameof(ProcessWeaponDamageEvents)} handler: {ex}");
}
}
- if (weaponDamage is not null)
- {
- unsafe
- {
- Alt.CoreImpl.Library.Server.Event_WeaponDamageEvent_SetDamageValue(eventPointer, weaponDamage.Value);
- }
- }
-
- if (cancel)
- {
- unsafe
- {
- Alt.CoreImpl.Library.Shared.Event_Cancel(eventPointer);
- }
- }
+ return (shouldCancel, weaponDamage);
}
public void OnPlayerChangeVehicleSeat(IntPtr vehiclePointer, IntPtr playerPointer, byte oldSeat,
diff --git a/api/AltV.Net/Data/WeaponDamageResponse.cs b/api/AltV.Net/Data/WeaponDamageResponse.cs
index 43bf4f694..840661a20 100644
--- a/api/AltV.Net/Data/WeaponDamageResponse.cs
+++ b/api/AltV.Net/Data/WeaponDamageResponse.cs
@@ -1,6 +1,21 @@
namespace AltV.Net.Data;
-public record WeaponDamageResponse(bool notCancel, uint? Damage) {
- public static implicit operator WeaponDamageResponse(bool val) => new WeaponDamageResponse(val, null);
- public static implicit operator WeaponDamageResponse(uint val) => new WeaponDamageResponse(true, val);
-};
\ No newline at end of file
+///
+/// Represents the response to a weapon damage event, indicating whether the damage is processed and the amount of damage.
+///
+public record WeaponDamageResponse(bool Cancel, uint? Damage)
+{
+ ///
+ /// Implicit conversion from a boolean value to .
+ /// Sets to the specified value and to null.
+ ///
+ /// Indicates whether the damage is processed.
+ public static implicit operator WeaponDamageResponse(bool cancel) => new(cancel, null);
+
+ ///
+ /// Implicit conversion from an uint value to .
+ /// Sets to true and to the specified value.
+ ///
+ /// The amount of damage.
+ public static implicit operator WeaponDamageResponse(uint damage) => new(false, damage);
+}
diff --git a/api/AltV.Net/Elements/Entities/ConnectionInfo.cs b/api/AltV.Net/Elements/Entities/ConnectionInfo.cs
index caffd25cb..72b89d7e6 100644
--- a/api/AltV.Net/Elements/Entities/ConnectionInfo.cs
+++ b/api/AltV.Net/Elements/Entities/ConnectionInfo.cs
@@ -11,7 +11,6 @@ namespace AltV.Net.Elements.Entities;
public class ConnectionInfo : BaseObject, IConnectionInfo
{
-
public IntPtr ConnectionInfoNativePointer { get; }
public override IntPtr NativePointer => ConnectionInfoNativePointer;
@@ -32,7 +31,8 @@ public static uint GetId(IntPtr pointer)
}
- public ConnectionInfo(ICore core, IntPtr nativePointer, uint id) : base(core, GetBaseObjectPointer(core, nativePointer), BaseObjectType.ConnectionInfo, id)
+ public ConnectionInfo(ICore core, IntPtr nativePointer, uint id) : base(core,
+ GetBaseObjectPointer(core, nativePointer), BaseObjectType.ConnectionInfo, id)
{
ConnectionInfoNativePointer = nativePointer;
}
@@ -83,6 +83,19 @@ public ulong HardwareIdExHash
}
}
+ public string HardwareId3
+ {
+ get
+ {
+ unsafe
+ {
+ var size = 0;
+ return Core.PtrToStringUtf8AndFree(
+ Core.Library.Server.ConnectionInfo_GetHwid3(ConnectionInfoNativePointer, &size), size);
+ }
+ }
+ }
+
public string AuthToken
{
get
@@ -274,7 +287,8 @@ public CloudAuthResult CloudAuthResult
{
unsafe
{
- return (CloudAuthResult)Core.Library.Server.ConnectionInfo_GetCloudAuthResult(ConnectionInfoNativePointer);
+ return (CloudAuthResult)Core.Library.Server.ConnectionInfo_GetCloudAuthResult(
+ ConnectionInfoNativePointer);
}
}
}
diff --git a/api/AltV.Net/Elements/Entities/IConnectionInfo.cs b/api/AltV.Net/Elements/Entities/IConnectionInfo.cs
index bceb3b01a..1ca0d477f 100644
--- a/api/AltV.Net/Elements/Entities/IConnectionInfo.cs
+++ b/api/AltV.Net/Elements/Entities/IConnectionInfo.cs
@@ -12,6 +12,7 @@ public interface IConnectionInfo : IBaseObject
ulong SocialId { get; }
ulong HardwareIdHash { get; }
ulong HardwareIdExHash { get; }
+ string HardwareId3 { get; }
string AuthToken { get; }
bool IsDebug { get; }
string Branch { get; }
diff --git a/api/AltV.Net/Elements/Entities/IPlayer.cs b/api/AltV.Net/Elements/Entities/IPlayer.cs
index d3eff7a78..43c374e9e 100644
--- a/api/AltV.Net/Elements/Entities/IPlayer.cs
+++ b/api/AltV.Net/Elements/Entities/IPlayer.cs
@@ -36,6 +36,8 @@ public interface IPlayer : ISharedPlayer, IEntity
ulong HardwareIdExHash { get; }
+ string HardwareId3 { get; }
+
string AuthToken { get; }
long DiscordId { get; }
diff --git a/api/AltV.Net/Elements/Entities/Player.cs b/api/AltV.Net/Elements/Entities/Player.cs
index ba8fd7459..df148ff2c 100644
--- a/api/AltV.Net/Elements/Entities/Player.cs
+++ b/api/AltV.Net/Elements/Entities/Player.cs
@@ -536,6 +536,20 @@ public ulong HardwareIdExHash
}
}
+ public string HardwareId3
+ {
+ get
+ {
+ unsafe
+ {
+ CheckIfEntityExistsOrCached();
+ var size = 0;
+ return Core.PtrToStringUtf8AndFree(
+ Core.Library.Server.Player_GetHwid3(PlayerNativePointer, &size), size);
+ }
+ }
+ }
+
public string AuthToken
{
get
diff --git a/api/AltV.Net/Events/Events.cs b/api/AltV.Net/Events/Events.cs
index cd9f358ea..a836025bb 100644
--- a/api/AltV.Net/Events/Events.cs
+++ b/api/AltV.Net/Events/Events.cs
@@ -64,7 +64,7 @@ public delegate bool ExplosionDelegate(IPlayer player, ExplosionType explosionTy
uint explosionFx, IEntity targetEntity);
public delegate WeaponDamageResponse WeaponDamageDelegate(IPlayer player, IEntity target, uint weapon, ushort damage,
- Position shotOffset, BodyPart bodyPart);
+ Position shotOffset, BodyPart bodyPart, IEntity sourceEntity);
public delegate void VehicleDestroyDelegate(IVehicle vehicle);
diff --git a/api/AltV.Net/ModuleWrapper.cs b/api/AltV.Net/ModuleWrapper.cs
index 7685e1ae3..ab9819b4d 100644
--- a/api/AltV.Net/ModuleWrapper.cs
+++ b/api/AltV.Net/ModuleWrapper.cs
@@ -255,9 +255,10 @@ public static void OnExplosion(IntPtr eventPointer, IntPtr playerPointer, Explos
}
public static void OnWeaponDamage(IntPtr eventPointer, IntPtr playerPointer, IntPtr entityPointer,
- BaseObjectType entityType, uint weapon, ushort damage, Position shotOffset, BodyPart bodyPart)
+ BaseObjectType entityType, uint weapon, ushort damage, Position shotOffset, BodyPart bodyPart,
+ IntPtr sourceEntityPointer, BaseObjectType sourceEntityType)
{
- _core.OnWeaponDamage(eventPointer, playerPointer, entityPointer, entityType, weapon, damage, shotOffset, bodyPart);
+ _core.OnWeaponDamage(eventPointer, playerPointer, entityPointer, entityType, weapon, damage, shotOffset, bodyPart, sourceEntityPointer, sourceEntityType);
}
public static void OnPlayerChangeVehicleSeat(IntPtr vehiclePointer, IntPtr playerPointer, byte oldSeat,
diff --git a/docs/articles/getting-started/setup.md b/docs/articles/getting-started/setup.md
index 8037f39d3..46bd58023 100644
--- a/docs/articles/getting-started/setup.md
+++ b/docs/articles/getting-started/setup.md
@@ -1,7 +1,7 @@
# Setup
You need the following requirements to use the c# module.
-* Latest [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0).
+* Latest [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
## Step 1
diff --git a/download_dotnet.ps1 b/download_dotnet.ps1
index 88f9d2f15..fc2137fc1 100644
--- a/download_dotnet.ps1
+++ b/download_dotnet.ps1
@@ -1,63 +1,72 @@
param(
$path,
- $os
+ $os,
+ $pack = $true
)
+Write-Host "Downloading latest dotnet"
Write-Host $path
Write-Host $os
+Write-Host $pack
$start = $pwd
-cd $path
+Set-Location $path
$dotnetRelease = Invoke-WebRequest 'https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/8.0/releases.json' | ConvertFrom-Json
$dotnetVersion = $dotnetRelease."latest-release"
-if($os -eq "win"){
+if ($os -eq "win") {
$file = "dotnet-sdk-win-x64.zip"
-}else{
+}
+else {
$file = "dotnet-sdk-linux-x64.tar.gz"
}
-$dotnetSdkZipUrl = $dotnetRelease.releases[0].sdk.files | where { $_.name -eq $file } | Select -ExpandProperty "url" | Out-String
+$dotnetSdkZipUrl = $dotnetRelease.releases[0].sdk.files | Where-Object { $_.name -eq $file } | Select-Object -ExpandProperty "url" | Out-String
Write-Host "Download dotnet version: $dotnetVersion"
Write-Host "Download from cdn: $dotnetSdkZipUrl"
-if($os -eq "win"){
- $dfile = "dontet_version_$dotnetVersion.zip"
-}else{
- $dfile = "dontet_version_$dotnetVersion.tar.gz"
+if ($os -eq "win") {
+ $dfile = "dotnet_version_$dotnetVersion.zip"
+}
+else {
+ $dfile = "dotnet_version_$dotnetVersion.tar.gz"
}
-Invoke-RestMethod -Uri $dotnetSdkZipUrl -OutFile "$path\$dfile"
+Invoke-RestMethod -Uri $dotnetSdkZipUrl -OutFile "$dfile"
-Write-Host "Download successfully to $path\$dfile"
+Write-Host "Download successfully to $dfile"
-if($os -eq "win"){
- Expand-Archive -LiteralPath "$path\\$dfile" -DestinationPath "$path\dontet_version_$dotnetVersion_$os"
-}else{
- mkdir "$path/dontet_version_$dotnetVersion_$os"
- tar -zxvf "$path/$dfile" -C "$path/dontet_version_$dotnetVersion_$os"
+if ($os -eq "win") {
+ Expand-Archive -LiteralPath "$dfile" -DestinationPath "dotnet_version_$dotnetVersion_$os"
+}
+else {
+ mkdir "dotnet_version_$dotnetVersion_$os"
+ tar -zxvf "$dfile" -C "dotnet_version_$dotnetVersion_$os"
}
Write-Host "Extraxt successfully"
-if($os -eq "win"){
- cd "$path\dontet_version_$dotnetVersion_$os\packs\Microsoft.NETCore.App.Host.win-x64\$dotnetVersion\runtimes\win-x64\native"
-}else{
- cd "$path\dontet_version_$dotnetVersion_$os\packs\Microsoft.NETCore.App.Host.linux-x64\$dotnetVersion\runtimes\linux-x64\native"
+if ($os -eq "win") {
+ Set-Location "dotnet_version_$dotnetVersion_$os\packs\Microsoft.NETCore.App.Host.win-x64\$dotnetVersion\runtimes\win-x64\native"
}
-
-Write-Host "Pack libnethost"
-
-if($os -eq "win"){
- tar -cvzf $path\libnethost.tar *
-}else{
- tar -cvzf "$path/libnethost.tar" *
+else {
+ Set-Location "dotnet_version_$dotnetVersion_$os\packs\Microsoft.NETCore.App.Host.linux-x64\$dotnetVersion\runtimes\linux-x64\native"
}
-Write-Host "Pack successfully"
+if ($pack) {
+ Write-Host "Pack libnethost"
+
+ if ($os -eq "win") {
+ tar -cvzf $path\libnethost.tar *
+ }
+ else {
+ tar -cvzf "$path/libnethost.tar" *
+ }
-cd $start
\ No newline at end of file
+ Write-Host "Pack successfully"
+ Set-Location $start
+}
\ No newline at end of file
diff --git a/gen_local_win_env.ps1 b/gen_local_win_env.ps1
new file mode 100644
index 000000000..c92105ab7
--- /dev/null
+++ b/gen_local_win_env.ps1
@@ -0,0 +1,28 @@
+$start = $pwd
+
+$nethostPath = "runtime/nethost"
+$tmpPath = "runtime/tmp"
+
+if (Test-Path -Path $nethostPath) {
+ # Delete folder
+ Remove-Item -Path $nethostPath -Recurse -Force
+ Write-Host "Folder '$nethostPath' deleted"
+}
+
+New-Item -ItemType Directory -Path $nethostPath | Out-Null
+Write-Host "Folder '$nethostPath' created"
+
+if (Test-Path -Path $tmpPath) {
+ # Delete folder
+ Remove-Item -Path $tmpPath -Recurse -Force
+}
+
+New-Item -ItemType Directory -Path $tmpPath | Out-Null
+
+& "./download_dotnet.ps1" $tmpPath "win" $false
+
+$currentDir = Get-Location
+Copy-Item -Path "$currentDir\*" -Destination "$start/$nethostPath" -Recurse -Force
+
+Set-Location $start
+Remove-Item -Path $tmpPath -Recurse -Force
\ No newline at end of file
diff --git a/runtime b/runtime
index bdc14db3b..7da6d1961 160000
--- a/runtime
+++ b/runtime
@@ -1 +1 @@
-Subproject commit bdc14db3b55e1cf96c0b85ba90ee7e573a47d929
+Subproject commit 7da6d196177f3bf7d712560fc3cfc2d010e56207