diff --git a/RageCoop.Client/Networking/Networking.cs b/RageCoop.Client/Networking/Networking.cs index 6378ddf..5b88da5 100644 --- a/RageCoop.Client/Networking/Networking.cs +++ b/RageCoop.Client/Networking/Networking.cs @@ -111,68 +111,7 @@ namespace RageCoop.Client } - private static object DecodeNativeCall(ulong hash, List args, bool returnValue, byte? returnType = null) - { - List arguments = new List(); - - 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((Hash)hash, arguments.ToArray()); - case 0x01: // bool - return Function.Call((Hash)hash, arguments.ToArray()); - case 0x02: // float - return Function.Call((Hash)hash, arguments.ToArray()); - case 0x03: // string - return Function.Call((Hash)hash, arguments.ToArray()); - case 0x04: // vector3 - return Function.Call((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 diff --git a/RageCoop.Client/Networking/Receive.cs b/RageCoop.Client/Networking/Receive.cs index c6b2e3b..38830de 100644 --- a/RageCoop.Client/Networking/Receive.cs +++ b/RageCoop.Client/Networking/Receive.cs @@ -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: diff --git a/RageCoop.Client/Scripting/API.cs b/RageCoop.Client/Scripting/API.cs index 4125f5c..eafcce4 100644 --- a/RageCoop.Client/Scripting/API.cs +++ b/RageCoop.Client/Scripting/API.cs @@ -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> 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); - } /// diff --git a/RageCoop.Client/Scripting/BaseScript.cs b/RageCoop.Client/Scripting/BaseScript.cs index 21b8b78..869f728 100644 --- a/RageCoop.Client/Scripting/BaseScript.cs +++ b/RageCoop.Client/Scripting/BaseScript.cs @@ -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 arguments = new List(); + 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 + { + + 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 { id, Function.Call(hash, arguments.ToArray()) }); + break; + case TypeCode.Byte: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + case TypeCode.Char: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.Single: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.Double: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.Int16: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.Int32: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.Int64: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.String: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.UInt16: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.UInt32: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(hash, arguments.ToArray()) }); + break; + + case TypeCode.UInt64: + API.SendCustomEvent(CustomEvents.NativeResponse, + new List { id, Function.Call(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; + } + } } } diff --git a/RageCoop.Core/CoreUtils.cs b/RageCoop.Core/CoreUtils.cs index f3d16ac..102874b 100644 --- a/RageCoop.Core/CoreUtils.cs +++ b/RageCoop.Core/CoreUtils.cs @@ -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 objects) + { + StringBuilder sb = new StringBuilder(); + foreach(var obj in objects) + { + sb.Append(obj.GetType()+":"+obj.ToString()+"\n"); + } + return sb.ToString(); + } + public static string Dump(this IEnumerable objects) + { + return "{"+string.Join(",",objects)+"}"; + } + public static void ForEach(this IEnumerable objects,Action action) + { + foreach(var obj in objects) + { + action(obj); + } + } } } diff --git a/RageCoop.Core/Packets/CustomEvent.cs b/RageCoop.Core/Packets/CustomEvent.cs index 95448e7..63698f7 100644 --- a/RageCoop.Core/Packets/CustomEvent.cs +++ b/RageCoop.Core/Packets/CustomEvent.cs @@ -10,7 +10,7 @@ namespace RageCoop.Core public class CustomEvent : Packet { public int Hash { get; set; } - public List Args { get; set; }=new List(); + public List 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(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()}"); + } } } diff --git a/RageCoop.Core/Packets/Packets.cs b/RageCoop.Core/Packets/Packets.cs index 2755fa4..d44a87e 100644 --- a/RageCoop.Core/Packets/Packets.cs +++ b/RageCoop.Core/Packets/Packets.cs @@ -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 diff --git a/RageCoop.Core/Scripting/BuiltInEvents.cs b/RageCoop.Core/Scripting/BuiltInEvents.cs deleted file mode 100644 index a8d2577..0000000 --- a/RageCoop.Core/Scripting/BuiltInEvents.cs +++ /dev/null @@ -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); - } - } -} diff --git a/RageCoop.Core/Scripting/CustomEvents.cs b/RageCoop.Core/Scripting/CustomEvents.cs new file mode 100644 index 0000000..c90b03e --- /dev/null +++ b/RageCoop.Core/Scripting/CustomEvents.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Security.Cryptography; + +namespace RageCoop.Core.Scripting +{ + /// + /// + /// + public static class CustomEvents + { + static MD5 Hasher = MD5.Create(); + static Dictionary Hashed=new Dictionary(); + 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"); + /// + /// Get a Int32 hash of a string. + /// + /// + /// + /// The exception is thrown when the name did not match a previously computed one and the hash was the same. + 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; + } + } + } + } +} diff --git a/RageCoop.Server/Client.cs b/RageCoop.Server/Client.cs index 48a5da4..67fe2dd 100644 --- a/RageCoop.Server/Client.cs +++ b/RageCoop.Server/Client.cs @@ -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 _customData = new(); - private long _callbacksCount = 0; - public readonly Dictionary> Callbacks = new(); + internal readonly Dictionary> Callbacks = new(); public bool IsReady { get; internal set; }=false; public string Username { get;internal set; } = "N/A"; #region CUSTOMDATA FUNCTIONS + /* public void SetData(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} <<"); } } - + /* /// /// Send a native call to client and ignore its return value. /// @@ -220,7 +221,53 @@ namespace RageCoop.Server Program.Logger.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<"); } } + */ + /// + /// Send a native call to client and do a callback when the response received. + /// + /// Type of the response + /// + /// + /// + public void SendNativeCall(Action callBack, GTA.Native.Hash hash, params object[] args) + { + var argsList= new List(args); + argsList.InsertRange(0, new object[] { (byte)Type.GetTypeCode(typeof(T)), RequestNativeCallID(callBack), (ulong)hash }); + + SendCustomEvent(CustomEvents.NativeCall, argsList); + } + /// + /// Send a native call to client and ignore it's response. + /// + /// + /// + public void SendNativeCall(GTA.Native.Hash hash, params object[] args) + { + var argsList = new List(args); + argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty,(ulong)hash}); + // Program.Logger.Debug(argsList.DumpWithType()); + SendCustomEvent(CustomEvents.NativeCall, argsList); + } + private int RequestNativeCallID(Action 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 diff --git a/RageCoop.Server/Scripting/API.cs b/RageCoop.Server/Scripting/API.cs index 767845a..aa50eb9 100644 --- a/RageCoop.Server/Scripting/API.cs +++ b/RageCoop.Server/Scripting/API.cs @@ -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 + /* /// /// 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} <<"); } } - + */ /// /// Get a list of all Clients /// /// All clients as a dictionary indexed by NetID public static Dictionary GetAllClients() { - return Server.Clients; + return new(Server.Clients); } /// @@ -194,7 +196,7 @@ namespace RageCoop.Server.Scripting { return; } - + List connections = netHandleList == null ? Server.MainNetServer.Connections : Server.MainNetServer.Connections.FindAll(c => netHandleList.Contains(c.RemoteUniqueIdentifier)); @@ -270,6 +272,10 @@ namespace RageCoop.Server.Scripting handlers.Add(handler); } } + public static void RegisterCustomEventHandler(string name, Action handler) + { + RegisterCustomEventHandler(CustomEvents.Hash(name), handler); + } public static Logger GetLogger() { return Program.Logger; diff --git a/RageCoop.Server/Scripting/BaseScript.cs b/RageCoop.Server/Scripting/BaseScript.cs index 89664bd..1bb713d 100644 --- a/RageCoop.Server/Scripting/BaseScript.cs +++ b/RageCoop.Server/Scripting/BaseScript.cs @@ -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 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); + } + } } } diff --git a/RageCoop.Server/Scripting/Resources.cs b/RageCoop.Server/Scripting/Resources.cs index d8097ab..324c1d6 100644 --- a/RageCoop.Server/Scripting/Resources.cs +++ b/RageCoop.Server/Scripting/Resources.cs @@ -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); + } } } } diff --git a/RageCoop.Server/Server.cs b/RageCoop.Server/Server.cs index b649042..5aba8f2 100644 --- a/RageCoop.Server/Server.cs +++ b/RageCoop.Server/Server.cs @@ -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.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();