Decompiled source of CardShopCoopCommunity v2.0.6

BepInEx/plugins/CardShopCoop/CardShopCoop.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using CC;
using CMF;
using CardShopCoop.Api;
using CardShopCoop.Attributes;
using CardShopCoop.Modules.Catalog;
using CardShopCoop.Modules.DebugTools;
using CardShopCoop.Modules.Decoration;
using CardShopCoop.Modules.Economy;
using CardShopCoop.Modules.Grading;
using CardShopCoop.Modules.Hud;
using CardShopCoop.Modules.Npc;
using CardShopCoop.Modules.PlayTable;
using CardShopCoop.Modules.Prediction;
using CardShopCoop.Modules.Presence;
using CardShopCoop.Modules.Pricing;
using CardShopCoop.Modules.Register;
using CardShopCoop.Modules.Report;
using CardShopCoop.Modules.SaveTransfer;
using CardShopCoop.Modules.SessionInput;
using CardShopCoop.Modules.World;
using CardShopCoop.Net;
using CardShopCoop.Net.Connection;
using CardShopCoop.Net.Datagrams;
using CardShopCoop.Net.Kcp;
using CardShopCoop.Net.Messages;
using CardShopCoop.Net.Protocol;
using CardShopCoop.Runtime;
using CardShopCoop.UI;
using CardShopCoop.Util;
using HarmonyLib;
using I2.Loc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Steamworks;
using TMPro;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using kcp2k;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("ConnectionState")]
[assembly: InternalsVisibleTo("ProtocolTransport")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Devin 'Meepen' Korb")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("CardShopCoopCommunity for TCG Card Shop Simulator.")]
[assembly: AssemblyFileVersion("2.0.6.0")]
[assembly: AssemblyInformationalVersion("2.0.6+7377084dcc6c7363bdd27c3bdca151eaad5dfb45")]
[assembly: AssemblyProduct("CardShopCoopCommunity")]
[assembly: AssemblyTitle("CardShopCoop")]
[assembly: AssemblyVersion("2.0.6.0")]
namespace kcp2k
{
	internal struct AckItem
	{
		internal uint serialNumber;

		internal uint timestamp;
	}
	public class Kcp
	{
		public const int RTO_NDL = 30;

		public const int RTO_MIN = 100;

		public const int RTO_DEF = 200;

		public const int RTO_MAX = 60000;

		public const int CMD_PUSH = 81;

		public const int CMD_ACK = 82;

		public const int CMD_WASK = 83;

		public const int CMD_WINS = 84;

		public const int ASK_SEND = 1;

		public const int ASK_TELL = 2;

		public const int WND_SND = 32;

		public const int WND_RCV = 128;

		public const int MTU_DEF = 1200;

		public const int MTU_MIN = 50;

		public const int ACK_FAST = 3;

		public const int INTERVAL = 100;

		public const int OVERHEAD = 24;

		public const int FRG_MAX = 255;

		public const int DEADLINK = 20;

		public const int THRESH_INIT = 2;

		public const int THRESH_MIN = 2;

		public const int PROBE_INIT = 7000;

		public const int PROBE_LIMIT = 120000;

		public const int FASTACK_LIMIT = 5;

		internal int state;

		private readonly uint conv;

		internal uint mtu;

		internal uint mss;

		internal uint snd_una;

		internal uint snd_nxt;

		internal uint rcv_nxt;

		internal uint ssthresh;

		internal int rx_rttval;

		internal int rx_srtt;

		internal int rx_rto;

		internal int rx_minrto;

		internal uint snd_wnd;

		internal uint rcv_wnd;

		internal uint rmt_wnd;

		internal uint cwnd;

		internal uint probe;

		internal uint interval;

		internal uint ts_flush;

		internal uint xmit;

		internal uint nodelay;

		internal bool updated;

		internal uint ts_probe;

		internal uint probe_wait;

		internal uint dead_link;

		internal uint incr;

		internal uint current;

		internal int fastresend;

		internal int fastlimit;

		internal bool nocwnd;

		internal readonly Queue<Segment> snd_queue = new Queue<Segment>(16);

		internal readonly Queue<Segment> rcv_queue = new Queue<Segment>(16);

		internal readonly List<Segment> snd_buf = new List<Segment>(16);

		internal readonly List<Segment> rcv_buf = new List<Segment>(16);

		internal readonly List<AckItem> acklist = new List<AckItem>(16);

		internal byte[] buffer;

		private readonly Action<byte[], int> output;

		private readonly Pool<Segment> SegmentPool = new Pool<Segment>(() => new Segment(), delegate(Segment segment)
		{
			segment.Reset();
		}, 32);

		public int WaitSnd => snd_buf.Count + snd_queue.Count;

		public Kcp(uint conv, Action<byte[], int> output)
		{
			this.conv = conv;
			this.output = output;
			snd_wnd = 32u;
			rcv_wnd = 128u;
			rmt_wnd = 128u;
			mtu = 1200u;
			mss = mtu - 24;
			rx_rto = 200;
			rx_minrto = 100;
			interval = 100u;
			ts_flush = 100u;
			ssthresh = 2u;
			fastlimit = 5;
			dead_link = 20u;
			buffer = new byte[(mtu + 24) * 3];
		}

		private Segment SegmentNew()
		{
			return SegmentPool.Take();
		}

		private void SegmentDelete(Segment seg)
		{
			SegmentPool.Return(seg);
		}

		internal uint WndUnused()
		{
			if (rcv_queue.Count < rcv_wnd)
			{
				return rcv_wnd - (uint)rcv_queue.Count;
			}
			return 0u;
		}

		public int Receive(byte[] buffer, int len)
		{
			if (len < 0)
			{
				throw new NotSupportedException("Receive ispeek for negative len is not supported!");
			}
			if (rcv_queue.Count == 0)
			{
				return -1;
			}
			if (len < 0)
			{
				len = -len;
			}
			int num = PeekSize();
			if (num < 0)
			{
				return -2;
			}
			if (num > len)
			{
				return -3;
			}
			bool flag = rcv_queue.Count >= rcv_wnd;
			int num2 = 0;
			len = 0;
			while (rcv_queue.Count > 0)
			{
				Segment segment = rcv_queue.Dequeue();
				Buffer.BlockCopy(segment.data.GetBuffer(), 0, buffer, num2, (int)segment.data.Position);
				num2 += (int)segment.data.Position;
				len += (int)segment.data.Position;
				uint frg = segment.frg;
				SegmentDelete(segment);
				if (frg == 0)
				{
					break;
				}
			}
			int num3 = 0;
			foreach (Segment item in rcv_buf)
			{
				if (item.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
				{
					num3++;
					rcv_queue.Enqueue(item);
					rcv_nxt++;
					continue;
				}
				break;
			}
			rcv_buf.RemoveRange(0, num3);
			if (rcv_queue.Count < rcv_wnd && flag)
			{
				probe |= 2u;
			}
			return len;
		}

		public int PeekSize()
		{
			int num = 0;
			if (rcv_queue.Count == 0)
			{
				return -1;
			}
			Segment segment = rcv_queue.Peek();
			if (segment.frg == 0)
			{
				return (int)segment.data.Position;
			}
			if (rcv_queue.Count < segment.frg + 1)
			{
				return -1;
			}
			foreach (Segment item in rcv_queue)
			{
				num += (int)item.data.Position;
				if (item.frg == 0)
				{
					break;
				}
			}
			return num;
		}

		public int Send(byte[] buffer, int offset, int len)
		{
			if (len < 0)
			{
				return -1;
			}
			int num = (int)((len <= mss) ? 1 : ((len + mss - 1) / mss));
			if (num > 255)
			{
				throw new Exception($"Send len={len} requires {num} fragments, but kcp can only handle up to {255} fragments.");
			}
			if (num >= rcv_wnd)
			{
				return -2;
			}
			if (num == 0)
			{
				num = 1;
			}
			for (int i = 0; i < num; i++)
			{
				int num2 = ((len > (int)mss) ? ((int)mss) : len);
				Segment segment = SegmentNew();
				if (len > 0)
				{
					segment.data.Write(buffer, offset, num2);
				}
				segment.frg = (uint)(num - i - 1);
				snd_queue.Enqueue(segment);
				offset += num2;
				len -= num2;
			}
			return 0;
		}

		private void UpdateAck(int rtt)
		{
			if (rx_srtt == 0)
			{
				rx_srtt = rtt;
				rx_rttval = rtt / 2;
			}
			else
			{
				int num = rtt - rx_srtt;
				if (num < 0)
				{
					num = -num;
				}
				rx_rttval = (3 * rx_rttval + num) / 4;
				rx_srtt = (7 * rx_srtt + rtt) / 8;
				if (rx_srtt < 1)
				{
					rx_srtt = 1;
				}
			}
			int value = rx_srtt + Math.Max((int)interval, 4 * rx_rttval);
			rx_rto = Utils.Clamp(value, rx_minrto, 60000);
		}

		internal void ShrinkBuf()
		{
			if (snd_buf.Count > 0)
			{
				Segment segment = snd_buf[0];
				snd_una = segment.sn;
			}
			else
			{
				snd_una = snd_nxt;
			}
		}

		internal void ParseAck(uint sn)
		{
			if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0)
			{
				return;
			}
			for (int i = 0; i < snd_buf.Count; i++)
			{
				Segment segment = snd_buf[i];
				if (sn == segment.sn)
				{
					snd_buf.RemoveAt(i);
					SegmentDelete(segment);
					break;
				}
				if (Utils.TimeDiff(sn, segment.sn) < 0)
				{
					break;
				}
			}
		}

		internal void ParseUna(uint una)
		{
			int num = 0;
			foreach (Segment item in snd_buf)
			{
				if (Utils.TimeDiff(una, item.sn) > 0)
				{
					num++;
					SegmentDelete(item);
					continue;
				}
				break;
			}
			snd_buf.RemoveRange(0, num);
		}

		internal void ParseFastack(uint sn, uint ts)
		{
			if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0)
			{
				return;
			}
			foreach (Segment item in snd_buf)
			{
				if (Utils.TimeDiff(sn, item.sn) < 0)
				{
					break;
				}
				if (sn != item.sn)
				{
					item.fastack++;
				}
			}
		}

		private void AckPush(uint sn, uint ts)
		{
			acklist.Add(new AckItem
			{
				serialNumber = sn,
				timestamp = ts
			});
		}

		private void ParseData(Segment newseg)
		{
			uint sn = newseg.sn;
			if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 || Utils.TimeDiff(sn, rcv_nxt) < 0)
			{
				SegmentDelete(newseg);
				return;
			}
			InsertSegmentInReceiveBuffer(newseg);
			MoveReceiveBufferReadySegmentsToQueue();
		}

		internal void InsertSegmentInReceiveBuffer(Segment newseg)
		{
			bool flag = false;
			int num;
			for (num = rcv_buf.Count - 1; num >= 0; num--)
			{
				Segment segment = rcv_buf[num];
				if (segment.sn == newseg.sn)
				{
					flag = true;
					break;
				}
				if (Utils.TimeDiff(newseg.sn, segment.sn) > 0)
				{
					break;
				}
			}
			if (!flag)
			{
				rcv_buf.Insert(num + 1, newseg);
			}
			else
			{
				SegmentDelete(newseg);
			}
		}

		private void MoveReceiveBufferReadySegmentsToQueue()
		{
			int num = 0;
			foreach (Segment item in rcv_buf)
			{
				if (item.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
				{
					num++;
					rcv_queue.Enqueue(item);
					rcv_nxt++;
					continue;
				}
				break;
			}
			rcv_buf.RemoveRange(0, num);
		}

		public int Input(byte[] data, int offset, int size)
		{
			uint earlier = snd_una;
			uint num = 0u;
			uint ts = 0u;
			int num2 = 0;
			if (data == null || size < 24)
			{
				return -1;
			}
			while (size >= 24)
			{
				offset += Utils.Decode32U(data, offset, out var value);
				if (value != conv)
				{
					return -1;
				}
				offset += Utils.Decode8u(data, offset, out var value2);
				offset += Utils.Decode8u(data, offset, out var value3);
				offset += Utils.Decode16U(data, offset, out var value4);
				offset += Utils.Decode32U(data, offset, out var value5);
				offset += Utils.Decode32U(data, offset, out var value6);
				offset += Utils.Decode32U(data, offset, out var value7);
				offset += Utils.Decode32U(data, offset, out var value8);
				size -= 24;
				if (size < value8 || (int)value8 < 0)
				{
					return -2;
				}
				if (value2 != 81 && value2 != 82 && value2 != 83 && value2 != 84)
				{
					return -3;
				}
				if (value2 == 81 && (value3 >= byte.MaxValue || (uint)(value3 + 1) >= rcv_wnd))
				{
					return -4;
				}
				rmt_wnd = value4;
				ParseUna(value7);
				ShrinkBuf();
				switch (value2)
				{
				case 82:
					if (Utils.TimeDiff(current, value5) >= 0)
					{
						UpdateAck(Utils.TimeDiff(current, value5));
					}
					ParseAck(value6);
					ShrinkBuf();
					if (num2 == 0)
					{
						num2 = 1;
						num = value6;
						ts = value5;
					}
					else if (Utils.TimeDiff(value6, num) > 0)
					{
						num = value6;
						ts = value5;
					}
					break;
				case 81:
					if (Utils.TimeDiff(value6, rcv_nxt + rcv_wnd) >= 0)
					{
						break;
					}
					AckPush(value6, value5);
					if (Utils.TimeDiff(value6, rcv_nxt) >= 0)
					{
						Segment segment = SegmentNew();
						segment.conv = value;
						segment.cmd = value2;
						segment.frg = value3;
						segment.wnd = value4;
						segment.ts = value5;
						segment.sn = value6;
						segment.una = value7;
						if (value8 != 0)
						{
							segment.data.Write(data, offset, (int)value8);
						}
						ParseData(segment);
					}
					break;
				case 83:
					probe |= 2u;
					break;
				default:
					return -3;
				case 84:
					break;
				}
				offset += (int)value8;
				size -= (int)value8;
			}
			if (num2 != 0)
			{
				ParseFastack(num, ts);
			}
			if (Utils.TimeDiff(snd_una, earlier) > 0 && cwnd < rmt_wnd)
			{
				if (cwnd < ssthresh)
				{
					cwnd++;
					incr += mss;
				}
				else
				{
					if (incr < mss)
					{
						incr = mss;
					}
					incr += mss * mss / incr + mss / 16;
					if ((cwnd + 1) * mss <= incr)
					{
						cwnd = (incr + mss - 1) / ((mss == 0) ? 1 : mss);
					}
				}
				if (cwnd > rmt_wnd)
				{
					cwnd = rmt_wnd;
					incr = rmt_wnd * mss;
				}
			}
			return 0;
		}

		private void MakeSpace(ref int size, int space)
		{
			if (size + space > mtu)
			{
				output(buffer, size);
				size = 0;
			}
		}

		private void FlushBuffer(int size)
		{
			if (size > 0)
			{
				output(buffer, size);
			}
		}

		public void Flush()
		{
			int size = 0;
			bool flag = false;
			if (!updated)
			{
				return;
			}
			Segment segment = SegmentNew();
			segment.conv = conv;
			segment.cmd = 82u;
			segment.wnd = WndUnused();
			segment.una = rcv_nxt;
			foreach (AckItem item in acklist)
			{
				MakeSpace(ref size, 24);
				segment.sn = item.serialNumber;
				segment.ts = item.timestamp;
				size += segment.Encode(buffer, size);
			}
			acklist.Clear();
			if (rmt_wnd == 0)
			{
				if (probe_wait == 0)
				{
					probe_wait = 7000u;
					ts_probe = current + probe_wait;
				}
				else if (Utils.TimeDiff(current, ts_probe) >= 0)
				{
					if (probe_wait < 7000)
					{
						probe_wait = 7000u;
					}
					probe_wait += probe_wait / 2;
					if (probe_wait > 120000)
					{
						probe_wait = 120000u;
					}
					ts_probe = current + probe_wait;
					probe |= 1u;
				}
			}
			else
			{
				ts_probe = 0u;
				probe_wait = 0u;
			}
			if ((probe & 1) != 0)
			{
				segment.cmd = 83u;
				MakeSpace(ref size, 24);
				size += segment.Encode(buffer, size);
			}
			if ((probe & 2) != 0)
			{
				segment.cmd = 84u;
				MakeSpace(ref size, 24);
				size += segment.Encode(buffer, size);
			}
			probe = 0u;
			uint num = Math.Min(snd_wnd, rmt_wnd);
			if (!nocwnd)
			{
				num = Math.Min(cwnd, num);
			}
			while (Utils.TimeDiff(snd_nxt, snd_una + num) < 0 && snd_queue.Count != 0)
			{
				Segment segment2 = snd_queue.Dequeue();
				segment2.conv = conv;
				segment2.cmd = 81u;
				segment2.wnd = segment.wnd;
				segment2.ts = current;
				segment2.sn = snd_nxt;
				snd_nxt++;
				segment2.una = rcv_nxt;
				segment2.resendts = current;
				segment2.rto = rx_rto;
				segment2.fastack = 0u;
				segment2.xmit = 0u;
				snd_buf.Add(segment2);
			}
			uint num2 = ((fastresend > 0) ? ((uint)fastresend) : uint.MaxValue);
			uint num3 = ((nodelay == 0) ? ((uint)rx_rto >> 3) : 0u);
			int num4 = 0;
			foreach (Segment item2 in snd_buf)
			{
				bool flag2 = false;
				if (item2.xmit == 0)
				{
					flag2 = true;
					item2.xmit++;
					item2.rto = rx_rto;
					item2.resendts = (uint)((int)current + item2.rto) + num3;
				}
				else if (Utils.TimeDiff(current, item2.resendts) >= 0)
				{
					flag2 = true;
					item2.xmit++;
					xmit++;
					if (nodelay == 0)
					{
						item2.rto += Math.Max(item2.rto, rx_rto);
					}
					else
					{
						int num5 = ((nodelay < 2) ? item2.rto : rx_rto);
						item2.rto += num5 / 2;
					}
					item2.resendts = current + (uint)item2.rto;
					flag = true;
				}
				else if (item2.fastack >= num2 && (item2.xmit <= fastlimit || fastlimit <= 0))
				{
					flag2 = true;
					item2.xmit++;
					item2.fastack = 0u;
					item2.resendts = current + (uint)item2.rto;
					num4++;
				}
				if (flag2)
				{
					item2.ts = current;
					item2.wnd = segment.wnd;
					item2.una = rcv_nxt;
					int space = 24 + (int)item2.data.Position;
					MakeSpace(ref size, space);
					size += item2.Encode(buffer, size);
					if (item2.data.Position > 0)
					{
						Buffer.BlockCopy(item2.data.GetBuffer(), 0, buffer, size, (int)item2.data.Position);
						size += (int)item2.data.Position;
					}
					if (item2.xmit >= dead_link)
					{
						state = -1;
					}
				}
			}
			SegmentDelete(segment);
			FlushBuffer(size);
			if (num4 > 0)
			{
				uint num6 = snd_nxt - snd_una;
				ssthresh = num6 / 2;
				if (ssthresh < 2)
				{
					ssthresh = 2u;
				}
				cwnd = ssthresh + num2;
				incr = cwnd * mss;
			}
			if (flag)
			{
				ssthresh = num / 2;
				if (ssthresh < 2)
				{
					ssthresh = 2u;
				}
				cwnd = 1u;
				incr = mss;
			}
			if (cwnd < 1)
			{
				cwnd = 1u;
				incr = mss;
			}
		}

		public void Update(uint currentTimeMilliSeconds)
		{
			current = currentTimeMilliSeconds;
			if (!updated)
			{
				updated = true;
				ts_flush = current;
			}
			int num = Utils.TimeDiff(current, ts_flush);
			if (num >= 10000 || num < -10000)
			{
				ts_flush = current;
				num = 0;
			}
			if (num >= 0)
			{
				ts_flush += interval;
				if (current >= ts_flush)
				{
					ts_flush = current + interval;
				}
				Flush();
			}
		}

		public uint Check(uint current_)
		{
			uint num = ts_flush;
			int num2 = int.MaxValue;
			if (!updated)
			{
				return current_;
			}
			if (Utils.TimeDiff(current_, num) >= 10000 || Utils.TimeDiff(current_, num) < -10000)
			{
				num = current_;
			}
			if (Utils.TimeDiff(current_, num) >= 0)
			{
				return current_;
			}
			int num3 = Utils.TimeDiff(num, current_);
			foreach (Segment item in snd_buf)
			{
				int num4 = Utils.TimeDiff(item.resendts, current_);
				if (num4 <= 0)
				{
					return current_;
				}
				if (num4 < num2)
				{
					num2 = num4;
				}
			}
			uint num5 = (uint)((num2 < num3) ? num2 : num3);
			if (num5 >= interval)
			{
				num5 = interval;
			}
			return current_ + num5;
		}

		public void SetMtu(uint mtu)
		{
			if (mtu < 50 || mtu < 24)
			{
				throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD");
			}
			buffer = new byte[(mtu + 24) * 3];
			this.mtu = mtu;
			mss = mtu - 24;
		}

		public void SetInterval(uint interval)
		{
			if (interval > 5000)
			{
				interval = 5000u;
			}
			else if (interval < 10)
			{
				interval = 10u;
			}
			this.interval = interval;
		}

		public void SetNoDelay(uint nodelay, uint interval = 100u, int resend = 0, bool nocwnd = false)
		{
			this.nodelay = nodelay;
			if (nodelay != 0)
			{
				rx_minrto = 30;
			}
			else
			{
				rx_minrto = 100;
			}
			if (interval >= 0)
			{
				if (interval > 5000)
				{
					interval = 5000u;
				}
				else if (interval < 10)
				{
					interval = 10u;
				}
				this.interval = interval;
			}
			if (resend >= 0)
			{
				fastresend = resend;
			}
			this.nocwnd = nocwnd;
		}

		public void SetWindowSize(uint sendWindow, uint receiveWindow)
		{
			if (sendWindow != 0)
			{
				snd_wnd = sendWindow;
			}
			if (receiveWindow != 0)
			{
				rcv_wnd = Math.Max(receiveWindow, 128u);
			}
		}
	}
	public class Pool<T>
	{
		private readonly Stack<T> objects = new Stack<T>();

		private readonly Func<T> objectGenerator;

		private readonly Action<T> objectResetter;

		public int Count => objects.Count;

		public Pool(Func<T> objectGenerator, Action<T> objectResetter, int initialCapacity)
		{
			this.objectGenerator = objectGenerator;
			this.objectResetter = objectResetter;
			for (int i = 0; i < initialCapacity; i++)
			{
				objects.Push(objectGenerator());
			}
		}

		public T Take()
		{
			if (objects.Count <= 0)
			{
				return objectGenerator();
			}
			return objects.Pop();
		}

		public void Return(T item)
		{
			objectResetter(item);
			objects.Push(item);
		}

		public void Clear()
		{
			objects.Clear();
		}
	}
	internal class Segment
	{
		internal uint conv;

		internal uint cmd;

		internal uint frg;

		internal uint wnd;

		internal uint ts;

		internal uint sn;

		internal uint una;

		internal uint resendts;

		internal int rto;

		internal uint fastack;

		internal uint xmit;

		internal MemoryStream data = new MemoryStream(1200);

		internal int Encode(byte[] ptr, int offset)
		{
			int num = offset;
			offset += Utils.Encode32U(ptr, offset, conv);
			offset += Utils.Encode8u(ptr, offset, (byte)cmd);
			offset += Utils.Encode8u(ptr, offset, (byte)frg);
			offset += Utils.Encode16U(ptr, offset, (ushort)wnd);
			offset += Utils.Encode32U(ptr, offset, ts);
			offset += Utils.Encode32U(ptr, offset, sn);
			offset += Utils.Encode32U(ptr, offset, una);
			offset += Utils.Encode32U(ptr, offset, (uint)data.Position);
			return offset - num;
		}

		internal void Reset()
		{
			conv = 0u;
			cmd = 0u;
			frg = 0u;
			wnd = 0u;
			ts = 0u;
			sn = 0u;
			una = 0u;
			rto = 0;
			xmit = 0u;
			resendts = 0u;
			fastack = 0u;
			data.SetLength(0L);
		}
	}
	public static class Utils
	{
		public static int Clamp(int value, int min, int max)
		{
			if (value < min)
			{
				return min;
			}
			if (value > max)
			{
				return max;
			}
			return value;
		}

		public static int Encode8u(byte[] p, int offset, byte value)
		{
			p[offset] = value;
			return 1;
		}

		public static int Decode8u(byte[] p, int offset, out byte value)
		{
			value = p[offset];
			return 1;
		}

		public static int Encode16U(byte[] p, int offset, ushort value)
		{
			p[offset] = (byte)value;
			p[1 + offset] = (byte)(value >> 8);
			return 2;
		}

		public static int Decode16U(byte[] p, int offset, out ushort value)
		{
			ushort num = 0;
			num |= p[offset];
			num |= (ushort)(p[1 + offset] << 8);
			value = num;
			return 2;
		}

		public static int Encode32U(byte[] p, int offset, uint value)
		{
			p[offset] = (byte)value;
			p[1 + offset] = (byte)(value >> 8);
			p[2 + offset] = (byte)(value >> 16);
			p[3 + offset] = (byte)(value >> 24);
			return 4;
		}

		public static int Decode32U(byte[] p, int offset, out uint value)
		{
			uint num = 0u;
			num |= p[offset];
			num |= (uint)(p[1 + offset] << 8);
			num |= (uint)(p[2 + offset] << 16);
			num |= (uint)(p[3 + offset] << 24);
			value = num;
			return 4;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public static int TimeDiff(uint later, uint earlier)
		{
			return (int)(later - earlier);
		}
	}
	public enum ErrorCode : byte
	{
		DnsResolve,
		Timeout,
		Congestion,
		InvalidReceive,
		InvalidSend,
		ConnectionClosed,
		Unexpected
	}
	public enum KcpChannel : byte
	{
		Reliable = 1,
		Unreliable
	}
	[Serializable]
	public class KcpConfig
	{
		public bool DualMode;

		public int RecvBufferSize;

		public int SendBufferSize;

		public int Mtu;

		public bool NoDelay;

		public uint Interval;

		public int FastResend;

		public bool CongestionWindow;

		public uint SendWindowSize;

		public uint ReceiveWindowSize;

		public int Timeout;

		public uint MaxRetransmits;

		public KcpConfig(bool DualMode = true, int RecvBufferSize = 7340032, int SendBufferSize = 7340032, int Mtu = 1200, bool NoDelay = true, uint Interval = 10u, int FastResend = 0, bool CongestionWindow = false, uint SendWindowSize = 32u, uint ReceiveWindowSize = 128u, int Timeout = 600000, uint MaxRetransmits = 20u)
		{
			this.DualMode = DualMode;
			this.RecvBufferSize = RecvBufferSize;
			this.SendBufferSize = SendBufferSize;
			this.Mtu = Mtu;
			this.NoDelay = NoDelay;
			this.Interval = Interval;
			this.FastResend = FastResend;
			this.CongestionWindow = CongestionWindow;
			this.SendWindowSize = SendWindowSize;
			this.ReceiveWindowSize = ReceiveWindowSize;
			this.Timeout = Timeout;
			this.MaxRetransmits = MaxRetransmits;
		}
	}
	public enum KcpHeaderReliable : byte
	{
		Hello = 1,
		Ping,
		Data
	}
	public enum KcpHeaderUnreliable : byte
	{
		Data = 4,
		Disconnect
	}
	public static class KcpHeader
	{
		public static bool ParseReliable(byte value, out KcpHeaderReliable header)
		{
			if (Enum.IsDefined(typeof(KcpHeaderReliable), value))
			{
				header = (KcpHeaderReliable)value;
				return true;
			}
			header = KcpHeaderReliable.Ping;
			return false;
		}

		public static bool ParseUnreliable(byte value, out KcpHeaderUnreliable header)
		{
			if (Enum.IsDefined(typeof(KcpHeaderUnreliable), value))
			{
				header = (KcpHeaderUnreliable)value;
				return true;
			}
			header = KcpHeaderUnreliable.Disconnect;
			return false;
		}
	}
	public abstract class KcpPeer
	{
		internal Kcp kcp;

		internal uint cookie;

		protected KcpState state;

		public const int DEFAULT_TIMEOUT = 600000;

		public int timeout;

		private uint lastReceiveTime;

		private uint currentTime;

		private readonly byte[] kcpMessageBuffer;

		private readonly byte[] kcpSendBuffer;

		private readonly byte[] rawSendBuffer;

		public const int PING_INTERVAL = 1000;

		private uint lastPingTime;

		internal const int QueueDisconnectThreshold = 10000;

		public const int CHANNEL_HEADER_SIZE = 1;

		public const int COOKIE_HEADER_SIZE = 4;

		public const int METADATA_SIZE = 5;

		public readonly int unreliableMax;

		public readonly int reliableMax;

		private bool disconnecting;

		private bool remoteDisconnectRequested;

		public int SendQueueCount => kcp.snd_queue.Count;

		public int ReceiveQueueCount => kcp.rcv_queue.Count;

		public int SendBufferCount => kcp.snd_buf.Count;

		public int ReceiveBufferCount => kcp.rcv_buf.Count;

		public uint MaxSendRate => kcp.snd_wnd * kcp.mtu * 1000 / kcp.interval;

		public uint MaxReceiveRate => kcp.rcv_wnd * kcp.mtu * 1000 / kcp.interval;

		private static int ReliableMaxMessageSize_Unconstrained(int mtu, uint rcv_wnd)
		{
			return (mtu - 24 - 5) * (int)(rcv_wnd - 1) - 1;
		}

		public static int ReliableMaxMessageSize(int mtu, uint rcv_wnd)
		{
			return ReliableMaxMessageSize_Unconstrained(mtu, Math.Min(rcv_wnd, 255u));
		}

		public static int UnreliableMaxMessageSize(int mtu)
		{
			return mtu - 5 - 1;
		}

		protected KcpPeer(KcpConfig config, uint cookie)
			: this(config, cookie, 0u)
		{
		}

		protected KcpPeer(KcpConfig config, uint cookie, uint initialTime)
		{
			Reset(config, initialTime);
			this.cookie = cookie;
			Log.Info($"[KCP] {GetType()}: created with cookie={cookie}");
			rawSendBuffer = new byte[config.Mtu];
			unreliableMax = UnreliableMaxMessageSize(config.Mtu);
			reliableMax = ReliableMaxMessageSize(config.Mtu, config.ReceiveWindowSize);
			kcpMessageBuffer = new byte[1 + reliableMax];
			kcpSendBuffer = new byte[1 + reliableMax];
		}

		protected void Reset(KcpConfig config)
		{
			Reset(config, 0u);
		}

		protected void Reset(KcpConfig config, uint initialTime)
		{
			cookie = 0u;
			state = KcpState.Connected;
			lastReceiveTime = initialTime;
			lastPingTime = initialTime;
			currentTime = initialTime;
			kcp = new Kcp(0u, RawSendReliable);
			kcp.SetNoDelay(config.NoDelay ? 1u : 0u, config.Interval, config.FastResend, !config.CongestionWindow);
			kcp.SetWindowSize(config.SendWindowSize, config.ReceiveWindowSize);
			kcp.SetMtu((uint)(config.Mtu - 5));
			kcp.dead_link = config.MaxRetransmits;
			timeout = config.Timeout;
		}

		protected void SetInitialTime(uint time)
		{
			lastReceiveTime = time;
			lastPingTime = time;
			currentTime = time;
		}

		protected abstract void OnAuthenticated();

		protected abstract void OnData(ArraySegment<byte> message, KcpChannel channel);

		protected abstract void OnDisconnected();

		protected virtual bool OnReliableHelloReceived()
		{
			return true;
		}

		protected abstract void OnError(ErrorCode error, string message);

		protected abstract void RawSend(ArraySegment<byte> data);

		private void HandleTimeout(uint time)
		{
			if (Utils.TimeDiff(time, lastReceiveTime) >= timeout)
			{
				OnError(ErrorCode.Timeout, $"{GetType()}: Connection timed out after not receiving any message for {timeout}ms. Disconnecting.");
				Disconnect();
			}
		}

		private void HandleDeadLink()
		{
			if (kcp.state == -1)
			{
				OnError(ErrorCode.Timeout, $"{GetType()}: dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting.");
				Disconnect();
			}
		}

		private void HandlePing(uint time)
		{
			if (Utils.TimeDiff(time, lastPingTime) >= 1000)
			{
				SendPing();
				lastPingTime = time;
			}
		}

		private void HandleChoked()
		{
			int num = kcp.rcv_queue.Count + kcp.snd_queue.Count + kcp.rcv_buf.Count + kcp.snd_buf.Count;
			if (num >= 10000)
			{
				OnError(ErrorCode.Congestion, $"{GetType()}: disconnecting connection because it can't process data fast enough.\n" + $"Queue total {num}>{10000}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" + "* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n* Or perhaps the network is simply too slow on our end, or on the other end.");
				kcp.snd_queue.Clear();
				Disconnect();
			}
		}

		private bool ReceiveNextReliable(uint time, out KcpHeaderReliable header, out ArraySegment<byte> message)
		{
			message = default(ArraySegment<byte>);
			header = KcpHeaderReliable.Ping;
			int num = kcp.PeekSize();
			if (num <= 0)
			{
				return false;
			}
			if (num > kcpMessageBuffer.Length)
			{
				OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {num} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection.");
				Disconnect();
				return false;
			}
			int num2 = kcp.Receive(kcpMessageBuffer, num);
			if (num2 < 0)
			{
				OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={num2}. closing connection.");
				Disconnect();
				return false;
			}
			byte b = kcpMessageBuffer[0];
			if (!KcpHeader.ParseReliable(b, out header))
			{
				OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed to parse header: {b} is not defined in {typeof(KcpHeaderReliable)}.");
				Disconnect();
				return false;
			}
			message = new ArraySegment<byte>(kcpMessageBuffer, 1, num - 1);
			lastReceiveTime = time;
			return true;
		}

		private void TickIncoming_Connected(uint time)
		{
			HandleTimeout(time);
			HandleDeadLink();
			HandlePing(time);
			HandleChoked();
			if (ReceiveNextReliable(time, out var header, out var _))
			{
				switch (header)
				{
				case KcpHeaderReliable.Hello:
					Log.Info($"[KCP] {GetType()}: received hello with cookie={cookie}");
					if (!OnReliableHelloReceived())
					{
						Disconnect();
						break;
					}
					state = KcpState.Authenticated;
					OnAuthenticated();
					break;
				case KcpHeaderReliable.Data:
					OnError(ErrorCode.InvalidReceive, $"[KCP] {GetType()}: received invalid header {header} while Connected. Disconnecting the connection.");
					Disconnect();
					break;
				}
			}
			if (remoteDisconnectRequested)
			{
				remoteDisconnectRequested = false;
				Disconnect();
			}
		}

		private void TickIncoming_Authenticated(uint time)
		{
			HandleTimeout(time);
			HandleDeadLink();
			HandlePing(time);
			HandleChoked();
			KcpHeaderReliable header;
			ArraySegment<byte> message;
			while (ReceiveNextReliable(time, out header, out message))
			{
				switch (header)
				{
				case KcpHeaderReliable.Hello:
					Log.Warning($"{GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection.");
					Disconnect();
					break;
				case KcpHeaderReliable.Data:
					if (message.Count > 0)
					{
						OnData(message, KcpChannel.Reliable);
						break;
					}
					OnError(ErrorCode.InvalidReceive, $"{GetType()}: received empty Data message while Authenticated. Disconnecting the connection.");
					Disconnect();
					break;
				}
			}
			if (remoteDisconnectRequested)
			{
				remoteDisconnectRequested = false;
				Disconnect();
			}
		}

		public virtual void TickIncoming(uint time)
		{
			currentTime = time;
			try
			{
				switch (state)
				{
				case KcpState.Connected:
					TickIncoming_Connected(time);
					break;
				case KcpState.Authenticated:
					TickIncoming_Authenticated(time);
					break;
				case KcpState.Disconnected:
					break;
				}
			}
			catch (SocketException arg)
			{
				OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg}. This is fine.");
				Disconnect();
			}
			catch (ObjectDisposedException arg2)
			{
				OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg2}. This is fine.");
				Disconnect();
			}
			catch (Exception arg3)
			{
				OnError(ErrorCode.Unexpected, $"{GetType()}: unexpected Exception: {arg3}");
				Disconnect();
			}
		}

		public virtual void TickOutgoing(uint time)
		{
			currentTime = time;
			try
			{
				switch (state)
				{
				case KcpState.Connected:
				case KcpState.Authenticated:
					kcp.Update(time);
					break;
				}
			}
			catch (SocketException arg)
			{
				OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg}. This is fine.");
				Disconnect();
			}
			catch (ObjectDisposedException arg2)
			{
				OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg2}. This is fine.");
				Disconnect();
			}
			catch (Exception arg3)
			{
				OnError(ErrorCode.Unexpected, $"{GetType()}: unexpected exception: {arg3}");
				Disconnect();
			}
		}

		public virtual void Tick(uint time)
		{
			TickIncoming(time);
			TickOutgoing(time);
		}

		protected bool OnRawInputReliable(ArraySegment<byte> message)
		{
			int num = kcp.Input(message.Array, message.Offset, message.Count);
			if (num != 0)
			{
				Log.Warning($"[KCP] {GetType()}: Input failed with error={num} for buffer with length={message.Count - 1}");
			}
			return num == 0;
		}

		protected void OnRawInputUnreliable(ArraySegment<byte> message)
		{
			if (message.Count < 1)
			{
				return;
			}
			byte b = message.Array[message.Offset];
			if (!KcpHeader.ParseUnreliable(b, out var header))
			{
				OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed to parse header: {b} is not defined in {typeof(KcpHeaderUnreliable)}.");
				Disconnect();
				return;
			}
			message = new ArraySegment<byte>(message.Array, message.Offset + 1, message.Count - 1);
			switch (header)
			{
			case KcpHeaderUnreliable.Data:
				if (state == KcpState.Authenticated)
				{
					OnData(message, KcpChannel.Unreliable);
					lastReceiveTime = currentTime;
				}
				break;
			case KcpHeaderUnreliable.Disconnect:
				Log.Info($"[KCP] {GetType()}: received disconnect message");
				remoteDisconnectRequested = true;
				break;
			}
		}

		private void RawSendReliable(byte[] data, int length)
		{
			rawSendBuffer[0] = 1;
			Utils.Encode32U(rawSendBuffer, 1, cookie);
			Buffer.BlockCopy(data, 0, rawSendBuffer, 5, length);
			ArraySegment<byte> data2 = new ArraySegment<byte>(rawSendBuffer, 0, length + 1 + 4);
			RawSend(data2);
		}

		private bool SendReliable(KcpHeaderReliable header, ArraySegment<byte> content)
		{
			if (1 + content.Count > kcpSendBuffer.Length)
			{
				OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={reliableMax}");
				return false;
			}
			kcpSendBuffer[0] = (byte)header;
			if (content.Count > 0)
			{
				Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count);
			}
			int num = kcp.Send(kcpSendBuffer, 0, 1 + content.Count);
			if (num < 0)
			{
				OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={num} for content with length={content.Count}");
				return false;
			}
			return true;
		}

		private bool SendUnreliable(KcpHeaderUnreliable header, ArraySegment<byte> content)
		{
			if (content.Count > unreliableMax)
			{
				Log.Error($"[KCP] {GetType()}: Failed to send unreliable message of size {content.Count} because it's larger than UnreliableMaxMessageSize={unreliableMax}");
				return false;
			}
			rawSendBuffer[0] = 2;
			Utils.Encode32U(rawSendBuffer, 1, cookie);
			rawSendBuffer[5] = (byte)header;
			if (content.Count > 0)
			{
				Buffer.BlockCopy(content.Array, content.Offset, rawSendBuffer, 6, content.Count);
			}
			ArraySegment<byte> data = new ArraySegment<byte>(rawSendBuffer, 0, content.Count + 1 + 4 + 1);
			RawSend(data);
			return true;
		}

		public void SendHello()
		{
			Log.Info($"[KCP] {GetType()}: sending handshake to other end with cookie={cookie}");
			SendReliable(KcpHeaderReliable.Hello, default(ArraySegment<byte>));
		}

		public bool SendData(ArraySegment<byte> data, KcpChannel channel)
		{
			if (data.Count == 0)
			{
				OnError(ErrorCode.InvalidSend, $"{GetType()}: tried sending empty message. This should never happen. Disconnecting.");
				Disconnect();
				return false;
			}
			switch (channel)
			{
			case KcpChannel.Reliable:
				return SendReliable(KcpHeaderReliable.Data, data);
			case KcpChannel.Unreliable:
				return SendUnreliable(KcpHeaderUnreliable.Data, data);
			default:
				OnError(ErrorCode.InvalidSend, $"{GetType()}: unknown channel {(byte)channel}.");
				return false;
			}
		}

		private void SendPing()
		{
			SendReliable(KcpHeaderReliable.Ping, default(ArraySegment<byte>));
		}

		private void SendDisconnect()
		{
			for (int i = 0; i < 5; i++)
			{
				SendUnreliable(KcpHeaderUnreliable.Disconnect, default(ArraySegment<byte>));
			}
		}

		public virtual void Disconnect()
		{
			if (state == KcpState.Disconnected || disconnecting)
			{
				return;
			}
			disconnecting = true;
			try
			{
				SendDisconnect();
			}
			catch (SocketException)
			{
			}
			catch (ObjectDisposedException)
			{
			}
			finally
			{
				Log.Info($"[KCP] {GetType()}: Disconnected.");
				state = KcpState.Disconnected;
				disconnecting = false;
				OnDisconnected();
			}
		}
	}
	public enum KcpState
	{
		Connected,
		Authenticated,
		Disconnected
	}
	public static class Log
	{
		public static Action<string> Info = Console.WriteLine;

		public static Action<string> Warning = Console.WriteLine;

		public static Action<string> Error = Console.Error.WriteLine;
	}
}
namespace CardShopCoop
{
	public enum CoopRole
	{
		None,
		Host,
		Client
	}
	public enum InviteState
	{
		Off,
		Resolving,
		Ready,
		LanOnly
	}
	public class CoopCore : MonoBehaviour
	{
		public string StatusLine = "Not connected";

		public string ErrorLine = "";

		public string HostPassword = "";

		public ulong LastFailedLobby;

		public InviteState InviteStatus;

		public string InviteCodeText;

		public string InviteReason;

		public int PortForwardState;

		private IConnectionProvider _connectionProvider;

		private ICoopTransport _providerTransportSource;

		private CoopRuntimeContext _liveRuntimeContext;

		private ISteamBridge _steam;

		private CoopRuntimeBehaviour _runtime;

		private PersistentRuntimeBehaviour _persistentRuntime;

		private CoopUI _ui;

		private readonly MessageRouter _messageRouter = new MessageRouter();

		private readonly MainThreadDispatcher _dispatcher = new MainThreadDispatcher(64, 4096, 3);

		private bool _shutdownCompleted;

		private bool _externalModsDiscovered;

		private bool _sessionInGame;

		private bool _worldReady;

		private int _localConnectionId = -1;

		private int _sessionGeneration = 1;

		private int _inviteGeneration;

		private ulong _autoJoinSteamLobby;

		private int _autoHostSlot = -1;

		private string _autoJoinIp;

		private int _autoPhase;

		private float _autoTimer;

		private string _joinPassword = "";

		private static int _unityThreadId;

		private readonly List<InMsg> _dispatchBuffer = new List<InMsg>(64);

		private readonly HashSet<int> _dispatchRejectedConnections = new HashSet<int>();

		private bool _dispatchBacklogWarned;

		private const int DispatchBudget = 256;

		private const int DispatchBacklogCap = 2048;

		private const int MaxIncomingAdmissionPerFrame = 512;

		private const int MaxDisconnectResultsPerFrame = 64;

		public static CoopCore Instance { get; private set; }

		public static CoopRole Role { get; private set; }

		public static bool WindowBlocksInput => SessionInputRuntime.WindowBlocksInput;

		public static bool IsTearingDown { get; private set; }

		public static bool InSessionWorld
		{
			get
			{
				if ((Object)(object)Instance != (Object)null)
				{
					return Instance._sessionInGame;
				}
				return false;
			}
		}

		public string EffectivePlayerName => PresenceApi.LocalPlayerName;

		public bool UsingSteamPersona => !string.Equals(EffectivePlayerName, CoopPlugin.PlayerName.Value, StringComparison.Ordinal);

		public ICoopTransport Net { get; private set; }

		public ISteamBridge Steam => _steam;

		public bool IsSteamSession { get; private set; }

		public string LastDisconnectCode { get; private set; } = "";

		public string LastDisconnectReason { get; private set; } = "";

		public bool LastDisconnectRetryable { get; private set; }

		internal static int SessionGeneration
		{
			get
			{
				CoopCore instance = Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					return Volatile.Read(in instance._sessionGeneration);
				}
				return -1;
			}
		}

		internal static int LocalConnectionId
		{
			get
			{
				if (!((Object)(object)Instance == (Object)null))
				{
					return Instance._localConnectionId;
				}
				return -1;
			}
		}

		internal CoopRuntimeContext LiveContext => _liveRuntimeContext;

		private IConnectionProvider ConnectionProvider
		{
			get
			{
				return _connectionProvider;
			}
			set
			{
				if (_connectionProvider == value)
				{
					OnProviderTransportChanged();
					return;
				}
				if (_connectionProvider != null)
				{
					_connectionProvider.TransportChanged -= OnProviderTransportChanged;
					_connectionProvider.PeerConnected -= OnProviderPeerConnected;
					_connectionProvider.Failed -= OnProviderFailed;
				}
				_connectionProvider = value;
				if (_connectionProvider != null)
				{
					_connectionProvider.TransportChanged += OnProviderTransportChanged;
					_connectionProvider.PeerConnected += OnProviderPeerConnected;
					_connectionProvider.Connected += OnProviderConnected;
					_connectionProvider.Failed += OnProviderFailed;
				}
				OnProviderTransportChanged();
			}
		}

		internal static bool IsSessionGeneration(int generation)
		{
			CoopCore instance = Instance;
			if ((Object)(object)instance != (Object)null && generation >= 0)
			{
				return Volatile.Read(in instance._sessionGeneration) == generation;
			}
			return false;
		}

		private void Awake()
		{
			Instance = this;
			_unityThreadId = Thread.CurrentThread.ManagedThreadId;
			_messageRouter.RegisterCoreAttributedHandlers(this);
			_dispatcher.Start();
			PresenceApi.ConfigureIdentity(delegate
			{
				string text = ((_steam == null) ? "" : _steam.LocalPersonaName);
				return (!string.IsNullOrWhiteSpace(text)) ? text : CoopPlugin.PlayerName.Value;
			});
			SaveTransferApi.ConfigureCore(() => Role, () => SessionGeneration, IsSessionGeneration, (Action action) => _dispatcher.TryEnqueue("save-transfer", action), null, delegate
			{
				Send(1, (INetMessage)(object)new FullyJoinedMessage());
			}, delegate(string reason)
			{
				string text = reason ?? "world transfer failed";
				if (!_dispatcher.TryEnqueue("save-transfer-failure", delegate
				{
					ErrorLine = text;
					SetStatus(text);
				}))
				{
					CoopPlugin.Log.LogError((object)("coop: could not publish save-transfer failure on the main thread: " + text));
				}
			}, delegate(PeerConnection connection, string reason)
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Expected O, but got Unknown
				Net?.GracefulDisconnect(connection, new DisconnectInfo("world transfer failed: " + reason, false, "transfer_failed", true, (ConnectionState)2));
			});
			_ui = new CoopUI();
			_persistentRuntime = ((Component)this).gameObject.AddComponent<PersistentRuntimeBehaviour>();
			_persistentRuntime.InitializePersistent(new CoopRuntimeContext(_messageRouter, InGameLevel, Broadcast, Send, ConnectionIds, PresenceApi.PeerName, delegate(string text, float seconds)
			{
				SetStatus(text);
			}, () => SaveTransferApi.PreloadHold));
			SceneManager.sceneLoaded += OnSceneLoaded;
			ReadCommandLine();
			_steam = SteamBridge.TryCreate();
			if (_steam == null && PlatformProbe.SteamworksPresent)
			{
				CoopPlugin.Log.LogInfo((object)"Steamworks assembly present but not functional; LAN and direct IP only.");
			}
			if (_steam != null)
			{
				_steam.Init();
				_steam.OnError = delegate(string error)
				{
					ErrorLine = error ?? "Steam error";
					CoopPlugin.Log.LogWarning((object)ErrorLine);
				};
				_steam.OnLobbyLive = delegate(ulong lobby)
				{
					SetStatus("Hosting via Steam - click 'Invite friend'");
					CoopPlugin.Log.LogInfo((object)("steam: lobby live " + lobby));
				};
				_steam.OnInviteAccepted = delegate(ulong lobby)
				{
					JoinSteamLobby(lobby);
				};
			}
			RegisterEventListeners();
			CatalogApi.EnumLendState();
		}

		private void RegisterEventListeners()
		{
			CEventManager.AddListener<CEventPlayer_OnOpenCardPack>((EventDelegate<CEventPlayer_OnOpenCardPack>)OnLocalPackOpened);
			CEventManager.AddListener<CEventPlayer_GameDataFinishLoaded>((EventDelegate<CEventPlayer_GameDataFinishLoaded>)OnGameDataFinishLoaded);
			CEventManager instance = CSingleton<CEventManager>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				CoopPlugin.Log.LogInfo((object)("[events] co-op listeners attached to CEventManager '" + ((Object)((Component)instance).gameObject).name + "'"));
			}
		}

		private void ReadCommandLine()
		{
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			for (int i = 0; i < commandLineArgs.Length; i++)
			{
				string text = commandLineArgs[i] ?? "";
				ulong result2;
				if (text.StartsWith("-coopautohost=", StringComparison.Ordinal) && int.TryParse(text.Substring(14), out var result))
				{
					_autoHostSlot = result;
				}
				else if (text.StartsWith("-coopautojoin=", StringComparison.Ordinal))
				{
					_autoJoinIp = text.Substring(14);
				}
				else if (text == "+connect_lobby" && i + 1 < commandLineArgs.Length && ulong.TryParse(commandLineArgs[i + 1], out result2))
				{
					_autoJoinSteamLobby = result2;
				}
			}
		}

		public void JoinSteamLobby(ulong lobby, string password = "")
		{
			ErrorLine = "";
			ClearDisconnectMetadata();
			if (Role != CoopRole.None)
			{
				ErrorLine = "Already in a session.";
				return;
			}
			if (InGameLevel())
			{
				ErrorLine = "Go to the main menu first, then accept the invite again.";
				return;
			}
			if (_steam == null)
			{
				ErrorLine = "This build has no Steam support - use LAN or direct IP.";
				return;
			}
			if (!_steam.SteamAvailable())
			{
				ErrorLine = "Steam isn't running.";
				return;
			}
			BeginSession(CoopRole.Client, steam: true);
			IsSteamSession = true;
			_joinPassword = password ?? "";
			LastFailedLobby = lobby;
			try
			{
				SetConnectionProvider(new SteamConnectionProvider(_steam));
				ConnectionProvider.Join(lobby.ToString(), 0, _joinPassword);
				SetStatus("Joining Steam lobby...");
			}
			catch (Exception ex)
			{
				AbortSessionStart("Could not join: " + ex.Message);
			}
		}

		public void StartHostingSteam(bool isPublic, string lobbyName, string password, int maxPlayers)
		{
			ErrorLine = "";
			ClearDisconnectMetadata();
			if (maxPlayers < 2 || maxPlayers > 250)
			{
				ErrorLine = $"Player count must be between {2} and {250}.";
				return;
			}
			if (Role != CoopRole.None)
			{
				ErrorLine = "Already in a session.";
				return;
			}
			if (!TryAdmitHostStart(out var error))
			{
				ErrorLine = error;
				return;
			}
			if (_steam == null)
			{
				ErrorLine = "This build has no Steam support - use LAN instead.";
				return;
			}
			if (!_steam.SteamAvailable())
			{
				ErrorLine = "Steam isn't running - use LAN instead.";
				return;
			}
			BeginSession(CoopRole.Host, steam: true);
			HostPassword = password ?? "";
			try
			{
				SetConnectionProvider(new SteamConnectionProvider(_steam));
				ConnectionProvider.Host(0, isPublic, lobbyName, HostPassword, maxPlayers);
				SetStatus("Creating Steam lobby...");
			}
			catch (Exception ex)
			{
				AbortSessionStart("Could not host: " + ex.Message);
			}
		}

		public void OpenSteamInvite()
		{
			_steam?.OpenInviteDialog();
		}

		public void StartHosting()
		{
			ErrorLine = "";
			ClearDisconnectMetadata();
			if (Role != CoopRole.None)
			{
				ErrorLine = "Already in a session.";
				return;
			}
			if (!TryAdmitHostStart(out var error))
			{
				ErrorLine = error;
				return;
			}
			BeginSession(CoopRole.Host, steam: false);
			try
			{
				SetConnectionProvider(new LanConnectionProvider());
				ConnectionProvider.Host(CoopPlugin.Port.Value);
				SetStatus("Hosting - waiting for a player...");
				if (CoopPlugin.AutoLanPassword.Value && string.IsNullOrEmpty(HostPassword))
				{
					HostPassword = GenerateSessionPassword();
				}
				BeginInviteResolve();
			}
			catch (Exception ex)
			{
				AbortSessionStart("Could not host: " + ex.Message);
			}
		}

		public void JoinHostedGame(string ip)
		{
			JoinHostedGame(ip, CoopPlugin.Port.Value, "");
		}

		public void JoinHostedGame(string ip, int joinPort, string password)
		{
			ErrorLine = "";
			ClearDisconnectMetadata();
			if (Role != CoopRole.None)
			{
				ErrorLine = "Already in a session.";
				return;
			}
			if (InGameLevel())
			{
				ErrorLine = "Join from the main menu (Title screen).";
				return;
			}
			ip = (ip ?? "").Trim();
			if (ip.Length == 0)
			{
				ErrorLine = "Enter the host's IP address.";
				return;
			}
			CoopPlugin.LastJoinIP.Value = ip;
			BeginSession(CoopRole.Client, steam: false);
			_joinPassword = password ?? "";
			try
			{
				SetConnectionProvider(new LanConnectionProvider());
				int port = ((joinPort > 0 && joinPort <= 65535) ? joinPort : CoopPlugin.Port.Value);
				ConnectionProvider.Join(ip, port);
				SetStatus("Connecting to " + ip + "...");
			}
			catch (Exception ex)
			{
				AbortSessionStart("Could not connect: " + ex.Message);
			}
		}

		public void Disconnect()
		{
			ClearDisconnectMetadata();
			Shutdown("disconnected");
		}

		public void SendEmote()
		{
			PresenceApi.SendEmote();
		}

		private void BeginSession(CoopRole role, bool steam)
		{
			_shutdownCompleted = false;
			_dispatcher.Start();
			Interlocked.Increment(ref _sessionGeneration);
			Role = role;
			IsSteamSession = steam;
			_localConnectionId = ((role != CoopRole.Host) ? (-1) : 0);
			if (role == CoopRole.Client)
			{
				SaveTransferApi.SetGuestBorrowedWorld(borrowed: true);
			}
		}

		private void OnProviderConnected()
		{
		}

		private void OnProviderFailed(string reason)
		{
			AbortSessionStart(reason ?? "connection failed");
		}

		private void OnProviderTransportChanged()
		{
			ICoopTransport coopTransport = _connectionProvider?.Transport;
			if (coopTransport != _providerTransportSource)
			{
				_providerTransportSource = coopTransport;
				Net = coopTransport;
				if (Net != null && Role != CoopRole.None && (Object)(object)_runtime == (Object)null)
				{
					ActivateLiveModuleHooks();
				}
			}
		}

		private void OnProviderPeerConnected(PeerConnection connection)
		{
			if (connection != null && Net != null && Role != CoopRole.Host && Role == CoopRole.Client)
			{
				if (Thread.CurrentThread.ManagedThreadId == _unityThreadId)
				{
					SendHello();
				}
				else if (!TryEnqueueMainThread(SendHello))
				{
					CoopPlugin.Log.LogError((object)"coop: could not queue the application Hello on the Unity thread");
				}
			}
		}

		private void SetConnectionProvider(IConnectionProvider provider)
		{
			ConnectionProvider = provider ?? throw new ArgumentNullException("provider");
		}

		private void ActivateLiveModuleHooks()
		{
			if ((Object)(object)_runtime != (Object)null)
			{
				return;
			}
			CoopRuntimeContext context = (_liveRuntimeContext = new CoopRuntimeContext(_messageRouter, InGameLevel, Broadcast, Send, ConnectionIds, PresenceApi.PeerName, delegate(string text, float seconds)
			{
				SetStatus(text);
			}, () => SaveTransferApi.PreloadHold, delegate(int connectionId, DisconnectInfo info)
			{
				PeerConnection val = ConnectionFor(connectionId);
				if (val != null)
				{
					Net?.GracefulDisconnect(val, info);
				}
			}, Relay));
			_runtime = ((Role == CoopRole.Host) ? ((CoopRuntimeBehaviour)((Component)this).gameObject.AddComponent<ServerRuntimeBehaviour>()) : ((CoopRuntimeBehaviour)((Component)this).gameObject.AddComponent<ClientRuntimeBehaviour>()));
			try
			{
				_runtime.Initialize(context);
			}
			catch
			{
				_liveRuntimeContext = null;
				Object.Destroy((Object)(object)_runtime);
				_runtime = null;
				throw;
			}
		}

		private void DeactivateLiveModuleHooks()
		{
			if ((Object)(object)_runtime == (Object)null)
			{
				return;
			}
			CoopRuntimeBehaviour runtime = _runtime;
			_runtime = null;
			try
			{
				runtime.Shutdown();
			}
			catch (Exception ex)
			{
				ManualLogSource log = CoopPlugin.Log;
				if (log != null)
				{
					log.LogError((object)("Co-op runtime shutdown failed: " + ex));
				}
			}
			finally
			{
				Object.Destroy((Object)(object)runtime);
			}
		}

		private void AbortSessionStart(string reason)
		{
			ErrorLine = reason ?? "session could not start";
			CoopPlugin.Log.LogError((object)("coop: " + ErrorLine));
			Shutdown(ErrorLine);
		}

		private void SendHello()
		{
			CatalogHelloData catalogHelloData = CatalogHandshake.BuildHelloData();
			List<string> frozenMessageCatalog = GetFrozenMessageCatalog();
			HelloMessage message = new HelloMessage
			{
				WireVersion = Msg.WireVersion,
				Version = "2.0.6",
				PlayerName = EffectivePlayerName,
				SteamId = ((_steam == null) ? 0 : _steam.LocalSteamId),
				Password = _joinPassword,
				PluginHash = ModParity.PluginHash(),
				EnumHash = catalogHelloData.EnumHash,
				CardsHash = catalogHelloData.CardsHash,
				PluginList = ModParity.PluginList(),
				CardsList = catalogHelloData.CardsList,
				EnumIdentity = catalogHelloData.EnumIdentity,
				GameVersion = (Application.version ?? ""),
				UnityVersion = (Application.unityVersion ?? ""),
				MessageCatalog = frozenMessageCatalog,
				MessageReliability = GetFrozenMessageReliability(frozenMessageCatalog)
			};
			PeerConnection val = ConnectionFor(1);
			if (val == null || !(Net is ICoopHandshakeTransport coopHandshakeTransport))
			{
				throw new InvalidOperationException("could not send the application Hello: no live handshake transport");
			}
			coopHandshakeTransport.SendHandshake(val, (INetMessage)(object)message);
		}

		private static List<string> GetFrozenMessageCatalog()
		{
			IReadOnlyList<string> orderedWireNames = MessageRegistry.CurrentSnapshot.OrderedWireNames;
			List<string> list = new List<string>(orderedWireNames.Count);
			for (int i = 0; i < orderedWireNames.Count; i++)
			{
				string text = orderedWireNames[i];
				if (text == null)
				{
					throw new InvalidOperationException("the frozen message registry contains a DTO without a Type.FullName");
				}
				list.Add(text);
			}
			return list;
		}

		private static byte[] GetFrozenMessageReliability(IReadOnlyList<string> messageCatalog)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Invalid comparison between Unknown and I4
			if (messageCatalog == null)
			{
				throw new ArgumentNullException("messageCatalog");
			}
			byte[] array = new byte[(messageCatalog.Count + 7) / 8];
			ProtocolSnapshot currentSnapshot = MessageRegistry.CurrentSnapshot;
			for (int i = 0; i < messageCatalog.Count; i++)
			{
				if (!currentSnapshot.TryGet(messageCatalog[i], out var descriptor))
				{
					throw new InvalidOperationException("the frozen message registry is missing " + messageCatalog[i]);
				}
				if ((int)descriptor.Reliability == 1)
				{
					array[i / 8] |= (byte)(1 << (i & 7));
				}
			}
			return array;
		}

		private static bool TryValidateMessageReliability(byte[] peerBits, byte[] expectedBits, int messageCount, out string reason)
		{
			if (peerBits == null)
			{
				reason = "message reliability bitset is missing";
				return false;
			}
			int num = (messageCount + 7) / 8;
			if (peerBits.Length != num)
			{
				reason = "message reliability bitset length differs (peer " + peerBits.Length + ", local " + num + ")";
				return false;
			}
			if (expectedBits == null || expectedBits.Length != num)
			{
				throw new InvalidOperationException("local message reliability bitset is invalid");
			}
			if (num > 0 && (messageCount & 7) != 0)
			{
				byte b = (byte)(255 << (messageCount & 7));
				if ((peerBits[num - 1] & b) != 0)
				{
					reason = "message reliability bitset has non-zero unused tail bits";
					return false;
				}
				if ((expectedBits[num - 1] & b) != 0)
				{
					throw new InvalidOperationException("local message reliability bitset has non-zero unused tail bits");
				}
			}
			for (int i = 0; i < num; i++)
			{
				if (peerBits[i] != expectedBits[i])
				{
					reason = "message reliability differs at byte " + i;
					return false;
				}
			}
			reason = null;
			return true;
		}

		private static bool TryValidateMessageCatalog(IList<string> peerCatalog, IReadOnlyList<string> expectedCatalog, out string reason)
		{
			if (peerCatalog == null)
			{
				reason = "message catalog is missing";
				return false;
			}
			if (peerCatalog.Count != expectedCatalog.Count)
			{
				reason = "message catalog count differs (peer " + peerCatalog.Count + ", local " + expectedCatalog.Count + ")";
				return false;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < peerCatalog.Count; i++)
			{
				string text = peerCatalog[i];
				if (text == null)
				{
					reason = "message catalog contains a null name at index " + i;
					return false;
				}
				if (!hashSet.Add(text))
				{
					reason = "message catalog contains a duplicate name at index " + i;
					return false;
				}
			}
			for (int j = 0; j < expectedCatalog.Count; j++)
			{
				if (!string.Equals(peerCatalog[j], expectedCatalog[j], StringComparison.Ordinal))
				{
					reason = "message catalog differs at index " + j + " (peer '" + BoundedMessageName(peerCatalog[j]) + "', local '" + BoundedMessageName(expectedCatalog[j]) + "')";
					return false;
				}
			}
			reason = null;
			return true;
		}

		private static string BoundedMessageName(string name)
		{
			if (name == null)
			{
				return "<null>";
			}
			if (name.Length > 96)
			{
				return name.Substring(0, 96) + "...";
			}
			return name;
		}

		private void ActivateMessageIdsBeforeTransfer(ICoopTransport transport, PeerConnection connection, int generation)
		{
			if (!IsSessionGeneration(generation))
			{
				throw new InvalidOperationException("session ended before message-id activation");
			}
			transport.ActivateMessageIds(connection);
		}

		[MessageHandler(typeof(HelloMessage))]
		private void HandleHello(MessageContext context, HelloMessage hello)
		{
			//IL_0427: Unknown result type (might be due to invalid IL or missing references)
			//IL_042c: Unknown result type (might be due to invalid IL or missing references)
			PeerConnection connection = context?.Connection;
			if (Role != CoopRole.Host || connection == null || hello == null || !ExpectControlState(connection, "Hello", (ConnectionState)1))
			{
				return;
			}
			string text = hello.PlayerName ?? "";
			if (hello.WireVersion != Msg.WireVersion)
			{
				PeerConnection connection2 = connection;
				int wireVersion = Msg.WireVersion;
				RejectConn(connection2, "wire protocol mismatch - host uses protocol " + wireVersion);
				return;
			}
			if (!string.Equals(hello.Version, "2.0.6", StringComparison.Ordinal))
			{
				RejectConn(connection, "version mismatch - host runs 2.0.6");
				return;
			}
			if (!string.Equals(hello.Password ?? "", HostPassword, StringComparison.Ordinal))
			{
				RejectConn(connection, "wrong password", retryable: true);
				return;
			}
			if ((hello.GameVersion ?? "") != (Application.version ?? "") || (hello.UnityVersion ?? "") != (Application.unityVersion ?? ""))
			{
				if (!CoopPlugin.AllowCrossBuildJoin.Value)
				{
					RejectConn(connection, "your GAME build doesn't match the host's");
					return;
				}
				CoopPlugin.Log.LogWarning((object)("AllowCrossBuildJoin is ON - accepting " + text + " across game builds; save compatibility is not guaranteed."));
			}
			if (!string.Equals(hello.PluginHash, ModParity.PluginHash(), StringComparison.Ordinal))
			{
				RejectConn(connection, ModParity.DescribeMismatch(hello.PluginList, 1024));
				return;
			}
			List<string> messageCatalog = GetFrozenMessageCatalog();
			if (!TryValidateMessageCatalog(hello.MessageCatalog, messageCatalog, out var reason))
			{
				CoopPlugin.Log.LogWarning((object)("message catalog handshake rejected for connection " + connection.Id + ": " + reason));
				RejectConn(connection, "message catalog mismatch: " + reason);
				return;
			}
			byte[] messageReliability = GetFrozenMessageReliability(messageCatalog);
			if (!TryValidateMessageReliability(hello.MessageReliability, messageReliability, messageCatalog.Count, out var reason2))
			{
				CoopPlugin.Log.LogWarning((object)("message reliability handshake rejected for connection " + connection.Id + ": " + reason2));
				RejectConn(connection, "message reliability mismatch: " + reason2);
				return;
			}
			if (!CatalogHandshake.IsIdentityUsable(hello.EnumIdentity))
			{
				CoopPlugin.Log.LogWarning((object)("catalog handshake rejected connection " + connection.Id + ": invalid or empty client enum identity."));
				RejectConn(connection, "invalid custom-content catalog from client");
				return;
			}
			CatalogEnumValidation catalogEnumValidation = CatalogHandshake.ValidateEnums(text, hello.EnumHash, hello.EnumIdentity);
			if (!catalogEnumValidation.HashesAreConsistent)
			{
				string text2 = catalogEnumValidation.HashFailureReason ?? "the catalog hash did not match the decoded catalog";
				CoopPlugin.Log.LogWarning((object)("catalog handshake rejected connection " + connection.Id + ": " + text2));
				RejectConn(connection, "invalid custom-card catalog: " + text2);
				return;
			}
			if (catalogEnumValidation.Conflicts.Count > 0)
			{
				CoopPlugin.Log.LogWarning((object)("catalog handshake rejected connection " + connection.Id + ": custom-content name set differs (" + catalogEnumValidation.Conflicts.Count + " entries)."));
				RejectConn(connection, CatalogHandshake.DescribeEnumKeysMismatch(catalogEnumValidation.Conflicts));
				return;
			}
			if (!CatalogHandshake.TryValidateCards(hello.CardsHash, hello.CardsList, out var reason3))
			{
				RejectConn(connection, reason3);
				return;
			}
			string text3 = ResolvePeerName(hello.SteamId, text);
			PresenceApi.SetPeerName(connection.Id, text3);
			if (!connection.TryTransition((ConnectionState)2))
			{
				CoopPlugin.Log.LogWarning((object)("Ignoring Hello from connection in invalid phase " + ((object)connection.State/*cast due to .constrained prefix*/).ToString()));
				return;
			}
			try
			{
				_runtime?.ClientJoined(connection);
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogError((object)("Feature ClientJoined callbacks failed: " + ex));
			}
			SetStatus("Hosting - " + text3 + " joined!");
			CoopPlugin.Log.LogInfo((object)(text3 + " passed admission; starting world transfer"));
			CatalogWelcomeBlobs blobs = CatalogHandshake.BuildWelcomeBlobs();
			ICoopTransport transport = Net;
			int transferGeneration = SessionGeneration;
			if (transport == null)
			{
				RejectConn(connection, "transport disappeared before world transfer");
			}
			else if (!SaveTransferApi.TryAuthorizeAndSend(connection, delegate(SaveTransferOffer offer)
			{
				((transport as ICoopHandshakeTransport) ?? throw new InvalidOperationException("could not send Welcome: no live handshake transport")).SendHandshake(connection, (INetMessage)(object)new WelcomeMessage
				{
					WireVersion = Msg.WireVersion,
					Version = "2.0.6",
					HostName = EffectivePlayerName,
					SteamId = ((_steam == null) ? 0 : _steam.LocalSteamId),
					SaveLength = offer.SaveLength,
					BundleLength = offer.BundleLength,
					SidecarsComplete = offer.SidecarsComplete,
					SidecarWarning = offer.SidecarWarning,
					SelfId = connection.Id,
					HostEnumIdentity = blobs.EnumIdentity,
					HostCardsBlob = blobs.CardsBlob,
					MessageCatalog = new List<string>(messageCatalog),
					MessageReliability = (byte[])messageReliability.Clone()
				});
				return true;
			}, delegate
			{
				ActivateMessageIdsBeforeTransfer(transport, connection, transferGeneration);
			}))
			{
				RejectConn(connection, "could not start the world transfer");
			}
		}

		[MessageHandler(typeof(WelcomeMessage))]
		private void HandleWelcome(MessageContext context, WelcomeMessage welcome)
		{
			PeerConnection val = context?.Connection;
			if (Role != CoopRole.Client || val == null || welcome == null || !ExpectControlState(val, "Welcome", (ConnectionState)1))
			{
				return;
			}
			if (welcome.WireVersion != Msg.WireVersion || !string.Equals(welcome.Version, "2.0.6", StringComparison.Ordinal))
			{
				Shutdown("plugin or wire version mismatch");
				return;
			}
			List<string> frozenMessageCatalog = GetFrozenMessageCatalog();
			if (!TryValidateMessageCatalog(welcome.MessageCatalog, frozenMessageCatalog, out var reason))
			{
				CoopPlugin.Log.LogWarning((object)("message catalog handshake rejected from host: " + reason));
				Shutdown("message catalog mismatch: " + reason);
				return;
			}
			byte[] frozenMessageReliability = GetFrozenMessageReliability(frozenMessageCatalog);
			if (!TryValidateMessageReliability(welcome.MessageReliability, frozenMessageReliability, frozenMessageCatalog.Count, out var reason2))
			{
				CoopPlugin.Log.LogWarning((object)("message reliability handshake rejected from host: " + reason2));
				Shutdown("message reliability mismatch: " + reason2);
				return;
			}
			Net.ActivateMessageIds(val);
			bool flag = CatalogHandshake.IsIdentityUsable(welcome.HostEnumIdentity);
			CatalogBlobData catalogBlobData = CatalogHandshake.ReadBlob(welcome.HostCardsBlob);
			if (!flag || !catalogBlobData.IsValid)
			{
				string text = (flag ? ("host CardsBlob: " + catalogBlobData.FailureReason) : "host enum identity is invalid or empty");
				CoopPlugin.Log.LogError((object)("catalog handshake received an invalid host blob: " + text));
				Shutdown("host sent an invalid custom-card catalog");
				return;
			}
			CatalogIdMap.Build(welcome.HostEnumIdentity);
			_localConnectionId = welcome.SelfId;
			PresenceApi.SetPeerName(1, ResolvePeerName(welcome.SteamId, welcome.HostName));
			if (!SaveTransferApi.TryBeginClientTransfer(welcome.SaveLength, welcome.BundleLength, welcome.SidecarsComplete, welcome.SidecarWarning))
			{
				Shutdown("invalid world transfer authorization");
				return;
			}
			if (!val.TryTransition((ConnectionState)2))
			{
				Shutdown("invalid handshake phase");
				return;
			}
			try
			{
				_runtime?.ClientJoined(val);
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogError((object)("Feature ClientJoined callbacks failed: " + ex));
			}
			SetStatus("Downloading " + (PresenceApi.PeerName(1) ?? "host") + "'s shop...");
		}

		[MessageHandler(typeof(ByeMessage))]
		private void HandleBye(MessageContext context, ByeMessage message)
		{
			PeerConnection connection = context?.Connection;
			ConnectionState[] array = new ConnectionState[3];
			RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
			if (ExpectControlState(connection, "Bye", (ConnectionState[])(object)array))
			{
				string text = (string.IsNullOrEmpty(message?.Reason) ? "the host ended the session" : message.Reason);
				if (Role == CoopRole.Client)
				{
					ErrorLine = text;
					Shutdown("rejected: " + text);
				}
				else
				{
					Net?.Kick(connection);
				}
			}
		}

		private void Send(int connectionId, INetMessage message)
		{
			PeerConnection val = ConnectionFor(connectionId);
			if (val == null || Net == null)
			{
				CoopPlugin.Log.LogError((object)("application send failed: no live connection " + connectionId + " for message " + ((object)message)?.GetType().FullName));
				throw new InvalidOperationException("application send has no live connection " + connectionId + " for message " + ((object)message)?.GetType().FullName);
			}
			Net.Send(val, message);
		}

		private void Broadcast(INetMessage message)
		{
			if (Net == null)
			{
				CoopPlugin.Log.LogError((object)("application broadcast failed: no live transport for message " + ((object)message)?.GetType().FullName));
				throw new InvalidOperationException("application broadcast has no live transport for message " + ((object)message)?.GetType().FullName);
			}
			Net.Broadcast(message);
		}

		private void Relay(int exceptConnectionId, INetMessage message)
		{
			if (Net == null)
			{
				CoopPlugin.Log.LogError((object)("application relay failed: no live transport for message " + ((object)message)?.GetType().FullName));
				throw new InvalidOperationException("application relay has no live transport for message " + ((object)message)?.GetType().FullName);
			}
			((Net as ICoopRelayTransport) ?? throw new InvalidOperationException("the live transport has no relay seam for message " + ((object)message)?.GetType().FullName)).Relay(message, exceptConnectionId);
		}

		private PeerConnection ConnectionFor(int id)
		{
			if (Net == null)
			{
				return null;
			}
			foreach (PeerConnection connection in Net.Connections)
			{
				if (connection.Id == id)
				{
					return connection;
				}
			}
			return null;
		}

		private IReadOnlyList<int> ConnectionIds()
		{
			if (Net != null)
			{
				return Net.Connections.Select((PeerConnection c) => c.Id).ToArray();
			}
			return Array.Empty<int>();
		}

		private void RejectConn(PeerConnection connection, string reason, bool retryable = false)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			if (connection != null)
			{
				CoopPlugin.Log.LogWarning((object)("rejected connection " + connection.Id + ": " + reason));
				Net?.GracefulDisconnect(connection, new DisconnectInfo(reason, false, "rejected", retryable, (ConnectionState)1));
			}
		}

		private void Shutdown(string reason, DisconnectInfo detail = null)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if (_shutdownCompleted)
			{
				return;
			}
			_shutdownCompleted = true;
			IsTearingDown = true;
			Interlocked.Increment(ref _sessionGeneration);
			_dispatcher.InvalidateEpoch();
			if (Net != null)
			{
				foreach (PeerConnection connection in Net.Connections)
				{
					Net.GracefulDisconnect(connection, (DisconnectInfo)(((object)detail) ?? ((object)new DisconnectInfo(reason ?? "session ended", false, "shutdown", true, (ConnectionState)4))));
				}
				Net.Stop();
				ConnectionEvent result;
				while (Net.Disconnects.TryDequeue(out result))
				{
					_runtime?.ClientDisconnected(result.Connection, result.Disconnect);
				}
			}
			try
			{
				DeactivateLiveModuleHooks();
			}
			finally
			{
				try
				{
					_persistentRuntime?.SessionStopped();
				}
				finally
				{
					ConnectionProvider?.Dispose();
					ConnectionProvider = null;
					Net = null;
					_liveRuntimeContext = null;
				}
			}
			SaveTransferApi.ClearBorrowedWorldIfSafe(InGameLevel());
			SaveTransferRuntimeReset();
			PresenceApi.ClearPeerNames();
			HudApi.Clear();
			CatalogIdMap.Clear();
			WorldHostBehaviour.ResetCardState();
			WorldClientBehaviour.ResetCardState();
			SceneRef.ClearAll();
			_dispatchBuffer.Clear();
			_dispatchRejectedConnections.Clear();
			_localConnectionId = -1;
			_sessionInGame = false;
			IsSteamSession = false;
			HostPassword = "";
			_joinPassword = "";
			_steam?.Leave();
			Application.runInBackground = false;
			Role = CoopRole.None;
			IsTearingDown = false;
			SetStatus((reason == null) ? "Not connected" : ("Not connected (" + reason + ")"));
		}

		private static void SaveTransferRuntimeReset()
		{
			SaveTransferRuntime.ResetForShutdown();
		}

		private void Update()
		{
			//IL_0095: 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)
			FrameCounter.Tick();
			if (!_externalModsDiscovered)
			{
				_externalModsDiscovered = true;
				ExternalCoopMods.Instance.EnsureDiscovered();
				_persistentRuntime?.IncludeExternalPersistentBehaviours();
				ExternalCoopMods.Instance.NotifyPersistentScanComplete();
			}
			PerfProbe.BeginFrame();
			PerfProbe.FlushThreadMetrics();
			using (PerfProbe.Sample("core.dispatcher"))
			{
				_dispatcher.Drain();
			}
			using (PerfProbe.Sample("core.auto-tick"))
			{
				AutoTick(Time.deltaTime);
			}
			if (Input.GetKeyDown(CoopPlugin.UiToggleKey.Value))
			{
				_ui.Visible = !_ui.Visible;
			}
			SessionInputRuntime.SetWindowModal(_ui.Visible && InGameplayScene());
			if (Role != CoopRole.None && Input.GetKeyDown(CoopPlugin.EmoteKey.Value) && !CoopUI.TextFieldFocused && !NativeTextInputFocused())
			{
				SendEmote();
			}
			if (SaveTransferApi.GuestBorrowedWorld && Role == CoopRole.None && !InGameLevel())
			{
				SaveTransferApi.ClearBorrowedWorldIfSafe(inGame: false);
			}
			if (Net == null)
			{
				return;
			}
			if (!_sessionInGame && InGameLevel())
			{
				_sessionInGame = true;
				CoopPlugin.Log.LogInfo((object)"co-op session world gate opened");
			}
			Application.runInBackground = true;
			using (PerfProbe.Sample("core.disconnect-drain"))
			{
				DrainDisconnects();
			}
			if (!SaveTransferApi.PreloadHold)
			{
				using (PerfProbe.Sample("core.admit-incoming"))
				{
					AdmitIncoming();
				}
				using (PerfProbe.Sample("core.dispatch-incoming"))
				{
					DispatchIncoming();
				}
				_ = Net;
			}
		}

		private void DrainDisconnects()
		{
			int num = 0;
			ConnectionEvent result;
			while (num++ < 64 && Net != null && Net.Disconnects.TryDequeue(out result))
			{
				int id = result.Connection.Id;
				_runtime?.ClientDisconnected(result.Connection, result.Disconnect);
				PresenceApi.RemovePeer(id);
				if (Role == CoopRole.Client)
				{
					DisconnectInfo disconnect = result.Disconnect;
					RememberDisconnect(disconnect);
					ErrorLine = ((disconnect == null) ? "Lost connection to the host." : ("Host disconnected (" + disconnect.Code + "): " + disconnect.Reason));
					CoopPlugin.Log.LogInfo((object)("host disconnected: " + (((disconnect != null) ? disconnect.Code : null) ?? "?") + " / " + (((disconnect != null) ? disconnect.Reason : null) ?? "no detail")));
					Shutdown(((disconnect != null) ? disconnect.Reason : null) ?? "host connection lost", disconnect);
					break;
				}
				ManualLogSource log = CoopPlugin.Log;
				string[] obj = new string[6]
				{
					"peer ",
					id.ToString(),
					" disconnected: ",
					null,
					null,
					null
				};
				DisconnectInfo disconnect2 = result.Disconnect;
				obj[3] = ((disconnect2 != null) ? disconnect2.Code : null) ?? "?";
				obj[4] = " / ";
				DisconnectInfo disconnect3 = result.Disconnect;
				obj[5] = ((disconnect3 != null) ? disconnect3.Reason : null) ?? "no detail";
				log.LogInfo((object)string.Concat(obj));
				SetStatus((Net.ConnectionCount == 0) ? "Hosting - waiting for a player..." : ("Hosting - " + Net.ConnectionCount + " player(s)"));
			}
		}

		private void AdmitIncoming()
		{
			int num = 0;
			InMsg message;
			while (num++ < 512 && Net != null && Net.TryDequeueIncoming(out message))
			{
				if (message.Connection != null && _dispatchRejectedConnections.Contains(message.Connection.Id))
				{
					continue;
				}
				if (_dispatchBuffer.Count < 2048)
				{
					_dispatchBuffer.Add(message);
					continue;
				}
				int num2 = FindBufferedTransient();
				if (num2 >= 0)
				{
					InMsg message2 = _dispatchBuffer[num2];
					_dispatchBuffer.RemoveAt(num2);
					LogDroppedTransient(message2);
					_dispatchBuffer.Add(message);
				}
				else if (IsReliable(message))
				{
					RejectDispatchOverload(message);
				}
				else
				{
					LogDroppedTransient(message);
				}
			}
			if (_dispatchBuffer.Count < 1024)
			{
				_dispatchBacklogWarned = false;
			}
			PerfProbe.RecordQueueDepth("core.dispatch", _dispatchBuffer.Count, 2048);
		}

		private void DispatchIncoming()
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			for (int i = 0; i < _dispatchBuffer.Count; i++)
			{
				InMsg message = _dispatchBuffer[i];
				if (message.Message == null)
				{
					num = i + 1;
					continue;
				}
				int num4 = DispatchCost(message);
				if (num3 > 0 && num2 + num4 > 256)
				{
					break;
				}
				num3++;
				num2 += num4;
				try
				{
					Dispatch(message);
				}
				catch (ReliableMessageHandlerException ex)
				{
					MarkDispatchConnectionRejected(message.Connection);
					ManualLogSource log = CoopPlugin.Log;
					string[] obj = new string[6] { "reliable dispatch failed for connection ", null, null, null, null, null };
					PeerConnection connection = message.Connection;
					obj[1] = ((connection != null) ? new int?(connection.Id) : ((int?)null)).ToString();
					obj[2] = " type=";
					obj[3] = message.MessageType?.FullName;
					obj[4] = "; the peer was disconnected and the remaining messages were held: ";
					obj[5] = ex.InnerException?.ToString();
					log.LogError((object)string.Concat(obj));
					num = i + 1;
					break;
				}
				catch (Exception ex2)
				{
					if (IsReliable(message))
					{
						RequestReliableDispatchRecovery(message, ex2);
						num = i + 1;
						break;
					}
					ManualLogSource log2 = CoopPlugin.Log;
					string[] obj2 = new string[6] { "transient dispatch conn=", null, null, null, null, null };
					PeerConnection connection2 = message.Connection;
					obj2[1] = ((connection2 != null) ? new int?(connection2.Id) : ((int?)null)).ToString();
					obj2[2] = " type=";
					obj2[3] = message.MessageType?.FullName;
					obj2[4] = " failed; dropping message: ";
					obj2[5] = ex2?.ToString();
					log2.LogWarning((object)string.Concat(obj2));
				}
				num = i + 1;
				if (Net == null)
				{
					break;
				}
			}
			if (num >= _dispatchBuffer.Count)
			{
				_dispatchBuffer.Clear();
			}
			else if (num > 0)
			{
				_dispatchBuffer.RemoveRange(0, num);
			}
			PerfProbe.RecordQueueDepth("core.dispatch", _dispatchBuffer.Count, 2048);
		}

		private bool Dispatch(InMsg message)
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Expected O, but got Unknown
			if (message.Connection == null || Net == null || _dispatchRejectedConnections.Contains(message.Connection.Id) || !IsActiveConnection(message.Connection))
			{
				return false;
			}
			MessageContext context = new MessageContext
			{
				Connection = message.Connection,
				InGame = InGameLevel(),
				WorkCost = DispatchCost(message),
				Transport = Net
			};
			if (_messageRouter.DispatchCore(context, message.Message))
			{
				return true;
			}
			if (message.ReceivedBeforeMessageIdActivation)
			{
				Net.GracefulDisconnect(message.Connection, new DisconnectInfo("non-handshake message received before catalog activation", false, "protocol_violation", false, message.Connection.State));
			}
			return false;
		}

		private static int DispatchCost(InMsg message)
		{
			if (message.Message is CardDeltaBatchMessage cardDeltaBatchMessage)
			{
				int val = cardDeltaBatchMessage.Deltas?.Count ?? 0;
				return Math.Max(1, Math.Min(val, 200));
			}
			if (message.Message is PricingDeltaBatchMessage pricingDeltaBatchMessage)
			{
				int val2 = (pricingDeltaBatchMessage.Items?.Count ?? 0) + (pricingDeltaBatchMessage.Cards?.Count ?? 0);
				return Math.Max(1, Math.Min(val2, 200));
			}
			return 1;
		}

		private int FindBufferedTransient()
		{
			for (int i = 0; i < _dispatchBuffer.Count; i++)
			{
				if (IsTransient(_dispatchBuffer[i]))
				{
					return i;
				}
			}
			return -1;
		}

		private static bool IsReliable(InMsg message)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Invalid comparison between Unknown and I4
			if (message.Message == null)
			{
				return true;
			}
			if (!MessageRegistry.CurrentSnapshot.TryGet(message.MessageType, out var descriptor))
			{
				return true;
			}
			return (int)descriptor.Reliability == 0;
		}

		private static bool IsTransient(InMsg message)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			if (message.Message != null && MessageRegistry.CurrentSnapshot.TryGet(message.MessageType, out var descriptor))
			{
				return (int)descriptor.Reliability == 1;
			}
			return false;
		}

		private void RejectDispatchOverload(InMsg message)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			PeerConnection connection = message.Connection;
			string text = message.MessageType?.FullName ?? "<unknown>";
			if (connection == null)
			{
				CoopPlugin.Log.LogError((object)("dispatch overload rejected reliable " + text + " without a peer connection"));
			}
			else if (_dispatchRejectedConnections.Add(connection.Id))
			{
				string text2 = "dispatch overload: reliable backlog exhausted; reconnect to recover";
				CoopPlugin.Log.LogError((object)("dispatch overload on connection " + connection.Id + ": rejecting reliable " + text + " (capacity " + 2048 + ")"));
				Net?.GracefulDisconnect(connection, new DisconnectInfo(text2, false, "dispatch_overload", true, connection.State));
			}
		}

		private void LogDroppedTransient(InMsg message)
		{
			if (!_dispatchBacklogWarned)
			{
				_dispatchBacklogWarned = true;
				ManualLogSource log = CoopPlugin.Log;
				string? obj = message.MessageType?.FullName ?? "<unknown>";
				PeerConnection connection = message.Connection;
				log.LogWarning((object)("dispatch backlog cap reached; dropping transient " + obj + " from connection " + ((connection != null) ? new int?(connection.Id) : ((int?)null))));
			}
		}

		private void RequestReliableDispatchRecovery(InMsg message, Exception error)
		{
			//IL_00ba: 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)
			//IL_00c9: Expected O, but got Unknown
			MarkDispatchConnectionRejected(message.Connection);
			ManualLogSource log = CoopPlugin.Log;
			string[] obj = new string[6] { "reliable dispatch conn=", null, null, null, null, null };
			PeerConnection connection = message.Connection;
			obj[1] = ((connection != null) ? new int?(connection.Id) : ((int?)null)).ToString();
			obj[2] = " type=";
			obj[3] = message.MessageType?.FullName;
			obj[4] = " failed; requesting session recovery: ";
			obj[5] = error?.ToString();
			log.LogError((object)string.Concat(obj));
			if (message.Connection != null)
			{
				Net?.GracefulDisconnect(message.Connection, new DisconnectInfo("reliable dispatch failed; session recovery required", false, "dispatch_failed", true, message.Connection.State));
			}
		}

		private void MarkDispatchConnectionRejected(PeerConnection connection)
		{
			if (connection != null)
			{
				_dispatchRejectedConnections.Add(connection.Id);
			}
		}

		private bool IsActiveConnection(PeerConnection connection)
		{
			foreach (PeerConnection item in Net?.Connections ?? Array.Empty<PeerConnection>())
			{
				if (item == connection)
				{
					return true;
				}
			}
			return false;
		}

		private static bool ExpectControlState(PeerConnection connection, string control, params ConnectionState[] expected)
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			if (connection == null || expected.All((ConnectionState state) => connection.State != state))
			{
				ManualLogSource log = CoopPlugin.Log;
				string[] obj = new string[6] { "Ignoring late/duplicate ", control, " from connection ", null, null, null };
				PeerConnection obj2 = connection;
				obj[3] = ((obj2 != null) ? new int?(obj2.Id) : ((int?)null)).ToString();
				obj[4] = " in phase ";
				PeerConnection obj3 = connection;
				obj[5] = ((obj3 != null) ? new ConnectionState?(obj3.State) : ((ConnectionState?)null)).ToString();
				log.LogWarning((object)string.Concat(obj));
				return false;
			}
			return true;
		}

		private string ResolvePeerName(ulong steamId, string wireName)
		{
			string result = (string.IsNullOrWhiteSpace(wireName) ? "Player" : wireName);
			if (steamId == 0L || _steam == null)
			{
				return result;
			}
			string text = _steam.FriendNickname(steamId);
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return result;
		}

		private void SetStatus(string status)
		{
			StatusLine = status ?? "";
			HudApi.SetStatusLine(StatusLine, 8f);
		}

		private void Guarded(string stage, Action action)
		{
			try
			{
				PerfProbe.Measure(stage, action);
			}
			catch (Exception e)
			{
				ModuleGuard.Log(stage, e);
			}
		}

		private void RememberDisconnect(DisconnectInfo detail)
		{
			if (detail != null)
			{
				LastDisconnectCode = detail.Code;
				LastDisconnectReason = detail.Reason;
				LastDisconnectRetryable = detail.Retryable;
			}
		}

		private void ClearDisconnectMetadata()
		{
			LastDisconnectCode = "";
			LastDisconnectReason = "";
			LastDisconnectRetryable = false;
			LastFailedLobby = 0uL;
		}

		private void OnLocalPackOpened(CEventPlayer_OnOpenCardPack eventData)
		{
			if (Role != CoopRole.None)
			{
				ICoopTransport net = Net;
				if (net != null && net.ConnectionCount > 0)
				{
					PresenceApi.SendActivity((EItemType)eventData.m_PackIndex);
				}
			}
		}

		private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _)
		{
			_worldReady = true;
			CoopPlugin.Log.LogInfo((object)"co-op: shop data finished loading; hosting is now admitted");
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			_worldReady = false;
			RegisterEventListeners();
			SceneRef.NotifySceneLoaded(scene);
			if (((Scene)(ref scene)).name == "Title" && Role == CoopRole.Client && Net != null)
			{
				Shutdown("left the session");
			}
			else if (((Scene)(ref scene)).name != "Title" && Role != CoopRole.None && Net != null && !SaveTransferApi.ClientReloading)
			{
				Shutdown("left the session (world reloaded)");
			}
		}

		private bool TryAdmitHostStart(out string error)
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			CGameManager val = GameManager();
			bool flag = (Object)(object)val != (Object)null && val.m_IsGameLevel;
			bool worldReady = _worldReady;
			bool guestBorrowedWorld = SaveTransferApi.GuestBorrowedWorld;
			if (flag && worldReady && !guestBorrowedWorld)
			{
				error = null;
				return true;
			}
			ShelfManager val2 = SceneRef<ShelfManager>.Get();
			error = (guestBorrowedWorld ? "Leave the joined shop and load your own shop before hosting." : ((!flag) ? "Load your shop first, then host." : "Wait for your shop to finish loading, then host."));
			CoopPlugin.Log.LogWarning((object)("host start rejected: scene=" + ((Scene)(ref activeScene)).name + " manager=" + (((Object)(object)val == (Object)null) ? "<null>" : ((object)val).GetType().FullName) + " game-level=" + flag + " ready=" + worldReady + " borrowed=" + guestBorrowedWorld + " shelf-data-loaded=" + ((Object)(object)val2 != (Object)null && val2.m_FinishLoadingObjectData) + " reason=" + error));
			return false;
		}

		private static CGameManager GameManager()
		{
			CGameManager instance = CGameManager.m_Instance;
			if (!((Object)(object)instance != (Object)null))
			{
				return SceneRef<CGameManager>.Get();
			}
			return instance;
		}

		private bool InGameLevel()
		{
			CGameManager val = GameManager();
			if ((Object)(object)val != (Object)null)
			{
				return val.m_IsGameLevel;
			}
			return false;
		}

		private static bool InGameplayScene()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).isLoaded)
			{
				return ((Scene)(ref activeScene)).name != "Title";
			}
			return false;
		}

		private void AutoTick(float deltaTime)
		{
			if (_autoPhase >= 99 || (_autoHostSlot < 0 && _autoJoinIp == null && _autoJoinSteamLobby == 0L))
			{
				return;
			}
			_autoTimer += deltaTime;
			if (_autoHostSlot >= 0)
			{
				if (_autoPhase == 0 && _autoTimer > 6f && !InGameLevel())
				{
					SaveTransferApi.ForceLoadSlot(_autoHostSlot);
					_autoPhase = 1;
					_autoTimer = 0f;
				}
				else if (_autoPhase == 1 && InGameLevel() && _worldReady)
				{
					_autoPhase = 2;
					_autoTimer = 0f;
				}
				else if (_autoPhase == 2 && _autoTimer > 3f)
				{
					StartHosting();
					_autoPhase = 99;
				}
			}
			else if (_autoJoinIp != null && _autoPhase == 0 && _autoTimer > 10f && !InGameLevel())
			{
				JoinHostedGame(_autoJoinIp);
				_autoPhase = 99;
			}
			else if (_autoJoinSteamLobby != 0L && _steam != null && _autoPhase == 0 && _autoTimer > 10f && !InGameLevel())
			{
				JoinSteamLobby(_autoJoinSteamLobby);
				_autoPhase = 99;
			}
		}

		private void BeginInviteResolve()
		{
			InviteStatus = InviteState.Resolving;
			InviteCodeText = null;
			InviteReason = null;
			PortForwardState = 0;
			int port = CoopPlugin.Port.Value;
			string password = HostPassword ?? "";
			int generation = Interlocked.Increment(ref _inviteGeneration);
			Thread thread = new Thread((ThreadStart)delegate
			{
				string publicIp = null;
				string reason = null;
				try
				{
					NetHelpers.BeginDiscovery();
					if (CoopPlugin.AutoPortForward.Value)
					{
						string reason2;
						long epoch;
						bool mapped = NetHelpers.TryMapPort(port, out reason2, out epoch);
						Publish(generation, delegate
						{
							PortForwardState = (mapped ? 1 : 2);
						});
						if (mapped)
						{
							publicIp = NetHelpers.UpnpExternalIp();
							if (publicIp != null && !NetHelpers.IsPublicIPv4(publicIp))
							{
								reason = "your router's internet address is private (carrier-grade NAT)";
								publicIp = null;
							}
						}
						else if (reason2 != null)
						{
							CoopPlugin.Log.LogInfo((object)("automatic port forwarding did not happen: " + reason2));
						}
					}
					if (publicIp == null)
					{
						publicIp = NetHelpers.StunPublicIp();
					}
					if (publicIp != null && !NetHelpers.IsPublicIPv4(publicIp))
					{
						publicIp = null;
					}
				}
				catch (Exception ex)
				{
					reason = ex.Message;
					CoopPlugin.Log.LogWarning((object)("invite code resolve failed: " + ex));
				}
				string text = publicIp ?? NetHelpers.LocalIPv4ForGateway() ?? NetHelpers.LocalIPv4();
				string code = ((text == null) ? null : InviteCode.Encode(text, port, password));
				Publish(generation, delegate
				{
					InviteCodeText = code;
					InviteReason = reason;
					InviteStatus = ((publicIp == null) ? InviteState.LanOnly : InviteState.Ready);
				});
			});
			thread.IsBackground = true;
			thread.Name = "CoopInvite";
			thread.Start();
		}

		private void Publish(int generation, Action action)
		{
			_dispatcher.TryEnqueue("invite-publish", delegate
			{
				if (Volatile.Read(in _inviteGeneration) == generation)
				{
					action();
				}
			});
		}

		private static string GenerateSessionPassword()
		{
			byte[] array = new byte[8];
			using (RNGCryptoServiceProvider rNGCryptoServiceProvider = new RNGCryptoServiceProvider())
			{
				rNGCryptoServiceProvider.GetBytes(array);
			}
			char[] array2 = new char[array.Length];
			for (int i = 0; i < array2.Length; i++)
			{
				array2[i] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"[array[i] & 0x1F];
			}
			return new string(array2);
		}

		private void OnGUI()
		{
			using (PerfProbe.Sample("core.ongui"))
			{
				_ui.Draw(this, Net);
			}
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			CEventManager.RemoveListener<CEventPlayer_OnOpenCardPack>((EventDelegate<CEventPlayer_OnOpenCardPack>)OnLocalPackOpened);
			CEventManager.RemoveListener<CEventPlayer_GameDataFinishLoaded>((EventDelegate<CEventPlayer_GameDataFinishLoaded>)OnGameDataFinishLoaded);
			Shutdown("plugin unloaded");
			try
			{
				_persistentRuntime?.Shutdown();
			}
			catch (Exception ex)
			{
				ManualLogSource log = CoopPlugin.Log;
				if (log != null)
				{
					log.LogError((object)("Co-op persistent runtime shutdown failed: " + ex));
				}
			}
			if ((Object)(object)_persistentRuntime != (Object)null)
			{
				Object.Destroy((Object)(object)_persistentRuntime);
				_persistentRuntime = null;
			}
			PerfProbe.Reset();
			_dispatcher.Stop();
			if (Instance == this)
			{
				Instance = null;
			}
		}

		private void OnApplicationQuit()
		{
			Shutdown("game closed");
		}

		[MessageHandler(typeof(FullyJoinedMessage))]
		private void HandleFullyJoinedMessage(MessageContext context, FullyJoinedMessage message)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			if (Role != CoopRole.Host || context.Connection == null)
			{
				return;
			}
			if ((int)context.Connection.State != 2)
			{
				CoopPlugin.Log.LogWarning((object)("Ignoring late/duplicate FullyJoined from connection " + context.Connection.Id + " in phase " + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString()));
				return;
			}
			if (!SaveTransferApi.TryConsumeHostTransferAcknowledgement(context.Connection))
			{
				CoopPlugin.Log.LogWarning((object)("Ignoring premature FullyJoined from connection " + context.Connection.Id + " before its world transfer was fully queued"));
				return;
			}
			try
			{
				_runtime?.FullyJoined(context.Connection);
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogError((object)("Feature FullyJoined callbacks failed: " + ex));
			}
			Send(context.ConnectionId, (INetMessage)(object)new FullyJoinedAckMessage());
			if (!context.Connection.TryTransition((ConnectionState)3))
			{
				CoopPlugin.Log.LogWarning((object)("Could not complete FullyJoined transition for connection " + context.Connection.Id + " in phase " + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString()));
			}
		}

		[MessageHandler(typeof(FullyJoinedAckMessage))]
		private void HandleFullyJoinedAckMessage(MessageContext context, FullyJoinedAckMessage message)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (Role != CoopRole.Client || context.Connection == null)
			{
				return;
			}
			if ((int)context.Connection.State != 2)
			{
				CoopPlugin.Log.LogWarning((object)("Ignoring late/duplicate FullyJoinedAck from connection " + context.Connection.Id + " in phase " + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString()));
			}
			else if (!SaveTransferApi.TryConsumeClientTransferAcknowledgement(context.Connection))
			{
				CoopPlugin.Log.LogWarning((object)"Ignoring premature FullyJoinedAck before the client finished loading the transferred world");
			}
			else if (context.Connection.TryTransition((ConnectionState)3))
			{
				try
				{
					_runtime?.FullyJoined(context.Connection);
				}
				catch (Exception ex)
				{
					CoopPlugin.Log.LogError((object)("Feature FullyJoined callbacks failed: " + ex));
				}
			}
		}

		[MessageHandler(typeof(DisconnectMessage))]
		private void HandleDisconnectMessage(MessageContext context, DisconnectMessage message)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			if (context.Connection == null)
			{
				CoopPlugin.Log.LogWarning((object)"Ignoring disconnect control without a connection");
				return;
			}
			ConnectionState val = (ConnectionState)(Enum.IsDefined(typeof(ConnectionState), message.Phase) ? message.Phase : 4);
			DisconnectInfo val2 = new DisconnectInfo(message.Reason, true, message.Code, message.Retryable, val);
			if (Role == CoopRole.Client)
			{
				RememberDisconnect(val2);
				Shutdown("remote: " + val2.Reason, val2);
			}
		}

		internal static void EnqueueMainThread(Action action)
		{
			if (action == null || TryEnqueueMainThread(action))
			{
				return;
			}
			throw new InvalidOperationException("CoopCore is not running");
		}

		internal static bool TryEnqueueMainThread(Action action)
		{
			if (action == null)
			{
				return false;
			}
			CoopCore instance = Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			return instance._dispatcher.TryEnqueue("external-main-thread", action);
		}

		public static bool TryGetLocalPlayerPosition(out Vector3 position)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			position = default(Vector3);
			return PresenceApi.TryGetLocalPlayerPosition(out position);
		}

		internal static bool NativeTextInputFocused()
		{
			EventSystem current = EventSystem.current;
			GameObject val = ((current != null) ? current.currentSelectedGameObject : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			TMP_InputField component = val.GetComponent<TMP_InputField>();
			if ((Object)(object)component != (Object)null)
			{
				return component.isFocused;
			}
			return false;
		}
	}
	public enum GradedAlertMode
	{
		Always,
		OncePerSession,
		Never
	}
	[BepInPlugin("dev.meepen.cardshopcoop", "CardShopCoopCommunity", "2.0.6")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class CoopPlugin : BaseUnityPlugin
	{
		public const string Guid = "dev.meepen.cardshopcoop";

		public const string Name = "CardShopCoopCommunity";

		public static ManualLogSource Log;

		public static ConfigEntry<int> Port;

		public static ConfigEntry<string> LastJoinIP;

		public static ConfigEntry<string> PlayerName;

		public static ConfigEntry<float> SendRateHz;

		public static ConfigEntry<bool> AvatarsEnabled;

		public static ConfigEntry<bool> AllowNsfw;

		public static ConfigEntry<KeyCode> UiToggleKey;

		public static ConfigEntry<KeyCode> EmoteKey;

		public static ConfigEntry<int> ClientWorldSlot;

		public static ConfigEntry<float> ServeReach;

		public static ConfigEntry<bool> AllowCrossBuildJoin;

		public static ConfigEntry<bool> AutoPortForward;

		public static ConfigEntry<bool> AutoLanPassword;

		public static ConfigEntry<GradedAlertMode> GradedDriftAlert;

		public static ConfigEntry<bool> PerfDebug;

		public static ConfigEntry<int> ArtificialLagMs;

		public static ConfigEntry<int> ArtificialJitterMs;

		public static ConfigEntry<bool> EnableGameCheatMenu;

		public static ConfigEntry<bool> ShowHiddenCategory;

		public const string Version = "2.0.6";

		private void Awake()
		{
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Invalid comparison between Unknown and I4
			//IL_0443: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			CoopApi.Binding = (ICoopBinding)(object)ExternalCoopMods.Instance;
			CoopApi.Log = ((BaseUnityPlugin)this).Logger;
			SceneRef.Install();
			ScreenSingletonProbe.Install();
			try
			{
				Assembly.Load("Newtonsoft.Json");
				((BaseUnityPlugin)this).Logger.LogInfo((object)"network payload serializer: Newtonsoft.Json");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("network payload serializer unavailable: Newtonsoft.Json.dll is required (" + ex.Message + ")"));
			}
			FileLog.Init(Paths.GameRootPath);
			((BaseUnityPlugin)this).Logger.LogEvent += delegate(object _, LogEventArgs e2)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				FileLog.Write($"{e2.Level,-7} {e2.Data}");
			};
			Port = ((BaseUnityPlugin)this).Config.Bind<int>("Network", "Po

BepInEx/plugins/CardShopCoop/CardShopCoop.Api.dll

Decompiled 13 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx.Logging;
using CardShopCoop.Net;
using CardShopCoop.Net.Connection;
using CardShopCoop.Net.Protocol;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("CardShopCoop")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Devin 'Meepen' Korb")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Public integration contract for CardShopCoopCommunity. Optional-dependency safe.")]
[assembly: AssemblyFileVersion("2.0.6.0")]
[assembly: AssemblyInformationalVersion("2.0.6+7377084dcc6c7363bdd27c3bdca151eaad5dfb45")]
[assembly: AssemblyProduct("CardShopCoopCommunity.Api")]
[assembly: AssemblyTitle("CardShopCoop.Api")]
[assembly: AssemblyVersion("2.0.6.0")]
namespace CardShopCoop.Net
{
	public interface INetMessage
	{
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public sealed class NetworkMessageAttribute : Attribute
	{
		public Reliability Reliability { get; set; }
	}
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
	public sealed class MessageHandlerAttribute : Attribute
	{
		public Type MessageType { get; }

		public MessageHandlerAttribute(Type messageType)
		{
			MessageType = messageType ?? throw new ArgumentNullException("messageType");
		}
	}
}
namespace CardShopCoop.Net.Protocol
{
	public enum Reliability
	{
		Reliable,
		Transient
	}
}
namespace CardShopCoop.Net.Connection
{
	public enum ConnectionState
	{
		Connected,
		Handshaking,
		Transferring,
		FullyJoined,
		Disconnecting,
		Disconnected
	}
	public sealed class DisconnectInfo
	{
		public const int MaxCodeLength = 32;

		public const int MaxDetailLength = 1024;

		public string Code { get; }

		public string Reason { get; }

		public bool Remote { get; }

		public bool Retryable { get; }

		public ConnectionState Phase { get; }

		public DisconnectInfo(string reason, bool remote = false, string code = "closed", bool retryable = false, ConnectionState phase = ConnectionState.Disconnecting)
		{
			if (!Enum.IsDefined(typeof(ConnectionState), phase))
			{
				phase = ConnectionState.Disconnecting;
			}
			Code = Bound(code, 32, "closed");
			Reason = Bound(reason, 1024, "PeerConnection closed");
			Remote = remote;
			Retryable = retryable;
			Phase = phase;
		}

		private static string Bound(string value, int max, string fallback)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return fallback;
			}
			value = value.Trim();
			if (value.Length > max)
			{
				return value.Substring(0, max);
			}
			return value;
		}
	}
	public class PeerConnection
	{
		private readonly object _stateLock = new object();

		private ConnectionState _state;

		private DisconnectInfo _disconnectInfo;

		public int Id { get; }

		public ConnectionState State
		{
			get
			{
				lock (_stateLock)
				{
					return _state;
				}
			}
		}

		internal bool IsDisconnectingOrDisconnected
		{
			get
			{
				lock (_stateLock)
				{
					return _state == ConnectionState.Disconnecting || _state == ConnectionState.Disconnected;
				}
			}
		}

		internal DisconnectInfo DisconnectReason
		{
			get
			{
				lock (_stateLock)
				{
					return _disconnectInfo;
				}
			}
		}

		internal PeerConnection(int id)
		{
			Id = id;
			_state = ConnectionState.Handshaking;
		}

		internal bool TryTransition(ConnectionState next)
		{
			lock (_stateLock)
			{
				if (_state == ConnectionState.Disconnected || _state == ConnectionState.Disconnecting)
				{
					return false;
				}
				if (_state == next)
				{
					return true;
				}
				if ((_state != ConnectionState.Handshaking || next != ConnectionState.Transferring) && (_state != ConnectionState.Transferring || next != ConnectionState.FullyJoined) && (_state != ConnectionState.Connected || next != ConnectionState.Handshaking) && next != ConnectionState.Disconnecting)
				{
					return false;
				}
				_state = next;
				return true;
			}
		}

		internal bool BeginDisconnect(DisconnectInfo info)
		{
			if (info == null)
			{
				throw new ArgumentNullException("info");
			}
			lock (_stateLock)
			{
				if (_state == ConnectionState.Disconnecting || _state == ConnectionState.Disconnected)
				{
					return false;
				}
				_state = ConnectionState.Disconnecting;
				_disconnectInfo = info;
				return true;
			}
		}

		internal bool RecordRemoteDisconnect(DisconnectInfo info)
		{
			if (info == null || !info.Remote)
			{
				throw new ArgumentException("Remote disconnect info is required", "info");
			}
			lock (_stateLock)
			{
				if (_state == ConnectionState.Disconnected)
				{
					return false;
				}
				if (_disconnectInfo != null)
				{
					return false;
				}
				_disconnectInfo = info;
				_state = ConnectionState.Disconnecting;
				return true;
			}
		}

		internal bool TryMarkDisconnected()
		{
			lock (_stateLock)
			{
				if (_state == ConnectionState.Disconnected)
				{
					return false;
				}
				if (_state != ConnectionState.Disconnecting)
				{
					return false;
				}
				_state = ConnectionState.Disconnected;
				return true;
			}
		}

		public override string ToString()
		{
			return "PeerConnection(" + Id + ")";
		}
	}
	public sealed class ConnectionEvent
	{
		public PeerConnection Connection { get; }

		public DisconnectInfo Disconnect { get; }

		public ConnectionEvent(PeerConnection connection, DisconnectInfo disconnect = null)
		{
			Connection = connection ?? throw new ArgumentNullException("connection");
			Disconnect = disconnect;
		}
	}
}
namespace CardShopCoop.Api
{
	internal interface ICoopBinding
	{
		ICoopContext Context { get; }

		bool PredictionActive { get; }

		bool IsReconciling { get; }

		bool IsApplying { get; }

		void Register(Assembly assembly);

		Guid Predict(string scope, Action<Guid> send, Action apply, Action undo);

		void Ack(Guid predictionId);

		void AckOrApply(Guid predictionId, Action apply);

		void Confirm(Guid predictionId, Action apply);

		bool IsPending(Guid predictionId);

		void Rollback(ICoopContext context, int connectionId, Guid predictionId);
	}
	public static class CoopApi
	{
		internal static ICoopBinding Binding { get; set; }

		public static bool IsAvailable => Binding != null;

		public static ICoopContext Context
		{
			get
			{
				if (Binding != null)
				{
					return Binding.Context;
				}
				return null;
			}
		}

		public static ManualLogSource Log { get; internal set; }

		public static void Register(Assembly assembly)
		{
			if (assembly == null)
			{
				throw new ArgumentNullException("assembly");
			}
			(Binding ?? throw new InvalidOperationException("CardShopCoopCommunity has not installed its API binding yet; call CoopApi.Register from your plugin's Awake after CardShopCoopCommunity is loaded (declare a BepInDependency on dev.meepen.cardshopcoop, or guard with CoopApi.IsAvailable).")).Register(assembly);
		}
	}
	public abstract class CoopBehaviour : MonoBehaviour
	{
		public ICoopContext Context { get; private set; }

		internal void SetContext(ICoopContext context)
		{
			Context = context;
		}
	}
	public interface ICoopMessageRegistry
	{
		void RegisterAttributedHandlers(object target);

		void UnregisterAttributedHandlers(object target);
	}
	public interface ICoopContext
	{
		bool IsHost { get; }

		bool IsClient { get; }

		bool InSession { get; }

		bool InGame { get; }

		int LocalConnectionId { get; }

		int HostConnectionId { get; }

		IReadOnlyList<int> ConnectionIds { get; }

		ICoopMessageRegistry Messages { get; }

		string PeerName(int connectionId);

		void Send(int connectionId, INetMessage message);

		void Broadcast(INetMessage message);
	}
	public sealed class CoopMessageContext
	{
		public PeerConnection Connection { get; }

		public bool InGame { get; }

		public int ConnectionId
		{
			get
			{
				if (Connection != null)
				{
					return Connection.Id;
				}
				return 0;
			}
		}

		public bool IsAuthenticated
		{
			get
			{
				if (Connection != null)
				{
					if (Connection.State != ConnectionState.Transferring)
					{
						return Connection.State == ConnectionState.FullyJoined;
					}
					return true;
				}
				return false;
			}
		}

		internal CoopMessageContext(PeerConnection connection, bool inGame)
		{
			Connection = connection;
			InGame = inGame;
		}
	}
	public static class CoopLog
	{
		private const int SwallowCooldownMs = 2000;

		private static readonly ConcurrentDictionary<string, int> LastSwallowed = new ConcurrentDictionary<string, int>();

		public static void Info(string message)
		{
			Safe(delegate(ManualLogSource log)
			{
				log.LogInfo((object)message);
			});
		}

		public static void Warn(string message)
		{
			Safe(delegate(ManualLogSource log)
			{
				log.LogWarning((object)message);
			});
		}

		public static void Error(string message)
		{
			Safe(delegate(ManualLogSource log)
			{
				log.LogError((object)message);
			});
		}

		public static void Warn(Exception exception)
		{
			Safe(delegate(ManualLogSource log)
			{
				log.LogWarning((object)("[caught] " + exception));
			});
		}

		public static void Error(Exception exception)
		{
			Safe(delegate(ManualLogSource log)
			{
				log.LogError((object)exception);
			});
		}

		public static void Swallow(Exception exception, [CallerMemberName] string member = "", [CallerFilePath] string file = "", [CallerLineNumber] int line = 0)
		{
			if (exception == null)
			{
				return;
			}
			string text;
			try
			{
				text = (string.IsNullOrEmpty(file) ? "?" : Path.GetFileNameWithoutExtension(file));
			}
			catch
			{
				text = "?";
			}
			string site = text + "." + (member ?? "?") + ":" + line;
			int tickCount = Environment.TickCount;
			if (!LastSwallowed.TryGetValue(site, out var value) || tickCount - value >= 2000)
			{
				LastSwallowed[site] = tickCount;
				Safe(delegate(ManualLogSource log)
				{
					log.LogWarning((object)("[caught] " + site + ": " + exception));
				});
			}
		}

		private static void Safe(Action<ManualLogSource> write)
		{
			ManualLogSource log = CoopApi.Log;
			if (log == null)
			{
				return;
			}
			try
			{
				write(log);
			}
			catch
			{
			}
		}
	}
	public interface IPredictedMessage : INetMessage
	{
		Guid PredictionId { get; set; }
	}
	public static class CoopPredict
	{
		public static bool IsAvailable
		{
			get
			{
				if (CoopApi.Binding != null)
				{
					return CoopApi.Binding.PredictionActive;
				}
				return false;
			}
		}

		public static bool IsReconciling
		{
			get
			{
				if (CoopApi.Binding != null)
				{
					return CoopApi.Binding.IsReconciling;
				}
				return false;
			}
		}

		public static bool IsApplying
		{
			get
			{
				if (CoopApi.Binding != null)
				{
					return CoopApi.Binding.IsApplying;
				}
				return false;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static Guid Predict(string scope, Action<Guid> send, Action apply, Action undo)
		{
			ICoopBinding coopBinding = RequireBinding();
			string name = Assembly.GetCallingAssembly().GetName().Name;
			return coopBinding.Predict(NamespaceScope(scope, name), send, apply, undo);
		}

		private static string NamespaceScope(string scope, string caller)
		{
			if (string.IsNullOrEmpty(scope))
			{
				throw new ArgumentException("A prediction scope is required.", "scope");
			}
			if (!string.IsNullOrEmpty(caller))
			{
				return caller + ":" + scope;
			}
			return scope;
		}

		private static ICoopBinding RequireBinding()
		{
			ICoopBinding binding = CoopApi.Binding;
			if (binding == null || !binding.PredictionActive)
			{
				throw new InvalidOperationException("Co-op client prediction is not active.");
			}
			return binding;
		}

		public static void Ack(Guid predictionId)
		{
			CoopApi.Binding?.Ack(predictionId);
		}

		public static void AckOrApply(Guid predictionId, Action apply)
		{
			CoopApi.Binding?.AckOrApply(predictionId, apply);
		}

		public static void Confirm(Guid predictionId, Action apply)
		{
			CoopApi.Binding?.Confirm(predictionId, apply);
		}

		public static bool IsPending(Guid predictionId)
		{
			if (CoopApi.Binding != null)
			{
				return CoopApi.Binding.IsPending(predictionId);
			}
			return false;
		}

		public static void Rollback(ICoopContext context, int connectionId, Guid predictionId)
		{
			if (!(predictionId == Guid.Empty))
			{
				if (context == null)
				{
					throw new ArgumentNullException("context");
				}
				CoopApi.Binding?.Rollback(context, connectionId, predictionId);
			}
		}
	}
	public static class CoopReflection
	{
		private static readonly HashSet<string> Reported = new HashSet<string>();

		private static readonly object Gate = new object();

		private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static Type OptionalType(string typeName, string assemblyName)
		{
			Type type = ResolveType(typeName, assemblyName);
			if (type == null)
			{
				Report("type", typeName + ", " + assemblyName, required: false);
			}
			return type;
		}

		public static FieldInfo OptionalField(Type type, string name)
		{
			FieldInfo? obj = ((type == null) ? null : type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
			if (obj == null)
			{
				Report("field", Describe(type, name), required: false);
			}
			return obj;
		}

		public static MethodInfo OptionalMethod(Type type, string name, params Type[] args)
		{
			MethodInfo methodInfo = ResolveMethod(type, name, args);
			if (methodInfo == null)
			{
				Report("method", Describe(type, name), required: false);
			}
			return methodInfo;
		}

		public static PropertyInfo OptionalProperty(Type type, string name)
		{
			PropertyInfo? obj = ((type == null) ? null : type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
			if (obj == null)
			{
				Report("property", Describe(type, name), required: false);
			}
			return obj;
		}

		public static FieldInfo RequiredField(Type type, string name)
		{
			FieldInfo? obj = ((type == null) ? null : type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
			if (obj == null)
			{
				Report("field", Describe(type, name), required: true);
			}
			return obj;
		}

		public static MethodInfo RequiredMethod(Type type, string name, params Type[] args)
		{
			MethodInfo methodInfo = ResolveMethod(type, name, args);
			if (methodInfo == null)
			{
				Report("method", Describe(type, name), required: true);
			}
			return methodInfo;
		}

		private static Type ResolveType(string typeName, string assemblyName)
		{
			try
			{
				Type type = Type.GetType(typeName + ", " + assemblyName, throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = CoopApi.Log;
				if (log != null)
				{
					log.LogWarning((object)("[api] type probe failed for " + typeName + ": " + ex.Message));
				}
			}
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				for (int i = 0; i < assemblies.Length; i++)
				{
					Type type2 = assemblies[i].GetType(typeName, throwOnError: false);
					if (type2 != null)
					{
						return type2;
					}
				}
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = CoopApi.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[api] assembly scan failed for " + typeName + ": " + ex2.Message));
				}
			}
			return null;
		}

		private static MethodInfo ResolveMethod(Type type, string name, Type[] args)
		{
			if (type == null)
			{
				return null;
			}
			if (args == null || args.Length == 0)
			{
				MethodInfo methodInfo = null;
				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods)
				{
					if (!(methodInfo2.Name != name))
					{
						if (methodInfo2.GetParameters().Length == 0)
						{
							return methodInfo2;
						}
						if ((object)methodInfo == null)
						{
							methodInfo = methodInfo2;
						}
					}
				}
				return methodInfo;
			}
			return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null);
		}

		private static string Describe(Type type, string name)
		{
			return ((type == null) ? "<null>" : type.FullName) + "." + name;
		}

		private static void Report(string kind, string name, bool required)
		{
			string item = (required ? "required:" : "optional:") + kind + ":" + name;
			lock (Gate)
			{
				if (!Reported.Add(item))
				{
					return;
				}
			}
			string text = "coop api: reflection surface changed: " + kind + " " + name + (required ? " (required)" : " (optional; feature disabled)");
			if (required)
			{
				ManualLogSource log = CoopApi.Log;
				if (log != null)
				{
					log.LogError((object)text);
				}
				throw new MissingMemberException(text);
			}
			ManualLogSource log2 = CoopApi.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)text);
			}
		}
	}
}
namespace CardShopCoop.Attributes
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public sealed class ServerBehaviourAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public sealed class ClientBehaviourAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public sealed class PersistentBehaviourAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	public sealed class OnClientJoinedAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	public sealed class OnClientDisconnectedAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	public sealed class OnFullyJoinedAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	public sealed class OnSessionStartedAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	public sealed class OnSessionStoppedAttribute : Attribute
	{
	}
}