Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Sakura Direct Connect v0.1.3
BepInEx/plugins/SakuraDirectConnect/SakuraDirectConnect.dll
Decompiled 3 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using RepoSteamTransport; using RepoSteamTransport.Transport; using Steamworks; using Steamworks.Data; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SakuraDirectConnect")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.3.0")] [assembly: AssemblyInformationalVersion("0.1.3")] [assembly: AssemblyProduct("SakuraDirectConnect")] [assembly: AssemblyTitle("SakuraDirectConnect")] [assembly: AssemblyVersion("0.1.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SakuraDirectConnect { internal enum ResolutionStatus { Empty, Pending, Ready, Failed } internal readonly struct HostResolution { internal readonly ResolutionStatus Status; internal readonly string Ip; internal readonly string Detail; internal HostResolution(ResolutionStatus status, string ip = "", string detail = "") { Status = status; Ip = ip; Detail = detail; } } internal sealed class HostResolver { private readonly object _gate = new object(); private readonly Func<string, Task<IPAddress[]>> _lookup; private readonly Func<long> _now; private string _key = ""; private string _pendingKey = ""; private string _ready = ""; private string _error = ""; private Task<IPAddress[]>? _pending; private long _started; private long _retryAt; private bool _timedOut; internal HostResolver() : this((string name) => Task.Run(() => Dns.GetHostAddressesAsync(name)), CreateClock()) { } internal HostResolver(Func<string, Task<IPAddress[]>> lookup, Func<long> now) { _lookup = lookup; _now = now; } private static Func<long> CreateClock() { Stopwatch clock = Stopwatch.StartNew(); return () => clock.ElapsedMilliseconds; } internal static bool TryDomain(string? raw, out string domain) { domain = ""; if (string.IsNullOrWhiteSpace(raw)) { return false; } string text = raw.Trim(); if (text.EndsWith(".", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - 1); } if (text.Length == 0 || text.IndexOfAny(new char[8] { ':', '/', '\\', '@', '?', '#', '|', ' ' }) >= 0) { return false; } bool flag = true; string text2 = text; foreach (char c in text2) { if ((c < '0' || c > '9') && c != '.') { flag = false; break; } } if (flag || text.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { return false; } try { text = new IdnMapping().GetAscii(text).ToLowerInvariant(); } catch (ArgumentException) { return false; } if (text.Length > 253) { return false; } string[] array = text.Split('.'); if (array.Length < 2) { return false; } string[] array2 = array; foreach (string text3 in array2) { if (text3.Length == 0 || text3.Length > 63 || text3[0] == '-' || text3[text3.Length - 1] == '-') { return false; } text2 = text3; foreach (char c2 in text2) { if ((c2 < 'a' || c2 > 'z') && (c2 < '0' || c2 > '9') && c2 != '-') { return false; } } } domain = text; return true; } internal HostResolution Poll(string? raw, ulong lobbyId) { lock (_gate) { string text = (raw ?? "").Trim(); string text2 = lobbyId.ToString(CultureInfo.InvariantCulture) + "|" + text.ToLowerInvariant(); if (_key != text2) { _key = text2; _ready = (_error = ""); _retryAt = 0L; } if (text.Length == 0) { return new HostResolution(ResolutionStatus.Empty); } if (Routing.TryHostIPv4(text, out IPAddress address)) { return new HostResolution(ResolutionStatus.Ready, address.ToString()); } if (!TryDomain(text, out string domain)) { return new HostResolution(ResolutionStatus.Failed, "", "Use an IPv4 or domain only, without scheme, port or path."); } if (_ready.Length > 0) { return new HostResolution(ResolutionStatus.Ready, _ready); } if (_pending != null) { if (!_pending.IsCompleted) { if (_now() - _started >= 5000) { _timedOut = true; return new HostResolution(ResolutionStatus.Failed, "", "DNS lookup timed out; original transport retained while the OS finishes the lookup."); } return new HostResolution(ResolutionStatus.Pending); } Task<IPAddress[]> pending = _pending; _pending = null; bool num = _pendingKey == _key && !_timedOut; Exception exception = pending.Exception; if (num) { if (pending.Status == TaskStatus.RanToCompletion) { IPAddress[] result = pending.Result; foreach (IPAddress iPAddress in result) { if (Routing.IsUsable(iPAddress)) { _ready = iPAddress.ToString(); break; } } } if (_ready.Length > 0) { return new HostResolution(ResolutionStatus.Ready, _ready); } _error = ((exception == null) ? "DNS returned no usable IPv4 address (an A record is required)." : ("DNS lookup failed: " + exception.GetBaseException().Message)); _retryAt = _now() + 15000; } else if (_pendingKey == _key) { _error = "Discarded a DNS result after timeout; retrying in 15 seconds."; _retryAt = _now() + 15000; } } if (_now() < _retryAt) { return new HostResolution(ResolutionStatus.Failed, "", _error); } try { _pendingKey = _key; _started = _now(); _timedOut = false; _pending = _lookup(domain); return new HostResolution(ResolutionStatus.Pending); } catch (Exception ex) { _error = "DNS lookup failed: " + ex.Message; _retryAt = _now() + 15000; return new HostResolution(ResolutionStatus.Failed, "", _error); } } } } [BepInPlugin("rorodo.sakura.directconnect", "Sakura Direct Connect", "0.1.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { internal sealed class HostState { internal readonly string HostIp; internal readonly LocalNetwork[] Networks; internal readonly long Expires; internal HostState(string ip, LocalNetwork[] networks, long expires) { HostIp = ip; Networks = networks; Expires = expires; } } public const string Guid = "rorodo.sakura.directconnect"; internal static Plugin? Instance; private static readonly Stopwatch Clock = Stopwatch.StartNew(); private ConfigEntry<bool> _enabled; private ConfigEntry<string> _hostIp; private HostResolver _resolver = new HostResolver(); private ConfigEntry<bool> _preferLan; private Harmony? _harmony; private RoomLifecycle? _lifecycle; private float _nextTick; private float _nextNetworks; private string _lastWarning = ""; private string _lastRoute = ""; private ulong _routeLobby; private ulong _routeOwner; private string _routeAdvertised = ""; private string _routeSelected = ""; private long _routeExpires; private volatile HostState? _hostState; private LocalNetwork[] _networks = Array.Empty<LocalNetwork>(); private bool Active { get { if (_harmony != null && _enabled.Value) { return ModConfig.Enabled?.Value ?? false; } return false; } } private void Awake() { //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Expected O, but got Unknown Instance = this; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable this extension. Original REPO Steam P2P is still required."); _hostIp = ((BaseUnityPlugin)this).Config.Bind<string>("Host", "HostIPv4", "", "HOST ONLY: reachable IPv4 (public, LAN, VPN) OR domain, e.g. repo.example.com. No scheme, port or path. Legacy key name retained. DNS is resolved asynchronously to IPv4 once per lobby/config; restart the lobby after DNS changes. Cloudflare records must be DNS-only for ordinary direct UDP. Leave empty for the original transport."); _preferLan = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "PreferLAN", true, "Try the original host-ID LAN discovery before using the advertised host IP. Guests need no IP settings."); try { MethodInfo methodInfo = RequireMethod(typeof(ModConfig), "ResolveTargetAddress", typeof(string), Type.EmptyTypes); MethodInfo methodInfo2 = RequireMethod(typeof(SdrFabric), "ResolveAdvertiseIp", typeof(string), new Type[1] { typeof(IPAddress) }); MethodInfo methodInfo3 = RequireMethod(typeof(ModConfig), "TryGetLobby", typeof(bool), new Type[1] { typeof(Lobby).MakeByRefType() }); _harmony = new Harmony("rorodo.sakura.directconnect"); Assembly assembly = typeof(ModConfig).Assembly; Type type = assembly.GetType("RepoSteamTransport.Transport.VirtualServer", throwOnError: true); Type type2 = assembly.GetType("RepoSteamTransport.Transport.EnetCodec", throwOnError: true); Type lobbyManager = FindType("SteamLobbyManager"); _lifecycle = new RoomLifecycle(type, type2, delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }); _lifecycle.Install(_harmony, type, lobbyManager); _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin), "TargetPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Plugin), "AdvertisePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "LobbyPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Sakura Direct Connect 0.1.3 loaded; host-exit room cleanup enabled. Original DLL unchanged. Tested API: Makfly REPO Steam P2P package 1.1.3."); if (!string.IsNullOrWhiteSpace(ModConfig.HostAddress?.Value)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Original HostAddress is nonempty. Lobby auto-routing takes precedence in Steam lobbies; outside a lobby the original manual setting is preserved."); } } catch (Exception ex) { _hostState = null; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; ((BaseUnityPlugin)this).Logger.LogError((object)("Extension disabled: incompatible original MOD API or patch failure. " + ex)); } } private static MethodInfo RequireMethod(Type type, string name, Type returns, Type[] args) { MethodInfo method = type.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null); if (method == null || method.ReturnType != returns) { throw new MissingMethodException(type.FullName, name); } return method; } private void Update() { if (_harmony == null || Time.unscaledTime < _nextTick) { return; } _nextTick = Time.unscaledTime + 0.5f; try { if (Time.unscaledTime >= _nextNetworks) { _nextNetworks = Time.unscaledTime + 10f; _networks = ReadNetworks(); } Lobby val = default(Lobby); if (!ModConfig.TryGetLobby(ref val)) { ResetSession(); } } catch (Exception ex) { _hostState = null; WarnOnce("Lobby update failed: " + ex.Message); } } private void ResetSession() { _hostState = null; _routeLobby = (_routeOwner = 0uL); _routeAdvertised = (_routeSelected = (_lastRoute = "")); _routeExpires = 0L; } private static LocalNetwork[] ReadNetworks() { List<LocalNetwork> list = new List<LocalNetwork>(); NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback) { continue; } foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) { if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Routing.IsUsable(unicastAddress.Address) && unicastAddress.IPv4Mask != null) { list.Add(new LocalNetwork(unicastAddress.Address, unicastAddress.IPv4Mask)); } } } return list.ToArray(); } private void ObserveLobby(Lobby lobby) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) if (!SteamClient.IsValid) { ResetSession(); return; } ulong num = SteamId.op_Implicit(((Lobby)(ref lobby)).Owner.Id); ulong num2 = SteamId.op_Implicit(SteamClient.SteamId); if (num == 0L || num != num2) { _hostState = null; return; } _lifecycle?.ObserveHost(SteamId.op_Implicit(((Lobby)(ref lobby)).Id)); string text = ""; HostResolution hostResolution = (Active ? _resolver.Poll(_hostIp.Value, _lifecycle?.HostEpoch ?? SteamId.op_Implicit(((Lobby)(ref lobby)).Id)) : new HostResolution(ResolutionStatus.Empty)); if (hostResolution.Status == ResolutionStatus.Ready) { string ip = hostResolution.Ip; _hostState = new HostState(ip, _networks, Clock.ElapsedMilliseconds + 5000); text = Routing.Encode(num, ip); } else { _hostState = null; if (hostResolution.Status == ResolutionStatus.Failed) { WarnOnce(hostResolution.Detail); } } string data = ((Lobby)(ref lobby)).GetData("sakura_direct_v1"); if (!(data == text) && (!string.IsNullOrEmpty(data) || text.Length != 0)) { if ((text.Length == 0) ? ((Lobby)(ref lobby)).DeleteData("sakura_direct_v1") : ((Lobby)(ref lobby)).SetData("sakura_direct_v1", text)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)((text.Length == 0) ? "Removed this lobby's direct-IP announcement." : ("Published this host's direct IP: " + _hostState.HostIp + " (UDP 7777/7778/7779)."))); } else { WarnOnce("Steam lobby metadata write failed; will retry."); } } } private static void LobbyPostfix(bool __result, Lobby __0) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Instance; if ((Object)(object)instance == (Object)null) { return; } try { if (__result) { instance.ObserveLobby(__0); } else { instance.ResetSession(); } } catch (Exception ex) { instance._hostState = null; instance.WarnOnce("Lobby metadata unavailable: " + ex.Message); } } private static bool TargetPrefix(ref string __result) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Instance; if ((Object)(object)instance == (Object)null || !instance.Active) { return true; } try { Lobby val = default(Lobby); if (!SteamClient.IsValid || !ModConfig.TryGetLobby(ref val)) { return true; } ulong num = SteamId.op_Implicit(((Lobby)(ref val)).Owner.Id); ulong num2 = SteamId.op_Implicit(SteamClient.SteamId); ulong num3 = SteamId.op_Implicit(((Lobby)(ref val)).Id); if (num == 0L || num == num2) { return true; } if (!Routing.TryAnnouncement(((Lobby)(ref val)).GetData("sakura_direct_v1"), num, num2, out string ip)) { __result = num.ToString(CultureInfo.InvariantCulture); instance.LogRoute("Current lobby has no valid direct-IP announcement; using its owner and the original LAN/ZeroTier/Steam ladder."); return false; } if (instance._routeLobby != num3 || instance._routeOwner != num || instance._routeAdvertised != ip || Clock.ElapsedMilliseconds >= instance._routeExpires) { string routeSelected = ip; if (instance._preferLan.Value) { try { if (Routing.TryIPv4(LanDiscovery.FindHost(num, 1200), out IPAddress address) && Routing.IsUsable(address)) { routeSelected = address.ToString(); } } catch (Exception ex) { instance.WarnOnce("LAN discovery failed; using announced host IP: " + ex.Message); } } instance._routeLobby = num3; instance._routeOwner = num; instance._routeAdvertised = ip; instance._routeSelected = routeSelected; instance._routeExpires = Clock.ElapsedMilliseconds + 10000; } __result = instance._routeSelected; instance.LogRoute("Current lobby direct route: " + __result + " (owner " + num + ")."); return false; } catch (Exception ex2) { instance.WarnOnce("Auto-route unavailable; original transport retained: " + ex2.Message); return true; } } private static bool AdvertisePrefix(IPAddress __0, ref string __result) { Plugin? instance = Instance; HostState hostState = ((instance != null) ? instance._hostState : null); if (hostState == null || Clock.ElapsedMilliseconds >= hostState.Expires || __0 == null) { return true; } __result = Routing.Advertise(__0, hostState.HostIp, hostState.Networks); return false; } private void LogRoute(string message) { if (_lastRoute != message) { _lastRoute = message; ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } } private void WarnOnce(string message) { if (_lastWarning != message) { _lastWarning = message; ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } } private void OnDestroy() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) _hostState = null; try { Lobby val = default(Lobby); if (SteamClient.IsValid && ModConfig.TryGetLobby(ref val) && SteamId.op_Implicit(((Lobby)(ref val)).Owner.Id) == SteamId.op_Implicit(SteamClient.SteamId)) { ((Lobby)(ref val)).DeleteData("sakura_direct_v1"); } } catch { } _lifecycle?.EndHost(); _hostState = null; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } if (RoomLifecycle.Current == _lifecycle) { RoomLifecycle.Current = null; } } private static Type FindType(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(name); if (type != null) { return type; } } throw new TypeLoadException(name); } private static void BeforeLeaveLobby() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Instance; if ((Object)(object)instance == (Object)null) { return; } try { Lobby val = default(Lobby); if (SteamClient.IsValid && ModConfig.TryGetLobby(ref val) && SteamId.op_Implicit(((Lobby)(ref val)).Owner.Id) == SteamId.op_Implicit(SteamClient.SteamId)) { ((Lobby)(ref val)).SetJoinable(false); ((Lobby)(ref val)).DeleteData("sakura_direct_v1"); } } catch (Exception ex) { ((BaseUnityPlugin)instance).Logger.LogError((object)("Host exit cleanup failed: " + ex)); } finally { instance._lifecycle?.EndHost(); instance.ResetSession(); } } } internal sealed class RoomLifecycle { internal static RoomLifecycle? Current; private readonly object gate = new object(); private readonly object serverLock; private readonly IDictionary sessions; private readonly IDictionary rooms; private readonly FieldInfo peerId; private readonly FieldInfo challenge; private readonly FieldInfo send; private readonly FieldInfo sessionRoom; private readonly FieldInfo sessionActor; private readonly MethodInfo disconnect; private readonly MethodInfo datagram; private readonly Action<string> log; private readonly Dictionary<string, int> retired = new Dictionary<string, int>(); private ulong lobbyId; private bool closed; internal ulong HostEpoch { get; private set; } internal RoomLifecycle(Type server, Type codec, Action<string> logger) { log = logger; serverLock = Field(server, "Lock").GetValue(null); sessions = (IDictionary)Field(server, "Sessions").GetValue(null); rooms = (IDictionary)Field(server, "Rooms").GetValue(null); Type t = server.GetNestedType("ClientSession", BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingMemberException("ClientSession"); peerId = Field(t, "PeerId"); challenge = Field(t, "Challenge"); send = Field(t, "Send"); sessionRoom = Field(t, "Room"); sessionActor = Field(t, "Actor"); disconnect = Method(codec, "BuildDisconnect", Type.EmptyTypes); datagram = Method(codec, "BuildDatagram", new Type[4] { typeof(int), typeof(int), typeof(int), typeof(List<byte[]>) }); } internal static FieldInfo Field(Type t, string name) { return t.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(t.FullName, name); } internal static MethodInfo Method(Type t, string name, Type[] args) { return t.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null) ?? throw new MissingMethodException(t.FullName, name); } internal void Install(Harmony harmony, Type server, Type lobbyManager) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00a2: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00d7: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown MethodInfo methodInfo = Method(server, "HandleClientDatagram", new Type[4] { typeof(string), typeof(byte[]), typeof(Action<byte[]>), typeof(string) }); MethodInfo methodInfo2 = Method(server, "PumpSessions", Type.EmptyTypes); MethodInfo methodInfo3 = Method(lobbyManager, "LeaveLobby", Type.EmptyTypes); Current = this; harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(RoomLifecycle), "DatagramPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RoomLifecycle), "Unlock", (Type[])null), (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(RoomLifecycle), "LockPump", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(RoomLifecycle), "Unlock", (Type[])null), (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(Plugin), "BeforeLeaveLobby", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } internal void ObserveHost(ulong id) { lock (gate) { if (id == 0L || id == lobbyId) { return; } lock (serverLock) { object obj = sessions["loopback:7778"]; if (lobbyId != 0L && !closed && obj != null && sessionRoom.GetValue(obj) != null && sessionActor.GetValue(obj) != null) { log("Lobby replacement preserved: " + lobbyId + " -> " + id + "; existing game/voice rooms and sessions unchanged."); lobbyId = id; return; } } Clear("starting a new host lobby", preserveMaster: true); lobbyId = id; HostEpoch = id; closed = false; } } internal void EndHost() { lock (gate) { if (lobbyId != 0L) { closed = true; lobbyId = 0uL; HostEpoch = 0uL; Clear("real host exit cleanup; disconnecting all remaining players"); } } } private void Clear(string reason, bool preserveMaster = false) { lock (serverLock) { int count = sessions.Count; int count2 = rooms.Count; foreach (DictionaryEntry session in sessions) { string text = (string)session.Key; object value = session.Value; int num = (int)challenge.GetValue(value); if (text.StartsWith("loopback:", StringComparison.Ordinal)) { continue; } retired[text] = num; try { byte[] item = (byte[])disconnect.Invoke(null, null); byte[] obj = (byte[])datagram.Invoke(null, new object[4] { (int)peerId.GetValue(value), Environment.TickCount, num, new List<byte[]> { item } }); Action<byte[]> action = (Action<byte[]>)send.GetValue(value); for (int i = 0; i < 3; i++) { action?.Invoke(obj); } } catch (Exception ex) { log("Disconnect notice failed for " + text + ": " + ex.GetBaseException().Message); } } object obj2 = (preserveMaster ? sessions["loopback:7777"] : null); sessions.Clear(); if (obj2 != null) { sessions["loopback:7777"] = obj2; } rooms.Clear(); log("Room cleanup: " + reason + "; removed " + count2 + " rooms and " + count + " sessions."); } } internal bool Accept(string key, byte[] packet) { if (key.StartsWith("loopback:", StringComparison.Ordinal)) { if (closed) { return key == "loopback:7777"; } return true; } if (closed) { return false; } if (packet == null || packet.Length < 12) { return false; } int num = (packet[8] << 24) | (packet[9] << 16) | (packet[10] << 8) | packet[11]; if (key.StartsWith("udp:", StringComparison.Ordinal) && packet.Length == 24 && packet[3] == 1 && packet[12] == 5 && num == 28036591) { return true; } if (retired.TryGetValue(key, out var value) && value == num) { return false; } if (!sessions.Contains(key) && (packet.Length < 24 || packet[3] == 0 || packet[12] != 2)) { return false; } return true; } private static bool DatagramPrefix(string __0, byte[] __1, ref object? __state) { RoomLifecycle current = Current; if (current == null) { return true; } Monitor.Enter(current.gate); __state = current.gate; lock (current.serverLock) { return current.Accept(__0, __1); } } private static void LockPump(ref object? __state) { RoomLifecycle current = Current; if (current != null) { Monitor.Enter(current.gate); __state = current.gate; } } private static Exception? Unlock(Exception? __exception, object? __state) { if (__state != null) { Monitor.Exit(__state); } return __exception; } } internal readonly struct LocalNetwork { internal readonly IPAddress Address; internal readonly IPAddress Mask; internal LocalNetwork(IPAddress address, IPAddress mask) { Address = address; Mask = mask; } } internal static class Routing { internal const string LobbyKey = "sakura_direct_v1"; internal static bool TryIPv4(string? value, out IPAddress address) { address = IPAddress.None; if (string.IsNullOrWhiteSpace(value)) { return false; } string[] array = value.Trim().Split('.'); if (array.Length != 4) { return false; } byte[] array2 = new byte[4]; for (int i = 0; i < 4; i++) { if (array[i].Length == 0 || array[i].Length > 3) { return false; } string text = array[i]; foreach (char c in text) { if (c < '0' || c > '9') { return false; } } if (!byte.TryParse(array[i], NumberStyles.None, CultureInfo.InvariantCulture, out array2[i])) { return false; } } address = new IPAddress(array2); return true; } internal static bool IsUsable(IPAddress address) { if (address.AddressFamily != AddressFamily.InterNetwork) { return false; } byte[] addressBytes = address.GetAddressBytes(); if (addressBytes[0] != 0 && addressBytes[0] != 127 && addressBytes[0] < 224) { if (addressBytes[0] == 169) { return addressBytes[1] != 254; } return true; } return false; } internal static bool IsPrivate(IPAddress address) { if (address.AddressFamily != AddressFamily.InterNetwork) { return false; } byte[] addressBytes = address.GetAddressBytes(); if (addressBytes[0] != 10 && (addressBytes[0] != 172 || addressBytes[1] < 16 || addressBytes[1] > 31)) { if (addressBytes[0] == 192) { return addressBytes[1] == 168; } return false; } return true; } internal static bool TryHostIPv4(string? value, out IPAddress address) { if (TryIPv4(value, out address)) { return IsUsable(address); } return false; } internal static string Encode(ulong owner, string ip) { return "1|" + owner.ToString(CultureInfo.InvariantCulture) + "|" + ip; } internal static bool TryAnnouncement(string? value, ulong actualOwner, ulong self, out string ip) { ip = ""; if (actualOwner == 0L || actualOwner == self || string.IsNullOrEmpty(value) || value.Length > 80) { return false; } string[] array = value.Split('|'); if (array.Length != 3 || array[0] != "1" || !ulong.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result != actualOwner || !TryHostIPv4(array[2], out IPAddress address)) { return false; } ip = address.ToString(); return true; } internal static bool SameSubnet(IPAddress a, IPAddress b, IPAddress mask) { if (a.AddressFamily != AddressFamily.InterNetwork || b.AddressFamily != AddressFamily.InterNetwork || mask.AddressFamily != AddressFamily.InterNetwork) { return false; } byte[] addressBytes = a.GetAddressBytes(); byte[] addressBytes2 = b.GetAddressBytes(); byte[] addressBytes3 = mask.GetAddressBytes(); int num = (addressBytes3[0] << 24) | (addressBytes3[1] << 16) | (addressBytes3[2] << 8) | addressBytes3[3]; uint num2 = (uint)(~num); if (num == 0 || (num2 & (num2 + 1)) != 0) { return false; } for (int i = 0; i < 4; i++) { if ((addressBytes[i] & addressBytes3[i]) != (addressBytes2[i] & addressBytes3[i])) { return false; } } return true; } internal static string Advertise(IPAddress remote, string hostIp, LocalNetwork[] networks) { if (remote.ToString() == hostIp) { return hostIp; } for (int i = 0; i < networks.Length; i++) { LocalNetwork localNetwork = networks[i]; if (IsUsable(localNetwork.Address) && SameSubnet(remote, localNetwork.Address, localNetwork.Mask)) { return localNetwork.Address.ToString(); } } return hostIp; } } }