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<ClientInstance>()
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<object>());
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<CodeInstruction> ClientStartConnectionTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> list = new List<CodeInstruction>(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<CodeInstruction> ServerStartConnectionTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
List<CodeInstruction> list = new List<CodeInstruction>(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<NetworkConnection, RemoteConnectionStateArgs> <0>__OfflineServerRemoteConnectionState;
public static ThreadStart <1>__OfflineAuthLoop;
public static DispatchDelegate<SteamNetConnectionStatusChangedCallback_t> <2>__OnConnectionStatusChanged;
public static FSteamNetworkingSocketsDebugOutput <3>__OnSteamNetworkingDebugOutput;
}
private static ConfigEntry<bool> OfflineLanEnabled;
private static ConfigEntry<bool> ForceOfflineSteamLobbyStartup;
private static ConfigEntry<bool> RedirectCreateLobbyToOfflineLan;
private static ConfigEntry<bool> ShowOfflineLanPanel;
private static ConfigEntry<string> OfflineLanJoinAddress;
private static ConfigEntry<string> OfflineLanPasscode;
private static ConfigEntry<string> OfflineLanPlayerName;
private static ConfigEntry<ushort> OfflineLanPort;
private static ConfigEntry<int> OfflineLanPanelX;
private static ConfigEntry<int> OfflineLanPanelY;
private static readonly ConcurrentDictionary<string, OfflineLanAuthInfo> AcceptedOfflineLanPeers = new ConcurrentDictionary<string, OfflineLanAuthInfo>(StringComparer.OrdinalIgnoreCase);
private static readonly ConcurrentDictionary<int, string> OfflineLanConnectionNames = new ConcurrentDictionary<int, string>();
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<ServerConnectionStateArgs> _serverConnectionDelegate;
private static Action<RemoteConnectionStateArgs> _remoteConnectionDelegate;
private static Action<ServerReceivedDataArgs> _serverReceivedDelegate;
private static Action<ClientConnectionStateArgs> _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<bool> Enabled;
private static ConfigEntry<RoutingMode> Mode;
private static ConfigEntry<bool> VerboseSteamNetworkingLogs;
private static ConfigEntry<bool> AllowUnencryptedSegments;
private static ConfigEntry<bool> ShowLobbyToggle;
private static ConfigEntry<int> ToggleX;
private static ConfigEntry<int> ToggleY;
private static ConfigFile _configFile;
private static Harmony _harmony;
private static Callback<SteamNetConnectionStatusChangedCallback_t> _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<bool>("Offline LAN", "Enabled", true, "Enable the offline LAN prototype panel and patches.");
ForceOfflineSteamLobbyStartup = config.Bind<bool>("Offline LAN", "ForceOfflineSteamLobbyStartup", true, "Skip Steam lobby callback setup so the offline prototype can run without Steam matchmaking.");
RedirectCreateLobbyToOfflineLan = config.Bind<bool>("Offline LAN", "RedirectCreateLobbyToOfflineLan", true, "Make STRAFTAT's normal Create Lobby host button start an offline LAN lobby.");
ShowOfflineLanPanel = config.Bind<bool>("Offline LAN", "ShowOfflineLanPanel", true, "Show the offline LAN host/join panel on the multiplayer home screen.");
OfflineLanJoinAddress = config.Bind<string>("Offline LAN", "JoinAddress", "192.168.1.100", "LAN IPv4 address of the host PC.");
OfflineLanPasscode = config.Bind<string>("Offline LAN", "Passcode", "LAN", "Passcode required before a client can join this offline LAN session.");
OfflineLanPlayerName = config.Bind<string>("Offline LAN", "PlayerName", Environment.UserName, "Display name used locally by the offline LAN prototype.");
OfflineLanPort = config.Bind<ushort>("Offline LAN", "Port", (ushort)7770, "UDP port used by FishNet Tugboat. The passcode handshake uses the next port.");
OfflineLanPanelX = config.Bind<int>("Offline LAN", "PanelX", 775, "Offline LAN panel X position in screen pixels.");
OfflineLanPanelY = config.Bind<int>("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<Canvas>();
obj.renderMode = (RenderMode)0;
obj.sortingOrder = 32766;
_offlineLanRoot.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)0;
_offlineLanRoot.AddComponent<GraphicRaycaster>();
GameObject val = CreateUiObject("Panel", _offlineLanRoot.transform);
RectTransform obj2 = val.AddComponent<RectTransform>();
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<Image>()).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<RectTransform>();
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<Image>();
((Graphic)val2).color = new Color(0.08f, 0.09f, 0.1f, 0.95f);
GameObject obj2 = CreateUiObject("Text", val.transform);
RectTransform obj3 = obj2.AddComponent<RectTransform>();
obj3.anchorMin = Vector2.zero;
obj3.anchorMax = Vector2.one;
obj3.offsetMin = new Vector2(6f, 2f);
obj3.offsetMax = new Vector2(-6f, -2f);
Text val3 = obj2.AddComponent<Text>();
val3.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
val3.fontSize = 13;
val3.alignment = (TextAnchor)3;
((Graphic)val3).color = Color.white;
InputField obj4 = val.AddComponent<InputField>();
((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<Tugboat>() ?? ((Component)networkManager).gameObject.AddComponent<Tugboat>());
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<object>());
}
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<ClientInstance>();
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<GameObject>(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<string> GetLocalIPv4Addresses()
{
List<string> list = new List<string>();
List<string> list2 = new List<string>();
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<bool>("General", "Enabled", true, "Master enable switch.");
Mode = ((BaseUnityPlugin)this).Config.Bind<RoutingMode>("Routing", "Mode", RoutingMode.ObserveOnly, "ObserveOnly = neutral Internet routing. PreferDirect = LAN/direct preferred. ExperimentalNoRelay = test only.");
VerboseSteamNetworkingLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseSteamNetworkingLogs", true, "Forward Steam Networking diagnostics to BepInEx logs.");
AllowUnencryptedSegments = ((BaseUnityPlugin)this).Config.Bind<bool>("Routing", "AllowUnencryptedSegments", false, "Dangerous/experimental. Only used if this Steamworks.NET build exposes the setting.");
ShowLobbyToggle = ((BaseUnityPlugin)this).Config.Bind<bool>("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<int>("Lobby UI", "ToggleX", 775, "Connection mode selector X position in screen pixels.");
ToggleY = ((BaseUnityPlugin)this).Config.Bind<int>("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<Canvas>();
obj.renderMode = (RenderMode)0;
obj.sortingOrder = 32767;
_uiRoot.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)0;
_uiRoot.AddComponent<GraphicRaycaster>();
GameObject val = CreateUiObject("Panel", _uiRoot.transform);
RectTransform obj2 = val.AddComponent<RectTransform>();
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<Image>()).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<EventSystem>();
val.AddComponent<StandaloneInputModule>();
}
}
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<RectTransform>();
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<Text>();
obj3.font = Resources.GetBuiltinResource<Font>("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<RectTransform>();
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<Image>();
((Graphic)val).color = new Color(0.16f, 0.18f, 0.2f, 0.95f);
Button obj3 = obj.AddComponent<Button>();
((Selectable)obj3).targetGraphic = (Graphic)(object)val;
((UnityEvent)obj3.onClick).AddListener((UnityAction)delegate
{
onClick();
});
CreateText(obj.transform, "Label", text, Vector2.zero, size, 14, (FontStyle)1).alignment = (TextAnchor)4;
return obj;
}
private void RefreshLobbyNetworkUi()
{
if ((Object)(object)_uiRoot != (Object)null)
{
_uiRoot.SetActive(ShouldShowLobbyNetworkUi() && IsHostMenuVisible());
}
if ((Object)(object)_statusText != (Object)null)
{
_statusText.text = (IsLanMode() ? "This PC: LAN preferred" : "This PC: Internet");
}
}
private static bool IsHostMenuVisible()
{
if (Time.unscaledTime < _nextHostMenuVisibilityCheckAt)
{
return _cachedHostMenuVisible;
}
_nextHostMenuVisibilityCheckAt = Time.unscaledTime + 0.5f;
try
{
bool flag = false;
bool flag2 = false;
bool flag3 = false;
TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
foreach (TMP_Text val in array)
{
if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
{
continue;
}
string text = val.text;
if (!string.IsNullOrEmpty(text))
{
if (text.IndexOf("Create Lobby", StringComparison.OrdinalIgnoreCase) >= 0)
{
flag = true;
}
else if (text.IndexOf("Lobby Name", StringComparison.OrdinalIgnoreCase) >= 0)
{
flag2 = true;
}
else if (text.IndexOf("Max Players", StringComparison.OrdinalIgnoreCase) >= 0)
{
flag3 = true;
}
}
}
_cachedHostMenuVisible = flag && flag2 && flag3 && !IsSettingsTextVisible();
return _cachedHostMenuVisible;
}
catch (Exception ex)
{
Log.LogDebug((object)("Host menu visibility check failed: " + ex.Message));
_cachedHostMenuVisible = false;
return false;
}
}
private static bool ShouldShowLobbyNetworkUi()
{
if (Enabled.Value && ShowLobbyToggle.Value)
{
if (OfflineLanEnabled != null)
{
return !OfflineLanEnabled.Value;
}
return true;
}
return false;
}
private static bool IsSettingsTextVisible()
{
TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
foreach (TMP_Text val in array)
{
if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
{
string text = val.text;
if (!string.IsNullOrEmpty(text) && (text.IndexOf("Settings", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Options", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Controls", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Resolution", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Sensitivity", StringComparison.OrdinalIgnoreCase) >= 0))
{
return true;
}
}
}
return false;
}
private static void PatchOptionalLobbyMethods()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Expected O, but got Unknown
HarmonyMethod prefix = new HarmonyMethod(typeof(LobbyLifecyclePatches), "BeforeLobbyLifecycleEvent", (Type[])null);
string[] lobbyPatchTypes = LobbyPatchTypes;
for (int i = 0; i < lobbyPatchTypes.Length; i++)
{
Type type = AccessTools.TypeByName(lobbyPatchTypes[i]);
if (!(type == null))
{
_steamLobbyType = type;
PatchMethodIfPresent(type, "CreateLobbyWithDelay", prefix);
PatchMethodIfPresent(type, "JoinLobbyWithDelay", prefix);
PatchMethodIfPresent(type, "JoinRichPresenceLobbyWithDelayyyyy", prefix);
PatchMethodIfPresent(type, "LeaveSteamLobby", prefix);
}
}
}
private static void PatchMethodIfPresent(Type type, string methodName, HarmonyMethod prefix)
{
MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
if (!(methodInfo == null))
{
_harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Log.LogInfo((object)("Patched lobby lifecycle method " + type.FullName + "." + methodName + "."));
}
}
private static void PatchFishySteamworksConnectionMethods()
{
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Expected O, but got Unknown
//IL_011b: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Expected O, but got Unknown
Type type = AccessTools.TypeByName("FishySteamworks.Client.ClientSocket");
MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "StartConnection", new Type[3]
{
typeof(string),
typeof(ushort),
typeof(bool)
}, (Type[])null));
if (methodInfo == null)
{
Log.LogWarning((object)"FishySteamworks.Client.ClientSocket.StartConnection was not found; per-connection ICE patch was not applied.");
return;
}
HarmonyMethod val = new HarmonyMethod(typeof(FishySteamworksPatches), "ClientStartConnectionTranspiler", (Type[])null);
_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
Type type2 = AccessTools.TypeByName("FishySteamworks.Server.ServerSocket");
MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.Method(type2, "StartConnection", new Type[4]
{
typeof(string),
typeof(ushort),
typeof(int),
typeof(bool)
}, (Type[])null));
if (methodInfo2 == null)
{
Log.LogWarning((object)"FishySteamworks.Server.ServerSocket.StartConnection was not found; listen-socket ICE patch was not applied.");
}
else
{
HarmonyMethod val2 = new HarmonyMethod(typeof(FishySteamworksPatches), "ServerStartConnectionTranspiler", (Type[])null);
_harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null);
Log.LogInfo((object)"Patched FishySteamworks server listen setup so LAN hosts can advertise ICE/direct routing.");
}
Log.LogInfo((object)"Patched FishySteamworks connection setup so LAN mode can enable per-connection ICE/direct routing.");
}
private static void DiscoverRuntimeTypes()
{
_steamLobbyType = _steamLobbyType ?? AccessTools.TypeByName("SteamLobby");
Log.LogInfo((object)((_steamLobbyType == null) ? "SteamLobby type was not found yet; lobby UI will wait for runtime discovery." : ("Detected STRAFTAT lobby type: " + _steamLobbyType.FullName + ".")));
}
private static bool ShouldShowLobbySelector()
{
if (_steamLobbyType == null)
{
_steamLobbyType = AccessTools.TypeByName("SteamLobby");
}
if (_steamLobbyType != null)
{
try
{
if (Object.FindObjectOfType(_steamLobbyType) != (Object)null)
{
return true;
}
}
catch (Exception ex)
{
Log.LogDebug((object)("Lobby visibility check failed: " + ex.Message));
}
}
return true;
}
internal static void OnSteamApiInitialized()
{
_steamInitSeen = true;
Log.LogInfo((object)"SteamAPI.Init completed; applying routing mode and registering diagnostics.");
ApplyRoutingMode("SteamAPI.Init");
RegisterSteamCallbacks("SteamAPI.Init");
}
internal static void OnLobbyLifecycleEvent(string methodName)
{
Log.LogInfo((object)$"Lobby lifecycle event starting: {methodName}; locking routing mode to {Mode.Value} for this lobby action.");
ApplyRoutingMode(methodName);
}
private static void SetLobbyMode(RoutingMode mode, string reason)
{
Mode.Value = mode;
_configFile.Save();
Log.LogInfo((object)$"Lobby network mode set to {Mode.Value} ({reason}). Create a new lobby or have players rejoin to use this route choice.");
ApplyRoutingMode(reason);
Plugin[] array = Object.FindObjectsOfType<Plugin>();
for (int i = 0; i < array.Length; i++)
{
array[i].RefreshLobbyNetworkUi();
}
}
private static bool IsLanMode()
{
if (Mode.Value != RoutingMode.PreferDirect)
{
return Mode.Value == RoutingMode.ExperimentalNoRelay;
}
return true;
}
private static int GetPerConnectionIceEnableValue()
{
if (!Enabled.Value || !IsLanMode())
{
return 0;
}
int num = 6;
Log.LogInfo((object)$"Applying per-connection P2P_Transport_ICE_Enable={num} for {Mode.Value} mode.");
return num;
}
private static SteamNetworkingConfigValue_t[] GetListenSocketConfigValues()
{
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: 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_006a: 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_0079: Unknown result type (might be due to invalid IL or missing references)
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
if (!Enabled.Value || !IsLanMode())
{
return (SteamNetworkingConfigValue_t[])(object)new SteamNetworkingConfigValue_t[0];
}
int num = 6;
Log.LogInfo((object)$"Applying listen-socket P2P_Transport_ICE_Enable={num} for {Mode.Value} mode.");
return (SteamNetworkingConfigValue_t[])(object)new SteamNetworkingConfigValue_t[1]
{
new SteamNetworkingConfigValue_t
{
m_eValue = (ESteamNetworkingConfigValue)104,
m_eDataType = (ESteamNetworkingConfigDataType)1,
m_val = new OptionValue
{
m_int32 = num
}
}
};
}
private static void RegisterSteamCallbacks(string reason)
{
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Expected O, but got Unknown
if (!Enabled.Value || !_steamInitSeen || _steamCallbacksRegistered)
{
return;
}
try
{
_connectionStatusCallback = Callback<SteamNetConnectionStatusChangedCallback_t>.Create((DispatchDelegate<SteamNetConnectionStatusChangedCallback_t>)OnConnectionStatusChanged);
if (VerboseSteamNetworkingLogs.Value)
{
object obj = <>O.<3>__OnSteamNetworkingDebugOutput;
if (obj == null)
{
FSteamNetworkingSocketsDebugOutput val = OnSteamNetworkingDebugOutput;
<>O.<3>__OnSteamNetworkingDebugOutput = val;
obj = (object)val;
}
_debugOutputCallback = (FSteamNetworkingSocketsDebugOutput)obj;
SteamNetworkingUtils.SetDebugOutputFunction((ESteamNetworkingSocketsDebugOutputType)5, _debugOutputCallback);
}
SteamNetworkingUtils.InitRelayNetworkAccess();
_steamCallbacksRegistered = true;
Log.LogInfo((object)("Steam Networking diagnostics registered (" + reason + ")."));
LogRelayStatus();
}
catch (Exception arg)
{
Log.LogWarning((object)$"Failed to register Steam Networking diagnostics ({reason}): {arg}");
}
}
private static void ApplyRoutingMode(string reason)
{
if (!Enabled.Value)
{
return;
}
if (!_steamInitSeen)
{
Log.LogDebug((object)$"Steamworks is not initialized yet; deferring routing mode {Mode.Value} ({reason}).");
}
else
{
if (_lastAppliedMode == Mode.Value && _lastUnencryptedSetting == AllowUnencryptedSegments.Value)
{
return;
}
try
{
switch (Mode.Value)
{
case RoutingMode.ObserveOnly:
Log.LogInfo((object)("Internet/ObserveOnly routing selected (" + reason + "). Steam routing is left neutral."));
break;
case RoutingMode.PreferDirect:
SetIntConfig((ESteamNetworkingConfigValue)104, 6);
SetIntConfig((ESteamNetworkingConfigValue)105, 0);
SetIntConfig((ESteamNetworkingConfigValue)106, 1000);
TrySetUnencryptedSegments();
Log.LogInfo((object)("LAN routing selected (" + reason + "). ICE private/public direct P2P is preferred and SDR relay remains available as fallback."));
break;
case RoutingMode.ExperimentalNoRelay:
SetIntConfig((ESteamNetworkingConfigValue)104, 6);
SetIntConfig((ESteamNetworkingConfigValue)105, 0);
SetIntConfig((ESteamNetworkingConfigValue)106, 1000000);
TrySetUnencryptedSegments();
Log.LogWarning((object)("ExperimentalNoRelay selected (" + reason + "). ICE private/public direct P2P is preferred, SDR relay is heavily penalized, and connections may fail off-LAN."));
break;
}
_lastAppliedMode = Mode.Value;
_lastUnencryptedSetting = AllowUnencryptedSegments.Value;
LogRelayStatus();
}
catch (Exception arg)
{
_lastAppliedMode = null;
Log.LogWarning((object)$"Failed to apply routing mode {Mode.Value} ({reason}): {arg}");
}
}
}
private static void TrySetUnencryptedSegments()
{
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
if (AllowUnencryptedSegments.Value)
{
if (!Enum.TryParse<ESteamNetworkingConfigValue>("k_ESteamNetworkingConfig_P2P_AllowUnencryptedSegments", out ESteamNetworkingConfigValue result))
{
Log.LogWarning((object)"AllowUnencryptedSegments is enabled, but this Steamworks.NET build does not expose P2P_AllowUnencryptedSegments.");
return;
}
SetIntConfig(result, 1);
Log.LogWarning((object)"AllowUnencryptedSegments was applied. Use only for local testing.");
}
}
private static void SetIntConfig(ESteamNetworkingConfigValue value, int configValue)
{
//IL_000e: 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_0028: Unknown result type (might be due to invalid IL or missing references)
IntPtr intPtr = Marshal.AllocHGlobal(4);
try
{
Marshal.WriteInt32(intPtr, configValue);
if (!SteamNetworkingUtils.SetConfigValue(value, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, intPtr))
{
Log.LogWarning((object)$"SteamNetworkingUtils.SetConfigValue failed for {value}={configValue}.");
}
else
{
Log.LogDebug((object)$"Applied {value}={configValue}.");
}
}
finally
{
Marshal.FreeHGlobal(intPtr);
}
}
private static void LogRelayStatus()
{
//IL_000b: 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_0023: 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_003a: 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)
if (!_steamInitSeen)
{
return;
}
try
{
SteamRelayNetworkStatus_t val = default(SteamRelayNetworkStatus_t);
ESteamNetworkingAvailability relayNetworkStatus = SteamNetworkingUtils.GetRelayNetworkStatus(ref val);
Log.LogInfo((object)$"Steam relay status: availability={relayNetworkStatus}, relay={val.m_eAvailNetworkConfig}, anyRelay={val.m_eAvailAnyRelay}, msg={((SteamRelayNetworkStatus_t)(ref val)).m_debugMsg}.");
}
catch (Exception ex)
{
Log.LogDebug((object)("Relay status unavailable: " + ex.Message));
}
}
private static void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//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_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: 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_003a: 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_0048: 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)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: 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)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
SteamNetConnectionInfo_t info = callback.m_info;
Log.LogInfo((object)$"Steam connection changed: conn={callback.m_hConn.m_HSteamNetConnection}, old={callback.m_eOldState}, new={info.m_eState}, flags={info.m_nFlags}, relayPOP={info.m_idPOPRelay}, remotePOP={info.m_idPOPRemote}, end={info.m_eEndReason}.");
}
private static void OnSteamNetworkingDebugOutput(ESteamNetworkingSocketsDebugOutputType type, StringBuilder message)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
if (VerboseSteamNetworkingLogs.Value && message != null)
{
Log.LogInfo((object)$"SteamNetworking[{type}]: {message}");
}
}
}
internal enum RoutingMode
{
ObserveOnly,
PreferDirect,
ExperimentalNoRelay
}
}