summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Networking/SLidgrenServer.cs
blob: 060b433bbdfd8f5e397d085f03425fa6d439d62a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
{
    /// <summary>A multiplayer server used to connect to an incoming player. This is an implementation of <see cref="LidgrenServer"/> that adds support for SMAPI's metadata context exchange.</summary>
    internal class SLidgrenServer : LidgrenServer
    {
        /*********
        ** Properties
        *********/

        /// <summary>The constructor for the internal <c>NetBufferReadStream</c> type.</summary>
        private readonly ConstructorInfo NetBufferReadStreamConstructor = SLidgrenServer.GetNetBufferReadStreamConstructor();

        /// <summary>The constructor for the internal <c>NetBufferWriteStream</c> type.</summary>
        private readonly ConstructorInfo NetBufferWriteStreamConstructor = SLidgrenServer.GetNetBufferWriteStreamConstructor();

        /// <summary>A method which reads farmer data from the given binary reader.</summary>
        private readonly Func<BinaryReader, NetFarmerRoot> ReadFarmer;

        /// <summary>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.</summary>
        private readonly Action<SLidgrenServer, NetIncomingMessage, IncomingMessage, Action> OnProcessingMessage;

        /// <summary>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.</summary>
        private readonly Action<SLidgrenServer, NetConnection, OutgoingMessage, Action> OnSendingMessage;

        /// <summary>The peer connections.</summary>
        private readonly Bimap<long, NetConnection> Peers;

        /// <summary>The underlying net server.</summary>
        private readonly IReflectedField<NetServer> Server;


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="gameServer">The underlying game server.</param>
        /// <param name="reflection">Simplifies access to private code.</param>
        /// <param name="readFarmer">A method which reads farmer data from the given binary reader.</param>
        /// <param name="onProcessingMessage">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.</param>
        /// <param name="onSendingMessage">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.</param>
        public SLidgrenServer(IGameServer gameServer, Reflector reflection, Func<BinaryReader, NetFarmerRoot> readFarmer, Action<SLidgrenServer, NetIncomingMessage, IncomingMessage, Action> onProcessingMessage, Action<SLidgrenServer, NetConnection, OutgoingMessage, Action> onSendingMessage)
            : base(gameServer)
        {
            this.ReadFarmer = readFarmer;
            this.OnProcessingMessage = onProcessingMessage;
            this.OnSendingMessage = onSendingMessage;
            this.Peers = reflection.GetField<Bimap<long, NetConnection>>(this, "peers").GetValue();
            this.Server = reflection.GetField<NetServer>(this, "server");
        }

        /// <summary>Send a message to a remote server.</summary>
        /// <param name="connection">The network connection.</param>
        /// <param name="message">The message to send.</param>
        /// <remarks>This is an implementation of <see cref="LidgrenServer.sendMessage(NetConnection, OutgoingMessage)"/> which calls <see cref="OnSendingMessage"/>. This method is invoked via <see cref="LidgrenServerPatch.Prefix_LidgrenServer_SendMessage"/>.</remarks>
        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);
            });
        }

        /// <summary>Parse a data message from a client.</summary>
        /// <param name="rawMessage">The raw network message to parse.</param>
        /// <remarks>This is an implementation of <see cref="LidgrenServer.parseDataMessageFromClient"/> which calls <see cref="OnProcessingMessage"/>. This method is invoked via <see cref="LidgrenServerPatch.Prefix_LidgrenServer_ParseDataMessageFromClient"/>.</remarks>
        [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
        *********/
        /// <summary>Get the constructor for the internal <c>NetBufferReadStream</c> type.</summary>
        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;
        }

        /// <summary>Get the constructor for the internal <c>NetBufferWriteStream</c> type.</summary>
        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;
        }
    }
}