From ff0c021a123d90fc4e7295e76fed40c8b2547585 Mon Sep 17 00:00:00 2001 From: kmyuhkyuk <72241714+kmyuhkyuk@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:38:50 +0800 Subject: [PATCH] Supports url address and ipv6 connecting --- .../Components/AddressLineEdit.cs | 69 +++++++++++++++++++ .../Components/IPAddressLineEdit.cs | 61 ---------------- .../Patchs/ENetClientPatch.cs | 9 ++- .../Patchs/ENetHostPatch.cs | 22 ++++++ .../Patchs/NJoinFriendScreenPatch.cs | 31 ++++----- .../SlayTheSpire2.LAN.Multiplayer.csproj | 3 + 6 files changed, 115 insertions(+), 80 deletions(-) create mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs delete mode 100644 SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs new file mode 100644 index 0000000..dcce3ca --- /dev/null +++ b/SlayTheSpire2.LAN.Multiplayer/Components/AddressLineEdit.cs @@ -0,0 +1,69 @@ +using System.Net; +using Godot; +using MegaCrit.Sts2.Core.Nodes.GodotExtensions; + +namespace SlayTheSpire2.LAN.Multiplayer.Components +{ + internal class AddressLineEdit : NMegaLineEdit + { + public override void _Ready() + { + TextSubmitted += OnTextSubmitted; + } + + public struct AddressInfo(bool isValid, string? address, ushort? port) + { + public bool IsValid = isValid; + public string? Address = address; + public ushort? Port = port; + } + + public AddressInfo GetAddressInfo() + { + var addressInfo = new AddressInfo(); + + var uriString = Text; + + if (uriString == "localhost") + { + uriString = $"http://{uriString}"; + } + + if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri) && uri.Scheme is "http" or "https") + { + addressInfo.IsValid = true; + addressInfo.Address = uri.Host; + + if (uri is { Port: > -1, IsDefaultPort: false }) + { + addressInfo.Port = (ushort)uri.Port; + } + + return addressInfo; + } + + if (IPAddress.TryParse(Text, out _)) + { + addressInfo.IsValid = true; + addressInfo.Address = Text; + } + else if (IPEndPoint.TryParse(Text, out var ipEndPoint)) + { + addressInfo.IsValid = true; + addressInfo.Port = (ushort)ipEndPoint.Port; + } + + return addressInfo; + } + + private void OnTextSubmitted(string newText) + { + Modulate = Colors.White; + + if (GetAddressInfo().IsValid) + return; + + Modulate = Colors.Red; + } + } +} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs b/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs deleted file mode 100644 index e8db748..0000000 --- a/SlayTheSpire2.LAN.Multiplayer/Components/IPAddressLineEdit.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Text.RegularExpressions; -using Godot; -using MegaCrit.Sts2.Core.Nodes.GodotExtensions; - -namespace SlayTheSpire2.LAN.Multiplayer.Components -{ - internal partial class IPAddressLineEdit : NMegaLineEdit - { - [GeneratedRegex(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$")] - private static partial Regex IPRegex(); - - [GeneratedRegex( - @"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d):((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([1-9][0-9]{0,3})|[0-9])$")] - private static partial Regex IPAndPortRegex(); - - [GeneratedRegex("^[0-9.:]*$")] - private static partial Regex IPAllowRegex(); - - private string _oldText = ""; - - public bool IsOnlyIP => IPRegex().IsMatch(Text); - - public bool IsIPAndPort => IPAndPortRegex().IsMatch(Text); - - public string? IPAddress => IsOnlyIP ? Text : IsIPAndPort ? Text.Split(':').First() : null; - - public ushort? Port => IsIPAndPort ? Convert.ToUInt16(Text.Split(':').Last()) : null; - - public override void _Ready() - { - TextChanged += OnTextChanged; - TextSubmitted += OnTextSubmitted; - } - - private void OnTextChanged(string newText) - { - if (IPAllowRegex().IsMatch(newText)) - { - _oldText = newText; - } - else - { - Text = _oldText; - CaretColumn = Text.Length; - } - } - - private void OnTextSubmitted(string newText) - { - Modulate = Colors.White; - - if (IPRegex().IsMatch(newText)) - return; - - if (IPAndPortRegex().IsMatch(newText)) - return; - - Modulate = Colors.Red; - } - } -} \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs index 713816e..37b757e 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetClientPatch.cs @@ -24,7 +24,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs return false; } - private static async Task ConnectToHost(ENetClient eNetClient, Logger logger, INetClientHandler handler, ulong netId, string ip, ushort port, CancellationToken cancelToken) + private static async Task ConnectToHost(ENetClient eNetClient, Logger logger, + INetClientHandler handler, ulong netId, string ip, ushort port, CancellationToken cancelToken) { while (true) { @@ -34,7 +35,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs var peer = connection.ConnectToHost(ip, port); Traverse.Create(eNetClient).Field("_peer").SetValue(peer); var timeoutTimer = 0; - while (!connection.TryService(out var output) || output is not { type: ENetConnection.EventType.Connect }) + while (!connection.TryService(out var output) || + output is not { type: ENetConnection.EventType.Connect }) { await Task.Delay(100, cancelToken); if (cancelToken.IsCancellationRequested) @@ -60,7 +62,8 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs } var bufferedPackets = new List(); - var (result, newNetId) = await SendAndWaitForNetIdAck(eNetClient, logger, peer, connection, netId, bufferedPackets, cancelToken); + var (result, newNetId) = await SendAndWaitForNetIdAck(eNetClient, logger, peer, connection, netId, + bufferedPackets, cancelToken); if (result.HasValue) { peer.PeerDisconnect(); diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs index f12b3f7..8cd1274 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/ENetHostPatch.cs @@ -1,6 +1,7 @@ using System.Collections; using Godot; using HarmonyLib; +using MegaCrit.Sts2.Core.Entities.Multiplayer; using MegaCrit.Sts2.Core.Helpers; using MegaCrit.Sts2.Core.Multiplayer.Transport; using MegaCrit.Sts2.Core.Multiplayer.Transport.ENet; @@ -97,4 +98,25 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs } } } + + [HarmonyPatch(typeof(ENetHost), "StartHost")] + internal class ENetHostStartHostPatch + { + private static bool Prefix(ushort port, int maxClients, Logger ____logger, ref ENetConnection? ____connection, + ref bool ____isConnected, ref NetErrorInfo? __result) + { + ____connection = new ENetConnection(); + var error = ____connection.CreateHostBound("*", port, maxClients); + if (error != Error.Ok) + { + ____logger.Error($"Failed to create host! {error}"); + __result = new NetErrorInfo(error); + return false; + } + + ____isConnected = true; + + return false; + } + } } \ No newline at end of file diff --git a/SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs b/SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs index abb7326..372d53f 100644 --- a/SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs +++ b/SlayTheSpire2.LAN.Multiplayer/Patchs/NJoinFriendScreenPatch.cs @@ -62,14 +62,14 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs ipAddressLabel.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter; } - var ipAddressLineEdit = new IPAddressLineEdit { Name = "IPInput" }; + var addressLineEdit = new AddressLineEdit { Name = "AddressInput" }; - vBoxContainer.AddChild(ipAddressLineEdit); + vBoxContainer.AddChild(addressLineEdit); - ipAddressLineEdit.Text = SettingsHelper.Instance.SettingsModel.IPAddress; - ipAddressLineEdit.Alignment = HorizontalAlignment.Center; - ipAddressLineEdit.CustomMinimumSize = new Vector2(300, 50); - ipAddressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter; + addressLineEdit.Text = SettingsHelper.Instance.SettingsModel.IPAddress; + addressLineEdit.Alignment = HorizontalAlignment.Center; + addressLineEdit.CustomMinimumSize = new Vector2(300, 50); + addressLineEdit.SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter; if (__instance.GetNode("RefreshButton").Duplicate() is NJoinFriendRefreshButton joinButton) @@ -83,28 +83,27 @@ namespace SlayTheSpire2.LAN.Multiplayer.Patchs joinButton.Connect(NClickableControl.SignalName.Released, Callable.From(_ => { - if (!ipAddressLineEdit.IsOnlyIP && - (!ipAddressLineEdit.IsIPAndPort || !ipAddressLineEdit.Port.HasValue)) + var addressInfo = addressLineEdit.GetAddressInfo(); + + if (!addressInfo.IsValid) return; - SettingsHelper.Instance.SettingsModel.IPAddress = ipAddressLineEdit.Text; + SettingsHelper.Instance.SettingsModel.IPAddress = addressLineEdit.Text; SettingsHelper.Instance.WriteSettings(); - var ipAddress = ipAddressLineEdit.IPAddress; ushort port = 33771; - if (ipAddressLineEdit.Port.HasValue) + if (addressInfo.Port.HasValue) { - port = ipAddressLineEdit.Port.Value; + port = addressInfo.Port.Value; } - var netId = SettingsHelper.Instance.SettingsModel.NetId; - DisplayServer.WindowSetTitle("Slay The Spire 2 (Client)"); - if (ipAddress != null) + if (addressInfo.Address != null) { TaskHelper.RunSafely( - __instance.JoinGameAsync(new ENetClientConnectionInitializer(netId, ipAddress, port))); + __instance.JoinGameAsync(new ENetClientConnectionInitializer( + SettingsHelper.Instance.SettingsModel.NetId, addressInfo.Address, port))); } })); diff --git a/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj b/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj index 4a4599e..54092f1 100644 --- a/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj +++ b/SlayTheSpire2.LAN.Multiplayer/SlayTheSpire2.LAN.Multiplayer.csproj @@ -15,6 +15,9 @@ V:\Slay.the.Spire.2\game\data_sts2_windows_x86_64\GodotSharp.dll + + V:\Slay.the.Spire.2\game\data_sts2_windows_x86_64\Steamworks.NET.dll + V:\Slay.the.Spire.2\game\data_sts2_windows_x86_64\sts2.dll