using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using Lidgren.Network; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Patches; using StardewValley; using StardewValley.Network; namespace StardewModdingAPI.Framework.Networking { /// A multiplayer server used to connect to an incoming player. This is an implementation of that adds support for SMAPI's metadata context exchange. internal class SLidgrenServer : LidgrenServer { /********* ** Properties *********/ /// The constructor for the internal NetBufferReadStream type. private readonly ConstructorInfo NetBufferReadStreamConstructor = SLidgrenServer.GetNetBufferReadStreamConstructor(); /// The constructor for the internal NetBufferWriteStream type. private readonly ConstructorInfo NetBufferWriteStreamConstructor = SLidgrenServer.GetNetBufferWriteStreamConstructor(); /// A method which reads farmer data from the given binary reader. private readonly Func ReadFarmer; /// A callback to raise when receiving a message. This receives the server instance, raw/parsed incoming message, and a callback to run the default logic. private readonly Action OnProcessingMessage; /// A callback to raise when sending a message. This receives the server instance, outgoing connection, outgoing message, target player ID, and a callback to run the default logic. private readonly Action OnSendingMessage; /// The peer connections. private readonly Bimap Peers; /// The underlying net server. private readonly IReflectedField Server; /********* ** Public methods *********/ /// Construct an instance. /// The underlying game server. /// Simplifies access to private code. /// A method which reads farmer data from the given binary reader. /// A callback to raise when receiving a message. This receives the server instance, raw/parsed incoming message, and a callback to run the default logic. /// A callback to raise when sending a message. This receives the server instance, outgoing connection, outgoing message, and a callback to run the default logic. public SLidgrenServer(IGameServer gameServer, Reflector reflection, Func readFarmer, Action onProcessingMessage, Action onSendingMessage) : base(gameServer) { this.ReadFarmer = readFarmer; this.OnProcessingMessage = onProcessingMessage; this.OnSendingMessage = onSendingMessage; this.Peers = reflection.GetField>(this, "peers").GetValue(); this.Server = reflection.GetField(this, "server"); } /// Send a message to a remote server. /// The network connection. /// The message to send. /// This is an implementation of which calls . This method is invoked via . public void SendMessage(NetConnection connection, OutgoingMessage message) { this.OnSendingMessage(this, connection, message, () => { NetServer server = this.Server.GetValue(); NetOutgoingMessage netMessage = server.CreateMessage(); using (Stream bufferWriteStream = (Stream)this.NetBufferWriteStreamConstructor.Invoke(new object[] { netMessage })) using (BinaryWriter writer = new BinaryWriter(bufferWriteStream)) message.Write(writer); server.SendMessage(netMessage, connection, NetDeliveryMethod.ReliableOrdered); }); } /// Parse a data message from a client. /// The raw network message to parse. /// This is an implementation of which calls . This method is invoked via . [SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "The callback is invoked synchronously.")] public bool ParseDataMessageFromClient(NetIncomingMessage rawMessage) { // add hook to call multiplayer core NetConnection peer = rawMessage.SenderConnection; using (IncomingMessage message = new IncomingMessage()) using (Stream readStream = (Stream)this.NetBufferReadStreamConstructor.Invoke(new object[] { rawMessage })) using (BinaryReader reader = new BinaryReader(readStream)) { while (rawMessage.LengthBits - rawMessage.Position >= 8) { message.Read(reader); this.OnProcessingMessage(this, rawMessage, message, () => { if (this.Peers.ContainsLeft(message.FarmerID) && this.Peers[message.FarmerID] == peer) this.gameServer.processIncomingMessage(message); else if (message.MessageType == Multiplayer.playerIntroduction) { NetFarmerRoot farmer = this.ReadFarmer(message.Reader); this.gameServer.checkFarmhandRequest("", farmer, msg => this.SendMessage(peer, msg), () => this.Peers[farmer.Value.UniqueMultiplayerID] = peer); } }); } } return false; } /********* ** Private methods *********/ /// Get the constructor for the internal NetBufferReadStream type. private static ConstructorInfo GetNetBufferReadStreamConstructor() { // get type string typeName = $"StardewValley.Network.NetBufferReadStream, {Constants.GameAssemblyName}"; Type type = Type.GetType(typeName); if (type == null) throw new InvalidOperationException($"Can't find type: {typeName}"); // get constructor ConstructorInfo constructor = type.GetConstructor(new[] { typeof(NetBuffer) }); if (constructor == null) throw new InvalidOperationException($"Can't find constructor for type: {typeName}"); return constructor; } /// Get the constructor for the internal NetBufferWriteStream type. private static ConstructorInfo GetNetBufferWriteStreamConstructor() { // get type string typeName = $"StardewValley.Network.NetBufferWriteStream, {Constants.GameAssemblyName}"; Type type = Type.GetType(typeName); if (type == null) throw new InvalidOperationException($"Can't find type: {typeName}"); // get constructor ConstructorInfo constructor = type.GetConstructor(new[] { typeof(NetBuffer) }); if (constructor == null) throw new InvalidOperationException($"Can't find constructor for type: {typeName}"); return constructor; } } }