using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using Harmony; using Lidgren.Network; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Networking; using StardewModdingAPI.Framework.Patching; using StardewValley; using StardewValley.Network; namespace StardewModdingAPI.Patches { /// A Harmony patch to enable the SMAPI multiplayer metadata handshake. internal class NetworkingPatch : IHarmonyPatch { /********* ** Properties *********/ /// The constructor for the internal NetBufferReadStream type. private static readonly ConstructorInfo NetBufferReadStreamConstructor = NetworkingPatch.GetNetBufferReadStreamConstructor(); /********* ** Accessors *********/ /// A unique name for this patch. public string Name => $"{nameof(NetworkingPatch)}"; /********* ** Public methods *********/ /// Apply the Harmony patch. /// The Harmony instance. public void Apply(HarmonyInstance harmony) { MethodInfo method = AccessTools.Method(typeof(LidgrenServer), "parseDataMessageFromClient"); MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(NetworkingPatch.Prefix_LidgrenServer_ParseDataMessageFromClient)); harmony.Patch(method, new HarmonyMethod(prefix), null); } /********* ** Private methods *********/ /// The method to call instead of the method. /// The instance being patched. /// The raw network message to parse. /// The private peers field on the instance. /// The private gameServer field on the instance. /// Returns whether to execute the original method. /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static bool Prefix_LidgrenServer_ParseDataMessageFromClient(LidgrenServer __instance, NetIncomingMessage dataMsg, Bimap ___peers, IGameServer ___gameServer) { // get SMAPI overrides SMultiplayer multiplayer = ((SGame)Game1.game1).Multiplayer; SLidgrenServer server = (SLidgrenServer)__instance; // add hook to call multiplayer core NetConnection peer = dataMsg.SenderConnection; using (IncomingMessage message = new IncomingMessage()) using (Stream readStream = (Stream)NetworkingPatch.NetBufferReadStreamConstructor.Invoke(new object[] { dataMsg })) using (BinaryReader reader = new BinaryReader(readStream)) { while (dataMsg.LengthBits - dataMsg.Position >= 8) { message.Read(reader); if (___peers.ContainsLeft(message.FarmerID) && ___peers[message.FarmerID] == peer) ___gameServer.processIncomingMessage(message); else if (message.MessageType == Multiplayer.playerIntroduction) { NetFarmerRoot farmer = multiplayer.readFarmer(message.Reader); ___gameServer.checkFarmhandRequest("", farmer, msg => server.SendMessage(peer, msg), () => ___peers[farmer.Value.UniqueMultiplayerID] = peer); } else multiplayer.ProcessMessageFromUnknownFarmhand(__instance, dataMsg, message); // added hook } } return false; } /// 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; } } }