Rework on SendNativeCall

This commit is contained in:
Sardelka
2022-06-23 09:46:38 +08:00
parent 84171e2949
commit 41385abc7d
14 changed files with 369 additions and 137 deletions

View File

@ -111,68 +111,7 @@ namespace RageCoop.Client
}
private static object DecodeNativeCall(ulong hash, List<object> args, bool returnValue, byte? returnType = null)
{
List<InputArgument> arguments = new List<InputArgument>();
if (args == null || args.Count == 0)
{
return null;
}
for (ushort i = 0; i < args.Count; i++)
{
object x = args.ElementAt(i);
switch (x)
{
case int _:
arguments.Add((int)x);
break;
case bool _:
arguments.Add((bool)x);
break;
case float _:
arguments.Add((float)x);
break;
case string _:
arguments.Add((string)x);
break;
case Vector3 _:
Vector3 vector = (Vector3)x;
arguments.Add((float)vector.X);
arguments.Add((float)vector.Y);
arguments.Add((float)vector.Z);
break;
default:
GTA.UI.Notification.Show("[DecodeNativeCall][" + hash + "]: Type of argument not found!");
return null;
}
}
if (!returnValue)
{
Function.Call((Hash)hash, arguments.ToArray());
return null;
}
switch (returnType.Value)
{
case 0x00: // int
return Function.Call<int>((Hash)hash, arguments.ToArray());
case 0x01: // bool
return Function.Call<bool>((Hash)hash, arguments.ToArray());
case 0x02: // float
return Function.Call<float>((Hash)hash, arguments.ToArray());
case 0x03: // string
return Function.Call<string>((Hash)hash, arguments.ToArray());
case 0x04: // vector3
return Function.Call<Vector3>((Hash)hash, arguments.ToArray());
default:
GTA.UI.Notification.Show("[DecodeNativeCall][" + hash + "]: Type of return not found!");
return null;
}
}
/*
private static void DecodeNativeCallWithResponse(Packets.NativeResponse packet)
{
object result = DecodeNativeCall(packet.Hash, packet.Args, true, packet.ResultType);
@ -202,6 +141,7 @@ namespace RageCoop.Client
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
Client.FlushSendQueue();
}
*/
#endregion // -- PLAYER --
#endregion

View File

@ -184,26 +184,6 @@ namespace RageCoop.Client
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketTypes.NativeCall:
{
Packets.NativeCall packet = new Packets.NativeCall();
packet.Unpack(data);
Main.QueueAction(() => { DecodeNativeCall(packet.Hash, packet.Args, false); });
}
break;
case PacketTypes.NativeResponse:
{
Packets.NativeResponse packet = new Packets.NativeResponse();
packet.Unpack(data);
DecodeNativeCallWithResponse(packet);
}
break;
case PacketTypes.CustomEvent:

View File

@ -95,6 +95,9 @@ namespace RageCoop.Client.Scripting
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
{
var args = new CustomEventReceivedArgs() { Hash=p.Hash, Args=p.Args};
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
List<Action<CustomEventReceivedArgs>> handlers;
if (CustomEventHandlers.TryGetValue(p.Hash, out handlers))
{
@ -210,7 +213,6 @@ namespace RageCoop.Client.Scripting
Hash=eventHash
};
Networking.Send(p, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <summary>

View File

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA.Native;
using GTA;
using RageCoop.Core;
using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
@ -12,6 +15,7 @@ namespace RageCoop.Client.Scripting
public override void OnStart()
{
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn);
API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall);
}
public override void OnStop()
@ -21,5 +25,125 @@ namespace RageCoop.Client.Scripting
{
API.Config.EnableAutoRespawn=(bool)args.Args[0];
}
private void NativeCall(CustomEventReceivedArgs e)
{
List<InputArgument> arguments = new List<InputArgument>();
int i;
var ty = (byte)e.Args[0];
Main.Logger.Debug($"gettype");
Main.Logger.Flush();
TypeCode returnType=(TypeCode)ty;
Main.Logger.Debug($"typeok");
Main.Logger.Flush();
i = returnType==TypeCode.Empty ? 1 : 2;
var hash = (Hash)e.Args[i++];
for(; i<e.Args.Count;i++)
{
arguments.Add(GetInputArgument(e.Args[i]));
}
Main.QueueAction(() =>
{
if (returnType==TypeCode.Empty)
{
Function.Call(hash, arguments.ToArray());
return;
}
var t = returnType;
int id = (int)e.Args[1];
switch (returnType)
{
case TypeCode.Boolean:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<bool>(hash, arguments.ToArray()) });
break;
case TypeCode.Byte:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<byte>(hash, arguments.ToArray()) });
break;
case TypeCode.Char:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<char>(hash, arguments.ToArray()) });
break;
case TypeCode.Single:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<float>(hash, arguments.ToArray()) });
break;
case TypeCode.Double:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<double>(hash, arguments.ToArray()) });
break;
case TypeCode.Int16:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<short>(hash, arguments.ToArray()) });
break;
case TypeCode.Int32:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<int>(hash, arguments.ToArray()) });
break;
case TypeCode.Int64:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<long>(hash, arguments.ToArray()) });
break;
case TypeCode.String:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<string>(hash, arguments.ToArray()) });
break;
case TypeCode.UInt16:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<ushort>(hash, arguments.ToArray()) });
break;
case TypeCode.UInt32:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<uint>(hash, arguments.ToArray()) });
break;
case TypeCode.UInt64:
API.SendCustomEvent(CustomEvents.NativeResponse,
new List<object> { id, Function.Call<ulong>(hash, arguments.ToArray()) });
break;
}
});
}
private InputArgument GetInputArgument(object obj)
{
// Implicit conversion
switch (obj)
{
case byte _:
return (byte)obj;
case short _:
return (short)obj;
case ushort _:
return (ushort)obj;
case int _:
return (int)obj;
case uint _:
return (uint)obj;
case long _:
return (long)obj;
case ulong _:
return (ulong)obj;
case float _:
return (float)obj;
case bool _:
return (bool)obj;
case string _:
return (obj as string);
default:
return null;
}
}
}
}

View File

@ -16,7 +16,7 @@ namespace RageCoop.Core
switch (obj)
{
case byte _:
return (0x01, BitConverter.GetBytes((byte)obj));
return (0x01, new byte[] { (byte)obj });
case short _:
return (0x02, BitConverter.GetBytes((short)obj));
case ushort _:
@ -120,5 +120,87 @@ namespace RageCoop.Core
{
return (flagToCheck & flag)!=0;
}
public static Type GetActualType(this TypeCode code)
{
switch (code)
{
case TypeCode.Boolean:
return typeof(bool);
case TypeCode.Byte:
return typeof(byte);
case TypeCode.Char:
return typeof(char);
case TypeCode.DateTime:
return typeof(DateTime);
case TypeCode.DBNull:
return typeof(DBNull);
case TypeCode.Decimal:
return typeof(decimal);
case TypeCode.Double:
return typeof(double);
case TypeCode.Empty:
return null;
case TypeCode.Int16:
return typeof(short);
case TypeCode.Int32:
return typeof(int);
case TypeCode.Int64:
return typeof(long);
case TypeCode.Object:
return typeof(object);
case TypeCode.SByte:
return typeof(sbyte);
case TypeCode.Single:
return typeof(Single);
case TypeCode.String:
return typeof(string);
case TypeCode.UInt16:
return typeof(UInt16);
case TypeCode.UInt32:
return typeof(UInt32);
case TypeCode.UInt64:
return typeof(UInt64);
}
return null;
}
public static string DumpWithType(this IEnumerable<object> objects)
{
StringBuilder sb = new StringBuilder();
foreach(var obj in objects)
{
sb.Append(obj.GetType()+":"+obj.ToString()+"\n");
}
return sb.ToString();
}
public static string Dump<T>(this IEnumerable<T> objects)
{
return "{"+string.Join(",",objects)+"}";
}
public static void ForEach<T>(this IEnumerable<T> objects,Action<T> action)
{
foreach(var obj in objects)
{
action(obj);
}
}
}
}

View File

@ -10,7 +10,7 @@ namespace RageCoop.Core
public class CustomEvent : Packet
{
public int Hash { get; set; }
public List<object> Args { get; set; }=new List<object>();
public List<object> Args { get; set; }
public override void Pack(NetOutgoingMessage message)
{
@ -23,7 +23,7 @@ namespace RageCoop.Core
foreach (var arg in Args)
{
tup=CoreUtils.GetBytesFromObject(arg);
if (tup.Item2==null)
if (tup.Item1==0||tup.Item2==null)
{
throw new ArgumentException($"Object of type {arg.GetType()} is not supported");
}
@ -41,10 +41,11 @@ namespace RageCoop.Core
Hash = reader.ReadInt();
var len=reader.ReadInt();
Args=new List<object>(len);
for (int i = 0; i < len; i++)
{
byte argType = reader.ReadByte();
switch (argType)
byte type = reader.ReadByte();
switch (type)
{
case 0x01:
Args.Add(reader.ReadByte());
@ -76,6 +77,9 @@ namespace RageCoop.Core
case 0x10:
Args.Add(reader.ReadString());
break;
default:
throw new InvalidOperationException($"Unexpected type:{type}\r\n{array.Dump()}");
}
}
}

View File

@ -15,8 +15,8 @@ namespace RageCoop.Core
PlayerInfoUpdate=3,
ChatMessage=10,
NativeCall=11,
NativeResponse=12,
// NativeCall=11,
// NativeResponse=12,
//Mod=13,
CleanUpWorld=14,
@ -191,7 +191,7 @@ namespace RageCoop.Core
#endregion
}
}
/*
#region ===== NATIVECALL =====
public class NativeCall : Packet
{
@ -423,6 +423,7 @@ namespace RageCoop.Core
}
}
#endregion // ===== NATIVECALL =====
*/
}
public static class CoopSerializer

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
namespace RageCoop.Core.Scripting
{
internal static class CustomEvents
{
static MD5 Hasher = MD5.Create();
public static int SendWeather = Hash("RageCoop.SendWeather");
public static int OnPlayerDied = Hash("RageCoop.OnPlayerDied");
public static int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn");
public static int Hash(string s)
{
return BitConverter.ToInt32(Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)), 0);
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
namespace RageCoop.Core.Scripting
{
/// <summary>
///
/// </summary>
public static class CustomEvents
{
static MD5 Hasher = MD5.Create();
static Dictionary<int,string> Hashed=new Dictionary<int,string>();
public static readonly int SetWeather = Hash("RageCoop.SetWeather");
public static readonly int OnPlayerDied = Hash("RageCoop.OnPlayerDied");
public static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn");
public static readonly int NativeCall = Hash("RageCoop.NativeCall");
// public static readonly int NativeCallNoResponse = Hash("RageCoop.NativeCallNoResponse");
public static readonly int NativeResponse = Hash("RageCoop.NativeResponse");
/// <summary>
/// Get a Int32 hash of a string.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">The exception is thrown when the name did not match a previously computed one and the hash was the same.</exception>
public static int Hash(string s)
{
var hash = BitConverter.ToInt32(Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)), 0);
string name;
lock (Hashed)
{
if (Hashed.TryGetValue(hash, out name))
{
if (name!=s)
{
throw new ArgumentException($"Hashed value has collision with another name:{name}, hashed value:{hash}");
}
else
{
return hash;
}
}
else
{
Hashed.Add(hash, s);
return hash;
}
}
}
}
}

View File

@ -4,6 +4,7 @@ using RageCoop.Core;
using Lidgren.Network;
using GTA.Math;
using RageCoop.Core.Scripting;
using System.Security.Cryptography;
namespace RageCoop.Server
{
@ -49,12 +50,11 @@ namespace RageCoop.Server
private PlayerConfig _config { get; set; }=new PlayerConfig();
public PlayerConfig Config { get { return _config; }set { _config=value;Server.SendPlayerInfos(); } }
private readonly Dictionary<string, object> _customData = new();
private long _callbacksCount = 0;
public readonly Dictionary<long, Action<object>> Callbacks = new();
internal readonly Dictionary<int, Action<object>> Callbacks = new();
public bool IsReady { get; internal set; }=false;
public string Username { get;internal set; } = "N/A";
#region CUSTOMDATA FUNCTIONS
/*
public void SetData<T>(string name, T data)
{
if (HasData(name))
@ -84,8 +84,9 @@ namespace RageCoop.Server
_customData.Remove(name);
}
}
#endregion
*/
#endregion
#region FUNCTIONS
public void Kick(string reason="You have been kicked!")
{
@ -113,7 +114,7 @@ namespace RageCoop.Server
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/*
/// <summary>
/// Send a native call to client and ignore its return value.
/// </summary>
@ -220,7 +221,53 @@ namespace RageCoop.Server
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
*/
/// <summary>
/// Send a native call to client and do a callback when the response received.
/// </summary>
/// <typeparam name="T">Type of the response</typeparam>
/// <param name="callBack"></param>
/// <param name="hash"></param>
/// <param name="args"></param>
public void SendNativeCall<T>(Action<object> callBack, GTA.Native.Hash hash, params object[] args)
{
var argsList= new List<object>(args);
argsList.InsertRange(0, new object[] { (byte)Type.GetTypeCode(typeof(T)), RequestNativeCallID<T>(callBack), (ulong)hash });
SendCustomEvent(CustomEvents.NativeCall, argsList);
}
/// <summary>
/// Send a native call to client and ignore it's response.
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
public void SendNativeCall(GTA.Native.Hash hash, params object[] args)
{
var argsList = new List<object>(args);
argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty,(ulong)hash});
// Program.Logger.Debug(argsList.DumpWithType());
SendCustomEvent(CustomEvents.NativeCall, argsList);
}
private int RequestNativeCallID<T>(Action<object> callback)
{
int ID = 0;
lock (Callbacks)
{
while ((ID==0)
|| Callbacks.ContainsKey(ID))
{
byte[] rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
Callbacks.Add(ID, callback);
}
return ID;
}
public void SendCleanUpWorld()
{
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
@ -239,7 +286,6 @@ namespace RageCoop.Server
if (!IsReady)
{
Program.Logger.Warning($"Player \"{Username}\" is not ready!");
return;
}
try

View File

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Net;
namespace RageCoop.Server.Scripting
@ -93,6 +94,7 @@ namespace RageCoop.Server.Scripting
}
#region FUNCTIONS
/*
/// <summary>
/// Send a native call (Function.Call) to all players.
/// Keys = int, float, bool, string and lvector3
@ -129,14 +131,14 @@ namespace RageCoop.Server.Scripting
Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
*/
/// <summary>
/// Get a list of all Clients
/// </summary>
/// <returns>All clients as a dictionary indexed by NetID</returns>
public static Dictionary<long, Client> GetAllClients()
{
return Server.Clients;
return new(Server.Clients);
}
/// <summary>
@ -270,6 +272,10 @@ namespace RageCoop.Server.Scripting
handlers.Add(handler);
}
}
public static void RegisterCustomEventHandler(string name, Action<CustomEventReceivedArgs> handler)
{
RegisterCustomEventHandler(CustomEvents.Hash(name), handler);
}
public static Logger GetLogger()
{
return Program.Logger;

View File

@ -11,6 +11,7 @@ namespace RageCoop.Server.Scripting
{
public override void OnStart()
{
API.RegisterCustomEventHandler(CustomEvents.NativeResponse, NativeResponse);
}
public override void OnStop()
{
@ -19,5 +20,26 @@ namespace RageCoop.Server.Scripting
{
c.SendCustomEvent(CustomEvents.SetAutoRespawn, new() { toggle });
}
void NativeResponse(CustomEventReceivedArgs e)
{
try
{
int id = (int)e.Args[0];
Action<object> callback;
lock (e.Sender.Callbacks)
{
if (e.Sender.Callbacks.TryGetValue(id, out callback))
{
callback(e.Args[1]);
e.Sender.Callbacks.Remove(id);
}
}
}
catch (Exception ex)
{
API.GetLogger().Error("Failed to parse NativeResponse");
API.GetLogger().Error(ex);
}
}
}
}

View File

@ -83,7 +83,15 @@ namespace RageCoop.Server.Scripting
{
foreach (var s in d.Scripts)
{
s.OnStop();
try
{
s.OnStop();
}
catch(Exception ex)
{
Logger?.Error(ex);
}
}
}
}

View File

@ -27,7 +27,7 @@ namespace RageCoop.Server
internal class Server
{
private static readonly string _compatibleVersion = "V0_5";
private static BaseScript BaseScript { get; set; }=new BaseScript();
public static BaseScript BaseScript { get; set; }=new BaseScript();
public static readonly Settings MainSettings = Util.Read<Settings>("Settings.xml");
public static NetServer MainNetServer;
@ -317,22 +317,6 @@ namespace RageCoop.Server
}
break;
case PacketTypes.NativeResponse:
{
Packets.NativeResponse packet = new();
packet.Unpack(data);
Client client = Util.GetClientByNetID(message.SenderConnection.RemoteUniqueIdentifier);
if (client != null)
{
if (client.Callbacks.ContainsKey(packet.ID))
{
client.Callbacks[packet.ID].Invoke(packet.Args[0]);
client.Callbacks.Remove(packet.ID);
}
}
}
break;
case PacketTypes.FileTransferComplete:
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();