diff --git a/CLre/API/App/Client.cs b/CLre/API/App/Client.cs index 51d4bcf..e1c2db9 100644 --- a/CLre/API/App/Client.cs +++ b/CLre/API/App/Client.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using HarmonyLib; +using User; namespace CLre.API.App { @@ -53,6 +54,12 @@ namespace CLre.API.App add => GameFrameworkEngine.gameFrameworkExit += value; remove => GameFrameworkEngine.gameFrameworkExit += value; } + + public static event EventHandler GameJoin + { + add => ClientGameJoinSequence_OnUserValidated_Patch.gameJoin += value; + remove => ClientGameJoinSequence_OnUserValidated_Patch.gameJoin += value; + } public static string Version { @@ -114,4 +121,27 @@ namespace CLre.API.App return AccessTools.Method("FrontEnd.FrontEndGuiEngine:SetMainMenuEnabled"); } } + + // TODO patch OnUserValidationFailed as well + [HarmonyPatch] + class ClientGameJoinSequence_OnUserValidated_Patch + { + internal static event EventHandler gameJoin; + + [HarmonyPostfix] + public static void AfterMethodCall(object __instance, ref SerializedAccountInfo validated) + { + if (gameJoin != null) gameJoin(__instance, new GameJoin + { + Success = true, + Data = validated, + }); + } + + [HarmonyTargetMethod] + public static MethodBase Target() + { + return AccessTools.Method("MultiplayerClient.ClientGameJoinSequence:OnUserValidated"); + } + } } \ No newline at end of file diff --git a/CLre/API/App/ClientEventArgs.cs b/CLre/API/App/ClientEventArgs.cs index 6dc25b9..c6bd59c 100644 --- a/CLre/API/App/ClientEventArgs.cs +++ b/CLre/API/App/ClientEventArgs.cs @@ -1,3 +1,5 @@ +using User; + namespace CLre.API.App { @@ -8,4 +10,10 @@ namespace CLre.API.App public struct GameExit{} public struct MenuReady{} + + public struct GameJoin + { + public bool Success; + public SerializedAccountInfo Data; + } } \ No newline at end of file diff --git a/CLre/API/Synergy/ClientHandshakeEngine.cs b/CLre/API/Synergy/ClientHandshakeEngine.cs new file mode 100644 index 0000000..06a4da6 --- /dev/null +++ b/CLre/API/Synergy/ClientHandshakeEngine.cs @@ -0,0 +1,83 @@ +using System.Collections; +using CLre.API.Engines; +using GameNetworkLayer.Shared; +using HarmonyLib; +using Svelto.ECS; + +namespace CLre.API.Synergy +{ + public class ClientHandshakeEngine : GameObsoleteEnginePreBuild + { + internal static ClientHandshakeEngine Instance = null; + + internal const NetworkDispatcherCode CLre_HANDSHAKE_NETCODE = (NetworkDispatcherCode) 218; + + private Utility.Reflection.INetMsgClientSender_SendMessage _sendMessage; + + private Utility.Reflection.INetMsgClientListener_RegisterListener _registerListener; + + public override void Ready() + { + //Utility.Logging.MetaLog("Building send message delegate"); + _sendMessage = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Client.NetMessageClientSender:SendMessage", + generics: new [] {typeof(SerializedCLreHandshake)}, + instance: MainLevel_BuildClasses_Patch.netMessageSender); + + //Utility.Logging.MetaLog("Building register listener delegate"); + _registerListener = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Client.NetMessageClientListener:RegisterListener", + generics: new [] {typeof(SerializedCLreHandshake)}, + instance: MainLevel_BuildClasses_Patch.netMessageListener); + _registerListener(CLre_HANDSHAKE_NETCODE, OnHandshakeReceived); + } + + public void OnHandshakeReceived(ref SerializedCLreHandshake p) + { + // TODO validate handshake msg + Utility.Logging.MetaLog($"Received CLre handshake! {p}"); + } + + public override IEntitiesDB entitiesDB { get; set; } + public override IEntityFactory entityFactory { get; set; } + + public IEnumerator Sender(SerializedCLreHandshake payload) + { + yield return null; + Utility.Logging.MetaLog("Sending Client CLre handshake"); + _sendMessage(CLre_HANDSHAKE_NETCODE, ref payload); + yield return null; + } + + internal static void Init() + { + Instance = new ClientHandshakeEngine(); + } + + public ClientHandshakeEngine(): base() + { + App.Client.GameJoin += (_, __) => + { + SerializedCLreHandshake payload = SerializedCLreHandshake.Current(); + Sender(payload).Run(); + }; + } + } + + [HarmonyPatch(typeof(GameFramework.MainLevel), "BuildClasses")] + class MainLevel_BuildClasses_Patch + { + internal static object netMessageListener; + + internal static object netMessageSender; + + [HarmonyPostfix] + public static void AfterMethodCall(object ____netMessageListener, object ____netMessageSender) + { + netMessageListener = ____netMessageListener; + netMessageSender = ____netMessageSender; + } + } +} \ No newline at end of file diff --git a/CLre/API/Synergy/ClientMessagingEngine.cs b/CLre/API/Synergy/ClientMessagingEngine.cs new file mode 100644 index 0000000..e240b7c --- /dev/null +++ b/CLre/API/Synergy/ClientMessagingEngine.cs @@ -0,0 +1,94 @@ +using System.Collections; +using System.Collections.Generic; +using GameNetworkLayer.Shared; +using Svelto.Context; +using Svelto.ECS; + +namespace CLre.API.Synergy +{ + class ClientMessagingEngine: Engines.GameObsoleteEnginePostBuild, IWaitForFrameworkDestruction, IWaitForFrameworkInitialization + { + private struct MessageQueueItem + { + public SerializedCLreMessage msg; + public NetworkDispatcherCode code; + } + + private Utility.Reflection.INetMsgClientSender_SendMessage _sendMessage; + + private Utility.Reflection.INetMsgClientListener_RegisterListener _registerListener; + + private Queue _messageQueue = new Queue(10); + + private bool _isRunning = false; + + public override void Ready() + { + //Utility.Logging.MetaLog("Building send message delegate"); + _sendMessage = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Client.NetMessageClientSender:SendMessage", + generics: new [] {typeof(SerializedCLreMessage)}, + instance: MainLevel_BuildClasses_Patch.netMessageSender); + + //Utility.Logging.MetaLog("Building register listener delegate"); + _registerListener = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Client.NetMessageClientListener:RegisterListener", + generics: new [] {typeof(SerializedCLreMessage)}, + instance: MainLevel_BuildClasses_Patch.netMessageListener); + _registerListener(Message.CLre_MESSAGE_NETCODE, OnMessageReceived); + } + + private void OnMessageReceived(ref SerializedCLreMessage data) + { + Message.HandleMessageReceive(ref data); + } + + internal void EnqueueMessage(ref SerializedCLreMessage msg) + { + _messageQueue.Enqueue(new MessageQueueItem + { + msg = msg, + code = Message.CLre_MESSAGE_NETCODE, + }); + } + + public override IEntitiesDB entitiesDB { get; set; } + public override IEntityFactory entityFactory { get; set; } + + public ClientMessagingEngine(): base() + { + App.Client.GameJoin += (_, __) => { MessageSender().Run(); }; + } + + public IEnumerator MessageSender() + { + while (!_isRunning) + { + yield return null; + } + while (_isRunning) + { + while (_messageQueue.Count != 0) + { + MessageQueueItem item = _messageQueue.Dequeue(); + API.Utility.Logging.MetaLog($"Sending message with id {item.msg.Id}"); + _sendMessage(item.code, ref item.msg); + } + + yield return null; + } + } + + public void OnFrameworkDestroyed() + { + _isRunning = false; + } + + public void OnFrameworkInitialized() + { + _isRunning = true; + } + } +} \ No newline at end of file diff --git a/CLre/API/Synergy/Message.cs b/CLre/API/Synergy/Message.cs new file mode 100644 index 0000000..75b623a --- /dev/null +++ b/CLre/API/Synergy/Message.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using GameNetworkLayer.Shared; + +namespace CLre.API.Synergy +{ + public static class Message + { + internal const NetworkDispatcherCode CLre_MESSAGE_NETCODE = (NetworkDispatcherCode) 219; + + private static readonly Dictionary> handlers = + new Dictionary>(); + + private static readonly ClientMessagingEngine msgEngine = new ClientMessagingEngine(); + + public static void SendCLreMessage(ref SerializedCLreMessage message) + { + msgEngine.EnqueueMessage(ref message); + } + + public static void RegisterListener(uint id, Action handler) + { + if (handlers.TryGetValue(id, out Action existing)) + { + existing += handler; + handlers[id] = existing; + } + else + { + handlers[id] = handler; + } + } + + internal static void HandleMessageReceive(ref SerializedCLreMessage msg) + { + ReceiveMessageArgs payload = new ReceiveMessageArgs + { + Message = msg, + }; + if (handlers.TryGetValue(msg.Id, out Action h)) + { + h(payload); + } + } + } + + public struct ReceiveMessageArgs + { + public SerializedCLreMessage Message; + } +} \ No newline at end of file diff --git a/CLre/API/Synergy/SerializedCLreHandshake.cs b/CLre/API/Synergy/SerializedCLreHandshake.cs new file mode 100644 index 0000000..845effb --- /dev/null +++ b/CLre/API/Synergy/SerializedCLreHandshake.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using NetworkFramework.Shared; + +namespace CLre.API.Synergy +{ + public struct SerializedCLreHandshake: ISerializedNetData + { + private byte major; + private byte minor; + private byte patch; + + private HandshakeFlag flags; + + private List modInfo; + + public Version Version + { + get => new Version(major, minor, patch); + set + { + major = (byte)value.Major; + minor = (byte)value.Minor; + patch = (byte)value.Build; + } + } + + public IEnumerable Mods + { + get => modInfo.ToArray(); + set + { + modInfo.Clear(); + foreach (var mod in value) + { + modInfo.Add(mod); + } + } + } + + public byte[] Serialize() + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + // version + writer.Write(major); + writer.Write(minor); + writer.Write(patch); + writer.Write((uint)flags); + writer.Write(modInfo.Count); + foreach (string mod in modInfo) + { + writer.Write(mod); + } + return stream.ToArray(); + } + } + } + + public void Deserialize(byte[] data) + { + using (MemoryStream stream = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + // version + major = reader.ReadByte(); + minor = reader.ReadByte(); + patch = reader.ReadByte(); + flags = (HandshakeFlag) reader.ReadUInt32(); + int modCount = reader.ReadInt32(); + modInfo = new List(modCount); + for (int i = 0; i < modCount; i++) + { + modInfo.Add(reader.ReadString()); + } + } + } + } + + public bool HasFlag(HandshakeFlag f) + { + return (flags & f) != HandshakeFlag.None; + } + + public void SetFlag(HandshakeFlag f) + { + flags |= f; + } + + public void UnsetFlag(HandshakeFlag f) + { + flags &= ~f; + } + + public static SerializedCLreHandshake Current() + { + Version v = Assembly.GetExecutingAssembly().GetName().Version; + List mods = new List(); + foreach (var plugin in IllusionInjector.PluginManager.Plugins) + { + mods.Add(plugin.Name); + } + + return new SerializedCLreHandshake + { + major = (byte) v.Major, + minor = (byte) v.Minor, + patch = (byte) v.Build, + flags = HandshakeFlag.Client, + modInfo = mods, + }; + } + + public static SerializedCLreHandshake RequireCLre() + { + Version v = Assembly.GetExecutingAssembly().GetName().Version; + List mods = new List(new []{Assembly.GetExecutingAssembly().GetName().Name}); + + return new SerializedCLreHandshake + { + major = (byte) v.Major, + minor = (byte) v.Minor, + patch = (byte) v.Build, + flags = HandshakeFlag.Client | HandshakeFlag.RequireAll, + modInfo = mods, + }; + } + + public static SerializedCLreHandshake Empty() + { + return new SerializedCLreHandshake + { + major = 0, + minor = 0, + patch = 0, + modInfo = new List(), + }; + } + + public override string ToString() + { + return $"CLre {Version} ({modInfo.Count} mods)"; + } + } + + [Flags] + public enum HandshakeFlag : uint + { + None = 0, + Client = 1, + Server = 1 << 1, + RequireAll = 1 << 2, + OptionalAll = 1 << 3, + Confirm = 1 << 4, + } +} \ No newline at end of file diff --git a/CLre/API/Synergy/SerializedCLreMessage.cs b/CLre/API/Synergy/SerializedCLreMessage.cs new file mode 100644 index 0000000..95de808 --- /dev/null +++ b/CLre/API/Synergy/SerializedCLreMessage.cs @@ -0,0 +1,44 @@ +using System.IO; +using NetworkFramework.Shared; + +namespace CLre.API.Synergy +{ + public struct SerializedCLreMessage: ISerializedNetData + { + public uint Id; + public byte[] Data; + + public byte[] Serialize() + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(Id); + writer.Write(Data.Length); + foreach (byte b in Data) + { + writer.Write(b); + } + return stream.ToArray(); + } + } + } + + public void Deserialize(byte[] data) + { + using (MemoryStream stream = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + Id = reader.ReadUInt32(); + Data = new byte[reader.ReadInt32()]; + for (int i = 0; i < Data.Length; i++) + { + Data[i] = reader.ReadByte(); + } + } + } + } + } +} \ No newline at end of file diff --git a/CLre/API/Tools/NetClientSender.cs b/CLre/API/Tools/NetClientSender.cs index 7b03a44..bf54398 100644 --- a/CLre/API/Tools/NetClientSender.cs +++ b/CLre/API/Tools/NetClientSender.cs @@ -98,13 +98,23 @@ namespace CLre.API.Tools { //Utility.Logging.Log($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); Traverse d = Traverse.Create(data); - StringBuilder sb = new StringBuilder($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); + string codeName = (short) code > 217 ? "CUSTOM" : code.ToString(); + StringBuilder sb = new StringBuilder($"Sending ISerializedNetData {data.GetType().FullName} (code: {codeName} {(short)code})"); foreach (string fieldName in d.Fields()) { Traverse field = d.Field(fieldName); sb.Append("\n"); sb.Append("\""); - sb.Append(fieldName.Substring(fieldName.IndexOf('<')+1, fieldName.LastIndexOf('>')-1)); + int start = fieldName.IndexOf('<'); + int len = fieldName.LastIndexOf('>'); + if (start != -1 && len > 0) + { + sb.Append(fieldName.Substring(start+1, len-1)); + } + else + { + sb.Append(fieldName); + } sb.Append("\": "); sb.Append(field.GetValue()); } diff --git a/CLre/API/Utility/Reflection.cs b/CLre/API/Utility/Reflection.cs index 37b1a5a..6c2d49b 100644 --- a/CLre/API/Utility/Reflection.cs +++ b/CLre/API/Utility/Reflection.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using GameNetworkLayer.Client; using GameNetworkLayer.Shared; using HarmonyLib; using NetworkFramework.Shared; @@ -28,6 +29,10 @@ namespace CLre.API.Utility public delegate T[] QueryEntitiesV1(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct; public delegate object[] SendMessage(NetworkDispatcherCode dispatcherCode, ref T value) where T : struct, ISerializedNetData; + + public delegate void INetMsgClientSender_SendMessage(NetworkDispatcherCode code, ref T value) where T : struct, ISerializedNetData; + + public delegate void INetMsgClientListener_RegisterListener(NetworkDispatcherCode code, NetCBClient proc) where T: struct, ISerializedNetData; // useful reflection functions public static TFuncProto BuildDelegate(MethodInfo method) where TFuncProto : Delegate diff --git a/CLre/CLre.cs b/CLre/CLre.cs index 1784aad..9ce761a 100644 --- a/CLre/CLre.cs +++ b/CLre/CLre.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Text; using CLre.API.Characters; +using CLre.API.Synergy; using CLre.API.Tools; using GameNetworkLayer.Shared; using HarmonyLib; @@ -51,6 +52,9 @@ namespace CLre Fixes.MiniScreenHelper.Init(); Fixes.UnderStructureCollider.Init(); + // API init + API.Synergy.ClientHandshakeEngine.Init(); + // misc LogIPAPlugins(); Fixes.BugfixAttributeUtility.LogBugfixes(); @@ -71,10 +75,23 @@ namespace CLre NetClientSender.DebugSendMessage(netData, harmonyInstance, NetClientSender.GetLogMethod(netData)); API.Utility.Logging.MetaLog("Patched SendMessage"); + + netData = typeof(API.Synergy.SerializedCLreHandshake); + NetClientSender.DebugSendMessage(netData, harmonyInstance, + NetClientSender.GetLogMethod(netData)); + API.Utility.Logging.MetaLog("Patched SendMessage"); NetClientListener.DebugReceiveMessage(NetworkDispatcherCode.EACMessageServerToClient, NetClientListener.Log); + NetClientListener.DebugReceiveMessage(API.Synergy.ClientHandshakeEngine.CLre_HANDSHAKE_NETCODE, + NetClientListener.Log); + + NetClientListener.DebugReceiveMessage(NetworkDispatcherCode.SendIsPvEToClient, + NetClientListener.Log); + + API.Utility.Logging.MetaLog($"Highest NetworkDispatcherCode number is {(int) NetworkDispatcherCode.StructureDestroyed} damn it Photon"); + // API debug and testing API.App.Client.InitComplete += (_, __) => { diff --git a/CLre/CLre.csproj b/CLre/CLre.csproj index ff2e0f8..d6f2fad 100644 --- a/CLre/CLre.csproj +++ b/CLre/CLre.csproj @@ -3,7 +3,7 @@ net472 true - 0.0.2 + 0.0.3 NGnius MIT https://git.exmods.org/NGnius/CLre diff --git a/CLre_server/API/MainServer/Server.cs b/CLre_server/API/MainServer/Server.cs index e32dbef..8337b9d 100644 --- a/CLre_server/API/MainServer/Server.cs +++ b/CLre_server/API/MainServer/Server.cs @@ -1,7 +1,9 @@ using System; using System.Reflection; +using GameNetworkLayer.Shared; using GameServer; using HarmonyLib; +using NetworkFramework.Server; using Svelto.Context; namespace CLre_server.API.MainServer @@ -45,6 +47,24 @@ namespace CLre_server.API.MainServer add => ServerReadyEngine.serverFrameworkDestroyed += value; remove => ServerReadyEngine.serverFrameworkDestroyed -= value; } + + public event EventHandler Connected + { + add => PhotonNetwork_ConnectUsingSettings_Patch.postConnect += value; + remove => PhotonNetwork_ConnectUsingSettings_Patch.postConnect -= value; + } + + public event EventHandler PlayerConnect + { + add => MainGameServer_SetupContainer_Patch.playerConnected += value; + remove => MainGameServer_SetupContainer_Patch.playerConnected -= value; + } + + public event EventHandler PlayerDisconnect + { + add => MainGameServer_SetupContainer_Patch.playerDisconnected += value; + remove => MainGameServer_SetupContainer_Patch.playerDisconnected -= value; + } // properties @@ -73,6 +93,9 @@ namespace CLre_server.API.MainServer [HarmonyPostfix] public static void AfterMethodCall(GameServerSettings ____gameServerSettings) { +#if DEBUG + Utility.Logging.MetaLog("Got GameServerSettings"); +#endif _gameServerSettings = ____gameServerSettings; } @@ -144,4 +167,38 @@ namespace CLre_server.API.MainServer }); } } + + [HarmonyPatch(typeof(GameServer.GameFramework.MainGameServer), "SetupContainer")] + class MainGameServer_SetupContainer_Patch + { + internal static event EventHandler playerConnected; + + internal static event EventHandler playerDisconnected; + + private static GameServer.GameFramework.MainGameServer mgs = null; + + [HarmonyPostfix] + public static void AfterMethodCall(GameServer.GameFramework.MainGameServer __instance, ref IPlayerConnectedCallbacks ____playerConnectedCallbacks) + { + mgs = __instance; + ____playerConnectedCallbacks.OnPlayerConnected += OnConnect; + ____playerConnectedCallbacks.OnPlayerDisconnected += OnDisconnect; + } + + public static void OnConnect(int playerId) + { + if (playerConnected != null) playerConnected(mgs, new PlayerConnectArgs + { + PlayerId = playerId, + }); + } + + public static void OnDisconnect(int playerId) + { + if (playerDisconnected != null) playerDisconnected(mgs, new PlayerConnectArgs + { + PlayerId = playerId, + }); + } + } } \ No newline at end of file diff --git a/CLre_server/API/MainServer/ServerEngines.cs b/CLre_server/API/MainServer/ServerEngines.cs index b3b990d..b55c0c3 100644 --- a/CLre_server/API/MainServer/ServerEngines.cs +++ b/CLre_server/API/MainServer/ServerEngines.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using CLre_server.API.Engines; using Game.DataLoader; using GameServer; @@ -22,8 +23,8 @@ namespace CLre_server.API.MainServer { photonVersion = PhotonNetwork.gameVersion, photonRegion = PhotonNetwork.CloudRegion, - gameGuid = gss.GetGameGuid(), - worldName = gss.GetWorldName(), + gameGuid = gss == null ? "" : gss.GetGameGuid(), + worldName = gss == null ? "" : gss.GetWorldName(), }); } @@ -37,8 +38,8 @@ namespace CLre_server.API.MainServer { photonVersion = PhotonNetwork.gameVersion, photonRegion = PhotonNetwork.CloudRegion, - gameGuid = gss.GetGameGuid(), - worldName = gss.GetWorldName(), + gameGuid = gss == null ? "" : gss.GetGameGuid(), + worldName = gss == null ? "" : gss.GetWorldName(), }); } diff --git a/CLre_server/API/MainServer/ServerEventArgs.cs b/CLre_server/API/MainServer/ServerEventArgs.cs index c68ce50..7103640 100644 --- a/CLre_server/API/MainServer/ServerEventArgs.cs +++ b/CLre_server/API/MainServer/ServerEventArgs.cs @@ -11,4 +11,9 @@ namespace CLre_server.API.MainServer } public struct StopEventArgs{} + + public struct PlayerConnectArgs + { + public int PlayerId; + } } \ No newline at end of file diff --git a/CLre_server/API/MainServer/UserVerification.cs b/CLre_server/API/MainServer/UserVerification.cs new file mode 100644 index 0000000..b2cbf62 --- /dev/null +++ b/CLre_server/API/MainServer/UserVerification.cs @@ -0,0 +1,95 @@ +using System; +using System.Reflection; +using GameNetworkLayer.Shared; +using HarmonyLib; +using Svelto.Context; +using Svelto.ECS; + +namespace CLre_server.API.MainServer +{ + public class UserVerification + { + public static UserVerification Instance { get; internal set; } + + private delegate void DisconnectPlayer_VerificationFailedProto(int playerId, string error); + private delegate void DisconnectPlayerProto(int playerId, NetworkDispatcherCode code); + + private readonly DisconnectPlayer_VerificationFailedProto _disconnectPlayerVerificationFailed; + private readonly DisconnectPlayerProto _disconnectPlayer; + + // This doesn't seem to do actually generate a popup, but it will stop the player from loading in + /*public void DisconnectPlayer_VerificationFailed(int playerId, string error) + { + _disconnectPlayerVerificationFailed(playerId, error); + }*/ + + public void DisconnectPlayer(int playerId, NetworkDispatcherCode code = NetworkDispatcherCode.ModVerificationFail) + { + _disconnectPlayer(playerId, code); + } + + internal UserVerification(IQueryingEntitiesEngine uvs) + { + Type uvsType = AccessTools.TypeByName("User.Server.UserVerificationServer"); + _disconnectPlayerVerificationFailed = + Utility.Reflection.MethodAsDelegate(uvsType, + "DisconnectPlayer_VerificationFailed", + //parameters: new [] {typeof(int), typeof(string)}, + instance: uvs); + + _disconnectPlayer = + Utility.Reflection.MethodAsDelegate(uvsType, "DisconnectPlayer", instance: uvs); + } + } + + // This seems to think that __instance is always simply a System.Object (caused by Harmony?), which doesn't work + /*[HarmonyPatch] + class UserVerificationServer_Constructor_Patch + { + + private static IQueryingEntitiesEngine uvs = null; + + [HarmonyPrefix] + public static void BeforeMethodCall() + { + } + + [HarmonyPostfix] + public static void AfterMethodCall(IQueryingEntitiesEngine __instance) + { + uvs = __instance; + UserVerification.Instance = new UserVerification(__instance); + } + + [HarmonyTargetMethod] + public static MethodBase Target() + { + return AccessTools.Constructor(AccessTools.TypeByName("User.Server.UserVerificationServer")); + } + }*/ + + [HarmonyPatch] + class MainGameServer_CreatePlayerDisconnectionSequence_Patch + { + + private static IQueryingEntitiesEngine uvs = null; + + [HarmonyPrefix] + public static void BeforeMethodCall() + { + } + + [HarmonyPostfix] + public static void AfterMethodCall(IQueryingEntitiesEngine userVerificationEng) + { + uvs = userVerificationEng; + UserVerification.Instance = new UserVerification(userVerificationEng); + } + + [HarmonyTargetMethod] + public static MethodBase Target() + { + return AccessTools.Method("GameServer.GameFramework.MainGameServer:CreatePlayerDisconnectionSequence"); + } + } +} \ No newline at end of file diff --git a/CLre_server/API/Synergy/Message.cs b/CLre_server/API/Synergy/Message.cs new file mode 100644 index 0000000..be75e8f --- /dev/null +++ b/CLre_server/API/Synergy/Message.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using GameNetworkLayer.Shared; + +namespace CLre_server.API.Synergy +{ + public static class Message + { + internal const NetworkDispatcherCode CLre_MESSAGE_NETCODE = (NetworkDispatcherCode) 219; + + private static readonly Dictionary> handlers = + new Dictionary>(); + + private static readonly ServerMessagingEngine msgEngine = new ServerMessagingEngine(); + + private static readonly List clrePlayers = new List(); + + public static void SendToAllCLreClients(ref SerializedCLreMessage message) + { + foreach (var playerId in clrePlayers) + { + SendCLreMessage(playerId, ref message); + } + } + + public static void SendCLreMessage(int playerId, ref SerializedCLreMessage message) + { + msgEngine.EnqueueMessage(playerId, ref message); + } + + public static void RegisterListener(uint id, Action handler) + { + if (handlers.TryGetValue(id, out Action existing)) + { + existing += handler; + handlers[id] = existing; + } + else + { + handlers[id] = handler; + } + } + + internal static void HandleMessageReceive(int playerId, ref SerializedCLreMessage msg) + { + ReceiveMessageArgs payload = new ReceiveMessageArgs + { + Message = msg, + PlayerId = playerId, + }; + if (handlers.TryGetValue(msg.Id, out Action h)) + { + h(payload); + } + } + + internal static void RegisterCLreClient(int playerId) + { + clrePlayers.Add(playerId); + } + } + + public struct ReceiveMessageArgs + { + public SerializedCLreMessage Message; + public int PlayerId; + } +} \ No newline at end of file diff --git a/CLre_server/API/Synergy/SerializedCLreHandshake.cs b/CLre_server/API/Synergy/SerializedCLreHandshake.cs new file mode 100644 index 0000000..0c20669 --- /dev/null +++ b/CLre_server/API/Synergy/SerializedCLreHandshake.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using NetworkFramework.Shared; + +namespace CLre_server.API.Synergy +{ + struct SerializedCLreHandshake: ISerializedNetData + { + private byte major; + private byte minor; + private byte patch; + + private HandshakeFlag flags; + + private List modInfo; + + public Version Version + { + get => new Version(major, minor, patch); + set + { + major = (byte)value.Major; + minor = (byte)value.Minor; + patch = (byte)value.Build; + } + } + + public IEnumerable Mods + { + get => modInfo.ToArray(); + set + { + modInfo.Clear(); + foreach (var mod in value) + { + modInfo.Add(mod); + } + } + } + + public byte[] Serialize() + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + // version + writer.Write(major); + writer.Write(minor); + writer.Write(patch); + writer.Write((uint)flags); + writer.Write(modInfo.Count); + foreach (string mod in modInfo) + { + writer.Write(mod); + } + return stream.ToArray(); + } + } + } + + public void Deserialize(byte[] data) + { + using (MemoryStream stream = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + // version + major = reader.ReadByte(); + minor = reader.ReadByte(); + patch = reader.ReadByte(); + flags = (HandshakeFlag) reader.ReadUInt32(); + int modCount = reader.ReadInt32(); + modInfo = new List(modCount); + for (int i = 0; i < modCount; i++) + { + modInfo.Add(reader.ReadString()); + } + } + } + } + + public bool HasFlag(HandshakeFlag f) + { + return (flags & f) != HandshakeFlag.None; + } + + public void SetFlag(HandshakeFlag f) + { + flags |= f; + } + + public void UnsetFlag(HandshakeFlag f) + { + flags &= ~f; + } + + public static SerializedCLreHandshake Current() + { + Version v = Assembly.GetExecutingAssembly().GetName().Version; + List mods = new List(); + foreach (var plugin in IllusionInjector.PluginManager.Plugins) + { + mods.Add(plugin.Name); + } + + return new SerializedCLreHandshake + { + major = (byte) v.Major, + minor = (byte) v.Minor, + patch = (byte) v.Build, + flags = HandshakeFlag.Server, + modInfo = mods, + }; + } + + public static SerializedCLreHandshake RequireCLre() + { + Version v = Assembly.GetExecutingAssembly().GetName().Version; + List mods = new List(new []{Assembly.GetExecutingAssembly().GetName().Name}); + + return new SerializedCLreHandshake + { + major = (byte) v.Major, + minor = (byte) v.Minor, + patch = (byte) v.Build, + flags = HandshakeFlag.Client | HandshakeFlag.RequireAll, + modInfo = mods, + }; + } + + public static SerializedCLreHandshake Empty() + { + return new SerializedCLreHandshake + { + major = 0, + minor = 0, + patch = 0, + modInfo = new List(), + }; + } + + public override string ToString() + { + return $"CLre {Version} ({modInfo.Count} mods)"; + } + } + + [Flags] + enum HandshakeFlag : uint + { + None = 0, + Client = 1, + Server = 1 << 1, + RequireAll = 1 << 2, + OptionalAll = 1 << 3, + Confirm = 1 << 4, + } +} \ No newline at end of file diff --git a/CLre_server/API/Synergy/SerializedCLreMessage.cs b/CLre_server/API/Synergy/SerializedCLreMessage.cs new file mode 100644 index 0000000..325285d --- /dev/null +++ b/CLre_server/API/Synergy/SerializedCLreMessage.cs @@ -0,0 +1,44 @@ +using System.IO; +using NetworkFramework.Shared; + +namespace CLre_server.API.Synergy +{ + public struct SerializedCLreMessage: ISerializedNetData + { + public uint Id; + public byte[] Data; + + public byte[] Serialize() + { + using (MemoryStream stream = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(Id); + writer.Write(Data.Length); + foreach (byte b in Data) + { + writer.Write(b); + } + return stream.ToArray(); + } + } + } + + public void Deserialize(byte[] data) + { + using (MemoryStream stream = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + Id = reader.ReadUInt32(); + Data = new byte[reader.ReadInt32()]; + for (int i = 0; i < Data.Length; i++) + { + Data[i] = reader.ReadByte(); + } + } + } + } + } +} \ No newline at end of file diff --git a/CLre_server/API/Synergy/ServerHandshakeEngine.cs b/CLre_server/API/Synergy/ServerHandshakeEngine.cs new file mode 100644 index 0000000..81185d8 --- /dev/null +++ b/CLre_server/API/Synergy/ServerHandshakeEngine.cs @@ -0,0 +1,78 @@ +using System.Collections; +using GameNetworkLayer.Shared; +using HarmonyLib; +using Svelto.ECS; + +namespace CLre_server.API.Synergy +{ + class ServerHandshakeEngine : Engines.ServerEnginePreBuild + { + internal static ServerHandshakeEngine Instance = null; + + internal const NetworkDispatcherCode CLre_HANDSHAKE_NETCODE = (NetworkDispatcherCode) 218; + + private Utility.Reflection.INetMsgServerSender_SendMessage _sendMessage; + + private Utility.Reflection.INetMsgServerListener_RegisterListener _registerListener; + + public override void Ready() + { + Utility.Logging.MetaLog("Building send message delegate"); + _sendMessage = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Server.NetMessageServerSender:SendMessage", + generics: new [] {typeof(SerializedCLreHandshake)}, + instance: MainGameServer_SetupContainer_Patch.netMessageSender); + + Utility.Logging.MetaLog("Building register listener delegate"); + _registerListener = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Server.NetMessageServerListener:RegisterListener", + generics: new [] {typeof(SerializedCLreHandshake)}, + instance: MainGameServer_SetupContainer_Patch.netMessageListener); + _registerListener(CLre_HANDSHAKE_NETCODE, OnHandshakeReceived); + } + + public void OnHandshakeReceived(int playerId, ref SerializedCLreHandshake p) + { + // TODO validate handshake msg + Utility.Logging.MetaLog($"Received CLre handshake from player {playerId}! {p}"); + Message.RegisterCLreClient(playerId); + SerializedCLreHandshake payload = SerializedCLreHandshake.Current(); + payload.SetFlag(HandshakeFlag.Confirm); + Sender(payload, playerId).Run(); + } + + public override IEntitiesDB entitiesDB { get; set; } + public override IEntityFactory entityFactory { get; set; } + + public IEnumerator Sender(SerializedCLreHandshake payload, int playerId) + { + yield return null; + Utility.Logging.MetaLog("Sending Server CLre handshake"); + _sendMessage(CLre_HANDSHAKE_NETCODE, ref payload, playerId); + yield return null; + } + + internal static void Init() + { + Instance = new ServerHandshakeEngine(); + } + } + + [HarmonyPatch(typeof(GameServer.GameFramework.MainGameServer), "SetupContainer")] + class MainGameServer_SetupContainer_Patch + { + internal static object netMessageListener; + + internal static object netMessageSender; + + [HarmonyPostfix] + public static void AfterMethodCall(object ____netMessageListener, object ____netMessageSender) + { + Utility.Logging.MetaLog($"Got NetMessage objects"); + netMessageListener = ____netMessageListener; + netMessageSender = ____netMessageSender; + } + } +} \ No newline at end of file diff --git a/CLre_server/API/Synergy/ServerMessagingEngine.cs b/CLre_server/API/Synergy/ServerMessagingEngine.cs new file mode 100644 index 0000000..409dc08 --- /dev/null +++ b/CLre_server/API/Synergy/ServerMessagingEngine.cs @@ -0,0 +1,96 @@ +using System.Collections; +using System.Collections.Generic; +using GameNetworkLayer.Shared; +using Svelto.Context; +using Svelto.ECS; + +namespace CLre_server.API.Synergy +{ + class ServerMessagingEngine: Engines.ServerEnginePreBuild, IWaitForFrameworkDestruction, IWaitForFrameworkInitialization + { + private struct MessageQueueItem + { + public SerializedCLreMessage msg; + public int playerId; + public NetworkDispatcherCode code; + } + + private Utility.Reflection.INetMsgServerSender_SendMessage _sendMessage; + + private Utility.Reflection.INetMsgServerListener_RegisterListener _registerListener; + + private Queue _messageQueue = new Queue(10); + + private bool _isRunning = false; + + public override void Ready() + { + //Utility.Logging.MetaLog("Building send message delegate"); + _sendMessage = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Server.NetMessageServerSender:SendMessage", + generics: new [] {typeof(SerializedCLreMessage)}, + instance: MainGameServer_SetupContainer_Patch.netMessageSender); + + //Utility.Logging.MetaLog("Building register listener delegate"); + _registerListener = + Utility.Reflection.MethodAsDelegate>( + "GameNetworkLayer.Server.NetMessageServerListener:RegisterListener", + generics: new [] {typeof(SerializedCLreMessage)}, + instance: MainGameServer_SetupContainer_Patch.netMessageListener); + _registerListener(Message.CLre_MESSAGE_NETCODE, OnMessageReceived); + } + + private void OnMessageReceived(int playerId, ref SerializedCLreMessage data) + { + Message.HandleMessageReceive(playerId, ref data); + } + + internal void EnqueueMessage(int playerId, ref SerializedCLreMessage msg) + { + _messageQueue.Enqueue(new MessageQueueItem + { + msg = msg, + playerId = playerId, + code = Message.CLre_MESSAGE_NETCODE, + }); + } + + public override IEntitiesDB entitiesDB { get; set; } + public override IEntityFactory entityFactory { get; set; } + + public ServerMessagingEngine(): base() + { + MainServer.Server.Instance.Connected += (_, __) => { MessageSender().Run(); }; + } + + public IEnumerator MessageSender() + { + while (!_isRunning) + { + yield return null; + } + while (_isRunning) + { + while (_messageQueue.Count != 0) + { + MessageQueueItem item = _messageQueue.Dequeue(); + API.Utility.Logging.MetaLog($"Sending message with id {item.msg.Id}"); + _sendMessage(item.code, ref item.msg, item.playerId); + } + + yield return null; + } + } + + public void OnFrameworkDestroyed() + { + _isRunning = false; + } + + public void OnFrameworkInitialized() + { + _isRunning = true; + } + } +} \ No newline at end of file diff --git a/CLre_server/API/Tools/NetServerSender.cs b/CLre_server/API/Tools/NetServerSender.cs index e0464d2..f4633a6 100644 --- a/CLre_server/API/Tools/NetServerSender.cs +++ b/CLre_server/API/Tools/NetServerSender.cs @@ -98,13 +98,23 @@ namespace CLre_server.API.Tools { //Utility.Logging.Log($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); Traverse d = Traverse.Create(data); - StringBuilder sb = new StringBuilder($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); + string codeName = (short) code > 217 ? "CUSTOM" : code.ToString(); + StringBuilder sb = new StringBuilder($"Sending ISerializedNetData {data.GetType().FullName} (code: {codeName} {(short)code})"); foreach (string fieldName in d.Fields()) { Traverse field = d.Field(fieldName); sb.Append("\n"); sb.Append("\""); - sb.Append(fieldName.Substring(fieldName.IndexOf('<')+1, fieldName.LastIndexOf('>')-1)); + int start = fieldName.IndexOf('<'); + int len = fieldName.LastIndexOf('>'); + if (start != -1 && len > 0) + { + sb.Append(fieldName.Substring(start+1, len-1)); + } + else + { + sb.Append(fieldName); + } sb.Append("\": "); sb.Append(field.GetValue()); } diff --git a/CLre_server/API/Utility/Reflection.cs b/CLre_server/API/Utility/Reflection.cs index 9361b50..93427a7 100644 --- a/CLre_server/API/Utility/Reflection.cs +++ b/CLre_server/API/Utility/Reflection.cs @@ -28,6 +28,11 @@ namespace CLre_server.API.Utility public delegate T[] QueryEntitiesV1(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct; public delegate object[] SendMessage(NetworkDispatcherCode dispatcherCode, ref T value) where T : struct, ISerializedNetData; + + public delegate void INetMsgServerSender_SendMessage(NetworkDispatcherCode code, ref T value, int playerId) where T : struct, ISerializedNetData; + + public delegate void INetMsgServerListener_RegisterListener(NetworkDispatcherCode code, NetCBServer proc) where T: struct, ISerializedNetData; + // useful reflection functions public static TFuncProto BuildDelegate(MethodInfo method) where TFuncProto : Delegate diff --git a/CLre_server/CLre_server.cs b/CLre_server/CLre_server.cs index fde19b4..2ce6683 100644 --- a/CLre_server/CLre_server.cs +++ b/CLre_server/CLre_server.cs @@ -44,6 +44,9 @@ namespace CLre_server // patches for bugs Fixes.InitLogSooner.Init(); + // API init + API.Synergy.ServerHandshakeEngine.Init(); + // misc LogIPAPlugins(); // log plugins again so they show up in the log, and not just stdout Fixes.BugfixAttributeUtility.LogBugfixes(); // log bugfixes that are applied @@ -59,10 +62,21 @@ namespace CLre_server NetServerSender.DebugSendMessage(netData, harmonyInstance, NetServerSender.GetLogMethod(netData)); API.Utility.Logging.MetaLog("Patched SendMessage"); + + netData = typeof(API.Synergy.SerializedCLreHandshake); + NetServerSender.DebugSendMessage(netData, harmonyInstance, + NetServerSender.GetLogMethod(netData)); + API.Utility.Logging.MetaLog("Patched SendMessage"); NetServerListener.DebugReceiveMessage(NetworkDispatcherCode.EACMessageServerToClient, NetServerListener.Log); + NetServerListener.DebugReceiveMessage(NetworkDispatcherCode.SendIsPvEToClient, + NetServerListener.Log); + + NetServerListener.DebugReceiveMessage(API.Synergy.ServerHandshakeEngine.CLre_HANDSHAKE_NETCODE, + NetServerListener.Log); + // API debug and testing API.MainServer.Server.Instance.FrameworkReady += (_, __) => API.Utility.Logging.MetaLog("(!) Server framework ready for business"); API.MainServer.Server.Instance.FrameworkExit += (_, __) => API.Utility.Logging.MetaLog("(!) Server framework shutting down"); // this seems to never happen diff --git a/CLre_server/CLre_server.csproj b/CLre_server/CLre_server.csproj index 2d2ed79..545b744 100644 --- a/CLre_server/CLre_server.csproj +++ b/CLre_server/CLre_server.csproj @@ -3,7 +3,7 @@ net472 true - 0.0.2 + 0.0.3 NGnius MIT https://git.exmods.org/NGnius/CLre