Implement networking API functionality

This commit is contained in:
NGnius (Graham) 2021-05-26 20:22:23 -04:00
parent 8b470aa690
commit 6db837b11a
24 changed files with 1147 additions and 10 deletions

View file

@ -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> 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> 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");
}
}
}

View file

@ -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;
}
}

View file

@ -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<SerializedCLreHandshake> _sendMessage;
private Utility.Reflection.INetMsgClientListener_RegisterListener<SerializedCLreHandshake> _registerListener;
public override void Ready()
{
//Utility.Logging.MetaLog("Building send message delegate");
_sendMessage =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgClientSender_SendMessage<SerializedCLreHandshake>>(
"GameNetworkLayer.Client.NetMessageClientSender:SendMessage",
generics: new [] {typeof(SerializedCLreHandshake)},
instance: MainLevel_BuildClasses_Patch.netMessageSender);
//Utility.Logging.MetaLog("Building register listener delegate");
_registerListener =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgClientListener_RegisterListener<SerializedCLreHandshake>>(
"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;
}
}
}

View file

@ -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<SerializedCLreMessage> _sendMessage;
private Utility.Reflection.INetMsgClientListener_RegisterListener<SerializedCLreMessage> _registerListener;
private Queue<MessageQueueItem> _messageQueue = new Queue<MessageQueueItem>(10);
private bool _isRunning = false;
public override void Ready()
{
//Utility.Logging.MetaLog("Building send message delegate");
_sendMessage =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgClientSender_SendMessage<SerializedCLreMessage>>(
"GameNetworkLayer.Client.NetMessageClientSender:SendMessage",
generics: new [] {typeof(SerializedCLreMessage)},
instance: MainLevel_BuildClasses_Patch.netMessageSender);
//Utility.Logging.MetaLog("Building register listener delegate");
_registerListener =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgClientListener_RegisterListener<SerializedCLreMessage>>(
"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;
}
}
}

View file

@ -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<uint, Action<ReceiveMessageArgs>> handlers =
new Dictionary<uint, Action<ReceiveMessageArgs>>();
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<ReceiveMessageArgs> handler)
{
if (handlers.TryGetValue(id, out Action<ReceiveMessageArgs> 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<ReceiveMessageArgs> h))
{
h(payload);
}
}
}
public struct ReceiveMessageArgs
{
public SerializedCLreMessage Message;
}
}

View file

@ -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<string> 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<string> 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<string>(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<string> mods = new List<string>();
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<string> mods = new List<string>(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<string>(),
};
}
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,
}
}

View file

@ -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();
}
}
}
}
}
}

View file

@ -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());
}

View file

@ -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<T>(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct;
public delegate object[] SendMessage<T>(NetworkDispatcherCode dispatcherCode, ref T value) where T : struct, ISerializedNetData;
public delegate void INetMsgClientSender_SendMessage<T>(NetworkDispatcherCode code, ref T value) where T : struct, ISerializedNetData;
public delegate void INetMsgClientListener_RegisterListener<T>(NetworkDispatcherCode code, NetCBClient<T> proc) where T: struct, ISerializedNetData;
// useful reflection functions
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method) where TFuncProto : Delegate

View file

@ -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<Shared.Inventory.HandheldEquipmentRequest>");
netData = typeof(API.Synergy.SerializedCLreHandshake);
NetClientSender.DebugSendMessage(netData, harmonyInstance,
NetClientSender.GetLogMethod(netData));
API.Utility.Logging.MetaLog("Patched SendMessage<SerializedCLreHandshake>");
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 += (_, __) =>
{

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>0.0.2</Version>
<Version>0.0.3</Version>
<Authors>NGnius</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/NGnius/CLre</PackageProjectUrl>

View file

@ -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<StartedEventArgs> Connected
{
add => PhotonNetwork_ConnectUsingSettings_Patch.postConnect += value;
remove => PhotonNetwork_ConnectUsingSettings_Patch.postConnect -= value;
}
public event EventHandler<PlayerConnectArgs> PlayerConnect
{
add => MainGameServer_SetupContainer_Patch.playerConnected += value;
remove => MainGameServer_SetupContainer_Patch.playerConnected -= value;
}
public event EventHandler<PlayerConnectArgs> 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<PlayerConnectArgs> playerConnected;
internal static event EventHandler<PlayerConnectArgs> 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,
});
}
}
}

View file

@ -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(),
});
}

View file

@ -11,4 +11,9 @@ namespace CLre_server.API.MainServer
}
public struct StopEventArgs{}
public struct PlayerConnectArgs
{
public int PlayerId;
}
}

View file

@ -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<DisconnectPlayer_VerificationFailedProto>(uvsType,
"DisconnectPlayer_VerificationFailed",
//parameters: new [] {typeof(int), typeof(string)},
instance: uvs);
_disconnectPlayer =
Utility.Reflection.MethodAsDelegate<DisconnectPlayerProto>(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");
}
}
}

View file

@ -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<uint, Action<ReceiveMessageArgs>> handlers =
new Dictionary<uint, Action<ReceiveMessageArgs>>();
private static readonly ServerMessagingEngine msgEngine = new ServerMessagingEngine();
private static readonly List<int> clrePlayers = new List<int>();
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<ReceiveMessageArgs> handler)
{
if (handlers.TryGetValue(id, out Action<ReceiveMessageArgs> 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<ReceiveMessageArgs> h))
{
h(payload);
}
}
internal static void RegisterCLreClient(int playerId)
{
clrePlayers.Add(playerId);
}
}
public struct ReceiveMessageArgs
{
public SerializedCLreMessage Message;
public int PlayerId;
}
}

View file

@ -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<string> 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<string> 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<string>(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<string> mods = new List<string>();
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<string> mods = new List<string>(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<string>(),
};
}
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,
}
}

View file

@ -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();
}
}
}
}
}
}

View file

@ -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<SerializedCLreHandshake> _sendMessage;
private Utility.Reflection.INetMsgServerListener_RegisterListener<SerializedCLreHandshake> _registerListener;
public override void Ready()
{
Utility.Logging.MetaLog("Building send message delegate");
_sendMessage =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreHandshake>>(
"GameNetworkLayer.Server.NetMessageServerSender:SendMessage",
generics: new [] {typeof(SerializedCLreHandshake)},
instance: MainGameServer_SetupContainer_Patch.netMessageSender);
Utility.Logging.MetaLog("Building register listener delegate");
_registerListener =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgServerListener_RegisterListener<SerializedCLreHandshake>>(
"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;
}
}
}

View file

@ -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<SerializedCLreMessage> _sendMessage;
private Utility.Reflection.INetMsgServerListener_RegisterListener<SerializedCLreMessage> _registerListener;
private Queue<MessageQueueItem> _messageQueue = new Queue<MessageQueueItem>(10);
private bool _isRunning = false;
public override void Ready()
{
//Utility.Logging.MetaLog("Building send message delegate");
_sendMessage =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreMessage>>(
"GameNetworkLayer.Server.NetMessageServerSender:SendMessage",
generics: new [] {typeof(SerializedCLreMessage)},
instance: MainGameServer_SetupContainer_Patch.netMessageSender);
//Utility.Logging.MetaLog("Building register listener delegate");
_registerListener =
Utility.Reflection.MethodAsDelegate<Utility.Reflection.INetMsgServerListener_RegisterListener<SerializedCLreMessage>>(
"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;
}
}
}

View file

@ -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());
}

View file

@ -28,6 +28,11 @@ namespace CLre_server.API.Utility
public delegate T[] QueryEntitiesV1<T>(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct;
public delegate object[] SendMessage<T>(NetworkDispatcherCode dispatcherCode, ref T value) where T : struct, ISerializedNetData;
public delegate void INetMsgServerSender_SendMessage<T>(NetworkDispatcherCode code, ref T value, int playerId) where T : struct, ISerializedNetData;
public delegate void INetMsgServerListener_RegisterListener<T>(NetworkDispatcherCode code, NetCBServer<T> proc) where T: struct, ISerializedNetData;
// useful reflection functions
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method) where TFuncProto : Delegate

View file

@ -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<Shared.Inventory.HandheldEquipmentRequest>");
netData = typeof(API.Synergy.SerializedCLreHandshake);
NetServerSender.DebugSendMessage(netData, harmonyInstance,
NetServerSender.GetLogMethod(netData));
API.Utility.Logging.MetaLog("Patched SendMessage<SerializedCLreHandshake>");
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

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Version>0.0.2</Version>
<Version>0.0.3</Version>
<Authors>NGnius</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.exmods.org/NGnius/CLre</PackageProjectUrl>