Some works for the new resource system

Rewrite some parts of CustomEvent
Expose some API as dll entry
This commit is contained in:
Sardelka9515
2023-02-01 21:21:07 +08:00
parent d4df041c44
commit f1b9bf0571
23 changed files with 558 additions and 223 deletions

View File

@ -49,9 +49,4 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="ScriptHookVDotNetCore">
<HintPath>..\..\libs\ScriptHookVDotNetCore.dll</HintPath>
</Reference>
</ItemGroup>
</Project> </Project>

View File

@ -42,6 +42,16 @@ namespace RageCoop.Client
DiagnosticMenu.Add( DiagnosticMenu.Add(
new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString())); new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
}; };
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
};
ShowOwnerItem.CheckboxChanged += (s, e) =>
{
Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
Util.SaveSettings();
};
#if DEBUG
SimulatedLatencyItem.Activated += (s, e) => SimulatedLatencyItem.Activated += (s, e) =>
{ {
try try
@ -55,16 +65,8 @@ namespace RageCoop.Client
Main.Logger.Error(ex); Main.Logger.Error(ex);
} }
}; };
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
};
ShowOwnerItem.CheckboxChanged += (s, e) =>
{
Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
Util.SaveSettings();
};
Menu.Add(SimulatedLatencyItem); Menu.Add(SimulatedLatencyItem);
#endif
Menu.Add(ShowNetworkInfoItem); Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem); Menu.Add(ShowOwnerItem);
Menu.AddSubMenu(DiagnosticMenu); Menu.AddSubMenu(DiagnosticMenu);

View File

@ -31,7 +31,6 @@ namespace RageCoop.Client
static Networking() static Networking()
{ {
Security = new Security(Main.Logger); Security = new Security(Main.Logger);
Packets.CustomEvent.ResolveHandle = _resolveHandle;
} }
public static float Latency => ServerConnection.AverageRoundtripTime / 2; public static float Latency => ServerConnection.AverageRoundtripTime / 2;
@ -65,12 +64,14 @@ namespace RageCoop.Client
var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0") var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{ {
AutoFlushSendQueue = false, AutoFlushSendQueue = false,
SimulatedMinimumLatency = SimulatedLatency,
SimulatedRandomLatency = 0,
AcceptIncomingConnections = true, AcceptIncomingConnections = true,
MaximumConnections = 32, MaximumConnections = 32,
PingInterval = 5 PingInterval = 5
}; };
#if DEBUG
config.SimulatedMinimumLatency = SimulatedLatency;
config.SimulatedRandomLatency = 0;
#endif
config.EnableMessageType(NetIncomingMessageType.UnconnectedData); config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess); config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
@ -153,7 +154,7 @@ namespace RageCoop.Client
} }
IsConnecting = false; IsConnecting = false;
},"Connect"); }, "Connect");
} }
} }

View File

@ -12,25 +12,7 @@ namespace RageCoop.Client
{ {
internal static partial class Networking internal static partial class Networking
{ {
/// <summary>
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent" />
/// </summary>
private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
{
switch (t)
{
case 50:
return EntityPool.ServerProps[reader.ReadInt32()].MainProp?.Handle;
case 51:
return EntityPool.GetPedByID(reader.ReadInt32())?.MainPed?.Handle;
case 52:
return EntityPool.GetVehicleByID(reader.ReadInt32())?.MainVehicle?.Handle;
case 60:
return EntityPool.ServerBlips[reader.ReadInt32()].Handle;
default:
throw new ArgumentException("Cannot resolve server side argument: " + t);
}
};
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false); private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);

View File

@ -1,4 +1,7 @@
using System.Reflection; 
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Resources; using System.Resources;
// General Information // General Information
@ -13,6 +16,7 @@ using System.Resources;
// Version information // Version information
[assembly: AssemblyVersion("1.6.0.24")] [assembly: AssemblyVersion("1.6.0.27")]
[assembly: AssemblyFileVersion("1.6.0.24")] [assembly: AssemblyFileVersion("1.6.0.27")]
[assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -33,12 +33,6 @@
<HintPath>..\..\libs\LemonUI.SHVDNC.dll</HintPath> <HintPath>..\..\libs\LemonUI.SHVDNC.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="..\..\.editorconfig">
<Link>.editorconfig</Link>
</None>
<None Include="Scripting\API.Exports.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="icon.png"> <Content Include="icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@ -0,0 +1,97 @@
using Lidgren.Network;
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using static RageCoop.Core.Scripting.CustomEvents;
namespace RageCoop.Client.Scripting
{
public static unsafe partial class API
{
[UnmanagedCallersOnly(EntryPoint = "Connect")]
public static void Connect(char* address) => Connect(new string(address));
[UnmanagedCallersOnly(EntryPoint = "GetLocalPlayerID")]
public static int GetLocalPlayerID() => LocalPlayerID;
/// <summary>
/// Get configuration value
/// </summary>
/// <param name="szName">The name of the config</param>
/// <param name="buf">Buffer to store retrived value</param>
/// <param name="bufSize">Buffer size</param>
/// <returns>The string length of returned value, not including the null terminator</returns>
[UnmanagedCallersOnly(EntryPoint = "GetConfigValue")]
public static int GetConfigValue(char* szName, char* buf, int bufSize)
{
var name = new string(szName);
var value = name switch
{
nameof(Config.EnableAutoRespawn) => Config.EnableAutoRespawn.ToString(),
nameof(Config.Username) => Config.Username.ToString(),
nameof(Config.BlipColor) => Config.BlipColor.ToString(),
nameof(Config.BlipScale) => Config.BlipScale.ToString(),
nameof(Config.BlipSprite) => Config.BlipSprite.ToString(),
_ => null
};
if (value == null)
return 0;
fixed (char* p = value)
{
var cbRequired = (value.Length + 1) * sizeof(char);
Buffer.MemoryCopy(p, buf, bufSize, cbRequired);
return value.Length;
}
}
[UnmanagedCallersOnly(EntryPoint = "SetConfigValue")]
public static void SetConfigValue(char* szName, char* szValue)
{
var name = new string(szName);
var value = new string(szValue);
switch (name)
{
case nameof(Config.EnableAutoRespawn): Config.EnableAutoRespawn = bool.Parse(value); break;
case nameof(Config.Username): Config.Username = value; break;
case nameof(Config.BlipColor): Config.BlipColor = Enum.Parse<BlipColor>(value); break;
case nameof(Config.BlipScale): Config.BlipScale = float.Parse(value); break;
case nameof(Config.BlipSprite): Config.BlipSprite = Enum.Parse<BlipSprite>(value); break;
};
}
[UnmanagedCallersOnly(EntryPoint = "LocalChatMessage")]
public static void LocalChatMessage(char* from, char* msg) => LocalChatMessage(new string(from), new string(msg));
[UnmanagedCallersOnly(EntryPoint = "SendChatMessage")]
public static void SendChatMessage(char* msg) => SendChatMessage(new string(msg));
[UnmanagedCallersOnly(EntryPoint = "GetEventHash")]
public static CustomEventHash GetEventHash(char* name)=>new string(name);
public static void SendCustomEvent(int hash, CustomEventFlags flags, byte* data, int cbData)
{
}
/// <summary>
/// Convert Entity ID to handle
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "IdToHandle")]
public static int IdToHandle(byte type, int id)
{
return type switch
{
T_ID_PROP => EntityPool.GetPropByID(id)?.MainProp?.Handle ?? 0,
T_ID_PED => EntityPool.GetPedByID(id)?.MainPed?.Handle ?? 0,
T_ID_VEH => EntityPool.GetVehicleByID(id)?.MainVehicle?.Handle ?? 0,
T_ID_BLIP => EntityPool.GetBlipByID(id)?.Handle ?? 0,
_ => 0,
};
}
}
}

View File

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using GTA; using GTA;
using Lidgren.Network; using Lidgren.Network;
@ -30,7 +31,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Provides vital functionality to interact with RAGECOOP /// Provides vital functionality to interact with RAGECOOP
/// </summary> /// </summary>
public static class API public static unsafe partial class API
{ {
#region INTERNAL #region INTERNAL
@ -270,6 +271,7 @@ namespace RageCoop.Client.Scripting
/// <summary> /// <summary>
/// Disconnect from current server or cancel the connection attempt. /// Disconnect from current server or cancel the connection attempt.
/// </summary> /// </summary>
[UnmanagedCallersOnly(EntryPoint = "Disconnect")]
public static void Disconnect() public static void Disconnect()
{ {
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null); if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
@ -369,9 +371,9 @@ namespace RageCoop.Client.Scripting
}; };
DownloadManager.DownloadCompleted += handler; DownloadManager.DownloadCompleted += handler;
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest
{ {
Name = name Name = name
}, },
p => p =>
{ {
if (p.Response != FileResponse.Loaded) if (p.Response != FileResponse.Loaded)

View File

@ -84,14 +84,10 @@ namespace RageCoop.Client
#region PEDS #region PEDS
public static SyncedPed GetPedByID(int id) public static SyncedPed GetPedByID(int id)
{ => PedsByID.TryGetValue(id, out var p) ? p : null;
return PedsByID.TryGetValue(id, out var p) ? p : null;
}
public static SyncedPed GetPedByHandle(int handle) public static SyncedPed GetPedByHandle(int handle)
{ => PedsByHandle.TryGetValue(handle, out var p) ? p : null;
return PedsByHandle.TryGetValue(handle, out var p) ? p : null;
}
public static List<int> GetPedIDs() public static List<int> GetPedIDs()
{ {
@ -179,14 +175,10 @@ namespace RageCoop.Client
#region VEHICLES #region VEHICLES
public static SyncedVehicle GetVehicleByID(int id) public static SyncedVehicle GetVehicleByID(int id)
{ => VehiclesByID.TryGetValue(id, out var v) ? v : null;
return VehiclesByID.TryGetValue(id, out var v) ? v : null;
}
public static SyncedVehicle GetVehicleByHandle(int handle) public static SyncedVehicle GetVehicleByHandle(int handle)
{ => VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
return VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
}
public static List<int> GetVehicleIDs() public static List<int> GetVehicleIDs()
{ {
@ -233,9 +225,7 @@ namespace RageCoop.Client
#region PROJECTILES #region PROJECTILES
public static SyncedProjectile GetProjectileByID(int id) public static SyncedProjectile GetProjectileByID(int id)
{ => ProjectilesByID.TryGetValue(id, out var p) ? p : null;
return ProjectilesByID.TryGetValue(id, out var p) ? p : null;
}
public static void Add(SyncedProjectile p) public static void Add(SyncedProjectile p)
{ {
@ -290,6 +280,16 @@ namespace RageCoop.Client
#endregion #endregion
#region SERVER OBJECTS
public static SyncedProp GetPropByID(int id)
=> ServerProps.TryGetValue(id, out var p) ? p : null;
public static Blip GetBlipByID(int id)
=> ServerBlips.TryGetValue(id, out var p) ? p : null;
#endregion
private static int vehStateIndex; private static int vehStateIndex;
private static int pedStateIndex; private static int pedStateIndex;
private static int vehStatesPerFrame; private static int vehStatesPerFrame;

View File

@ -1,59 +0,0 @@
using System.IO;
using System.Text;
using GTA.Math;
namespace RageCoop.Core
{
internal class BitReader : BinaryReader
{
public BitReader(byte[] array) : base(new MemoryStream(array))
{
}
~BitReader()
{
Close();
Dispose();
}
public byte[] ReadByteArray()
{
return base.ReadBytes(ReadInt32());
}
public override string ReadString()
{
return Encoding.UTF8.GetString(ReadBytes(ReadInt32()));
}
public Vector3 ReadVector3()
{
return new Vector3
{
X = ReadSingle(),
Y = ReadSingle(),
Z = ReadSingle()
};
}
public Vector2 ReadVector2()
{
return new Vector2
{
X = ReadSingle(),
Y = ReadSingle()
};
}
public Quaternion ReadQuaternion()
{
return new Quaternion
{
X = ReadSingle(),
Y = ReadSingle(),
Z = ReadSingle(),
W = ReadSingle()
};
}
}
}

View File

@ -5,10 +5,21 @@ using GTA.Math;
namespace RageCoop.Core namespace RageCoop.Core
{ {
internal unsafe abstract class BufferBase public unsafe abstract class BufferBase
{ {
/// <summary>
/// Size of this buffer in memory
/// </summary>
public int Size { get; protected set; } public int Size { get; protected set; }
public int CurrentIndex { get; protected set; }
/// <summary>
/// The current read/write index
/// </summary>
public int Position { get; protected set; }
/// <summary>
/// Pointer to the start of this buffer
/// </summary>
public byte* Address { get; protected set; } public byte* Address { get; protected set; }
/// <summary> /// <summary>
@ -20,9 +31,17 @@ namespace RageCoop.Core
protected T* Alloc<T>(int count = 1) where T : unmanaged protected T* Alloc<T>(int count = 1) where T : unmanaged
=> (T*)Alloc(count * sizeof(T)); => (T*)Alloc(count * sizeof(T));
/// <summary>
/// Reset position to the start of this buffer
/// </summary>
public void Reset()
{
Position = 0;
}
} }
internal unsafe sealed class WriteBuffer : BufferBase public unsafe sealed class WriteBuffer : BufferBase
{ {
public WriteBuffer(int size) public WriteBuffer(int size)
{ {
@ -49,12 +68,12 @@ namespace RageCoop.Core
protected override byte* Alloc(int cbSize) protected override byte* Alloc(int cbSize)
{ {
var index = CurrentIndex; var index = Position;
CurrentIndex += cbSize; Position += cbSize;
// Resize the buffer by at least 50% if there's no sufficient space // Resize the buffer by at least 50% if there's no sufficient space
if (CurrentIndex > Size) if (Position > Size)
Resize(Math.Max(CurrentIndex + 1, (int)(Size * 1.5f))); Resize(Math.Max(Position + 1, (int)(Size * 1.5f)));
return Address + index; return Address + index;
} }
@ -113,22 +132,83 @@ namespace RageCoop.Core
faddr[2] = quat.Z; faddr[2] = quat.Z;
faddr[3] = quat.W; faddr[3] = quat.W;
} }
public void Write<T>(ReadOnlySpan<T> source) where T : unmanaged
{
var len = source.Length;
fixed (T* pSource = source)
{
Buffer.MemoryCopy(pSource, Alloc(sizeof(T) * len), len, len);
}
}
public void Write<T>(Span<T> source) where T : unmanaged => Write((ReadOnlySpan<T>)source);
/// <summary>
/// Write an array, prefix the data with its length so it can latter be read using <see cref="ReadBuffer.ReadArray{T}"/>
/// </summary>
public void WriteArray<T>(T[] values) where T : unmanaged
{
var len = values.Length;
WriteVal(len);
fixed (T* pFrom = values)
{
Buffer.MemoryCopy(pFrom, Alloc(sizeof(T) * len), len, len);
}
}
/// <summary>
/// Allocate a byte array on managed heap and copy the data of specified size to it
/// </summary>
/// <param name="cbSize"></param>
/// <returns>The newly created managed byte array</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public byte[] ToByteArray(int cbSize)
{
if (cbSize > Size)
throw new ArgumentOutOfRangeException(nameof(cbSize));
var result = new byte[cbSize];
fixed (byte* pResult = result)
{
Buffer.MemoryCopy(Address, pResult, Size, Size);
}
return result;
}
/// <summary>
/// Free the associated memory allocated on the unmanaged heap
/// </summary>
public void Free() => Marshal.FreeHGlobal((IntPtr)Address);
} }
internal unsafe sealed class ReadBuffer : BufferBase public unsafe sealed class ReadBuffer : BufferBase
{ {
public ReadBuffer(byte* address, int size) /// <summary>
/// Initialize an empty instance, needs to call <see cref="Initialise(byte*, int)"/> before reading data
/// </summary>
public ReadBuffer()
{
}
public ReadBuffer(byte* address, int size) => Initialise(address, size);
public void Initialise(byte* address, int size)
{ {
Address = address; Address = address;
Size = size; Size = size;
Reset();
} }
protected override byte* Alloc(int cbSize) protected override byte* Alloc(int cbSize)
{ {
var index = CurrentIndex; if (Address == null)
CurrentIndex += cbSize; throw new NullReferenceException("Address is null");
if (CurrentIndex > Size) var index = Position;
Position += cbSize;
if (Position > Size)
throw new InvalidOperationException("Attempting to read beyond the existing buffer"); throw new InvalidOperationException("Attempting to read beyond the existing buffer");
return Address + index; return Address + index;
@ -178,5 +258,36 @@ namespace RageCoop.Core
W = faddr[3], W = faddr[3],
}; };
} }
/// <summary>
/// Read a span of type <typeparamref name="T"/> from current position to <paramref name="destination"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="destination"></param>
public void Read<T>(Span<T> destination) where T : unmanaged
{
var len = destination.Length;
fixed (T* pTo = destination)
{
Buffer.MemoryCopy(Alloc(len * sizeof(T)), pTo, len, len);
}
}
/// <summary>
/// Reads an array previously written using <see cref="WriteBuffer.WriteArray{T}(T[])"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T[] ReadArray<T>() where T : unmanaged
{
var len = ReadVal<int>();
var from = Alloc<T>(len);
var result = new T[len];
fixed (T* pTo = result)
{
Buffer.MemoryCopy(from, pTo, len, len);
}
return result;
}
} }
} }

View File

@ -9,7 +9,6 @@ namespace RageCoop.Core
{ {
internal class CustomEvent : Packet internal class CustomEvent : Packet
{ {
public static Func<byte, NetIncomingMessage, object> ResolveHandle = null;
public CustomEventFlags Flags; public CustomEventFlags Flags;
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None) public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
@ -19,77 +18,36 @@ namespace RageCoop.Core
public override PacketType Type => PacketType.CustomEvent; public override PacketType Type => PacketType.CustomEvent;
public int Hash { get; set; } public int Hash { get; set; }
public object[] Args { get; set; } public byte[] Payload;
public object[] Args;
protected override void Serialize(NetOutgoingMessage m) protected override void Serialize(NetOutgoingMessage m)
{ {
Args = Args ?? new object[] { };
m.Write((byte)Flags); m.Write((byte)Flags);
m.Write(Hash); m.Write(Hash);
m.Write(Args.Length); if (Args != null)
foreach (var arg in Args) CoreUtils.GetBytesFromObject(arg, m); {
lock (WriteBufferShared)
{
WriteBufferShared.Reset();
CustomEvents.WriteObjects(WriteBufferShared, Args);
Payload = WriteBufferShared.ToByteArray(WriteBufferShared.Position);
}
}
m.Write(Payload);
} }
public override void Deserialize(NetIncomingMessage m) public unsafe override void Deserialize(NetIncomingMessage m)
{ {
Flags = (CustomEventFlags)m.ReadByte(); Flags = (CustomEventFlags)m.ReadByte();
Hash = m.ReadInt32(); Hash = m.ReadInt32();
Args = new object[m.ReadInt32()]; Payload = m.ReadBytes(m.LengthBytes - m.PositionInBytes);
for (var i = 0; i < Args.Length; i++) fixed (byte* p = Payload)
{ {
var type = m.ReadByte(); lock (ReadBufferShared)
switch (type)
{ {
case 0x01: ReadBufferShared.Initialise(p,Payload.Length);
Args[i] = m.ReadByte(); Args = CustomEvents.ReadObjects(ReadBufferShared);
break;
case 0x02:
Args[i] = m.ReadInt32();
break;
case 0x03:
Args[i] = m.ReadUInt16();
break;
case 0x04:
Args[i] = m.ReadInt32();
break;
case 0x05:
Args[i] = m.ReadUInt32();
break;
case 0x06:
Args[i] = m.ReadInt64();
break;
case 0x07:
Args[i] = m.ReadUInt64();
break;
case 0x08:
Args[i] = m.ReadFloat();
break;
case 0x09:
Args[i] = m.ReadBoolean();
break;
case 0x10:
Args[i] = m.ReadString();
break;
case 0x11:
Args[i] = m.ReadVector3();
break;
case 0x12:
Args[i] = m.ReadQuaternion();
break;
case 0x13:
Args[i] = (Model)m.ReadInt32();
break;
case 0x14:
Args[i] = m.ReadVector2();
break;
case 0x15:
Args[i] = m.ReadByteArray();
break;
default:
if (ResolveHandle == null) throw new InvalidOperationException($"Unexpected type: {type}");
Args[i] = ResolveHandle(type, m);
break;
} }
} }
} }

View File

@ -152,6 +152,9 @@ namespace RageCoop.Core
internal abstract class Packet : IPacket internal abstract class Packet : IPacket
{ {
public static WriteBuffer WriteBufferShared = new(1024);
public static ReadBuffer ReadBufferShared = new();
public abstract PacketType Type { get; } public abstract PacketType Type { get; }
public virtual void Deserialize(NetIncomingMessage m) public virtual void Deserialize(NetIncomingMessage m)

View File

@ -1,5 +1,8 @@
using System; using GTA;
using GTA.Math;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -94,7 +97,7 @@ namespace RageCoop.Core.Scripting
/// <summary> /// <summary>
/// </summary> /// </summary>
public static class CustomEvents public static partial class CustomEvents
{ {
internal static readonly CustomEventHash OnPlayerDied = "RageCoop.OnPlayerDied"; internal static readonly CustomEventHash OnPlayerDied = "RageCoop.OnPlayerDied";
internal static readonly CustomEventHash SetWeather = "RageCoop.SetWeather"; internal static readonly CustomEventHash SetWeather = "RageCoop.SetWeather";
@ -116,17 +119,177 @@ namespace RageCoop.Core.Scripting
internal static readonly CustomEventHash WeatherTimeSync = "RageCoop.WeatherTimeSync"; internal static readonly CustomEventHash WeatherTimeSync = "RageCoop.WeatherTimeSync";
internal static readonly CustomEventHash IsHost = "RageCoop.IsHost"; internal static readonly CustomEventHash IsHost = "RageCoop.IsHost";
/// <summary> #region TYPE CONSTANTS
/// Get event hash from string.
/// </summary> public const byte T_BYTE = 1;
/// <returns></returns> public const byte T_SHORT = 2;
/// <remarks> public const byte T_USHORT = 3;
/// This method is obsoete, you should use implicit operator or <see cref="CustomEventHash.FromString(string)" /> public const byte T_INT = 4;
/// </remarks> public const byte T_UINT = 5;
[Obsolete] public const byte T_LONG = 6;
public static int Hash(string s) public const byte T_ULONG = 7;
public const byte T_FLOAT = 8;
public const byte T_BOOL = 9;
public const byte T_STR = 10;
public const byte T_VEC3 = 11;
public const byte T_QUAT = 12;
public const byte T_MODEL = 13;
public const byte T_VEC2 = 14;
public const byte T_BYTEARR = 15;
public const byte T_ID_PROP = 50;
public const byte T_ID_PED = 51;
public const byte T_ID_VEH = 52;
public const byte T_ID_BLIP = 60;
#endregion
public static void WriteObjects(WriteBuffer b, params object[] objs)
{ {
return CustomEventHash.FromString(s); b.WriteVal(objs.Length);
foreach(var obj in objs)
{
switch (obj)
{
case byte value:
b.WriteVal(T_BYTE);
b.WriteVal(value);
break;
case short value:
b.WriteVal(T_SHORT);
b.WriteVal(value);
break;
case ushort value:
b.WriteVal(T_USHORT);
b.WriteVal(value);
break;
case int value:
b.WriteVal(T_INT);
b.WriteVal(value);
break;
case uint value:
b.WriteVal(T_UINT);
b.WriteVal(value);
break;
case long value:
b.WriteVal(T_LONG);
b.WriteVal(value);
break;
case ulong value:
b.WriteVal(T_ULONG);
b.WriteVal(value);
break;
case float value:
b.WriteVal(T_FLOAT);
b.WriteVal(value);
break;
case bool value:
b.WriteVal(T_BOOL);
b.WriteVal(value);
break;
case string value:
b.WriteVal(T_STR);
b.Write(value);
break;
case Vector2 value:
b.WriteVal(T_VEC2);
b.Write(ref value);
break;
case Vector3 value:
b.WriteVal(T_VEC3);
b.Write(ref value);
break;
case Quaternion value:
b.WriteVal(T_QUAT);
b.Write(ref value);
break;
case Model value:
b.WriteVal(T_MODEL);
b.WriteVal(value);
break;
case byte[] value:
b.WriteVal(T_BYTEARR);
b.WriteArray(value);
break;
case Tuple<byte, byte[]> value:
b.WriteVal(value.Item1);
b.Write(new ReadOnlySpan<byte>(value.Item2));
break;
default:
throw new Exception("Unsupported object type: " + obj.GetType());
}
}
} }
public static object[] ReadObjects(ReadBuffer r)
{
var Args = new object[r.ReadVal<int>()];
for (var i = 0; i < Args.Length; i++)
{
var type = r.ReadVal<byte>();
switch (type)
{
case T_BYTE:
Args[i] = r.ReadVal<byte>();
break;
case T_SHORT:
Args[i] = r.ReadVal<short>();
break;
case T_USHORT:
Args[i] = r.ReadVal<ushort>();
break;
case T_INT:
Args[i] = r.ReadVal<int>();
break;
case T_UINT:
Args[i] = r.ReadVal<uint>();
break;
case T_LONG:
Args[i] = r.ReadVal<long>();
break;
case T_ULONG:
Args[i] = r.ReadVal<ulong>();
break;
case T_FLOAT:
Args[i] = r.ReadVal<float>();
break;
case T_BOOL:
Args[i] = r.ReadVal<bool>();
break;
case T_STR:
r.Read(out string str);
Args[i] = str;
break;
case T_VEC3:
r.Read(out Vector3 vec);
Args[i] = vec;
break;
case T_QUAT:
r.Read(out Quaternion quat);
Args[i] = quat;
break;
case T_MODEL:
Args[i] = r.ReadVal<Model>();
break;
case T_VEC2:
r.Read(out Vector2 vec2);
Args[i] = vec2;
break;
case T_BYTEARR:
Args[i] = r.ReadArray<byte>();
break;
case T_ID_BLIP:
case T_ID_PED:
case T_ID_PROP:
case T_ID_VEH:
Args[i] = IdToHandle(type,r.ReadVal<int>());
break;
default:
throw new InvalidOperationException($"Unexpected type: {type}");
}
}
return Args;
}
[LibraryImport("RageCoop.Client.dll")]
public static partial int IdToHandle(byte type,int id);
} }
} }

54
Core/XSpan.cs Normal file
View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
/// <summary>
/// A light-weight and less restricted implementation of <see cref="Span{T}"/>, gonna be used at some point, maybe?
/// </summary>
/// <typeparam name="T"></typeparam>
public readonly unsafe struct XSpan<T> where T : unmanaged
{
public XSpan(void* address, int len)
{
Address = (T*)address;
Length = len;
}
public T this[int i]
{
get { return Address[i]; }
set { Address[i] = value; }
}
public readonly T* Address;
public readonly int Length;
public void CopyTo(XSpan<T> dest, int destStart = 0)
{
for (int i = 0; i < Length; i++)
{
dest[destStart + i] = this[i];
}
}
public XSpan<byte> Slice(int start) => new(Address + start, Length - start);
public XSpan<byte> Slice(int start, int len) => new(Address + start, len);
public static implicit operator Span<T>(XSpan<T> s)
{
return new Span<T>(s.Address, s.Length);
}
public static implicit operator XSpan<T>(Span<T> s)
{
fixed (T* ptr = s)
{
return new XSpan<T>(ptr, s.Length);
}
}
}
}

View File

@ -0,0 +1,7 @@
namespace RageCoop.Client.Scripting
{
public static class API
{
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
internal class ClientScript
{
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Core\RageCoop.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -15,7 +15,7 @@ using System.Resources;
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
// Version information // Version information
[assembly: AssemblyVersion("1.6.0.25")] [assembly: AssemblyVersion("1.6.0.28")]
[assembly: AssemblyFileVersion("1.6.0.25")] [assembly: AssemblyFileVersion("1.6.0.28")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )] [assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -6,6 +6,7 @@ using GTA.Math;
using GTA.Native; using GTA.Native;
using RageCoop.Core; using RageCoop.Core;
using RageCoop.Core.Scripting; using RageCoop.Core.Scripting;
using static RageCoop.Core.Scripting.CustomEvents;
namespace RageCoop.Server.Scripting; namespace RageCoop.Server.Scripting;
@ -78,11 +79,11 @@ public abstract class ServerObject
switch (this) switch (this)
{ {
case ServerProp _: case ServerProp _:
return 50; return T_ID_PROP;
case ServerPed _: case ServerPed _:
return 51; return T_ID_PED;
case ServerVehicle _: case ServerVehicle _:
return 52; return T_ID_VEH;
default: default:
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -280,7 +281,7 @@ public class ServerBlip
/// <summary> /// <summary>
/// Pass this as an argument in CustomEvent or NativeCall to convert this object to handle at client side. /// Pass this as an argument in CustomEvent or NativeCall to convert this object to handle at client side.
/// </summary> /// </summary>
public Tuple<byte, byte[]> Handle => new(60, BitConverter.GetBytes(ID)); public Tuple<byte, byte[]> Handle => new(T_ID_BLIP, BitConverter.GetBytes(ID));
/// <summary> /// <summary>
/// Network ID (not handle!) /// Network ID (not handle!)

View File

@ -39,7 +39,7 @@ namespace UnitTest
buf.Write(e.str); buf.Write(e.str);
} }
Console.WriteLine($"Buffer size: {buf.Size}"); Console.WriteLine($"Buffer size: {buf.Size}");
Console.WriteLine($"Current position: {buf.CurrentIndex}"); Console.WriteLine($"Current position: {buf.Position}");
Console.WriteLine("Validating data"); Console.WriteLine("Validating data");
var reader = new ReadBuffer(buf.Address, buf.Size); var reader = new ReadBuffer(buf.Address, buf.Size);

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -11,10 +11,4 @@
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" /> <ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="ScriptHookVDotNetCore">
<HintPath>..\..\libs\ScriptHookVDotNetCore.dll</HintPath>
</Reference>
</ItemGroup>
</Project> </Project>

View File

@ -1,3 +1,4 @@
git submodule update --init
if exist "bin" rmdir /s /q "bin" if exist "bin" rmdir /s /q "bin"
dotnet build -c Release dotnet build -c Release
dotnet build -c API Core\RageCoop.Core.csproj dotnet build -c API Core\RageCoop.Core.csproj