Decompiled source of GungeonTogether LAN v0.3.7

BepInEx/plugins/GungeonTogetherLAN/GungeonTogether.LAN-0.3.7-alpha.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using Dungeonator;
using FullInspector;
using GungeonTogether.Lan;
using GungeonTogether.Networking;
using HarmonyLib;
using InControl;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("GungeonTogether LAN Dungeon Sync Alpha")]
[assembly: AssemblyDescription("Experimental host-authoritative two-PC dungeon synchronization")]
[assembly: AssemblyFileVersion("0.3.7.0")]
[assembly: AssemblyVersion("0.3.7.0")]
namespace GungeonTogether.Networking
{
	public sealed class RemotePlayerAvatar : MonoBehaviour
	{
		private readonly SnapshotBuffer<LanPlayerState> frames = new SnapshotBuffer<LanPlayerState>(0.06);

		private tk2dSprite sprite;

		private string collectionKey;

		private tk2dSpriteCollectionData collection;

		private Vector3 firstPosition;

		public int RenderedFrames { get; private set; }

		public float DistanceMoved { get; private set; }

		public string CollectionName
		{
			get
			{
				if (!((Object)(object)collection == (Object)null))
				{
					return ((Object)collection).name;
				}
				return "";
			}
		}

		public static RemotePlayerAvatar Create(ulong peer, Vector2 position, float rotation)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			return new GameObject("GT_FoyerPeer_" + peer).AddComponent<RemotePlayerAvatar>();
		}

		public void Apply(LanPlayerState state)
		{
			frames.Push(state, state.Sequence, state.SentAt, Time.realtimeSinceStartup);
		}

		private void LateUpdate()
		{
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: 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_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			if (!frames.Sample(Time.realtimeSinceStartup, out var from, out var to, out var blend))
			{
				return;
			}
			string text = from.CollectionGuid + "|" + from.Collection;
			if ((Object)(object)collection == (Object)null || collectionKey != text)
			{
				NativeWorld.CharacterPrefab(from.Character);
				collection = FindCollection(from);
				collectionKey = text;
			}
			if (!((Object)(object)collection == (Object)null) && from.SpriteId < collection.spriteDefinitions.Length)
			{
				if ((Object)(object)sprite == (Object)null)
				{
					sprite = ((Component)this).gameObject.AddComponent<tk2dSprite>();
				}
				if ((Object)(object)((tk2dBaseSprite)sprite).Collection != (Object)(object)collection || ((tk2dBaseSprite)sprite).spriteId != from.SpriteId)
				{
					((tk2dBaseSprite)sprite).SetSprite(collection, from.SpriteId);
				}
				Vector3 val = default(Vector3);
				((Vector3)(ref val))..ctor(from.X, from.Y, from.Z);
				Vector3 val2 = default(Vector3);
				((Vector3)(ref val2))..ctor(to.X, to.Y, to.Z);
				Transform transform = ((Component)this).transform;
				Vector3 val3 = val2 - val;
				transform.position = ((((Vector3)(ref val3)).sqrMagnitude < 64f) ? Vector3.Lerp(val, val2, blend) : val);
				if (RenderedFrames == 0)
				{
					firstPosition = ((Component)this).transform.position;
				}
				RenderedFrames++;
				DistanceMoved = Mathf.Max(DistanceMoved, Vector3.Distance(firstPosition, ((Component)this).transform.position));
				((Component)this).transform.rotation = Quaternion.Euler(0f, 0f, Mathf.LerpAngle(from.Rotation, to.Rotation, blend));
				((Component)this).transform.localScale = new Vector3(from.ScaleX, from.ScaleY, 1f);
				((tk2dBaseSprite)sprite).HeightOffGround = from.Height;
				((tk2dBaseSprite)sprite).FlipX = from.FlipX;
			}
		}

		private static tk2dSpriteCollectionData FindCollection(LanPlayerState state)
		{
			tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll<tk2dSpriteCollectionData>();
			foreach (tk2dSpriteCollectionData val in array)
			{
				if ((!string.IsNullOrEmpty(state.CollectionGuid) && val.spriteCollectionGUID == state.CollectionGuid) || (string.IsNullOrEmpty(state.CollectionGuid) && (((Object)val).name == state.Collection || val.spriteCollectionName == state.Collection)))
				{
					return val.inst;
				}
			}
			if (!string.IsNullOrEmpty(state.CollectionGuid))
			{
				try
				{
					return tk2dSystem.LoadResourceByGUID<tk2dSpriteCollectionData>(state.CollectionGuid);
				}
				catch
				{
				}
			}
			return null;
		}
	}
}
namespace GungeonTogether.Lan
{
	public sealed class SoundSwitch
	{
		public uint Group;

		public uint Value;
	}
	public sealed class SoundCue
	{
		public uint Event;

		public uint Emitter;

		public float X;

		public float Y;

		public readonly List<SoundSwitch> Switches = new List<SoundSwitch>();
	}
	public sealed class SoundBatch
	{
		public const int MaxCues = 64;

		public const int MaxSwitches = 8;

		public int Epoch;

		public uint Sequence;

		public double SentAt;

		public readonly List<SoundCue> Cues = new List<SoundCue>();

		public byte[] Encode()
		{
			Validate();
			return Wire.Write(DungeonMessage.Audio, delegate(BinaryWriter w)
			{
				w.Write(Epoch);
				w.Write(Sequence);
				w.Write(SentAt);
				w.Write((byte)Cues.Count);
				foreach (SoundCue cue in Cues)
				{
					w.Write(cue.Event);
					w.Write(cue.Emitter);
					w.Write(cue.X);
					w.Write(cue.Y);
					w.Write((byte)cue.Switches.Count);
					foreach (SoundSwitch @switch in cue.Switches)
					{
						w.Write(@switch.Group);
						w.Write(@switch.Value);
					}
				}
			});
		}

		public static SoundBatch Decode(byte[] bytes)
		{
			return Wire.Read(bytes, DungeonMessage.Audio, delegate(BinaryReader r)
			{
				SoundBatch soundBatch = new SoundBatch
				{
					Epoch = r.ReadInt32(),
					Sequence = r.ReadUInt32(),
					SentAt = r.ReadDouble()
				};
				int num = r.ReadByte();
				if (num > 64)
				{
					throw new IOException("Audio batch too large.");
				}
				for (int i = 0; i < num; i++)
				{
					SoundCue soundCue = new SoundCue
					{
						Event = r.ReadUInt32(),
						Emitter = r.ReadUInt32(),
						X = r.ReadSingle(),
						Y = r.ReadSingle()
					};
					int num2 = r.ReadByte();
					if (num2 > 8)
					{
						throw new IOException("Too many audio switches.");
					}
					for (int j = 0; j < num2; j++)
					{
						soundCue.Switches.Add(new SoundSwitch
						{
							Group = r.ReadUInt32(),
							Value = r.ReadUInt32()
						});
					}
					soundBatch.Cues.Add(soundCue);
				}
				soundBatch.Validate();
				return soundBatch;
			});
		}

		private void Validate()
		{
			if (Epoch < 1 || Cues.Count > 64)
			{
				throw new IOException("Invalid audio epoch or count.");
			}
			Wire.Time(SentAt);
			foreach (SoundCue cue in Cues)
			{
				if (cue == null || cue.Event == 0 || cue.Emitter == 0 || cue.Switches.Count > 8)
				{
					throw new IOException("Invalid sound cue.");
				}
				Wire.Range(cue.X, -100000f, 100000f);
				Wire.Range(cue.Y, -100000f, 100000f);
				HashSet<uint> hashSet = new HashSet<uint>();
				foreach (SoundSwitch @switch in cue.Switches)
				{
					if (@switch == null || @switch.Group == 0 || @switch.Value == 0 || !hashSet.Add(@switch.Group))
					{
						throw new IOException("Invalid sound switch.");
					}
				}
			}
		}
	}
	public sealed class SoundSequence
	{
		private int epoch;

		private uint sequence;

		private bool seen;

		private double clockOffset = double.PositiveInfinity;

		private void EnsureEpoch(int currentEpoch)
		{
			if (epoch != currentEpoch)
			{
				epoch = currentEpoch;
				sequence = 0u;
				seen = false;
				clockOffset = double.PositiveInfinity;
			}
		}

		public void ObserveClock(int currentEpoch, double senderTime, double localTime)
		{
			EnsureEpoch(currentEpoch);
			Wire.Time(senderTime);
			Wire.Time(localTime);
			clockOffset = Math.Min(clockOffset, localTime - senderTime);
		}

		public bool Accept(SoundBatch batch, int currentEpoch, double latestSnapshotTime, double localTime = -1.0)
		{
			if (batch.Epoch != currentEpoch)
			{
				return false;
			}
			EnsureEpoch(currentEpoch);
			if (seen && !Wire.Newer(batch.Sequence, sequence))
			{
				return false;
			}
			seen = true;
			sequence = batch.Sequence;
			if (localTime >= 0.0 && !double.IsPositiveInfinity(clockOffset))
			{
				latestSnapshotTime = Math.Max(latestSnapshotTime, localTime - clockOffset);
			}
			if (!(latestSnapshotTime <= 0.0))
			{
				return batch.SentAt >= latestSnapshotTime - 0.3;
			}
			return true;
		}
	}
	public sealed class AudioReplication : IDisposable
	{
		private sealed class Source
		{
			public uint Id;

			public GameObject Object;

			public float Touched;

			public readonly Dictionary<uint, uint> Switches = new Dictionary<uint, uint>();
		}

		[HarmonyPatch]
		private static class Events
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				MethodInfo[] methods = typeof(AkSoundEngine).GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.Name == "PostEvent" && (object)methodInfo.ReturnType == typeof(uint))
					{
						yield return methodInfo;
					}
				}
			}

			[HarmonyPriority(800)]
			private static bool Prefix(object __0, GameObject __1, ref uint __result)
			{
				DungeonSession session = Session;
				if (session == null || session.Phase != DungeonSession.RunPhase.Running || replaying || !BattleEvent(__0))
				{
					return true;
				}
				if (session.IsHost)
				{
					session.Audio.Capture(__0, __1);
					return true;
				}
				__result = 0u;
				return false;
			}
		}

		[HarmonyPatch]
		private static class Switches
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				MethodInfo[] methods = typeof(AkSoundEngine).GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.Name == "SetSwitch" && methodInfo.GetParameters().Length == 3 && (object)methodInfo.GetParameters()[2].ParameterType == typeof(GameObject))
					{
						yield return methodInfo;
					}
				}
			}

			private static void Prefix(object __0, object __1, GameObject __2)
			{
				DungeonSession session = Session;
				if (session == null || !session.IsHost || session.Phase != DungeonSession.RunPhase.Running || replaying)
				{
					return;
				}
				Source source = session.Audio.GetSource(__2);
				if (source != null)
				{
					uint key = Id(__0);
					if (source.Switches.ContainsKey(key) || source.Switches.Count < 8)
					{
						source.Switches[key] = Id(__1);
					}
				}
			}
		}

		private readonly Dictionary<int, Source> sources = new Dictionary<int, Source>();

		private readonly Dictionary<uint, Source> receivers = new Dictionary<uint, Source>();

		private readonly List<SoundCue> pending = new List<SoundCue>();

		private readonly List<int> expired = new List<int>();

		private readonly List<uint> expiredReceivers = new List<uint>();

		private readonly SoundSequence sequence = new SoundSequence();

		private uint nextSource;

		private uint nextBatch;

		private float nextFlush;

		private float oldest;

		[ThreadStatic]
		private static bool replaying;

		public int Captured { get; private set; }

		public int Dispatched { get; private set; }

		public int Dropped { get; private set; }

		public int FailedEvents { get; private set; }

		public int SwitchesApplied { get; private set; }

		public int Queued => pending.Count;

		private static DungeonSession Session
		{
			get
			{
				if (!((Object)(object)DungeonPlugin.Instance == (Object)null))
				{
					return DungeonPlugin.Instance.Session;
				}
				return null;
			}
		}

		public void ObserveClock(int epoch, double senderTime)
		{
			sequence.ObserveClock(epoch, senderTime, Time.realtimeSinceStartup);
		}

		private static bool BattleEvent(object id)
		{
			if (!(id is string text))
			{
				if (id is uint)
				{
					return (uint)id != 0;
				}
				return false;
			}
			string[] array = new string[8] { "WPN_", "ENM_", "OBJ_", "CHR_", "FS_", "VO_", "PLR_", "BOSS_" };
			foreach (string text2 in array)
			{
				if (text.StartsWith("Play_" + text2, StringComparison.OrdinalIgnoreCase) || text.StartsWith("Stop_" + text2, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static uint Id(object value)
		{
			if (!(value is uint))
			{
				return AkSoundEngine.GetIDFromString((string)value);
			}
			return (uint)value;
		}

		public void PlayHurt(PlayerController player, int character)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			replaying = true;
			try
			{
				AkSoundEngine.SetSwitch("CHR_Player", CharacterCatalog.Identity(character), ((Component)player).gameObject);
				AkSoundEngine.PostEvent("Play_CHR_general_hurt_01", ((Component)player).gameObject);
			}
			finally
			{
				replaying = false;
			}
		}

		private Source GetSource(GameObject obj)
		{
			if ((Object)(object)obj == (Object)null)
			{
				return null;
			}
			if (!sources.TryGetValue(((Object)obj).GetInstanceID(), out var value))
			{
				if (sources.Count >= 256)
				{
					return null;
				}
				value = new Source
				{
					Id = ++nextSource,
					Object = obj
				};
				Gun component = obj.GetComponent<Gun>();
				if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.gunSwitchGroup))
				{
					value.Switches[AkSoundEngine.GetIDFromString("WPN_Guns")] = AkSoundEngine.GetIDFromString(component.gunSwitchGroup);
				}
				PlayerController component2 = obj.GetComponent<PlayerController>();
				if ((Object)(object)component2 != (Object)null)
				{
					value.Switches[AkSoundEngine.GetIDFromString("CHR_Player")] = AkSoundEngine.GetIDFromString(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref component2.characterIdentity)/*cast due to .constrained prefix*/).ToString());
				}
				sources.Add(((Object)obj).GetInstanceID(), value);
			}
			value.Touched = Time.realtimeSinceStartup;
			return value;
		}

		private void Capture(object id, GameObject obj)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			if (pending.Count >= 256)
			{
				Dropped++;
				return;
			}
			Source source = GetSource(obj);
			if (source == null)
			{
				return;
			}
			Vector3 position = obj.transform.position;
			PlayerController secondaryPlayer = GameManager.Instance.SecondaryPlayer;
			if (((Object)(object)secondaryPlayer != (Object)null && (Object)(object)obj == (Object)(object)((Component)secondaryPlayer).gameObject && Id(id) == AkSoundEngine.GetIDFromString("Play_CHR_general_hurt_01")) || ((Object)(object)secondaryPlayer != (Object)null && Vector2.Distance(Vector3Extensions.XY(position), ((GameActor)secondaryPlayer).CenterPosition) > 80f))
			{
				return;
			}
			SoundCue soundCue = new SoundCue
			{
				Event = Id(id),
				Emitter = source.Id,
				X = position.x,
				Y = position.y
			};
			foreach (KeyValuePair<uint, uint> @switch in source.Switches)
			{
				soundCue.Switches.Add(new SoundSwitch
				{
					Group = @switch.Key,
					Value = @switch.Value
				});
			}
			if (pending.Count == 0)
			{
				oldest = Time.realtimeSinceStartup;
			}
			pending.Add(soundCue);
			Captured++;
		}

		public void Flush(int epoch, LanTransport transport)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			expired.Clear();
			foreach (KeyValuePair<int, Source> source in sources)
			{
				if ((Object)(object)source.Value.Object == (Object)null || realtimeSinceStartup - source.Value.Touched > 10f)
				{
					expired.Add(source.Key);
				}
			}
			foreach (int item in expired)
			{
				sources.Remove(item);
			}
			if (!(realtimeSinceStartup < nextFlush) && pending.Count != 0)
			{
				nextFlush = realtimeSinceStartup + 1f / 30f;
				if (realtimeSinceStartup - oldest > 0.25f || transport.OldestPendingMilliseconds > 150.0)
				{
					Dropped += pending.Count;
					pending.Clear();
					return;
				}
				SoundBatch soundBatch = new SoundBatch
				{
					Epoch = epoch,
					Sequence = ++nextBatch,
					SentAt = realtimeSinceStartup
				};
				int count = Math.Min(64, pending.Count);
				soundBatch.Cues.AddRange(pending.GetRange(0, count));
				pending.RemoveRange(0, count);
				transport.Send(soundBatch.Encode());
			}
		}

		public void Receive(SoundBatch batch, int epoch, WorldSnapshot snapshot)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			if (!sequence.Accept(batch, epoch, snapshot?.SentAt ?? 0.0, Time.realtimeSinceStartup))
			{
				Dropped += batch.Cues.Count;
				return;
			}
			replaying = true;
			try
			{
				foreach (SoundCue cue in batch.Cues)
				{
					if (!receivers.TryGetValue(cue.Emitter, out var value))
					{
						if (receivers.Count >= 256)
						{
							Dropped++;
							continue;
						}
						value = new Source
						{
							Object = new GameObject("GT_NetworkSound")
						};
						receivers.Add(cue.Emitter, value);
					}
					value.Touched = Time.realtimeSinceStartup;
					value.Object.transform.position = new Vector3(cue.X, cue.Y, 0f);
					foreach (SoundSwitch @switch in cue.Switches)
					{
						AkSoundEngine.SetSwitch(@switch.Group, @switch.Value, value.Object);
						SwitchesApplied++;
					}
					uint num = AkSoundEngine.PostEvent(cue.Event, value.Object);
					Dispatched++;
					if (num == 0 && !DungeonPlugin.Instance.IsSmoke)
					{
						FailedEvents++;
					}
				}
			}
			finally
			{
				replaying = false;
			}
			Maintain();
		}

		public void Maintain()
		{
			expiredReceivers.Clear();
			foreach (KeyValuePair<uint, Source> receiver in receivers)
			{
				if (Time.realtimeSinceStartup - receiver.Value.Touched > 5f)
				{
					expiredReceivers.Add(receiver.Key);
				}
			}
			foreach (uint expiredReceiver in expiredReceivers)
			{
				DestroySource(receivers[expiredReceiver]);
				receivers.Remove(expiredReceiver);
			}
		}

		private static void DestroySource(Source source)
		{
			if (!((Object)(object)source.Object == (Object)null))
			{
				AkSoundEngine.StopAll(source.Object);
				Object.Destroy((Object)(object)source.Object);
			}
		}

		public void Dispose()
		{
			foreach (Source value in receivers.Values)
			{
				DestroySource(value);
			}
			receivers.Clear();
			sources.Clear();
			pending.Clear();
		}
	}
	public static class CharacterCatalog
	{
		public const int GuestCount = 5;

		private static readonly string[] Prefabs = new string[9] { "PlayerCoopCultist", "PlayerRogue", "PlayerMarine", "PlayerConvict", "PlayerGuide", "PlayerRobot", "PlayerBullet", "PlayerGunslinger", "PlayerEevee" };

		private static readonly string[] Identities = new string[9] { "CoopCultist", "Pilot", "Soldier", "Convict", "Guide", "Robot", "Bullet", "Gunslinger", "Eevee" };

		private static readonly string[] Names = new string[9] { "Cultist", "Pilot", "Marine", "Convict", "Hunter", "Robot", "Bullet", "Gunslinger", "Paradox" };

		public static bool Valid(int id)
		{
			if (id >= 0)
			{
				return id < Prefabs.Length;
			}
			return false;
		}

		public static bool ValidGuest(int id)
		{
			if (id >= 0)
			{
				return id < 5;
			}
			return false;
		}

		public static string Prefab(int id)
		{
			if (!Valid(id))
			{
				throw new IOException("Unknown character.");
			}
			return Prefabs[id];
		}

		public static string Name(int id)
		{
			if (!Valid(id))
			{
				return "?";
			}
			return Names[id];
		}

		public static string Identity(int id)
		{
			if (!Valid(id))
			{
				throw new IOException("Unknown character.");
			}
			return Identities[id];
		}

		public static int FromIdentity(string identity)
		{
			for (int i = 0; i < Identities.Length; i++)
			{
				if (Identities[i] == identity)
				{
					return i;
				}
			}
			throw new IOException("Unsupported host character: " + identity);
		}
	}
	public sealed class LobbyChoice
	{
		public int Character;

		public byte[] Encode()
		{
			return Wire.Write(DungeonMessage.Character, delegate(BinaryWriter w)
			{
				w.Write(Character);
			});
		}

		public static LobbyChoice Decode(byte[] bytes)
		{
			return Wire.Read(bytes, DungeonMessage.Character, delegate(BinaryReader r)
			{
				int num = r.ReadInt32();
				if (!CharacterCatalog.ValidGuest(num))
				{
					throw new IOException("Unsupported guest character.");
				}
				return new LobbyChoice
				{
					Character = num
				};
			});
		}
	}
	internal static class ClientSimulationGuard
	{
		[HarmonyPatch(typeof(GenericIntroDoer), "TriggerSequence")]
		private static class ClientBossIntro
		{
			private static bool Prefix()
			{
				return !Mirroring;
			}
		}

		[HarmonyPatch(typeof(PhysicsEngine), "LateUpdate")]
		private static class ClientPhysics
		{
			private static bool Prefix()
			{
				return !Mirroring;
			}
		}

		private static readonly FieldInfo CurrentRoomField = AccessTools.Field(typeof(PlayerController), "m_currentRoom");

		private static readonly FieldInfo CurrentlyVisibleField = AccessTools.Field(typeof(RoomHandler), "m_currentlyVisible");

		private static bool Mirroring => (((Object)(object)DungeonPlugin.Instance == (Object)null) ? null : DungeonPlugin.Instance.Session)?.Mirroring ?? false;

		internal static void UpdatePlaceholderRoom(PlayerController player, RoomHandler room)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || room == null || player.CurrentRoom == room)
			{
				return;
			}
			if ((object)CurrentRoomField == null || (object)CurrentlyVisibleField == null)
			{
				throw new MissingFieldException("The native client room fields are missing from this game build.");
			}
			RoomHandler currentRoom = player.CurrentRoom;
			CurrentRoomField.SetValue(player, room);
			bool flag = false;
			PlayerController[] allPlayers = GameManager.Instance.AllPlayers;
			foreach (PlayerController val in allPlayers)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)player && val.CurrentRoom == currentRoom)
				{
					flag = true;
				}
			}
			if (currentRoom != null && !flag)
			{
				CurrentlyVisibleField.SetValue(currentRoom, false);
				currentRoom.visibility = (VisibilityStatus)1;
			}
			CurrentlyVisibleField.SetValue(room, true);
			room.visibility = (VisibilityStatus)2;
			if ((Object)(object)Pixelator.Instance != (Object)null)
			{
				IntVector2 val2 = Vector3Extensions.IntXY(((BraveBehaviour)player).transform.position, (VectorConversions)0);
				if (currentRoom != null)
				{
					Pixelator.Instance.ProcessOcclusionChange(val2, 0.3f, currentRoom, false);
				}
				Pixelator.Instance.ProcessOcclusionChange(val2, 1f, room, false);
				Pixelator.Instance.ProcessRoomAdditionalExits(val2, room, false);
			}
			ShadowSystem.ForceRoomLightsUpdate(room, 0.1f);
		}

		internal static void MovePlaceholder(PlayerController player, Vector2 position)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			((BraveBehaviour)player).transform.position = new Vector3(position.x, position.y, ((BraveBehaviour)player).transform.position.z);
			if ((Object)(object)((BraveBehaviour)player).specRigidbody != (Object)null)
			{
				((BraveBehaviour)player).specRigidbody.Position = new Position(position);
			}
		}
	}
	internal sealed class DesktopSmoke : MonoBehaviour
	{
		private delegate bool WindowCallback(IntPtr handle, IntPtr parameter);

		private IntPtr window;

		private float nextCheck;

		public bool Verified { get; private set; }

		[DllImport("user32.dll")]
		private static extern int GetWindowLong(IntPtr window, int index);

		[DllImport("user32.dll")]
		private static extern int SetWindowLong(IntPtr window, int index, int value);

		[DllImport("user32.dll")]
		private static extern bool SetWindowPos(IntPtr window, IntPtr after, int x, int y, int width, int height, uint flags);

		[DllImport("user32.dll")]
		private static extern IntPtr GetForegroundWindow();

		[DllImport("user32.dll")]
		private static extern bool EnumWindows(WindowCallback callback, IntPtr parameter);

		[DllImport("user32.dll")]
		private static extern uint GetWindowThreadProcessId(IntPtr handle, out uint process);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		private static extern int GetClassName(IntPtr handle, StringBuilder name, int size);

		[DllImport("kernel32.dll")]
		private static extern uint GetCurrentProcessId();

		private void Awake()
		{
			Application.runInBackground = true;
			Application.targetFrameRate = 60;
			QualitySettings.vSyncCount = 0;
			Screen.SetResolution(800, 600, false);
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
			ConfigureWindow();
		}

		private void ConfigureWindow()
		{
			uint current = GetCurrentProcessId();
			EnumWindows(delegate(IntPtr handle, IntPtr parameter)
			{
				GetWindowThreadProcessId(handle, out var process);
				if (process != current)
				{
					return true;
				}
				StringBuilder stringBuilder = new StringBuilder(128);
				GetClassName(handle, stringBuilder, stringBuilder.Capacity);
				if (stringBuilder.ToString() != "UnityWndClass")
				{
					return true;
				}
				window = handle;
				return false;
			}, IntPtr.Zero);
			if (!(window == IntPtr.Zero))
			{
				SetWindowLong(window, -20, GetWindowLong(window, -20) | 0x8000000);
				SetWindowPos(window, new IntPtr(1), 20, 20, 0, 0, 81u);
			}
		}

		private void LateUpdate()
		{
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			AudioListener.pause = true;
			AudioListener.volume = 0f;
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
			if (!(Time.realtimeSinceStartup < nextCheck))
			{
				nextCheck = Time.realtimeSinceStartup + 1f;
				if (GameManager.HasInstance)
				{
					AccessTools.Field(typeof(GameManager), "m_applicationHasFocus").SetValue(GameManager.Instance, true);
				}
				if (window == IntPtr.Zero)
				{
					ConfigureWindow();
				}
				if (Screen.fullScreen || Screen.width > 820 || Screen.height > 620)
				{
					Screen.SetResolution(800, 600, false);
				}
				Verified = window != IntPtr.Zero && !Screen.fullScreen && Screen.width <= 820 && Screen.height <= 620 && (int)Cursor.lockState == 0 && GetForegroundWindow() != window;
			}
		}
	}
	[BepInPlugin("com.gungeontogether.lan", "Gungeon Together LAN Alpha", "0.3.7")]
	[BepInIncompatibility("com.llamerrr.gungeontogether")]
	public sealed class DungeonPlugin : BaseUnityPlugin
	{
		private bool hostTab = true;

		private bool showNetworkDetails;

		private GUIStyle menuTitle;

		private GUIStyle menuText;

		private GUIStyle menuSmall;

		private GUIStyle menuButton;

		private GUIStyle menuField;

		private GUIStyle menuValue;

		private static readonly Color Ink = new Color(0.075f, 0.068f, 0.068f, 0.98f);

		private static readonly Color Panel = new Color(0.13f, 0.115f, 0.105f, 1f);

		private static readonly Color Border = new Color(0.38f, 0.32f, 0.23f, 1f);

		private static readonly Color Gold = new Color(0.79f, 0.66f, 0.39f, 1f);

		private static readonly Color Paper = new Color(0.91f, 0.87f, 0.76f, 1f);

		private static readonly Color Muted = new Color(0.61f, 0.59f, 0.53f, 1f);

		public static DungeonPlugin Instance;

		private LanTransport transport;

		private Harmony harmony;

		private string ip;

		private string bind;

		private string port;

		private string code;

		private string fingerprint;

		private string error = "";

		private Rect window = new Rect(36f, 36f, 620f, 586f);

		private bool visible = true;

		private readonly bool presentationProbe = Array.IndexOf(Environment.GetCommandLineArgs(), "--gt-presentation-probe") >= 0;

		private bool clientMovementPrediction;

		private RemotePlayerAvatar foyerAvatar;

		private float nextPosition;

		private uint foyerSequence;

		private int guestChoice;

		private PlayerController menuPlayer;

		private string smokeRole;

		private bool profileCheck;

		private bool profilePathChecked;

		private string profileObservedPath;

		private int smokePort = 27639;

		private float smokeDeadline;

		private float smokeStarted;

		private float nextSmokeDiagnostic;

		private float nextControlsDiagnostic;

		private bool smokeRunStarted;

		private bool smokeLayout;

		private bool smokeSnapshot;

		private bool smokeMovePassed;

		private bool smokeShotPassed;

		private bool smokeCaptureRequested;

		private AIActor smokeEnemy;

		private float smokeEnemyHealth;

		private bool smokeEnemyCreated;

		private bool smokeDamagePassed;

		private bool smokeSecondFloor;

		private float smokeSecondFloorAt;

		private bool smokeComplete;

		private Vector3 smokeCultistStart;

		private int smokeStartAmmo;

		private DesktopSmoke desktopSmoke;

		private float smokeLobbyAt;

		private bool smokeFoyerPassed;

		private bool smokeDesktopPassed;

		private bool smokeMenuRequested;

		private bool smokeMenuVisible;

		public DungeonSession Session { get; private set; }

		public bool IsSmoke
		{
			get
			{
				if (smokeRole == null)
				{
					return profileCheck;
				}
				return true;
			}
		}

		public bool UsesSeparateProfile { get; private set; }

		private void InitMenuStyles()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			if (menuTitle == null)
			{
				menuTitle = TextStyle(25, Gold, (TextAnchor)3);
				menuText = TextStyle(16, Paper, (TextAnchor)0);
				menuSmall = TextStyle(13, Muted, (TextAnchor)0);
				menuValue = TextStyle(19, Paper, (TextAnchor)4);
				menuButton = TextStyle(16, Paper, (TextAnchor)4);
				menuField = new GUIStyle(GUI.skin.textField);
				menuField.fontSize = 18;
				menuField.padding = new RectOffset(10, 8, 7, 5);
				menuField.normal.textColor = Paper;
				menuField.focused.textColor = Paper;
				menuField.normal.background = Texture2D.whiteTexture;
				menuField.focused.background = Texture2D.whiteTexture;
			}
		}

		private static GUIStyle TextStyle(int size, Color color, TextAnchor anchor)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_0040: Expected O, but got Unknown
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = size
			};
			val.normal.textColor = color;
			val.alignment = anchor;
			val.wordWrap = true;
			val.padding = new RectOffset(0, 0, 0, 0);
			return val;
		}

		private static void Fill(Rect rect, Color color)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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)
			Color color2 = GUI.color;
			GUI.color = color;
			GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color2;
		}

		private static void Frame(Rect rect, Color fill, Color edge)
		{
			//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_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			Fill(rect, edge);
			Fill(new Rect(((Rect)(ref rect)).x + 2f, ((Rect)(ref rect)).y + 2f, ((Rect)(ref rect)).width - 4f, ((Rect)(ref rect)).height - 4f), fill);
		}

		private bool MenuButton(Rect rect, string text, bool enabled, bool accent)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: 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_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			bool num = enabled && ((Rect)(ref rect)).Contains(Event.current.mousePosition);
			Color val = (accent ? Gold : Border);
			Color fill = (Color)(num ? new Color(0.25f, 0.215f, 0.155f) : Panel);
			Frame(rect, fill, (Color)(enabled ? val : new Color(0.22f, 0.205f, 0.18f)));
			GUIStyle val2 = menuButton;
			Color textColor = val2.normal.textColor;
			val2.normal.textColor = ((!enabled) ? Muted : (accent ? Gold : Paper));
			GUIStyleState hover = val2.hover;
			GUIStyleState active = val2.active;
			Color val3 = (val2.focused.textColor = val2.normal.textColor);
			Color textColor3 = (active.textColor = val3);
			hover.textColor = textColor3;
			bool enabled2 = GUI.enabled;
			GUI.enabled = enabled;
			bool result = GUI.Button(rect, "", val2);
			GUI.enabled = enabled2;
			GUI.Label(rect, text, val2);
			val2.normal.textColor = textColor;
			return result;
		}

		private string MenuField(Rect rect, string value, int length, bool enabled)
		{
			//IL_0000: 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_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: 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)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			Frame(rect, new Color(0.055f, 0.05f, 0.05f), Border);
			Color backgroundColor = GUI.backgroundColor;
			GUI.backgroundColor = new Color(0.055f, 0.05f, 0.05f);
			bool enabled2 = GUI.enabled;
			GUI.enabled = enabled;
			string result = GUI.TextField(new Rect(((Rect)(ref rect)).x + 2f, ((Rect)(ref rect)).y + 2f, ((Rect)(ref rect)).width - 4f, ((Rect)(ref rect)).height - 4f), value, length, menuField);
			GUI.enabled = enabled2;
			GUI.backgroundColor = backgroundColor;
			return result;
		}

		private void OnGUI()
		{
			//IL_0164: 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_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Expected O, but got Unknown
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			if (transport == null)
			{
				return;
			}
			if (Session != null && Session.Mirroring && InventoryBook.BookOpen)
			{
				InventoryBook.DrawCaption(Session);
			}
			if (IsSmoke && !smokeMenuVisible)
			{
				return;
			}
			InitMenuStyles();
			if (!visible)
			{
				return;
			}
			Matrix4x4 matrix = GUI.matrix;
			float num = Mathf.Min(1f, Mathf.Min((float)Screen.width / 692f, (float)Screen.height / 642f));
			num = Mathf.Max(0.4f, num);
			GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(num, num, 1f));
			try
			{
				float num2 = (float)Screen.width / num;
				float num3 = (float)Screen.height / num;
				((Rect)(ref window)).x = Mathf.Clamp(((Rect)(ref window)).x, 8f, Mathf.Max(8f, num2 - ((Rect)(ref window)).width - 8f));
				((Rect)(ref window)).y = Mathf.Clamp(((Rect)(ref window)).y, 8f, Mathf.Max(8f, num3 - ((Rect)(ref window)).height - 8f));
				window = GUI.Window(27631, window, new WindowFunction(DrawMenu), "", GUIStyle.none);
			}
			finally
			{
				GUI.matrix = matrix;
			}
		}

		private void DrawMenu(int id)
		{
			//IL_0014: 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_004b: 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_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: 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_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: 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_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: 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_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0383: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0421: Unknown result type (might be due to invalid IL or missing references)
			//IL_0426: Unknown result type (might be due to invalid IL or missing references)
			//IL_042b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0449: Unknown result type (might be due to invalid IL or missing references)
			//IL_08ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0910: Unknown result type (might be due to invalid IL or missing references)
			//IL_0963: Unknown result type (might be due to invalid IL or missing references)
			//IL_099a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0507: Unknown result type (might be due to invalid IL or missing references)
			//IL_09c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0535: Unknown result type (might be due to invalid IL or missing references)
			//IL_0560: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_059c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0674: Unknown result type (might be due to invalid IL or missing references)
			Fill(new Rect(6f, 6f, 614f, 580f), new Color(0f, 0f, 0f, 0.5f));
			Frame(new Rect(0f, 0f, 614f, 580f), Ink, Border);
			Fill(new Rect(6f, 6f, 602f, 2f), Gold);
			Fill(new Rect(26f, 26f, 12f, 23f), Gold);
			Fill(new Rect(28f, 21f, 8f, 5f), Gold);
			Fill(new Rect(24f, 50f, 16f, 3f), Border);
			GUI.Label(new Rect(55f, 18f, 480f, 39f), "GUNGEON TOGETHER", menuTitle);
			GUI.Label(new Rect(55f, 59f, 490f, 23f), "NETWORK CO-OP   /   LAN 0.3.7 · ALPHA", menuSmall);
			if (MenuButton(new Rect(550f, 23f, 36f, 32f), "×", enabled: true, accent: false))
			{
				visible = false;
			}
			if (!UsesSeparateProfile)
			{
				GUI.Label(new Rect(28f, 113f, 558f, 130f), "Normal saves are active.\n\nFor co-op, use Start Modded in a separate mod profile or launch with the LAN launcher.", menuText);
				GUI.Label(new Rect(28f, 480f, 558f, 44f), "Close menu: Esc or Ctrl+P", menuSmall);
				GUI.DragWindow(new Rect(0f, 0f, 540f, 82f));
				DrawMenuCursor();
				return;
			}
			if (transport.Active)
			{
				hostTab = transport.IsHost;
			}
			if (MenuButton(new Rect(28f, 99f, 272f, 37f), "HOST", !transport.Active, hostTab))
			{
				hostTab = true;
			}
			if (MenuButton(new Rect(314f, 99f, 272f, 37f), "JOIN", !transport.Active, !hostTab))
			{
				hostTab = false;
			}
			GUI.Label(new Rect(28f, 152f, 558f, 23f), hostTab ? "Listen address" : "Host Radmin / LAN IPv4", menuSmall);
			if (hostTab)
			{
				bind = MenuField(new Rect(28f, 177f, 558f, 38f), bind, 64, !transport.Active);
			}
			else
			{
				ip = MenuField(new Rect(28f, 177f, 558f, 38f), ip, 64, !transport.Active);
			}
			GUI.Label(new Rect(28f, 225f, 105f, 20f), "TCP port", menuSmall);
			GUI.Label(new Rect(150f, 225f, 436f, 20f), "Room code - use the same code on both PCs", menuSmall);
			port = MenuField(new Rect(28f, 250f, 105f, 38f), port, 5, !transport.Active);
			code = MenuField(new Rect(150f, 250f, 436f, 38f), code, 64, !transport.Active);
			Frame(new Rect(28f, 306f, 558f, 88f), Panel, Border);
			GUI.Label(new Rect(44f, 317f, 525f, 20f), "PLAYER TWO · RUN CHARACTER", menuSmall);
			bool enabled = !hostTab && (Session == null || !Session.InRun);
			int id2 = ((hostTab && Session != null) ? Session.GuestCharacter : guestChoice);
			if (hostTab && (Session == null || !Session.GuestChoiceReceived))
			{
				GUI.Label(new Rect(44f, 348f, 525f, 32f), "Your friend chooses a hero on their PC", menuText);
			}
			else
			{
				if (MenuButton(new Rect(44f, 344f, 42f, 34f), "<", enabled, accent: false))
				{
					ChangeGuest(-1);
				}
				GUI.Label(new Rect(98f, 343f, 391f, 35f), CharacterCatalog.Name(id2), menuValue);
				if (MenuButton(new Rect(528f, 344f, 42f, 34f), ">", enabled, accent: false))
				{
					ChangeGuest(1);
				}
			}
			if (!transport.Active)
			{
				if (MenuButton(new Rect(28f, 410f, 558f, 43f), hostTab ? "CREATE ROOM" : "JOIN BY IP", enabled: true, accent: true))
				{
					Connect(hostTab);
				}
			}
			else
			{
				bool enabled2 = transport.IsHost && transport.Connected && Session != null && Session.GuestChoiceReceived && !Session.InRun;
				string text = ((!transport.Connected) ? "WAITING FOR CONNECTION…" : ((Session != null && Session.InRun) ? "RUN STARTED" : (transport.IsHost ? "START RUN" : "WAITING FOR HOST TO START…")));
				if (MenuButton(new Rect(28f, 410f, 558f, 43f), text, enabled2, accent: true))
				{
					try
					{
						ClearAvatar();
						Session.StartHost();
						visible = false;
						error = "";
					}
					catch (Exception ex)
					{
						error = ex.Message;
						Log(error);
					}
				}
			}
			string text2 = "Connected · RTT " + transport.RoundTripMilliseconds.ToString("0") + " ms · smoothing " + ((Session == null) ? 40.0 : Session.SmoothingMilliseconds).ToString("0") + " ms";
			if (Session != null && Session.Mirroring)
			{
				text2 = text2 + " · response " + Session.InputLatencyMs.ToString("0") + " ms";
			}
			string text3 = ((error.Length != 0) ? error : (transport.Connected ? text2 : (transport.Active ? "Check the address and room code on both PCs." : (hostTab ? "Share your Radmin / LAN IPv4 and room code with your friend." : "Both players must be on the same LAN or Radmin network."))));
			if (error.Length == 0 && Session != null && Session.InRun && Session.Phase != DungeonSession.RunPhase.Running)
			{
				text3 = Session.Status;
			}
			if (showNetworkDetails)
			{
				text3 = (transport.Connected ? (text2 + " · queue " + transport.PendingBytes / 1024 + " KiB\n") : (transport.Status + "\n")) + ((Session == null) ? "" : Session.Status) + ((error.Length == 0) ? "" : ("\n" + error));
			}
			if (showNetworkDetails && Session != null && Session.MovementBlockReason.Length > 0)
			{
				text3 = text3 + "\nPlayer two controls: " + Session.MovementBlockReason;
			}
			GUI.Label(new Rect(28f, 465f, 558f, 54f), text3, menuSmall);
			if (MenuButton(new Rect(28f, 534f, 248f, 30f), "DISCONNECT / FOYER", transport.Active || (Session != null && Session.InRun), accent: false))
			{
				ReturnToFoyer();
			}
			if (MenuButton(new Rect(290f, 534f, 142f, 30f), "DETAILS", enabled: true, accent: false))
			{
				showNetworkDetails = !showNetworkDetails;
			}
			if (MenuButton(new Rect(446f, 534f, 140f, 30f), "CLOSE · Esc", enabled: true, accent: false))
			{
				visible = false;
			}
			GUI.DragWindow(new Rect(0f, 0f, 540f, 82f));
			DrawMenuCursor();
		}

		private void DrawMenuCursor()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//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_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			if ((int)Event.current.type == 7)
			{
				Vector2 mousePosition = Event.current.mousePosition;
				Rect val = new Rect(0f, 0f, 614f, 580f);
				if (((Rect)(ref val)).Contains(mousePosition))
				{
					float num = Mathf.Round(mousePosition.x);
					float num2 = Mathf.Round(mousePosition.y);
					Color color = default(Color);
					((Color)(ref color))..ctor(0.025f, 0.02f, 0.015f);
					Fill(new Rect(num - 7f, num2 - 2f, 15f, 5f), color);
					Fill(new Rect(num - 2f, num2 - 7f, 5f, 15f), color);
					Fill(new Rect(num - 6f, num2 - 1f, 13f, 3f), Paper);
					Fill(new Rect(num - 1f, num2 - 6f, 3f, 13f), Paper);
					Fill(new Rect(num - 1f, num2 - 1f, 3f, 3f), Gold);
				}
			}
		}

		private void ChangeGuest(int direction)
		{
			guestChoice = (guestChoice + direction + 5) % 5;
			((BaseUnityPlugin)this).Config.Bind<int>("LAN", "GuestCharacter", 0, "Guest run character: 0 Cultist, 1 Pilot, 2 Marine, 3 Convict, 4 Hunter.").Value = guestChoice;
			if (Session != null)
			{
				Session.ChooseGuest(guestChoice);
			}
		}

		private void Awake()
		{
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Expected O, but got Unknown
			Instance = this;
			clientMovementPrediction = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "ClientMovementPrediction", true, "Bounded visual prediction for normal guest walking. Disable to retain the fast authoritative player stream without speculative movement.").Value;
			Application.runInBackground = true;
			ProfilePolicy.SaveProfileMode value = ((BaseUnityPlugin)this).Config.Bind<ProfilePolicy.SaveProfileMode>("Profile", "SaveProfileMode", ProfilePolicy.SaveProfileMode.Auto, "Auto: manager profile uses separate LAN saves; direct Steam launch keeps normal saves. Normal: LAN disabled. LAN: always use LAN saves.").Value;
			UsesSeparateProfile = ProfilePolicy.UsesSeparateProfile(Environment.GetCommandLineArgs(), Paths.GameRootPath, Paths.BepInExRootPath, value);
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			foreach (string obj in commandLineArgs)
			{
				if (obj == "--gt-dungeon-smoke-host")
				{
					smokeRole = "host";
				}
				if (obj == "--gt-dungeon-smoke-client")
				{
					smokeRole = "client";
				}
				if (obj == "--gt-profile-check")
				{
					profileCheck = true;
				}
			}
			visible = UsesSeparateProfile && !profileCheck;
			if (UsesSeparateProfile && !IsSmoke)
			{
				string text = Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/dungeon-profile");
				try
				{
					ProfileDisplayPolicy.Initialize(Application.persistentDataPath, text, Log);
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("LAN display settings could not be initialized: " + ex.Message));
				}
				Directory.CreateDirectory(text);
				SaveManager.SavePath = text;
				SaveManager.OldSavePath = text;
			}
			if (IsSmoke)
			{
				GameManager.AUDIO_ENABLED = false;
				AudioListener.pause = true;
				AudioListener.volume = 0f;
				string text2 = Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/dungeon-profile");
				Directory.CreateDirectory(text2);
				SaveManager.SavePath = text2;
				SaveManager.OldSavePath = text2;
				desktopSmoke = ((Component)this).gameObject.AddComponent<DesktopSmoke>();
			}
			if (smokeRole != null)
			{
				Foyer.DoIntroSequence = false;
				Foyer.DoMainMenu = false;
				visible = false;
				smokeDeadline = Time.realtimeSinceStartup + 150f;
			}
			harmony = new Harmony("com.gungeontogether.lan.dungeon-0.3.7");
			try
			{
				harmony.PatchAll(typeof(DungeonPlugin).Assembly);
			}
			catch (Exception ex2)
			{
				if (IsSmoke)
				{
					Log("GT_DUNGEON_SMOKE_FAIL: patch initialization: " + ex2);
					Application.Quit();
				}
				throw;
			}
			ip = ((BaseUnityPlugin)this).Config.Bind<string>("LAN", "HostIP", "127.0.0.1", "Host's Radmin IPv4.").Value;
			bind = ((BaseUnityPlugin)this).Config.Bind<string>("LAN", "BindIP", "0.0.0.0", "Host's own Radmin IPv4 or all interfaces.").Value;
			port = ((BaseUnityPlugin)this).Config.Bind<int>("LAN", "Port", 27631, "TCP port.").Value.ToString();
			guestChoice = ((BaseUnityPlugin)this).Config.Bind<int>("LAN", "GuestCharacter", 0, "Guest run character: 0 Cultist, 1 Pilot, 2 Marine, 3 Convict, 4 Hunter.").Value;
			if (!CharacterCatalog.ValidGuest(guestChoice))
			{
				guestChoice = 0;
			}
			commandLineArgs = Environment.GetCommandLineArgs();
			foreach (string text3 in commandLineArgs)
			{
				if (smokeRole != null && text3.StartsWith("--gt-smoke-character=", StringComparison.Ordinal) && int.TryParse(text3.Substring("--gt-smoke-character=".Length), out var result) && CharacterCatalog.ValidGuest(result))
				{
					guestChoice = result;
				}
				if (smokeRole != null && text3.StartsWith("--gt-smoke-port=", StringComparison.Ordinal) && int.TryParse(text3.Substring("--gt-smoke-port=".Length), out var result2) && result2 >= 1024 && result2 <= 65535)
				{
					smokePort = result2;
				}
			}
			code = Guid.NewGuid().ToString("N").Substring(0, 8);
			fingerprint = GameFingerprint();
			transport = new LanTransport(() => Time.realtimeSinceStartup);
			transport.PacketReceived += Receive;
			transport.ConnectedEvent += delegate
			{
				ClearAvatar();
				foyerSequence = 0u;
				if (Session != null)
				{
					Session.Dispose();
				}
				Session = new DungeonSession(transport, ((Component)this).gameObject, Log);
				Session.EnableMovementPrediction = clientMovementPrediction;
				if (!transport.IsHost)
				{
					Session.ChooseGuest(guestChoice);
				}
				Log("GT_DUNGEON_CONNECTED");
			};
			transport.DisconnectedEvent += delegate
			{
				ClearAvatar();
				if (Session != null)
				{
					Session.Disconnected();
				}
				Log(transport.Status);
			};
			Log("GT_DUNGEON_READY 0.3.7; buffered rendering, character selection; game=" + fingerprint);
			Log("GT_PROFILE_MODE " + (UsesSeparateProfile ? "LAN" : "NORMAL"));
			RuntimeDisplayRestore.Attach(((Component)this).gameObject, Log);
		}

		private static string GameFingerprint()
		{
			string path = Path.Combine(Paths.GameRootPath, "EtG_Data/Managed");
			using MemoryStream memoryStream = new MemoryStream();
			using SHA256 sHA = SHA256.Create();
			string[] array = new string[3] { "Assembly-CSharp.dll", "Assembly-CSharp-firstpass.dll", "UnityEngine.CoreModule.dll" };
			foreach (string path2 in array)
			{
				using FileStream inputStream = File.OpenRead(Path.Combine(path, path2));
				byte[] array2 = sHA.ComputeHash(inputStream);
				memoryStream.Write(array2, 0, array2.Length);
			}
			return BitConverter.ToString(sHA.ComputeHash(memoryStream.ToArray())).Replace("-", "").ToLowerInvariant();
		}

		private void Update()
		{
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			if (transport == null)
			{
				return;
			}
			if (Input.GetKeyDown((KeyCode)112) && (Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)))
			{
				visible = !visible;
			}
			if (visible && Input.GetKeyDown((KeyCode)27))
			{
				visible = false;
			}
			UpdateMenuInput();
			transport.Poll();
			if (Session != null)
			{
				Session.MuteClientInput = visible;
				Session.Update();
			}
			transport.FlushPending();
			if (Session != null && Session.Phase == DungeonSession.RunPhase.Fault && smokeRole == null)
			{
				visible = true;
			}
			if (profileCheck)
			{
				TickProfileCheck();
				return;
			}
			if (smokeRole != null)
			{
				TickSmoke();
			}
			if (Session != null && Session.InRun)
			{
				return;
			}
			if (transport.Connected && Time.realtimeSinceStartup >= nextPosition)
			{
				nextPosition = Time.realtimeSinceStartup + 0.05f;
				GameManager val = Object.FindObjectOfType<GameManager>();
				if ((Object)(object)val != (Object)null && val.IsFoyer && (Object)(object)val.PrimaryPlayer != (Object)null && (Object)(object)((BraveBehaviour)val.PrimaryPlayer).sprite != (Object)null)
				{
					PlayerController primaryPlayer = val.PrimaryPlayer;
					Vector3 position = ((BraveBehaviour)((BraveBehaviour)primaryPlayer).sprite).transform.position;
					Vector3 lossyScale = ((BraveBehaviour)((BraveBehaviour)primaryPlayer).sprite).transform.lossyScale;
					transport.SendLatest(new LanPlayerState
					{
						InFoyer = true,
						Sequence = ++foyerSequence,
						SentAt = Time.realtimeSinceStartup,
						X = position.x,
						Y = position.y,
						Z = position.z,
						Height = ((BraveBehaviour)primaryPlayer).sprite.HeightOffGround,
						ScaleX = lossyScale.x * ((BraveBehaviour)primaryPlayer).sprite.scale.x,
						ScaleY = lossyScale.y * ((BraveBehaviour)primaryPlayer).sprite.scale.y,
						Rotation = ((BraveBehaviour)((BraveBehaviour)primaryPlayer).sprite).transform.eulerAngles.z,
						SpriteId = ((BraveBehaviour)primaryPlayer).sprite.spriteId,
						FlipX = ((BraveBehaviour)primaryPlayer).sprite.FlipX,
						Collection = ((Object)((BraveBehaviour)primaryPlayer).sprite.Collection).name,
						CollectionGuid = (((BraveBehaviour)primaryPlayer).sprite.Collection.spriteCollectionGUID ?? ""),
						Character = CharacterCatalog.FromIdentity(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref primaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString())
					}.Encode());
				}
			}
			transport.FlushPending();
		}

		private void Receive(byte[] bytes)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			if (bytes.Length == 0)
			{
				return;
			}
			if (bytes[0] == 1)
			{
				if (Session != null && Session.InRun)
				{
					return;
				}
				LanPlayerState lanPlayerState = LanPlayerState.Decode(bytes);
				GameManager val = Object.FindObjectOfType<GameManager>();
				if (lanPlayerState.InFoyer && !((Object)(object)val == (Object)null) && val.IsFoyer && !((Object)(object)val.PrimaryPlayer == (Object)null))
				{
					if ((Object)(object)foyerAvatar == (Object)null)
					{
						foyerAvatar = RemotePlayerAvatar.Create((ulong)(transport.IsHost ? 2 : 1), new Vector2(lanPlayerState.X, lanPlayerState.Y), lanPlayerState.Rotation);
					}
					foyerAvatar.Apply(lanPlayerState);
				}
			}
			else if (Session != null)
			{
				if (bytes[0] == 3)
				{
					ClearAvatar();
					visible = false;
				}
				Session.Receive(bytes);
			}
		}

		private void ClearAvatar()
		{
			if ((Object)(object)foyerAvatar != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)foyerAvatar).gameObject);
			}
			foyerAvatar = null;
		}

		private void LateUpdate()
		{
			if (Session != null)
			{
				Session.LateUpdate();
			}
		}

		private void UpdateMenuInput()
		{
			PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null);
			if ((Object)(object)menuPlayer != (Object)null && ((Object)(object)menuPlayer != (Object)(object)val || !visible || !UsesSeparateProfile))
			{
				menuPlayer.ClearInputOverride("GT_Menu");
			}
			menuPlayer = val;
			if ((Object)(object)menuPlayer != (Object)null && visible && UsesSeparateProfile && !IsSmoke)
			{
				menuPlayer.SetInputOverride("GT_Menu");
			}
		}

		private void Connect(bool host)
		{
			try
			{
				if (!UsesSeparateProfile)
				{
					throw new IOException("Start LAN using the updated GungeonTogether-LAN.exe; normal save profile is active.");
				}
				if (!int.TryParse(port, out var result) || result < 1 || result > 65535)
				{
					throw new IOException("Invalid TCP port.");
				}
				GameManager val = Object.FindObjectOfType<GameManager>();
				if ((Object)(object)val == (Object)null || !val.IsFoyer || (Object)(object)val.PrimaryPlayer == (Object)null)
				{
					throw new IOException("Choose a character in the foyer before connecting.");
				}
				if (host)
				{
					transport.Host(bind.Trim(), result, code, fingerprint);
				}
				else
				{
					transport.Join(ip.Trim(), result, code, fingerprint);
				}
				error = "";
			}
			catch (Exception ex)
			{
				error = ex.Message;
			}
		}

		private void ReturnToFoyer()
		{
			bool num = Session != null && Session.InRun;
			if (Session != null)
			{
				Session.Dispose();
				Session = null;
			}
			transport.Dispose();
			ClearAvatar();
			error = "";
			if (num && GameManager.HasInstance)
			{
				GameManager.Instance.LoadCharacterSelect(false, true);
			}
		}

		private void Log(string text)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			if (smokeRole != null)
			{
				File.AppendAllText(Path.Combine(Paths.GameRootPath, "dungeon-smoke-" + smokeRole + ".txt"), text + "\n");
			}
		}

		internal void ProfileInitialized()
		{
			if (profileCheck && !profilePathChecked)
			{
				profilePathChecked = true;
				profileObservedPath = Path.GetFullPath(SaveManager.SavePath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				string path = (UsesSeparateProfile ? Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/dungeon-profile") : Application.persistentDataPath);
				path = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
				bool flag = string.Equals(profileObservedPath, path, StringComparison.OrdinalIgnoreCase);
				string text = Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/profile-check-fixture");
				Directory.CreateDirectory(text);
				SaveManager.SavePath = text;
				SaveManager.OldSavePath = text;
				string text2 = "GT_PROFILE_PATH_" + (flag ? "PASS" : "FAIL") + " mode=" + (UsesSeparateProfile ? "LAN" : "NORMAL");
				File.WriteAllText(Path.Combine(Paths.GameRootPath, "profile-check-result.txt"), text2 + "\n");
				((BaseUnityPlugin)this).Logger.LogInfo((object)text2);
			}
		}

		private void TickProfileCheck()
		{
			if (profilePathChecked && GameManager.HasInstance && !GameManager.Instance.IsLoadingLevel)
			{
				string path = Path.Combine(Paths.GameRootPath, "profile-check-result.txt");
				if (File.ReadAllText(path).IndexOf("GT_PROFILE_GAME_READY", StringComparison.Ordinal) < 0)
				{
					GameStatsManager val = default(GameStatsManager);
					bool flag = SaveManager.Load<GameStatsManager>(SaveManager.GameSave, ref val, true, 0u, (Func<string, uint, string>)null, (SaveSlot?)null);
					File.AppendAllText(path, "GT_PROFILE_SAVE_LOAD_" + ((flag && val != null) ? "PASS" : "FAIL") + "\n");
					File.AppendAllText(path, "GT_PROFILE_GAME_READY\n");
					((BaseUnityPlugin)this).Logger.LogInfo((object)"GT_PROFILE_GAME_READY");
				}
			}
		}

		private void TickSmoke()
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0615: Unknown result type (might be due to invalid IL or missing references)
			//IL_061a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0416: Unknown result type (might be due to invalid IL or missing references)
			//IL_041b: Unknown result type (might be due to invalid IL or missing references)
			//IL_08ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_08d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_07c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_07cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_07d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_07d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_07db: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a1f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a24: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a41: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a46: Unknown result type (might be due to invalid IL or missing references)
			if (smokeComplete)
			{
				return;
			}
			try
			{
				if (Time.realtimeSinceStartup > smokeDeadline)
				{
					throw new IOException("Smoke timed out: " + ((Session == null) ? transport.Status : Session.Status));
				}
				GameManager val = Object.FindObjectOfType<GameManager>();
				if ((Object)(object)val == (Object)null || (Object)(object)val.Dungeon == (Object)null || Dungeon.IsGenerating || val.IsLoadingLevel)
				{
					return;
				}
				if ((Object)(object)desktopSmoke != (Object)null && desktopSmoke.Verified && !smokeDesktopPassed)
				{
					smokeDesktopPassed = true;
					Log("GT_DESKTOP_SAFE_PASS fullscreen=" + Screen.fullScreen + " size=" + Screen.width + "x" + Screen.height + " cursor=" + ((object)Cursor.lockState/*cast due to .constrained prefix*/).ToString() + " background=" + Application.runInBackground);
				}
				if (Time.realtimeSinceStartup >= nextSmokeDiagnostic)
				{
					nextSmokeDiagnostic = Time.realtimeSinceStartup + 5f;
					Log("GT_SMOKE_STATE foyer=" + val.IsFoyer + " loading=" + val.IsLoadingLevel + " generating=" + Dungeon.IsGenerating + " player=" + ((Object)(object)val.PrimaryPlayer != (Object)null) + " scene=" + ((val.GetLastLoadedLevelDefinition() == null) ? "null" : val.GetLastLoadedLevelDefinition().dungeonSceneName));
				}
				if (!InputManager.IsSetup)
				{
					typeof(InputManager).GetMethod("SetupInternal", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
				}
				if (val.IsFoyer && (Object)(object)val.PrimaryPlayer == (Object)null)
				{
					GameObject val2 = (GameManager.PlayerPrefabForNewGame = NativeWorld.CharacterPrefab((smokeRole == "host") ? 1 : 4));
					RoomHandler val4 = val.Dungeon.data.rooms[0];
					Vector3 val5 = default(Vector3);
					((Vector3)(ref val5))..ctor((float)val4.area.basePosition.x + (float)val4.area.dimensions.x / 2f, (float)val4.area.basePosition.y + (float)val4.area.dimensions.y / 2f, 0f);
					val.PrimaryPlayer = Object.Instantiate<GameObject>(val2, val5, Quaternion.identity).GetComponent<PlayerController>();
					val.PrimaryPlayer.PlayerIDX = 0;
					val.PrimaryPlayer.ForceChangeRoom(val4);
				}
				if (!transport.Active && Session == null)
				{
					if (smokeRole == "host")
					{
						transport.Host("127.0.0.1", smokePort, "dungeon-smoke", fingerprint);
					}
					else
					{
						transport.Join("127.0.0.1", smokePort, "dungeon-smoke", fingerprint);
					}
					Log("GT_DUNGEON_SMOKE_CONNECTING");
				}
				if (transport.Connected && val.IsFoyer && Session != null && !Session.InRun)
				{
					if (smokeLobbyAt == 0f)
					{
						smokeLobbyAt = Time.realtimeSinceStartup;
					}
					float num = Time.realtimeSinceStartup - smokeLobbyAt;
					val.PrimaryPlayer.usingForcedInput = num > 0.3f;
					val.PrimaryPlayer.forcedInput = new Vector2((float)((Mathf.Sin(num * 4f) >= 0f) ? 1 : (-1)), 0f);
					if (num > 1.7f && !smokeFoyerPassed)
					{
						if ((Object)(object)foyerAvatar != (Object)null && foyerAvatar.RenderedFrames >= 10 && foyerAvatar.DistanceMoved >= 0.2f)
						{
							smokeFoyerPassed = true;
							Log("GT_FOYER_INTERPOLATION_PASS frames=" + foyerAvatar.RenderedFrames + " distance=" + foyerAvatar.DistanceMoved + " collection=" + foyerAvatar.CollectionName);
						}
						else if (num > 15f)
						{
							throw new IOException("Foyer interpolation did not render remote movement.");
						}
					}
					if (num > 1.8f && !smokeMenuRequested)
					{
						smokeMenuRequested = true;
						((MonoBehaviour)this).StartCoroutine(CaptureSmokeMenu());
					}
				}
				if (smokeRole == "host" && transport.Connected && Session != null && Session.GuestChoiceReceived && smokeFoyerPassed && Time.realtimeSinceStartup - smokeLobbyAt > 3f && !smokeRunStarted)
				{
					val.PrimaryPlayer.usingForcedInput = false;
					smokeRunStarted = true;
					Session.StartHost();
					Log("GT_DUNGEON_SMOKE_RUN_STARTED");
				}
				if (Session != null && Session.Phase == DungeonSession.RunPhase.Fault)
				{
					throw new IOException(Session.Status);
				}
				if (Session != null && Session.Phase == DungeonSession.RunPhase.Running && !smokeLayout)
				{
					smokeLayout = true;
					smokeStarted = Time.realtimeSinceStartup;
					Log("GT_DUNGEON_SMOKE_LAYOUT_PASS");
					if (smokeRole == "host")
					{
						smokeCultistStart = ((BraveBehaviour)val.SecondaryPlayer).transform.position;
						smokeStartAmmo = ((!((Object)(object)((GameActor)val.SecondaryPlayer).CurrentGun == (Object)null)) ? ((GameActor)val.SecondaryPlayer).CurrentGun.CurrentAmmo : 0);
					}
					VerifySmokeCharacters(val);
				}
				if (smokeRole == "client" && Session != null && Session.Snapshot != null && !smokeSnapshot)
				{
					smokeSnapshot = true;
					Log("GT_DUNGEON_SMOKE_SNAPSHOT_PASS sprites=" + Session.Snapshot.Sprites.Count);
				}
				if (smokeLayout && smokeRole == "client")
				{
					float num2 = Time.realtimeSinceStartup - smokeStarted;
					Session.SmokeControls = Session.Epoch == 1;
					Session.SmokeMoveX = ((num2 > 2f && num2 < 2.4f) ? 1 : 0);
					Session.SmokeShoot = num2 > 3f && num2 < 12f && (int)(num2 * 6f) % 2 == 0;
				}
				if (smokeLayout && smokeRole == "host" && Session.Epoch == 1 && !smokeEnemyCreated && Time.realtimeSinceStartup - smokeStarted > 3f)
				{
					smokeEnemyCreated = true;
					AIActor orLoadByGuid = EnemyDatabase.GetOrLoadByGuid("01972dee89fc4404a5c408d50007dad5");
					PlayerController secondaryPlayer = val.SecondaryPlayer;
					Vector2 val6 = ((BraveBehaviour)secondaryPlayer).specRigidbody.UnitCenter + Vector2.right * 2.5f;
					smokeEnemy = AIActor.Spawn(orLoadByGuid, val6, secondaryPlayer.CurrentRoom, true, (AwakenAnimationType)0, true);
					((Behaviour)((BraveBehaviour)smokeEnemy).behaviorSpeculator).enabled = false;
					smokeEnemyHealth = ((BraveBehaviour)smokeEnemy).healthHaver.GetCurrentHealth();
					Log("GT_DUNGEON_SMOKE_ENEMY_SPAWN health=" + smokeEnemyHealth + " guid=" + smokeEnemy.EnemyGuid);
				}
				if (smokeEnemyCreated && !smokeDamagePassed && ((Object)(object)smokeEnemy == (Object)null || ((BraveBehaviour)smokeEnemy).healthHaver.GetCurrentHealth() < smokeEnemyHealth))
				{
					smokeDamagePassed = true;
					Log("GT_DUNGEON_SMOKE_DAMAGE_PASS: native remote-player projectile damaged enemy");
				}
				if (smokeLayout && smokeRole == "host" && Time.realtimeSinceStartup - smokeStarted > 3f)
				{
					PlayerController secondaryPlayer2 = val.SecondaryPlayer;
					float num3 = Vector3.Distance(smokeCultistStart, ((BraveBehaviour)secondaryPlayer2).transform.position);
					if (num3 > 0.2f && !smokeMovePassed)
					{
						smokeMovePassed = true;
						Log("GT_DUNGEON_SMOKE_REMOTE_MOVE_PASS distance=" + num3);
					}
					Projectile[] array = Object.FindObjectsOfType<Projectile>();
					for (int i = 0; i < array.Length; i++)
					{
						if ((Object)(object)array[i].Owner == (Object)(object)secondaryPlayer2 && !smokeShotPassed)
						{
							smokeShotPassed = true;
							Log("GT_DUNGEON_SMOKE_REMOTE_SHOOT_PASS native projectile owned by selected guest");
						}
					}
					if ((!smokeMovePassed || !smokeShotPassed) && Time.realtimeSinceStartup >= nextControlsDiagnostic)
					{
						nextControlsDiagnostic = Time.realtimeSinceStartup + 1f;
						Log("GT_SMOKE_CONTROLS move=" + num3 + " ammo=" + (((Object)(object)((GameActor)secondaryPlayer2).CurrentGun == (Object)null) ? (-1) : ((GameActor)secondaryPlayer2).CurrentGun.CurrentAmmo) + " activeShoot=" + ((OneAxisInputControl)Session.Controls.Actions.ShootAction).Value + " activeMove=" + ((object)((TwoAxisInputControl)Session.Controls.Actions.Move).Vector/*cast due to .constrained prefix*/).ToString() + " input=" + ((object)secondaryPlayer2.CurrentInputState/*cast due to .constrained prefix*/).ToString() + " override=" + secondaryPlayer2.IsInputOverridden + " paused=" + val.IsPaused + " delta=" + BraveTime.DeltaTime + " packetSeq=" + Session.Controls.LastInputSequence + " packetMove=" + Session.Controls.LastMoveX + " attached=" + Session.Controls.Device.IsAttached);
					}
				}
				if (smokeLayout && !smokeCaptureRequested && Time.realtimeSinceStartup - smokeStarted > 9f)
				{
					smokeCaptureRequested = true;
					ScreenCapture.CaptureScreenshot(Path.Combine(Paths.GameRootPath, "dungeon-smoke-" + smokeRole + ".png"));
				}
				if (smokeLayout && smokeRole == "host" && !smokeSecondFloor && Time.realtimeSinceStartup - smokeStarted > 13f)
				{
					if (!smokeMovePassed || !smokeShotPassed || !smokeDamagePassed)
					{
						throw new IOException("Native remote move/fire/damage test failed.");
					}
					smokeSecondFloor = true;
					Log("GT_DUNGEON_SMOKE_TRANSITION_STARTED");
					val.LoadCustomLevel("tt5");
				}
				if (!smokeLayout || Session == null || Session.Epoch < 2 || Session.Phase != DungeonSession.RunPhase.Running || (!(smokeRole == "host") && (Session.Snapshot == null || Session.Snapshot.Epoch < 2)))
				{
					return;
				}
				if (smokeSecondFloorAt == 0f)
				{
					smokeSecondFloorAt = Time.realtimeSinceStartup;
					Log("GT_DUNGEON_SMOKE_SECOND_FLOOR_SNAPSHOT_READY");
				}
				if (!(Time.realtimeSinceStartup - smokeSecondFloorAt < 3f))
				{
					VerifySmokeCharacters(val);
					if (!smokeDesktopPassed)
					{
						throw new IOException("Desktop-safe window verification did not pass.");
					}
					if (smokeRole == "client" && Session.InterpolatedFrames < 10)
					{
						throw new IOException("Dungeon render interpolation did not execute.");
					}
					if (smokeRole == "client")
					{
						Log("GT_DUNGEON_INTERPOLATION_PASS renderFrames=" + Session.RenderedFrames + " interpolated=" + Session.InterpolatedFrames);
					}
					Log("GT_DUNGEON_SMOKE_SECOND_FLOOR_PASS");
					Log("GT_DUNGEON_SMOKE_PASS");
					smokeComplete = true;
					smokeDeadline = float.MaxValue;
				}
			}
			catch (Exception ex)
			{
				Log("GT_DUNGEON_SMOKE_FAIL: " + ex);
				Application.Quit();
				smokeDeadline = float.MaxValue;
			}
		}

		private void VerifySmokeCharacters(GameManager gm)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			if (gm.AllPlayers.Length != 2 || (Object)(object)gm.SecondaryPlayer == (Object)null || CharacterCatalog.FromIdentity(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref gm.SecondaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString()) != Session.GuestCharacter)
			{
				throw new IOException("Selected guest character was not retained across generation.");
			}
			if ((int)gm.PrimaryPlayer.characterIdentity != 0)
			{
				throw new IOException("Host character identity differs between PCs.");
			}
			if ((Object)(object)((GameActor)gm.SecondaryPlayer).CurrentGun == (Object)null)
			{
				throw new IOException("Selected guest has no starting gun.");
			}
			Log("GT_CHARACTER_PASS epoch=" + Session.Epoch + " identity=" + ((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref gm.SecondaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString() + " gun=" + ((PickupObject)((GameActor)gm.SecondaryPlayer).CurrentGun).PickupObjectId);
		}

		private IEnumerator CaptureSmokeMenu()
		{
			hostTab = smokeRole == "host";
			visible = true;
			smokeMenuVisible = true;
			yield return (object)new WaitForEndOfFrame();
			ScreenCapture.CaptureScreenshot(Path.Combine(Paths.GameRootPath, "menu-smoke-" + smokeRole + ".png"));
			yield return (object)new WaitForEndOfFrame();
			visible = false;
			smokeMenuVisible = false;
			Log("GT_MENU_CAPTURED");
		}

		private void OnDestroy()
		{
			if ((Object)(object)menuPlayer != (Object)null)
			{
				menuPlayer.ClearInputOverride("GT_Menu");
			}
			if (Session != null)
			{
				Session.Dispose();
			}
			if (transport != null)
			{
				transport.Dispose();
			}
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Instance = null;
			ClearAvatar();
		}
	}
	public enum DungeonMessage : byte
	{
		Input = 2,
		Level,
		Ready,
		Snapshot,
		Collection,
		Stop,
		Character,
		Audio,
		Motion,
		Equipment,
		Secrets,
		SceneEffects
	}
	public sealed class RemoteInput
	{
		public const float DefaultViewAspect = 1.7777778f;

		public const float MinViewAspect = 0.25f;

		public const float MaxViewAspect = 8f;

		public uint Sequence;

		public int Epoch;

		public float MoveX;

		public float MoveY;

		public float AimX;

		public float AimY;

		public float ViewAspect = 1.7777778f;

		public ulong Buttons;

		public byte[] Encode()
		{
			return Wire.Write(DungeonMessage.Input, delegate(BinaryWriter w)
			{
				w.Write(Epoch);
				w.Write(Sequence);
				w.Write(MoveX);
				w.Write(MoveY);
				w.Write(AimX);
				w.Write(AimY);
				w.Write(Buttons);
				w.Write(ViewAspect);
			});
		}

		public static RemoteInput Decode(byte[] bytes)
		{
			return Wire.Read(bytes, DungeonMessage.Input, delegate(BinaryReader r)
			{
				RemoteInput remoteInput = new RemoteInput
				{
					Epoch = r.ReadInt32(),
					Sequence = r.ReadUInt32(),
					MoveX = r.ReadSingle(),
					MoveY = r.ReadSingle(),
					AimX = r.ReadSingle(),
					AimY = r.ReadSingle(),
					Buttons = r.ReadUInt64(),
					ViewAspect = r.ReadSingle()
				};
				Wire.Range(remoteInput.MoveX, -1f, 1f);
				Wire.Range(remoteInput.MoveY, -1f, 1f);
				Wire.Range(remoteInput.AimX, -1f, 1f);
				Wire.Range(remoteInput.AimY, -1f, 1f);
				Wire.Range(remoteInput.ViewAspect, 0.25f, 8f);
				if (remoteInput.Buttons >> 41 != 0L || remoteInput.Epoch < 1)
				{
					throw new IOException("Invalid remote controls.");
				}
				return remoteInput;
			});
		}
	}
	public sealed class LevelCommand
	{
		public int Epoch;

		public int Seed;

		public int HostCharacter = 1;

		public int GuestCharacter;

		public string Scene = "tt_castle";

		public string LayoutHash = "";

		public string GenerationData = "";

		public byte[] Encode()
		{
			return Wire.Write(DungeonMessage.Level, delegate(BinaryWriter w)
			{
				w.Write(Epoch);
				w.Write(Seed);
				w.Write(Scene);
				w.Write(LayoutHash);
				w.Write(GenerationData);
				w.Write(HostCharacter);
				w.Write(GuestCharacter);
			});
		}

		public static LevelCommand Decode(byte[] bytes)
		{
			return Wire.Read(bytes, DungeonMessage.Level, delegate(BinaryReader r)
			{
				LevelCommand levelCommand = new LevelCommand
				{
					Epoch = r.ReadInt32(),
					Seed = r.ReadInt32(),
					Scene = Wire.String(r, 128),
					LayoutHash = Wire.String(r, 64),
					GenerationData = Wire.String(r, 131072)
				};
				levelCommand.HostCharacter = r.ReadInt32();
				levelCommand.GuestCharacter = r.ReadInt32();
				if (!CharacterCatalog.Valid(levelCommand.HostCharacter) || !CharacterCatalog.ValidGuest(levelCommand.GuestCharacter))
				{
					throw new IOException("Invalid run characters.");
				}
				if (levelCommand.Epoch < 1 || levelCommand.Seed <= 0 || !ValidScene(levelCommand.Scene) || levelCommand.LayoutHash.Length != 64)
				{
					throw new IOException("Invalid dungeon command.");
				}
				return levelCommand;
			});
		}

		public static bool ValidScene(string scene)
		{
			if (string.IsNullOrEmpty(scene) || scene.Length > 128 || (!scene.StartsWith("tt", StringComparison.Ordinal) && !scene.StartsWith("ss_", StringComparison.Ordinal) && !scene.StartsWith("fs_", StringComparison.Ordinal)))
			{
				return false;
			}
			foreach (char c in scene)
			{
				if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_')
				{
					return false;
				}
			}
			return true;
		}
	}
	public sealed class CollectionInfo
	{
		public int Epoch;

		public int Id;

		public string Guid = "";

		public string Name = "";

		public string Asset = "";

		public string EnemyGuid = "";

		public byte[] Encode()
		{
			return Wire.Write(DungeonMessage.Collection, delegate(BinaryWriter w)
			{
				w.Write(Epoch);
				w.Write(Id);
				w.Write(Guid);
				w.Write(Name);
				w.Write(Asset);
				w.Write(EnemyGuid);
			});
		}

		public static CollectionInfo Decode(byte[] bytes)
		{
			return Wire.Read(bytes, DungeonMessage.Collection, delegate(BinaryReader r)
			{
				CollectionInfo collectionInfo = new CollectionInfo
				{
					Epoch = r.ReadInt32(),
					Id = r.ReadInt32(),
					Guid = Wire.String(r, 256),
					Name = Wire.String(r, 256),
					Asset = Wire.String(r, 256),
					EnemyGuid = Wire.String(r, 32)
				};
				if (collectionInfo.Id < 1 || collectionInfo.Id > 4096 || collectionInfo.Epoch < 1)
				{
					throw new IOException("Invalid sprite collection ID.");
				}
				if (!EnemyAssetIdentity.Valid(collectionInfo.EnemyGuid))
				{
					throw new IOException("Invalid native enemy asset identifier.");
				}
				return collectionInfo;
			});
		}
	}
	public sealed class SpriteState
	{
		public int Id;

		public int CollectionId;

		public int SpriteId;

		public int SortingOrder;

		public float X;

		public float Y;

		public float Z;

		public float Rotation;

		public float ScaleX;

		public float ScaleY;

		public float ScaleZ;

		public float Height;

		public uint Color;

		public uint OverrideColor;

		public byte Flags;

		public byte Layer;

		public byte Kind;

		public byte Anchor;

		public float Width;

		public float HeightPixels;

		public float ClipX;

		public float ClipY;

		public float ClipRight = 1f;

		public float ClipTop = 1f;

		public void Write(BinaryWriter w)
		{
			w.Write(Id);
			w.Write(CollectionId);
			w.Write(SpriteId);
			w.Write(SortingOrder);
			w.Write(X);
			w.Write(Y);
			w.Write(Z);
			w.Write(Rotation);
			w.Write(ScaleX);
			w.Write(ScaleY);
			w.Write(ScaleZ);
			w.Write(Height);
			w.Write(Color);
			w.Write(OverrideColor);
			w.Write(Flags);
			w.Write(Layer);
			w.Write(Kind);
			w.Write(Anchor);
			w.Write(Width);
			w.Write(HeightPixels);
			w.Write(ClipX);
			w.Write(ClipY);
			w.Write(ClipRight);
			w.Write(ClipTop);
		}

		public static SpriteState Read(BinaryReader r)
		{
			SpriteState spriteState = new SpriteState
			{
				Id = r.ReadInt32(),
				CollectionId = r.ReadInt32(),
				SpriteId = r.ReadInt32(),
				SortingOrder = r.ReadInt32(),
				X = r.ReadSingle(),
				Y = r.ReadSingle(),
				Z = r.ReadSingle(),
				Rotation = r.ReadSingle(),
				ScaleX = r.ReadSingle(),
				ScaleY = r.ReadSingle(),
				ScaleZ = r.ReadSingle(),
				Height = r.ReadSingle(),
				Color = r.ReadUInt32(),
				OverrideColor = r.ReadUInt32(),
				Flags = r.ReadByte(),
				Layer = r.ReadByte()
			};
			spriteState.Kind = r.ReadByte();
			spriteState.Anchor = r.ReadByte();
			spriteState.Width = r.ReadSingle();
			spriteState.HeightPixels = r.ReadSingle();
			spriteState.ClipX = r.ReadSingle();
			spriteState.ClipY = r.ReadSingle();
			spriteState.ClipRight = r.ReadSingle();
			spriteState.ClipTop = r.ReadSingle();
			if (spriteState.Id == 0 || spriteState.CollectionId < 1 || spriteState.CollectionId > 4096 || spriteState.SpriteId < 0 || spriteState.SpriteId > 100000 || (spriteState.Flags & -32) != 0 || spriteState.Layer > 31)
			{
				throw new IOException("Invalid sprite snapshot.");
			}
			Wire.Range(spriteState.X, -100000f, 100000f);
			Wire.Range(spriteState.Y, -100000f, 100000f);
			Wire.Range(spriteState.Z, -100000f, 100000f);
			Wire.Range(spriteState.Rotation, -3600f, 3600f);
			Wire.Range(spriteState.ScaleX, -1000f, 1000f);
			Wire.Range(spriteState.ScaleY, -1000f, 1000f);
			Wire.Range(spriteState.ScaleZ, -1000f, 1000f);
			Wire.Range(spriteState.Height, -1000f, 1000f);
			if (spriteState.Kind > 3 || spriteState.Anchor > 8)
			{
				throw new IOException("Unknown sprite geometry.");
			}
			Wire.Range(spriteState.Width, 0f, 100000f);
			Wire.Range(spriteState.HeightPixels, 0f, 100000f);
			Wire.Range(spriteState.ClipX, -1000f, 1000f);
			Wire.Range(spriteState.ClipY, -1000f, 1000f);
			Wire.Range(spriteState.ClipRight, -1000f, 1000f);
			Wire.Range(spriteState.ClipTop, -1000f, 1000f);
			return spriteState;
		}
	}
	public sealed class WorldSnapshot
	{
		public const int MaxHudCounter = 1000000;

		public const int MaxHudNameLength = 96;

		public int Epoch;

		public uint Sequence;

		public double SentAt;

		public uint InputSequence;

		public float CameraX;

		public float CameraY;

		public float CameraZ;

		public float CameraSize;

		public float PlayerX;

		public float PlayerY;

		public float Health;

		public float Armor;

		public int Ammo;

		public int Currency;

		public int Keys;

		public int Blanks;

		public float MaxHealth;

		public int GunId = -1;

		public int ItemId = -1;

		public int ClipAmmo;

		public int ClipCapacity;

		public bool Reloading;

		public string GunName = "";

		public string ActiveItemName = "";

		public float ActiveItemCooldown;

		public bool Paused;

		public bool InfiniteAmmo;

		public bool InfiniteKeys;

		public bool ActiveItemReady;

		public bool ActiveItemActive;

		public bool IsGhost;

		public bool IsDead;

		public bool GameOver;

		public readonly List<SpriteState> Sprites = new List<SpriteState>();

		public byte[] Encode()
		{
			ValidateHud();
			if (Sprites.Count > 4096)
			{
				throw new IOException("Too many replicated sprites.");
			}
			return Wire.Write(DungeonMessage.Snapshot, delegate(BinaryWriter w)
			{
				w.Write(Epoch);
				w.Write(Sequence);
				w.Write(SentAt);
				w.Write(InputSequence);
				w.Write(CameraX);
				w.Write(CameraY);
				w.Write(CameraZ);
				w.Write(CameraSize);
				w.Write(PlayerX);
				w.Write(PlayerY);
				w.Write(Health);
				w.Write(Armor);
				w.Write(Ammo);
				w.Write(Paused);
				w.Write(Currency);
				w.Write(Keys);
				w.Write(Blanks);
				w.Write(GunName);
				w.Write(ActiveItemName);
				w.Write(ActiveItemCooldown);
				w.Write(InfiniteAmmo);
				w.Write(InfiniteKeys);
				w.Write(ActiveItemReady);
				w.Write(ActiveItemActive);
				w.Write(IsGhost);
				w.Write(IsDead);
				w.Write(GameOver);
				w.Write(MaxHealth);
				w.Write(GunId);
				w.Write(ItemId);
				w.Write(ClipAmmo);
				w.Write(ClipCapacity);
				w.Write(Reloading);
				w.Write(Sprites.Count);
				foreach (SpriteState sprite in Sprites)
				{
					sprite.Write(w);
				}
			});
		}

		public static WorldSnapshot Decode(byte[] bytes)
		{
			return Wire.Read(bytes, DungeonMessage.Snapshot, delegate(BinaryReader r)
			{
				WorldSnapshot worldSnapshot = new WorldSnapshot
				{
					Epoch = r.ReadInt32(),
					Sequence = r.ReadUInt32(),
					SentAt = r.ReadDouble(),
					InputSequence = r.ReadUInt32(),
					CameraX = r.ReadSingle(),
					CameraY = r.ReadSingle(),
					CameraZ = r.ReadSingle(),
					CameraSize = r.ReadSingle(),
					PlayerX = r.ReadSingle(),
					PlayerY = r.ReadSingle(),
					Health = r.ReadSingle(),
					Armor = r.ReadSingle(),
					Ammo = r.ReadInt32(),
					Paused = r.ReadBoolean()
				};
				worldSnapshot.Currency = r.ReadInt32();
				worldSnapshot.Keys = r.ReadInt32();
				worldSnapshot.Blanks = r.ReadInt32();
				worldSnapshot.GunName = Wire.String(r, 96);
				worldSnapshot.ActiveItemName = Wire.String(r, 96);
				worldSnapshot.ActiveItemCooldown = r.ReadSingle();
				worldSnapshot.InfiniteAmmo = r.ReadBoolean();
				worldSnapshot.InfiniteKeys = r.ReadBoolean();
				worldSnapshot.ActiveItemReady = r.ReadBoolean();
				worldSnapshot.ActiveItemActive = r.ReadBoolean();
				worldSnapshot.IsGhost = r.ReadBoolean();
				worldSnapshot.IsDead = r.ReadBoolean();
				worldSnapshot.GameOver = r.ReadBoolean();
				worldSnapshot.MaxHealth = r.ReadSingle();
				worldSnapshot.GunId = r.ReadInt32();
				worldSnapshot.ItemId = r.ReadInt32();
				worldSnapshot.ClipAmmo = r.ReadInt32();
				worldSnapshot.ClipCapacity = r.ReadInt32();
				worldSnapshot.Reloading = r.ReadBoolean();
				worldSnapshot.ValidateHud();
				Wire.Time(worldSnapshot.SentAt);
				Wire.Range(worldSnapshot.CameraX, -100000f, 100000f);
				Wire.Range(worldSnapshot.CameraY, -100000f, 100000f);
				Wire.Range(worldSnapshot.CameraZ, -100000f, 100000f);
				Wire.Range(worldSnapshot.CameraSize, 0.01f, 10000f);
				Wire.Range(worldSnapshot.PlayerX, -100000f, 100000f);
				Wire.Range(worldSnapshot.PlayerY, -100000f, 100000f);
				Wire.Range(worldSnapshot.Health, 0f, 10000f);
				Wire.Range(worldSnapshot.Armor, 0f, 10000f);
				int num = r.ReadInt32();
				if (num < 0 || num > 4096 || worldSnapshot.Epoch < 1 || worldSnapshot.Ammo < 0)
				{
					throw new IOException("Invalid world snapshot size.");
				}
				HashSet<int> hashSet = new HashSet<int>();
				for (int i = 0; i < num; i++)
				{
					SpriteState spriteState = SpriteState.Read(r);
					if (!hashSet.Add(spriteState.Id))
					{
						throw new IOException("Duplicate entity ID.");
					}
					worldSnapshot.Sprites.Add(spriteState);
				}
				return worldSnapshot;
			});
		}

		private void ValidateHud()
		{
			Wire.Range(MaxHealth, 0f, 10000f);
			if (GunId < -1 || GunId > 1000000 || ItemId < -1 || ItemId > 1000000 || ClipAmmo < 0 || ClipAmmo > 1000000 || ClipCapacity < 0 || ClipCapacity > 1000000)
			{
				throw new IOException("Invalid guest equipment HUD data.");
			}
			if (Ammo < 0 || Ammo > 1000000 || Currency < 0 || Currency > 1000000 || Keys < 0 || Keys > 1000000 || Blanks < 0 || Blanks > 1000000)
			{
				throw new IOException("Invalid guest HUD counter.");
			}
			Wire.Range(ActiveItemCooldown, 0f, 1f);
			if (GunName == null || ActiveItemName == null || GunName.Length > 96 || ActiveItemName.Length > 96)
			{
				throw new IOException("Invalid guest HUD name length.");
			}
			string gunName = GunName;
			for (int i = 0; i < gunName.Length; i++)
			{
				if (char.IsControl(gunName[i]))
				{
					throw new IOException("Invalid guest gun name.");
				}
			}
			gunName = ActiveItemName;
			for (int i = 0; i < gunName.Length; i++)
			{
				if (char.IsControl(gunName[i]))
				{
					throw new IOException("Invalid guest active item name.");
				}
			}
		}

		public static string CleanHudName(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "";
			}
			int num = Math.Min(value.Length, 96);
			if (num < value.Length && char.IsHighSurrogate(value[num - 1]))
			{
				num--;
			}
			char[] array = value.Substring(0, num).ToCharArray();
			for (int i = 0; i < array.Length; i++)
			{
				if (char.IsControl(array[i]))
				{
					array[i] = ' ';
				}
			}
			return new string(array);
		}
	}
	public static class Wire
	{
		public static byte[] Write(DungeonMessage type, Action<BinaryWriter> action)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write((byte)type);
			action(binaryWriter);
			return memoryStream.ToArray();
		}

		public static T Read<T>(byte[] bytes, DungeonMessage type, Func<BinaryReader, T> action)
		{
			using MemoryStream memoryStream = new MemoryStream(bytes);
			using BinaryReader binaryReader = new BinaryReader(memoryStream);
			if ((uint)binaryReader.ReadByte() != (uint)type)
			{
				throw new IOException("Unexpected dungeon packet.");
			}
			T result = action(binaryReader);
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new IOException("Trailing packet bytes.");
			}
			return result;
		}

		public static string String(BinaryReader r, int max)
		{
			string text = r.ReadString();
			if (text.Length > max)
			{
				throw new IOException("String too long.");
			}
			return text;
		}

		public static void Range(float value, float min, float max)
		{
			if (float.IsNaN(value) || float.IsInfinity(value) || value < min || value > max)
			{
				throw new IOException("Invalid numeric snapshot value.");
			}
		}

		public static void Time(double value)
		{
			if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0 || value > 1000000000000.0)
			{
				throw new IOException("Invalid frame timestamp.");
			}
		}

		public static bool Newer(uint value, uint previous)
		{
			return (int)(value - previous) > 0;
		}
	}
	public sealed class DungeonSession : IDisposable
	{
		public enum RunPhase
		{
			Lobby,
			Generating,
			AwaitingClient,
			Running,
			Fault
		}

		private readonly LanTransport transport;

		private readonly GameObject timeOwner;

		private readonly Action<string> log;

		private SpriteReplication sprites = new SpriteReplication();

		private RemoteControls controls;

		private string scene = "tt_castle";

		private string hash = "";

		private string generationContext;

		private string clientOriginalContext;

		private int epoch;

		private int seed;

		private uint inputSequence;

		private uint snapshotSequence;

		private uint lastSnapshot;

		private float deadline;

		private double nextInput;

		private double nextSnapshot;

		private double nextMotion;

		private uint motionSequence;

		private ulong previousButtons;

		public bool EnableMovementPrediction = true;

		private bool loadingCommand;

		private readonly InputAcknowledgements inputAcks = new InputAcknowledgements();

		private readonly WorldPresentation presentation = new WorldPresentation();

		public readonly NativeHud Hud = new NativeHud();

		internal readonly ScenePresentation SceneEffects = new ScenePresentation();

		private float nextNetworkLog;

		private int hostCharacter = 1;

		private GameObject clientOriginalPrefab;

		public bool MuteClientInput;

		public bool SmokeControls;

		public float SmokeMoveX;

		public bool SmokeShoot;

		public AudioReplication Audio { get; private set; } = new AudioReplication();

		public double SmoothingMilliseconds => sprites.SmoothingMilliseconds;

		public InventoryState Equipment { get; private set; }

		internal InventoryBook Book { get; private set; }

		public int RevealedSecretRooms => presentation.AppliedSecretRooms;

		public string MovementBlockReason => SceneEffects.MovementBlock;

		public double InputLatencyMs => inputAcks.Milliseconds;

		public int GuestCharacter { get; private set; }

		public bool GuestChoiceReceived { get; private set; }

		public RunPhase Phase { get; private set; }

		public bool IsHost => transport.IsHost;

		public bool Mirroring
		{
			get
			{
				if (!IsHost)
				{
					return Phase == RunPhase.Running;
				}
				return false;
			}
		}

		public bool InRun => Phase != RunPhase.Lobby;

		public string Status { get; private set; }

		public int Epoch => epoch;

		public int Seed => seed;

		public RemoteControls Controls => controls;

		public WorldSnapshot Snapshot => sprites.LastSnapshot;

		public int RenderedFrames => sprites.RenderedFrames;

		public int InterpolatedFrames => sprites.InterpolatedFrames;

		public bool CanClientLoadLevel
		{
			get
			{
				if (!IsHost && InRun)
				{
					return loadingCommand;
				}
				return true;
			}
		}

		public DungeonSession(LanTransport transport, GameObject timeOwner, Action<string> log)
		{
			this.transport = transport;
			this.timeOwner = timeOwner;
			this.log = log;
			Phase = RunPhase.Lobby;
			Status = "Connected: host can start synchronized dungeon";
		}

		public void ChooseGuest(int character)
		{
			if (!IsHost && Phase == RunPhase.Lobby && CharacterCatalog.ValidGuest(character))
			{
				GuestCharacter = character;
				Send(new LobbyChoice
				{
					Character = character
				}.Encode());
			}
		}

		public void StartHost()
		{
			if (!IsHost || !transport.Connected || Phase != RunPhase.Lobby)
			{
				throw new IOException("Connect two PCs in the foyer first.");
			}
			GameManager instance = GameManager.Instance;
			if (!instance.IsFoyer || (Object)(object)instance.PrimaryPlayer == (Object)null)
			{
				throw new IOException("Start from the foyer with a selected character.");
			}
			if (!GuestChoiceReceived)
			{
				throw new IOException("Wait for the friend's character selection.");
			}
			hostCharacter = CharacterCatalog.FromIdentity(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref instance.PrimaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString());
			controls = new RemoteControls();
			NativeWorld.EnsureGuest(GuestCharacter);
			controls.Bind();
			controls.Enabled = false;
			generationContext = NativeWorld.ExportGenerationContext();
			epoch = 1;
			seed = Random.Range(1, 999999999);
			scene = "tt_castle";
			if ((Object)(object)DungeonPlugin.Instance != (Object)null && DungeonPlugin.Instance.IsSmoke)
			{
				string[] commandLineArgs = Environment.GetCommandLineArgs();
				foreach (string text in commandLineArgs)
				{
					if (text.StartsWith("--gt-smoke-seed=", StringComparison.Ordinal) && int.TryParse(text.Substring("--gt-smoke-seed=".Length), out var result) && result > 0)
					{
						seed = result;
					}
				}
			}
			SetGenerating();
			NativeWorld.DepartFoyer();
			instance.InitializeForRunWithSeed(seed);
			ins