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