Decompiled source of CardShopCoop v1.0.37

CardShopCoop.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
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 CardShopCoop.Net;
using CardShopCoop.Patches;
using CardShopCoop.Sync;
using CardShopCoop.UI;
using CardShopCoop.Util;
using HarmonyLib;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("CardShopCoop")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.37.0")]
[assembly: AssemblyInformationalVersion("1.0.37+dd37610e424c21f00c01126ab0e098dd73f7733e")]
[assembly: AssemblyProduct("CardShopCoop")]
[assembly: AssemblyTitle("CardShopCoop")]
[assembly: AssemblyVersion("1.0.37.0")]
namespace CardShopCoop
{
	public enum CoopRole
	{
		None,
		Host,
		Client
	}
	public class CoopCore : MonoBehaviour
	{
		private struct HeldPurchase
		{
			public InMsg Msg;

			public double At;
		}

		private struct ChargeVerdict
		{
			public bool Accepted;

			public double At;
		}

		private struct PendingCard
		{
			public bool IsAdd;

			public int Amount;

			public CardData Card;
		}

		private struct MyCardPrice
		{
			public CardData Card;

			public float Value;

			public bool Acked;

			public double LastSend;

			public int Attempts;
		}

		private struct MyItemPrice
		{
			public float Value;

			public double At;
		}

		private enum PurchaseGate
		{
			Process,
			Drop,
			Hold
		}

		public static bool GuestBorrowedWorld;

		public static bool HostServeKeyEnabled = false;

		public string StatusLine = "Not connected";

		public string ErrorLine = "";

		public string HostTimeLine = "";

		public string RegisterLine = "";

		public float RegisterLineTimer;

		private float _serveThrottle;

		public readonly Dictionary<int, string> PeerNames = new Dictionary<int, string>();

		private ICoopTransport _net;

		private readonly SteamLobby _steamLobby = new SteamLobby();

		private ulong _autoJoinSteamLobby;

		private readonly AvatarManager _avatars = new AvatarManager();

		private readonly WorldSync _world = new WorldSync();

		private readonly NpcSync _npcs = new NpcSync();

		private readonly CardShelfSync _cardShelves = new CardShelfSync();

		private readonly ObjMoveSync _objMoves = new ObjMoveSync();

		private readonly BoxSync _boxes = new BoxSync();

		private readonly PopulationSync _population = new PopulationSync();

		private readonly GradingSync _grading = new GradingSync();

		private readonly TradeServe _trades = new TradeServe();

		private readonly PlayTableSync _tables = new PlayTableSync();

		private readonly StaffSync _staff = new StaffSync();

		private readonly ShopStateSync _shopState = new ShopStateSync();

		private readonly SettingsSync _settings = new SettingsSync();

		private readonly MarketSync _market = new MarketSync();

		private readonly ReportSync _report = new ReportSync();

		private readonly ContainerSync _containers = new ContainerSync();

		private readonly TournamentSync _tournament = new TournamentSync();

		private readonly CardBoxSync _cardBoxes = new CardBoxSync();

		private readonly FurnBoxSync _furnBoxes = new FurnBoxSync();

		private string _lastShopNameSent;

		private float _shopNameTimer = -1f;

		private readonly RegisterMirror _registerMirror = new RegisterMirror();

		private float _npcSweepTimer = -1.3f;

		private float _regStateTimer = -0.17f;

		public string PromptLine = "";

		private readonly ConcurrentQueue<Action> _mainThread = new ConcurrentQueue<Action>();

		private CoopUI _ui;

		private MemoryStream _saveBuf;

		private int _saveExpected = -1;

		private byte[] _pendingSave;

		private MemoryStream _bundleBuf;

		private int _bundleExpected = -1;

		private int _hostSlot;

		private bool _worldRequested;

		private float _priceTimer = -0.45f;

		private int _lastPriceHash;

		private float _priceHeal;

		private readonly List<KeyValuePair<int, float>> _priceBuf = new List<KeyValuePair<int, float>>();

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

		private float _stateTimer;

		private float _pingTimer;

		private float _econTimer = -0.11f;

		private float _dayTimer = -0.9f;

		private Vector3 _lastPos;

		private bool _hasLastPos;

		private double _lastCoinSent = double.MinValue;

		private long _lastProgressSent = long.MinValue;

		private float _coinHeal;

		private float _progressHeal;

		private double _pendingReduceThisFrame;

		private readonly List<HeldPurchase> _heldPurchases = new List<HeldPurchase>();

		private bool _deliveringHeld;

		private readonly Dictionary<int, ChargeVerdict> _chargeVerdicts = new Dictionary<int, ChargeVerdict>();

		private const double VerdictTtl = 1.0;

		private const double VerdictDeclineTtl = 10.0;

		private readonly Dictionary<int, double> _lastDeclineToast = new Dictionary<int, double>();

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

		private bool _loggedEconLink;

		private bool _loggedTimeLink;

		private long _diagSent;

		private long _diagRecvStates;

		private float _diagTimer = -7.3f;

		private float _errLogCooldown;

		private static readonly FieldInfo FiTimeHour = typeof(LightManager).GetField("m_TimeHour", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo FiTimeMin = typeof(LightManager).GetField("m_TimeMin", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo FiTimeMinFloat = typeof(LightManager).GetField("m_TimeMinFloat", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo FiHasDayEnded = typeof(LightManager).GetField("m_HasDayEnded", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly MethodInfo MiDayReset = typeof(LightManager).GetMethod("DelayUpdateEnv", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo FiTimeOfDayIdx = typeof(LightManager).GetField("m_TImeOfDayIndex", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo FiFinishLoading = typeof(LightManager).GetField("m_FinishLoading", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly MethodInfo MiLightInit = typeof(LightManager).GetMethod("Init", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly MethodInfo MiUpdateLightData = typeof(LightManager).GetMethod("UpdateLightTimeData", BindingFlags.Instance | BindingFlags.NonPublic);

		private float _lightSyncTimer = -2.3f;

		private LightManager _lightManager;

		private float _cardResyncTimer = -5.2f;

		private int _lastCardResyncHash;

		private float _cardResyncHeal;

		private float _cardPriceHealTimer = -2.1f;

		private int _lastCardPriceHash;

		private float _cardPriceHealBeat;

		private int _lastStockResyncHash;

		private float _stockResyncHeal;

		private readonly List<KeyValuePair<CardData, float>> _cardPriceBuf = new List<KeyValuePair<CardData, float>>();

		private float _licenseSyncTimer = -3.7f;

		private double _lastLicenseBuyTime = -999.0;

		private string _lastLightJson;

		private float _lightHeal;

		private double _lastDayMirrorAt = -999.0;

		private int _lastLicenseHash;

		private float _licenseHeal;

		private float _dt;

		private bool _syncActive;

		private Action _actNetPump;

		private Action _actAvatars;

		private Action _actWorld;

		private Action _actCardShelves;

		private Action _actObjMoves;

		private Action _actBoxes;

		private Action _actPopulation;

		private Action _actNpcPuppets;

		private Action _actRegisterMirror;

		private Action _actNpcSweep;

		private Action _actStateSend;

		private Action _actNpcCollect;

		private Action _actRegisterCollect;

		private Action _actModules;

		private Action _actCardPriceRetry;

		private Action _actFrameCardWork;

		private CustomerManager _cmSweep;

		private CustomerManager _cmSpray;

		private bool _renamerHandled;

		private TMP_Text _shopSign;

		private string _lastShopNameApplied;

		private int _heldBoxFrame = -1;

		private object _heldBoxA;

		private object _heldBoxB;

		private object _heldBoxC;

		public static bool ClientReloading;

		private float _reloadGrace;

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

		private readonly HashSet<long> _dispatchSeen = new HashSet<long>();

		private const int DispatchBudget = 256;

		private int _autoHostSlot = -1;

		private string _autoJoinIp;

		private int _autoPhase;

		private float _autoTimer;

		private static bool _enumLendWarned;

		public string HostPassword = "";

		private string _joinPassword = "";

		public CSteamID LastFailedLobby = CSteamID.Nil;

		private const int EnumBlobCap = 262144;

		private readonly Dictionary<string, int> _enumSyncSentTo = new Dictionary<string, int>();

		private readonly Dictionary<string, int> _enumSyncSentToPeer = new Dictionary<string, int>();

		private const int EnumSyncMaxSends = 2;

		private const int EnumSyncMaxSendsPerPeer = 5;

		private readonly List<KeyValuePair<int, float>> _pendingKicks = new List<KeyValuePair<int, float>>();

		private readonly List<PendingCard> _pendingCardDeltas = new List<PendingCard>();

		private readonly List<KeyValuePair<CardData, float>> _pendingCardPrices = new List<KeyValuePair<CardData, float>>();

		private readonly List<PendingCard> _cardDeltaOutbox = new List<PendingCard>();

		private const int CardDeltaBatchMax = 200;

		private bool _flushingCardDeltas;

		private readonly List<PendingCard> _batchRelayBuf = new List<PendingCard>();

		private static bool _binderRefreshPending;

		private static readonly List<PendingCard> _deltaLogBuf = new List<PendingCard>();

		private static int _deltaAppliedThisFrame;

		private readonly Dictionary<string, MyCardPrice> _myCardPrices = new Dictionary<string, MyCardPrice>();

		private readonly List<string> _cardPriceRetryKeys = new List<string>();

		private float _cardPriceRetryTimer;

		private const int MyCardPriceMax = 1024;

		private const int CardPriceMaxAttempts = 12;

		private const float CardPriceEpsilon = 0.0075f;

		private readonly Dictionary<int, MyItemPrice> _myItemPriceEdits = new Dictionary<int, MyItemPrice>();

		private const int MyItemPriceMax = 256;

		private const double ItemPriceHoldSeconds = 6.0;

		private static InteractionPlayerController _deltaIpc;

		private static readonly Dictionary<ECardExpansionType, HashSet<EMonsterType>> _shownMonsters = new Dictionary<ECardExpansionType, HashSet<EMonsterType>>();

		private static readonly HashSet<string> _priceWarnedKeys = new HashSet<string>();

		private static readonly MethodInfo MiBinderResort = AccessTools.Method(typeof(CollectionBinderFlipAnimCtrl), "OnSortingMethodUpdated", (Type[])null, (Type[])null);

		private static readonly FieldInfo FiBinderIsBookOpen = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_IsBookOpen");

		private static readonly FieldInfo FiBinderUI = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_CollectionBinderUI");

		private static readonly FieldInfo FiBinderIsGradedAlbum = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_IsGradedCardAlbum");

		private static readonly FieldInfo FiBinderExpansionType = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_ExpansionType");

		private int _selfId = -1;

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

		private static InventoryBase _inventory;

		private Transform _playerTf;

		private Transform _playerCamTf;

		private InteractionPlayerController _playerIpc;

		private static readonly FieldInfo FiHoldBox = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingBox");

		private static readonly FieldInfo FiHoldItemBox = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingItemBox");

		private static readonly FieldInfo FiHoldBoxShelf = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingBoxShelf");

		private static readonly FieldInfo FiHoldBoxCard = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingBoxCard");

		private static readonly FieldInfo FiHoldItemList = AccessTools.Field(typeof(InteractionPlayerController), "m_HoldItemList");

		private static readonly FieldInfo FiIsHoldBoxMode = AccessTools.Field(typeof(InteractionPlayerController), "m_IsHoldBoxMode");

		private readonly List<int> _holdTypesBuf = new List<int>(6);

		private readonly List<CardData> _holdCardsBuf = new List<CardData>(4);

		private static readonly FieldInfo FiHoldCard3dList = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingCard3dList");

		private static readonly FieldInfo FiViewAlbum = AccessTools.Field(typeof(InteractionPlayerController), "m_IsViewCardAlbumMode");

		private static bool _eplProbed;

		private static PropertyInfo _eplAssetsProp;

		private static PropertyInfo _eplItemLibProp;

		private static PropertyInfo _eplRestockProp;

		private static readonly FieldInfo FiPanelIndex = AccessTools.Field(typeof(RestockItemPanelUI), "m_Index");

		private static readonly FieldInfo FiPanelLicGrp = AccessTools.Field(typeof(RestockItemPanelUI), "m_LicenseUIGrp");

		private static readonly FieldInfo FiPanelUIGrp = AccessTools.Field(typeof(RestockItemPanelUI), "m_UIGrp");

		private bool _catalogSent;

		private float _catalogTimer;

		private int _lastCatalogSentHash;

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

		private readonly Dictionary<int, string> _rosterNames = new Dictionary<int, string>();

		private HashSet<int> _clientPriced = new HashSet<int>();

		private HashSet<int> _incomingPriced = new HashSet<int>();

		public static CoopCore Instance { get; private set; }

		public static CoopRole Role { get; private set; } = CoopRole.None;

		public bool IsSteamSession { get; private set; }

		private bool ClientPreloadHold
		{
			get
			{
				if (ClientReloading)
				{
					return _reloadGrace <= 0f;
				}
				return false;
			}
		}

		public SteamLobby Lobby => _steamLobby;

		private void Guarded(string stage, Action action)
		{
			try
			{
				action();
			}
			catch (Exception arg)
			{
				if (_errLogCooldown <= 0f)
				{
					_errLogCooldown = 5f;
					CoopPlugin.Log.LogError((object)$"[{stage}] {arg}");
				}
			}
		}

		private static int DispatchCost(InMsg m)
		{
			if (m.Type != MsgType.CardDeltaBatch)
			{
				return 1;
			}
			byte[] payload = m.Payload;
			if (payload == null || payload.Length < 4)
			{
				return 1;
			}
			int num = payload[0] | (payload[1] << 8) | (payload[2] << 16) | (payload[3] << 24);
			if (num < 1)
			{
				return 1;
			}
			if (num <= 200)
			{
				return num;
			}
			return 200;
		}

		private unsafe void Awake()
		{
			Instance = this;
			_ui = new CoopUI();
			_world.OnLocalChanges = OnLocalWorldChanges;
			_cardShelves.OnLocalChanges = delegate(List<CardShelfSync.Entry> changes)
			{
				if (Role == CoopRole.Host)
				{
					Broadcast(MsgType.CardShelfDelta, delegate(BinaryWriter bw)
					{
						CardShelfSync.WriteEntries(bw, changes);
					});
				}
				else if (Role == CoopRole.Client)
				{
					Send(1, MsgType.CardShelfRequest, delegate(BinaryWriter bw)
					{
						CardShelfSync.WriteEntries(bw, changes);
					});
				}
			};
			_objMoves.OnLocalChanges = delegate(List<ObjMoveSync.Entry> changes)
			{
				if (Role == CoopRole.Host)
				{
					Broadcast(MsgType.ObjMoveDelta, delegate(BinaryWriter bw)
					{
						ObjMoveSync.WriteEntries(bw, changes);
					});
				}
				else if (Role == CoopRole.Client)
				{
					Send(1, MsgType.ObjMoveRequest, delegate(BinaryWriter bw)
					{
						ObjMoveSync.WriteEntries(bw, changes);
					});
				}
			};
			_population.OnHostSnapshot = delegate(List<List<PopulationSync.Entry>> all)
			{
				Broadcast(MsgType.PopState, delegate(BinaryWriter bw)
				{
					PopulationSync.Write(bw, all);
				});
			};
			_boxes.OnHostSnapshot = delegate(List<BoxSync.Entry> list)
			{
				Broadcast(MsgType.BoxState, delegate(BinaryWriter bw)
				{
					BoxSync.WriteEntries(bw, list);
				});
			};
			_boxes.OnClientChanges = delegate(List<BoxSync.Entry> list)
			{
				Send(1, MsgType.BoxRequest, delegate(BinaryWriter bw)
				{
					BoxSync.WriteEntries(bw, list);
				});
			};
			BoxSync.IsLocallyCarried = delegate(InteractablePackagingBox_Item box)
			{
				if ((Object)(object)_playerIpc == (Object)null || (Object)(object)box == (Object)null)
				{
					return false;
				}
				try
				{
					if (_heldBoxFrame != Time.frameCount)
					{
						_heldBoxFrame = Time.frameCount;
						_heldBoxA = FiHoldItemBox?.GetValue(_playerIpc);
						_heldBoxB = FiHoldBox?.GetValue(_playerIpc);
						_heldBoxC = FiHoldBoxCard?.GetValue(_playerIpc);
					}
					return _heldBoxA == box || _heldBoxB == box;
				}
				catch
				{
					return false;
				}
			};
			CardBoxSync.IsLocallyCarried = delegate(InteractablePackagingBox_Card box)
			{
				if ((Object)(object)_playerIpc == (Object)null || (Object)(object)box == (Object)null)
				{
					return false;
				}
				try
				{
					if (_heldBoxFrame != Time.frameCount)
					{
						_heldBoxFrame = Time.frameCount;
						_heldBoxA = FiHoldItemBox?.GetValue(_playerIpc);
						_heldBoxB = FiHoldBox?.GetValue(_playerIpc);
						_heldBoxC = FiHoldBoxCard?.GetValue(_playerIpc);
					}
					return _heldBoxC == box || _heldBoxB == box;
				}
				catch
				{
					return false;
				}
			};
			BoxSync.LocalBoxDestroyed = delegate(InteractablePackagingBox_Item box)
			{
				if (InGameLevel() && !ClientReloading)
				{
					if (Role == CoopRole.Client)
					{
						_boxes.NotifyLocalDestroyed(box);
					}
					else if (Role == CoopRole.Host)
					{
						_boxes.HostNotifyLocalDestroyed();
					}
				}
			};
			_boxes.OnLocalRemoved = delegate(int idx, int type)
			{
				Send(1, MsgType.BoxRemoved, delegate(BinaryWriter bw)
				{
					bw.Write(idx);
					Msg.WriteItemType(bw, (EItemType)type);
				});
			};
			PopulationSync.OnClientStructureChanged = delegate(int kind)
			{
				if (Role == CoopRole.Client && (kind == 2 || kind == 3))
				{
					_cardShelves.InvalidateBaseline();
				}
			};
			_actCardPriceRetry = CardPriceRetryTick;
			_actFrameCardWork = FlushFrameCardWork;
			_actNetPump = delegate
			{
				_net.PumpMainThread();
			};
			_actAvatars = delegate
			{
				AvatarManager.ViewCamera = _playerCamTf;
				_avatars.Tick(_dt);
			};
			_actWorld = delegate
			{
				_world.Tick(_dt, _syncActive);
			};
			_actCardShelves = delegate
			{
				_cardShelves.IsClientRole = Role == CoopRole.Client;
				_cardShelves.Tick(_dt, _syncActive);
			};
			_actObjMoves = delegate
			{
				_objMoves.Tick(_dt, _syncActive);
			};
			_actBoxes = delegate
			{
				if (Role == CoopRole.Host)
				{
					_boxes.HostTick(_dt, _syncActive);
				}
				else if (Role == CoopRole.Client)
				{
					_boxes.ClientTick(_dt, _syncActive && !ClientPreloadHold);
				}
			};
			_actPopulation = delegate
			{
				if (Role == CoopRole.Host)
				{
					_population.HostTick(_dt, _syncActive);
				}
			};
			_actNpcPuppets = delegate
			{
				_npcs.TickPuppets(_dt, InGameLevel());
			};
			_actRegisterMirror = RegisterMirrorTick;
			_actNpcSweep = NpcSweepTick;
			_actStateSend = StateSendTick;
			_actNpcCollect = NpcCollectTick;
			_actRegisterCollect = RegisterCollectTick;
			_grading.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.GradingOp, w);
			};
			_grading.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.GradingState, w);
			};
			_trades.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.TradeOp, w);
			};
			_trades.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.TradeState, w);
			};
			_tables.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.TableState, w);
			};
			_staff.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.StaffOp, w);
			};
			_staff.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.StaffState, w);
			};
			_shopState.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.ShopOp, w);
			};
			_shopState.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.ShopState, w);
			};
			_settings.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.SettingsOp, w);
			};
			_settings.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.SettingsState, w);
			};
			_market.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.MarketState, w);
			};
			_report.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.ReportState, w);
			};
			_containers.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.ContainerOp, w);
			};
			_containers.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.ContainerState, w);
			};
			_containers.RequestBoxResync = delegate
			{
				_boxes.ForceBroadcastNextTick();
			};
			_tournament.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.TournamentState, w);
			};
			_cardBoxes.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.CardBoxOp, w);
			};
			_cardBoxes.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.CardBoxState, w);
			};
			_furnBoxes.SendOp = delegate(Action<BinaryWriter> w)
			{
				Send(1, MsgType.FurnBoxOp, w);
			};
			_furnBoxes.BroadcastState = delegate(Action<BinaryWriter> w)
			{
				Broadcast(MsgType.FurnBoxState, w);
			};
			FurnBoxSync.IsLocallyCarried = delegate(InteractablePackagingBox_Shelf box)
			{
				if ((Object)(object)_playerIpc == (Object)null || (Object)(object)box == (Object)null)
				{
					return false;
				}
				try
				{
					if (_heldBoxFrame != Time.frameCount)
					{
						_heldBoxFrame = Time.frameCount;
						_heldBoxA = FiHoldItemBox?.GetValue(_playerIpc);
						_heldBoxB = FiHoldBox?.GetValue(_playerIpc);
						_heldBoxC = FiHoldBoxCard?.GetValue(_playerIpc);
					}
					return _heldBoxB == box;
				}
				catch
				{
					return false;
				}
			};
			_actModules = ModulesTick;
			SceneManager.sceneLoaded += OnSceneLoaded;
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			for (int num = 0; num < commandLineArgs.Length; num++)
			{
				string text = commandLineArgs[num];
				ulong result2;
				if (text.StartsWith("-coopautohost=") && int.TryParse(text.Substring(14), out var result))
				{
					_autoHostSlot = result;
				}
				else if (text.StartsWith("-coopautojoin="))
				{
					_autoJoinIp = text.Substring(14);
				}
				else if (text == "+connect_lobby" && num + 1 < commandLineArgs.Length && ulong.TryParse(commandLineArgs[num + 1], out result2))
				{
					_autoJoinSteamLobby = result2;
				}
			}
			if (_autoHostSlot >= 0)
			{
				CoopPlugin.Log.LogInfo((object)$"AUTO: will load slot {_autoHostSlot} and host");
			}
			if (_autoJoinIp != null)
			{
				CoopPlugin.Log.LogInfo((object)("AUTO: will join " + _autoJoinIp));
			}
			if (_autoJoinSteamLobby != 0L)
			{
				CoopPlugin.Log.LogInfo((object)$"AUTO: will join Steam lobby {_autoJoinSteamLobby}");
			}
			_steamLobby.Init();
			_steamLobby.OnError = delegate(string err)
			{
				ErrorLine = err;
				CoopPlugin.Log.LogWarning((object)err);
			};
			_steamLobby.OnLobbyCreated = delegate(CSteamID lobby)
			{
				//IL_002b: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				if (_net is SteamTransport steamTransport)
				{
					steamTransport.LobbyId = lobby;
				}
				StatusLine = "Hosting via Steam - click 'Invite friend'";
				ManualLogSource log = CoopPlugin.Log;
				CSteamID val = lobby;
				log.LogInfo((object)("steam: lobby live " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString()));
			};
			_steamLobby.OnEnteredLobby = delegate(CSteamID owner)
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				if (Role == CoopRole.Client && _net is SteamTransport steamTransport)
				{
					steamTransport.LobbyId = _steamLobby.LobbyId;
					steamTransport.ConnectToHost(owner);
					StatusLine = "Connected via Steam - requesting world...";
					SendHello();
				}
			};
			_steamLobby.OnInviteAccepted = delegate(CSteamID lobby)
			{
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				ManualLogSource log = CoopPlugin.Log;
				CSteamID val = lobby;
				log.LogInfo((object)("steam: invite accepted -> lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString()));
				JoinSteam(lobby);
			};
			CEventManager.AddListener<CEventPlayer_OnOpenCardPack>((EventDelegate<CEventPlayer_OnOpenCardPack>)OnLocalPackOpened);
			try
			{
				EnumLendState();
			}
			catch
			{
			}
		}

		public static string EnumLendState()
		{
			try
			{
				if (!ModParity.HostEnumInstalled())
				{
					return null;
				}
				if (!_enumLendWarned)
				{
					_enumLendWarned = true;
					CoopPlugin.Log.LogWarning((object)"CardShopCoop: your custom-card database is currently the HOST's synced copy from a co-op session. Your OWN solo modded saves may not load until you restore it (restore via the co-op window) and RESTART the game.");
				}
				return "custom-card database is the HOST's copy (co-op sync) - solo modded saves may not load; restore via the co-op window";
			}
			catch
			{
				return null;
			}
		}

		public void JoinSteam(CSteamID lobby, string password = "")
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			ErrorLine = "";
			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 (!_steamLobby.SteamAvailable())
			{
				ErrorLine = "Steam isn't running.";
				return;
			}
			if (ModParity.RestartRequiredForJoin)
			{
				ErrorLine = "the host's card database was installed on this PC - RESTART the game before joining";
				return;
			}
			Role = CoopRole.Client;
			GuestBorrowedWorld = true;
			IsSteamSession = true;
			_joinPassword = password ?? "";
			LastFailedLobby = lobby;
			_net = new SteamTransport(isHost: false)
			{
				KeepaliveFrame = Msg.Build(MsgType.Ping)
			};
			StatusLine = "Joining Steam lobby...";
			_steamLobby.Join(lobby);
		}

		public void StartHostingSteam(bool isPublic, string lobbyName, string password)
		{
			ErrorLine = "";
			if (Role != CoopRole.None)
			{
				ErrorLine = "Already in a session.";
				return;
			}
			if (!InGameLevel())
			{
				ErrorLine = "Load your shop first, then host.";
				return;
			}
			if (!_steamLobby.SteamAvailable())
			{
				ErrorLine = "Steam isn't running - use LAN instead.";
				return;
			}
			CardShopCoop.Util.EnumMap.Clear();
			Role = CoopRole.Host;
			IsSteamSession = true;
			HostPassword = password ?? "";
			_net = new SteamTransport(isHost: true)
			{
				KeepaliveFrame = Msg.Build(MsgType.Ping)
			};
			StatusLine = "Creating Steam lobby...";
			_steamLobby.Host(isPublic, lobbyName, HostPassword.Length > 0);
		}

		public void OpenSteamInvite()
		{
			_steamLobby.OpenInviteDialog();
		}

		private void SendHello()
		{
			Send(1, MsgType.Hello, delegate(BinaryWriter bw)
			{
				bw.Write("1.0.37");
				bw.Write(CoopPlugin.PlayerName.Value);
				bw.Write(_joinPassword ?? "");
				bw.Write(ModParity.PluginHash());
				bw.Write(ModParity.EnumHash());
				bw.Write(ModParity.CardsHash());
				WriteCappedList(bw, ModParity.PluginList());
				WriteCappedList(bw, ModParity.CardsList());
				byte[] array;
				try
				{
					array = Msg.Gzip(Encoding.UTF8.GetBytes(string.Join("\n", ModParity.EnumLines())));
				}
				catch (Exception ex)
				{
					CoopPlugin.Log.LogWarning((object)("enum lines for Hello: " + ex.Message));
					array = Msg.Gzip(new byte[0]);
				}
				bw.Write(array.Length);
				bw.Write(array);
			});
		}

		private static List<string> SafeEnumLines()
		{
			try
			{
				return ModParity.EnumLines() ?? new List<string>();
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("enum lines: " + ex.Message));
				return new List<string>();
			}
		}

		private static List<string> SafeCardsList()
		{
			try
			{
				return ModParity.CardsList() ?? new List<string>();
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("cards list: " + ex.Message));
				return new List<string>();
			}
		}

		private static byte[] GzipLines(List<string> lines)
		{
			try
			{
				string[] array = (lines ?? new List<string>()).ToArray();
				byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\n", array));
				if (bytes.Length > 262144)
				{
					CoopPlugin.Log.LogWarning((object)("registry blob is OVER THE WIRE CAP: " + array.Length + " ids, " + bytes.Length + " bytes uncompressed vs a " + 262144 + "-byte cap - the other PC will IGNORE it and modded ids will not be translated this session (ids must already match)"));
				}
				return Msg.Gzip(bytes);
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("registry blob: " + ex.Message));
				return Msg.Gzip(new byte[0]);
			}
		}

		private static List<string> ReadCappedEnumBlob(BinaryReader br, out string digest)
		{
			digest = "none";
			List<string> list = new List<string>();
			try
			{
				int num = br.ReadInt32();
				if (num <= 0)
				{
					return list;
				}
				if (num > 262144)
				{
					int num2 = num;
					while (num2 > 0)
					{
						int num3 = br.ReadBytes(Math.Min(num2, 8192)).Length;
						if (num3 <= 0)
						{
							break;
						}
						num2 -= num3;
					}
					CoopPlugin.Log.LogWarning((object)("registry blob over cap (" + num + " bytes compressed, cap " + 262144 + ") - ignored; modded ids will not be translated from it"));
					return list;
				}
				byte[] array = br.ReadBytes(num);
				if (array.Length != num)
				{
					return list;
				}
				string text = GunzipCapped(array, 262144);
				if (text == null)
				{
					return list;
				}
				digest = Fnv(text).ToString("X8");
				string[] array2 = text.Split(new char[1] { '\n' });
				for (int i = 0; i < array2.Length; i++)
				{
					string text2 = array2[i].Trim();
					if (text2.Length > 0)
					{
						list.Add(text2);
					}
				}
			}
			catch
			{
			}
			return list;
		}

		private static string GunzipCapped(byte[] data, int cap)
		{
			try
			{
				using MemoryStream stream = new MemoryStream(data, writable: false);
				using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress);
				using MemoryStream memoryStream = new MemoryStream();
				byte[] array = new byte[8192];
				int num;
				while ((num = gZipStream.Read(array, 0, array.Length)) > 0)
				{
					if (memoryStream.Length + num > cap)
					{
						CoopPlugin.Log.LogWarning((object)("registry blob unpacked OVER CAP (more than " + cap + " bytes from " + data.Length + " compressed) - ignored, NOT a vanilla peer"));
						return null;
					}
					memoryStream.Write(array, 0, num);
				}
				return Encoding.UTF8.GetString(memoryStream.ToArray());
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("registry blob could not be unpacked (" + data.Length + " bytes, not valid gzip: " + ex.Message + ") - ignored"));
				return null;
			}
		}

		private static List<string> EnumConflicts(List<string> theirs, List<string> ours)
		{
			List<string> list = new List<string>();
			if (theirs == null || theirs.Count == 0 || ours == null || ours.Count == 0)
			{
				return list;
			}
			Dictionary<string, string> dictionary = EnumMap(theirs);
			Dictionary<string, string> dictionary2 = EnumMap(ours);
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (dictionary2.TryGetValue(item.Key, out var value) && value != item.Value)
				{
					list.Add(item.Key + " -> yours " + item.Value + ", host " + value);
				}
			}
			list.Sort(StringComparer.Ordinal);
			return list;
		}

		private static Dictionary<string, string> EnumMap(List<string> lines)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			foreach (string line in lines)
			{
				if (!string.IsNullOrEmpty(line))
				{
					int num = line.LastIndexOf('=');
					if (num > 0 && num != line.Length - 1)
					{
						dictionary[line.Substring(0, num)] = line.Substring(num + 1);
					}
				}
			}
			return dictionary;
		}

		private static string DescribeConflicts(List<string> conflicts)
		{
			int num = Math.Min(conflicts.Count, 5);
			string text = string.Join("; ", conflicts.GetRange(0, num).ToArray());
			if (conflicts.Count > num)
			{
				text += $" (+{conflicts.Count - num} more)";
			}
			if (text.Length > 400)
			{
				text = text.Substring(0, 397) + "...";
			}
			return text;
		}

		private static string PeerSyncKey(string name, string enumDigest)
		{
			string text = (name ?? "").Trim().ToLowerInvariant();
			string text2 = (string.IsNullOrEmpty(enumDigest) ? "none" : enumDigest);
			return ((text.Length > 0) ? ("n:" + text) : "anon") + "|" + text2;
		}

		private static void WriteCappedList(BinaryWriter bw, List<string> list)
		{
			int num = ((list != null) ? Math.Min(list.Count, 256) : 0);
			bw.Write(num);
			for (int i = 0; i < num; i++)
			{
				bw.Write(list[i] ?? "");
			}
		}

		private static List<string> ReadCappedList(BinaryReader br)
		{
			List<string> list = new List<string>();
			try
			{
				int num = br.ReadInt32();
				if (num < 0)
				{
					num = 0;
				}
				if (num > 256)
				{
					num = 256;
				}
				for (int i = 0; i < num; i++)
				{
					list.Add(br.ReadString());
				}
			}
			catch
			{
			}
			return list;
		}

		private static string DescribeModDiff(List<string> theirs, List<string> ours, string head, string diffLabel)
		{
			if (theirs == null || theirs.Count == 0 || ours == null || ours.Count == 0)
			{
				return null;
			}
			Dictionary<string, string> dictionary = DiffMap(theirs);
			Dictionary<string, string> dictionary2 = DiffMap(ours);
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			foreach (KeyValuePair<string, string> item in dictionary2)
			{
				if (!dictionary.ContainsKey(item.Key))
				{
					list.Add(item.Key);
				}
			}
			foreach (KeyValuePair<string, string> item2 in dictionary)
			{
				if (!dictionary2.TryGetValue(item2.Key, out var value))
				{
					list2.Add(item2.Key);
				}
				else if (value != item2.Value)
				{
					list3.Add(item2.Key + " (host " + value + " vs yours " + item2.Value + ")");
				}
			}
			if (list.Count == 0 && list2.Count == 0 && list3.Count == 0)
			{
				return null;
			}
			list.Sort(StringComparer.Ordinal);
			list2.Sort(StringComparer.Ordinal);
			list3.Sort(StringComparer.Ordinal);
			List<string> list4 = new List<string>();
			if (list.Count > 0)
			{
				list4.Add("you are missing: " + JoinCapped(list));
			}
			if (list2.Count > 0)
			{
				list4.Add("you have extra: " + JoinCapped(list2));
			}
			if (list3.Count > 0)
			{
				list4.Add(diffLabel + ": " + JoinCapped(list3));
			}
			string text = head + string.Join(" | ", list4);
			if (text.Length > 700)
			{
				text = text.Substring(0, 697) + "...";
			}
			return text;
		}

		private static Dictionary<string, string> DiffMap(List<string> entries)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			foreach (string entry in entries)
			{
				if (!string.IsNullOrEmpty(entry))
				{
					int num = entry.IndexOf('=');
					string key = ((num > 0) ? entry.Substring(0, num) : entry);
					string value = ((num > 0) ? entry.Substring(num + 1) : "");
					dictionary[key] = value;
				}
			}
			return dictionary;
		}

		private static string JoinCapped(List<string> items)
		{
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			for (int i = 0; i < items.Count; i++)
			{
				string text = ((num > 0) ? ", " : "") + items[i];
				if (num > 0 && stringBuilder.Length + text.Length > 220)
				{
					break;
				}
				stringBuilder.Append(text);
				num++;
			}
			if (num < items.Count)
			{
				stringBuilder.Append($" (+{items.Count - num} more)");
			}
			return stringBuilder.ToString();
		}

		private void RejectConn(int connId, string reason)
		{
			CoopPlugin.Log.LogWarning((object)$"rejected connection {connId}: {reason}");
			Send(connId, MsgType.Bye, delegate(BinaryWriter bw)
			{
				bw.Write(reason);
			});
			_pendingKicks.Add(new KeyValuePair<int, float>(connId, 1.5f));
		}

		private static void ReadHoldPayload(BinaryReader br, byte hold, out List<int> types, out List<CardData> cards)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected I4, but got Unknown
			types = null;
			cards = null;
			int num = br.ReadByte();
			if (num == 0)
			{
				return;
			}
			if (hold == 3)
			{
				cards = new List<CardData>(num);
				for (int i = 0; i < num; i++)
				{
					cards.Add(Msg.ReadCard(br));
				}
			}
			else
			{
				types = new List<int>(num);
				for (int j = 0; j < num; j++)
				{
					types.Add((int)Msg.ReadItemType(br));
				}
			}
		}

		private static void WriteHoldPayload(BinaryWriter bw, byte hold, List<int> types, List<CardData> cards)
		{
			if (hold == 3)
			{
				bw.Write((byte)(cards?.Count ?? 0));
				if (cards == null)
				{
					return;
				}
				{
					foreach (CardData card in cards)
					{
						Msg.WriteCard(bw, card);
					}
					return;
				}
			}
			bw.Write((byte)(types?.Count ?? 0));
			if (types == null)
			{
				return;
			}
			foreach (int type in types)
			{
				Msg.WriteItemType(bw, (EItemType)type);
			}
		}

		private static bool ApplyCardDelta(bool isAdd, int amount, CardData card, out bool relayAnyway)
		{
			relayAnyway = false;
			if (card.cardGrade != 0 && (card.cardGrade < 1 || card.cardGrade > 10) && !GradingInterop.Present)
			{
				CoopPlugin.Log.LogWarning((object)$"card delta: dropping corrupt graded card {CardIdent(card)} (grade {card.cardGrade}) - not applied (Grading Overhaul absent)");
				return false;
			}
			GamePatches.ApplyingRemoteCards = true;
			try
			{
				if (!CardSetInstalledHere(card))
				{
					relayAnyway = true;
					if (_priceWarnedKeys.Add("delta:" + CardPriceKey(card)))
					{
						CoopPlugin.Log.LogWarning((object)("card delta: " + CardIdent(card) + " is from a card set you don't have installed - skipped"));
					}
					return false;
				}
				if (isAdd)
				{
					if (card.cardGrade > 10)
					{
						GradingInterop.Remember(card);
					}
					CPlayerData.AddCard(card, amount);
				}
				else if (card.cardGrade > 0)
				{
					int num = 0;
					for (int i = 0; i < amount; i++)
					{
						if (!CPlayerData.HasGradedCardInAlbum(card))
						{
							break;
						}
						CPlayerData.RemoveGradedCard(card, true);
						num++;
					}
					if (num == 0)
					{
						CoopPlugin.Log.LogWarning((object)$"graded remove: {CardIdent(card)} (grade {card.cardGrade}) not in this album - skipped (album mismatch?)");
						return false;
					}
				}
				else
				{
					int cardAmount = CPlayerData.GetCardAmount(card);
					if (cardAmount < amount)
					{
						CoopPlugin.Log.LogWarning((object)$"card delta would drive {CardIdent(card)} negative (have {cardAmount}, remove {amount}) - skipped (card registry mismatch?)");
						return false;
					}
					CPlayerData.ReduceCard(card, amount);
				}
			}
			finally
			{
				GamePatches.ApplyingRemoteCards = false;
			}
			_deltaAppliedThisFrame++;
			if (_deltaLogBuf.Count < 5)
			{
				_deltaLogBuf.Add(new PendingCard
				{
					IsAdd = isAdd,
					Amount = amount,
					Card = SnapshotCard(card)
				});
			}
			_binderRefreshPending = true;
			return true;
		}

		private static void ReadCardDelta(BinaryReader br, out bool isAdd, out int amount, out CardData card)
		{
			isAdd = br.ReadBoolean();
			amount = br.ReadInt32();
			card = Msg.ReadCard(br);
		}

		private bool ApplyOrHoldCardDelta(bool isAdd, int amount, CardData card, out bool relayAnyway)
		{
			relayAnyway = false;
			if (!InGameLevel())
			{
				_pendingCardDeltas.Add(new PendingCard
				{
					IsAdd = isAdd,
					Amount = amount,
					Card = card
				});
				return false;
			}
			return ApplyCardDelta(isAdd, amount, card, out relayAnyway);
		}

		private static CardData SnapshotCard(CardData c)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			return new CardData
			{
				expansionType = c.expansionType,
				monsterType = c.monsterType,
				borderType = c.borderType,
				isFoil = c.isFoil,
				isDestiny = c.isDestiny,
				isChampionCard = c.isChampionCard,
				isNew = c.isNew,
				cardGrade = c.cardGrade,
				gradedCardIndex = c.gradedCardIndex
			};
		}

		private static string CardPriceKey(CardData card)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected I4, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected I4, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected I4, but got Unknown
			if (card == null)
			{
				return null;
			}
			return (int)card.expansionType + ":" + (int)card.monsterType + ":" + (int)card.borderType + ":" + (card.isFoil ? 1 : 0) + (card.isDestiny ? 1 : 0) + (card.isChampionCard ? 1 : 0) + ":" + card.cardGrade;
		}

		internal static void ClearCardSetCache()
		{
			_shownMonsters.Clear();
		}

		private static bool MonsterHasDataRowHere(ECardExpansionType expansion, EMonsterType monster)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Inv() == (Object)null)
			{
				return false;
			}
			if (!_shownMonsters.TryGetValue(expansion, out var value))
			{
				List<EMonsterType> shownMonsterList = InventoryBase.GetShownMonsterList(expansion);
				if (shownMonsterList == null || shownMonsterList.Count == 0)
				{
					return false;
				}
				value = new HashSet<EMonsterType>(shownMonsterList);
				_shownMonsters[expansion] = value;
			}
			return value.Contains(monster);
		}

		internal static bool CardSetInstalledHere(CardData card)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (card == null)
				{
					return false;
				}
				if ((int)card.expansionType == -1)
				{
					return false;
				}
				if ((int)card.monsterType == 0)
				{
					return false;
				}
				if (!Enum.IsDefined(typeof(ECardExpansionType), card.expansionType))
				{
					return false;
				}
				if (CPlayerData.GetCardCollectedList(card.expansionType, card.isDestiny) == null)
				{
					return false;
				}
				return MonsterHasDataRowHere(card.expansionType, card.monsterType);
			}
			catch (Exception ex)
			{
				if (_priceWarnedKeys.Add("check:" + CardPriceKey(card)))
				{
					CoopPlugin.Log.LogWarning((object)("card set check: " + ex.Message));
				}
				return false;
			}
		}

		private static string CardIdent(CardData c)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected I4, but got Unknown
			if (c == null)
			{
				return "(null card)";
			}
			if ((int)c.expansionType < 7)
			{
				return ((object)Unsafe.As<EMonsterType, EMonsterType>(ref c.monsterType)/*cast due to .constrained prefix*/).ToString();
			}
			return ((object)Unsafe.As<ECardExpansionType, ECardExpansionType>(ref c.expansionType)/*cast due to .constrained prefix*/).ToString() + "#" + (int)c.monsterType;
		}

		internal static void WarnRefusedCard(CardData c, string context)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected I4, but got Unknown
			if (c != null && _priceWarnedKeys.Add(context + ":" + CardPriceKey(c)))
			{
				CoopPlugin.Log.LogWarning((object)$"{context}: {c.expansionType}#{(int)c.monsterType} is from a card set this PC doesn't have - the card could NOT be processed here");
			}
		}

		private static bool ApplyRemoteCardPrice(CardData card, float price, string from, out float actual, out bool relayAnyway)
		{
			actual = price;
			relayAnyway = false;
			if (card == null)
			{
				return false;
			}
			string text = CardPriceKey(card);
			if (!CardSetInstalledHere(card))
			{
				relayAnyway = true;
				if (_priceWarnedKeys.Add("set:" + text))
				{
					CoopPlugin.Log.LogWarning((object)("card price for unknown card set skipped - other side has a content pack this PC doesn't (" + text + "; further ones logged once each)"));
				}
				return false;
			}
			if (card.cardGrade > 10)
			{
				if (!GradingInterop.Present)
				{
					relayAnyway = true;
					return false;
				}
				GradingInterop.Remember(card);
			}
			float num = float.NaN;
			try
			{
				num = CPlayerData.GetCardPrice(card);
			}
			catch
			{
			}
			try
			{
				CPlayerData.SetCardPrice(card, price);
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("card price apply: " + ex.Message));
				return false;
			}
			try
			{
				actual = CPlayerData.GetCardPrice(card);
			}
			catch (Exception ex2)
			{
				if (_priceWarnedKeys.Add("read:" + text))
				{
					CoopPlugin.Log.LogWarning((object)("card price read-back: " + ex2.Message));
				}
				actual = price;
			}
			if (Math.Abs(actual - price) > 0.0075f)
			{
				if (_priceWarnedKeys.Add("store:" + text))
				{
					CoopPlugin.Log.LogWarning((object)$"card price {text}: the game's price store did not accept {price:F4} (it holds {actual:F4}) - modded expansion? (logged once per card)");
				}
				relayAnyway = true;
				return false;
			}
			if (float.IsNaN(num) || Math.Abs(num - actual) > 0.0075f)
			{
				CoopPlugin.Log.LogInfo((object)$"card price applied: {text} = {actual:F2} (from {from})");
			}
			return true;
		}

		private static void RefreshOpenBinder()
		{
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Invalid comparison between Unknown and I4
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)_deltaIpc == (Object)null)
				{
					_deltaIpc = Object.FindObjectOfType<InteractionPlayerController>();
				}
				CollectionBinderFlipAnimCtrl val = (((Object)(object)_deltaIpc != (Object)null) ? _deltaIpc.m_CollectionBinderFlipAnimCtrl : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				val.SetCanUpdateSort(true);
				bool flag = FiBinderIsBookOpen != null && (bool)FiBinderIsBookOpen.GetValue(val);
				if (flag && MiBinderResort != null)
				{
					MiBinderResort.Invoke(val, new object[1] { false });
				}
				if (!flag || !(FiBinderUI != null))
				{
					return;
				}
				object? value = FiBinderUI.GetValue(val);
				CollectionBinderUI val2 = (CollectionBinderUI)((value is CollectionBinderUI) ? value : null);
				if (!((Object)(object)val2 != (Object)null))
				{
					return;
				}
				bool num = FiBinderIsGradedAlbum != null && (bool)FiBinderIsGradedAlbum.GetValue(val);
				ECardExpansionType val3 = (ECardExpansionType)((!(FiBinderExpansionType != null)) ? (-1) : ((int)(ECardExpansionType)FiBinderExpansionType.GetValue(val)));
				if (num)
				{
					float num2 = 0f;
					for (int i = 0; i < CPlayerData.m_GradedCardInventoryList.Count; i++)
					{
						if (CPlayerData.m_GradedCardInventoryList[i].amount > 10)
						{
							CPlayerData.m_GradedCardInventoryList[i].amount = 10;
						}
						num2 += CPlayerData.GetCardMarketPrice(CPlayerData.GetGradedCardData(CPlayerData.m_GradedCardInventoryList[i]));
					}
					val2.SetTotalValue(num2);
				}
				else if ((int)val3 == 2)
				{
					val2.SetTotalValue(CPlayerData.GetCardAlbumTotalValue(val3, false) + CPlayerData.GetCardAlbumTotalValue(val3, true));
				}
				else
				{
					val2.SetTotalValue(CPlayerData.GetCardAlbumTotalValue(val3, false));
				}
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("binder relayout after card change failed: " + ex.Message));
			}
		}

		private void FlushPendingCardWork()
		{
			if (!InGameLevel() || (_pendingCardDeltas.Count == 0 && _pendingCardPrices.Count == 0))
			{
				return;
			}
			Guarded("pending-cards", delegate
			{
				foreach (PendingCard pendingCardDelta in _pendingCardDeltas)
				{
					ApplyCardDelta(pendingCardDelta.IsAdd, pendingCardDelta.Amount, pendingCardDelta.Card, out var _);
				}
				if (_pendingCardDeltas.Count > 0)
				{
					CoopPlugin.Log.LogInfo((object)$"applied {_pendingCardDeltas.Count} card change(s) held during loading");
				}
				_pendingCardDeltas.Clear();
				bool flag = Role == CoopRole.Host;
				List<KeyValuePair<CardData, float>> list = (flag ? new List<KeyValuePair<CardData, float>>() : null);
				GamePatches.ApplyingRemotePrice = true;
				try
				{
					foreach (KeyValuePair<CardData, float> pendingCardPrice in _pendingCardPrices)
					{
						float actual;
						bool relayAnyway2;
						bool flag2 = ApplyRemoteCardPrice(pendingCardPrice.Key, pendingCardPrice.Value, "load queue", out actual, out relayAnyway2);
						if (flag)
						{
							if (flag2)
							{
								list.Add(new KeyValuePair<CardData, float>(pendingCardPrice.Key, actual));
							}
							else if (relayAnyway2)
							{
								list.Add(pendingCardPrice);
							}
						}
					}
				}
				catch (Exception ex)
				{
					CoopPlugin.Log.LogWarning((object)("pending card price apply: " + ex.Message));
				}
				finally
				{
					GamePatches.ApplyingRemotePrice = false;
				}
				_pendingCardPrices.Clear();
				if (list != null)
				{
					for (int i = 0; i < list.Count; i++)
					{
						KeyValuePair<CardData, float> kv = list[i];
						Broadcast(MsgType.CardPriceSet, delegate(BinaryWriter bw)
						{
							Msg.WriteCard(bw, kv.Key);
							bw.Write(kv.Value);
						});
					}
				}
			});
		}

		private void RelayRawToOthers(int senderConn, MsgType type, byte[] payload)
		{
			if (Role != CoopRole.Host || _net == null || _net.ConnectionCount <= 1)
			{
				return;
			}
			FlushCardDeltaOutbox();
			byte[] frame = Msg.Build(type, delegate(BinaryWriter bw)
			{
				if (payload != null)
				{
					bw.Write(payload);
				}
			});
			foreach (int item in _net.ConnIds())
			{
				if (item != senderConn)
				{
					_net.Send(item, frame);
				}
			}
		}

		private void RelayCardDeltaBatchToOthers(int senderConn, List<PendingCard> deltas)
		{
			if (Role != CoopRole.Host || _net == null || _net.ConnectionCount <= 1 || deltas.Count == 0)
			{
				return;
			}
			FlushCardDeltaOutbox();
			byte[] frame = Msg.Build(MsgType.CardDeltaBatch, delegate(BinaryWriter bw)
			{
				bw.Write(deltas.Count);
				for (int i = 0; i < deltas.Count; i++)
				{
					bw.Write(deltas[i].IsAdd);
					bw.Write(deltas[i].Amount);
					Msg.WriteCard(bw, deltas[i].Card);
				}
			});
			foreach (int item in _net.ConnIds())
			{
				if (item != senderConn)
				{
					_net.Send(item, frame);
				}
			}
		}

		private void FlushCardDeltaOutbox()
		{
			if (_cardDeltaOutbox.Count == 0 || _flushingCardDeltas)
			{
				return;
			}
			if (_net == null)
			{
				_cardDeltaOutbox.Clear();
				return;
			}
			_flushingCardDeltas = true;
			try
			{
				int count = _cardDeltaOutbox.Count;
				int n;
				for (int i = 0; i < count; i += n)
				{
					int start = i;
					n = Math.Min(200, count - start);
					Broadcast(MsgType.CardDeltaBatch, delegate(BinaryWriter bw)
					{
						bw.Write(n);
						for (int j = start; j < start + n; j++)
						{
							PendingCard pendingCard = _cardDeltaOutbox[j];
							bw.Write(pendingCard.IsAdd);
							bw.Write(pendingCard.Amount);
							Msg.WriteCard(bw, pendingCard.Card);
						}
					});
				}
				if (count > 200)
				{
					CoopPlugin.Log.LogInfo((object)$"card deltas: {count} sent as {(count + 200 - 1) / 200} batch(es)");
				}
				_cardDeltaOutbox.Clear();
			}
			finally
			{
				_flushingCardDeltas = false;
			}
		}

		private void FlushFrameCardWork()
		{
			if (_deltaAppliedThisFrame > 0)
			{
				if (_deltaAppliedThisFrame <= 5)
				{
					for (int i = 0; i < _deltaLogBuf.Count; i++)
					{
						PendingCard pendingCard = _deltaLogBuf[i];
						CoopPlugin.Log.LogInfo((object)string.Format("card delta applied: {0}{1} {2}{3}", pendingCard.IsAdd ? "+" : "-", pendingCard.Amount, CardIdent(pendingCard.Card), (pendingCard.Card.cardGrade > 0) ? $" (grade {pendingCard.Card.cardGrade})" : (pendingCard.Card.isFoil ? " (foil)" : "")));
					}
				}
				else
				{
					CoopPlugin.Log.LogInfo((object)$"applied {_deltaAppliedThisFrame} card deltas");
				}
				_deltaLogBuf.Clear();
				_deltaAppliedThisFrame = 0;
			}
			if (_binderRefreshPending)
			{
				_binderRefreshPending = false;
				RefreshOpenBinder();
			}
			FlushCardDeltaOutbox();
		}

		private void RelayTagToOthers(int senderConn, byte kind, int extra = -1)
		{
			if (Role != CoopRole.Host || _net == null || _net.ConnectionCount <= 1)
			{
				return;
			}
			byte[] frame = Msg.Build(MsgType.RelayTag, delegate(BinaryWriter bw)
			{
				bw.Write((byte)senderConn);
				bw.Write(kind);
				Msg.WriteItemType(bw, (EItemType)extra);
			});
			foreach (int item in _net.ConnIds())
			{
				if (item != senderConn)
				{
					_net.Send(item, frame);
				}
			}
		}

		private void BroadcastRoster()
		{
			if (Role != CoopRole.Host)
			{
				return;
			}
			List<KeyValuePair<int, string>> entries = new List<KeyValuePair<int, string>>(PeerNames);
			Broadcast(MsgType.Roster, delegate(BinaryWriter bw)
			{
				bw.Write((byte)entries.Count);
				foreach (KeyValuePair<int, string> item in entries)
				{
					bw.Write((byte)item.Key);
					bw.Write(item.Value);
				}
			});
		}

		private void OnLocalPackOpened(CEventPlayer_OnOpenCardPack evt)
		{
			if (Role != CoopRole.None && _net != null && _net.ConnectionCount > 0)
			{
				Broadcast(MsgType.Activity, delegate(BinaryWriter bw)
				{
					bw.Write((byte)1);
					Msg.WriteItemType(bw, (EItemType)evt.m_PackIndex);
				});
			}
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			CEventManager.RemoveListener<CEventPlayer_OnOpenCardPack>((EventDelegate<CEventPlayer_OnOpenCardPack>)OnLocalPackOpened);
			Shutdown("plugin unloaded");
		}

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

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			_avatars.Clear();
			_world.Reset();
			_npcs.Reset();
			_cardShelves.Reset();
			_objMoves.Reset();
			_boxes.Reset();
			_population.Reset();
			_registerMirror.Reset();
			ModulesReset();
			PromptLine = "";
			_lightManager = null;
			_cmSweep = null;
			_cmSpray = null;
			_inventory = null;
			_renamerHandled = false;
			_catalogSent = false;
			if (ClientReloading)
			{
				_reloadGrace = 10f;
			}
			_playerTf = null;
			_playerCamTf = null;
			_playerIpc = null;
			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 && !ClientReloading)
			{
				Shutdown("left the session (world reloaded)");
			}
		}

		private bool InGameLevel()
		{
			CGameManager instance = CSingleton<CGameManager>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.m_IsGameLevel;
			}
			return false;
		}

		private static InventoryBase Inv()
		{
			if ((Object)(object)_inventory == (Object)null)
			{
				_inventory = Object.FindObjectOfType<InventoryBase>();
			}
			return _inventory;
		}

		private void ModulesTick()
		{
			bool flag = InGameLevel();
			if (Role == CoopRole.Host)
			{
				_grading.HostTick(_dt, flag);
				_trades.HostTick(_dt, flag);
				_tables.HostTick(_dt, flag);
				_staff.HostTick(_dt, flag);
				_shopState.HostTick(_dt, flag);
				_settings.HostTick(_dt, flag);
				_market.HostTick(_dt, flag);
				_report.HostTick(_dt, flag);
				_containers.HostTick(_dt, flag);
				_tournament.HostTick(_dt, flag);
				_cardBoxes.HostTick(_dt, flag);
				_furnBoxes.HostTick(_dt, flag);
			}
			else
			{
				if (Role != CoopRole.Client)
				{
					return;
				}
				_trades.ClientTick(_dt, flag);
				_cardBoxes.ClientTick(_dt, flag && !ClientPreloadHold);
				_furnBoxes.ClientTick(_dt, flag && !ClientPreloadHold);
				_catalogTimer += _dt;
				if (flag && (_catalogTimer >= 45f || !_catalogSent))
				{
					_catalogTimer = 0f;
					_catalogSent = true;
					int num = LocalCatalogHash();
					if (num != _lastCatalogSentHash)
					{
						_lastCatalogSentHash = num;
						SendCatalogDigest();
					}
				}
			}
		}

		private static int LocalCatalogHash()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected I4, but got Unknown
			try
			{
				int num = CatalogCount();
				int num2 = 17;
				for (int i = 0; i < num; i++)
				{
					RestockData val = CatalogAt(i);
					if (val != null)
					{
						num2 = num2 * 31 + ((val.itemType << 1) | val.isBigBox);
					}
				}
				return num2;
			}
			catch
			{
				return 0;
			}
		}

		private void ModulesReset()
		{
			_grading.Reset();
			_trades.Reset();
			_tables.Reset();
			_staff.Reset();
			_shopState.Reset();
			_settings.Reset();
			_market.Reset();
			_report.Reset();
			_containers.Reset();
			_tournament.Reset();
			_cardBoxes.Reset();
			_furnBoxes.Reset();
		}

		private void ModulesForceResend()
		{
			_grading.ForceResend();
			_trades.ForceResend();
			_tables.ForceResend();
			_staff.ForceResend();
			_shopState.ForceResend();
			_settings.ForceResend();
			_market.ForceResend();
			_report.ForceResend();
			_containers.ForceResend();
			_tournament.ForceResend();
			_cardBoxes.ForceResend();
			_furnBoxes.ForceResend();
		}

		private void RegisterMirrorTick()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			_registerMirror.Tick(_dt);
			_regStateTimer += _dt;
			if (_regStateTimer >= 0.5f && InGameLevel())
			{
				_regStateTimer -= 0.5f;
				Transform val = ResolvePlayer();
				int nearestCounter = (((Object)(object)val != (Object)null) ? RegisterServe.FindNearestCounter(val.position, CoopPlugin.ServeReach.Value, quiet: true) : (-1));
				string text = _trades.PromptFor(nearestCounter) ?? _registerMirror.PromptFor(nearestCounter);
				if (text == null && Role == CoopRole.Client && _trades.AnyKnownOffer())
				{
					text = $"a customer wants to trade - go to the counter and press {CoopPlugin.ServeKey.Value}";
				}
				PromptLine = text ?? "";
			}
		}

		private void NpcSweepTick()
		{
			if (!_renamerHandled)
			{
				_renamerHandled = true;
				ShopRenamer val = Object.FindObjectOfType<ShopRenamer>();
				if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf)
				{
					try
					{
						_shopSign = (TMP_Text)(object)val.m_ShopName;
					}
					catch
					{
					}
					((Component)val).gameObject.SetActive(false);
					CoopPlugin.Log.LogInfo((object)"disabled shop-renamer trigger (host names the shop)");
					if ((Object)(object)_shopSign != (Object)null && !string.IsNullOrEmpty(_lastShopNameApplied))
					{
						try
						{
							_shopSign.text = _lastShopNameApplied;
						}
						catch
						{
						}
					}
				}
			}
			if ((Object)(object)_cmSweep == (Object)null)
			{
				_cmSweep = Object.FindObjectOfType<CustomerManager>();
			}
			if ((Object)(object)_cmSweep != (Object)null)
			{
				List<Customer> customerList = _cmSweep.GetCustomerList();
				for (int i = 0; i < customerList.Count; i++)
				{
					if ((Object)(object)customerList[i] != (Object)null && ((Component)customerList[i]).gameObject.activeSelf)
					{
						((Component)customerList[i]).gameObject.SetActive(false);
					}
				}
			}
			List<Worker> workerList = WorkerManager.GetWorkerList();
			if (workerList == null)
			{
				return;
			}
			for (int j = 0; j < workerList.Count; j++)
			{
				if ((Object)(object)workerList[j] != (Object)null && ((Component)workerList[j]).gameObject.activeSelf)
				{
					((Component)workerList[j]).gameObject.SetActive(false);
				}
			}
		}

		private void StateSendTick()
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			float num = 1f / Mathf.Clamp(CoopPlugin.SendRateHz.Value, 4f, 30f);
			Transform val = (InGameLevel() ? ResolvePlayer() : null);
			if (_stateTimer < num || (Object)(object)val == (Object)null)
			{
				return;
			}
			Vector3 pos = val.position;
			float speed = 0f;
			if (_hasLastPos)
			{
				Vector3 val2 = pos - _lastPos;
				val2.y = 0f;
				speed = Mathf.Clamp(((Vector3)(ref val2)).magnitude / _stateTimer, 0f, 6f);
			}
			_lastPos = pos;
			_hasLastPos = true;
			float yaw = (((Object)(object)_playerCamTf != (Object)null) ? _playerCamTf.eulerAngles.y : (((Object)(object)Camera.main != (Object)null) ? ((Component)Camera.main).transform.eulerAngles.y : val.eulerAngles.y));
			byte hold = ComputeHoldState();
			BroadcastTransient(MsgType.PlayerState, delegate(BinaryWriter bw)
			{
				bw.Write(pos.x);
				bw.Write(pos.y);
				bw.Write(pos.z);
				bw.Write(yaw);
				bw.Write(speed);
				bw.Write(hold);
				if (hold == 3)
				{
					bw.Write((byte)_holdCardsBuf.Count);
					{
						foreach (CardData item in _holdCardsBuf)
						{
							Msg.WriteCard(bw, item);
						}
						return;
					}
				}
				bw.Write((byte)_holdTypesBuf.Count);
				foreach (int item2 in _holdTypesBuf)
				{
					Msg.WriteItemType(bw, (EItemType)item2);
				}
			});
			_diagSent++;
			_stateTimer = 0f;
		}

		private void NpcCollectTick()
		{
			List<byte[]> list = _npcs.HostCollect(_dt);
			if (list == null)
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				byte[] c = list[i];
				BroadcastTransient(MsgType.NpcState, delegate(BinaryWriter bw)
				{
					bw.Write(c);
				});
			}
		}

		private void RegisterCollectTick()
		{
			_regStateTimer += _dt;
			if (!(_regStateTimer >= 0.5f))
			{
				return;
			}
			_regStateTimer -= 0.5f;
			byte[] batch = RegisterServe.CollectStates();
			if (batch != null)
			{
				BroadcastTransient(MsgType.RegisterState, delegate(BinaryWriter bw)
				{
					bw.Write(batch);
				});
			}
		}

		private Transform ResolvePlayer()
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_playerTf != (Object)null)
			{
				return _playerTf;
			}
			InteractionPlayerController val = InteractionPlayerController.m_Instance;
			if ((Object)(object)val == (Object)null)
			{
				val = Object.FindObjectOfType<InteractionPlayerController>();
			}
			if ((Object)(object)val != (Object)null)
			{
				_playerIpc = val;
				_playerTf = (((Object)(object)val.m_WalkerCtrl != (Object)null) ? ((Component)val.m_WalkerCtrl).transform : ((Component)val).transform);
				_playerCamTf = (((Object)(object)val.m_Cam != (Object)null) ? ((Component)val.m_Cam).transform : null);
				CoopPlugin.Log.LogInfo((object)string.Format("Player body resolved: {0} at {1}, cam={2}", ((Object)_playerTf).name, _playerTf.position, ((Object)(object)_playerCamTf != (Object)null) ? ((Object)_playerCamTf).name : "none"));
			}
			return _playerTf;
		}

		public static void ForceExitHoldBox(Object heldBox)
		{
			CoopCore instance = Instance;
			InteractionPlayerController val = (((Object)(object)instance != (Object)null) ? instance._playerIpc : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				object? obj = FiHoldItemBox?.GetValue(val);
				object obj2 = FiHoldBox?.GetValue(val);
				object obj3 = FiHoldBoxCard?.GetValue(val);
				if (obj == heldBox || obj2 == heldBox || obj3 == heldBox)
				{
					val.OnExitHoldBoxMode();
					CoopPlugin.Log.LogInfo((object)"ForceExitHoldBox: released hold-box mode for a box being retired by reconcile");
				}
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("ForceExitHoldBox: " + ex.Message));
			}
		}

		private void RecoverStuckHoldBox()
		{
			if ((Object)(object)_playerIpc == (Object)null)
			{
				return;
			}
			try
			{
				object obj = FiIsHoldBoxMode?.GetValue(_playerIpc);
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					object? obj2 = FiHoldBox?.GetValue(_playerIpc);
					object? obj3 = ((obj2 is Object) ? obj2 : null);
					object? obj4 = FiHoldItemBox?.GetValue(_playerIpc);
					Object val = (Object)((obj4 is Object) ? obj4 : null);
					object? obj5 = FiHoldBoxCard?.GetValue(_playerIpc);
					Object val2 = (Object)((obj5 is Object) ? obj5 : null);
					if ((Object)obj3 == (Object)null && val == (Object)null && val2 == (Object)null)
					{
						_playerIpc.OnExitHoldBoxMode();
						CoopPlugin.Log.LogInfo((object)"RecoverStuckHoldBox: cleared a stranded hold-box lock (no live held box)");
					}
				}
			}
			catch
			{
			}
		}

		private byte ComputeHoldState()
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected I4, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Expected I4, but got Unknown
			_holdTypesBuf.Clear();
			_holdCardsBuf.Clear();
			if ((Object)(object)_playerIpc == (Object)null)
			{
				return 0;
			}
			try
			{
				if (IsAlive(FiHoldBox) || IsAlive(FiHoldItemBox) || IsAlive(FiHoldBoxShelf) || IsAlive(FiHoldBoxCard))
				{
					object? obj = FiHoldItemBox?.GetValue(_playerIpc);
					InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)((obj is InteractablePackagingBox_Item) ? obj : null);
					if (val != null && (Object)(object)val != (Object)null)
					{
						_holdTypesBuf.Add(val.m_IsBigBox ? 1 : 0);
						try
						{
							_holdTypesBuf.Add((int)val.m_ItemCompartment.GetItemType());
						}
						catch
						{
							_holdTypesBuf.Add(0);
						}
					}
					return 1;
				}
				if (FiHoldItemList?.GetValue(_playerIpc) is List<Item> { Count: >0 } list)
				{
					for (int i = 0; i < list.Count; i++)
					{
						if (_holdTypesBuf.Count >= 6)
						{
							break;
						}
						if ((Object)(object)list[i] != (Object)null)
						{
							_holdTypesBuf.Add((int)list[i].GetItemType());
						}
					}
					return 2;
				}
				if (FiHoldCard3dList?.GetValue(_playerIpc) is List<InteractableCard3d> { Count: >0 } list2)
				{
					for (int j = 0; j < list2.Count; j++)
					{
						if (_holdCardsBuf.Count >= 4)
						{
							break;
						}
						InteractableCard3d val2 = list2[j];
						if ((Object)(object)val2 != (Object)null && (Object)(object)val2.m_Card3dUI != (Object)null && (Object)(object)val2.m_Card3dUI.m_CardUI != (Object)null)
						{
							_holdCardsBuf.Add(val2.m_Card3dUI.m_CardUI.GetCardData());
						}
					}
					if (_holdCardsBuf.Count > 0)
					{
						return 3;
					}
				}
				object obj3 = FiViewAlbum?.GetValue(_playerIpc);
				bool flag = default(bool);
				int num;
				if (obj3 is bool)
				{
					flag = (bool)obj3;
					num = 1;
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					return 4;
				}
			}
			catch
			{
			}
			return 0;
		}

		private bool IsAlive(FieldInfo fi)
		{
			object? obj = fi?.GetValue(_playerIpc);
			return (Object)((obj is Object) ? obj : null) != (Object)null;
		}

		public void StartHosting()
		{
			ErrorLine = "";
			if (Role != CoopRole.None)
			{
				ErrorLine = "Already in a session.";
				return;
			}
			if (!InGameLevel())
			{
				ErrorLine = "Load your shop first, then host.";
				return;
			}
			CardShopCoop.Util.EnumMap.Clear();
			try
			{
				Transport transport = new Transport
				{
					KeepaliveFrame = Msg.Build(MsgType.Ping)
				};
				transport.StartHost(CoopPlugin.Port.Value);
				_net = transport;
				Role = CoopRole.Host;
				StatusLine = "Hosting - waiting for a player...";
				CoopPlugin.Log.LogInfo((object)$"Hosting on port {CoopPlugin.Port.Value}");
			}
			catch (Exception ex)
			{
				ErrorLine = "Could not host: " + ex.Message;
				_net?.Stop();
				_net = null;
				Role = CoopRole.None;
			}
		}

		public void Join(string ip)
		{
			ErrorLine = "";
			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;
			}
			if (ModParity.RestartRequiredForJoin)
			{
				ErrorLine = "the host's card database was installed on this PC - RESTART the game before joining";
				return;
			}
			CoopPlugin.LastJoinIP.Value = ip;
			Role = CoopRole.Client;
			GuestBorrowedWorld = true;
			StatusLine = "Connecting to " + ip + "...";
			Transport net = new Transport
			{
				KeepaliveFrame = Msg.Build(MsgType.Ping)
			};
			_net = net;
			int port = CoopPlugin.Port.Value;
			Thread thread = new Thread((ThreadStart)delegate
			{
				try
				{
					net.StartClient(ip, port);
					_mainThread.Enqueue(delegate
					{
						StatusLine = "Connected - requesting world...";
						SendHello();
					});
				}
				catch (Exception ex)
				{
					Exception ex2 = ex;
					Exception e = ex2;
					_mainThread.Enqueue(delegate
					{
						ErrorLine = "Could not connect: " + e.Message;
						Shutdown(null);
					});
				}
			});
			thread.IsBackground = true;
			thread.Name = "CoopConnect";
			thread.Start();
		}

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

		public void SendEmote()
		{
			if (_net != null && Role != CoopRole.None)
			{
				Broadcast(MsgType.Emote, delegate(BinaryWriter bw)
				{
					bw.Write((byte)1);
				});
			}
		}

		public void ForwardContribution(byte kind, float value)
		{
			if (Role == CoopRole.Client && _net != null)
			{
				Send(1, MsgType.EconContrib, delegate(BinaryWriter bw)
				{
					bw.Write(kind);
					bw.Write(value);
				});
			}
		}

		public void ForwardSprayHit(Vector3 pos, float range, int potency)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (Role == CoopRole.Client && _net != null)
			{
				Send(1, MsgType.SprayHit, delegate(BinaryWriter bw)
				{
					bw.Write(pos.x);
					bw.Write(pos.y);
					bw.Write(pos.z);
					bw.Write(range);
					bw.Write(potency);
				});
			}
		}

		public void ForwardCardDelta(CardData card, int amount, bool isAdd)
		{
			if (Role != CoopRole.None && _net != null && card != null && amount > 0)
			{
				_cardDeltaOutbox.Add(new PendingCard
				{
					IsAdd = isAdd,
					Amount = amount,
					Card = SnapshotCard(card)
				});
			}
		}

		public void SendCardDeltaTo(int connId, CardData card, int amount, bool isAdd)
		{
			if (Role == CoopRole.Host && _net != null && card != null && amount > 0)
			{
				Send(connId, MsgType.CardDelta, delegate(BinaryWriter bw)
				{
					bw.Write(isAdd);
					bw.Write(amount);
					Msg.WriteCard(bw, card);
				});
			}
		}

		public void ForwardGradedRemoval(CardData card)
		{
			if (Role != CoopRole.None && _net != null && card != null && card.cardGrade > 0)
			{
				Broadcast(MsgType.GradedRemove, delegate(BinaryWriter bw)
				{
					Msg.WriteCard(bw, card);
				});
			}
		}

		public void ForwardOrder(int restockIndex, int count)
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			if (Role != CoopRole.Client || _net == null)
			{
				return;
			}
			RestockData rd = null;
			try
			{
				rd = InventoryBase.GetRestockData(restockIndex);
			}
			catch
			{
			}
			if (rd == null)
			{
				CoopPlugin.Log.LogWarning((object)$"order: bad restock index {restockIndex}");
				return;
			}
			float lineCost = 0f;
			try
			{
				lineCost = CPlayerData.GetItemCost(rd.itemType) * (float)RestockManager.GetMaxItemCountInBox(rd.itemType, rd.isBigBox) * (float)count;
			}
			catch
			{
			}
			Send(1, MsgType.OrderRequest, delegate(BinaryWriter bw)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				Msg.WriteItemType(bw, rd.itemType);
				bw.Write(rd.isBigBox);
				bw.Write(rd.name ?? "");
				bw.Write(count);
				bw.Write(lineCost);
			});
		}

		public void ForwardLicense(int restockIndex)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected I4, but got Unknown
			if (Role == CoopRole.None || _net == null)
			{
				return;
			}
			RestockData val = null;
			try
			{
				val = InventoryBase.GetRestockData(restockIndex);
			}
			catch
			{
			}
			if (val == null)
			{
				return;
			}
			_lastLicenseBuyTime = Time.realtimeSinceStartupAsDouble;
			int itemType = (int)val.itemType;
			bool isBig = val.isBigBox;
			string rdName = val.name ?? "";
			if (Role == CoopRole.Host)
			{
				Broadcast(MsgType.LicenseUnlock, delegate(BinaryWriter bw)
				{
					Msg.WriteItemType(bw, (EItemType)itemType);
					bw.Write(isBig);
					bw.Write(rdName);
				});
			}
			else
			{
				Send(1, MsgType.LicenseUnlock, delegate(BinaryWriter bw)
				{
					Msg.WriteItemType(bw, (EItemType)itemType);
					bw.Write(isBig);
					bw.Write(rdName);
				});
			}
		}

		private static int EplExtraCount()
		{
			try
			{
				if (!_eplProbed)
				{
					_eplProbed = true;
					_eplAssetsProp = AccessTools.TypeByName("EnhancedPrefabLoader.Core.EplRuntimeData")?.GetProperty("Assets", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					object obj = _eplAssetsProp?.GetValue(null);
					_eplItemLibProp = obj?.GetType().GetProperty("ItemLibrary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					_eplRestockProp = ((obj == null) ? null : _eplItemLibProp?.GetValue(obj))?.GetType().GetProperty("RestockEntries", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					CoopPlugin.Log.LogInfo((object)((_eplRestockProp != null) ? "EPL catalog bridge active (virtual restock entries visible)" : "EPL catalog bridge inactive (EPL absent or its internals changed) - vanilla catalog only"));
				}
				object obj2 = _eplAssetsProp?.GetValue(null);
				object obj3 = ((obj2 == null) ? null : _eplItemLibProp?.GetValue(obj2));
				return ((obj3 == null) ? null : (_eplRestockProp?.GetValue(obj3) as ICollection))?.Count ?? 0;
			}
			catch
			{
				return 0;
			}
		}

		private static int CatalogCount()
		{
			int num = 0;
			try
			{
				num = Inv().m_StockItemData_SO.m_RestockDataList.Count;
			}
			catch
			{
			}
			return num + EplExtraCount();
		}

		private static RestockData CatalogAt(int i)
		{
			try
			{
				return InventoryBase.GetRestockData(i);
			}
			catch
			{
				return null;
			}
		}

		private static int ResolveRestockIndex(int itemType, bool isBig, string name, out bool sizeDiffers)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Invalid comparison between Unknown and I4
			sizeDiffers = false;
			try
			{
				int num = CatalogCount();
				for (int i = 0; i < num; i++)
				{
					RestockData val = CatalogAt(i);
					if (val != null && (int)val.itemType == itemType && val.isBigBox == isBig)
					{
						return i;
					}
				}
				if (!string.IsNullOrEmpty(name))
				{
					for (int j = 0; j < num; j++)
					{
						RestockData val2 = CatalogAt(j);
						if (val2 != null && val2.name == name && val2.isBigBox == isBig)
						{
							return j;
						}
					}
				}
				sizeDiffers = true;
				for (int k = 0; k < num; k++)
				{
					RestockData val3 = CatalogAt(k);
					if (val3 != null && (int)val3.itemType == itemType)
					{
						return k;
					}
				}
				if (!string.IsNullOrEmpty(name))
				{
					for (int l = 0; l < num; l++)
					{
						RestockData val4 = CatalogAt(l);
						if (val4 != null && val4.name == name)
						{
							return l;
						}
					}
				}
			}
			catch
			{
			}
			return -1;
		}

		private bool ApplyLicenseUnlock(int itemType, bool isBig, string name)
		{
			bool sizeDiffers;
			int num = ResolveRestockIndex(itemType, isBig, name, out sizeDiffers);
			if (num < 0)
			{
				CoopPlugin.Log.LogWarning((object)$"license unlock: no local product for type {itemType} big={isBig} '{name}'");
				return false;
			}
			if (CPlayerData.GetIsItemLicenseUnlocked(num))
			{
				return true;
			}
			GamePatches.ApplyingRemoteLicense = true;
			try
			{
				CPlayerData.SetUnlockItemLicense(num);
				try
				{
					AchievementManager.OnItemLicenseUnlocked((EItemType)itemType);
				}
				catch
				{
				}
				try
				{
					GameInstance.m_IsItemLicenseUnlocked = true;
				}
				catch
				{
				}
				try
				{
					if (itemType == 1)
					{
						TutorialManager.AddTaskValue((ETutorialTaskCondition)14, 1f);
					}
				}
				catch
				{
				}
			}
			finally
			{
				GamePatches.ApplyingRemoteLicense = false;
			}
			RefreshLicensePanels();
			CoopPlugin.Log.LogInfo((object)$"license unlocked by partner: {(object)(EItemType)itemType} big={isBig}");
			return true;
		}

		private static void RefreshLicensePanels()
		{
			try
			{
				RestockItemPanelUI[] array = Object.FindObjectsOfType<RestockItemPanelUI>();
				foreach (RestockItemPanelUI obj in array)
				{
					if (!(FiPanelIndex?.GetValue(obj) is int num) || num < 0)
					{
						continue;
					}
					bool flag = false;
					try
					{
						flag = CPlayerData.GetIsItemLicenseUnlocked(num);
					}
					catch
					{
					}
					if (flag)
					{
						object? obj3 = FiPanelLicGrp?.GetValue(obj);
						object? obj4 = ((obj3 is GameObject) ? obj3 : null);
						if (obj4 != null)
						{
							((GameObject)obj4).SetActive(false);
						}
						object? obj5 = FiPanelUIGrp?.GetValue(obj);
						object? obj6 = ((obj5 is GameObject) ? obj5 : null);
						if (obj6 != null)
						{
							((GameObject)obj6).SetActive(true);
						}
					}
				}
			}
			catch
			{
			}
		}

		private void SendCatalogDigest()
		{
			try
			{
				int num = CatalogCount();
				List<RestockData> entries = new List<RestockData>(num);
				for (int i = 0; i < num; i++)
				{
					RestockData val = CatalogAt(i);
					if (val != null && !string.IsNullOrEmpty(val.name))
					{
						entries.Add(val);
					}
				}
				Send(1, MsgType.CatalogDigest, delegate(BinaryWriter bw)
				{
					//IL_002f: Unknown result type (might be due to invalid IL or missing references)
					int num2 = Mathf.Min(entries.Count, 65535);
					bw.Write((ushort)num2);
					for (int j = 0; j < num2; j++)
					{
						Msg.WriteItemType(bw, entries[j].itemType);
						bw.Write(entries[j].isBigBox);
						bw.Write(Fnv(entries[j].name ?? ""));
					}
				});
			}
			catch (Exception ex)
			{
				CoopPlugin.Log.LogWarning((object)("catalog digest: " + ex.Message));
			}
		}

		private void CompareCatalogs(BinaryReader br, int connId)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected I4, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected I4, but got Unknown
			int num = br.ReadUInt16();
			HashSet<long> hashSet = new HashSet<long>();
			for (int i = 0; i < num; i++)
			{
				int type = (int)Msg.ReadItemType(br);
				bool big = br.ReadBoolean();
				int nameHash = br.ReadInt32();
				hashSet.Add(CatalogKey(type, big, nameHash));
			}
			if ((Object)(object)Inv() == (Object)null)
			{
				return;
			}
			int num2 = CatalogCount();
			int num3 = 0;
			int num4 = 0;
			List<string> list = new List<string>();
			for (int j = 0; j < num2; j++)
			{
				RestockData val = CatalogAt(j);
				if (val == null || string.IsNullOrEmpty(val.name))
				{
					continue;
				}
				if (hashSet.Contains(CatalogKey((int)val.itemType, val.isBigBox, Fnv(val.name))))
				{
					num4++;
					continue;
				}
				num3++;
				if (list.Count < 6)
				{
					list.Add(val.name);
				}
			}
			int num5 = hashSet.Count - num4;
			if (num3 == 0 && num5 == 0)
			{
				CoopPlugin.Log.LogInfo((object)$"catalog check: identical ({num4} products)");
				if (_catalogWarnedConns.Remove(connId))
				{
					RegisterLine = "catalogs match now - the earlier warning was mod startup timing, all good";
					RegisterLineTimer = 8f;
					Send(connId, MsgType.Toast, delegate(BinaryWriter bw)
					{
						bw.Write("catalogs match now - the earlier warning was mod startup timing, all good");
					});
				}
				return;
			}
			string value;
			string arg = (PeerNames.TryGetValue(connId, out value) ? value : "joiner");
			string summary = $"heads-up: product catalogs differ ({num3} only on host, {num5} only on {arg}) - mismatched items can't be ordered; match your content packs";
			CoopPlugin.Log.LogWarning((object)("catalog check: " + summary + ((list.Count > 0) ? (" | host-only e.g.: " + string.Join(" / ", list.ToArray())) : "")));
			_catalogWarnedConns.Add(connId);
			RegisterLine = summary;
			RegisterLineTimer = 10f;
			Send(connId, MsgType.Toast, delegate(BinaryWriter bw)
			{
				bw.Write(summary);
			});
		}

		private void LogCatalogCandidates(string name)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected I4, but got Unknown
			try
			{
				if (string.IsNullOrEmpty(name))
				{
					return;
				}
				string text = name.Split(new char[1] { ' ' })[0];
				int num = CatalogCount();
				List<string> list = new List<string>();
				for (int i = 0; i < num; i++)
				{
					if (list.Count >= 8)
					{
						break;
					}
					RestockData val = CatalogAt(i);
					if (val != null && val.name != null && val.name.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						list.Add($"{val.name} (type {(int)val.itemType}, big={val.isBigBox})");
					}
				}
				CoopPlugin.Log.LogInfo((object)((list.Count > 0) ? ("similar host entries: " + string.Join(" | ", list.ToArray())) : ("no host entries resembling '" + text + "'")));
			}
			catch
			{
			}
		}

		private static long CatalogKey(int type, bool big, int nameHash)
		{
			return (long)((ulong)((long)type << 33) ^ ((ulong)(uint)nameHash << 1)) ^ (long)(big ? 1 : 0);
		}

		private static int Fnv(string s)
		{
			uint num = 2166136261u;
			for (int i = 0; i < s.Length; i++)
			{
				num ^= s[i];
				num *= 16777619;
			}
			return (int)num;
		}

		public void ForwardFurniture(int objType, Vector3 pos, Quaternion rot)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (Role == CoopRole.Client && _net != null)
			{
				Send(1, MsgType.FurnitureOrder, delegate(BinaryWriter bw)
				{
					Msg.WriteObjType(bw, (EObjectType)objType);
					bw.Write(pos.x);
					bw.Write(pos.y);
					bw.Write(pos.z);
					bw.Write(rot.x);
					bw.Write(rot.y);
					bw.Write(rot.z);
					bw.Write(rot.w);
				});
			}
		}

		public void ForwardItemPrice(EItemType itemType, float price)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected I4, but got Unknown
			if (Role == CoopRole.Client && _net != null)
			{
				Send(1, MsgType.ItemPriceContrib, delegate(BinaryWriter bw)
				{
					//IL_0002: Unknown result type (might be due to invalid IL or missing references)
					Msg.WriteItemType(bw, itemType);
					bw.Write(price);
				});
				_myItemPriceEdits[(int)itemType] = new MyItemPrice
				{
					Value = price,
					At = Time.realtimeSinceStartupAsDouble
				};
				TrimMyItemPriceEdits();
			}
		}

		public void ForwardCardPrice(CardData card, float price)
		{
			if (Role == CoopRole.None || _net == null || card == null)
			{
				return;
			}
			Broadcast(MsgType.CardPriceSet, delegate(BinaryWriter bw)
			{
				Msg.WriteCard(bw, card);
				bw.Write(price);
			});
			if (Role == CoopRole.Client)
			{
				string text = CardPriceKey(card);
				if (text != null)
				{
					_myCardPrices[text] = new MyCardPrice
					{
						Card = SnapshotCard(card),
						Value = price,
						Acked = false,
						LastSend = Time.realtimeSinceStartupAsDouble,
						Attempts = 1
					};
					TrimMyCardPrices();
				}
			}
		}

		private void TrimMyCardPrices()
		{
			while (_myCardPrices.Count > 1024)
			{
				string text = null;
				double num = double.MaxValue;
				foreach (KeyValuePair<string, MyCardPrice> myCardPrice in _myCardPrices)
				{
					if (myCardPrice.Value.Acked && myCardPrice.Value.LastSend < num)
					{
						num = myCardPrice.Value.LastSend;
						text = myCardPrice.Key;
					}
				}
				if (text == null)
				{
					foreach (KeyValuePair<string, MyCardPrice> myCardPrice2 in _myCardPrices)
					{
						if (myCardPrice2.Value.LastSend < num)
						{
							num = myCardPrice2.Value.LastSend;
							text = myCardPrice2.Key;
						}
					}
				}
				if (text != null)
				{
					_myCardPrices.Remove(text);
					continue;
				}
				break;
			}
		}

		private void TrimMyItemPriceEdits()
		{
			while (_myItemPriceEdits.Count > 256)
			{
				int key = 0;
				bool flag = false;
				double num = double.MaxValue;
				foreach (KeyValuePair<int, MyItemPrice> myItemPriceEdit in _myItemPriceEdits)
				{
					if (!flag || myItemPriceEdit.Value.At < num)
					{
						num = myItemPriceEdit.Value.At;
						key = myItemPriceEdit.Key;
						flag = true;
					}
				}
				if (flag)
				{
					_myItemPriceEdits.Remove(key);
					continue;
				}
				break;
			}
		}

		private bool HeldLocalItemPrice(int itemType, float incoming)
		{
			if (_myItemPriceEdits.Count == 0)
			{
				return false;
			}
			if (!_myItemPriceEdits.TryGetValue(itemType, out var value))
			{
				return false;
			}
			if (Time.realtimeSinceStartupAsDouble - value.At >= 6.0)
			{
				_myItemPriceEdits.Remove(itemType);
				return false;
			}
			return Math.Abs(value.Value - incoming) > 0.0075f;
		}

		private void CardPriceRetryTick()
		{
			if (Role != CoopRole.Client || _net == null || _myCardPrices.Count == 0 || !InGameLevel())
			{
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			_cardPriceRetryKeys.Clear();
			foreach (KeyValuePair<string, MyCardPrice> myCardPrice in _myCardPrices)
			{
				if (!myCardPrice.Value.Acked && realtimeSinceStartupAsDouble - myCardPrice.Value.LastSend >= 3.0)
				{
					_cardPriceRetryKeys.Add(myCardPrice.Key);
				}
			}
			for (int i = 0; i < _cardPriceRetryKeys.Count; i++)
			{
				string text = _cardPriceRetryKeys[i];
				if (!_myCardPrices.TryGetValue(text, out var value))
				{
					continue;
				}
				if (value.Attempts >= 12)
				{
					_myCardPrices.Remove(text);
					CoopPlugin.Log.LogWarning((object)("card price for " + text + " never confirmed - keeping the local value until the host's next price sync"));
					continue;
				}
				CardData card = value.Card;
				float value2 = value.Value;
				Broadcast(MsgType.CardPriceSet, delegate(BinaryWriter bw)
				{
					Msg.WriteCard(bw, card);
					bw.Write(value2);
				});
				value.LastSend = realtimeSinceStartupAsDouble;
				value.Attempts++;
				_myCardPrices[text] = value;
			}
		}

		private void Shutdown(string reason)
		{
			if (_net != null)
			{
				try
				{
					Broadcast(MsgType.Bye, null);
				}
				catch
				{
				}
				_net.Stop();
				_net = null;
			}
			_avatars.Clear();
			PeerNames.Clear();
			_heldPurchases.Clear();
			_deliveringHeld = false;
			_chargeVerdicts.Clear();
			_lastDeclineToast.Clear();
			_enumSyncSentTo.Clear();
			_enumSyncSentToPeer.Clear();
			CardShopCoop.Util.EnumMap.Clear();
			ClearCardSetCache();
			_clientPriced.Clear();
			_incomingPriced.Clear();
			_cardDeltaOutbox.Clear();
			_batchRelayBuf.Clear();
			_flushingCardDeltas = false;
			_binderRefreshPending = false;
			_deltaLogBuf.Clear();
			_deltaAppliedThisFrame = 0;
			_pendingCardDeltas.Clear();
			_pendingCardPrices.Clear();
			_myCardPrices.Clear();
			_cardPriceRetryKeys.Clear();
			_cardPriceRetryTimer = 0f;
			_myItemPriceEdits.Clear();
			_priceWarnedKeys.Clear();
			_dispatchBuf.Clear();
			_dispatchSeen.Clear();
			_saveBuf = null;
			_saveExpected = -1;
			_pendingSave = null;
			_bundleBuf = null;
			_bundleExpected = -1;
			_worldRequested = false;
			_hasLastPos = false;
			_lastCoinSent = double.MinValue;
			_lastPriceHash = 0;
			_cardPriceBuf.Clear();
			_lastCardPriceHash = 0;
			_cardPriceHealBeat = 0f;
			_cardPriceHealTimer = -2.1f;
			_lastProgressSent = long.MinValue;
			_world.Reset();
			_npcs.Reset();
			_cardShelves.Reset();
			_objMoves.Reset();
			_boxes.Reset();
			_population.Reset();
			_registerMirror.Reset();
			ModulesReset();
			PromptLine = "";
			_lastShopNameSent = null;
			_steamLobby.Leave();
			IsSteamSession = false;
			HostPassword = "";
			_joinPassword = "";
			_selfId = -1;
			_relayIds.Clear();
			_pendingKicks.Clear();
			Application.runInBackground = false;
			Role = CoopRole.None;
			if (!InGameLevel())
			{
				GuestBorrowedWorld = false;
			}
			if (reason != null)
			{
				StatusLine = "Not connected (" + reason + ")";
				CoopPlugin.Log.LogInfo((object)("Session ended: " + reason));
			}
		}

		private void Send(int connId, MsgType type, Action<BinaryWriter> write)
		{
			FlushCardDeltaOutbox();
			_net?.Send(connId, Msg.Build(type, write));
		}

		private void Broadcast(MsgType type, Action<BinaryWriter> write)
		{
			FlushCardDeltaOutbox();
			_net?.Broadcast(Msg.Build(type, write));
		}

		private void BroadcastTransient(MsgType type, Action<BinaryWriter> write)
		{
			FlushCardDeltaOutbox();
			_net?.BroadcastTransient(Msg.Build(type, write));
		}

		private void ResolveHeldPurchases(int connId, bool deliver)
		{
			if (_heldPurchases.Count == 0)
			{
				return;
			}
			bool flag = false;
			int num = 0;
			while (num < _heldPurchases.Count)
			{
				if (_heldPurchases[num].Msg.ConnId != connId)
				{
					num++;
					continue;
				}
				InMsg msg = _heldPurchases[num].Msg;
				_heldPurchases.RemoveAt(num);
				if (deliver)
				{
					_deliveringHeld = true;
					try
					{
						Dispatch(msg);
					}
					finally
					{
						_deliveringHeld = false;
					}
					continue;
				}
				CoopPlugin.Log.LogInfo((object)$"purchase ({msg.Type}) from conn {connId} cancelled - its charge was declined (shared wallet short)");
				if (!flag)
				{
					flag = true;
					_lastDeclineToast[connId] = Time.realtimeSinceStartupAsDouble;
					Send(connId, MsgType.Toast, delegate(BinaryWriter bw)
					{
						bw.Write("not enough money - the purchase was cancelled");
					});
				}
			}
		}

		private PurchaseGate GateProduct(InMsg msg)
		{
			if (_deliveringHeld)
			{
				return PurchaseGate.Process;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			if (_chargeVerdicts.TryGetValue(msg.ConnId, out var value) && realtimeSinceStartupAsDouble - value.At < (value.Accepted ? 1.0 : 10.0))
			{
				if (value.Accepted)
				{
					return PurchaseGate.Process;
				}
				CoopPlugin.Log.LogInfo((object)$"purchase ({msg.Type}) from conn {msg.ConnId} dropped - its charge was declined (shared wallet short)");
				if (!_lastDeclineToast.TryGetValue(msg.ConnId, out var value2) || realtimeSinceStartupAsDouble - value2 >= 1.0)
				{
					_lastDeclineToast[msg.ConnId] = realtimeSinceStartupAsDouble;
					Send(msg.ConnId, MsgType.Toast, delegate(BinaryWriter bw)
					{
						bw.Write("not enough money - the purchase was cancelled");
					});
				}
				return PurchaseGate.Drop;
			}
			_heldPurchases.Add(new HeldPurchase
			{
				Msg = msg,
				At = realtimeSinceStartupAsDouble
			});
			return PurchaseGate.Hold;
		}

		private void PumpHeldPurchases()
		{
			if (_heldPurchases.Count == 0)
			{
				return;
			}
			double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble;
			while (_heldPurchases.Count > 0 && realtimeSinceStartupAsDouble - _heldPurchases[0].At > 1.5)
			{
				InMsg msg = _heldPurchases[0].Msg;
				_heldPurchases.RemoveAt(0);
				CoopPlugin.Log.LogInfo((object)$"held purchase ({msg.Type}) from conn {msg.ConnId} saw no charge within 1.5s - delivering (fail-open)");
				_deliveringHeld = true;
				try
				{
					Dispatch(msg);
				}
				finally
				{
					_deliveringHeld = false;
				}
			}
		}

		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;
		}

		private void Update()
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_08ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_08df: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			Action result;
			while (_mainThread.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception ex)
				{
					CoopPlugin.Log.LogError((object)ex);
				}
			}
			if (GuestBorrowedWorld && Role == CoopRole.None && !InGameLevel())
			{
				GuestBorrowedWorld = false;
			}
			if (Role == CoopRole.Client && InGameLevel())
			{
				RecoverStuckHoldBox();
			}
			AutoTick(Time.deltaTime);
			if (Input.GetKeyDown(CoopPlugin.UiToggleKey.Value))
			{
				_ui.Visible = !_ui.Visible;
			}
			if (Role != CoopRole.None && Input.GetKeyDown(CoopPlugin.EmoteKey.Value) && !CoopUI.TextFieldFocused)
			{
				SendEmote();
			}
			if (_serveThrottle > 0f)
			{
				_serveThrottle -= Time.deltaTime;
			}
			if (RegisterLineTimer > 0f)
			{
				RegisterLineTimer -= Time.deltaTime;
				if (RegisterLineTimer <= 0f)
				{
					RegisterLine = "";
				}
			}
			bool serveTap = Input.GetKeyDown(CoopPlugin.ServeKey.Value);
			if (serveTap && Role == CoopRole.Client && (CoopUI.TextFieldFocused || NativeTextInputFocused()))
			{
				CoopPlugin.Log.LogInfo((object)("serve key ignored (a text field has focus - " + (CoopUI.TextFieldFocused ? "co-op window" : "game input") + ")"));
			}
			if (Role == CoopRole.Client && _serveThrottle <= 0f && InGameLevel() && (serveTap || Input.GetKey(CoopPlugin.ServeKey.Value)) && !CoopUI.TextFieldFocused && !NativeTextInputFocused())
			{
				_serveThrottle = 0.25f;
				Guarded("serve", delegate
				{
					//IL_0020: Unknown result type (might be due to invalid IL or missing references)
					Transform val2 = ResolvePlayer();
					int idx = (((Object)(object)val2 != (Object)null) ? RegisterServe.FindNearestCounter(val2.position, CoopPlugin.ServeReach.Value, !serveTap) : (-1));
					if (idx < 0 || !_trades.HasOffer(idx))
					{
						if (idx < 0)
						{
							if (serveTap)
							{
								RegisterLine = "walk up to the register first";
								RegisterLineTimer = 2f;
							}
						}
						else
						{
							Send(1, MsgType.ServeRequest, delegate(BinaryWriter bw)
							{
								bw.Write(idx);
							});
						}
					}
				});
			}
			if (Role == CoopRole.Host && HostServeKeyEnabled && _serveThrottle <= 0f && InGameLevel() && (serveTap || Input.GetKey(CoopPlugin.ServeKey.Value)) && !CoopUI.TextFieldFocused && !NativeTextInputFocused())
			{
				_serveThrottle = 0.25f;
				Guarded("host-serve", delegate
				{
					//IL_0019: Unknown result type (might be due to invalid IL or missing references)
					Transform val2 = ResolvePlayer();
					int num10 = (((Object)(object)val2 != (Object)null) ? RegisterServe.FindNearestCounter(val2.position, CoopPlugin.ServeReach.Value, !serveTap) : (-1));
					if (num10 < 0 || !_trades.HasOffer(num10))
					{
						if (num10 < 0)
						{
							if (serveTap)
							{
								RegisterLine = "walk up to the register first";
								RegisterLineTimer = 2f;
							}
						}
						else
						{
							byte[] scanEcho;
							string text4 = RegisterServe.Serve(num10, CoopPlugin.PlayerName.Value, out scanEcho);
							if (!string.IsNullOrEmpty(text4))
							{
								RegisterLine = text4;
								RegisterLi