From ac1c274b02cd48973553c83dd3a822fdddbfe5a7 Mon Sep 17 00:00:00 2001 From: kmyuhkyuk <72241714+kmyuhkyuk@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:27:41 +0800 Subject: [PATCH] Fixed IdCollision HandshakeResponse will not send Added ConnectToHost auto try NetID feature --- .../Components/IPAddressLineEdit.cs | 22 +----- .../ENetClientConnectionInitializerPatch.cs | 73 +++++++++++++++++++ .../Patchs/ENetHostPatch.cs | 40 ++++++++++ 3 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientConnectionInitializerPatch.cs create mode 100644 SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs index d34b06e..e8db748 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs @@ -22,27 +22,9 @@ namespace SlayTheSpire2.LAN.Multiplayer.Components public bool IsIPAndPort => IPAndPortRegex().IsMatch(Text); - public string? IPAddress - { - get - { - if (IsOnlyIP) - return Text; + public string? IPAddress => IsOnlyIP ? Text : IsIPAndPort ? Text.Split(':').First() : null; - return IsIPAndPort ? Text.Split(':').First() : null; - } - } - - public ushort? Port - { - get - { - if (IsIPAndPort) - return Convert.ToUInt16(Text.Split(':').Last()); - - return null; - } - } + public ushort? Port => IsIPAndPort ? Convert.ToUInt16(Text.Split(':').Last()) : null; public override void _Ready() { diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientConnectionInitializerPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientConnectionInitializerPatch.cs new file mode 100644 index 0000000..670fad5 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientConnectionInitializerPatch.cs @@ -0,0 +1,73 @@ +using HarmonyLib; +using MegaCrit.Sts2.Core.Entities.Multiplayer; +using MegaCrit.Sts2.Core.Helpers; +using MegaCrit.Sts2.Core.Logging; +using MegaCrit.Sts2.Core.Multiplayer; +using MegaCrit.Sts2.Core.Multiplayer.Connection; +using MegaCrit.Sts2.Core.Multiplayer.Transport.ENet; +using MegaCrit.Sts2.Core.Platform; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch(typeof(ENetClientConnectionInitializer), "Connect")] + internal class ENetClientConnectionInitializerPatch + { + private static bool Prefix(NetClientGameService gameService, + CancellationToken cancelToken, ulong ____netId, string ____ip, ushort ____port, ref Task __result) + { + __result = TaskHelper.RunSafely(Connect(gameService, cancelToken, ____netId, ____ip, ____port)); + + return false; + } + + private static async Task Connect(NetClientGameService gameService, + CancellationToken cancelToken, ulong netId, string ip, ushort port) + { + if (gameService.IsConnected) + { + throw new InvalidOperationException( + "NetClientGameService must not be connected when passed to ENetClientConnectionInitializer!"); + } + + var eNetClient = new ENetClient(gameService); + gameService.Initialize(eNetClient, PlatformType.None); + + var count = 0; + const int tryCount = 10; + NetErrorInfo? netErrorInfo = null; + + while (count < tryCount) + { + if (cancelToken.IsCancellationRequested) + return netErrorInfo; + + netErrorInfo = await eNetClient.ConnectToHost(netId, ip, port, cancelToken); + + if (!netErrorInfo.HasValue) + { + Log.Info($"Connect {ip}:{port} Host Game NetID:{netId}"); + return null; + } + + if (netErrorInfo.Value.GetReason() != NetError.Kicked) + return netErrorInfo; + + var nextNetId = netId + 1000u; + + if (count < tryCount - 1) + { + Log.Warn($"{ip}:{port} Host Game NetID:{netId} already occupied, next try NetID:{nextNetId}"); + } + + netId = nextNetId; + + count++; + } + + return netErrorInfo; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs new file mode 100644 index 0000000..41a8028 --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs @@ -0,0 +1,40 @@ +using System.Reflection; +using System.Reflection.Emit; +using Godot; +using HarmonyLib; +using MegaCrit.Sts2.Core.Multiplayer.Transport.ENet; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedType.Global + +namespace SlayTheSpire2.LAN.Multiplayer.Patchs +{ + [HarmonyPatch] + internal class ENetHostDoClientHandshakePatch + { + private static MethodInfo TargetMethod() + { + return AccessTools.AsyncMoveNext(typeof(ENetHost).GetMethod("DoClientHandshake", + BindingFlags.Instance | BindingFlags.NonPublic)); + } + + private static IEnumerable Transpiler(IEnumerable instructions) + { + //Fixed IdCollision HandshakeResponse will not send + + var peerDisconnectMethod = AccessTools.Method(typeof(ENetPacketPeer), "PeerDisconnect"); + var peerDisconnectLaterMethod = AccessTools.Method(typeof(ENetPacketPeer), "PeerDisconnectLater"); + + foreach (var instruction in instructions) + { + if (instruction.opcode == OpCodes.Callvirt && instruction.operand as MethodInfo == peerDisconnectMethod) + { + yield return new CodeInstruction(OpCodes.Callvirt, peerDisconnectLaterMethod); + continue; + } + + yield return instruction; + } + } + } +} \ No newline at end of file