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(); /// 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 incoming message, a method to send a message, and a callback to run the default logic. private readonly Action, Action> OnProcessingMessage; /// The peer connections. private readonly Bimap Peers; /********* ** 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 incoming message, a method to send a message, and a callback to run the default logic. public SLidgrenServer(IGameServer gameServer, Reflector reflection, Func readFarmer, Action, Action> onProcessingMessage) : base(gameServer) { this.ReadFarmer = readFarmer; this.OnProcessingMessage = onProcessingMessage; this.Peers = reflection.GetField>(this, "peers").GetValue(); } /// 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(message, outgoing => this.sendMessage(rawMessage.SenderConnection, outgoing), () => { 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; } } }