using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ComputerysModdingUtilities; using FishNet; using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Managing.Server; using FishNet.Managing.Transporting; using FishNet.Object; using FishNet.Transporting; using FishNet.Transporting.Tugboat; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: StraftatMod(true)] [assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")] [assembly: AssemblyCompany("StraftatLocalP2P")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+0bc8fcc8d3de24c0174366cf7eb8e6f59156748f")] [assembly: AssemblyProduct("StraftatLocalP2P")] [assembly: AssemblyTitle("StraftatLocalP2P")] [assembly: AssemblyVersion("0.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 StraftatLocalP2P { [BepInPlugin("com.local.straftat.p2p", "STRAFTAT LAN P2P Optimizer", "0.2.0")] public sealed class Plugin : BaseUnityPlugin { private enum OfflineLanRole { None, Host, Client } private sealed class OfflineLanAuthInfo { public string Name; public DateTime AcceptedAtUtc; } private static class OfflineLanPatches { public static bool SteamLobbyStartPrefix(SteamLobby __instance) { if (!OfflineLanEnabled.Value || !ForceOfflineSteamLobbyStartup.Value) { return true; } try { __instance.maxPlayers = (((Object)(object)__instance.MaxPlayersDropdown != (Object)null) ? (__instance.MaxPlayersDropdown.value + 2) : 4); Log.LogInfo((object)"Offline LAN skipped SteamLobby.Start Steam callback setup."); } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN minimal SteamLobby.Start setup failed: {arg}"); } return false; } public static bool SteamLobbyCreatePrefix(SteamLobby __instance) { if (!ShouldRedirectCreateLobbyToOfflineLan()) { return true; } HostOfflineLanFromNormalButton(__instance); return false; } public static bool SteamLobbyCreateLobbyDirectPrefix(SteamLobby __instance) { if (!ShouldRedirectCreateLobbyToOfflineLan()) { return true; } HostOfflineLanFromNormalButton(__instance); return false; } public static bool SteamLobbySetMaxPlayersPrefix(SteamLobby __instance, TMP_Dropdown _dropdown) { if (!OfflineLanEnabled.Value || !ForceOfflineSteamLobbyStartup.Value) { return true; } __instance.maxPlayers = (((Object)(object)_dropdown != (Object)null) ? _dropdown.value : 2) + 2; Transport val = InstanceFinder.TransportManager?.Transport; if ((Object)(object)val != (Object)null) { val.SetMaximumClients(Mathf.Max(2, __instance.maxPlayers)); } if (OfflineLanActive) { BroadcastOfflineMaxPlayers("dropdown changed"); } Log.LogInfo((object)$"Offline LAN max players set to {__instance.maxPlayers}."); return false; } public static bool SteamLobbySetAllowMidMatchJoiningPrefix(SteamLobby __instance, bool value) { if (!OfflineLanActive) { return true; } __instance._allowMidMatchJoining = value; return false; } public static bool SteamLobbyLeaveLobbyPrefix() { if (!OfflineLanActive) { return true; } StopOfflineLanSession(loadMainMenu: true); return false; } public static bool SteamLobbyLeaveSteamLobbyPrefix() { return !OfflineLanActive; } public static bool ValidateSteamIdPrefix(ref bool __result) { if (!OfflineLanActive) { return true; } __result = true; return false; } public static bool SkipSteamOnlyMethodPrefix() { return !OfflineLanActive; } public static bool LobbyControllerUpdateLobbyNamePrefix(LobbyController __instance) { if (!OfflineLanActive) { return true; } if ((Object)(object)__instance.LobbyNameText != (Object)null) { string text = (((Object)(object)SteamLobby.Instance != (Object)null && !string.IsNullOrWhiteSpace(SteamLobby.Instance.lobbyName)) ? SteamLobby.Instance.lobbyName : "Offline LAN"); ((TMP_Text)__instance.LobbyNameText).text = ((_offlineLanRole == OfflineLanRole.Host) ? text : "Offline LAN Client"); } return false; } public static void ClientInstanceOnStartClientPostfix(ClientInstance __instance) { if (OfflineLanActive && !((Object)(object)__instance == (Object)null)) { __instance.nonSteamworksTransport = false; if (((NetworkBehaviour)__instance).IsOwner && (Object)(object)LobbyController.Instance != (Object)null) { LobbyController.Instance.LocalPlayerController = __instance; LobbyController.Instance.UpdatePlayerList(); } } } public static bool ClientInstanceSetSyncValuesPrefix(ClientInstance __instance, NetworkObject newPlayer) { if (!OfflineLanActive || (Object)(object)__instance == (Object)null || (Object)(object)newPlayer == (Object)null) { return true; } try { NetworkConnection owner = newPlayer.Owner; int num = 0; int[] array = (from x in Object.FindObjectsOfType() select x.PlayerId).ToArray(); for (int i = 0; i < Math.Max(4, array.Length + 1); i++) { if (!array.Contains(i)) { num = i; break; } } ulong offlineLanId = GetOfflineLanId(owner, num); AccessTools.Method(typeof(ClientInstance), "AddNewPlayer", (Type[])null, (Type[])null)?.Invoke(__instance, new object[4] { owner, newPlayer, num, offlineLanId }); AccessTools.Method(typeof(ClientInstance), "UpdateOnClients", Type.EmptyTypes, (Type[])null)?.Invoke(__instance, Array.Empty()); AccessTools.Method(typeof(ClientInstance), "RunIntoLobby", (Type[])null, (Type[])null)?.Invoke(__instance, new object[1] { num }); AccessTools.Method(typeof(ClientInstance), "InitiateClient", (Type[])null, (Type[])null)?.Invoke(__instance, new object[2] { owner, num }); BroadcastOfflineMaxPlayers("player registered"); Log.LogInfo((object)$"Offline LAN registered player id={num}, lanId={offlineLanId}, connection={owner?.ClientId}."); } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN SetSyncValues replacement failed: {arg}"); } return false; } public static bool ClientInstanceAddNewPlayerPrefix(ClientInstance __instance, NetworkConnection owner, NetworkObject newPlayer, int id, ulong steamid) { if (!OfflineLanActive || (Object)(object)__instance == (Object)null || owner == (NetworkConnection)null || (Object)(object)newPlayer == (Object)null) { return true; } try { __instance.PlayerSteamID = steamid; __instance.PlayerId = id; __instance.ConnectionID = owner.ClientId; if (!SteamLobby.Instance.players.Contains(newPlayer)) { SteamLobby.Instance.players.Add(newPlayer); } ClientInstance.playerInstances[id] = __instance; __instance.PlayerName = GetOfflineLanPlayerName(owner, id, steamid); ((Object)((Component)__instance).gameObject).name = __instance.PlayerName; if (!owner.IsLocalClient && (Object)(object)VoiceChatUI.Instance != (Object)null) { VoiceChatUI.Instance.CreateVoiceChatUIObject(__instance); } Log.LogInfo((object)$"Offline LAN added player | LAN ID: {steamid}, PlayerId: {id}, ConnectionID: {owner.ClientId}, PlayerName: {__instance.PlayerName}"); } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN AddNewPlayer replacement failed: {arg}"); } return false; } public static bool UnityDebugLogFormatPrefix(LogType logType, string format, object[] args) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 if (!OfflineLanActive || (int)logType != 2 || string.IsNullOrEmpty(format)) { return true; } if (format.Contains("Cannot complete action because client is not active") || format.Contains("Cannot complete operation as server when server is not active") || format.Contains("Server socket is not started")) { return false; } return true; } } private static class SteamApiPatches { [HarmonyPatch(typeof(SteamAPI), "Init")] [HarmonyPostfix] private static void SteamApiInitPostfix(bool __result) { if (__result) { OnSteamApiInitialized(); } else { Log.LogWarning((object)"SteamAPI.Init returned false; routing config was not applied."); } } } private static class LobbyLifecyclePatches { public static void BeforeLobbyLifecycleEvent(MethodBase __originalMethod) { OnLobbyLifecycleEvent(__originalMethod?.Name ?? "unknown"); } } private static class FishySteamworksPatches { public static IEnumerable ClientStartConnectionTranspiler(IEnumerable instructions, ILGenerator generator) { List list = new List(instructions); FieldInfo objB = AccessTools.Field(typeof(OptionValue), "m_int32"); MethodInfo operand = AccessTools.Method(typeof(Plugin), "GetPerConnectionIceEnableValue", (Type[])null, (Type[])null); bool flag = false; for (int i = 0; i < list.Count - 1; i++) { if (!flag && IsLoadInt32Zero(list[i]) && list[i + 1].opcode == OpCodes.Stfld && object.Equals(list[i + 1].operand, objB)) { list[i].opcode = OpCodes.Call; list[i].operand = operand; flag = true; } } if (flag) { Log.LogInfo((object)"FishySteamworks ClientSocket ICE config transpiler applied."); } else { Log.LogWarning((object)"FishySteamworks ClientSocket ICE config transpiler did not find the expected m_int32=0 assignment."); } return list; } public static IEnumerable ServerStartConnectionTranspiler(IEnumerable instructions, ILGenerator generator) { List list = new List(instructions); Type typeFromHandle = typeof(SteamNetworkingConfigValue_t); MethodInfo operand = AccessTools.Method(typeof(Plugin), "GetListenSocketConfigValues", (Type[])null, (Type[])null); bool flag = false; for (int i = 0; i < list.Count - 1; i++) { if (!flag && IsLoadInt32Zero(list[i]) && list[i + 1].opcode == OpCodes.Newarr && object.Equals(list[i + 1].operand, typeFromHandle)) { list[i].opcode = OpCodes.Call; list[i].operand = operand; list[i + 1].opcode = OpCodes.Nop; list[i + 1].operand = null; flag = true; } } if (flag) { Log.LogInfo((object)"FishySteamworks ServerSocket listen config transpiler applied."); } else { Log.LogWarning((object)"FishySteamworks ServerSocket listen config transpiler did not find the expected empty config array."); } return list; } private static bool IsLoadInt32Zero(CodeInstruction instruction) { if (instruction.opcode == OpCodes.Ldc_I4_0) { return true; } if (instruction.opcode == OpCodes.Ldc_I4 && instruction.operand is int num) { return num == 0; } if (instruction.opcode == OpCodes.Ldc_I4_S && instruction.operand is sbyte b) { return b == 0; } return false; } } [CompilerGenerated] private static class <>O { public static Action <0>__OfflineServerRemoteConnectionState; public static ThreadStart <1>__OfflineAuthLoop; public static DispatchDelegate <2>__OnConnectionStatusChanged; public static FSteamNetworkingSocketsDebugOutput <3>__OnSteamNetworkingDebugOutput; } private static ConfigEntry OfflineLanEnabled; private static ConfigEntry ForceOfflineSteamLobbyStartup; private static ConfigEntry RedirectCreateLobbyToOfflineLan; private static ConfigEntry ShowOfflineLanPanel; private static ConfigEntry OfflineLanJoinAddress; private static ConfigEntry OfflineLanPasscode; private static ConfigEntry OfflineLanPlayerName; private static ConfigEntry OfflineLanPort; private static ConfigEntry OfflineLanPanelX; private static ConfigEntry OfflineLanPanelY; private static readonly ConcurrentDictionary AcceptedOfflineLanPeers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private static readonly ConcurrentDictionary OfflineLanConnectionNames = new ConcurrentDictionary(); private static OfflineLanRole _offlineLanRole; private static UdpClient _offlineAuthListener; private static Thread _offlineAuthThread; private static volatile bool _offlineAuthRunning; private static string _offlineLanStatus = "Ready"; private static Tugboat _offlineTugboat; private static bool _offlineTransportBound; private static bool _offlineLeaveInProgress; private static bool _offlineLanStarting; private static string _cachedLocalIPv4Address = "127.0.0.1"; private static float _nextLocalIpRefreshAt; private static Action _serverConnectionDelegate; private static Action _remoteConnectionDelegate; private static Action _serverReceivedDelegate; private static Action _clientConnectionLogDelegate; private GameObject _offlineLanRoot; private Text _offlineLanStatusText; private InputField _offlineIpInput; private InputField _offlinePasscodeInput; private InputField _offlinePortInput; private InputField _offlineNameInput; private GameObject _offlineLeaveButton; public const string PluginGuid = "com.local.straftat.p2p"; public const string PluginName = "STRAFTAT LAN P2P Optimizer"; public const string PluginVersion = "0.2.0"; private static readonly string[] LobbyPatchTypes = new string[1] { "SteamLobby" }; private static ManualLogSource Log; private static ConfigEntry Enabled; private static ConfigEntry Mode; private static ConfigEntry VerboseSteamNetworkingLogs; private static ConfigEntry AllowUnencryptedSegments; private static ConfigEntry ShowLobbyToggle; private static ConfigEntry ToggleX; private static ConfigEntry ToggleY; private static ConfigFile _configFile; private static Harmony _harmony; private static Callback _connectionStatusCallback; private static FSteamNetworkingSocketsDebugOutput _debugOutputCallback; private static bool _steamCallbacksRegistered; private static bool _steamInitSeen; private static RoutingMode? _lastAppliedMode; private static bool _lastUnencryptedSetting; private static Type _steamLobbyType; private const int IceEnablePrivateAndPublic = 6; private GameObject _uiRoot; private Text _statusText; private float _nextApplyAttemptAt; private static bool _cachedHostMenuVisible; private static float _nextHostMenuVisibilityCheckAt; private static Plugin _instance; private static bool OfflineLanActive => _offlineLanRole != OfflineLanRole.None; private static void ConfigureOfflineLan(ConfigFile config) { OfflineLanEnabled = config.Bind("Offline LAN", "Enabled", true, "Enable the offline LAN prototype panel and patches."); ForceOfflineSteamLobbyStartup = config.Bind("Offline LAN", "ForceOfflineSteamLobbyStartup", true, "Skip Steam lobby callback setup so the offline prototype can run without Steam matchmaking."); RedirectCreateLobbyToOfflineLan = config.Bind("Offline LAN", "RedirectCreateLobbyToOfflineLan", true, "Make STRAFTAT's normal Create Lobby host button start an offline LAN lobby."); ShowOfflineLanPanel = config.Bind("Offline LAN", "ShowOfflineLanPanel", true, "Show the offline LAN host/join panel on the multiplayer home screen."); OfflineLanJoinAddress = config.Bind("Offline LAN", "JoinAddress", "192.168.1.100", "LAN IPv4 address of the host PC."); OfflineLanPasscode = config.Bind("Offline LAN", "Passcode", "LAN", "Passcode required before a client can join this offline LAN session."); OfflineLanPlayerName = config.Bind("Offline LAN", "PlayerName", Environment.UserName, "Display name used locally by the offline LAN prototype."); OfflineLanPort = config.Bind("Offline LAN", "Port", (ushort)7770, "UDP port used by FishNet Tugboat. The passcode handshake uses the next port."); OfflineLanPanelX = config.Bind("Offline LAN", "PanelX", 775, "Offline LAN panel X position in screen pixels."); OfflineLanPanelY = config.Bind("Offline LAN", "PanelY", 610, "Offline LAN panel Y position in screen pixels."); if (OfflineLanPanelY.Value == 525) { OfflineLanPanelY.Value = 610; } } private static void PatchOfflineLanMethods() { if (OfflineLanEnabled.Value) { PatchIfPresent("SteamLobby", "Start", typeof(OfflineLanPatches), "SteamLobbyStartPrefix"); PatchIfPresent("SteamLobby", "Create", typeof(OfflineLanPatches), "SteamLobbyCreatePrefix"); PatchIfPresent("SteamLobby", "CreateLobbyDirect", typeof(OfflineLanPatches), "SteamLobbyCreateLobbyDirectPrefix"); PatchIfPresent("SteamLobby", "SetMaxPlayers", typeof(OfflineLanPatches), "SteamLobbySetMaxPlayersPrefix", new Type[1] { typeof(TMP_Dropdown) }); PatchIfPresent("SteamLobby", "SetAllowMidMatchJoining", typeof(OfflineLanPatches), "SteamLobbySetAllowMidMatchJoiningPrefix", new Type[1] { typeof(bool) }); PatchIfPresent("SteamLobby", "LeaveLobby", typeof(OfflineLanPatches), "SteamLobbyLeaveLobbyPrefix"); PatchIfPresent("SteamLobby", "LeaveSteamLobby", typeof(OfflineLanPatches), "SteamLobbyLeaveSteamLobbyPrefix"); PatchIfPresent("SteamLobby", "ValidateSteamIDisInLobby", typeof(OfflineLanPatches), "ValidateSteamIdPrefix", new Type[1] { typeof(ulong) }); PatchIfPresent("SteamLobby", "UpdateLobbyType", typeof(OfflineLanPatches), "SkipSteamOnlyMethodPrefix"); PatchIfPresent("SteamLobby", "SetGamemodeString", typeof(OfflineLanPatches), "SkipSteamOnlyMethodPrefix"); PatchIfPresent("SteamLobby", "UpdatePlayerCountDisplay", typeof(OfflineLanPatches), "SkipSteamOnlyMethodPrefix"); PatchIfPresent("LobbyController", "UpdateLobbyName", typeof(OfflineLanPatches), "LobbyControllerUpdateLobbyNamePrefix"); PatchIfPresent("ClientInstance", "OnStartClient", typeof(OfflineLanPatches), "ClientInstanceOnStartClientPostfix", null, postfix: true); PatchIfPresent("ClientInstance", "SetSyncValues", typeof(OfflineLanPatches), "ClientInstanceSetSyncValuesPrefix"); PatchIfPresent("ClientInstance", "RpcLogic___AddNewPlayer_2166193949", typeof(OfflineLanPatches), "ClientInstanceAddNewPlayerPrefix"); PatchUnityLogFilter(); } } private static void PatchUnityLogFilter() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown Type type = AccessTools.TypeByName("UnityEngine.DebugLogHandler"); if (!(type == null)) { HarmonyMethod val = new HarmonyMethod(typeof(OfflineLanPatches), "UnityDebugLogFormatPrefix", (Type[])null); MethodInfo methodInfo = AccessTools.Method(type, "LogFormat", new Type[4] { typeof(LogType), typeof(Object), typeof(string), typeof(object[]) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(type, "LogFormat", new Type[5] { typeof(LogType), typeof(LogOption), typeof(Object), typeof(string), typeof(object[]) }, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } if (methodInfo2 != null) { _harmony.Patch((MethodBase)methodInfo2, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void PatchIfPresent(string typeName, string methodName, Type patchType, string patchName, Type[] argumentTypes = null, bool postfix = false) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown Type type = AccessTools.TypeByName(typeName); MethodInfo methodInfo = ((type == null) ? null : ((argumentTypes == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, argumentTypes, (Type[])null))); if (methodInfo == null) { Log.LogWarning((object)("Offline LAN patch target " + typeName + "." + methodName + " was not found.")); return; } HarmonyMethod val = new HarmonyMethod(patchType, patchName, (Type[])null); if (postfix) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.LogInfo((object)("Patched offline LAN method " + typeName + "." + methodName + ".")); } private void TryCreateOfflineLanUi(string reason) { if (!OfflineLanEnabled.Value || !ShowOfflineLanPanel.Value) { return; } try { CreateOfflineLanUi(); RefreshOfflineLanUi(); } catch (Exception arg) { Log.LogWarning((object)$"Failed to create Offline LAN UI during {reason}: {arg}"); } } private void CreateOfflineLanUi() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_offlineLanRoot != (Object)null)) { EnsureEventSystem(); _offlineLanRoot = new GameObject("StraftatLocalP2P_OfflineLanPanel"); Object.DontDestroyOnLoad((Object)(object)_offlineLanRoot); Canvas obj = _offlineLanRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32766; _offlineLanRoot.AddComponent().uiScaleMode = (ScaleMode)0; _offlineLanRoot.AddComponent(); GameObject val = CreateUiObject("Panel", _offlineLanRoot.transform); RectTransform obj2 = val.AddComponent(); obj2.anchorMin = new Vector2(0f, 1f); obj2.anchorMax = new Vector2(0f, 1f); obj2.pivot = new Vector2(0f, 1f); obj2.anchoredPosition = new Vector2((float)OfflineLanPanelX.Value, (float)(-OfflineLanPanelY.Value)); obj2.sizeDelta = new Vector2(360f, 238f); ((Graphic)val.AddComponent()).color = new Color(0f, 0f, 0f, 0.68f); CreateText(val.transform, "Title", "Offline LAN", new Vector2(12f, -8f), new Vector2(336f, 24f), 16, (FontStyle)1); _offlineLanStatusText = CreateText(val.transform, "Status", "", new Vector2(12f, -33f), new Vector2(336f, 32f), 12, (FontStyle)0); CreateText(val.transform, "NameLabel", "Name", new Vector2(12f, -72f), new Vector2(80f, 20f), 12, (FontStyle)1); _offlineNameInput = CreateInput(val.transform, "NameInput", OfflineLanPlayerName.Value, new Vector2(96f, -70f), new Vector2(252f, 24f)); CreateText(val.transform, "IpLabel", "Host IP", new Vector2(12f, -102f), new Vector2(80f, 20f), 12, (FontStyle)1); _offlineIpInput = CreateInput(val.transform, "IpInput", OfflineLanJoinAddress.Value, new Vector2(96f, -100f), new Vector2(168f, 24f)); _offlinePortInput = CreateInput(val.transform, "PortInput", OfflineLanPort.Value.ToString(), new Vector2(270f, -100f), new Vector2(78f, 24f)); CreateText(val.transform, "PasscodeLabel", "Passcode", new Vector2(12f, -132f), new Vector2(80f, 20f), 12, (FontStyle)1); _offlinePasscodeInput = CreateInput(val.transform, "PasscodeInput", OfflineLanPasscode.Value, new Vector2(96f, -130f), new Vector2(252f, 24f)); CreateButton(val.transform, "HostButton", "Host Offline LAN", new Vector2(12f, -166f), new Vector2(160f, 30f), delegate { HostOfflineLanFromUi(); }); CreateButton(val.transform, "JoinButton", "Join IP", new Vector2(188f, -166f), new Vector2(160f, 30f), delegate { JoinOfflineLanFromUi(); }); _offlineLeaveButton = CreateButton(val.transform, "LeaveButton", "Leave Offline LAN", new Vector2(12f, -202f), new Vector2(336f, 28f), delegate { StopOfflineLanSession(loadMainMenu: true); }); Log.LogInfo((object)"Created Offline LAN panel."); } } private static InputField CreateInput(Transform parent, string name, string value, Vector2 position, Vector2 size) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: 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_00fc: Unknown result type (might be due to invalid IL or missing references) GameObject val = CreateUiObject(name, parent); RectTransform obj = val.AddComponent(); obj.anchorMin = new Vector2(0f, 1f); obj.anchorMax = new Vector2(0f, 1f); obj.pivot = new Vector2(0f, 1f); obj.anchoredPosition = position; obj.sizeDelta = size; Image val2 = val.AddComponent(); ((Graphic)val2).color = new Color(0.08f, 0.09f, 0.1f, 0.95f); GameObject obj2 = CreateUiObject("Text", val.transform); RectTransform obj3 = obj2.AddComponent(); obj3.anchorMin = Vector2.zero; obj3.anchorMax = Vector2.one; obj3.offsetMin = new Vector2(6f, 2f); obj3.offsetMax = new Vector2(-6f, -2f); Text val3 = obj2.AddComponent(); val3.font = Resources.GetBuiltinResource("Arial.ttf"); val3.fontSize = 13; val3.alignment = (TextAnchor)3; ((Graphic)val3).color = Color.white; InputField obj4 = val.AddComponent(); ((Selectable)obj4).targetGraphic = (Graphic)(object)val2; obj4.textComponent = val3; obj4.text = value ?? string.Empty; obj4.characterLimit = 64; return obj4; } private void OfflineLanUpdate() { if (OfflineLanEnabled.Value) { if ((Object)(object)_offlineLanRoot == (Object)null) { TryCreateOfflineLanUi("Update"); } RefreshOfflineLanUi(); } } private void RefreshOfflineLanUi() { if ((Object)(object)_offlineLanRoot != (Object)null) { _offlineLanRoot.SetActive(ShowOfflineLanPanel.Value && IsHostMenuVisible()); } if ((Object)(object)_offlineLanStatusText != (Object)null) { string localIPv4Address = GetLocalIPv4Address(); string text = ((_offlineLanRole == OfflineLanRole.None) ? "Idle" : _offlineLanRole.ToString()); _offlineLanStatusText.text = $"{text} | {localIPv4Address}:{OfflineLanPort.Value} | {_offlineLanStatus}"; } if ((Object)(object)_offlineLeaveButton != (Object)null) { _offlineLeaveButton.SetActive(OfflineLanActive || _offlineLanStarting); } } private void OfflineLanShutdownUi() { StopOfflineAuthListener(); } private void HostOfflineLanFromUi() { PullOfflineLanUiValues(); HostOfflineLan(); } private void JoinOfflineLanFromUi() { PullOfflineLanUiValues(); JoinOfflineLan(); } private void PullOfflineLanUiValues() { if ((Object)(object)_offlineIpInput != (Object)null) { OfflineLanJoinAddress.Value = _offlineIpInput.text.Trim(); } if ((Object)(object)_offlinePasscodeInput != (Object)null) { OfflineLanPasscode.Value = _offlinePasscodeInput.text; } if ((Object)(object)_offlineNameInput != (Object)null) { OfflineLanPlayerName.Value = (string.IsNullOrWhiteSpace(_offlineNameInput.text) ? Environment.UserName : _offlineNameInput.text.Trim()); } if ((Object)(object)_offlinePortInput != (Object)null && ushort.TryParse(_offlinePortInput.text, out var result)) { OfflineLanPort.Value = result; } _configFile.Save(); } private static void HostOfflineLan() { if (_offlineLanStarting) { _offlineLanStatus = "Already starting"; } else if (OfflineLanActive) { _offlineLanStatus = "Already active"; } else if ((Object)(object)_instance == (Object)null) { _offlineLanStatus = "Plugin instance missing"; } else { ((MonoBehaviour)_instance).StartCoroutine(HostOfflineLanRoutine()); } } private static IEnumerator HostOfflineLanRoutine() { _offlineLanStarting = true; if (!PrepareOfflineLanTransport()) { _offlineLanStarting = false; yield break; } StopOfflineLanSession(loadMainMenu: false); if (!PrepareOfflineLanTransport()) { _offlineLanStarting = false; yield break; } _offlineLanRole = OfflineLanRole.Host; _offlineLanStatus = "Starting host"; AcceptedOfflineLanPeers.Clear(); WhitelistIp("127.0.0.1", OfflineLanPlayerName.Value); WhitelistIp("::1", OfflineLanPlayerName.Value); WhitelistIp("localhost", OfflineLanPlayerName.Value); foreach (string localIPv4Address in GetLocalIPv4Addresses()) { WhitelistIp(localIPv4Address, OfflineLanPlayerName.Value); } StartOfflineAuthListener(); NetworkManager networkManager = InstanceFinder.NetworkManager; Transport transport = InstanceFinder.TransportManager.Transport; ApplyOfflineHostOptions(); transport.SetPort(OfflineLanPort.Value); transport.SetServerBindAddress(string.Empty, (IPAddressType)0); int configuredMaxClients = GetConfiguredMaxClients(); transport.SetMaximumClients(configuredMaxClients); Log.LogInfo((object)$"Offline LAN Tugboat maximum clients set to {configuredMaxClients}."); HookOfflineServerEvents(subscribe: true); if (!networkManager.ServerManager.StartConnection(OfflineLanPort.Value)) { _offlineLanStatus = "Host failed"; Log.LogError((object)"Offline LAN host failed to start Tugboat server."); CleanupFailedOfflineStart(); _offlineLanStarting = false; yield break; } float timeoutAt2 = Time.realtimeSinceStartup + 3f; while (!networkManager.ServerManager.Started && Time.realtimeSinceStartup < timeoutAt2) { yield return null; } if (!networkManager.ServerManager.Started) { _offlineLanStatus = "Server start timed out"; Log.LogError((object)"Offline LAN Tugboat server socket opened but FishNet did not report ServerManager.Started."); CleanupFailedOfflineStart(); _offlineLanStarting = false; yield break; } if (!networkManager.ClientManager.StartConnection("127.0.0.1", OfflineLanPort.Value)) { _offlineLanStatus = "Local client failed"; Log.LogError((object)"Offline LAN host started, but local client failed to connect."); CleanupFailedOfflineStart(); _offlineLanStarting = false; yield break; } timeoutAt2 = Time.realtimeSinceStartup + 3f; while (!networkManager.ClientManager.Started && Time.realtimeSinceStartup < timeoutAt2) { yield return null; } if (!networkManager.ClientManager.Started) { _offlineLanStatus = "Local client timed out"; Log.LogError((object)"Offline LAN local client did not report ClientManager.Started."); CleanupFailedOfflineStart(); _offlineLanStarting = false; } else { SetupOfflineLobbyUi(asHost: true); BroadcastOfflineMaxPlayers("host started"); SpawnOfflineSceneMotorIfNeeded(); _offlineLanStatus = $"Hosting {GetLocalIPv4Address()}:{OfflineLanPort.Value}"; Log.LogInfo((object)$"Offline LAN host started on {GetLocalIPv4Address()}:{OfflineLanPort.Value}. Auth port={OfflineLanPort.Value + 1}."); _offlineLanStarting = false; } } private static void JoinOfflineLan() { string text = OfflineLanJoinAddress.Value.Trim(); if (string.IsNullOrEmpty(text)) { _offlineLanStatus = "Host IP required"; } else if (_offlineLanStarting) { _offlineLanStatus = "Already starting"; } else if (OfflineLanActive) { _offlineLanStatus = "Already active"; } else if ((Object)(object)_instance == (Object)null) { _offlineLanStatus = "Plugin instance missing"; } else { ((MonoBehaviour)_instance).StartCoroutine(JoinOfflineLanRoutine(text)); } } private static IEnumerator JoinOfflineLanRoutine(string host) { _offlineLanStarting = true; if (!PrepareOfflineLanTransport()) { _offlineLanStarting = false; yield break; } StopOfflineLanSession(loadMainMenu: false); if (!PrepareOfflineLanTransport()) { _offlineLanStarting = false; yield break; } _offlineLanRole = OfflineLanRole.Client; _offlineLanStatus = "Authenticating"; if (!SendOfflineAuthRequest(host)) { _offlineLanRole = OfflineLanRole.None; _offlineLanStarting = false; yield break; } Log.LogInfo((object)$"Offline LAN passcode accepted by {host}:{OfflineLanPort.Value + 1}."); NetworkManager networkManager = InstanceFinder.NetworkManager; Transport transport = InstanceFinder.TransportManager.Transport; transport.SetPort(OfflineLanPort.Value); transport.SetClientAddress(host); if (!networkManager.ClientManager.StartConnection(host, OfflineLanPort.Value)) { _offlineLanStatus = "Join failed"; Log.LogError((object)$"Offline LAN client failed to start connection to {host}:{OfflineLanPort.Value}."); CleanupFailedOfflineStart(); _offlineLanStarting = false; yield break; } _offlineLanStatus = $"Connecting {host}:{OfflineLanPort.Value}"; Log.LogInfo((object)$"Offline LAN client connecting to {host}:{OfflineLanPort.Value}."); float timeoutAt = Time.realtimeSinceStartup + 6f; while (!networkManager.ClientManager.Started && Time.realtimeSinceStartup < timeoutAt) { yield return null; } if (!networkManager.ClientManager.Started) { _offlineLanStatus = "Join timed out"; Log.LogError((object)$"Offline LAN client timed out waiting for FishNet ClientManager.Started at {host}:{OfflineLanPort.Value}. Check Windows Firewall/UDP port {OfflineLanPort.Value} on the host."); CleanupFailedOfflineStart(); _offlineLanStarting = false; } else { SetupOfflineLobbyUi(asHost: false); _offlineLanStatus = $"Connected {host}:{OfflineLanPort.Value}"; Log.LogInfo((object)$"Offline LAN client connected to {host}:{OfflineLanPort.Value}."); _offlineLanStarting = false; } } private static bool PrepareOfflineLanTransport() { try { NetworkManager networkManager = InstanceFinder.NetworkManager; TransportManager transportManager = InstanceFinder.TransportManager; if ((Object)(object)networkManager == (Object)null || (Object)(object)transportManager == (Object)null) { _offlineLanStatus = "NetworkManager missing"; Log.LogWarning((object)"Offline LAN cannot start because FishNet NetworkManager is not ready."); return false; } Tugboat val = (_offlineTugboat = ((Component)networkManager).GetComponent() ?? ((Component)networkManager).gameObject.AddComponent()); if (!_offlineTransportBound || (Object)(object)transportManager.Transport != (Object)(object)val) { RebindTransport(networkManager, val); _offlineTransportBound = true; } else if (_serverConnectionDelegate == null || _remoteConnectionDelegate == null || _serverReceivedDelegate == null) { HookServerManagerToTugboat(networkManager, val); } if (_clientConnectionLogDelegate == null) { HookClientConnectionLogging(val); } PauseManager.Instance.nonSteamworksTransport = false; return true; } catch (Exception arg) { _offlineLanStatus = "Transport exception"; Log.LogError((object)$"Failed to prepare Tugboat transport: {arg}"); return false; } } private static void RebindTransport(NetworkManager networkManager, Tugboat tugboat) { TransportManager transportManager = networkManager.TransportManager; InvokeTransportSubscription(networkManager.ClientManager, subscribe: false); UnhookServerManagerFromTugboat(); transportManager.Transport = (Transport)(object)tugboat; ((Transport)tugboat).Initialize(networkManager, 0); ResetTransportManagerBuffers(transportManager); InvokeTransportSubscription(networkManager.ClientManager, subscribe: true); HookServerManagerToTugboat(networkManager, tugboat); HookClientConnectionLogging(tugboat); Log.LogInfo((object)"Offline LAN switched FishNet transport to Tugboat."); } private static void HookServerManagerToTugboat(NetworkManager networkManager, Tugboat tugboat) { ServerManager serverManager = networkManager.ServerManager; MethodInfo serverConnectionMethod = AccessTools.Method(((object)serverManager).GetType(), "Transport_OnServerConnectionState", (Type[])null, (Type[])null); MethodInfo remoteConnectionMethod = AccessTools.Method(((object)serverManager).GetType(), "Transport_OnRemoteConnectionState", (Type[])null, (Type[])null); MethodInfo serverReceivedMethod = AccessTools.Method(((object)serverManager).GetType(), "Transport_OnServerReceivedData", (Type[])null, (Type[])null); if (serverConnectionMethod == null || remoteConnectionMethod == null || serverReceivedMethod == null) { Log.LogWarning((object)"Offline LAN could not find FishNet ServerManager transport handlers."); return; } _serverConnectionDelegate = delegate(ServerConnectionStateArgs args) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) Log.LogInfo((object)$"Offline LAN Tugboat server state: {args.ConnectionState}."); serverConnectionMethod.Invoke(serverManager, new object[1] { args }); }; _remoteConnectionDelegate = delegate(RemoteConnectionStateArgs args) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) string arg = NormalizePeerAddress(((Transport)tugboat).GetConnectionAddress(args.ConnectionId)); Log.LogInfo((object)$"Offline LAN Tugboat remote state: connection={args.ConnectionId}, state={args.ConnectionState}, address={arg}."); remoteConnectionMethod.Invoke(serverManager, new object[1] { args }); }; _serverReceivedDelegate = delegate(ServerReceivedDataArgs args) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) serverReceivedMethod.Invoke(serverManager, new object[1] { args }); }; ((Transport)tugboat).OnServerConnectionState += _serverConnectionDelegate; ((Transport)tugboat).OnRemoteConnectionState += _remoteConnectionDelegate; ((Transport)tugboat).OnServerReceivedData += _serverReceivedDelegate; Log.LogInfo((object)"Offline LAN bound FishNet ServerManager to Tugboat events."); } private static void HookClientConnectionLogging(Tugboat tugboat) { if (!((Object)(object)tugboat == (Object)null) && _clientConnectionLogDelegate == null) { _clientConnectionLogDelegate = delegate(ClientConnectionStateArgs args) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Log.LogInfo((object)$"Offline LAN Tugboat client state: {args.ConnectionState}."); }; ((Transport)tugboat).OnClientConnectionState += _clientConnectionLogDelegate; } } private static void UnhookClientConnectionLogging() { if ((Object)(object)_offlineTugboat != (Object)null && _clientConnectionLogDelegate != null) { ((Transport)_offlineTugboat).OnClientConnectionState -= _clientConnectionLogDelegate; } _clientConnectionLogDelegate = null; } private static void UnhookServerManagerFromTugboat() { if ((Object)(object)_offlineTugboat != (Object)null) { if (_serverConnectionDelegate != null) { ((Transport)_offlineTugboat).OnServerConnectionState -= _serverConnectionDelegate; } if (_remoteConnectionDelegate != null) { ((Transport)_offlineTugboat).OnRemoteConnectionState -= _remoteConnectionDelegate; } if (_serverReceivedDelegate != null) { ((Transport)_offlineTugboat).OnServerReceivedData -= _serverReceivedDelegate; } } _serverConnectionDelegate = null; _remoteConnectionDelegate = null; _serverReceivedDelegate = null; UnhookClientConnectionLogging(); } private static void InvokeTransportSubscription(object manager, bool subscribe) { if (manager != null) { MethodInfo methodInfo = AccessTools.Method(manager.GetType(), "SubscribeToEvents", (Type[])null, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(manager, new object[1] { subscribe }); } } } private static void ResetTransportManagerBuffers(object transportManager) { AccessTools.Field(transportManager.GetType(), "_lowestMtu")?.SetValue(transportManager, new int[2]); (AccessTools.Field(transportManager.GetType(), "_toServerBundles")?.GetValue(transportManager) as IList)?.Clear(); AccessTools.Method(transportManager.GetType(), "InitializeToServerBundles", (Type[])null, (Type[])null)?.Invoke(transportManager, Array.Empty()); } private static int GetConfiguredMaxClients() { try { SteamLobby instance = SteamLobby.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.MaxPlayersDropdown != (Object)null) { return Mathf.Max(2, instance.maxPlayers); } } catch { } return 4; } private static void SetupOfflineLobbyUi(bool asHost) { SteamLobby instance = SteamLobby.Instance; if (!((Object)(object)instance == (Object)null)) { if ((Object)(object)MenuController.Instance != (Object)null && !MenuController.Instance.playMenu.activeSelf) { MenuController.Instance.OpenGame(); } instance.inSteamLobby = true; instance.CurrentLobbyID = 0uL; ApplyOfflineHostOptions(); if (string.IsNullOrWhiteSpace(instance.lobbyName)) { instance.lobbyName = "Offline LAN"; } if ((Object)(object)instance.LobbyWindow != (Object)null) { instance.LobbyWindow.SetActive(true); } if ((Object)(object)instance.LobbiesBrowser != (Object)null) { instance.LobbiesBrowser.SetActive(false); } if ((Object)(object)instance.HostButton != (Object)null) { instance.HostButton.SetActive(!asHost); } if ((Object)(object)instance.StopButton != (Object)null) { instance.StopButton.SetActive(asHost); } if ((Object)(object)instance.LobbyNameText != (Object)null) { ((TMP_Text)instance.LobbyNameText).text = (asHost ? instance.lobbyName : "Offline LAN Client"); } if ((Object)(object)instance.lobbyIdText != (Object)null) { instance.lobbyIdText.SetLobbyIDText(asHost ? $"{GetLocalIPv4Address()}:{OfflineLanPort.Value}" : $"{OfflineLanJoinAddress.Value}:{OfflineLanPort.Value}"); } instance.SetHUDActive(true); if ((Object)(object)PauseManager.Instance != (Object)null) { PauseManager.Instance.serverStarted = asHost; PauseManager.Instance.WriteOfflineLog(asHost ? $"Offline LAN hosting on {GetLocalIPv4Address()}:{OfflineLanPort.Value}" : $"Offline LAN joining {OfflineLanJoinAddress.Value}:{OfflineLanPort.Value}"); } } } private static void ApplyOfflineHostOptions() { SteamLobby instance = SteamLobby.Instance; if (!((Object)(object)instance == (Object)null)) { if ((Object)(object)instance.MaxPlayersDropdown != (Object)null) { instance.maxPlayers = instance.MaxPlayersDropdown.value + 2; } else if (instance.maxPlayers < 2) { instance.maxPlayers = 4; } if ((Object)(object)instance.LobbyTypeDropdownBeforeLobby != (Object)null) { instance.SetLobbyType(instance.LobbyTypeDropdownBeforeLobby); } if ((Object)(object)instance.GamemodeDropdown != (Object)null) { instance.SetGamemode(instance.GamemodeDropdown); } Transport val = InstanceFinder.TransportManager?.Transport; if ((Object)(object)val != (Object)null) { val.SetMaximumClients(Mathf.Max(2, instance.maxPlayers)); } } } private static void BroadcastOfflineMaxPlayers(string reason) { try { SteamLobby instance = SteamLobby.Instance; if (!((Object)(object)instance == (Object)null)) { int num = Mathf.Max(2, instance.maxPlayers); Transport obj = InstanceFinder.TransportManager?.Transport; if (obj != null) { obj.SetMaximumClients(num); } MethodInfo methodInfo = AccessTools.Method(typeof(ClientInstance), "RpcLogic___UpdateObserversMaxPlayers_3316948804", (Type[])null, (Type[])null); ClientInstance[] array = Object.FindObjectsOfType(); foreach (ClientInstance obj2 in array) { methodInfo?.Invoke(obj2, new object[1] { num }); } if ((Object)(object)InstanceFinder.ServerManager != (Object)null && InstanceFinder.ServerManager.Started && (Object)(object)LobbyController.Instance != (Object)null && (Object)(object)LobbyController.Instance.LocalPlayerController != (Object)null) { LobbyController.Instance.LocalPlayerController.UpdateServerMaxPlayers(); } LobbyController instance2 = LobbyController.Instance; if (instance2 != null) { instance2.UpdatePlayerList(); } Log.LogInfo((object)$"Offline LAN broadcast max players={num} ({reason})."); } } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN failed to broadcast max players ({reason}): {arg}"); } } private static void SpawnOfflineSceneMotorIfNeeded() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)SceneMotor.Instance != (Object)null)) { SteamLobby instance = SteamLobby.Instance; object? obj = AccessTools.Field(typeof(SteamLobby), "_sceneMotorPrefab")?.GetValue(instance); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { Log.LogWarning((object)"Offline LAN could not find SteamLobby._sceneMotorPrefab; lobby may not be startable."); return; } GameObject val2 = Object.Instantiate(val, ((Component)instance).transform.position, Quaternion.identity); InstanceFinder.ServerManager.Spawn(val2, (NetworkConnection)null); Log.LogInfo((object)"Offline LAN spawned SceneMotor prefab."); } } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN failed to spawn SceneMotor: {arg}"); } } private static void StopOfflineLanSession(bool loadMainMenu) { //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) if (_offlineLeaveInProgress) { return; } _offlineLeaveInProgress = true; try { HookOfflineServerEvents(subscribe: false); StopOfflineAuthListener(); UnhookServerManagerFromTugboat(); NetworkManager networkManager = InstanceFinder.NetworkManager; if ((Object)(object)networkManager != (Object)null) { if ((Object)(object)networkManager.ClientManager != (Object)null && networkManager.ClientManager.Started) { networkManager.ClientManager.StopConnection(); } if ((Object)(object)networkManager.ServerManager != (Object)null && networkManager.ServerManager.Started) { networkManager.ServerManager.StopConnection(true); } } SteamLobby instance = SteamLobby.Instance; if ((Object)(object)instance != (Object)null) { instance.inSteamLobby = false; instance.CurrentLobbyID = 0uL; instance.players.Clear(); if ((Object)(object)instance.LobbyWindow != (Object)null) { instance.LobbyWindow.SetActive(false); } if ((Object)(object)instance.HostButton != (Object)null) { instance.HostButton.SetActive(true); } if ((Object)(object)instance.StopButton != (Object)null) { instance.StopButton.SetActive(false); } if ((Object)(object)instance.lobbyIdText != (Object)null) { instance.lobbyIdText.SetLobbyIDText("Not In a Lobby"); } instance.SetHUDActive(false); } ClientInstance.playerInstances.Clear(); AcceptedOfflineLanPeers.Clear(); OfflineLanConnectionNames.Clear(); if ((Object)(object)PauseManager.Instance != (Object)null) { PauseManager.Instance.serverStarted = false; } if (loadMainMenu) { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != "MainMenu") { SceneManager.LoadScene("MainMenu"); } } _offlineLanRole = OfflineLanRole.None; _offlineLanStarting = false; _offlineLanStatus = "Stopped"; } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN stop failed: {arg}"); } finally { _offlineLeaveInProgress = false; } } private static void CleanupFailedOfflineStart() { try { HookOfflineServerEvents(subscribe: false); StopOfflineAuthListener(); NetworkManager networkManager = InstanceFinder.NetworkManager; if ((Object)(object)networkManager != (Object)null) { if ((Object)(object)networkManager.ClientManager != (Object)null) { networkManager.ClientManager.StopConnection(); } if ((Object)(object)networkManager.ServerManager != (Object)null) { networkManager.ServerManager.StopConnection(false); } } _offlineLanRole = OfflineLanRole.None; AcceptedOfflineLanPeers.Clear(); OfflineLanConnectionNames.Clear(); } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN failed-start cleanup failed: {arg}"); } } private static void HookOfflineServerEvents(bool subscribe) { ServerManager serverManager = InstanceFinder.ServerManager; if (!((Object)(object)serverManager == (Object)null)) { serverManager.OnRemoteConnectionState -= OfflineServerRemoteConnectionState; if (subscribe) { serverManager.OnRemoteConnectionState += OfflineServerRemoteConnectionState; } } } private static void OfflineServerRemoteConnectionState(NetworkConnection connection, RemoteConnectionStateArgs args) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (!OfflineLanActive || _offlineLanRole != OfflineLanRole.Host || (int)args.ConnectionState != 2) { return; } string text = NormalizePeerAddress(InstanceFinder.TransportManager.Transport.GetConnectionAddress(args.ConnectionId)); if (IsWhitelisted(text)) { if (TryGetWhitelistedName(text, out var name)) { OfflineLanConnectionNames[args.ConnectionId] = name; } Log.LogInfo((object)$"Offline LAN accepted connection {args.ConnectionId} from {text}."); BroadcastOfflineMaxPlayers("remote connected"); } else { Log.LogWarning((object)$"Offline LAN rejected unauthenticated connection {args.ConnectionId} from {text}."); InstanceFinder.ServerManager.Kick(args.ConnectionId, (KickReason)0, (LoggingType)3, "Offline LAN passcode was not accepted."); } } private static void StartOfflineAuthListener() { StopOfflineAuthListener(); _offlineAuthRunning = true; _offlineAuthThread = new Thread(OfflineAuthLoop) { IsBackground = true, Name = "StraftatOfflineLanAuth" }; _offlineAuthThread.Start(); } private static void StopOfflineAuthListener() { _offlineAuthRunning = false; try { _offlineAuthListener?.Close(); } catch { } _offlineAuthListener = null; } private static void OfflineAuthLoop() { try { _offlineAuthListener = new UdpClient(OfflineLanPort.Value + 1); while (_offlineAuthRunning) { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] bytes = _offlineAuthListener.Receive(ref remoteEP); string[] array = Encoding.UTF8.GetString(bytes).Split(new char[1] { '|' }); bool num = array.Length >= 4 && array[0] == "SLAN1" && ConstantTimeEquals(array[1], OfflineLanPasscode.Value); string name = ((array.Length >= 3) ? array[2] : "LAN Player"); string s = "NO"; if (num) { string text = NormalizePeerAddress(remoteEP.Address.ToString()); name = SanitizeOfflineLanName(name); WhitelistIp(text, name); s = "OK|STRAFTAT LAN|" + SanitizeOfflineLanName(OfflineLanPlayerName.Value); Log.LogInfo((object)$"Offline LAN auth accepted {text}:{remoteEP.Port} as {name}."); } else { Log.LogWarning((object)$"Offline LAN auth rejected {NormalizePeerAddress(remoteEP.Address.ToString())}:{remoteEP.Port}."); } byte[] bytes2 = Encoding.UTF8.GetBytes(s); _offlineAuthListener.Send(bytes2, bytes2.Length, remoteEP); } } catch (SocketException) { } catch (ObjectDisposedException) { } catch (Exception arg) { Log.LogWarning((object)$"Offline LAN auth listener stopped unexpectedly: {arg}"); } } private static bool SendOfflineAuthRequest(string host) { try { using UdpClient udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 700; IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(host), OfflineLanPort.Value + 1); byte[] bytes = Encoding.UTF8.GetBytes("SLAN1|" + OfflineLanPasscode.Value + "|" + SanitizeOfflineLanName(OfflineLanPlayerName.Value) + "|" + Application.version); for (int i = 1; i <= 3; i++) { udpClient.Send(bytes, bytes.Length, endPoint); try { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); string @string = Encoding.UTF8.GetString(udpClient.Receive(ref remoteEP)); if (@string.StartsWith("OK|", StringComparison.Ordinal)) { string[] array = @string.Split(new char[1] { '|' }); if (array.Length >= 3) { WhitelistIp(host, array[2]); } _offlineLanStatus = "Passcode accepted"; return true; } _offlineLanStatus = "Passcode rejected"; Log.LogWarning((object)"Offline LAN host rejected the passcode."); return false; } catch (SocketException) { _offlineLanStatus = $"Auth retry {i}/3"; } } } catch (Exception ex2) { _offlineLanStatus = "Auth exception"; Log.LogWarning((object)("Offline LAN auth request failed: " + ex2.Message)); return false; } _offlineLanStatus = "No auth reply"; return false; } private static void WhitelistIp(string ip, string name) { if (!string.IsNullOrWhiteSpace(ip)) { AcceptedOfflineLanPeers[NormalizePeerAddress(ip)] = new OfflineLanAuthInfo { Name = SanitizeOfflineLanName(name), AcceptedAtUtc = DateTime.UtcNow }; } } private static bool TryGetWhitelistedName(string ip, out string name) { name = null; if (AcceptedOfflineLanPeers.TryGetValue(NormalizePeerAddress(ip), out var value) && !string.IsNullOrWhiteSpace(value.Name)) { name = value.Name; return true; } return false; } private static string SanitizeOfflineLanName(string name) { if (string.IsNullOrWhiteSpace(name)) { return "LAN Player"; } name = name.Trim().Replace('|', '/'); if (name.Length > 32) { return name.Substring(0, 32); } return name; } private static bool IsWhitelisted(string ip) { ip = NormalizePeerAddress(ip); switch (ip) { case "127.0.0.1": case "::1": case "localhost": return true; default: return AcceptedOfflineLanPeers.ContainsKey(ip); } } private static string NormalizePeerAddress(string address) { if (string.IsNullOrWhiteSpace(address)) { return string.Empty; } address = address.Trim(); if (address.StartsWith("[", StringComparison.Ordinal)) { int num = address.IndexOf(']'); if (num <= 1) { return address; } return address.Substring(1, num - 1); } if (address.StartsWith("::ffff:", StringComparison.OrdinalIgnoreCase)) { address = address.Substring("::ffff:".Length); } int num2 = address.LastIndexOf(':'); if (num2 > 0 && address.IndexOf(':') == num2) { address = address.Substring(0, num2); } return address; } private static bool ConstantTimeEquals(string left, string right) { left = left ?? string.Empty; right = right ?? string.Empty; int num = left.Length ^ right.Length; int num2 = Math.Max(left.Length, right.Length); for (int i = 0; i < num2; i++) { char c = ((i < left.Length) ? left[i] : '\0'); char c2 = ((i < right.Length) ? right[i] : '\0'); num |= c ^ c2; } return num == 0; } private static string GetLocalIPv4Address() { if (Time.unscaledTime < _nextLocalIpRefreshAt && !string.IsNullOrWhiteSpace(_cachedLocalIPv4Address)) { return _cachedLocalIPv4Address; } _nextLocalIpRefreshAt = Time.unscaledTime + 5f; _cachedLocalIPv4Address = GetLocalIPv4Addresses().FirstOrDefault() ?? "127.0.0.1"; return _cachedLocalIPv4Address; } private static IReadOnlyList GetLocalIPv4Addresses() { List list = new List(); List list2 = new List(); try { NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Tunnel) { continue; } IPInterfaceProperties iPProperties = networkInterface.GetIPProperties(); bool flag = iPProperties.GatewayAddresses.Any((GatewayIPAddressInformation g) => g.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.Any.Equals(g.Address)); foreach (UnicastIPAddressInformation unicastAddress in iPProperties.UnicastAddresses) { IPAddress address2 = unicastAddress.Address; if (address2.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(address2) && !address2.ToString().StartsWith("169.254.", StringComparison.Ordinal)) { (flag ? list : list2).Add(address2.ToString()); } } } } catch { } if (list.Count > 0) { return list.Distinct().ToList(); } if (list2.Count > 0) { return list2.Distinct().ToList(); } try { return (from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList where address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(address) select address.ToString()).Distinct().ToList(); } catch { return new string[1] { "127.0.0.1" }; } } private static ulong GetOfflineLanId(NetworkConnection owner, int playerId) { int num = ((owner == (NetworkConnection)null) ? playerId : owner.ClientId); return (ulong)(90000000000000000L + Mathf.Max(0, num + 1)); } private static string GetOfflineLanPlayerName(NetworkConnection owner, int playerId, ulong lanId) { if (owner != (NetworkConnection)null && owner.IsLocalClient) { return SanitizeOfflineLanName(OfflineLanPlayerName.Value); } if (owner != (NetworkConnection)null) { if (OfflineLanConnectionNames.TryGetValue(owner.ClientId, out var value) && !string.IsNullOrWhiteSpace(value)) { return value; } TransportManager transportManager = InstanceFinder.TransportManager; object address; if (transportManager == null) { address = null; } else { Transport transport = transportManager.Transport; address = ((transport != null) ? transport.GetConnectionAddress(owner.ClientId) : null); } if (TryGetWhitelistedName(NormalizePeerAddress((string)address), out var name)) { return name; } } foreach (OfflineLanAuthInfo item in AcceptedOfflineLanPeers.Values.OrderByDescending((OfflineLanAuthInfo v) => v.AcceptedAtUtc)) { if (!string.IsNullOrWhiteSpace(item.Name)) { return item.Name; } } if (playerId != 0) { return $"LAN Player {playerId + 1}"; } return "LAN Host"; } private static bool ShouldRedirectCreateLobbyToOfflineLan() { if (!OfflineLanEnabled.Value || !RedirectCreateLobbyToOfflineLan.Value) { return false; } if (!ForceOfflineSteamLobbyStartup.Value) { return OfflineLanActive; } return true; } private static void HostOfflineLanFromNormalButton(SteamLobby lobby) { try { ApplyOfflineHostOptions(); Log.LogInfo((object)"Redirecting STRAFTAT Create Lobby button to Offline LAN host."); HostOfflineLan(); } catch (Exception arg) { _offlineLanStatus = "Host button exception"; Log.LogError((object)$"Offline LAN Create Lobby redirect failed: {arg}"); } } private void Awake() { //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _instance = this; _configFile = ((BaseUnityPlugin)this).Config; Enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master enable switch."); Mode = ((BaseUnityPlugin)this).Config.Bind("Routing", "Mode", RoutingMode.ObserveOnly, "ObserveOnly = neutral Internet routing. PreferDirect = LAN/direct preferred. ExperimentalNoRelay = test only."); VerboseSteamNetworkingLogs = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "VerboseSteamNetworkingLogs", true, "Forward Steam Networking diagnostics to BepInEx logs."); AllowUnencryptedSegments = ((BaseUnityPlugin)this).Config.Bind("Routing", "AllowUnencryptedSegments", false, "Dangerous/experimental. Only used if this Steamworks.NET build exposes the setting."); ShowLobbyToggle = ((BaseUnityPlugin)this).Config.Bind("Lobby UI", "ShowLobbyToggle", false, "Legacy Steam-routing selector. Offline LAN uses its own panel, so this should normally stay off."); ToggleX = ((BaseUnityPlugin)this).Config.Bind("Lobby UI", "ToggleX", 775, "Connection mode selector X position in screen pixels."); ToggleY = ((BaseUnityPlugin)this).Config.Bind("Lobby UI", "ToggleY", 410, "Connection mode selector Y position in screen pixels."); ConfigureOfflineLan(((BaseUnityPlugin)this).Config); Mode.SettingChanged += delegate { ApplyRoutingMode("config changed"); }; VerboseSteamNetworkingLogs.SettingChanged += delegate { RegisterSteamCallbacks("diagnostics setting changed"); }; AllowUnencryptedSegments.SettingChanged += delegate { ApplyRoutingMode("unencrypted setting changed"); }; if (!Enabled.Value) { Log.LogInfo((object)"Plugin disabled by config."); return; } _harmony = new Harmony("com.local.straftat.p2p"); _harmony.PatchAll(typeof(SteamApiPatches)); PatchOptionalLobbyMethods(); PatchFishySteamworksConnectionMethods(); PatchOfflineLanMethods(); Log.LogInfo((object)string.Format("{0} {1} loaded in {2} mode.", "STRAFTAT LAN P2P Optimizer", "0.2.0", Mode.Value)); DiscoverRuntimeTypes(); ApplyRoutingMode("plugin awake"); if (ShouldShowLobbyNetworkUi()) { TryCreateLobbyNetworkUi("Awake"); } TryCreateOfflineLanUi("Awake"); } private void Start() { if (ShouldShowLobbyNetworkUi()) { TryCreateLobbyNetworkUi("Start"); } TryCreateOfflineLanUi("Start"); RefreshLobbyNetworkUi(); } private void OnDestroy() { OfflineLanShutdownUi(); _connectionStatusCallback?.Dispose(); _connectionStatusCallback = null; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { if (Enabled.Value) { if (ShouldShowLobbyNetworkUi() && (Object)(object)_uiRoot == (Object)null) { TryCreateLobbyNetworkUi("Update"); } RefreshLobbyNetworkUi(); OfflineLanUpdate(); if (ShouldShowLobbyNetworkUi() && Input.GetKeyDown((KeyCode)289)) { SetLobbyMode((!IsLanMode()) ? RoutingMode.PreferDirect : RoutingMode.ObserveOnly, "F8 hotkey"); } if (Time.unscaledTime >= _nextApplyAttemptAt) { _nextApplyAttemptAt = Time.unscaledTime + 5f; ApplyRoutingMode("periodic retry"); RegisterSteamCallbacks("periodic retry"); } } } private void TryCreateLobbyNetworkUi(string reason) { try { CreateLobbyNetworkUi(); RefreshLobbyNetworkUi(); } catch (Exception arg) { Log.LogWarning((object)$"Failed to create Lobby Network selector UI during {reason}: {arg}"); } } private void CreateLobbyNetworkUi() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_uiRoot != (Object)null)) { EnsureEventSystem(); _uiRoot = new GameObject("StraftatLocalP2P_LobbyNetworkSelector"); Object.DontDestroyOnLoad((Object)(object)_uiRoot); Canvas obj = _uiRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32767; _uiRoot.AddComponent().uiScaleMode = (ScaleMode)0; _uiRoot.AddComponent(); GameObject val = CreateUiObject("Panel", _uiRoot.transform); RectTransform obj2 = val.AddComponent(); obj2.anchorMin = new Vector2(0f, 1f); obj2.anchorMax = new Vector2(0f, 1f); obj2.pivot = new Vector2(0f, 1f); obj2.anchoredPosition = new Vector2((float)ToggleX.Value, (float)(-ToggleY.Value)); obj2.sizeDelta = new Vector2(245f, 102f); ((Graphic)val.AddComponent()).color = new Color(0f, 0f, 0f, 0.58f); CreateText(val.transform, "Title", "Connection Mode", new Vector2(10f, -8f), new Vector2(225f, 22f), 15, (FontStyle)1); _statusText = CreateText(val.transform, "Status", "", new Vector2(10f, -34f), new Vector2(225f, 20f), 12, (FontStyle)0); CreateButton(val.transform, "InternetButton", "Internet", new Vector2(10f, -66f), new Vector2(106f, 28f), delegate { SetLobbyMode(RoutingMode.ObserveOnly, "lobby selector"); }); CreateButton(val.transform, "LanButton", "LAN", new Vector2(128f, -66f), new Vector2(106f, 28f), delegate { SetLobbyMode(RoutingMode.PreferDirect, "lobby selector"); }); Log.LogInfo((object)"Created Lobby Network selector UI. Press F8 to toggle LAN/Internet if the selector is hidden."); } } private static void EnsureEventSystem() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)EventSystem.current != (Object)null)) { GameObject val = new GameObject("StraftatLocalP2P_EventSystem"); Object.DontDestroyOnLoad((Object)val); val.AddComponent(); val.AddComponent(); } } private static GameObject CreateUiObject(string name, Transform parent) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown GameObject val = new GameObject(name); val.transform.SetParent(parent, false); return val; } private static Text CreateText(Transform parent, string name, string text, Vector2 position, Vector2 size, int fontSize, FontStyle style) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) GameObject obj = CreateUiObject(name, parent); RectTransform obj2 = obj.AddComponent(); obj2.anchorMin = new Vector2(0f, 1f); obj2.anchorMax = new Vector2(0f, 1f); obj2.pivot = new Vector2(0f, 1f); obj2.anchoredPosition = position; obj2.sizeDelta = size; Text obj3 = obj.AddComponent(); obj3.font = Resources.GetBuiltinResource("Arial.ttf"); obj3.fontSize = fontSize; obj3.fontStyle = style; obj3.alignment = (TextAnchor)3; ((Graphic)obj3).color = Color.white; obj3.text = text; return obj3; } private static GameObject CreateButton(Transform parent, string name, string text, Vector2 position, Vector2 size, Action onClick) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) GameObject obj = CreateUiObject(name, parent); RectTransform obj2 = obj.AddComponent(); obj2.anchorMin = new Vector2(0f, 1f); obj2.anchorMax = new Vector2(0f, 1f); obj2.pivot = new Vector2(0f, 1f); obj2.anchoredPosition = position; obj2.sizeDelta = size; Image val = obj.AddComponent(); ((Graphic)val).color = new Color(0.16f, 0.18f, 0.2f, 0.95f); Button obj3 = obj.AddComponent