using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using CC; using CMF; using CardShopCoop.Api; using CardShopCoop.Attributes; using CardShopCoop.Modules.Catalog; using CardShopCoop.Modules.DebugTools; using CardShopCoop.Modules.Decoration; using CardShopCoop.Modules.Economy; using CardShopCoop.Modules.Grading; using CardShopCoop.Modules.Hud; using CardShopCoop.Modules.Npc; using CardShopCoop.Modules.PlayTable; using CardShopCoop.Modules.Prediction; using CardShopCoop.Modules.Presence; using CardShopCoop.Modules.Pricing; using CardShopCoop.Modules.Register; using CardShopCoop.Modules.Report; using CardShopCoop.Modules.SaveTransfer; using CardShopCoop.Modules.SessionInput; using CardShopCoop.Modules.World; using CardShopCoop.Net; using CardShopCoop.Net.Connection; using CardShopCoop.Net.Datagrams; using CardShopCoop.Net.Kcp; using CardShopCoop.Net.Messages; using CardShopCoop.Net.Protocol; using CardShopCoop.Runtime; using CardShopCoop.UI; using CardShopCoop.Util; using HarmonyLib; using I2.Loc; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Steamworks; using TMPro; using Unity.Profiling; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; using kcp2k; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("ConnectionState")] [assembly: InternalsVisibleTo("ProtocolTransport")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Devin 'Meepen' Korb")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("CardShopCoopCommunity for TCG Card Shop Simulator.")] [assembly: AssemblyFileVersion("2.0.6.0")] [assembly: AssemblyInformationalVersion("2.0.6+7377084dcc6c7363bdd27c3bdca151eaad5dfb45")] [assembly: AssemblyProduct("CardShopCoopCommunity")] [assembly: AssemblyTitle("CardShopCoop")] [assembly: AssemblyVersion("2.0.6.0")] namespace kcp2k { internal struct AckItem { internal uint serialNumber; internal uint timestamp; } public class Kcp { public const int RTO_NDL = 30; public const int RTO_MIN = 100; public const int RTO_DEF = 200; public const int RTO_MAX = 60000; public const int CMD_PUSH = 81; public const int CMD_ACK = 82; public const int CMD_WASK = 83; public const int CMD_WINS = 84; public const int ASK_SEND = 1; public const int ASK_TELL = 2; public const int WND_SND = 32; public const int WND_RCV = 128; public const int MTU_DEF = 1200; public const int MTU_MIN = 50; public const int ACK_FAST = 3; public const int INTERVAL = 100; public const int OVERHEAD = 24; public const int FRG_MAX = 255; public const int DEADLINK = 20; public const int THRESH_INIT = 2; public const int THRESH_MIN = 2; public const int PROBE_INIT = 7000; public const int PROBE_LIMIT = 120000; public const int FASTACK_LIMIT = 5; internal int state; private readonly uint conv; internal uint mtu; internal uint mss; internal uint snd_una; internal uint snd_nxt; internal uint rcv_nxt; internal uint ssthresh; internal int rx_rttval; internal int rx_srtt; internal int rx_rto; internal int rx_minrto; internal uint snd_wnd; internal uint rcv_wnd; internal uint rmt_wnd; internal uint cwnd; internal uint probe; internal uint interval; internal uint ts_flush; internal uint xmit; internal uint nodelay; internal bool updated; internal uint ts_probe; internal uint probe_wait; internal uint dead_link; internal uint incr; internal uint current; internal int fastresend; internal int fastlimit; internal bool nocwnd; internal readonly Queue snd_queue = new Queue(16); internal readonly Queue rcv_queue = new Queue(16); internal readonly List snd_buf = new List(16); internal readonly List rcv_buf = new List(16); internal readonly List acklist = new List(16); internal byte[] buffer; private readonly Action output; private readonly Pool SegmentPool = new Pool(() => new Segment(), delegate(Segment segment) { segment.Reset(); }, 32); public int WaitSnd => snd_buf.Count + snd_queue.Count; public Kcp(uint conv, Action output) { this.conv = conv; this.output = output; snd_wnd = 32u; rcv_wnd = 128u; rmt_wnd = 128u; mtu = 1200u; mss = mtu - 24; rx_rto = 200; rx_minrto = 100; interval = 100u; ts_flush = 100u; ssthresh = 2u; fastlimit = 5; dead_link = 20u; buffer = new byte[(mtu + 24) * 3]; } private Segment SegmentNew() { return SegmentPool.Take(); } private void SegmentDelete(Segment seg) { SegmentPool.Return(seg); } internal uint WndUnused() { if (rcv_queue.Count < rcv_wnd) { return rcv_wnd - (uint)rcv_queue.Count; } return 0u; } public int Receive(byte[] buffer, int len) { if (len < 0) { throw new NotSupportedException("Receive ispeek for negative len is not supported!"); } if (rcv_queue.Count == 0) { return -1; } if (len < 0) { len = -len; } int num = PeekSize(); if (num < 0) { return -2; } if (num > len) { return -3; } bool flag = rcv_queue.Count >= rcv_wnd; int num2 = 0; len = 0; while (rcv_queue.Count > 0) { Segment segment = rcv_queue.Dequeue(); Buffer.BlockCopy(segment.data.GetBuffer(), 0, buffer, num2, (int)segment.data.Position); num2 += (int)segment.data.Position; len += (int)segment.data.Position; uint frg = segment.frg; SegmentDelete(segment); if (frg == 0) { break; } } int num3 = 0; foreach (Segment item in rcv_buf) { if (item.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) { num3++; rcv_queue.Enqueue(item); rcv_nxt++; continue; } break; } rcv_buf.RemoveRange(0, num3); if (rcv_queue.Count < rcv_wnd && flag) { probe |= 2u; } return len; } public int PeekSize() { int num = 0; if (rcv_queue.Count == 0) { return -1; } Segment segment = rcv_queue.Peek(); if (segment.frg == 0) { return (int)segment.data.Position; } if (rcv_queue.Count < segment.frg + 1) { return -1; } foreach (Segment item in rcv_queue) { num += (int)item.data.Position; if (item.frg == 0) { break; } } return num; } public int Send(byte[] buffer, int offset, int len) { if (len < 0) { return -1; } int num = (int)((len <= mss) ? 1 : ((len + mss - 1) / mss)); if (num > 255) { throw new Exception($"Send len={len} requires {num} fragments, but kcp can only handle up to {255} fragments."); } if (num >= rcv_wnd) { return -2; } if (num == 0) { num = 1; } for (int i = 0; i < num; i++) { int num2 = ((len > (int)mss) ? ((int)mss) : len); Segment segment = SegmentNew(); if (len > 0) { segment.data.Write(buffer, offset, num2); } segment.frg = (uint)(num - i - 1); snd_queue.Enqueue(segment); offset += num2; len -= num2; } return 0; } private void UpdateAck(int rtt) { if (rx_srtt == 0) { rx_srtt = rtt; rx_rttval = rtt / 2; } else { int num = rtt - rx_srtt; if (num < 0) { num = -num; } rx_rttval = (3 * rx_rttval + num) / 4; rx_srtt = (7 * rx_srtt + rtt) / 8; if (rx_srtt < 1) { rx_srtt = 1; } } int value = rx_srtt + Math.Max((int)interval, 4 * rx_rttval); rx_rto = Utils.Clamp(value, rx_minrto, 60000); } internal void ShrinkBuf() { if (snd_buf.Count > 0) { Segment segment = snd_buf[0]; snd_una = segment.sn; } else { snd_una = snd_nxt; } } internal void ParseAck(uint sn) { if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0) { return; } for (int i = 0; i < snd_buf.Count; i++) { Segment segment = snd_buf[i]; if (sn == segment.sn) { snd_buf.RemoveAt(i); SegmentDelete(segment); break; } if (Utils.TimeDiff(sn, segment.sn) < 0) { break; } } } internal void ParseUna(uint una) { int num = 0; foreach (Segment item in snd_buf) { if (Utils.TimeDiff(una, item.sn) > 0) { num++; SegmentDelete(item); continue; } break; } snd_buf.RemoveRange(0, num); } internal void ParseFastack(uint sn, uint ts) { if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0) { return; } foreach (Segment item in snd_buf) { if (Utils.TimeDiff(sn, item.sn) < 0) { break; } if (sn != item.sn) { item.fastack++; } } } private void AckPush(uint sn, uint ts) { acklist.Add(new AckItem { serialNumber = sn, timestamp = ts }); } private void ParseData(Segment newseg) { uint sn = newseg.sn; if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 || Utils.TimeDiff(sn, rcv_nxt) < 0) { SegmentDelete(newseg); return; } InsertSegmentInReceiveBuffer(newseg); MoveReceiveBufferReadySegmentsToQueue(); } internal void InsertSegmentInReceiveBuffer(Segment newseg) { bool flag = false; int num; for (num = rcv_buf.Count - 1; num >= 0; num--) { Segment segment = rcv_buf[num]; if (segment.sn == newseg.sn) { flag = true; break; } if (Utils.TimeDiff(newseg.sn, segment.sn) > 0) { break; } } if (!flag) { rcv_buf.Insert(num + 1, newseg); } else { SegmentDelete(newseg); } } private void MoveReceiveBufferReadySegmentsToQueue() { int num = 0; foreach (Segment item in rcv_buf) { if (item.sn == rcv_nxt && rcv_queue.Count < rcv_wnd) { num++; rcv_queue.Enqueue(item); rcv_nxt++; continue; } break; } rcv_buf.RemoveRange(0, num); } public int Input(byte[] data, int offset, int size) { uint earlier = snd_una; uint num = 0u; uint ts = 0u; int num2 = 0; if (data == null || size < 24) { return -1; } while (size >= 24) { offset += Utils.Decode32U(data, offset, out var value); if (value != conv) { return -1; } offset += Utils.Decode8u(data, offset, out var value2); offset += Utils.Decode8u(data, offset, out var value3); offset += Utils.Decode16U(data, offset, out var value4); offset += Utils.Decode32U(data, offset, out var value5); offset += Utils.Decode32U(data, offset, out var value6); offset += Utils.Decode32U(data, offset, out var value7); offset += Utils.Decode32U(data, offset, out var value8); size -= 24; if (size < value8 || (int)value8 < 0) { return -2; } if (value2 != 81 && value2 != 82 && value2 != 83 && value2 != 84) { return -3; } if (value2 == 81 && (value3 >= byte.MaxValue || (uint)(value3 + 1) >= rcv_wnd)) { return -4; } rmt_wnd = value4; ParseUna(value7); ShrinkBuf(); switch (value2) { case 82: if (Utils.TimeDiff(current, value5) >= 0) { UpdateAck(Utils.TimeDiff(current, value5)); } ParseAck(value6); ShrinkBuf(); if (num2 == 0) { num2 = 1; num = value6; ts = value5; } else if (Utils.TimeDiff(value6, num) > 0) { num = value6; ts = value5; } break; case 81: if (Utils.TimeDiff(value6, rcv_nxt + rcv_wnd) >= 0) { break; } AckPush(value6, value5); if (Utils.TimeDiff(value6, rcv_nxt) >= 0) { Segment segment = SegmentNew(); segment.conv = value; segment.cmd = value2; segment.frg = value3; segment.wnd = value4; segment.ts = value5; segment.sn = value6; segment.una = value7; if (value8 != 0) { segment.data.Write(data, offset, (int)value8); } ParseData(segment); } break; case 83: probe |= 2u; break; default: return -3; case 84: break; } offset += (int)value8; size -= (int)value8; } if (num2 != 0) { ParseFastack(num, ts); } if (Utils.TimeDiff(snd_una, earlier) > 0 && cwnd < rmt_wnd) { if (cwnd < ssthresh) { cwnd++; incr += mss; } else { if (incr < mss) { incr = mss; } incr += mss * mss / incr + mss / 16; if ((cwnd + 1) * mss <= incr) { cwnd = (incr + mss - 1) / ((mss == 0) ? 1 : mss); } } if (cwnd > rmt_wnd) { cwnd = rmt_wnd; incr = rmt_wnd * mss; } } return 0; } private void MakeSpace(ref int size, int space) { if (size + space > mtu) { output(buffer, size); size = 0; } } private void FlushBuffer(int size) { if (size > 0) { output(buffer, size); } } public void Flush() { int size = 0; bool flag = false; if (!updated) { return; } Segment segment = SegmentNew(); segment.conv = conv; segment.cmd = 82u; segment.wnd = WndUnused(); segment.una = rcv_nxt; foreach (AckItem item in acklist) { MakeSpace(ref size, 24); segment.sn = item.serialNumber; segment.ts = item.timestamp; size += segment.Encode(buffer, size); } acklist.Clear(); if (rmt_wnd == 0) { if (probe_wait == 0) { probe_wait = 7000u; ts_probe = current + probe_wait; } else if (Utils.TimeDiff(current, ts_probe) >= 0) { if (probe_wait < 7000) { probe_wait = 7000u; } probe_wait += probe_wait / 2; if (probe_wait > 120000) { probe_wait = 120000u; } ts_probe = current + probe_wait; probe |= 1u; } } else { ts_probe = 0u; probe_wait = 0u; } if ((probe & 1) != 0) { segment.cmd = 83u; MakeSpace(ref size, 24); size += segment.Encode(buffer, size); } if ((probe & 2) != 0) { segment.cmd = 84u; MakeSpace(ref size, 24); size += segment.Encode(buffer, size); } probe = 0u; uint num = Math.Min(snd_wnd, rmt_wnd); if (!nocwnd) { num = Math.Min(cwnd, num); } while (Utils.TimeDiff(snd_nxt, snd_una + num) < 0 && snd_queue.Count != 0) { Segment segment2 = snd_queue.Dequeue(); segment2.conv = conv; segment2.cmd = 81u; segment2.wnd = segment.wnd; segment2.ts = current; segment2.sn = snd_nxt; snd_nxt++; segment2.una = rcv_nxt; segment2.resendts = current; segment2.rto = rx_rto; segment2.fastack = 0u; segment2.xmit = 0u; snd_buf.Add(segment2); } uint num2 = ((fastresend > 0) ? ((uint)fastresend) : uint.MaxValue); uint num3 = ((nodelay == 0) ? ((uint)rx_rto >> 3) : 0u); int num4 = 0; foreach (Segment item2 in snd_buf) { bool flag2 = false; if (item2.xmit == 0) { flag2 = true; item2.xmit++; item2.rto = rx_rto; item2.resendts = (uint)((int)current + item2.rto) + num3; } else if (Utils.TimeDiff(current, item2.resendts) >= 0) { flag2 = true; item2.xmit++; xmit++; if (nodelay == 0) { item2.rto += Math.Max(item2.rto, rx_rto); } else { int num5 = ((nodelay < 2) ? item2.rto : rx_rto); item2.rto += num5 / 2; } item2.resendts = current + (uint)item2.rto; flag = true; } else if (item2.fastack >= num2 && (item2.xmit <= fastlimit || fastlimit <= 0)) { flag2 = true; item2.xmit++; item2.fastack = 0u; item2.resendts = current + (uint)item2.rto; num4++; } if (flag2) { item2.ts = current; item2.wnd = segment.wnd; item2.una = rcv_nxt; int space = 24 + (int)item2.data.Position; MakeSpace(ref size, space); size += item2.Encode(buffer, size); if (item2.data.Position > 0) { Buffer.BlockCopy(item2.data.GetBuffer(), 0, buffer, size, (int)item2.data.Position); size += (int)item2.data.Position; } if (item2.xmit >= dead_link) { state = -1; } } } SegmentDelete(segment); FlushBuffer(size); if (num4 > 0) { uint num6 = snd_nxt - snd_una; ssthresh = num6 / 2; if (ssthresh < 2) { ssthresh = 2u; } cwnd = ssthresh + num2; incr = cwnd * mss; } if (flag) { ssthresh = num / 2; if (ssthresh < 2) { ssthresh = 2u; } cwnd = 1u; incr = mss; } if (cwnd < 1) { cwnd = 1u; incr = mss; } } public void Update(uint currentTimeMilliSeconds) { current = currentTimeMilliSeconds; if (!updated) { updated = true; ts_flush = current; } int num = Utils.TimeDiff(current, ts_flush); if (num >= 10000 || num < -10000) { ts_flush = current; num = 0; } if (num >= 0) { ts_flush += interval; if (current >= ts_flush) { ts_flush = current + interval; } Flush(); } } public uint Check(uint current_) { uint num = ts_flush; int num2 = int.MaxValue; if (!updated) { return current_; } if (Utils.TimeDiff(current_, num) >= 10000 || Utils.TimeDiff(current_, num) < -10000) { num = current_; } if (Utils.TimeDiff(current_, num) >= 0) { return current_; } int num3 = Utils.TimeDiff(num, current_); foreach (Segment item in snd_buf) { int num4 = Utils.TimeDiff(item.resendts, current_); if (num4 <= 0) { return current_; } if (num4 < num2) { num2 = num4; } } uint num5 = (uint)((num2 < num3) ? num2 : num3); if (num5 >= interval) { num5 = interval; } return current_ + num5; } public void SetMtu(uint mtu) { if (mtu < 50 || mtu < 24) { throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD"); } buffer = new byte[(mtu + 24) * 3]; this.mtu = mtu; mss = mtu - 24; } public void SetInterval(uint interval) { if (interval > 5000) { interval = 5000u; } else if (interval < 10) { interval = 10u; } this.interval = interval; } public void SetNoDelay(uint nodelay, uint interval = 100u, int resend = 0, bool nocwnd = false) { this.nodelay = nodelay; if (nodelay != 0) { rx_minrto = 30; } else { rx_minrto = 100; } if (interval >= 0) { if (interval > 5000) { interval = 5000u; } else if (interval < 10) { interval = 10u; } this.interval = interval; } if (resend >= 0) { fastresend = resend; } this.nocwnd = nocwnd; } public void SetWindowSize(uint sendWindow, uint receiveWindow) { if (sendWindow != 0) { snd_wnd = sendWindow; } if (receiveWindow != 0) { rcv_wnd = Math.Max(receiveWindow, 128u); } } } public class Pool { private readonly Stack objects = new Stack(); private readonly Func objectGenerator; private readonly Action objectResetter; public int Count => objects.Count; public Pool(Func objectGenerator, Action objectResetter, int initialCapacity) { this.objectGenerator = objectGenerator; this.objectResetter = objectResetter; for (int i = 0; i < initialCapacity; i++) { objects.Push(objectGenerator()); } } public T Take() { if (objects.Count <= 0) { return objectGenerator(); } return objects.Pop(); } public void Return(T item) { objectResetter(item); objects.Push(item); } public void Clear() { objects.Clear(); } } internal class Segment { internal uint conv; internal uint cmd; internal uint frg; internal uint wnd; internal uint ts; internal uint sn; internal uint una; internal uint resendts; internal int rto; internal uint fastack; internal uint xmit; internal MemoryStream data = new MemoryStream(1200); internal int Encode(byte[] ptr, int offset) { int num = offset; offset += Utils.Encode32U(ptr, offset, conv); offset += Utils.Encode8u(ptr, offset, (byte)cmd); offset += Utils.Encode8u(ptr, offset, (byte)frg); offset += Utils.Encode16U(ptr, offset, (ushort)wnd); offset += Utils.Encode32U(ptr, offset, ts); offset += Utils.Encode32U(ptr, offset, sn); offset += Utils.Encode32U(ptr, offset, una); offset += Utils.Encode32U(ptr, offset, (uint)data.Position); return offset - num; } internal void Reset() { conv = 0u; cmd = 0u; frg = 0u; wnd = 0u; ts = 0u; sn = 0u; una = 0u; rto = 0; xmit = 0u; resendts = 0u; fastack = 0u; data.SetLength(0L); } } public static class Utils { public static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } public static int Encode8u(byte[] p, int offset, byte value) { p[offset] = value; return 1; } public static int Decode8u(byte[] p, int offset, out byte value) { value = p[offset]; return 1; } public static int Encode16U(byte[] p, int offset, ushort value) { p[offset] = (byte)value; p[1 + offset] = (byte)(value >> 8); return 2; } public static int Decode16U(byte[] p, int offset, out ushort value) { ushort num = 0; num |= p[offset]; num |= (ushort)(p[1 + offset] << 8); value = num; return 2; } public static int Encode32U(byte[] p, int offset, uint value) { p[offset] = (byte)value; p[1 + offset] = (byte)(value >> 8); p[2 + offset] = (byte)(value >> 16); p[3 + offset] = (byte)(value >> 24); return 4; } public static int Decode32U(byte[] p, int offset, out uint value) { uint num = 0u; num |= p[offset]; num |= (uint)(p[1 + offset] << 8); num |= (uint)(p[2 + offset] << 16); num |= (uint)(p[3 + offset] << 24); value = num; return 4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TimeDiff(uint later, uint earlier) { return (int)(later - earlier); } } public enum ErrorCode : byte { DnsResolve, Timeout, Congestion, InvalidReceive, InvalidSend, ConnectionClosed, Unexpected } public enum KcpChannel : byte { Reliable = 1, Unreliable } [Serializable] public class KcpConfig { public bool DualMode; public int RecvBufferSize; public int SendBufferSize; public int Mtu; public bool NoDelay; public uint Interval; public int FastResend; public bool CongestionWindow; public uint SendWindowSize; public uint ReceiveWindowSize; public int Timeout; public uint MaxRetransmits; public KcpConfig(bool DualMode = true, int RecvBufferSize = 7340032, int SendBufferSize = 7340032, int Mtu = 1200, bool NoDelay = true, uint Interval = 10u, int FastResend = 0, bool CongestionWindow = false, uint SendWindowSize = 32u, uint ReceiveWindowSize = 128u, int Timeout = 600000, uint MaxRetransmits = 20u) { this.DualMode = DualMode; this.RecvBufferSize = RecvBufferSize; this.SendBufferSize = SendBufferSize; this.Mtu = Mtu; this.NoDelay = NoDelay; this.Interval = Interval; this.FastResend = FastResend; this.CongestionWindow = CongestionWindow; this.SendWindowSize = SendWindowSize; this.ReceiveWindowSize = ReceiveWindowSize; this.Timeout = Timeout; this.MaxRetransmits = MaxRetransmits; } } public enum KcpHeaderReliable : byte { Hello = 1, Ping, Data } public enum KcpHeaderUnreliable : byte { Data = 4, Disconnect } public static class KcpHeader { public static bool ParseReliable(byte value, out KcpHeaderReliable header) { if (Enum.IsDefined(typeof(KcpHeaderReliable), value)) { header = (KcpHeaderReliable)value; return true; } header = KcpHeaderReliable.Ping; return false; } public static bool ParseUnreliable(byte value, out KcpHeaderUnreliable header) { if (Enum.IsDefined(typeof(KcpHeaderUnreliable), value)) { header = (KcpHeaderUnreliable)value; return true; } header = KcpHeaderUnreliable.Disconnect; return false; } } public abstract class KcpPeer { internal Kcp kcp; internal uint cookie; protected KcpState state; public const int DEFAULT_TIMEOUT = 600000; public int timeout; private uint lastReceiveTime; private uint currentTime; private readonly byte[] kcpMessageBuffer; private readonly byte[] kcpSendBuffer; private readonly byte[] rawSendBuffer; public const int PING_INTERVAL = 1000; private uint lastPingTime; internal const int QueueDisconnectThreshold = 10000; public const int CHANNEL_HEADER_SIZE = 1; public const int COOKIE_HEADER_SIZE = 4; public const int METADATA_SIZE = 5; public readonly int unreliableMax; public readonly int reliableMax; private bool disconnecting; private bool remoteDisconnectRequested; public int SendQueueCount => kcp.snd_queue.Count; public int ReceiveQueueCount => kcp.rcv_queue.Count; public int SendBufferCount => kcp.snd_buf.Count; public int ReceiveBufferCount => kcp.rcv_buf.Count; public uint MaxSendRate => kcp.snd_wnd * kcp.mtu * 1000 / kcp.interval; public uint MaxReceiveRate => kcp.rcv_wnd * kcp.mtu * 1000 / kcp.interval; private static int ReliableMaxMessageSize_Unconstrained(int mtu, uint rcv_wnd) { return (mtu - 24 - 5) * (int)(rcv_wnd - 1) - 1; } public static int ReliableMaxMessageSize(int mtu, uint rcv_wnd) { return ReliableMaxMessageSize_Unconstrained(mtu, Math.Min(rcv_wnd, 255u)); } public static int UnreliableMaxMessageSize(int mtu) { return mtu - 5 - 1; } protected KcpPeer(KcpConfig config, uint cookie) : this(config, cookie, 0u) { } protected KcpPeer(KcpConfig config, uint cookie, uint initialTime) { Reset(config, initialTime); this.cookie = cookie; Log.Info($"[KCP] {GetType()}: created with cookie={cookie}"); rawSendBuffer = new byte[config.Mtu]; unreliableMax = UnreliableMaxMessageSize(config.Mtu); reliableMax = ReliableMaxMessageSize(config.Mtu, config.ReceiveWindowSize); kcpMessageBuffer = new byte[1 + reliableMax]; kcpSendBuffer = new byte[1 + reliableMax]; } protected void Reset(KcpConfig config) { Reset(config, 0u); } protected void Reset(KcpConfig config, uint initialTime) { cookie = 0u; state = KcpState.Connected; lastReceiveTime = initialTime; lastPingTime = initialTime; currentTime = initialTime; kcp = new Kcp(0u, RawSendReliable); kcp.SetNoDelay(config.NoDelay ? 1u : 0u, config.Interval, config.FastResend, !config.CongestionWindow); kcp.SetWindowSize(config.SendWindowSize, config.ReceiveWindowSize); kcp.SetMtu((uint)(config.Mtu - 5)); kcp.dead_link = config.MaxRetransmits; timeout = config.Timeout; } protected void SetInitialTime(uint time) { lastReceiveTime = time; lastPingTime = time; currentTime = time; } protected abstract void OnAuthenticated(); protected abstract void OnData(ArraySegment message, KcpChannel channel); protected abstract void OnDisconnected(); protected virtual bool OnReliableHelloReceived() { return true; } protected abstract void OnError(ErrorCode error, string message); protected abstract void RawSend(ArraySegment data); private void HandleTimeout(uint time) { if (Utils.TimeDiff(time, lastReceiveTime) >= timeout) { OnError(ErrorCode.Timeout, $"{GetType()}: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); Disconnect(); } } private void HandleDeadLink() { if (kcp.state == -1) { OnError(ErrorCode.Timeout, $"{GetType()}: dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting."); Disconnect(); } } private void HandlePing(uint time) { if (Utils.TimeDiff(time, lastPingTime) >= 1000) { SendPing(); lastPingTime = time; } } private void HandleChoked() { int num = kcp.rcv_queue.Count + kcp.snd_queue.Count + kcp.rcv_buf.Count + kcp.snd_buf.Count; if (num >= 10000) { OnError(ErrorCode.Congestion, $"{GetType()}: disconnecting connection because it can't process data fast enough.\n" + $"Queue total {num}>{10000}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" + "* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n* Or perhaps the network is simply too slow on our end, or on the other end."); kcp.snd_queue.Clear(); Disconnect(); } } private bool ReceiveNextReliable(uint time, out KcpHeaderReliable header, out ArraySegment message) { message = default(ArraySegment); header = KcpHeaderReliable.Ping; int num = kcp.PeekSize(); if (num <= 0) { return false; } if (num > kcpMessageBuffer.Length) { OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {num} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); Disconnect(); return false; } int num2 = kcp.Receive(kcpMessageBuffer, num); if (num2 < 0) { OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={num2}. closing connection."); Disconnect(); return false; } byte b = kcpMessageBuffer[0]; if (!KcpHeader.ParseReliable(b, out header)) { OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed to parse header: {b} is not defined in {typeof(KcpHeaderReliable)}."); Disconnect(); return false; } message = new ArraySegment(kcpMessageBuffer, 1, num - 1); lastReceiveTime = time; return true; } private void TickIncoming_Connected(uint time) { HandleTimeout(time); HandleDeadLink(); HandlePing(time); HandleChoked(); if (ReceiveNextReliable(time, out var header, out var _)) { switch (header) { case KcpHeaderReliable.Hello: Log.Info($"[KCP] {GetType()}: received hello with cookie={cookie}"); if (!OnReliableHelloReceived()) { Disconnect(); break; } state = KcpState.Authenticated; OnAuthenticated(); break; case KcpHeaderReliable.Data: OnError(ErrorCode.InvalidReceive, $"[KCP] {GetType()}: received invalid header {header} while Connected. Disconnecting the connection."); Disconnect(); break; } } if (remoteDisconnectRequested) { remoteDisconnectRequested = false; Disconnect(); } } private void TickIncoming_Authenticated(uint time) { HandleTimeout(time); HandleDeadLink(); HandlePing(time); HandleChoked(); KcpHeaderReliable header; ArraySegment message; while (ReceiveNextReliable(time, out header, out message)) { switch (header) { case KcpHeaderReliable.Hello: Log.Warning($"{GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection."); Disconnect(); break; case KcpHeaderReliable.Data: if (message.Count > 0) { OnData(message, KcpChannel.Reliable); break; } OnError(ErrorCode.InvalidReceive, $"{GetType()}: received empty Data message while Authenticated. Disconnecting the connection."); Disconnect(); break; } } if (remoteDisconnectRequested) { remoteDisconnectRequested = false; Disconnect(); } } public virtual void TickIncoming(uint time) { currentTime = time; try { switch (state) { case KcpState.Connected: TickIncoming_Connected(time); break; case KcpState.Authenticated: TickIncoming_Authenticated(time); break; case KcpState.Disconnected: break; } } catch (SocketException arg) { OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg}. This is fine."); Disconnect(); } catch (ObjectDisposedException arg2) { OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg2}. This is fine."); Disconnect(); } catch (Exception arg3) { OnError(ErrorCode.Unexpected, $"{GetType()}: unexpected Exception: {arg3}"); Disconnect(); } } public virtual void TickOutgoing(uint time) { currentTime = time; try { switch (state) { case KcpState.Connected: case KcpState.Authenticated: kcp.Update(time); break; } } catch (SocketException arg) { OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg}. This is fine."); Disconnect(); } catch (ObjectDisposedException arg2) { OnError(ErrorCode.ConnectionClosed, $"{GetType()}: Disconnecting because {arg2}. This is fine."); Disconnect(); } catch (Exception arg3) { OnError(ErrorCode.Unexpected, $"{GetType()}: unexpected exception: {arg3}"); Disconnect(); } } public virtual void Tick(uint time) { TickIncoming(time); TickOutgoing(time); } protected bool OnRawInputReliable(ArraySegment message) { int num = kcp.Input(message.Array, message.Offset, message.Count); if (num != 0) { Log.Warning($"[KCP] {GetType()}: Input failed with error={num} for buffer with length={message.Count - 1}"); } return num == 0; } protected void OnRawInputUnreliable(ArraySegment message) { if (message.Count < 1) { return; } byte b = message.Array[message.Offset]; if (!KcpHeader.ParseUnreliable(b, out var header)) { OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed to parse header: {b} is not defined in {typeof(KcpHeaderUnreliable)}."); Disconnect(); return; } message = new ArraySegment(message.Array, message.Offset + 1, message.Count - 1); switch (header) { case KcpHeaderUnreliable.Data: if (state == KcpState.Authenticated) { OnData(message, KcpChannel.Unreliable); lastReceiveTime = currentTime; } break; case KcpHeaderUnreliable.Disconnect: Log.Info($"[KCP] {GetType()}: received disconnect message"); remoteDisconnectRequested = true; break; } } private void RawSendReliable(byte[] data, int length) { rawSendBuffer[0] = 1; Utils.Encode32U(rawSendBuffer, 1, cookie); Buffer.BlockCopy(data, 0, rawSendBuffer, 5, length); ArraySegment data2 = new ArraySegment(rawSendBuffer, 0, length + 1 + 4); RawSend(data2); } private bool SendReliable(KcpHeaderReliable header, ArraySegment content) { if (1 + content.Count > kcpSendBuffer.Length) { OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={reliableMax}"); return false; } kcpSendBuffer[0] = (byte)header; if (content.Count > 0) { Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count); } int num = kcp.Send(kcpSendBuffer, 0, 1 + content.Count); if (num < 0) { OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={num} for content with length={content.Count}"); return false; } return true; } private bool SendUnreliable(KcpHeaderUnreliable header, ArraySegment content) { if (content.Count > unreliableMax) { Log.Error($"[KCP] {GetType()}: Failed to send unreliable message of size {content.Count} because it's larger than UnreliableMaxMessageSize={unreliableMax}"); return false; } rawSendBuffer[0] = 2; Utils.Encode32U(rawSendBuffer, 1, cookie); rawSendBuffer[5] = (byte)header; if (content.Count > 0) { Buffer.BlockCopy(content.Array, content.Offset, rawSendBuffer, 6, content.Count); } ArraySegment data = new ArraySegment(rawSendBuffer, 0, content.Count + 1 + 4 + 1); RawSend(data); return true; } public void SendHello() { Log.Info($"[KCP] {GetType()}: sending handshake to other end with cookie={cookie}"); SendReliable(KcpHeaderReliable.Hello, default(ArraySegment)); } public bool SendData(ArraySegment data, KcpChannel channel) { if (data.Count == 0) { OnError(ErrorCode.InvalidSend, $"{GetType()}: tried sending empty message. This should never happen. Disconnecting."); Disconnect(); return false; } switch (channel) { case KcpChannel.Reliable: return SendReliable(KcpHeaderReliable.Data, data); case KcpChannel.Unreliable: return SendUnreliable(KcpHeaderUnreliable.Data, data); default: OnError(ErrorCode.InvalidSend, $"{GetType()}: unknown channel {(byte)channel}."); return false; } } private void SendPing() { SendReliable(KcpHeaderReliable.Ping, default(ArraySegment)); } private void SendDisconnect() { for (int i = 0; i < 5; i++) { SendUnreliable(KcpHeaderUnreliable.Disconnect, default(ArraySegment)); } } public virtual void Disconnect() { if (state == KcpState.Disconnected || disconnecting) { return; } disconnecting = true; try { SendDisconnect(); } catch (SocketException) { } catch (ObjectDisposedException) { } finally { Log.Info($"[KCP] {GetType()}: Disconnected."); state = KcpState.Disconnected; disconnecting = false; OnDisconnected(); } } } public enum KcpState { Connected, Authenticated, Disconnected } public static class Log { public static Action Info = Console.WriteLine; public static Action Warning = Console.WriteLine; public static Action Error = Console.Error.WriteLine; } } namespace CardShopCoop { public enum CoopRole { None, Host, Client } public enum InviteState { Off, Resolving, Ready, LanOnly } public class CoopCore : MonoBehaviour { public string StatusLine = "Not connected"; public string ErrorLine = ""; public string HostPassword = ""; public ulong LastFailedLobby; public InviteState InviteStatus; public string InviteCodeText; public string InviteReason; public int PortForwardState; private IConnectionProvider _connectionProvider; private ICoopTransport _providerTransportSource; private CoopRuntimeContext _liveRuntimeContext; private ISteamBridge _steam; private CoopRuntimeBehaviour _runtime; private PersistentRuntimeBehaviour _persistentRuntime; private CoopUI _ui; private readonly MessageRouter _messageRouter = new MessageRouter(); private readonly MainThreadDispatcher _dispatcher = new MainThreadDispatcher(64, 4096, 3); private bool _shutdownCompleted; private bool _externalModsDiscovered; private bool _sessionInGame; private bool _worldReady; private int _localConnectionId = -1; private int _sessionGeneration = 1; private int _inviteGeneration; private ulong _autoJoinSteamLobby; private int _autoHostSlot = -1; private string _autoJoinIp; private int _autoPhase; private float _autoTimer; private string _joinPassword = ""; private static int _unityThreadId; private readonly List _dispatchBuffer = new List(64); private readonly HashSet _dispatchRejectedConnections = new HashSet(); private bool _dispatchBacklogWarned; private const int DispatchBudget = 256; private const int DispatchBacklogCap = 2048; private const int MaxIncomingAdmissionPerFrame = 512; private const int MaxDisconnectResultsPerFrame = 64; public static CoopCore Instance { get; private set; } public static CoopRole Role { get; private set; } public static bool WindowBlocksInput => SessionInputRuntime.WindowBlocksInput; public static bool IsTearingDown { get; private set; } public static bool InSessionWorld { get { if ((Object)(object)Instance != (Object)null) { return Instance._sessionInGame; } return false; } } public string EffectivePlayerName => PresenceApi.LocalPlayerName; public bool UsingSteamPersona => !string.Equals(EffectivePlayerName, CoopPlugin.PlayerName.Value, StringComparison.Ordinal); public ICoopTransport Net { get; private set; } public ISteamBridge Steam => _steam; public bool IsSteamSession { get; private set; } public string LastDisconnectCode { get; private set; } = ""; public string LastDisconnectReason { get; private set; } = ""; public bool LastDisconnectRetryable { get; private set; } internal static int SessionGeneration { get { CoopCore instance = Instance; if (!((Object)(object)instance == (Object)null)) { return Volatile.Read(in instance._sessionGeneration); } return -1; } } internal static int LocalConnectionId { get { if (!((Object)(object)Instance == (Object)null)) { return Instance._localConnectionId; } return -1; } } internal CoopRuntimeContext LiveContext => _liveRuntimeContext; private IConnectionProvider ConnectionProvider { get { return _connectionProvider; } set { if (_connectionProvider == value) { OnProviderTransportChanged(); return; } if (_connectionProvider != null) { _connectionProvider.TransportChanged -= OnProviderTransportChanged; _connectionProvider.PeerConnected -= OnProviderPeerConnected; _connectionProvider.Failed -= OnProviderFailed; } _connectionProvider = value; if (_connectionProvider != null) { _connectionProvider.TransportChanged += OnProviderTransportChanged; _connectionProvider.PeerConnected += OnProviderPeerConnected; _connectionProvider.Connected += OnProviderConnected; _connectionProvider.Failed += OnProviderFailed; } OnProviderTransportChanged(); } } internal static bool IsSessionGeneration(int generation) { CoopCore instance = Instance; if ((Object)(object)instance != (Object)null && generation >= 0) { return Volatile.Read(in instance._sessionGeneration) == generation; } return false; } private void Awake() { Instance = this; _unityThreadId = Thread.CurrentThread.ManagedThreadId; _messageRouter.RegisterCoreAttributedHandlers(this); _dispatcher.Start(); PresenceApi.ConfigureIdentity(delegate { string text = ((_steam == null) ? "" : _steam.LocalPersonaName); return (!string.IsNullOrWhiteSpace(text)) ? text : CoopPlugin.PlayerName.Value; }); SaveTransferApi.ConfigureCore(() => Role, () => SessionGeneration, IsSessionGeneration, (Action action) => _dispatcher.TryEnqueue("save-transfer", action), null, delegate { Send(1, (INetMessage)(object)new FullyJoinedMessage()); }, delegate(string reason) { string text = reason ?? "world transfer failed"; if (!_dispatcher.TryEnqueue("save-transfer-failure", delegate { ErrorLine = text; SetStatus(text); })) { CoopPlugin.Log.LogError((object)("coop: could not publish save-transfer failure on the main thread: " + text)); } }, delegate(PeerConnection connection, string reason) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown Net?.GracefulDisconnect(connection, new DisconnectInfo("world transfer failed: " + reason, false, "transfer_failed", true, (ConnectionState)2)); }); _ui = new CoopUI(); _persistentRuntime = ((Component)this).gameObject.AddComponent(); _persistentRuntime.InitializePersistent(new CoopRuntimeContext(_messageRouter, InGameLevel, Broadcast, Send, ConnectionIds, PresenceApi.PeerName, delegate(string text, float seconds) { SetStatus(text); }, () => SaveTransferApi.PreloadHold)); SceneManager.sceneLoaded += OnSceneLoaded; ReadCommandLine(); _steam = SteamBridge.TryCreate(); if (_steam == null && PlatformProbe.SteamworksPresent) { CoopPlugin.Log.LogInfo((object)"Steamworks assembly present but not functional; LAN and direct IP only."); } if (_steam != null) { _steam.Init(); _steam.OnError = delegate(string error) { ErrorLine = error ?? "Steam error"; CoopPlugin.Log.LogWarning((object)ErrorLine); }; _steam.OnLobbyLive = delegate(ulong lobby) { SetStatus("Hosting via Steam - click 'Invite friend'"); CoopPlugin.Log.LogInfo((object)("steam: lobby live " + lobby)); }; _steam.OnInviteAccepted = delegate(ulong lobby) { JoinSteamLobby(lobby); }; } RegisterEventListeners(); CatalogApi.EnumLendState(); } private void RegisterEventListeners() { CEventManager.AddListener((EventDelegate)OnLocalPackOpened); CEventManager.AddListener((EventDelegate)OnGameDataFinishLoaded); CEventManager instance = CSingleton.Instance; if ((Object)(object)instance != (Object)null) { CoopPlugin.Log.LogInfo((object)("[events] co-op listeners attached to CEventManager '" + ((Object)((Component)instance).gameObject).name + "'")); } } private void ReadCommandLine() { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < commandLineArgs.Length; i++) { string text = commandLineArgs[i] ?? ""; ulong result2; if (text.StartsWith("-coopautohost=", StringComparison.Ordinal) && int.TryParse(text.Substring(14), out var result)) { _autoHostSlot = result; } else if (text.StartsWith("-coopautojoin=", StringComparison.Ordinal)) { _autoJoinIp = text.Substring(14); } else if (text == "+connect_lobby" && i + 1 < commandLineArgs.Length && ulong.TryParse(commandLineArgs[i + 1], out result2)) { _autoJoinSteamLobby = result2; } } } public void JoinSteamLobby(ulong lobby, string password = "") { ErrorLine = ""; ClearDisconnectMetadata(); if (Role != CoopRole.None) { ErrorLine = "Already in a session."; return; } if (InGameLevel()) { ErrorLine = "Go to the main menu first, then accept the invite again."; return; } if (_steam == null) { ErrorLine = "This build has no Steam support - use LAN or direct IP."; return; } if (!_steam.SteamAvailable()) { ErrorLine = "Steam isn't running."; return; } BeginSession(CoopRole.Client, steam: true); IsSteamSession = true; _joinPassword = password ?? ""; LastFailedLobby = lobby; try { SetConnectionProvider(new SteamConnectionProvider(_steam)); ConnectionProvider.Join(lobby.ToString(), 0, _joinPassword); SetStatus("Joining Steam lobby..."); } catch (Exception ex) { AbortSessionStart("Could not join: " + ex.Message); } } public void StartHostingSteam(bool isPublic, string lobbyName, string password, int maxPlayers) { ErrorLine = ""; ClearDisconnectMetadata(); if (maxPlayers < 2 || maxPlayers > 250) { ErrorLine = $"Player count must be between {2} and {250}."; return; } if (Role != CoopRole.None) { ErrorLine = "Already in a session."; return; } if (!TryAdmitHostStart(out var error)) { ErrorLine = error; return; } if (_steam == null) { ErrorLine = "This build has no Steam support - use LAN instead."; return; } if (!_steam.SteamAvailable()) { ErrorLine = "Steam isn't running - use LAN instead."; return; } BeginSession(CoopRole.Host, steam: true); HostPassword = password ?? ""; try { SetConnectionProvider(new SteamConnectionProvider(_steam)); ConnectionProvider.Host(0, isPublic, lobbyName, HostPassword, maxPlayers); SetStatus("Creating Steam lobby..."); } catch (Exception ex) { AbortSessionStart("Could not host: " + ex.Message); } } public void OpenSteamInvite() { _steam?.OpenInviteDialog(); } public void StartHosting() { ErrorLine = ""; ClearDisconnectMetadata(); if (Role != CoopRole.None) { ErrorLine = "Already in a session."; return; } if (!TryAdmitHostStart(out var error)) { ErrorLine = error; return; } BeginSession(CoopRole.Host, steam: false); try { SetConnectionProvider(new LanConnectionProvider()); ConnectionProvider.Host(CoopPlugin.Port.Value); SetStatus("Hosting - waiting for a player..."); if (CoopPlugin.AutoLanPassword.Value && string.IsNullOrEmpty(HostPassword)) { HostPassword = GenerateSessionPassword(); } BeginInviteResolve(); } catch (Exception ex) { AbortSessionStart("Could not host: " + ex.Message); } } public void JoinHostedGame(string ip) { JoinHostedGame(ip, CoopPlugin.Port.Value, ""); } public void JoinHostedGame(string ip, int joinPort, string password) { ErrorLine = ""; ClearDisconnectMetadata(); if (Role != CoopRole.None) { ErrorLine = "Already in a session."; return; } if (InGameLevel()) { ErrorLine = "Join from the main menu (Title screen)."; return; } ip = (ip ?? "").Trim(); if (ip.Length == 0) { ErrorLine = "Enter the host's IP address."; return; } CoopPlugin.LastJoinIP.Value = ip; BeginSession(CoopRole.Client, steam: false); _joinPassword = password ?? ""; try { SetConnectionProvider(new LanConnectionProvider()); int port = ((joinPort > 0 && joinPort <= 65535) ? joinPort : CoopPlugin.Port.Value); ConnectionProvider.Join(ip, port); SetStatus("Connecting to " + ip + "..."); } catch (Exception ex) { AbortSessionStart("Could not connect: " + ex.Message); } } public void Disconnect() { ClearDisconnectMetadata(); Shutdown("disconnected"); } public void SendEmote() { PresenceApi.SendEmote(); } private void BeginSession(CoopRole role, bool steam) { _shutdownCompleted = false; _dispatcher.Start(); Interlocked.Increment(ref _sessionGeneration); Role = role; IsSteamSession = steam; _localConnectionId = ((role != CoopRole.Host) ? (-1) : 0); if (role == CoopRole.Client) { SaveTransferApi.SetGuestBorrowedWorld(borrowed: true); } } private void OnProviderConnected() { } private void OnProviderFailed(string reason) { AbortSessionStart(reason ?? "connection failed"); } private void OnProviderTransportChanged() { ICoopTransport coopTransport = _connectionProvider?.Transport; if (coopTransport != _providerTransportSource) { _providerTransportSource = coopTransport; Net = coopTransport; if (Net != null && Role != CoopRole.None && (Object)(object)_runtime == (Object)null) { ActivateLiveModuleHooks(); } } } private void OnProviderPeerConnected(PeerConnection connection) { if (connection != null && Net != null && Role != CoopRole.Host && Role == CoopRole.Client) { if (Thread.CurrentThread.ManagedThreadId == _unityThreadId) { SendHello(); } else if (!TryEnqueueMainThread(SendHello)) { CoopPlugin.Log.LogError((object)"coop: could not queue the application Hello on the Unity thread"); } } } private void SetConnectionProvider(IConnectionProvider provider) { ConnectionProvider = provider ?? throw new ArgumentNullException("provider"); } private void ActivateLiveModuleHooks() { if ((Object)(object)_runtime != (Object)null) { return; } CoopRuntimeContext context = (_liveRuntimeContext = new CoopRuntimeContext(_messageRouter, InGameLevel, Broadcast, Send, ConnectionIds, PresenceApi.PeerName, delegate(string text, float seconds) { SetStatus(text); }, () => SaveTransferApi.PreloadHold, delegate(int connectionId, DisconnectInfo info) { PeerConnection val = ConnectionFor(connectionId); if (val != null) { Net?.GracefulDisconnect(val, info); } }, Relay)); _runtime = ((Role == CoopRole.Host) ? ((CoopRuntimeBehaviour)((Component)this).gameObject.AddComponent()) : ((CoopRuntimeBehaviour)((Component)this).gameObject.AddComponent())); try { _runtime.Initialize(context); } catch { _liveRuntimeContext = null; Object.Destroy((Object)(object)_runtime); _runtime = null; throw; } } private void DeactivateLiveModuleHooks() { if ((Object)(object)_runtime == (Object)null) { return; } CoopRuntimeBehaviour runtime = _runtime; _runtime = null; try { runtime.Shutdown(); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("Co-op runtime shutdown failed: " + ex)); } } finally { Object.Destroy((Object)(object)runtime); } } private void AbortSessionStart(string reason) { ErrorLine = reason ?? "session could not start"; CoopPlugin.Log.LogError((object)("coop: " + ErrorLine)); Shutdown(ErrorLine); } private void SendHello() { CatalogHelloData catalogHelloData = CatalogHandshake.BuildHelloData(); List frozenMessageCatalog = GetFrozenMessageCatalog(); HelloMessage message = new HelloMessage { WireVersion = Msg.WireVersion, Version = "2.0.6", PlayerName = EffectivePlayerName, SteamId = ((_steam == null) ? 0 : _steam.LocalSteamId), Password = _joinPassword, PluginHash = ModParity.PluginHash(), EnumHash = catalogHelloData.EnumHash, CardsHash = catalogHelloData.CardsHash, PluginList = ModParity.PluginList(), CardsList = catalogHelloData.CardsList, EnumIdentity = catalogHelloData.EnumIdentity, GameVersion = (Application.version ?? ""), UnityVersion = (Application.unityVersion ?? ""), MessageCatalog = frozenMessageCatalog, MessageReliability = GetFrozenMessageReliability(frozenMessageCatalog) }; PeerConnection val = ConnectionFor(1); if (val == null || !(Net is ICoopHandshakeTransport coopHandshakeTransport)) { throw new InvalidOperationException("could not send the application Hello: no live handshake transport"); } coopHandshakeTransport.SendHandshake(val, (INetMessage)(object)message); } private static List GetFrozenMessageCatalog() { IReadOnlyList orderedWireNames = MessageRegistry.CurrentSnapshot.OrderedWireNames; List list = new List(orderedWireNames.Count); for (int i = 0; i < orderedWireNames.Count; i++) { string text = orderedWireNames[i]; if (text == null) { throw new InvalidOperationException("the frozen message registry contains a DTO without a Type.FullName"); } list.Add(text); } return list; } private static byte[] GetFrozenMessageReliability(IReadOnlyList messageCatalog) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 if (messageCatalog == null) { throw new ArgumentNullException("messageCatalog"); } byte[] array = new byte[(messageCatalog.Count + 7) / 8]; ProtocolSnapshot currentSnapshot = MessageRegistry.CurrentSnapshot; for (int i = 0; i < messageCatalog.Count; i++) { if (!currentSnapshot.TryGet(messageCatalog[i], out var descriptor)) { throw new InvalidOperationException("the frozen message registry is missing " + messageCatalog[i]); } if ((int)descriptor.Reliability == 1) { array[i / 8] |= (byte)(1 << (i & 7)); } } return array; } private static bool TryValidateMessageReliability(byte[] peerBits, byte[] expectedBits, int messageCount, out string reason) { if (peerBits == null) { reason = "message reliability bitset is missing"; return false; } int num = (messageCount + 7) / 8; if (peerBits.Length != num) { reason = "message reliability bitset length differs (peer " + peerBits.Length + ", local " + num + ")"; return false; } if (expectedBits == null || expectedBits.Length != num) { throw new InvalidOperationException("local message reliability bitset is invalid"); } if (num > 0 && (messageCount & 7) != 0) { byte b = (byte)(255 << (messageCount & 7)); if ((peerBits[num - 1] & b) != 0) { reason = "message reliability bitset has non-zero unused tail bits"; return false; } if ((expectedBits[num - 1] & b) != 0) { throw new InvalidOperationException("local message reliability bitset has non-zero unused tail bits"); } } for (int i = 0; i < num; i++) { if (peerBits[i] != expectedBits[i]) { reason = "message reliability differs at byte " + i; return false; } } reason = null; return true; } private static bool TryValidateMessageCatalog(IList peerCatalog, IReadOnlyList expectedCatalog, out string reason) { if (peerCatalog == null) { reason = "message catalog is missing"; return false; } if (peerCatalog.Count != expectedCatalog.Count) { reason = "message catalog count differs (peer " + peerCatalog.Count + ", local " + expectedCatalog.Count + ")"; return false; } HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < peerCatalog.Count; i++) { string text = peerCatalog[i]; if (text == null) { reason = "message catalog contains a null name at index " + i; return false; } if (!hashSet.Add(text)) { reason = "message catalog contains a duplicate name at index " + i; return false; } } for (int j = 0; j < expectedCatalog.Count; j++) { if (!string.Equals(peerCatalog[j], expectedCatalog[j], StringComparison.Ordinal)) { reason = "message catalog differs at index " + j + " (peer '" + BoundedMessageName(peerCatalog[j]) + "', local '" + BoundedMessageName(expectedCatalog[j]) + "')"; return false; } } reason = null; return true; } private static string BoundedMessageName(string name) { if (name == null) { return ""; } if (name.Length > 96) { return name.Substring(0, 96) + "..."; } return name; } private void ActivateMessageIdsBeforeTransfer(ICoopTransport transport, PeerConnection connection, int generation) { if (!IsSessionGeneration(generation)) { throw new InvalidOperationException("session ended before message-id activation"); } transport.ActivateMessageIds(connection); } [MessageHandler(typeof(HelloMessage))] private void HandleHello(MessageContext context, HelloMessage hello) { //IL_0427: Unknown result type (might be due to invalid IL or missing references) //IL_042c: Unknown result type (might be due to invalid IL or missing references) PeerConnection connection = context?.Connection; if (Role != CoopRole.Host || connection == null || hello == null || !ExpectControlState(connection, "Hello", (ConnectionState)1)) { return; } string text = hello.PlayerName ?? ""; if (hello.WireVersion != Msg.WireVersion) { PeerConnection connection2 = connection; int wireVersion = Msg.WireVersion; RejectConn(connection2, "wire protocol mismatch - host uses protocol " + wireVersion); return; } if (!string.Equals(hello.Version, "2.0.6", StringComparison.Ordinal)) { RejectConn(connection, "version mismatch - host runs 2.0.6"); return; } if (!string.Equals(hello.Password ?? "", HostPassword, StringComparison.Ordinal)) { RejectConn(connection, "wrong password", retryable: true); return; } if ((hello.GameVersion ?? "") != (Application.version ?? "") || (hello.UnityVersion ?? "") != (Application.unityVersion ?? "")) { if (!CoopPlugin.AllowCrossBuildJoin.Value) { RejectConn(connection, "your GAME build doesn't match the host's"); return; } CoopPlugin.Log.LogWarning((object)("AllowCrossBuildJoin is ON - accepting " + text + " across game builds; save compatibility is not guaranteed.")); } if (!string.Equals(hello.PluginHash, ModParity.PluginHash(), StringComparison.Ordinal)) { RejectConn(connection, ModParity.DescribeMismatch(hello.PluginList, 1024)); return; } List messageCatalog = GetFrozenMessageCatalog(); if (!TryValidateMessageCatalog(hello.MessageCatalog, messageCatalog, out var reason)) { CoopPlugin.Log.LogWarning((object)("message catalog handshake rejected for connection " + connection.Id + ": " + reason)); RejectConn(connection, "message catalog mismatch: " + reason); return; } byte[] messageReliability = GetFrozenMessageReliability(messageCatalog); if (!TryValidateMessageReliability(hello.MessageReliability, messageReliability, messageCatalog.Count, out var reason2)) { CoopPlugin.Log.LogWarning((object)("message reliability handshake rejected for connection " + connection.Id + ": " + reason2)); RejectConn(connection, "message reliability mismatch: " + reason2); return; } if (!CatalogHandshake.IsIdentityUsable(hello.EnumIdentity)) { CoopPlugin.Log.LogWarning((object)("catalog handshake rejected connection " + connection.Id + ": invalid or empty client enum identity.")); RejectConn(connection, "invalid custom-content catalog from client"); return; } CatalogEnumValidation catalogEnumValidation = CatalogHandshake.ValidateEnums(text, hello.EnumHash, hello.EnumIdentity); if (!catalogEnumValidation.HashesAreConsistent) { string text2 = catalogEnumValidation.HashFailureReason ?? "the catalog hash did not match the decoded catalog"; CoopPlugin.Log.LogWarning((object)("catalog handshake rejected connection " + connection.Id + ": " + text2)); RejectConn(connection, "invalid custom-card catalog: " + text2); return; } if (catalogEnumValidation.Conflicts.Count > 0) { CoopPlugin.Log.LogWarning((object)("catalog handshake rejected connection " + connection.Id + ": custom-content name set differs (" + catalogEnumValidation.Conflicts.Count + " entries).")); RejectConn(connection, CatalogHandshake.DescribeEnumKeysMismatch(catalogEnumValidation.Conflicts)); return; } if (!CatalogHandshake.TryValidateCards(hello.CardsHash, hello.CardsList, out var reason3)) { RejectConn(connection, reason3); return; } string text3 = ResolvePeerName(hello.SteamId, text); PresenceApi.SetPeerName(connection.Id, text3); if (!connection.TryTransition((ConnectionState)2)) { CoopPlugin.Log.LogWarning((object)("Ignoring Hello from connection in invalid phase " + ((object)connection.State/*cast due to .constrained prefix*/).ToString())); return; } try { _runtime?.ClientJoined(connection); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Feature ClientJoined callbacks failed: " + ex)); } SetStatus("Hosting - " + text3 + " joined!"); CoopPlugin.Log.LogInfo((object)(text3 + " passed admission; starting world transfer")); CatalogWelcomeBlobs blobs = CatalogHandshake.BuildWelcomeBlobs(); ICoopTransport transport = Net; int transferGeneration = SessionGeneration; if (transport == null) { RejectConn(connection, "transport disappeared before world transfer"); } else if (!SaveTransferApi.TryAuthorizeAndSend(connection, delegate(SaveTransferOffer offer) { ((transport as ICoopHandshakeTransport) ?? throw new InvalidOperationException("could not send Welcome: no live handshake transport")).SendHandshake(connection, (INetMessage)(object)new WelcomeMessage { WireVersion = Msg.WireVersion, Version = "2.0.6", HostName = EffectivePlayerName, SteamId = ((_steam == null) ? 0 : _steam.LocalSteamId), SaveLength = offer.SaveLength, BundleLength = offer.BundleLength, SidecarsComplete = offer.SidecarsComplete, SidecarWarning = offer.SidecarWarning, SelfId = connection.Id, HostEnumIdentity = blobs.EnumIdentity, HostCardsBlob = blobs.CardsBlob, MessageCatalog = new List(messageCatalog), MessageReliability = (byte[])messageReliability.Clone() }); return true; }, delegate { ActivateMessageIdsBeforeTransfer(transport, connection, transferGeneration); })) { RejectConn(connection, "could not start the world transfer"); } } [MessageHandler(typeof(WelcomeMessage))] private void HandleWelcome(MessageContext context, WelcomeMessage welcome) { PeerConnection val = context?.Connection; if (Role != CoopRole.Client || val == null || welcome == null || !ExpectControlState(val, "Welcome", (ConnectionState)1)) { return; } if (welcome.WireVersion != Msg.WireVersion || !string.Equals(welcome.Version, "2.0.6", StringComparison.Ordinal)) { Shutdown("plugin or wire version mismatch"); return; } List frozenMessageCatalog = GetFrozenMessageCatalog(); if (!TryValidateMessageCatalog(welcome.MessageCatalog, frozenMessageCatalog, out var reason)) { CoopPlugin.Log.LogWarning((object)("message catalog handshake rejected from host: " + reason)); Shutdown("message catalog mismatch: " + reason); return; } byte[] frozenMessageReliability = GetFrozenMessageReliability(frozenMessageCatalog); if (!TryValidateMessageReliability(welcome.MessageReliability, frozenMessageReliability, frozenMessageCatalog.Count, out var reason2)) { CoopPlugin.Log.LogWarning((object)("message reliability handshake rejected from host: " + reason2)); Shutdown("message reliability mismatch: " + reason2); return; } Net.ActivateMessageIds(val); bool flag = CatalogHandshake.IsIdentityUsable(welcome.HostEnumIdentity); CatalogBlobData catalogBlobData = CatalogHandshake.ReadBlob(welcome.HostCardsBlob); if (!flag || !catalogBlobData.IsValid) { string text = (flag ? ("host CardsBlob: " + catalogBlobData.FailureReason) : "host enum identity is invalid or empty"); CoopPlugin.Log.LogError((object)("catalog handshake received an invalid host blob: " + text)); Shutdown("host sent an invalid custom-card catalog"); return; } CatalogIdMap.Build(welcome.HostEnumIdentity); _localConnectionId = welcome.SelfId; PresenceApi.SetPeerName(1, ResolvePeerName(welcome.SteamId, welcome.HostName)); if (!SaveTransferApi.TryBeginClientTransfer(welcome.SaveLength, welcome.BundleLength, welcome.SidecarsComplete, welcome.SidecarWarning)) { Shutdown("invalid world transfer authorization"); return; } if (!val.TryTransition((ConnectionState)2)) { Shutdown("invalid handshake phase"); return; } try { _runtime?.ClientJoined(val); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Feature ClientJoined callbacks failed: " + ex)); } SetStatus("Downloading " + (PresenceApi.PeerName(1) ?? "host") + "'s shop..."); } [MessageHandler(typeof(ByeMessage))] private void HandleBye(MessageContext context, ByeMessage message) { PeerConnection connection = context?.Connection; ConnectionState[] array = new ConnectionState[3]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); if (ExpectControlState(connection, "Bye", (ConnectionState[])(object)array)) { string text = (string.IsNullOrEmpty(message?.Reason) ? "the host ended the session" : message.Reason); if (Role == CoopRole.Client) { ErrorLine = text; Shutdown("rejected: " + text); } else { Net?.Kick(connection); } } } private void Send(int connectionId, INetMessage message) { PeerConnection val = ConnectionFor(connectionId); if (val == null || Net == null) { CoopPlugin.Log.LogError((object)("application send failed: no live connection " + connectionId + " for message " + ((object)message)?.GetType().FullName)); throw new InvalidOperationException("application send has no live connection " + connectionId + " for message " + ((object)message)?.GetType().FullName); } Net.Send(val, message); } private void Broadcast(INetMessage message) { if (Net == null) { CoopPlugin.Log.LogError((object)("application broadcast failed: no live transport for message " + ((object)message)?.GetType().FullName)); throw new InvalidOperationException("application broadcast has no live transport for message " + ((object)message)?.GetType().FullName); } Net.Broadcast(message); } private void Relay(int exceptConnectionId, INetMessage message) { if (Net == null) { CoopPlugin.Log.LogError((object)("application relay failed: no live transport for message " + ((object)message)?.GetType().FullName)); throw new InvalidOperationException("application relay has no live transport for message " + ((object)message)?.GetType().FullName); } ((Net as ICoopRelayTransport) ?? throw new InvalidOperationException("the live transport has no relay seam for message " + ((object)message)?.GetType().FullName)).Relay(message, exceptConnectionId); } private PeerConnection ConnectionFor(int id) { if (Net == null) { return null; } foreach (PeerConnection connection in Net.Connections) { if (connection.Id == id) { return connection; } } return null; } private IReadOnlyList ConnectionIds() { if (Net != null) { return Net.Connections.Select((PeerConnection c) => c.Id).ToArray(); } return Array.Empty(); } private void RejectConn(PeerConnection connection, string reason, bool retryable = false) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown if (connection != null) { CoopPlugin.Log.LogWarning((object)("rejected connection " + connection.Id + ": " + reason)); Net?.GracefulDisconnect(connection, new DisconnectInfo(reason, false, "rejected", retryable, (ConnectionState)1)); } } private void Shutdown(string reason, DisconnectInfo detail = null) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (_shutdownCompleted) { return; } _shutdownCompleted = true; IsTearingDown = true; Interlocked.Increment(ref _sessionGeneration); _dispatcher.InvalidateEpoch(); if (Net != null) { foreach (PeerConnection connection in Net.Connections) { Net.GracefulDisconnect(connection, (DisconnectInfo)(((object)detail) ?? ((object)new DisconnectInfo(reason ?? "session ended", false, "shutdown", true, (ConnectionState)4)))); } Net.Stop(); ConnectionEvent result; while (Net.Disconnects.TryDequeue(out result)) { _runtime?.ClientDisconnected(result.Connection, result.Disconnect); } } try { DeactivateLiveModuleHooks(); } finally { try { _persistentRuntime?.SessionStopped(); } finally { ConnectionProvider?.Dispose(); ConnectionProvider = null; Net = null; _liveRuntimeContext = null; } } SaveTransferApi.ClearBorrowedWorldIfSafe(InGameLevel()); SaveTransferRuntimeReset(); PresenceApi.ClearPeerNames(); HudApi.Clear(); CatalogIdMap.Clear(); WorldHostBehaviour.ResetCardState(); WorldClientBehaviour.ResetCardState(); SceneRef.ClearAll(); _dispatchBuffer.Clear(); _dispatchRejectedConnections.Clear(); _localConnectionId = -1; _sessionInGame = false; IsSteamSession = false; HostPassword = ""; _joinPassword = ""; _steam?.Leave(); Application.runInBackground = false; Role = CoopRole.None; IsTearingDown = false; SetStatus((reason == null) ? "Not connected" : ("Not connected (" + reason + ")")); } private static void SaveTransferRuntimeReset() { SaveTransferRuntime.ResetForShutdown(); } private void Update() { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) FrameCounter.Tick(); if (!_externalModsDiscovered) { _externalModsDiscovered = true; ExternalCoopMods.Instance.EnsureDiscovered(); _persistentRuntime?.IncludeExternalPersistentBehaviours(); ExternalCoopMods.Instance.NotifyPersistentScanComplete(); } PerfProbe.BeginFrame(); PerfProbe.FlushThreadMetrics(); using (PerfProbe.Sample("core.dispatcher")) { _dispatcher.Drain(); } using (PerfProbe.Sample("core.auto-tick")) { AutoTick(Time.deltaTime); } if (Input.GetKeyDown(CoopPlugin.UiToggleKey.Value)) { _ui.Visible = !_ui.Visible; } SessionInputRuntime.SetWindowModal(_ui.Visible && InGameplayScene()); if (Role != CoopRole.None && Input.GetKeyDown(CoopPlugin.EmoteKey.Value) && !CoopUI.TextFieldFocused && !NativeTextInputFocused()) { SendEmote(); } if (SaveTransferApi.GuestBorrowedWorld && Role == CoopRole.None && !InGameLevel()) { SaveTransferApi.ClearBorrowedWorldIfSafe(inGame: false); } if (Net == null) { return; } if (!_sessionInGame && InGameLevel()) { _sessionInGame = true; CoopPlugin.Log.LogInfo((object)"co-op session world gate opened"); } Application.runInBackground = true; using (PerfProbe.Sample("core.disconnect-drain")) { DrainDisconnects(); } if (!SaveTransferApi.PreloadHold) { using (PerfProbe.Sample("core.admit-incoming")) { AdmitIncoming(); } using (PerfProbe.Sample("core.dispatch-incoming")) { DispatchIncoming(); } _ = Net; } } private void DrainDisconnects() { int num = 0; ConnectionEvent result; while (num++ < 64 && Net != null && Net.Disconnects.TryDequeue(out result)) { int id = result.Connection.Id; _runtime?.ClientDisconnected(result.Connection, result.Disconnect); PresenceApi.RemovePeer(id); if (Role == CoopRole.Client) { DisconnectInfo disconnect = result.Disconnect; RememberDisconnect(disconnect); ErrorLine = ((disconnect == null) ? "Lost connection to the host." : ("Host disconnected (" + disconnect.Code + "): " + disconnect.Reason)); CoopPlugin.Log.LogInfo((object)("host disconnected: " + (((disconnect != null) ? disconnect.Code : null) ?? "?") + " / " + (((disconnect != null) ? disconnect.Reason : null) ?? "no detail"))); Shutdown(((disconnect != null) ? disconnect.Reason : null) ?? "host connection lost", disconnect); break; } ManualLogSource log = CoopPlugin.Log; string[] obj = new string[6] { "peer ", id.ToString(), " disconnected: ", null, null, null }; DisconnectInfo disconnect2 = result.Disconnect; obj[3] = ((disconnect2 != null) ? disconnect2.Code : null) ?? "?"; obj[4] = " / "; DisconnectInfo disconnect3 = result.Disconnect; obj[5] = ((disconnect3 != null) ? disconnect3.Reason : null) ?? "no detail"; log.LogInfo((object)string.Concat(obj)); SetStatus((Net.ConnectionCount == 0) ? "Hosting - waiting for a player..." : ("Hosting - " + Net.ConnectionCount + " player(s)")); } } private void AdmitIncoming() { int num = 0; InMsg message; while (num++ < 512 && Net != null && Net.TryDequeueIncoming(out message)) { if (message.Connection != null && _dispatchRejectedConnections.Contains(message.Connection.Id)) { continue; } if (_dispatchBuffer.Count < 2048) { _dispatchBuffer.Add(message); continue; } int num2 = FindBufferedTransient(); if (num2 >= 0) { InMsg message2 = _dispatchBuffer[num2]; _dispatchBuffer.RemoveAt(num2); LogDroppedTransient(message2); _dispatchBuffer.Add(message); } else if (IsReliable(message)) { RejectDispatchOverload(message); } else { LogDroppedTransient(message); } } if (_dispatchBuffer.Count < 1024) { _dispatchBacklogWarned = false; } PerfProbe.RecordQueueDepth("core.dispatch", _dispatchBuffer.Count, 2048); } private void DispatchIncoming() { int num = 0; int num2 = 0; int num3 = 0; for (int i = 0; i < _dispatchBuffer.Count; i++) { InMsg message = _dispatchBuffer[i]; if (message.Message == null) { num = i + 1; continue; } int num4 = DispatchCost(message); if (num3 > 0 && num2 + num4 > 256) { break; } num3++; num2 += num4; try { Dispatch(message); } catch (ReliableMessageHandlerException ex) { MarkDispatchConnectionRejected(message.Connection); ManualLogSource log = CoopPlugin.Log; string[] obj = new string[6] { "reliable dispatch failed for connection ", null, null, null, null, null }; PeerConnection connection = message.Connection; obj[1] = ((connection != null) ? new int?(connection.Id) : ((int?)null)).ToString(); obj[2] = " type="; obj[3] = message.MessageType?.FullName; obj[4] = "; the peer was disconnected and the remaining messages were held: "; obj[5] = ex.InnerException?.ToString(); log.LogError((object)string.Concat(obj)); num = i + 1; break; } catch (Exception ex2) { if (IsReliable(message)) { RequestReliableDispatchRecovery(message, ex2); num = i + 1; break; } ManualLogSource log2 = CoopPlugin.Log; string[] obj2 = new string[6] { "transient dispatch conn=", null, null, null, null, null }; PeerConnection connection2 = message.Connection; obj2[1] = ((connection2 != null) ? new int?(connection2.Id) : ((int?)null)).ToString(); obj2[2] = " type="; obj2[3] = message.MessageType?.FullName; obj2[4] = " failed; dropping message: "; obj2[5] = ex2?.ToString(); log2.LogWarning((object)string.Concat(obj2)); } num = i + 1; if (Net == null) { break; } } if (num >= _dispatchBuffer.Count) { _dispatchBuffer.Clear(); } else if (num > 0) { _dispatchBuffer.RemoveRange(0, num); } PerfProbe.RecordQueueDepth("core.dispatch", _dispatchBuffer.Count, 2048); } private bool Dispatch(InMsg message) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown if (message.Connection == null || Net == null || _dispatchRejectedConnections.Contains(message.Connection.Id) || !IsActiveConnection(message.Connection)) { return false; } MessageContext context = new MessageContext { Connection = message.Connection, InGame = InGameLevel(), WorkCost = DispatchCost(message), Transport = Net }; if (_messageRouter.DispatchCore(context, message.Message)) { return true; } if (message.ReceivedBeforeMessageIdActivation) { Net.GracefulDisconnect(message.Connection, new DisconnectInfo("non-handshake message received before catalog activation", false, "protocol_violation", false, message.Connection.State)); } return false; } private static int DispatchCost(InMsg message) { if (message.Message is CardDeltaBatchMessage cardDeltaBatchMessage) { int val = cardDeltaBatchMessage.Deltas?.Count ?? 0; return Math.Max(1, Math.Min(val, 200)); } if (message.Message is PricingDeltaBatchMessage pricingDeltaBatchMessage) { int val2 = (pricingDeltaBatchMessage.Items?.Count ?? 0) + (pricingDeltaBatchMessage.Cards?.Count ?? 0); return Math.Max(1, Math.Min(val2, 200)); } return 1; } private int FindBufferedTransient() { for (int i = 0; i < _dispatchBuffer.Count; i++) { if (IsTransient(_dispatchBuffer[i])) { return i; } } return -1; } private static bool IsReliable(InMsg message) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 if (message.Message == null) { return true; } if (!MessageRegistry.CurrentSnapshot.TryGet(message.MessageType, out var descriptor)) { return true; } return (int)descriptor.Reliability == 0; } private static bool IsTransient(InMsg message) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if (message.Message != null && MessageRegistry.CurrentSnapshot.TryGet(message.MessageType, out var descriptor)) { return (int)descriptor.Reliability == 1; } return false; } private void RejectDispatchOverload(InMsg message) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown PeerConnection connection = message.Connection; string text = message.MessageType?.FullName ?? ""; if (connection == null) { CoopPlugin.Log.LogError((object)("dispatch overload rejected reliable " + text + " without a peer connection")); } else if (_dispatchRejectedConnections.Add(connection.Id)) { string text2 = "dispatch overload: reliable backlog exhausted; reconnect to recover"; CoopPlugin.Log.LogError((object)("dispatch overload on connection " + connection.Id + ": rejecting reliable " + text + " (capacity " + 2048 + ")")); Net?.GracefulDisconnect(connection, new DisconnectInfo(text2, false, "dispatch_overload", true, connection.State)); } } private void LogDroppedTransient(InMsg message) { if (!_dispatchBacklogWarned) { _dispatchBacklogWarned = true; ManualLogSource log = CoopPlugin.Log; string? obj = message.MessageType?.FullName ?? ""; PeerConnection connection = message.Connection; log.LogWarning((object)("dispatch backlog cap reached; dropping transient " + obj + " from connection " + ((connection != null) ? new int?(connection.Id) : ((int?)null)))); } } private void RequestReliableDispatchRecovery(InMsg message, Exception error) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown MarkDispatchConnectionRejected(message.Connection); ManualLogSource log = CoopPlugin.Log; string[] obj = new string[6] { "reliable dispatch conn=", null, null, null, null, null }; PeerConnection connection = message.Connection; obj[1] = ((connection != null) ? new int?(connection.Id) : ((int?)null)).ToString(); obj[2] = " type="; obj[3] = message.MessageType?.FullName; obj[4] = " failed; requesting session recovery: "; obj[5] = error?.ToString(); log.LogError((object)string.Concat(obj)); if (message.Connection != null) { Net?.GracefulDisconnect(message.Connection, new DisconnectInfo("reliable dispatch failed; session recovery required", false, "dispatch_failed", true, message.Connection.State)); } } private void MarkDispatchConnectionRejected(PeerConnection connection) { if (connection != null) { _dispatchRejectedConnections.Add(connection.Id); } } private bool IsActiveConnection(PeerConnection connection) { foreach (PeerConnection item in Net?.Connections ?? Array.Empty()) { if (item == connection) { return true; } } return false; } private static bool ExpectControlState(PeerConnection connection, string control, params ConnectionState[] expected) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (connection == null || expected.All((ConnectionState state) => connection.State != state)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[6] { "Ignoring late/duplicate ", control, " from connection ", null, null, null }; PeerConnection obj2 = connection; obj[3] = ((obj2 != null) ? new int?(obj2.Id) : ((int?)null)).ToString(); obj[4] = " in phase "; PeerConnection obj3 = connection; obj[5] = ((obj3 != null) ? new ConnectionState?(obj3.State) : ((ConnectionState?)null)).ToString(); log.LogWarning((object)string.Concat(obj)); return false; } return true; } private string ResolvePeerName(ulong steamId, string wireName) { string result = (string.IsNullOrWhiteSpace(wireName) ? "Player" : wireName); if (steamId == 0L || _steam == null) { return result; } string text = _steam.FriendNickname(steamId); if (!string.IsNullOrWhiteSpace(text)) { return text; } return result; } private void SetStatus(string status) { StatusLine = status ?? ""; HudApi.SetStatusLine(StatusLine, 8f); } private void Guarded(string stage, Action action) { try { PerfProbe.Measure(stage, action); } catch (Exception e) { ModuleGuard.Log(stage, e); } } private void RememberDisconnect(DisconnectInfo detail) { if (detail != null) { LastDisconnectCode = detail.Code; LastDisconnectReason = detail.Reason; LastDisconnectRetryable = detail.Retryable; } } private void ClearDisconnectMetadata() { LastDisconnectCode = ""; LastDisconnectReason = ""; LastDisconnectRetryable = false; LastFailedLobby = 0uL; } private void OnLocalPackOpened(CEventPlayer_OnOpenCardPack eventData) { if (Role != CoopRole.None) { ICoopTransport net = Net; if (net != null && net.ConnectionCount > 0) { PresenceApi.SendActivity((EItemType)eventData.m_PackIndex); } } } private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _) { _worldReady = true; CoopPlugin.Log.LogInfo((object)"co-op: shop data finished loading; hosting is now admitted"); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) _worldReady = false; RegisterEventListeners(); SceneRef.NotifySceneLoaded(scene); if (((Scene)(ref scene)).name == "Title" && Role == CoopRole.Client && Net != null) { Shutdown("left the session"); } else if (((Scene)(ref scene)).name != "Title" && Role != CoopRole.None && Net != null && !SaveTransferApi.ClientReloading) { Shutdown("left the session (world reloaded)"); } } private bool TryAdmitHostStart(out string error) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); CGameManager val = GameManager(); bool flag = (Object)(object)val != (Object)null && val.m_IsGameLevel; bool worldReady = _worldReady; bool guestBorrowedWorld = SaveTransferApi.GuestBorrowedWorld; if (flag && worldReady && !guestBorrowedWorld) { error = null; return true; } ShelfManager val2 = SceneRef.Get(); error = (guestBorrowedWorld ? "Leave the joined shop and load your own shop before hosting." : ((!flag) ? "Load your shop first, then host." : "Wait for your shop to finish loading, then host.")); CoopPlugin.Log.LogWarning((object)("host start rejected: scene=" + ((Scene)(ref activeScene)).name + " manager=" + (((Object)(object)val == (Object)null) ? "" : ((object)val).GetType().FullName) + " game-level=" + flag + " ready=" + worldReady + " borrowed=" + guestBorrowedWorld + " shelf-data-loaded=" + ((Object)(object)val2 != (Object)null && val2.m_FinishLoadingObjectData) + " reason=" + error)); return false; } private static CGameManager GameManager() { CGameManager instance = CGameManager.m_Instance; if (!((Object)(object)instance != (Object)null)) { return SceneRef.Get(); } return instance; } private bool InGameLevel() { CGameManager val = GameManager(); if ((Object)(object)val != (Object)null) { return val.m_IsGameLevel; } return false; } private static bool InGameplayScene() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).isLoaded) { return ((Scene)(ref activeScene)).name != "Title"; } return false; } private void AutoTick(float deltaTime) { if (_autoPhase >= 99 || (_autoHostSlot < 0 && _autoJoinIp == null && _autoJoinSteamLobby == 0L)) { return; } _autoTimer += deltaTime; if (_autoHostSlot >= 0) { if (_autoPhase == 0 && _autoTimer > 6f && !InGameLevel()) { SaveTransferApi.ForceLoadSlot(_autoHostSlot); _autoPhase = 1; _autoTimer = 0f; } else if (_autoPhase == 1 && InGameLevel() && _worldReady) { _autoPhase = 2; _autoTimer = 0f; } else if (_autoPhase == 2 && _autoTimer > 3f) { StartHosting(); _autoPhase = 99; } } else if (_autoJoinIp != null && _autoPhase == 0 && _autoTimer > 10f && !InGameLevel()) { JoinHostedGame(_autoJoinIp); _autoPhase = 99; } else if (_autoJoinSteamLobby != 0L && _steam != null && _autoPhase == 0 && _autoTimer > 10f && !InGameLevel()) { JoinSteamLobby(_autoJoinSteamLobby); _autoPhase = 99; } } private void BeginInviteResolve() { InviteStatus = InviteState.Resolving; InviteCodeText = null; InviteReason = null; PortForwardState = 0; int port = CoopPlugin.Port.Value; string password = HostPassword ?? ""; int generation = Interlocked.Increment(ref _inviteGeneration); Thread thread = new Thread((ThreadStart)delegate { string publicIp = null; string reason = null; try { NetHelpers.BeginDiscovery(); if (CoopPlugin.AutoPortForward.Value) { string reason2; long epoch; bool mapped = NetHelpers.TryMapPort(port, out reason2, out epoch); Publish(generation, delegate { PortForwardState = (mapped ? 1 : 2); }); if (mapped) { publicIp = NetHelpers.UpnpExternalIp(); if (publicIp != null && !NetHelpers.IsPublicIPv4(publicIp)) { reason = "your router's internet address is private (carrier-grade NAT)"; publicIp = null; } } else if (reason2 != null) { CoopPlugin.Log.LogInfo((object)("automatic port forwarding did not happen: " + reason2)); } } if (publicIp == null) { publicIp = NetHelpers.StunPublicIp(); } if (publicIp != null && !NetHelpers.IsPublicIPv4(publicIp)) { publicIp = null; } } catch (Exception ex) { reason = ex.Message; CoopPlugin.Log.LogWarning((object)("invite code resolve failed: " + ex)); } string text = publicIp ?? NetHelpers.LocalIPv4ForGateway() ?? NetHelpers.LocalIPv4(); string code = ((text == null) ? null : InviteCode.Encode(text, port, password)); Publish(generation, delegate { InviteCodeText = code; InviteReason = reason; InviteStatus = ((publicIp == null) ? InviteState.LanOnly : InviteState.Ready); }); }); thread.IsBackground = true; thread.Name = "CoopInvite"; thread.Start(); } private void Publish(int generation, Action action) { _dispatcher.TryEnqueue("invite-publish", delegate { if (Volatile.Read(in _inviteGeneration) == generation) { action(); } }); } private static string GenerateSessionPassword() { byte[] array = new byte[8]; using (RNGCryptoServiceProvider rNGCryptoServiceProvider = new RNGCryptoServiceProvider()) { rNGCryptoServiceProvider.GetBytes(array); } char[] array2 = new char[array.Length]; for (int i = 0; i < array2.Length; i++) { array2[i] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"[array[i] & 0x1F]; } return new string(array2); } private void OnGUI() { using (PerfProbe.Sample("core.ongui")) { _ui.Draw(this, Net); } } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; CEventManager.RemoveListener((EventDelegate)OnLocalPackOpened); CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); Shutdown("plugin unloaded"); try { _persistentRuntime?.Shutdown(); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("Co-op persistent runtime shutdown failed: " + ex)); } } if ((Object)(object)_persistentRuntime != (Object)null) { Object.Destroy((Object)(object)_persistentRuntime); _persistentRuntime = null; } PerfProbe.Reset(); _dispatcher.Stop(); if (Instance == this) { Instance = null; } } private void OnApplicationQuit() { Shutdown("game closed"); } [MessageHandler(typeof(FullyJoinedMessage))] private void HandleFullyJoinedMessage(MessageContext context, FullyJoinedMessage message) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) if (Role != CoopRole.Host || context.Connection == null) { return; } if ((int)context.Connection.State != 2) { CoopPlugin.Log.LogWarning((object)("Ignoring late/duplicate FullyJoined from connection " + context.Connection.Id + " in phase " + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString())); return; } if (!SaveTransferApi.TryConsumeHostTransferAcknowledgement(context.Connection)) { CoopPlugin.Log.LogWarning((object)("Ignoring premature FullyJoined from connection " + context.Connection.Id + " before its world transfer was fully queued")); return; } try { _runtime?.FullyJoined(context.Connection); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Feature FullyJoined callbacks failed: " + ex)); } Send(context.ConnectionId, (INetMessage)(object)new FullyJoinedAckMessage()); if (!context.Connection.TryTransition((ConnectionState)3)) { CoopPlugin.Log.LogWarning((object)("Could not complete FullyJoined transition for connection " + context.Connection.Id + " in phase " + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString())); } } [MessageHandler(typeof(FullyJoinedAckMessage))] private void HandleFullyJoinedAckMessage(MessageContext context, FullyJoinedAckMessage message) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (Role != CoopRole.Client || context.Connection == null) { return; } if ((int)context.Connection.State != 2) { CoopPlugin.Log.LogWarning((object)("Ignoring late/duplicate FullyJoinedAck from connection " + context.Connection.Id + " in phase " + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString())); } else if (!SaveTransferApi.TryConsumeClientTransferAcknowledgement(context.Connection)) { CoopPlugin.Log.LogWarning((object)"Ignoring premature FullyJoinedAck before the client finished loading the transferred world"); } else if (context.Connection.TryTransition((ConnectionState)3)) { try { _runtime?.FullyJoined(context.Connection); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Feature FullyJoined callbacks failed: " + ex)); } } } [MessageHandler(typeof(DisconnectMessage))] private void HandleDisconnectMessage(MessageContext context, DisconnectMessage message) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown if (context.Connection == null) { CoopPlugin.Log.LogWarning((object)"Ignoring disconnect control without a connection"); return; } ConnectionState val = (ConnectionState)(Enum.IsDefined(typeof(ConnectionState), message.Phase) ? message.Phase : 4); DisconnectInfo val2 = new DisconnectInfo(message.Reason, true, message.Code, message.Retryable, val); if (Role == CoopRole.Client) { RememberDisconnect(val2); Shutdown("remote: " + val2.Reason, val2); } } internal static void EnqueueMainThread(Action action) { if (action == null || TryEnqueueMainThread(action)) { return; } throw new InvalidOperationException("CoopCore is not running"); } internal static bool TryEnqueueMainThread(Action action) { if (action == null) { return false; } CoopCore instance = Instance; if ((Object)(object)instance == (Object)null) { return false; } return instance._dispatcher.TryEnqueue("external-main-thread", action); } public static bool TryGetLocalPlayerPosition(out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); return PresenceApi.TryGetLocalPlayerPosition(out position); } internal static bool NativeTextInputFocused() { EventSystem current = EventSystem.current; GameObject val = ((current != null) ? current.currentSelectedGameObject : null); if ((Object)(object)val == (Object)null) { return false; } TMP_InputField component = val.GetComponent(); if ((Object)(object)component != (Object)null) { return component.isFocused; } return false; } } public enum GradedAlertMode { Always, OncePerSession, Never } [BepInPlugin("dev.meepen.cardshopcoop", "CardShopCoopCommunity", "2.0.6")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class CoopPlugin : BaseUnityPlugin { public const string Guid = "dev.meepen.cardshopcoop"; public const string Name = "CardShopCoopCommunity"; public static ManualLogSource Log; public static ConfigEntry Port; public static ConfigEntry LastJoinIP; public static ConfigEntry PlayerName; public static ConfigEntry SendRateHz; public static ConfigEntry AvatarsEnabled; public static ConfigEntry AllowNsfw; public static ConfigEntry UiToggleKey; public static ConfigEntry EmoteKey; public static ConfigEntry ClientWorldSlot; public static ConfigEntry ServeReach; public static ConfigEntry AllowCrossBuildJoin; public static ConfigEntry AutoPortForward; public static ConfigEntry AutoLanPassword; public static ConfigEntry GradedDriftAlert; public static ConfigEntry PerfDebug; public static ConfigEntry ArtificialLagMs; public static ConfigEntry ArtificialJitterMs; public static ConfigEntry EnableGameCheatMenu; public static ConfigEntry ShowHiddenCategory; public const string Version = "2.0.6"; private void Awake() { //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Invalid comparison between Unknown and I4 //IL_0443: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; CoopApi.Binding = (ICoopBinding)(object)ExternalCoopMods.Instance; CoopApi.Log = ((BaseUnityPlugin)this).Logger; SceneRef.Install(); ScreenSingletonProbe.Install(); try { Assembly.Load("Newtonsoft.Json"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"network payload serializer: Newtonsoft.Json"); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("network payload serializer unavailable: Newtonsoft.Json.dll is required (" + ex.Message + ")")); } FileLog.Init(Paths.GameRootPath); ((BaseUnityPlugin)this).Logger.LogEvent += delegate(object _, LogEventArgs e2) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) FileLog.Write($"{e2.Level,-7} {e2.Data}"); }; Port = ((BaseUnityPlugin)this).Config.Bind("Network", "Port", 27886, "UDP port used for LAN/KCP hosting. Both PCs' firewalls must allow the game on this port."); LastJoinIP = ((BaseUnityPlugin)this).Config.Bind("Network", "LastJoinIP", "192.168.1.100", "IP address of the host PC (remembered after a successful join)."); PlayerName = ((BaseUnityPlugin)this).Config.Bind("Player", "Name", Environment.UserName, "Name shown above your head on the other player's screen."); SendRateHz = ((BaseUnityPlugin)this).Config.Bind("Network", "SendRateHz", 15f, "How many position updates per second to send (8-20 is sensible)."); if (Mathf.Approximately(SendRateHz.Value, 12f)) { SendRateHz.Value = 15f; } AvatarsEnabled = ((BaseUnityPlugin)this).Config.Bind("Player", "AvatarsEnabled", true, "Show the other player as a walking character in your shop."); AllowNsfw = ((BaseUnityPlugin)this).Config.Bind("Player", "AllowNsfw", false, "Allow nude/NSFW character appearances. When off, new characters get a random clothed preset, the Nude wardrobe option is hidden, and fully nude players are shown in the game's random clothed customer look. When on, you may choose Nude and you will see other players who chose it."); UiToggleKey = ((BaseUnityPlugin)this).Config.Bind("Keys", "UiToggleKey", (KeyCode)283, "Toggles the co-op window. (F3 is reserved for future co-op options.)"); if ((int)UiToggleKey.Value == 292) { UiToggleKey.Value = (KeyCode)283; } EmoteKey = ((BaseUnityPlugin)this).Config.Bind("Keys", "EmoteKey", (KeyCode)103, "Sends a wave emote that pops above your avatar."); ClientWorldSlot = ((BaseUnityPlugin)this).Config.Bind("Network", "ClientWorldSlot", -100, "Save slot the co-op world uses when JOINING someone. The default -100 is a reserved negative slot the save UI never shows, so your own local saves (0-3) are never touched. Only change this if you need the borrowed world to live at a specific slot."); ServeReach = ((BaseUnityPlugin)this).Config.Bind("Player", "ServeReach", 1.6f, "How close (meters, to the counter's center) a JOINER must stand to answer a counter trade customer. The counter itself is ~1m wide, so values below ~1.2 make it unreachable."); AllowCrossBuildJoin = ((BaseUnityPlugin)this).Config.Bind("Network", "AllowCrossBuildJoin", false, "Let players join even when the GAME build fingerprint (game version / Unity version) differs from the host's. Dangerous: two different game builds can corrupt each other's saves. Only enable for supervised testing of Steam <-> Game Pass cross-play."); AutoPortForward = ((BaseUnityPlugin)this).Config.Bind("Network", "AutoPortForward", true, "While you are LAN-hosting, ask your router over UPnP to open the co-op port so a friend outside your house can join with an invite code - and remove that opening again when you stop hosting. Turn it off if you forward the port yourself, or if you would rather nothing touched the router. Either way the invite code still works inside your own house."); AutoLanPassword = ((BaseUnityPlugin)this).Config.Bind("Network", "AutoLanPassword", true, "Hosting via LAN generates a random session password. It is baked into the invite code (so a friend using the code notices nothing), shown in the host panel for a friend typing your IP by hand, and checked exactly like the Steam lobby password. Leave this on: the port your router opens for you is a door into your game, and this is the lock on it."); GradedDriftAlert = ((BaseUnityPlugin)this).Config.Bind("Graded", "DriftAlert", GradedAlertMode.Always, "How loudly to announce that your graded albums have drifted apart. THIS CONTROLS THE HOST'S SCREEN. While you are HOSTING it decides both your own on-screen line and the heads-up sent to the joiner. While you are JOINING it does nothing at all: the host's setting alone decides whether you get that heads-up, because the drift is only ever announced from the host's side. Always: announce every check that finds a difference. OncePerSession: say it once per player per session and then stay quiet - including for a later, bigger difference. Never: never put it on screen at all. The log records every check whichever you pick and on both PCs, so a support log stays complete whatever you choose; this only controls the screen, and it never changes what the co-op panel's adopt button offers."); PerfDebug = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "PerfDebug", false, "Enable Unity hitch profiling. Logs frames slower than 33 ms with available main-thread, GC, rendering, physics, and co-op stage timings; individual stages above 5 ms are also rate-limited. Also logs a [perf-update] breakdown of every mod Update/LateUpdate and Harmony patch body each window. Enable temporarily to find lag spikes; it does not change gameplay."); ArtificialLagMs = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "ArtificialLagMs", 0, "TESTING ONLY. Adds this many milliseconds of latency to every network message you RECEIVE. Set the same value on both PCs for symmetric lag (each hop adds one delay, so a round trip is roughly twice the value). 0 disables it."); ArtificialJitterMs = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "ArtificialJitterMs", 0, "TESTING ONLY. Randomly varies each received message's artificial latency by up to this many milliseconds, plus or minus, so the connection feels unstable. 0 is a steady delay. Only meaningful while ArtificialLagMs is above 0."); EnableGameCheatMenu = ((BaseUnityPlugin)this).Config.Bind("Hidden", "EnableGameCheatMenu", false, "TESTING ONLY. Allows the game's 1.00 cheat menu to open in solo mode or for the HOST. Clients are always blocked. Requires ShowHiddenCategory=true as an additional safety gate."); ShowHiddenCategory = ((BaseUnityPlugin)this).Config.Bind("Hidden", "ShowHiddenCategory", false, "TESTING ONLY. Shows the hidden category in the CardShopCoopCommunity window. The game cheat menu cannot open unless this and EnableGameCheatMenu are both enabled."); ModuleCatalog.Bind(((BaseUnityPlugin)this).Config); ModuleCatalog.LogConfiguration(); UpdateProfiler.Install(); if (!PlatformProbe.SteamworksPresent) { Log.LogInfo((object)"Steamworks assembly not detected - LAN and direct IP only (a HarmonyX ReflectionTypeLoadException warning naming Steamworks types may appear when any mod - including this one - enumerates loaded types; it is EXPECTED on this build and harmless)"); } else { Log.LogInfo((object)"Steamworks assembly detected."); } Log.LogInfo((object)("save backend: " + PlatformProbe.SaveBackendDescription + " - decided from the game's own save-completion counter at join time, never from Steamworks presence.")); GameObject val = new GameObject("CardShopCoop"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; bool flag; try { AttachCore(val); flag = true; } catch (Exception ex2) { flag = false; Log.LogError((object)("CardShopCoopCommunity 2.0.6: CoopCore FAILED TO LOAD - co-op is disabled this session and NO game patches were installed (the game runs exactly as vanilla). " + ex2.GetType().Name + ": " + ex2.Message)); try { Object.Destroy((Object)(object)val); } catch (Exception e) { Swallow.Log(e, "Awake", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/CoopPlugin.cs", 204); } } if (flag) { Log.LogInfo((object)string.Format("{0} {1} loaded. Press {2} in-game to open the co-op window.", "CardShopCoopCommunity", "2.0.6", UiToggleKey.Value)); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void AttachCore(GameObject go) { go.AddComponent(); } } internal static class Swallow { private const int CooldownMs = 2000; private static readonly ConcurrentDictionary LastLogged = new ConcurrentDictionary(); public static void Log(Exception e, [CallerMemberName] string member = "", [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) { if (e == null) { return; } string text; try { text = (string.IsNullOrEmpty(file) ? "?" : Path.GetFileNameWithoutExtension(file)); } catch { text = "?"; } string text2 = text + "." + (member ?? "?") + ":" + line; int tickCount = Environment.TickCount; if (LastLogged.TryGetValue(text2, out var value) && tickCount - value < 2000) { return; } LastLogged[text2] = tickCount; try { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("[caught] " + text2 + ": " + e)); } } catch { } } } } namespace CardShopCoop.Util { public abstract class Cached where T : class { private T _value; public T Get() { if (_value != null && !IsDestroyed(_value)) { return _value; } _value = GetRawValue(); return _value; } public void Clear() { _value = null; } protected virtual T GetRawValue() { return null; } private static bool IsDestroyed(T value) { Object val = (Object)(object)((value is Object) ? value : null); if (val != null) { return val == (Object)null; } return false; } } public static class FileLog { private static StreamWriter _writer; private static readonly object Lock = new object(); private static int _lastFlushTick; public static string Path { get; private set; } public static void Init(string gameRoot) { try { int id = Process.GetCurrentProcess().Id; Path = System.IO.Path.Combine(gameRoot, "BepInEx", $"CardShopCoopCommunity_{id}.log"); _writer = new StreamWriter(Path, append: false); AppDomain.CurrentDomain.ProcessExit += delegate { Flush(); }; Write("log started " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); Flush(); } catch { _writer = null; } } public static void Write(string line) { if (_writer == null) { return; } lock (Lock) { try { _writer.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {line}"); int tickCount = Environment.TickCount; if (tickCount - _lastFlushTick > 1000) { _writer.Flush(); _lastFlushTick = tickCount; } } catch (Exception e) { Swallow.Log(e, "Write", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/FileLog.cs", 62); } } } private static void Flush() { lock (Lock) { try { _writer?.Flush(); } catch (Exception e) { Swallow.Log(e, "Flush", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/FileLog.cs", 74); } } } } internal static class FrameCounter { private const double WindowSeconds = 1.0; private static int _frames; private static double _windowStart; private static double _fps; internal static double Fps => _fps; internal static void Tick() { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (_windowStart <= 0.0) { _windowStart = realtimeSinceStartupAsDouble; _frames = 0; return; } _frames++; double num = realtimeSinceStartupAsDouble - _windowStart; if (num >= 1.0) { _fps = (double)_frames / num; _frames = 0; _windowStart = realtimeSinceStartupAsDouble; } } internal static void Reset() { _frames = 0; _windowStart = 0.0; _fps = 0.0; } } public static class ModParity { private static string _plugins; public static string PluginHash() { if (_plugins != null) { return _plugins; } try { _plugins = Short(Sha1(string.Join(";", PluginEntries()))); } catch { _plugins = "err"; } return _plugins; } public static List PluginList() { try { return PluginEntries(); } catch (Exception e) { Swallow.Log(e, "PluginList", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/ModParity.cs", 37); return new List(); } } private static List PluginEntries() { List list = new List(); foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { list.Add(pluginInfo.Key + "=" + pluginInfo.Value.Metadata.Version); } list.Sort(StringComparer.Ordinal); return list; } public static string DescribeMismatch(IReadOnlyList peerEntries, int maxLength) { if (maxLength < "your mod set differs from the host's - both players need identical mods".Length) { maxLength = "your mod set differs from the host's - both players need identical mods".Length; } Dictionary dictionary = ParseEntries(PluginEntries()); Dictionary dictionary2 = ParseEntries(peerEntries); List list = new List(); List list2 = new List(); List list3 = new List(); foreach (string item in Union(dictionary, dictionary2)) { string value; bool num = dictionary.TryGetValue(item, out value); string value2; bool flag = dictionary2.TryGetValue(item, out value2); if (num && flag) { if (!string.Equals(value, value2, StringComparison.Ordinal)) { list3.Add(item + " (client " + value2 + ", host " + value + ")"); } } else if (flag) { list2.Add(item + "=" + value2); } else { list.Add(item + "=" + value); } } if (list.Count == 0 && list2.Count == 0 && list3.Count == 0) { return "your mod set differs from the host's - both players need identical mods"; } StringBuilder stringBuilder = new StringBuilder(); int omitted = 0; AppendCategory(stringBuilder, "only on host", list, maxLength, ref omitted); AppendCategory(stringBuilder, "only on client", list2, maxLength, ref omitted); AppendCategory(stringBuilder, "different version", list3, maxLength, ref omitted); string text = ((stringBuilder.Length == 0) ? "your mod set differs from the host's - both players need identical mods" : ("your mod set differs from the host's - both players need identical mods. " + stringBuilder)); if (omitted > 0) { text = text + " (+" + omitted + " more)"; } if (text.Length > maxLength) { return text.Substring(0, maxLength); } return text; } private static Dictionary ParseEntries(IReadOnlyList entries) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (entries == null) { return dictionary; } foreach (string entry in entries) { if (!string.IsNullOrEmpty(entry)) { int num = entry.IndexOf('='); string key = ((num < 0) ? entry : entry.Substring(0, num)); string value = ((num < 0) ? "?" : entry.Substring(num + 1)); if (!dictionary.ContainsKey(key)) { dictionary[key] = value; } } } return dictionary; } private static List Union(Dictionary local, Dictionary peer) { SortedSet sortedSet = new SortedSet(StringComparer.Ordinal); foreach (string key in local.Keys) { sortedSet.Add(key); } foreach (string key2 in peer.Keys) { sortedSet.Add(key2); } return new List(sortedSet); } private static void AppendCategory(StringBuilder sb, string label, List items, int maxLength, ref int omitted) { if (items.Count == 0) { return; } int length = sb.Length; sb.Append((sb.Length == 0) ? "" : "; ").Append(label).Append(": "); int num = 0; for (int i = 0; i < items.Count; i++) { string text = ((num == 0) ? items[i] : (", " + items[i])); if (sb.Length + text.Length + 16 > maxLength) { omitted += items.Count - i; if (num == 0) { sb.Length = length; } break; } sb.Append(text); num++; } } public static Type ResolveType(string typeName, string assemblySimpleName) { try { Type type = Type.GetType(typeName + ", " + assemblySimpleName, throwOnError: false); if (type != null) { return type; } } catch (Exception e) { Swallow.Log(e, "ResolveType", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/ModParity.cs", 204); } try { return AccessTools.TypeByName(typeName); } catch (Exception e2) { Swallow.Log(e2, "ResolveType", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/ModParity.cs", 209); return null; } } private static string Sha1(string value) { using SHA1 sHA = SHA1.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(value))).Replace("-", ""); } private static string Short(string hex) { if (hex.Length <= 16) { return hex; } return hex.Substring(0, 16); } } internal static class PerfProbe { private struct FrameStage { internal long TotalTicks; internal long MaxTicks; internal int Calls; } private sealed class FrameSlot { internal int Frame = -1; internal double ElapsedMs; internal readonly Dictionary Stages = new Dictionary(); internal void Reset(int frame) { Frame = frame; ElapsedMs = (double)Time.unscaledDeltaTime * 1000.0; Stages.Clear(); } internal void Clear() { Frame = -1; ElapsedMs = 0.0; Stages.Clear(); } } private sealed class ThreadMetric { public long Count; public long TotalTicks; public long MaxTicks; } private sealed class HandlerMetric { public long Count; public long TotalTicks; public long MaxTicks; public long TotalCost; public long Failures; } private sealed class QueueMetric { public long Samples; public int LastDepth; public int PeakDepth; public int Capacity; } private sealed class RecorderMetric : IDisposable { internal readonly string Name; internal readonly bool Nanoseconds; internal readonly bool Bytes; internal ProfilerRecorder Recorder; internal bool Valid => ((ProfilerRecorder)(ref Recorder)).Valid; internal RecorderMetric(string name, ProfilerCategory category, string marker, bool nanoseconds = false, bool bytes = false) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Name = name; Nanoseconds = nanoseconds; Bytes = bytes; Recorder = ProfilerRecorder.StartNew(category, marker, 1, (ProfilerRecorderOptions)24); } public void Dispose() { ((ProfilerRecorder)(ref Recorder)).Dispose(); } } internal readonly struct Scope : IDisposable { private readonly string _stage; private readonly long _start; private readonly bool _sample; internal Scope(string stage) { _stage = stage; _start = (Enabled ? Stopwatch.GetTimestamp() : 0); _sample = _start != 0; _ = _sample; } public void Dispose() { if (!_sample) { return; } try { } finally { End(_stage, _start); } } } public readonly struct ThreadScope : IDisposable { private readonly string _stage; private readonly long _start; internal ThreadScope(string stage) { _stage = stage; _start = StartThreadMetric(); } public void Dispose() { if (_start != 0L) { EndThreadMetric(_stage, _start); } } } private const double StageThresholdMs = 5.0; private const double HitchThresholdMs = 33.0; private const double RepeatSeconds = 2.0; private const double AverageIntervalSeconds = 5.0; private const int TopStageCount = 8; private static readonly Dictionary LastStageLog = new Dictionary(); private static readonly Dictionary AverageStageTicks = new Dictionary(); private static readonly FrameSlot[] FrameSlots = new FrameSlot[2] { new FrameSlot(), new FrameSlot() }; private static readonly ConcurrentDictionary ThreadMetrics = new ConcurrentDictionary(); private static readonly ConcurrentDictionary HandlerMetrics = new ConcurrentDictionary(); private static readonly ConcurrentDictionary QueueMetrics = new ConcurrentDictionary(); private static readonly List Recorders = new List(); private static bool _recordersStarted; private static double _lastHitchLog; private static int _suppressedHitches; private static int _averageFrames; private static double _averageWindowStart; public static bool Enabled { get { try { return CoopPlugin.PerfDebug != null && CoopPlugin.PerfDebug.Value; } catch (Exception e) { Swallow.Log(e, "Enabled", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/PerfProbe.cs", 183); return false; } } } public static void BeginFrame() { if (!Enabled) { if (_recordersStarted) { StopRecorders(); ClearFrameSlots(); ThreadMetrics.Clear(); HandlerMetrics.Clear(); QueueMetrics.Clear(); LastStageLog.Clear(); AverageStageTicks.Clear(); _lastHitchLog = 0.0; _suppressedHitches = 0; _averageFrames = 0; _averageWindowStart = 0.0; } } else { EnsureRecorders(); AccumulateAverage(); FrameSlot frameSlot = FindFrameSlot(Time.frameCount - 1); if (frameSlot != null) { ReportPreviousFrame(frameSlot.Frame, frameSlot.ElapsedMs, frameSlot.Stages); frameSlot.Clear(); } } } public static Scope Sample(string stage) { if (string.IsNullOrEmpty(stage)) { throw new ArgumentException("A performance stage name is required.", "stage"); } return new Scope(stage); } public static ThreadScope ThreadSample(string stage) { if (string.IsNullOrEmpty(stage)) { throw new ArgumentException("A performance stage name is required.", "stage"); } return new ThreadScope(stage); } public static void Measure(string stage, Action action) { if (!Enabled) { action(); return; } using (Sample(stage)) { action(); } } public static void Reset() { LastStageLog.Clear(); ClearFrameSlots(); ThreadMetrics.Clear(); HandlerMetrics.Clear(); QueueMetrics.Clear(); _lastHitchLog = 0.0; _suppressedHitches = 0; AverageStageTicks.Clear(); _averageFrames = 0; _averageWindowStart = 0.0; StopRecorders(); UpdateProfiler.Reset(); } public static long Start() { if (!Enabled) { return 0L; } return Stopwatch.GetTimestamp(); } public static void End(string stage, long start) { if (start == 0L) { return; } long num = Stopwatch.GetTimestamp() - start; RecordFrameStage(stage, num); double num2 = TicksToMilliseconds(num); if (!(num2 < 5.0)) { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (!LastStageLog.TryGetValue(stage, out var value) || !(realtimeSinceStartupAsDouble - value < 2.0)) { LastStageLog[stage] = realtimeSinceStartupAsDouble; CoopPlugin.Log.LogWarning((object)$"[perf] {stage} took {num2:F1} ms"); } } } public static void End(string stagePrefix, Type type, long start) { if (start != 0L) { End(stagePrefix + (type?.Name ?? "unknown"), start); } } public static long StartThreadMetric() { if (!Enabled) { return 0L; } return Stopwatch.GetTimestamp(); } public static void EndThreadMetric(string stage, long start) { if (start != 0L) { long num = Stopwatch.GetTimestamp() - start; ThreadMetric orAdd = ThreadMetrics.GetOrAdd(stage, (string _) => new ThreadMetric()); Interlocked.Increment(ref orAdd.Count); Interlocked.Add(ref orAdd.TotalTicks, num); long num2; do { num2 = Interlocked.Read(in orAdd.MaxTicks); } while (num2 < num && Interlocked.CompareExchange(ref orAdd.MaxTicks, num, num2) != num2); } } public static void EndThreadMetric(string stagePrefix, Type type, long start) { if (start != 0L) { EndThreadMetric(stagePrefix + (type?.Name ?? "unknown"), start); } } internal static long StartHandlerMetric() { if (!Enabled) { return 0L; } return Stopwatch.GetTimestamp(); } internal static void EndHandlerMetric(Type type, int cost, long start, bool failed) { if (start != 0L) { string key = "net.handler." + (type?.FullName ?? "unknown"); HandlerMetric orAdd = HandlerMetrics.GetOrAdd(key, (string _) => new HandlerMetric()); long num = Stopwatch.GetTimestamp() - start; Interlocked.Increment(ref orAdd.Count); Interlocked.Add(ref orAdd.TotalTicks, num); Interlocked.Add(ref orAdd.TotalCost, Math.Max(1, cost)); if (failed) { Interlocked.Increment(ref orAdd.Failures); } long num2; do { num2 = Interlocked.Read(in orAdd.MaxTicks); } while (num2 < num && Interlocked.CompareExchange(ref orAdd.MaxTicks, num, num2) != num2); } } internal static void RecordQueueDepth(string name, int depth, int capacity) { if (Enabled && !string.IsNullOrEmpty(name)) { QueueMetric orAdd = QueueMetrics.GetOrAdd(name, (string _) => new QueueMetric()); depth = Math.Max(0, depth); capacity = Math.Max(0, capacity); Interlocked.Increment(ref orAdd.Samples); Interlocked.Exchange(ref orAdd.LastDepth, depth); Interlocked.Exchange(ref orAdd.Capacity, capacity); int num; do { num = Interlocked.CompareExchange(ref orAdd.PeakDepth, 0, 0); } while (num < depth && Interlocked.CompareExchange(ref orAdd.PeakDepth, depth, num) != num); } } public static void FlushThreadMetrics() { if (!Enabled) { return; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; foreach (KeyValuePair threadMetric in ThreadMetrics) { ThreadMetric value = threadMetric.Value; long num = Interlocked.Exchange(ref value.Count, 0L); long ticks = Interlocked.Exchange(ref value.TotalTicks, 0L); long ticks2 = Interlocked.Exchange(ref value.MaxTicks, 0L); if (num != 0L) { double num2 = TicksToMilliseconds(ticks2); if (!(num2 < 5.0) && (!LastStageLog.TryGetValue(threadMetric.Key, out var value2) || !(realtimeSinceStartupAsDouble - value2 < 2.0))) { LastStageLog[threadMetric.Key] = realtimeSinceStartupAsDouble; double num3 = TicksToMilliseconds(ticks) / (double)num; CoopPlugin.Log.LogWarning((object)($"[perf] {threadMetric.Key} {num} call(s), " + $"avg {num3:F1} ms, max {num2:F1} ms")); } } } FlushHandlerMetrics(realtimeSinceStartupAsDouble); FlushQueueMetrics(realtimeSinceStartupAsDouble); } private static void FlushHandlerMetrics(double now) { foreach (KeyValuePair handlerMetric in HandlerMetrics) { HandlerMetric value = handlerMetric.Value; long num = Interlocked.Exchange(ref value.Count, 0L); long ticks = Interlocked.Exchange(ref value.TotalTicks, 0L); long ticks2 = Interlocked.Exchange(ref value.MaxTicks, 0L); long num2 = Interlocked.Exchange(ref value.TotalCost, 0L); long num3 = Interlocked.Exchange(ref value.Failures, 0L); if (num == 0L) { continue; } double num4 = TicksToMilliseconds(ticks2); if (!LastStageLog.TryGetValue(handlerMetric.Key, out var value2) || !(now - value2 < 2.0)) { LastStageLog[handlerMetric.Key] = now; double num5 = TicksToMilliseconds(ticks) / (double)num; double num6 = (double)num2 / (double)num; string text = $"[perf] {handlerMetric.Key} {num} call(s), " + $"avg {num5:F1} ms, max {num4:F1} ms, " + $"avg-cost {num6:F1}, failures {num3}"; if (num3 != 0L || num4 >= 5.0) { CoopPlugin.Log.LogWarning((object)text); } else { CoopPlugin.Log.LogDebug((object)text); } } } } private static void FlushQueueMetrics(double now) { foreach (KeyValuePair queueMetric in QueueMetrics) { QueueMetric value = queueMetric.Value; long num = Interlocked.Exchange(ref value.Samples, 0L); int num2 = Interlocked.Exchange(ref value.LastDepth, 0); int num3 = Interlocked.Exchange(ref value.PeakDepth, 0); int num4 = Interlocked.CompareExchange(ref value.Capacity, 0, 0); if (num != 0L && (!LastStageLog.TryGetValue("queue." + queueMetric.Key, out var value2) || !(now - value2 < 2.0))) { LastStageLog["queue." + queueMetric.Key] = now; CoopPlugin.Log.LogInfo((object)($"[perf] queue {queueMetric.Key}: depth {num2}, peak {num3}/" + num4 + ", samples " + num)); } } } private static void RecordFrameStage(string stage, long elapsed) { Dictionary stages = GetFrameSlot(Time.frameCount).Stages; stages.TryGetValue(stage, out var value); value.TotalTicks += elapsed; value.Calls++; if (elapsed > value.MaxTicks) { value.MaxTicks = elapsed; } stages[stage] = value; AverageStageTicks.TryGetValue(stage, out var value2); AverageStageTicks[stage] = value2 + elapsed; } private static void AccumulateAverage() { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (_averageWindowStart <= 0.0) { _averageWindowStart = realtimeSinceStartupAsDouble; } _averageFrames++; double num = realtimeSinceStartupAsDouble - _averageWindowStart; if (!(num < 5.0)) { int frames = Math.Max(1, _averageFrames); string[] value = (from pair in AverageStageTicks.OrderByDescending((KeyValuePair pair) => pair.Value).Take(8) select pair.Key + "=" + (TicksToMilliseconds(pair.Value) / (double)frames).ToString("F2") + "ms").ToArray(); CoopPlugin.Log.LogInfo((object)("[perf-avg] fps=" + ((double)frames / num).ToString("F1") + " frame=" + (num * 1000.0 / (double)frames).ToString("F2") + "ms stages=[" + string.Join(",", value) + "]")); UpdateProfiler.Flush(num); AverageStageTicks.Clear(); _averageFrames = 0; _averageWindowStart = realtimeSinceStartupAsDouble; } } private static void EnsureRecorders() { //IL_0013: 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_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (!_recordersStarted) { _recordersStarted = true; TryAddRecorder("main", ProfilerCategory.Internal, "Main Thread", nanoseconds: true); TryAddRecorder("gc-alloc", ProfilerCategory.Memory, "GC Allocated In Frame", nanoseconds: false, bytes: true); TryAddRecorder("gc-used", ProfilerCategory.Memory, "GC Used Memory", nanoseconds: false, bytes: true); TryAddRecorder("draw-calls", ProfilerCategory.Render, "Draw Calls Count"); TryAddRecorder("batches", ProfilerCategory.Render, "Batches Count"); TryAddRecorder("physics", ProfilerCategory.Physics, "Physics.Processing", nanoseconds: true); UpdateProfiler.Install(); string text = ((Recorders.Count == 0) ? "none" : string.Join(",", Recorders.Select((RecorderMetric metric) => metric.Name))); CoopPlugin.Log.LogInfo((object)("[perf] Unity hitch recorder enabled; available counters=" + text + "; hitch-threshold=" + 33.0.ToString("F0") + " ms; avg-window=" + 5.0.ToString("F0") + "s")); } } private static void TryAddRecorder(string name, ProfilerCategory category, string marker, bool nanoseconds = false, bool bytes = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { RecorderMetric recorderMetric = new RecorderMetric(name, category, marker, nanoseconds, bytes); if (recorderMetric.Valid) { Recorders.Add(recorderMetric); } else { recorderMetric.Dispose(); } } catch (Exception ex) { CoopPlugin.Log.LogDebug((object)("[perf] Unity counter unavailable: " + marker + " (" + ex.Message + ")")); } } private static void ReportPreviousFrame(int frame, double elapsedMs, IReadOnlyDictionary frameStages) { double num = ReadNanoseconds("main"); double num2 = ((num > 0.0) ? num : elapsedMs); if (num2 < 33.0) { return; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (_lastHitchLog > 0.0 && realtimeSinceStartupAsDouble - _lastHitchLog < 2.0) { _suppressedHitches++; return; } string[] array = (from pair in frameStages.OrderByDescending((KeyValuePair pair) => pair.Value.TotalTicks).Take(8) select pair.Key + "=" + TicksToMilliseconds(pair.Value.TotalTicks).ToString("F1") + "ms" + ((pair.Value.Calls > 1) ? ("x" + pair.Value.Calls) : "")).ToArray(); List list = new List { "frame=" + frame, "frame-time=" + num2.ToString("F1") + "ms" }; if (num > 0.0 && Math.Abs(num - elapsedMs) > 0.1) { list.Add("wall=" + elapsedMs.ToString("F1") + "ms"); } AppendMetric(list, "gc-alloc"); AppendMetric(list, "gc-used"); AppendMetric(list, "draw-calls"); AppendMetric(list, "batches"); AppendMetric(list, "physics"); if (array.Length != 0) { list.Add("stages=[" + string.Join(",", array) + "]"); } if (_suppressedHitches > 0) { list.Add("suppressed=" + _suppressedHitches); } _lastHitchLog = realtimeSinceStartupAsDouble; _suppressedHitches = 0; CoopPlugin.Log.LogWarning((object)("[perf-frame] " + string.Join(" ", list))); } private static void AppendMetric(ICollection details, string name) { RecorderMetric recorderMetric = Recorders.FirstOrDefault((RecorderMetric candidate) => candidate.Name == name); if (recorderMetric != null && recorderMetric.Valid) { long lastValue = ((ProfilerRecorder)(ref recorderMetric.Recorder)).LastValue; if (recorderMetric.Nanoseconds) { details.Add(name + "=" + ((double)lastValue / 1000000.0).ToString("F1") + "ms"); } else if (recorderMetric.Bytes) { details.Add(name + "=" + FormatBytes(lastValue)); } else { details.Add(name + "=" + lastValue); } } } private static double ReadNanoseconds(string name) { RecorderMetric recorderMetric = Recorders.FirstOrDefault((RecorderMetric candidate) => candidate.Name == name); if (recorderMetric != null && recorderMetric.Valid) { return (double)((ProfilerRecorder)(ref recorderMetric.Recorder)).LastValue / 1000000.0; } return 0.0; } private static string FormatBytes(long bytes) { if (Math.Abs(bytes) >= 1048576) { return ((double)bytes / 1048576.0).ToString("F1") + "MiB"; } if (Math.Abs(bytes) >= 1024) { return ((double)bytes / 1024.0).ToString("F1") + "KiB"; } return bytes + "B"; } private static double TicksToMilliseconds(long ticks) { return (double)ticks * 1000.0 / (double)Stopwatch.Frequency; } private static FrameSlot FindFrameSlot(int frame) { for (int i = 0; i < FrameSlots.Length; i++) { if (FrameSlots[i].Frame == frame) { return FrameSlots[i]; } } return null; } private static FrameSlot GetFrameSlot(int frame) { FrameSlot frameSlot = FindFrameSlot(frame); if (frameSlot != null) { return frameSlot; } FrameSlot obj = ((FrameSlots[0].Frame <= FrameSlots[1].Frame) ? FrameSlots[0] : FrameSlots[1]); obj.Reset(frame); return obj; } private static void ClearFrameSlots() { for (int i = 0; i < FrameSlots.Length; i++) { FrameSlots[i].Clear(); } } private static void StopRecorders() { if (_recordersStarted) { for (int i = 0; i < Recorders.Count; i++) { Recorders[i].Dispose(); } Recorders.Clear(); _recordersStarted = false; UpdateProfiler.Uninstall(); } } } internal static class ReflectionSurface { private static readonly HashSet Reported = new HashSet(); private static readonly object Gate = new object(); internal static Type OptionalType(string name, string assembly) { Type type = ModParity.ResolveType(name, assembly); if (type == null) { Missing("type", null, name + ", " + assembly, required: false); } return type; } private static void Missing(string kind, Type type, string name, bool required) { string item = (required ? "required" : "optional") + ":" + kind + ":" + ((type == null) ? "" : type.FullName) + "." + name; bool flag; lock (Gate) { flag = Reported.Add(item); } if (!flag) { return; } string text = "coop: reflection surface changed: " + kind + " " + ((type == null) ? "" : type.FullName) + "." + name + (required ? " (required)" : " (optional; feature disabled)"); if (required) { if (CoopPlugin.Log != null) { CoopPlugin.Log.LogError((object)text); } throw new MissingMemberException(text); } if (CoopPlugin.Log != null) { CoopPlugin.Log.LogWarning((object)text); } } internal static FieldInfo RequiredField(Type type, string name) { FieldInfo? obj = ((type == null) ? null : type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); if (obj == null) { Missing("field", type, name, required: true); } return obj; } internal static MethodInfo RequiredMethod(Type type, string name, params Type[] args) { MethodInfo methodInfo = ResolveMethod(type, name, args); if (methodInfo == null) { Missing("method", type, name, required: true); } return methodInfo; } internal static PropertyInfo OptionalProperty(Type type, string name) { PropertyInfo? obj = ((type == null) ? null : type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); if (obj == null) { Missing("property", type, name, required: false); } return obj; } internal static FieldInfo OptionalField(Type type, string name) { FieldInfo? obj = ((type == null) ? null : type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); if (obj == null) { Missing("field", type, name, required: false); } return obj; } internal static MethodInfo OptionalMethod(Type type, string name, params Type[] args) { MethodInfo methodInfo = ResolveMethod(type, name, args); if (methodInfo == null) { Missing("method", type, name, required: false); } return methodInfo; } private static MethodInfo ResolveMethod(Type type, string name, Type[] args) { if (type == null) { return null; } if (args == null || args.Length == 0) { MethodInfo methodInfo = null; MethodInfo methodInfo2 = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo3 in methods) { if (!(methodInfo3.Name != name)) { if (methodInfo == null) { methodInfo = methodInfo3; } if (methodInfo3.GetParameters().Length == 0) { methodInfo2 = methodInfo3; break; } } } return methodInfo2 ?? methodInfo; } return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null); } } internal static class ScreenSingletonProbe { private sealed class SingletonFields { private readonly FieldInfo _instance; private readonly FieldInfo _isSet; internal FieldInfo Instance => _instance; internal FieldInfo IsSet => _isSet; internal SingletonFields(FieldInfo instance, FieldInfo isSet) { _instance = instance; _isSet = isSet; } } private static readonly Type[] ScreenTypes = new Type[4] { typeof(LoadingScreen), typeof(SaveLoadGameSlotSelectScreen), typeof(SettingScreen), typeof(PauseScreen) }; private static readonly Dictionary CachedFields = new Dictionary(); private static readonly HashSet LatchesReported = new HashSet(); internal static void Install() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0071: Expected O, but got Unknown Harmony val = new Harmony("dev.meepen.cardshopcoop.screen-singletons"); TryPatch(val, typeof(CGameManager), "OnLevelFinishedLoading", "ReportLevelFinishedLoading"); TryPatch(val, typeof(LoadingScreen), "CloseScreen", "ReportLoadingScreenClose"); TryPatch(val, typeof(PauseScreen), "CloseScreen", "ReportPauseScreenClose"); TryPatch(val, typeof(SaveLoadGameSlotSelectScreen), "OpenScreen", "ReportSaveScreenOpen"); } private static void ReportLevelFinishedLoading() { Report("level finished loading"); } private static void ReportLoadingScreenClose() { Report("loading screen close"); } private static void ReportPauseScreenClose() { Report("pause menu close"); } private static void ReportSaveScreenOpen() { Report("save/load screen open"); } private static void Report(string site) { List list = new List(ScreenTypes.Length); for (int i = 0; i < ScreenTypes.Length; i++) { list.Add(Describe(ScreenTypes[i])); } CoopPlugin.Log.LogInfo((object)("[screens] " + site + ": " + string.Join(" ", list))); } private static string Describe(Type screenType) { SingletonFields fields = GetFields(screenType); object? obj = fields.Instance?.GetValue(null); Component val = (Component)((obj is Component) ? obj : null); bool? flag = ((fields.IsSet == null) ? ((bool?)null) : new bool?((bool)fields.IsSet.GetValue(null))); Object obj2 = Object.FindObjectOfType(screenType); Component val2 = (Component)(object)((obj2 is Component) ? obj2 : null); if (flag == true && (Object)(object)val == (Object)null && LatchesReported.Add(screenType.Name)) { CoopPlugin.Log.LogWarning((object)("[screens] " + screenType.Name + " is latched to a destroyed object: CSingleton.isSet is still true while the native screen is gone, so the game keeps reading the dead scene object. CardShopCoopCommunity does not modify game state.")); } string text = ((!((Object)(object)val == (Object)null)) ? ((Object)val.gameObject).name : ((flag == true) ? "destroyed" : "unresolved")); string text2 = (((Object)(object)val2 == (Object)null) ? "none" : ((Object)val2.gameObject).name); return screenType.Name + "[isSet=" + ((!flag.HasValue) ? "n/a" : (flag.Value ? "true" : "false")) + " instance=" + text + " live=" + text2 + "]"; } private static SingletonFields GetFields(Type screenType) { if (CachedFields.TryGetValue(screenType, out var value)) { return value; } Type type = typeof(CSingleton<>).MakeGenericType(screenType); value = new SingletonFields(type.GetField("instance", BindingFlags.Static | BindingFlags.NonPublic), type.GetField("isSet", BindingFlags.Static | BindingFlags.NonPublic)); CachedFields.Add(screenType, value); return value; } private static void TryPatch(Harmony harmony, Type type, string method, string prefixName) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("[screens] probe target missing: " + type.Name + "." + method)); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ScreenSingletonProbe), prefixName, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("[screens] probe patch failed for " + type.Name + "." + method + ": " + ex.Message)); } } } internal static class UnityCompat { private static readonly Func _velocityGetter; private static readonly Action _velocitySetter; private static readonly PropertyInfo _textWrapping; private static readonly object _wrapEnabled; private static readonly object _wrapDisabled; private static readonly PropertyInfo _legacyWordWrapping; static UnityCompat() { PropertyInfo? obj = typeof(Rigidbody).GetProperty("linearVelocity") ?? typeof(Rigidbody).GetProperty("velocity"); if (obj == null) { throw new MissingMemberException(typeof(Rigidbody).FullName, "linearVelocity/velocity"); } _velocityGetter = (Func)obj.GetGetMethod().CreateDelegate(typeof(Func)); _velocitySetter = (Action)obj.GetSetMethod().CreateDelegate(typeof(Action)); _textWrapping = typeof(TMP_Text).GetProperty("textWrappingMode"); if (_textWrapping != null) { _wrapEnabled = Enum.Parse(_textWrapping.PropertyType, "Normal"); _wrapDisabled = Enum.Parse(_textWrapping.PropertyType, "NoWrap"); return; } _legacyWordWrapping = typeof(TMP_Text).GetProperty("enableWordWrapping"); if (!(_legacyWordWrapping == null)) { return; } throw new MissingMemberException(typeof(TMP_Text).FullName, "textWrappingMode/enableWordWrapping"); } internal static Vector3 Velocity(Rigidbody body) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return _velocityGetter(body); } internal static void SetVelocity(Rigidbody body, Vector3 velocity) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) _velocitySetter(body, velocity); } internal static void SetWordWrapping(TMP_Text text, bool enabled) { if (_textWrapping != null) { _textWrapping.SetValue(text, enabled ? _wrapEnabled : _wrapDisabled); } else { _legacyWordWrapping.SetValue(text, enabled); } } } internal static class UpdateProfiler { private sealed class Stat { internal long Calls; internal long Ticks; internal long MaxTicks; } private const string HarmonyId = "dev.meepen.cardshopcoop.perf.update-profiler"; private static readonly string[] CallbackPhases = new string[2] { "Update", "LateUpdate" }; private static readonly Dictionary Stats = new Dictionary(); private static readonly object StatsLock = new object(); private static Harmony _harmony; private static bool _installed; internal static void Install() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown if (_installed || !PerfProbe.Enabled) { return; } _installed = true; _harmony = new Harmony("dev.meepen.cardshopcoop.perf.update-profiler"); HarmonyMethod prefix = new HarmonyMethod(AccessTools.Method(typeof(UpdateProfiler), "Prefix", (Type[])null, (Type[])null)); HarmonyMethod postfix = new HarmonyMethod(AccessTools.Method(typeof(UpdateProfiler), "Postfix", (Type[])null, (Type[])null)); int num = 0; int num2 = 0; foreach (Type item in SafeGetTypes()) { if (typeof(MonoBehaviour).IsAssignableFrom(item) && !item.IsAbstract) { string[] callbackPhases = CallbackPhases; foreach (string name in callbackPhases) { MethodInfo method = item.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (!(method == null) && !method.IsAbstract && TryPatch(method, prefix, postfix)) { num++; } } } MethodInfo[] methods = item.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo method2 in methods) { if (IsPatchBody(method2) && TryPatch(method2, prefix, postfix)) { num2++; } } } CoopPlugin.Log.LogInfo((object)("[perf-update] profiling " + num + " Update/LateUpdate + " + num2 + " Harmony patch method(s)")); } private static bool TryPatch(MethodBase method, HarmonyMethod prefix, HarmonyMethod postfix) { try { _harmony.Patch(method, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("[perf-update] could not profile " + Label(method) + ": " + ex.Message)); return false; } } private static bool IsPatchBody(MethodInfo method) { if (!method.IsDefined(typeof(HarmonyPrefix), inherit: false) && !method.IsDefined(typeof(HarmonyPostfix), inherit: false)) { return method.IsDefined(typeof(HarmonyTranspiler), inherit: false); } return true; } internal static void Uninstall() { if (!_installed) { return; } _installed = false; try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception e) { Swallow.Log(e, "Uninstall", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Util/UpdateProfiler.cs", 125); } _harmony = null; lock (StatsLock) { Stats.Clear(); } } internal static void Reset() { lock (StatsLock) { Stats.Clear(); } } private static void Prefix(out long __state) { __state = (PerfProbe.Enabled ? Stopwatch.GetTimestamp() : 0); } private static void Postfix(long __state, MethodBase __originalMethod) { if (__state == 0L) { return; } string key = Label(__originalMethod); long num = Stopwatch.GetTimestamp() - __state; lock (StatsLock) { if (!Stats.TryGetValue(key, out var value)) { value = new Stat(); Stats[key] = value; } value.Calls++; value.Ticks += num; if (num > value.MaxTicks) { value.MaxTicks = num; } } } internal static void Flush(double seconds) { string text; lock (StatsLock) { if (Stats.Count == 0) { return; } long num = 0L; List list = new List(Stats.Count); foreach (KeyValuePair item in Stats.OrderByDescending((KeyValuePair pair) => pair.Value.Ticks)) { Stat value = item.Value; num += value.Ticks; list.Add(item.Key + "=" + TicksToMs(value.Ticks / Math.Max(1L, value.Calls)).ToString("F3") + "ms avg/" + TicksToMs(value.MaxTicks).ToString("F2") + "ms max x" + value.Calls); } Stats.Clear(); text = "[perf-update] total=" + TicksToMs(num).ToString("F2") + "ms over " + seconds.ToString("F1") + "s: [" + string.Join(", ", list) + "]"; } CoopPlugin.Log.LogInfo((object)text); } private static double TicksToMs(long ticks) { return (double)ticks * 1000.0 / (double)Stopwatch.Frequency; } private static string Label(MethodBase method) { List list = new List(); Type declaringType = method.DeclaringType; while (declaringType != null) { list.Add(declaringType.Name); declaringType = declaringType.DeclaringType; } list.Reverse(); list.Add(method.Name); return string.Join(".", list); } private static IEnumerable SafeGetTypes() { try { return typeof(UpdateProfiler).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { List list = new List(); Type[] types = ex.Types; foreach (Type type in types) { if (type != null) { list.Add(type); } } return list; } } } } namespace CardShopCoop.UI { public static class CoopTheme { public static readonly Color Panel = Rgb(27, 29, 33, 0.98f); public static readonly Color PanelBorder = Rgb(62, 67, 75); public static readonly Color HeaderBg = Rgb(31, 52, 55); public static readonly Color HeaderText = Rgb(232, 236, 239); public static readonly Color Text = Rgb(229, 233, 236); public static readonly Color TextDim = Rgb(156, 164, 171); public static readonly Color SectionBg = Rgb(37, 40, 45, 0.96f); public static readonly Color SectionBorder = Rgb(58, 63, 70); public static readonly Color Primary = Rgb(62, 158, 140); public static readonly Color PrimaryHover = Rgb(71, 178, 158); public static readonly Color PrimaryActive = Rgb(54, 139, 123); public static readonly Color Secondary = Rgb(48, 52, 58); public static readonly Color SecondaryHover = Rgb(59, 64, 71); public static readonly Color SecondaryBorder = Rgb(74, 80, 88); public static readonly Color Danger = Rgb(218, 91, 78); public static readonly Color Warn = Rgb(226, 165, 72); public static readonly Color Success = Rgb(88, 190, 109); public static readonly Color DividerCol = Rgb(62, 67, 75); public static readonly Color FieldBg = Rgb(46, 50, 56); public static readonly Color FieldBorder = Rgb(72, 78, 86); public static readonly Color HudBg = Rgb(31, 29, 26, 0.78f); public static GUIStyle Window; public static GUIStyle HeaderStrip; public static GUIStyle Header; public static GUIStyle HeaderVersion; public static GUIStyle SectionHeader; public static GUIStyle Label; public static GUIStyle LabelDim; public static GUIStyle LabelDimMiddle; public static GUIStyle LabelDimWrap; public static GUIStyle LabelBold; public static GUIStyle LabelWrap; public static GUIStyle LabelDanger; public static GUIStyle LabelWarn; public static GUIStyle SectionBox; public static GUIStyle ContentPanel; public static GUIStyle ScrollView; public static GUIStyle ScrollContent; public static GUIStyle Toggle; public static GUIStyle Tab; public static GUIStyle TabSelected; public static GUIStyle ButtonPrimary; public static GUIStyle ButtonSecondary; public static GUIStyle ButtonDanger; public static GUIStyle TextField; public static GUIStyle ChipSuccess; public static GUIStyle ChipWarn; public static GUIStyle ChipDanger; public static GUIStyle ChipInfo; public static GUIStyle HudPill; public static GUIStyle HudPillBig; public static GUIStyle RowEven; public static GUIStyle RowOdd; private static Texture2D _panelTex; private static Texture2D _headerTex; private static Texture2D _shadowTex; private static Texture2D _fieldTex; private static Texture2D _fieldFocusTex; private static Texture2D _sectionTex; private static Texture2D _contentTex; private static Texture2D _tabTex; private static Texture2D _tabHoverTex; private static Texture2D _tabSelectedTex; private static Texture2D _dotOffTex; private static Texture2D _dotWaitTex; private static Texture2D _dotOkTex; private static Texture2D _dividerTex; private static Texture2D _rowEvenTex; private static Texture2D _rowOddTex; private static Texture2D _hudTex; private static Texture2D _primaryTex; private static Texture2D _primaryHoverTex; private static Texture2D _primaryActiveTex; private static Texture2D _secondaryTex; private static Texture2D _secondaryHoverTex; private static Texture2D _secondaryActiveTex; private static Texture2D _dangerTex; private static Texture2D _dangerHoverTex; private static Texture2D _dangerActiveTex; private static Texture2D _chipSuccessTex; private static Texture2D _chipWarnTex; private static Texture2D _chipDangerTex; private static Texture2D _chipInfoTex; private static bool _built; private const int WinTex = 28; private const int WinRadius = 10; private const int CtlTex = 16; private const int CtlRadius = 6; private const int SecTex = 20; private const int SecRadius = 8; private const int PillTex = 20; private const int PillRadius = 8; private const int HudTexSz = 32; private const int HudRadius = 14; private static GUIStyle _shadowStyle; private static GUIContent _titleGc; private static GUIContent _versionGc; public static void EnsureBuilt() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0203: 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) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_036a: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Unknown result type (might be due to invalid IL or missing references) //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_03db: Expected O, but got Unknown //IL_03db: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Expected O, but got Unknown //IL_03f3: Expected O, but got Unknown //IL_0475: Unknown result type (might be due to invalid IL or missing references) //IL_0489: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_04aa: Expected O, but got Unknown //IL_04af: Expected O, but got Unknown //IL_04cd: Unknown result type (might be due to invalid IL or missing references) //IL_04d2: Unknown result type (might be due to invalid IL or missing references) //IL_04d9: Unknown result type (might be due to invalid IL or missing references) //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Unknown result type (might be due to invalid IL or missing references) //IL_04f4: Expected O, but got Unknown //IL_04fe: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0517: Unknown result type (might be due to invalid IL or missing references) //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_0526: Unknown result type (might be due to invalid IL or missing references) //IL_0532: Expected O, but got Unknown //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_0546: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_055f: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_056d: Unknown result type (might be due to invalid IL or missing references) //IL_0575: Unknown result type (might be due to invalid IL or missing references) //IL_057a: Unknown result type (might be due to invalid IL or missing references) //IL_0584: Expected O, but got Unknown //IL_0589: Expected O, but got Unknown //IL_0593: Unknown result type (might be due to invalid IL or missing references) //IL_05a7: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05c0: Expected O, but got Unknown //IL_05ca: Unknown result type (might be due to invalid IL or missing references) //IL_05de: Unknown result type (might be due to invalid IL or missing references) //IL_05e3: Unknown result type (might be due to invalid IL or missing references) //IL_05ea: Unknown result type (might be due to invalid IL or missing references) //IL_05f7: Expected O, but got Unknown //IL_0601: Unknown result type (might be due to invalid IL or missing references) //IL_0610: Unknown result type (might be due to invalid IL or missing references) //IL_0615: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Expected O, but got Unknown //IL_062b: Unknown result type (might be due to invalid IL or missing references) //IL_0630: Unknown result type (might be due to invalid IL or missing references) //IL_0637: Unknown result type (might be due to invalid IL or missing references) //IL_063f: Unknown result type (might be due to invalid IL or missing references) //IL_064b: Expected O, but got Unknown //IL_0655: Unknown result type (might be due to invalid IL or missing references) //IL_0669: Unknown result type (might be due to invalid IL or missing references) //IL_066e: Unknown result type (might be due to invalid IL or missing references) //IL_0675: Unknown result type (might be due to invalid IL or missing references) //IL_067d: Unknown result type (might be due to invalid IL or missing references) //IL_0689: Expected O, but got Unknown //IL_0693: Unknown result type (might be due to invalid IL or missing references) //IL_06a7: Unknown result type (might be due to invalid IL or missing references) //IL_06ac: Unknown result type (might be due to invalid IL or missing references) //IL_06b3: Unknown result type (might be due to invalid IL or missing references) //IL_06bb: Unknown result type (might be due to invalid IL or missing references) //IL_06c7: Expected O, but got Unknown //IL_06d1: Unknown result type (might be due to invalid IL or missing references) //IL_06e5: Unknown result type (might be due to invalid IL or missing references) //IL_06ea: Unknown result type (might be due to invalid IL or missing references) //IL_06f1: Unknown result type (might be due to invalid IL or missing references) //IL_06f9: Unknown result type (might be due to invalid IL or missing references) //IL_0705: Expected O, but got Unknown //IL_070f: Unknown result type (might be due to invalid IL or missing references) //IL_0723: Unknown result type (might be due to invalid IL or missing references) //IL_0728: Unknown result type (might be due to invalid IL or missing references) //IL_072f: Unknown result type (might be due to invalid IL or missing references) //IL_0737: Unknown result type (might be due to invalid IL or missing references) //IL_0743: Expected O, but got Unknown //IL_074d: Unknown result type (might be due to invalid IL or missing references) //IL_0761: Unknown result type (might be due to invalid IL or missing references) //IL_0766: Unknown result type (might be due to invalid IL or missing references) //IL_076b: Unknown result type (might be due to invalid IL or missing references) //IL_0775: Expected O, but got Unknown //IL_0775: Unknown result type (might be due to invalid IL or missing references) //IL_077e: Unknown result type (might be due to invalid IL or missing references) //IL_0788: Expected O, but got Unknown //IL_0788: Unknown result type (might be due to invalid IL or missing references) //IL_078d: Unknown result type (might be due to invalid IL or missing references) //IL_0797: Expected O, but got Unknown //IL_079c: Expected O, but got Unknown //IL_07ba: Unknown result type (might be due to invalid IL or missing references) //IL_07bf: Unknown result type (might be due to invalid IL or missing references) //IL_07c4: Unknown result type (might be due to invalid IL or missing references) //IL_07ce: Expected O, but got Unknown //IL_07ce: Unknown result type (might be due to invalid IL or missing references) //IL_07d7: Unknown result type (might be due to invalid IL or missing references) //IL_07e1: Expected O, but got Unknown //IL_07e1: Unknown result type (might be due to invalid IL or missing references) //IL_07e6: Unknown result type (might be due to invalid IL or missing references) //IL_07f0: Expected O, but got Unknown //IL_07f5: Expected O, but got Unknown //IL_0813: Unknown result type (might be due to invalid IL or missing references) //IL_0818: Unknown result type (might be due to invalid IL or missing references) //IL_081d: Unknown result type (might be due to invalid IL or missing references) //IL_0827: Expected O, but got Unknown //IL_0827: Unknown result type (might be due to invalid IL or missing references) //IL_082c: Unknown result type (might be due to invalid IL or missing references) //IL_0836: Expected O, but got Unknown //IL_083b: Expected O, but got Unknown //IL_083b: Unknown result type (might be due to invalid IL or missing references) //IL_0840: Unknown result type (might be due to invalid IL or missing references) //IL_0845: Unknown result type (might be due to invalid IL or missing references) //IL_084f: Expected O, but got Unknown //IL_084f: Unknown result type (might be due to invalid IL or missing references) //IL_0854: Unknown result type (might be due to invalid IL or missing references) //IL_085e: Expected O, but got Unknown //IL_0863: Expected O, but got Unknown //IL_086d: Unknown result type (might be due to invalid IL or missing references) //IL_0886: Unknown result type (might be due to invalid IL or missing references) //IL_089f: Unknown result type (might be due to invalid IL or missing references) //IL_08a4: Unknown result type (might be due to invalid IL or missing references) //IL_08ab: Unknown result type (might be due to invalid IL or missing references) //IL_08b8: Expected O, but got Unknown //IL_08bd: Unknown result type (might be due to invalid IL or missing references) //IL_08d6: Unknown result type (might be due to invalid IL or missing references) //IL_08f4: Unknown result type (might be due to invalid IL or missing references) //IL_0912: Unknown result type (might be due to invalid IL or missing references) //IL_092b: Unknown result type (might be due to invalid IL or missing references) //IL_0930: Unknown result type (might be due to invalid IL or missing references) //IL_0937: Unknown result type (might be due to invalid IL or missing references) //IL_093f: Unknown result type (might be due to invalid IL or missing references) //IL_0946: Unknown result type (might be due to invalid IL or missing references) //IL_094b: Unknown result type (might be due to invalid IL or missing references) //IL_0955: Expected O, but got Unknown //IL_0955: Unknown result type (might be due to invalid IL or missing references) //IL_095a: Unknown result type (might be due to invalid IL or missing references) //IL_0964: Expected O, but got Unknown //IL_0969: Expected O, but got Unknown //IL_09aa: Unknown result type (might be due to invalid IL or missing references) //IL_09b9: Unknown result type (might be due to invalid IL or missing references) //IL_09cd: Unknown result type (might be due to invalid IL or missing references) //IL_09d7: Unknown result type (might be due to invalid IL or missing references) //IL_09eb: Unknown result type (might be due to invalid IL or missing references) //IL_09ff: Unknown result type (might be due to invalid IL or missing references) //IL_0a0e: Unknown result type (might be due to invalid IL or missing references) //IL_0a13: Unknown result type (might be due to invalid IL or missing references) //IL_0a1a: Unknown result type (might be due to invalid IL or missing references) //IL_0a21: Unknown result type (might be due to invalid IL or missing references) //IL_0a29: Unknown result type (might be due to invalid IL or missing references) //IL_0a30: Unknown result type (might be due to invalid IL or missing references) //IL_0a39: Unknown result type (might be due to invalid IL or missing references) //IL_0a43: Expected O, but got Unknown //IL_0a43: Unknown result type (might be due to invalid IL or missing references) //IL_0a4c: Unknown result type (might be due to invalid IL or missing references) //IL_0a56: Expected O, but got Unknown //IL_0a5b: Expected O, but got Unknown //IL_0a79: Unknown result type (might be due to invalid IL or missing references) //IL_0a88: Unknown result type (might be due to invalid IL or missing references) //IL_0a8d: Unknown result type (might be due to invalid IL or missing references) //IL_0a95: Unknown result type (might be due to invalid IL or missing references) //IL_0a9c: Unknown result type (might be due to invalid IL or missing references) //IL_0aa5: Unknown result type (might be due to invalid IL or missing references) //IL_0aaf: Expected O, but got Unknown //IL_0ab4: Expected O, but got Unknown //IL_0ad2: Unknown result type (might be due to invalid IL or missing references) if (!_built) { _built = true; _panelTex = MakeRounded(28, 28, 10, Panel, PanelBorder, 1); _headerTex = MakeRoundedTop(28, 28, 10, HeaderBg); _shadowTex = MakeShadow(40, 40, 12, 0.35f, 4f); _fieldTex = MakeRounded(16, 16, 6, FieldBg, FieldBorder, 1); _fieldFocusTex = MakeRounded(16, 16, 6, FieldBg, Primary, 1); _sectionTex = MakeRounded(20, 20, 8, SectionBg, SectionBorder, 1); _contentTex = MakeRoundedBottom(20, 20, 8, Panel, PanelBorder, 1); _tabTex = MakeRoundedTop(20, 20, 8, Secondary); _tabHoverTex = MakeRoundedTop(20, 20, 8, SecondaryHover); _tabSelectedTex = MakeRoundedTop(20, 20, 8, Primary); _dotOffTex = MakeRounded(16, 16, 8, TextDim, TextDim, 0); _dotWaitTex = MakeRounded(16, 16, 8, Warn, Warn, 0); _dotOkTex = MakeRounded(16, 16, 8, Success, Success, 0); _dividerTex = Solid(DividerCol); _rowEvenTex = Solid(Rgb(255, 255, 255, 0.28f)); _rowOddTex = Solid(Rgb(120, 110, 90, 0.1f)); _hudTex = MakeRounded(32, 32, 14, HudBg, HudBg, 0); _primaryTex = MakeRounded(16, 16, 6, Primary, Mul(Primary, 0.85f), 1); _primaryHoverTex = MakeRounded(16, 16, 6, PrimaryHover, Mul(PrimaryHover, 0.85f), 1); _primaryActiveTex = MakeRounded(16, 16, 6, PrimaryActive, Mul(PrimaryActive, 0.85f), 1); _secondaryTex = MakeRounded(16, 16, 6, Secondary, SecondaryBorder, 1); _secondaryHoverTex = MakeRounded(16, 16, 6, SecondaryHover, SecondaryBorder, 1); _secondaryActiveTex = MakeRounded(16, 16, 6, Mul(Secondary, 0.94f), SecondaryBorder, 1); _dangerTex = MakeRounded(16, 16, 6, Danger, Mul(Danger, 0.85f), 1); _dangerHoverTex = MakeRounded(16, 16, 6, Lerp(Danger, Color.white, 0.1f), Mul(Danger, 0.85f), 1); _dangerActiveTex = MakeRounded(16, 16, 6, Mul(Danger, 0.88f), Mul(Danger, 0.8f), 1); _chipSuccessTex = MakeRounded(20, 20, 8, WithA(Success, 0.18f), WithA(Success, 0.55f), 1); _chipWarnTex = MakeRounded(20, 20, 8, WithA(Warn, 0.18f), WithA(Warn, 0.55f), 1); _chipDangerTex = MakeRounded(20, 20, 8, WithA(Danger, 0.18f), WithA(Danger, 0.55f), 1); _chipInfoTex = MakeRounded(20, 20, 8, WithA(Primary, 0.18f), WithA(Primary, 0.55f), 1); Window = new GUIStyle(GUI.skin.window) { richText = true, padding = new RectOffset(14, 14, 40, 12), border = new RectOffset(10, 10, 10, 10) }; Window.normal.background = _panelTex; Window.onNormal.background = _panelTex; Window.focused.background = _panelTex; Window.onFocused.background = _panelTex; Window.hover.background = _panelTex; Window.active.background = _panelTex; Window.normal.textColor = Clear(); Window.onNormal.textColor = Clear(); HeaderStrip = new GUIStyle { border = new RectOffset(10, 10, 10, 3) }; HeaderStrip.normal.background = _headerTex; Header = new GUIStyle(GUI.skin.label) { richText = true, fontStyle = (FontStyle)1, fontSize = 15, alignment = (TextAnchor)3 }; Header.normal.textColor = HeaderText; HeaderVersion = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 11, alignment = (TextAnchor)5 }; HeaderVersion.normal.textColor = WithA(HeaderText, 0.8f); SectionHeader = new GUIStyle(GUI.skin.label) { richText = true, fontStyle = (FontStyle)1, fontSize = 12, margin = new RectOffset(0, 0, 0, 4) }; SectionHeader.normal.textColor = PrimaryHover; Label = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 12 }; Label.normal.textColor = Text; LabelDim = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 11 }; LabelDim.normal.textColor = TextDim; LabelDimMiddle = new GUIStyle(LabelDim) { alignment = (TextAnchor)3 }; LabelBold = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 12, fontStyle = (FontStyle)1 }; LabelBold.normal.textColor = Text; LabelWrap = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 12, wordWrap = true }; LabelWrap.normal.textColor = Text; LabelDimWrap = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 11, wordWrap = true }; LabelDimWrap.normal.textColor = TextDim; LabelDanger = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 12, wordWrap = true }; LabelDanger.normal.textColor = Danger; LabelWarn = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 12, wordWrap = true }; LabelWarn.normal.textColor = Warn; SectionBox = new GUIStyle(GUI.skin.box) { border = new RectOffset(8, 8, 8, 8), padding = new RectOffset(10, 10, 10, 10), margin = new RectOffset(0, 0, 4, 4) }; SectionBox.normal.background = _sectionTex; ContentPanel = new GUIStyle(GUI.skin.box) { border = new RectOffset(8, 8, 8, 8), padding = new RectOffset(10, 10, 10, 10), margin = new RectOffset(0, 0, 0, 4) }; ContentPanel.normal.background = _contentTex; ScrollView = new GUIStyle(GUI.skin.scrollView) { padding = new RectOffset(0, 0, 0, 0), margin = new RectOffset(0, 0, 0, 0) }; ScrollContent = new GUIStyle { padding = new RectOffset(0, 0, 0, 0), margin = new RectOffset(0, 0, 0, 0) }; Tab = MakeTab(_tabTex, _tabHoverTex, TextDim); TabSelected = MakeTab(_tabSelectedTex, _tabSelectedTex, Color.white); Toggle = new GUIStyle(GUI.skin.toggle) { richText = true, fontSize = 12 }; SetTextColorAllStates(Toggle, Text); ButtonPrimary = MakeButton(_primaryTex, _primaryHoverTex, _primaryActiveTex, Color.white); ButtonSecondary = MakeButton(_secondaryTex, _secondaryHoverTex, _secondaryActiveTex, Text); ButtonDanger = MakeButton(_dangerTex, _dangerHoverTex, _dangerActiveTex, Color.white); TextField = new GUIStyle(GUI.skin.textField) { richText = false, fontSize = 12, alignment = (TextAnchor)3, padding = new RectOffset(6, 6, 4, 4), border = new RectOffset(6, 6, 6, 6) }; TextField.normal.background = _fieldTex; TextField.hover.background = _fieldTex; TextField.focused.background = _fieldFocusTex; SetTextColorAllStates(TextField, Text); ChipSuccess = MakeChip(_chipSuccessTex, Success); ChipWarn = MakeChip(_chipWarnTex, Mul(Warn, 0.92f)); ChipDanger = MakeChip(_chipDangerTex, Danger); ChipInfo = MakeChip(_chipInfoTex, PrimaryHover); HudPill = new GUIStyle { richText = true, wordWrap = true, fontSize = 13, alignment = (TextAnchor)4, border = new RectOffset(14, 14, 14, 14), padding = new RectOffset(14, 14, 9, 9) }; HudPill.normal.background = _hudTex; HudPill.normal.textColor = Color.white; HudPillBig = new GUIStyle(HudPill) { fontSize = 20, fontStyle = (FontStyle)1, padding = new RectOffset(18, 18, 10, 10) }; HudPillBig.normal.background = _hudTex; HudPillBig.normal.textColor = Color.white; RowEven = MakeRow(_rowEvenTex); RowOdd = MakeRow(_rowOddTex); } } public static void DrawWindowShadow(Rect win) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_shadowTex == (Object)null)) { GUI.Box(new Rect(((Rect)(ref win)).x + 3f, ((Rect)(ref win)).y + 4f, ((Rect)(ref win)).width, ((Rect)(ref win)).height), GUIContent.none, HeaderShadow()); } } private static GUIStyle HeaderShadow() { //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_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0024: Expected O, but got Unknown if (_shadowStyle == null) { _shadowStyle = new GUIStyle { border = new RectOffset(16, 16, 16, 16) }; _shadowStyle.normal.background = _shadowTex; } return _shadowStyle; } public static void DrawWindowChrome(Rect r, string title, string version) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) GUI.Box(new Rect(0f, 0f, ((Rect)(ref r)).width, 30f), GUIContent.none, HeaderStrip); if (_titleGc == null || _titleGc.text != title) { _titleGc = new GUIContent(title); } GUI.Label(new Rect(13f, 5f, ((Rect)(ref r)).width - 26f, 20f), _titleGc, Header); if (_versionGc == null || _versionGc.text != version) { _versionGc = new GUIContent(version); } GUI.Label(new Rect(13f, 6f, ((Rect)(ref r)).width - 52f, 18f), _versionGc, HeaderVersion); } public static void Divider() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0043: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(3f); Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(1f) }); if ((int)Event.current.type == 7) { GUI.DrawTexture(rect, (Texture)(object)_dividerTex); } GUILayout.Space(3f); } public static void Chip(string text, GUIStyle chipStyle) { GUILayout.Label(text, chipStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); } public static Vector2 PillSize(GUIStyle style, GUIContent content, float maxW) { //IL_0002: 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_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Vector2 val = style.CalcSize(content); if (val.x > maxW) { val.x = maxW; val.y = style.CalcHeight(content, maxW); } return val; } private static GUIStyle MakeButton(Texture2D bg, Texture2D hover, Texture2D active, Color textColor) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0048: 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_0057: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_0066: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_00ae: 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) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.button) { richText = true, fontSize = 12, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, fixedHeight = 26f, padding = new RectOffset(10, 10, 4, 4), margin = new RectOffset(2, 2, 3, 3), border = new RectOffset(6, 6, 6, 6) }; val.normal.background = bg; val.normal.textColor = textColor; val.hover.background = hover; val.hover.textColor = textColor; val.active.background = active; val.active.textColor = textColor; val.focused.background = bg; val.focused.textColor = textColor; val.onNormal.background = bg; val.onNormal.textColor = textColor; val.onHover.background = hover; val.onHover.textColor = textColor; val.onActive.background = active; val.onActive.textColor = textColor; val.onFocused.background = bg; val.onFocused.textColor = textColor; return val; } public static void DrawConnectionIndicator(Rect rect, int state) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) Texture2D val = ((state >= 2) ? _dotOkTex : ((state == 1) ? _dotWaitTex : _dotOffTex)); if ((Object)(object)val != (Object)null) { GUI.DrawTexture(rect, (Texture)(object)val, (ScaleMode)0, true); } } private static GUIStyle MakeTab(Texture2D bg, Texture2D hover, Color textColor) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0048: 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_0057: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_0066: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_00ae: 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) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.button) { richText = false, fontSize = 11, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, fixedHeight = 30f, padding = new RectOffset(14, 14, 4, 4), margin = new RectOffset(0, 2, 2, 0), border = new RectOffset(8, 8, 8, 0) }; val.normal.background = bg; val.normal.textColor = textColor; val.hover.background = hover; val.hover.textColor = textColor; val.active.background = hover; val.active.textColor = textColor; val.focused.background = bg; val.focused.textColor = textColor; return val; } private static GUIStyle MakeChip(Texture2D bg, Color textColor) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { richText = true, fontSize = 11, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, padding = new RectOffset(8, 8, 3, 3), margin = new RectOffset(0, 0, 2, 2), border = new RectOffset(8, 8, 8, 8) }; val.normal.background = bg; val.normal.textColor = textColor; return val; } private static GUIStyle MakeRow(Texture2D bg) { //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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown GUIStyle val = new GUIStyle { padding = new RectOffset(8, 6, 4, 4), margin = new RectOffset(0, 0, 1, 1), border = new RectOffset(2, 2, 2, 2) }; val.normal.background = bg; return val; } private static void SetTextColorAllStates(GUIStyle s, Color c) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) s.normal.textColor = c; s.hover.textColor = c; s.active.textColor = c; s.focused.textColor = c; s.onNormal.textColor = c; s.onHover.textColor = c; s.onActive.textColor = c; s.onFocused.textColor = c; } public static Texture2D MakeRounded(int w, int h, int radius, Color fill, Color border, int borderWidth) { //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) return MakeCore(w, h, radius, radius, radius, radius, fill, border, borderWidth); } private static Texture2D MakeRoundedTop(int w, int h, int radius, Color fill) { //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) return MakeCore(w, h, radius, radius, 0f, 0f, fill, fill, 0f); } private static Texture2D MakeRoundedBottom(int w, int h, int radius, Color fill, Color border, int borderWidth) { //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) return MakeCore(w, h, 0f, 0f, radius, radius, fill, border, borderWidth); } private static Texture2D MakeCore(int w, int h, float rTL, float rTR, float rBR, float rBL, Color fill, Color border, float bw) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) Color[] array = (Color[])(object)new Color[w * h]; float num = (float)w * 0.5f; float num2 = (float)h * 0.5f; Color val = default(Color); for (int i = 0; i < h; i++) { float py = (float)i + 0.5f - num2; int num3 = h - 1 - i; for (int j = 0; j < w; j++) { float num4 = SdRoundBox((float)j + 0.5f - num, py, num, num2, rTL, rTR, rBR, rBL); float num5 = Coverage(num4); float num6 = ((bw > 0f) ? Coverage(num4 + bw) : num5); float num7 = Mathf.Max(0f, num5 - num6); float num8 = num6; float num9 = fill.a * num8 + border.a * num7; if (num9 <= 0.0001f) { ((Color)(ref val))..ctor(fill.r, fill.g, fill.b, 0f); } else { float num10 = fill.r * fill.a * num8 + border.r * border.a * num7; float num11 = fill.g * fill.a * num8 + border.g * border.a * num7; float num12 = fill.b * fill.a * num8 + border.b * border.a * num7; ((Color)(ref val))..ctor(num10 / num9, num11 / num9, num12 / num9, num9); } array[num3 * w + j] = val; } } return Bake(w, h, array); } public static Texture2D MakeShadow(int w, int h, int radius, float maxAlpha, float blur) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) Color[] array = (Color[])(object)new Color[w * h]; float num = (float)w * 0.5f; float num2 = (float)h * 0.5f; float bx = num - blur; float num3 = num2 - blur; for (int i = 0; i < h; i++) { float py = (float)i + 0.5f - num2; int num4 = h - 1 - i; for (int j = 0; j < w; j++) { float x = SdRoundBox((float)j + 0.5f - num, py, bx, num3, radius, radius, radius, radius); float num5 = maxAlpha * (1f - SmoothStep01(0f - blur, blur, x)); array[num4 * w + j] = new Color(0f, 0f, 0f, num5); } } return Bake(w, h, array); } private static Texture2D Solid(Color c) { //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) return Bake(1, 1, (Color[])(object)new Color[1] { c }); } private static Texture2D Bake(int w, int h, Color[] px) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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_0018: 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_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown Texture2D val = new Texture2D(w, h, (TextureFormat)5, false) { hideFlags = (HideFlags)61, filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; val.SetPixels(px); val.Apply(false, true); return val; } private static float SdRoundBox(float px, float py, float bx, float by, float rTL, float rTR, float rBR, float rBL) { float num = ((!(px > 0f)) ? ((py > 0f) ? rBL : rTL) : ((py > 0f) ? rBR : rTR)); float num2 = Mathf.Abs(px) - bx + num; float num3 = Mathf.Abs(py) - by + num; float num4 = Mathf.Max(num2, 0f); float num5 = Mathf.Max(num3, 0f); float num6 = Mathf.Sqrt(num4 * num4 + num5 * num5); float num7 = Mathf.Min(Mathf.Max(num2, num3), 0f); return num6 + num7 - num; } private static float Coverage(float d) { return 1f - SmoothStep01(-0.5f, 0.5f, d); } private static float SmoothStep01(float edge0, float edge1, float x) { float num = Mathf.Clamp01((x - edge0) / (edge1 - edge0)); return num * num * (3f - 2f * num); } private static Color Rgb(int r, int g, int b, float a = 1f) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) return new Color((float)r / 255f, (float)g / 255f, (float)b / 255f, a); } private static Color Mul(Color c, float m) { //IL_0000: 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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) return new Color(c.r * m, c.g * m, c.b * m, c.a); } private static Color WithA(Color c, float a) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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) return new Color(c.r, c.g, c.b, a); } private static Color Lerp(Color a, Color b, float t) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return Color.Lerp(a, b, t); } private static Color Clear() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) return new Color(0f, 0f, 0f, 0f); } } public class CoopUI { private enum CoopTab { Session, Character, Settings, Hidden } public bool Visible = true; public static bool TextFieldFocused; private Rect _win = new Rect(24f, 96f, 400f, 10f); private int _fpsShown = -1; private string _versionWithFps; private string _ipField; private string _nameField; private string _lanIps; private string _lanIpsOther; private readonly CopyButton _ipCopy = new CopyButton(); private string _inviteField = ""; private readonly CopyButton _inviteCopy = new CopyButton(); private readonly CopyButton _passwordCopy = new CopyButton(); private string _lanPwField = ""; private bool _characterOpen = true; private CoopTab _tab; private Vector2 _sessionScroll; private Vector2 _characterScroll; private Vector2 _settingsScroll; private Vector2 _hiddenScroll; private bool _characterSnapshotReady; private bool _characterFemale; private readonly List _presetNames = new List(); private int _presetSelection; private readonly List _presetSelectionBuffer = new List(1) { 0 }; private int _characterSnapshotGeneration = -1; private CharacterCustomization _characterCustomizer; private readonly List> _hairOptions = new List>(); private readonly List _hairSelections = new List(); private readonly List _hairColors = new List(); private readonly List> _apparelOptions = new List>(); private readonly List> _apparelNames = new List>(); private readonly List _apparelSelections = new List(); private readonly List _apparelMaterials = new List(); private readonly List _apparelColors = new List(); private int _colorEditorKind = -1; private int _colorEditorSlot = -1; private Texture2D _svPickerTexture; private Texture2D _huePickerTexture; private float _pickerTextureHue = -1f; private int _colorDragTarget; private readonly List _floatNames = new List(); private readonly List _floatValues = new List(); private InviteState _invState; private string _invCode; private string _invReason; private string _invPassword; private int _invPortState; private bool _browserOpen; private string _searchField = ""; private int _page; private bool _publicLobby; private string _lobbyNameField = ""; private string _hostPwField = ""; private string _hostPlayersField = 4.ToString(CultureInfo.InvariantCulture); private string _joinPwField = ""; private ulong _pwPromptLobby; private const int PageSize = 6; private const float MaxWindowHeight = 720f; private const float WindowScreenMargin = 16f; private float _viewportHeight; private string _errorSeen; private string _errorText; private GUIContent _errorGc; private string _enumRestoreMsg; private string _hostTimeSeen; private string _hostTimeText; private GUIContent _hostTimeGc; private string _registerSeen; private string _registerText; private GUIContent _registerGc; private string _nextDayWaitSeen; private string _nextDayWaitText; private GUIContent _nextDayWaitGc; private string _endOfDayPendingSeen; private string _endOfDayPendingText; private GUIContent _endOfDayPendingGc; private KeyCode _toggleKeySeen; private string _toggleHint; private readonly List _adoptOffers = new List(); private static int IpRank(string ip) { if (ip.StartsWith("192.168.")) { return 0; } if (ip.StartsWith("10.")) { return 1; } if (ip.StartsWith("172.")) { return 2; } return 3; } public void Draw(CoopCore core, ICoopTransport net) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0100: 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_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: 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_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Expected O, but got Unknown //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Unknown result type (might be due to invalid IL or missing references) CoopTheme.EnsureBuilt(); if (_ipField == null) { _ipField = CoopPlugin.LastJoinIP.Value; } if (_nameField == null) { _nameField = core.EffectivePlayerName; } if (!Visible) { TextFieldFocused = false; if (core.ErrorLine.Length > 0) { if (core.ErrorLine != _errorSeen) { _errorSeen = core.ErrorLine; _errorText = "CO-OP: " + core.ErrorLine + ""; _errorGc = new GUIContent(_errorText); } Vector2 val = CoopTheme.PillSize(CoopTheme.HudPill, _errorGc, 660f); GUI.Label(new Rect(8f, (float)Screen.height - val.y - 6f, val.x, val.y), _errorGc, CoopTheme.HudPill); } } if (CoopCore.Role == CoopRole.Client && HudApi.HostTimeText.Length > 0) { if (HudApi.HostTimeText != _hostTimeSeen) { _hostTimeSeen = HudApi.HostTimeText; _hostTimeText = "" + HudApi.HostTimeText + " - co-op"; _hostTimeGc = new GUIContent(_hostTimeText); } Vector2 val2 = CoopTheme.PillSize(CoopTheme.HudPill, _hostTimeGc, 440f); GUI.Label(new Rect(((float)Screen.width - val2.x) / 2f, 4f, val2.x, val2.y), _hostTimeGc, CoopTheme.HudPill); } if (HudApi.ToastText.Length > 0) { DrawHudPill(HudApi.ToastText, "#8ef58a", (float)Screen.height * 0.63f, ref _registerSeen, ref _registerText, ref _registerGc); } if (HudApi.NextDayWaitActive && HudApi.NextDayWaitText.Length > 0) { DrawHudPill(HudApi.NextDayWaitText, "#ffd54a", (float)Screen.height * 0.7f, ref _nextDayWaitSeen, ref _nextDayWaitText, ref _nextDayWaitGc); } if (HudApi.EndOfDayPendingActive && HudApi.EndOfDayPendingText.Length > 0) { DrawHudPill(HudApi.EndOfDayPendingText, "#ffd54a", (float)Screen.height * 0.7f, ref _endOfDayPendingSeen, ref _endOfDayPendingText, ref _endOfDayPendingGc); } if (!Visible) { PresenceApi.SetCharacterPreview(active: false); return; } ((Rect)(ref _win)).width = Mathf.Min(400f, Mathf.Max(1f, (float)Screen.width - 32f)); ((Rect)(ref _win)).height = TargetWindowHeight(); ((Rect)(ref _win)).x = Mathf.Clamp(((Rect)(ref _win)).x, 0f, Mathf.Max(0f, (float)Screen.width - ((Rect)(ref _win)).width)); ((Rect)(ref _win)).y = Mathf.Clamp(((Rect)(ref _win)).y, 0f, Mathf.Max(0f, (float)Screen.height - ((Rect)(ref _win)).height)); CoopTheme.DrawWindowShadow(_win); _win = GUILayout.Window(867530, _win, (WindowFunction)delegate { WindowFn(core, net); }, "", CoopTheme.Window, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(((Rect)(ref _win)).width), GUILayout.Height(((Rect)(ref _win)).height) }); ((Rect)(ref _win)).x = Mathf.Clamp(((Rect)(ref _win)).x, 0f, Mathf.Max(0f, (float)Screen.width - ((Rect)(ref _win)).width)); ((Rect)(ref _win)).y = Mathf.Clamp(((Rect)(ref _win)).y, 0f, Mathf.Max(0f, (float)Screen.height - ((Rect)(ref _win)).height)); } private static void DrawHudPill(string source, string color, float y, ref string seen, ref string cached, ref GUIContent content) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown if (source != seen) { seen = source; cached = "" + source + ""; content = new GUIContent(cached); } Vector2 val = CoopTheme.PillSize(CoopTheme.HudPillBig, content, 740f); GUI.Label(new Rect(((float)Screen.width - val.x) / 2f, y, val.x, val.y), content, CoopTheme.HudPillBig); } private void WindowFn(CoopCore core, ICoopTransport net) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Invalid comparison between Unknown and I4 //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) CoopTheme.EnsureBuilt(); int num = Mathf.RoundToInt((float)FrameCounter.Fps); if (_versionWithFps == null || num != _fpsShown) { _fpsShown = num; _versionWithFps = "v2.0.6 " + num + " FPS"; } CoopTheme.DrawWindowChrome(new Rect(0f, 0f, ((Rect)(ref _win)).width, ((Rect)(ref _win)).height), "CardShopCoopCommunity".ToUpperInvariant(), _versionWithFps); CoopTheme.DrawConnectionIndicator(new Rect(((Rect)(ref _win)).width - 30f, 9f, 12f, 12f), GetConnectionState(core, net)); if (_tab == CoopTab.Character && CoopCore.Role == CoopRole.None) { _tab = CoopTab.Session; } if (_tab == CoopTab.Hidden && !HiddenCategoryVisible()) { _tab = CoopTab.Session; } DrawTabs(core); PresenceApi.SetCharacterPreview(_tab == CoopTab.Character && CoopCore.Role != CoopRole.None); GUILayout.BeginVertical(CoopTheme.ContentPanel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); Vector2 val = ((_tab == CoopTab.Character) ? _characterScroll : ((_tab == CoopTab.Settings) ? _settingsScroll : ((_tab == CoopTab.Hidden) ? _hiddenScroll : _sessionScroll))); val = GUILayout.BeginScrollView(val, CoopTheme.ScrollView, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.BeginVertical(CoopTheme.ScrollContent, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(_viewportHeight) }); if (core.ErrorLine.Length > 0) { GUILayout.BeginHorizontal(Array.Empty()); CoopTheme.Chip("PROBLEM", CoopTheme.ChipDanger); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Label(core.ErrorLine, CoopTheme.LabelDanger, Array.Empty()); GUILayout.Space(4f); } if (_tab == CoopTab.Character && CoopCore.Role != CoopRole.None) { DrawCharacterSelector(core); } else if (_tab == CoopTab.Settings) { DrawSettings(core); } else if (_tab == CoopTab.Hidden) { DrawHidden(core); } else { DrawSession(core, net); } GUILayout.EndVertical(); GUILayout.EndScrollView(); if ((int)Event.current.type == 7) { Rect lastRect = GUILayoutUtility.GetLastRect(); float height = ((Rect)(ref lastRect)).height; if (height > 0f && height <= ((Rect)(ref _win)).height) { _viewportHeight = height; } } if (_tab == CoopTab.Character) { _characterScroll = val; } else if (_tab == CoopTab.Settings) { _settingsScroll = val; } else if (_tab == CoopTab.Hidden) { _hiddenScroll = val; } else { _sessionScroll = val; } GUILayout.EndVertical(); FinishWindow(); } private void DrawSession(CoopCore core, ICoopTransport net) { if (_enumRestoreMsg != null) { GUILayout.BeginHorizontal(Array.Empty()); CoopTheme.Chip("CARD DATABASE", CoopTheme.ChipWarn); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Label(_enumRestoreMsg, CoopTheme.LabelWarn, Array.Empty()); GUILayout.Space(4f); } string text = CatalogApi.EnumLendState(); if (text != null) { GUILayout.BeginHorizontal(Array.Empty()); CoopTheme.Chip("CARD DATABASE", CoopTheme.ChipWarn); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Label(text, CoopTheme.LabelWarn, Array.Empty()); if (CoopCore.Role == CoopRole.None && GUILayout.Button("Restore MY card database (for solo saves - restart after)", CoopTheme.ButtonDanger, Array.Empty())) { CatalogApi.RestoreEnumBackup(out _enumRestoreMsg); } GUILayout.Space(4f); } switch (CoopCore.Role) { case CoopRole.None: DrawNone(core); break; case CoopRole.Host: DrawHost(core, net); break; case CoopRole.Client: DrawClient(core); break; } DrawGradedAdopt(core); } private void FinishWindow() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) TextFieldFocused = GUI.GetNameOfFocusedControl()?.StartsWith("coop_") ?? false; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(ToggleHintText(), CoopTheme.LabelDim, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUI.DragWindow(new Rect(0f, 0f, 10000f, 20f)); } private string ToggleHintText() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_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_0036: Unknown result type (might be due to invalid IL or missing references) KeyCode val = (KeyCode)((CoopPlugin.UiToggleKey != null) ? ((int)CoopPlugin.UiToggleKey.Value) : 0); if (_toggleHint == null || val != _toggleKeySeen) { _toggleKeySeen = val; _toggleHint = (((int)val == 0) ? "Press the toggle key to toggle this window" : $"Press {val} to toggle this window"); } return _toggleHint; } private static float TargetWindowHeight() { float num = Mathf.Max(1f, Mathf.Min(720f, (float)Screen.height - 16f)); float num2 = Mathf.Min(320f, num); return Mathf.Clamp((float)Screen.height * 0.72f, num2, num); } private void DrawTabs(CoopCore core) { GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = _tab != CoopTab.Session; if (GUILayout.Button("SESSION", (_tab == CoopTab.Session) ? CoopTheme.TabSelected : CoopTheme.Tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { _tab = CoopTab.Session; } GUI.enabled = CoopCore.Role != CoopRole.None && _tab != CoopTab.Character; if (GUILayout.Button("CHARACTER", (_tab == CoopTab.Character) ? CoopTheme.TabSelected : CoopTheme.Tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(104f) })) { _tab = CoopTab.Character; } GUI.enabled = _tab != CoopTab.Settings; if (GUILayout.Button("SETTINGS", (_tab == CoopTab.Settings) ? CoopTheme.TabSelected : CoopTheme.Tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(96f) })) { _tab = CoopTab.Settings; } if (HiddenCategoryVisible()) { GUI.enabled = _tab != CoopTab.Hidden; if (GUILayout.Button("HIDDEN", (_tab == CoopTab.Hidden) ? CoopTheme.TabSelected : CoopTheme.Tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(88f) })) { _tab = CoopTab.Hidden; } } GUI.enabled = true; GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private void DrawSettings(CoopCore core) { GUILayout.Label("APPEARANCE", CoopTheme.SectionHeader, Array.Empty()); bool flag = GUILayout.Toggle(CoopPlugin.AllowNsfw.Value, CoopPlugin.AllowNsfw.Value ? "NSFW allowed" : "NSFW hidden", CoopTheme.Toggle, Array.Empty()); if (flag != CoopPlugin.AllowNsfw.Value) { PresenceApi.SetNsfwAllowed(flag); } GUILayout.Space(8f); GUILayout.Label("JOINING", CoopTheme.SectionHeader, Array.Empty()); DrawConfigToggle(CoopPlugin.AllowCrossBuildJoin, "Let a joiner on a DIFFERENT game build in (host only - risky)"); GUILayout.Space(8f); GUILayout.Label("LOGGING", CoopTheme.SectionHeader, Array.Empty()); DrawConfigToggle(CoopPlugin.PerfDebug, "Unity hitch profiling and stage timing (PerfDebug)"); GUILayout.Space(8f); GUILayout.Label("LATENCY TESTING", CoopTheme.SectionHeader, Array.Empty()); DrawConfigIntSlider(CoopPlugin.ArtificialLagMs, 0, 2000, "Artificial lag"); DrawConfigIntSlider(CoopPlugin.ArtificialJitterMs, 0, 1000, "Jitter"); GUILayout.Space(8f); GUILayout.Label("MODULES (DIAGNOSTIC)", CoopTheme.SectionHeader, Array.Empty()); GUILayout.Label("Uncheck a module to turn it off entirely - its patches, handlers and per-frame work stop. Use this to find which module costs frame rate. Applies after rejoining (persistent modules after a restart); leave the essentials on so you can still connect.", CoopTheme.LabelDimWrap, Array.Empty()); foreach (string item in ModuleCatalog.Names()) { ConfigEntry val = ModuleCatalog.Entry(item); if (val != null) { bool flag2 = GUILayout.Toggle(val.Value, " " + item, CoopTheme.Toggle, Array.Empty()); if (flag2 != val.Value) { val.Value = flag2; } } } GUILayout.Space(8f); GUILayout.Label("Settings are saved to BepInEx/config/dev.meepen.cardshopcoop.cfg.", CoopTheme.LabelDim, Array.Empty()); } private static bool HiddenCategoryVisible() { if (CoopPlugin.ShowHiddenCategory != null) { return CoopPlugin.ShowHiddenCategory.Value; } return false; } private static void DrawHidden(CoopCore core) { DrawConfigToggle(CoopPlugin.ShowHiddenCategory, "Show hidden category"); DrawConfigToggle(CoopPlugin.EnableGameCheatMenu, "Enable game cheat menu"); } private static void DrawConfigToggle(ConfigEntry entry, string label) { if (entry != null) { bool flag = GUILayout.Toggle(entry.Value, " " + label, CoopTheme.Toggle, Array.Empty()); if (flag != entry.Value) { entry.Value = flag; } } } private static void DrawConfigIntSlider(ConfigEntry entry, int min, int max, string label) { if (entry != null) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(label, CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) }); int num = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)entry.Value, (float)min, (float)max, Array.Empty())); GUILayout.Label(num + " ms", CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) }); GUILayout.EndHorizontal(); if (num != entry.Value) { entry.Value = num; } } } private static int GetConnectionState(CoopCore core, ICoopTransport net) { if (CoopCore.Role == CoopRole.Host) { if ((net?.ConnectionCount ?? 0) <= 0) { return 1; } return 2; } if (CoopCore.Role == CoopRole.Client) { string text = core.StatusLine ?? ""; if (text.IndexOf("Connecting", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("loading", StringComparison.OrdinalIgnoreCase) < 0) { return 2; } return 1; } return 0; } private void DrawCharacterSelector(CoopCore core) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if ((int)Event.current.type == 8 && (!_characterSnapshotReady || _characterSnapshotGeneration != PresenceApi.PlayerModelGeneration)) { PresenceModelEntry model = PresenceApi.GetLocalPlayerModel(); _characterFemale = model.Female; _presetNames.Clear(); _presetNames.AddRange(PresenceApi.GetLocalPresetNames()); _presetSelection = _presetNames.FindIndex((string x) => x == (_characterFemale ? "Female" : "Male") + model.ModelIndex); if (_presetSelection < 0) { _presetSelection = 0; } _presetSelectionBuffer[0] = _presetSelection; _characterCustomizer = PresenceApi.GetLocalCustomization(); CacheWardrobeControls(); _characterSnapshotReady = true; _characterSnapshotGeneration = PresenceApi.PlayerModelGeneration; } if (!_characterSnapshotReady) { return; } GUILayout.BeginVertical(CoopTheme.SectionBox, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); CoopTheme.Chip("MY CHARACTER", CoopTheme.ChipInfo); GUILayout.FlexibleSpace(); _characterOpen = GUILayout.Toggle(_characterOpen, _characterOpen ? "â–²" : "â–¼", CoopTheme.Toggle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }); GUILayout.EndHorizontal(); if (_characterOpen) { GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = PresenceApi.CanUndoPlayerModel; if (GUILayout.Button("Undo", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { PresenceApi.UndoPlayerModel(); } GUI.enabled = PresenceApi.CanRedoPlayerModel; if (GUILayout.Button("Redo", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(58f) })) { PresenceApi.RedoPlayerModel(); } GUI.enabled = true; GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Gender", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) }); if (GUILayout.Button(_characterFemale ? "Female" : "Male", CoopTheme.ButtonSecondary, Array.Empty())) { _characterFemale = !_characterFemale; PresenceApi.SetLocalPlayerModel(_characterFemale, 0); } GUILayout.EndHorizontal(); if (_presetNames.Count > 0) { DrawCycleRow("Preset", _presetNames, _presetSelectionBuffer, 0, delegate { _presetSelection = _presetSelectionBuffer[0]; PresenceApi.ApplyLocalPreset(_presetNames[_presetSelection]); }); } else { GUILayout.Label("No presets found for this gender.", CoopTheme.LabelDimWrap, Array.Empty()); } DrawWardrobeControls(core); } GUILayout.EndVertical(); GUILayout.Space(4f); } private void CacheWardrobeControls() { //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) _hairOptions.Clear(); _hairSelections.Clear(); _hairColors.Clear(); _apparelOptions.Clear(); _apparelNames.Clear(); _apparelSelections.Clear(); _apparelMaterials.Clear(); _apparelColors.Clear(); _floatNames.Clear(); _floatValues.Clear(); if ((Object)(object)_characterCustomizer == (Object)null) { return; } CC_CharacterData storedCharacterData = _characterCustomizer.StoredCharacterData; if (storedCharacterData == null) { return; } for (int i = 0; i < _characterCustomizer.HairTables.Count; i++) { List list = new List { "None" }; scrObj_Hair val = _characterCustomizer.HairTables[i]; if ((Object)(object)val != (Object)null && val.Hairstyles != null) { foreach (Hairstyle hairstyle in val.Hairstyles) { list.Add(hairstyle.Name ?? "(unnamed)"); } } _hairOptions.Add(list); string current2 = ((i < storedCharacterData.HairNames.Count) ? storedCharacterData.HairNames[i] : ""); int num = list.FindIndex((string x) => x == current2); _hairSelections.Add((num >= 0) ? num : 0); _hairColors.Add(ReadHairColor(storedCharacterData, i)); } for (int num2 = 0; num2 < _characterCustomizer.ApparelTables.Count; num2++) { List list2 = new List(); List list3 = new List(); if (CoopPlugin.AllowNsfw.Value) { list2.Add("Nude"); list3.Add(""); } scrObj_Apparel val2 = _characterCustomizer.ApparelTables[num2]; if ((Object)(object)val2 != (Object)null && val2.Items != null) { foreach (Apparel item2 in val2.Items) { list2.Add(string.IsNullOrEmpty(item2.DisplayName) ? (item2.Name ?? "(unnamed)") : item2.DisplayName); list3.Add(item2.Name ?? ""); } } _apparelOptions.Add(list2); _apparelNames.Add(list3); string item = ((num2 < storedCharacterData.ApparelNames.Count) ? storedCharacterData.ApparelNames[num2] : ""); int num3 = list3.IndexOf(item); _apparelSelections.Add((num3 >= 0) ? num3 : 0); _apparelMaterials.Add((num2 < storedCharacterData.ApparelMaterials.Count) ? Mathf.Max(0, storedCharacterData.ApparelMaterials[num2]) : 0); _apparelColors.Add(ReadApparelTint(storedCharacterData, num2)); } if (storedCharacterData.FloatProperties == null) { return; } foreach (CC_Property floatProperty in storedCharacterData.FloatProperties) { if (floatProperty != null && !string.IsNullOrEmpty(floatProperty.propertyName)) { _floatNames.Add(floatProperty.propertyName); _floatValues.Add(floatProperty.floatValue); } } } private void DrawWardrobeControls(CoopCore core) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Expected O, but got Unknown //IL_02a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_characterCustomizer == (Object)null) { return; } if (_hairOptions.Count > 0 || _apparelOptions.Count > 0) { GUILayout.Label("WARDROBE", CoopTheme.SectionHeader, Array.Empty()); } int slot; for (slot = 0; slot < _hairOptions.Count; slot++) { DrawCycleColorRow("Hair " + (slot + 1), _hairOptions[slot], _hairSelections, slot, _hairColors[slot], 0, slot, delegate(Color color) { //IL_0016: 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) _hairColors[slot] = color; PresenceApi.SetLocalHairColor(slot, color); }, delegate { ApplyHair(core, slot); }); } int slot2; for (slot2 = 0; slot2 < _apparelOptions.Count; slot2++) { if (_apparelOptions[slot2].Count == 0) { continue; } string label = (((Object)(object)_characterCustomizer.ApparelTables[slot2] != (Object)null && !string.IsNullOrEmpty(_characterCustomizer.ApparelTables[slot2].Label)) ? _characterCustomizer.ApparelTables[slot2].Label : ("Apparel " + (slot2 + 1))); DrawCycleColorRow(label, _apparelOptions[slot2], _apparelSelections, slot2, _apparelColors[slot2], 1, slot2, delegate(Color color) { //IL_0016: 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) _apparelColors[slot2] = color; PresenceApi.SetLocalApparelTint(slot2, color); }, delegate { ApplyApparel(core, slot2); }); if (string.IsNullOrEmpty(_apparelNames[slot2][_apparelSelections[slot2]])) { continue; } scrObj_Apparel val = _characterCustomizer.ApparelTables[slot2]; int num = _apparelSelections[slot2] - (CoopPlugin.AllowNsfw.Value ? 1 : 0); int num2 = (((Object)(object)val != (Object)null && num >= 0 && num < val.Items.Count && val.Items[num].Materials != null) ? val.Items[num].Materials.Count : 0); if (num2 > 1) { DrawNumberRow("Material", num2, _apparelMaterials, slot2, delegate { ApplyApparel(core, slot2); }); } } for (int num3 = 0; num3 < _floatNames.Count; num3++) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(_floatNames[num3], CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(130f) }); float num4 = ((_floatNames[num3] == "Height" || _floatNames[num3] == "Width") ? 0.5f : 0f); float num5 = ((_floatNames[num3] == "Height" || _floatNames[num3] == "Width") ? 1.5f : 1f); float num6 = GUILayout.HorizontalSlider(_floatValues[num3], num4, num5, Array.Empty()); GUILayout.Label(num6.ToString("0.00"), CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }); GUILayout.EndHorizontal(); if (!Mathf.Approximately(num6, _floatValues[num3])) { _floatValues[num3] = num6; CC_Property val2 = new CC_Property { propertyName = _floatNames[num3], floatValue = num6 }; _characterCustomizer.setFloatProperty(val2, true); PresenceApi.CommitLocalCustomization(); } } } private void DrawCycleRow(string label, List options, List selections, int index, Action apply) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(label, CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(86f) }); if (GUILayout.Button("<", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { selections[index] = (selections[index] + options.Count - 1) % options.Count; apply(); } GUILayout.Label(options[selections[index]], CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button(">", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { selections[index] = (selections[index] + 1) % options.Count; apply(); } GUILayout.EndHorizontal(); } private void DrawCycleColorRow(string label, List options, List selections, int index, Color color, int kind, int slot, Action applyColor, Action applySelection) { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) bool flag = _colorEditorKind == kind && _colorEditorSlot == slot; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(label, CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(86f) }); if (GUILayout.Button("<", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { selections[index] = (selections[index] + options.Count - 1) % options.Count; applySelection(); } GUILayout.Label(options[selections[index]], CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button(">", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { selections[index] = (selections[index] + 1) % options.Count; applySelection(); } Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = color; if (GUILayout.Button(" ", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { _colorEditorKind = (flag ? (-1) : kind); _colorEditorSlot = (flag ? (-1) : slot); if (flag) { _colorDragTarget = 0; } } GUI.backgroundColor = backgroundColor; GUILayout.EndHorizontal(); if (flag) { float hue = default(float); float saturation = default(float); float value = default(float); Color.RGBToHSV(color, ref hue, ref saturation, ref value); DrawColorPicker(ref hue, ref saturation, ref value, applyColor); } } private void DrawColorPicker(ref float hue, ref float saturation, ref float value, Action applyColor) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: 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_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Invalid comparison between Unknown and I4 //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Invalid comparison between Unknown and I4 //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) EnsureColorPickerTextures(); if (!Mathf.Approximately(_pickerTextureHue, hue)) { RebuildSaturationValueTexture(hue); } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Color", CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(86f) }); Rect rect = GUILayoutUtility.GetRect(150f, 92f, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(150f), GUILayout.Height(92f) }); Rect rect2 = GUILayoutUtility.GetRect(18f, 92f, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(18f), GUILayout.Height(92f) }); GUI.DrawTexture(rect, (Texture)(object)_svPickerTexture, (ScaleMode)0, false); GUI.DrawTexture(rect2, (Texture)(object)_huePickerTexture, (ScaleMode)0, false); DrawPickerMarker(new Rect(((Rect)(ref rect)).x + saturation * ((Rect)(ref rect)).width - 4f, ((Rect)(ref rect)).y + (1f - value) * ((Rect)(ref rect)).height - 4f, 8f, 8f), Color.white); DrawPickerMarker(new Rect(((Rect)(ref rect2)).x - 2f, ((Rect)(ref rect2)).y + (1f - hue) * ((Rect)(ref rect2)).height - 2f, ((Rect)(ref rect2)).width + 4f, 4f), Color.black); GUILayout.EndHorizontal(); Event current = Event.current; Vector2 val = GUIUtility.GUIToScreenPoint(current.mousePosition); Vector2 val2 = GUIUtility.GUIToScreenPoint(((Rect)(ref rect)).position); Vector2 val3 = GUIUtility.GUIToScreenPoint(((Rect)(ref rect2)).position); Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(val2.x, val2.y, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height); Rect val5 = default(Rect); ((Rect)(ref val5))..ctor(val3.x, val3.y, ((Rect)(ref rect2)).width, ((Rect)(ref rect2)).height); bool flag = false; if ((int)current.type == 0) { if (((Rect)(ref val4)).Contains(val)) { _colorDragTarget = 1; saturation = Mathf.Clamp01((val.x - ((Rect)(ref val4)).x) / ((Rect)(ref val4)).width); value = Mathf.Clamp01(1f - (val.y - ((Rect)(ref val4)).y) / ((Rect)(ref val4)).height); flag = true; current.Use(); } else if (((Rect)(ref val5)).Contains(val)) { _colorDragTarget = 2; hue = Mathf.Clamp01(1f - (val.y - ((Rect)(ref val5)).y) / ((Rect)(ref val5)).height); flag = true; current.Use(); } } else if ((int)current.type == 3 && _colorDragTarget != 0) { if (_colorDragTarget == 1) { saturation = Mathf.Clamp01((val.x - ((Rect)(ref val4)).x) / ((Rect)(ref val4)).width); value = Mathf.Clamp01(1f - (val.y - ((Rect)(ref val4)).y) / ((Rect)(ref val4)).height); } else { hue = Mathf.Clamp01(1f - (val.y - ((Rect)(ref val5)).y) / ((Rect)(ref val5)).height); } flag = true; current.Use(); } else if ((int)current.type == 1) { _colorDragTarget = 0; } if (flag) { applyColor(Color.HSVToRGB(hue, saturation, value)); } } private void EnsureColorPickerTextures() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_svPickerTexture == (Object)null) { _svPickerTexture = new Texture2D(96, 64, (TextureFormat)4, false) { wrapMode = (TextureWrapMode)1 }; } if ((Object)(object)_huePickerTexture == (Object)null) { _huePickerTexture = new Texture2D(1, 96, (TextureFormat)4, false) { wrapMode = (TextureWrapMode)1 }; for (int i = 0; i < 96; i++) { _huePickerTexture.SetPixel(0, i, Color.HSVToRGB(1f - (float)i / 95f, 1f, 1f)); } _huePickerTexture.Apply(); } } private void RebuildSaturationValueTexture(float hue) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) _pickerTextureHue = hue; for (int i = 0; i < 64; i++) { for (int j = 0; j < 96; j++) { _svPickerTexture.SetPixel(j, i, Color.HSVToRGB(hue, (float)j / 95f, (float)i / 63f)); } } _svPickerTexture.Apply(); } private static void DrawPickerMarker(Rect rect, Color color) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Color color2 = GUI.color; GUI.color = color; GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture); GUI.color = color2; } private static Color ReadHairColor(CC_CharacterData data, int slot) { //IL_0043: 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) Color result = default(Color); if (data != null && data.HairColor != null && slot >= 0 && slot < data.HairColor.Count && ColorUtility.TryParseHtmlString("#" + data.HairColor[slot].stringValue, ref result)) { return result; } return Color.white; } private static Color ReadApparelTint(CC_CharacterData data, int slot) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (data != null && data.ColorProperties != null) { string text = "CardShopCoop.ApparelTint." + slot; Color result = default(Color); foreach (CC_Property colorProperty in data.ColorProperties) { if (colorProperty != null && !(colorProperty.propertyName != text) && ColorUtility.TryParseHtmlString("#" + colorProperty.stringValue, ref result)) { return result; } } } return Color.white; } private void DrawNumberRow(string label, int count, List values, int index, Action apply) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(label, CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(86f) }); if (GUILayout.Button("<", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { values[index] = (values[index] + count - 1) % count; apply(); } GUILayout.Label((values[index] + 1).ToString(), CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button(">", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { values[index] = (values[index] + 1) % count; apply(); } GUILayout.EndHorizontal(); } private void ApplyHair(CoopCore core, int slot) { if (!((Object)(object)_characterCustomizer == (Object)null)) { if (_hairSelections[slot] == 0) { PresenceApi.ClearLocalHair(slot); return; } _characterCustomizer.setHairByName(_hairOptions[slot][_hairSelections[slot]], slot); PresenceApi.CommitLocalCustomization(); } } private void ApplyApparel(CoopCore core, int slot) { if (!((Object)(object)_characterCustomizer == (Object)null)) { string text = _apparelNames[slot][_apparelSelections[slot]]; if (string.IsNullOrEmpty(text)) { PresenceApi.ClearLocalApparel(slot); return; } int num = Mathf.Max(0, _apparelMaterials[slot]); _characterCustomizer.setApparelByName(text, slot, num); PresenceApi.CommitLocalCustomization(); } } private void DrawStatusRow(CoopCore core, ICoopTransport net) { ClassifyStatus(core, net, out var chip, out var text); GUILayout.BeginHorizontal(Array.Empty()); if (chip != null) { CoopTheme.Chip(text, chip); GUILayout.Space(6f); GUILayout.Label(core.StatusLine, CoopTheme.LabelDim, Array.Empty()); } else { GUILayout.Label(core.StatusLine, CoopTheme.Label, Array.Empty()); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private static void ClassifyStatus(CoopCore core, ICoopTransport net, out GUIStyle chip, out string text) { chip = null; text = null; string hay = core.StatusLine ?? ""; switch (CoopCore.Role) { case CoopRole.Host: if ((net?.ConnectionCount ?? 0) > 0) { chip = CoopTheme.ChipSuccess; text = "HOSTING"; } else { chip = CoopTheme.ChipInfo; text = "WAITING"; } break; case CoopRole.Client: if (Has(hay, "download") || Has(hay, "loading") || Has(hay, "requesting") || Has(hay, "received") || Has(hay, "Joining") || Has(hay, "Connecting")) { chip = CoopTheme.ChipInfo; text = "CONNECTING"; } else { chip = CoopTheme.ChipSuccess; text = "CONNECTED"; } break; default: if (Has(hay, "Joining") || Has(hay, "Creating") || Has(hay, "Connecting")) { chip = CoopTheme.ChipInfo; text = "CONNECTING"; } break; } } private static bool Has(string hay, string needle) { return hay.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0; } private void DrawNone(CoopCore core) { if (_browserOpen) { DrawBrowser(core); return; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Your name:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) }); if (core.UsingSteamPersona) { GUILayout.Label(core.EffectivePlayerName, CoopTheme.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) }); GUILayout.Label("(from Steam)", CoopTheme.LabelDim, Array.Empty()); } else { GUI.SetNextControlName("coop_name"); string text = GUILayout.TextField(_nameField, 16, CoopTheme.TextField, Array.Empty()); if (text != _nameField) { _nameField = text; if (text.Trim().Length > 0) { CoopPlugin.PlayerName.Value = text.Trim(); } } } GUILayout.EndHorizontal(); GUILayout.Space(4f); GUILayout.BeginVertical(CoopTheme.SectionBox, Array.Empty()); GUILayout.Label("HOST YOUR SHOP", CoopTheme.SectionHeader, Array.Empty()); _publicLobby = GUILayout.Toggle(_publicLobby, " public lobby (shows in the browser)", CoopTheme.Toggle, Array.Empty()); if (_publicLobby) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Lobby name:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUI.SetNextControlName("coop_lobbyname"); _lobbyNameField = GUILayout.TextField(_lobbyNameField, 28, CoopTheme.TextField, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Password:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUI.SetNextControlName("coop_hostpw"); _hostPwField = GUILayout.TextField(_hostPwField, 20, CoopTheme.TextField, Array.Empty()); GUILayout.Label("(blank = open)", CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUILayout.EndHorizontal(); } if (core.Steam != null) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Players:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUI.SetNextControlName("coop_hostplayers"); _hostPlayersField = GUILayout.TextField(_hostPlayersField, 3, CoopTheme.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }); GUILayout.Label("(2-250)", CoopTheme.LabelDim, Array.Empty()); GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(Array.Empty()); if (core.Steam != null) { if (GUILayout.Button("Host via Steam", CoopTheme.ButtonPrimary, Array.Empty())) { if (int.TryParse(_hostPlayersField, NumberStyles.None, CultureInfo.InvariantCulture, out var result) && result >= 2 && result <= 250) { core.StartHostingSteam(_publicLobby, _lobbyNameField, _publicLobby ? _hostPwField : "", result); } else { core.ErrorLine = $"Player count must be between {2} and " + $"{250}."; } } if (GUILayout.Button("Host via LAN", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { core.StartHosting(); } } else if (GUILayout.Button("Host via LAN", CoopTheme.ButtonPrimary, Array.Empty())) { core.StartHosting(); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); CoopTheme.Divider(); GUILayout.BeginVertical(CoopTheme.SectionBox, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label("JOIN A FRIEND", CoopTheme.SectionHeader, Array.Empty()); if (core.Steam != null) { if (GUILayout.Button("Browse public lobbies", CoopTheme.ButtonPrimary, Array.Empty())) { _browserOpen = true; _page = 0; _pwPromptLobby = 0uL; core.Steam.RefreshList(); } GUILayout.Label("Accept the host's Steam invite.", CoopTheme.LabelDim, Array.Empty()); } else { GUILayout.Label("This build has no Steam support - join by IP below.", CoopTheme.LabelDim, Array.Empty()); } if (core.LastDisconnectRetryable && core.LastDisconnectCode == "rejected" && core.LastDisconnectReason == "wrong password" && core.LastFailedLobby != 0L) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Password:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUI.SetNextControlName("coop_joinpw"); _joinPwField = GUILayout.TextField(_joinPwField, 20, CoopTheme.TextField, Array.Empty()); if (GUILayout.Button("Retry", CoopTheme.ButtonPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) })) { core.JoinSteamLobby(core.LastFailedLobby, _joinPwField); } GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(Array.Empty()); GUI.SetNextControlName("coop_ip"); _ipField = GUILayout.TextField(_ipField, 24, CoopTheme.TextField, Array.Empty()); GUILayout.Label("pw", CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(20f) }); GUI.SetNextControlName("coop_lanpw"); _lanPwField = GUILayout.TextField(_lanPwField, 20, CoopTheme.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(84f) }); if (GUILayout.Button("Join LAN", CoopTheme.ButtonPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(84f) })) { core.JoinHostedGame(_ipField, CoopPlugin.Port.Value, _lanPwField.Trim().ToUpperInvariant()); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUI.SetNextControlName("coop_invite"); _inviteField = GUILayout.TextField(_inviteField, 128, CoopTheme.TextField, Array.Empty()); if (GUILayout.Button("Join by code", CoopTheme.ButtonPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { if (InviteCode.TryParse(_inviteField, out var ip, out var port, out var password)) { _ipField = ip; core.JoinHostedGame(ip, port, password); } else { core.ErrorLine = "that invite code doesn't look right - check for typos"; } } GUILayout.EndHorizontal(); GUILayout.Label("Paste an invite code, or enter an IP and password.", CoopTheme.LabelDim, Array.Empty()); GUILayout.Label($"LAN port {CoopPlugin.Port.Value} - all players need this mod + the same mods.", CoopTheme.LabelDim, Array.Empty()); GUILayout.EndVertical(); } private void DrawHost(CoopCore core, ICoopTransport net) { //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(CoopTheme.SectionBox, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label("HOSTING", CoopTheme.SectionHeader, Array.Empty()); if (core.IsSteamSession) { GUILayout.Label("Hosting through Steam - no IPs needed.", CoopTheme.Label, Array.Empty()); if (GUILayout.Button("Invite friend (Steam overlay)", CoopTheme.ButtonPrimary, Array.Empty())) { core.OpenSteamInvite(); } GUILayout.Label((net == null || net.ConnectionCount == 0) ? "Waiting for your invite to be accepted..." : PlayersLine(core), CoopTheme.Label, Array.Empty()); } else { if (_lanIps == null) { List list = LocalIPv4s(); list.Sort((string a, string b) => IpRank(a).CompareTo(IpRank(b))); _lanIps = ((list.Count > 0) ? list[0] : "(no LAN address found)"); _lanIpsOther = ((list.Count > 1) ? string.Join(" ", list.GetRange(1, list.Count - 1)) : ""); } _ipCopy.Draw("Copy IP address", IpClipboardText(), CoopTheme.ButtonSecondary); DrawInvite(core); GUILayout.Label((net == null || net.ConnectionCount == 0) ? "Waiting for a player..." : PlayersLine(core), CoopTheme.Label, Array.Empty()); } if (GUILayout.Button("Wave (" + ((object)CoopPlugin.EmoteKey.Value/*cast due to .constrained prefix*/).ToString() + ")", CoopTheme.ButtonSecondary, Array.Empty())) { PresenceApi.SendEmote(); } if (GUILayout.Button("Stop hosting", CoopTheme.ButtonDanger, Array.Empty())) { core.Disconnect(); } GUILayout.EndVertical(); } private void DrawInvite(CoopCore core) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if ((int)Event.current.type == 8) { _invState = core.InviteStatus; _invCode = core.InviteCodeText; _invReason = core.InviteReason; _invPortState = core.PortForwardState; _invPassword = core.HostPassword; } if (_invState != InviteState.Off) { string invCode = _invCode; float num = CopyButtonWidth(); float num2 = CoopTheme.ButtonSecondary.fixedHeight + (float)CoopTheme.ButtonSecondary.margin.vertical; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(InviteStatusText(_invState), CoopTheme.LabelDimMiddle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num2) }); GUILayout.FlexibleSpace(); GUI.enabled = invCode != null; _inviteCopy.Draw("Copy invite code", invCode, CoopTheme.ButtonSecondary, GUILayout.Width(num)); GUI.enabled = true; GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(_invPassword)) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("session password: " + _invPassword + "", CoopTheme.LabelDimMiddle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(num2) }); _passwordCopy.Draw("Copy password", _invPassword, CoopTheme.ButtonSecondary, GUILayout.Width(num)); GUILayout.EndHorizontal(); } if (_invState == InviteState.LanOnly) { string invReason = _invReason; GUILayout.Label("LAN-only code - " + ((invReason != null) ? invReason : "couldn't reach the internet resolver") + ". It carries your LAN address, so it still works for someone in this house.", CoopTheme.LabelWrap, Array.Empty()); } if (_invPortState == 1) { GUILayout.Label("port forward requested on your router automatically", CoopTheme.LabelDim, Array.Empty()); } else if (_invPortState == 2) { GUILayout.Label($"router declined - forward port {CoopPlugin.Port.Value} manually, or let the other player host", CoopTheme.LabelDimWrap, Array.Empty()); } } } private string IpClipboardText() { string text = $"{_lanIps} (port {CoopPlugin.Port.Value})"; if (!string.IsNullOrEmpty(_lanIpsOther)) { text = text + "\nOther addresses: " + _lanIpsOther; } return text; } private static float CopyButtonWidth() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0033: 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_0054: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) return Mathf.Max(new float[3] { CoopTheme.ButtonSecondary.CalcSize(new GUIContent("Copy invite code")).x, CoopTheme.ButtonSecondary.CalcSize(new GUIContent("Copy password")).x, CoopTheme.ButtonSecondary.CalcSize(new GUIContent("Copied!")).x }); } private static string InviteStatusText(InviteState state) { return state switch { InviteState.Resolving => "Preparing code...", InviteState.Ready => "Code ready", InviteState.LanOnly => "Code ready", _ => "", }; } private void DrawClient(CoopCore core) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(CoopTheme.SectionBox, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Label("IN SESSION", CoopTheme.SectionHeader, Array.Empty()); GUILayout.Label(PlayersLine(core), CoopTheme.Label, Array.Empty()); if (GUILayout.Button("Wave (" + ((object)CoopPlugin.EmoteKey.Value/*cast due to .constrained prefix*/).ToString() + ")", CoopTheme.ButtonSecondary, Array.Empty())) { PresenceApi.SendEmote(); } if (GUILayout.Button("Leave session", CoopTheme.ButtonDanger, Array.Empty())) { core.Disconnect(); } GUILayout.EndVertical(); } private void DrawGradedAdopt(CoopCore core) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if ((int)Event.current.type == 8) { _adoptOffers.Clear(); _adoptOffers.AddRange(GradingUiApi.Offers); } if (_adoptOffers.Count == 0) { return; } CoopTheme.Divider(); GUILayout.BeginVertical(CoopTheme.SectionBox, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); CoopTheme.Chip("GRADED ALBUM", CoopTheme.ChipWarn); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Label("Only missing graded cards will be added; existing certificates are skipped.", CoopTheme.LabelWrap, Array.Empty()); for (int i = 0; i < _adoptOffers.Count; i++) { GradingOffer gradingOffer = _adoptOffers[i]; if (GUILayout.Button($"Adopt {gradingOffer.Count} graded card(s) {gradingOffer.Who} has that you don't", CoopTheme.ButtonSecondary, Array.Empty())) { GradingUiApi.Adopt(gradingOffer.ConnectionId); } } GUILayout.EndVertical(); } private void DrawBrowser(CoopCore core) { if (core.Steam == null) { _browserOpen = false; return; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("PUBLIC LOBBIES", CoopTheme.SectionHeader, Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(core.Steam.ListRefreshing ? "..." : "Refresh", CoopTheme.ButtonPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f) })) { core.Steam.RefreshList(); } if (GUILayout.Button("Back", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(54f) })) { _browserOpen = false; _pwPromptLobby = 0uL; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Search:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f) }); GUI.SetNextControlName("coop_search"); string text = GUILayout.TextField(_searchField, 24, CoopTheme.TextField, Array.Empty()); if (text != _searchField) { _searchField = text; _page = 0; } GUILayout.EndHorizontal(); List list = new List(); foreach (LobbyRow lobby in core.Steam.Lobbies) { if (_searchField.Length == 0 || (lobby.Name ?? "").IndexOf(_searchField, StringComparison.OrdinalIgnoreCase) >= 0) { list.Add(lobby); } } int num = Mathf.Max(1, (list.Count + 6 - 1) / 6); _page = Mathf.Clamp(_page, 0, num - 1); if (list.Count == 0) { GUILayout.Label(core.Steam.ListRefreshing ? "Searching..." : "No lobbies found - hit Refresh, or host one!", CoopTheme.LabelDim, Array.Empty()); } for (int i = _page * 6; i < list.Count && i < (_page + 1) * 6; i++) { LobbyRow lobbyRow = list[i]; bool flag = lobbyRow.Ver == "2.0.6"; GUILayout.BeginHorizontal(((i & 1) == 0) ? CoopTheme.RowEven : CoopTheme.RowOdd, Array.Empty()); GUILayout.Label((lobbyRow.HasPw ? "[pw] " : "") + lobbyRow.Name, CoopTheme.LabelBold, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); GUILayout.FlexibleSpace(); GUILayout.Label($"{lobbyRow.Players}/{lobbyRow.Max}" + (flag ? "" : (" v" + lobbyRow.Ver + "")), CoopTheme.LabelDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); GUILayout.Space(6f); GUI.enabled = flag; if (GUILayout.Button("Join", CoopTheme.ButtonPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(56f) })) { if (lobbyRow.HasPw) { _pwPromptLobby = lobbyRow.Id; _joinPwField = ""; } else { core.JoinSteamLobby(lobbyRow.Id); _browserOpen = false; } } GUI.enabled = true; GUILayout.EndHorizontal(); if (_pwPromptLobby == lobbyRow.Id) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Password:", CoopTheme.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }); GUI.SetNextControlName("coop_joinpw"); _joinPwField = GUILayout.TextField(_joinPwField, 20, CoopTheme.TextField, Array.Empty()); if (GUILayout.Button("Go", CoopTheme.ButtonPrimary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(44f) })) { core.JoinSteamLobby(lobbyRow.Id, _joinPwField); _browserOpen = false; _pwPromptLobby = 0uL; } GUILayout.EndHorizontal(); } } GUILayout.BeginHorizontal(Array.Empty()); GUI.enabled = _page > 0; if (GUILayout.Button("< Prev", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) })) { _page--; } GUI.enabled = _page < num - 1; if (GUILayout.Button("Next >", CoopTheme.ButtonSecondary, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) })) { _page++; } GUI.enabled = true; GUILayout.FlexibleSpace(); GUILayout.Label($"page {_page + 1}/{num} - {list.Count} lobbies", CoopTheme.LabelDim, Array.Empty()); GUILayout.EndHorizontal(); } private static string PlayersLine(CoopCore core) { if (PresenceApi.PeerNames.Count == 0) { return "Linked."; } List values = new List(PresenceApi.PeerNames); return "Playing with: " + string.Join(", ", values); } private static List LocalIPv4s() { List list = new List(); try { NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback) { continue; } foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) { if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) { string text = unicastAddress.Address.ToString(); if (!text.StartsWith("169.254")) { list.Add(text); } } } } } catch (Exception e) { Swallow.Log(e, "LocalIPv4s", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/UI/CoopUI.cs", 1777); } if (list.Count == 0) { list.Add("(no LAN address found)"); } return list; } } public sealed class CopyButton { private const float RevertSeconds = 2.5f; private string _seen; private float _revertAt; public bool Draw(string label, string value, GUIStyle style, params GUILayoutOption[] options) { if (!string.Equals(value, _seen)) { _seen = value; _revertAt = 0f; } if (GUILayout.Button((Time.realtimeSinceStartup < _revertAt) ? "Copied!" : label, style, options)) { GUIUtility.systemCopyBuffer = value ?? string.Empty; _revertAt = Time.realtimeSinceStartup + 2.5f; return true; } return false; } } } namespace CardShopCoop.Runtime { public abstract class CoopBehaviour : CoopBehaviour { protected CoopRuntimeContext RuntimeContext { get; private set; } internal void SetRuntimeContext(CoopRuntimeContext context) { RuntimeContext = context ?? throw new ArgumentNullException("context"); ((CoopBehaviour)this).SetContext((ICoopContext)(object)context); } } internal sealed class CoopBehaviourRegistry { private readonly List _behaviours = new List(); private readonly Dictionary> _events = new Dictionary>(); private readonly HashSet _createdTypes = new HashSet(); private readonly GameObject _parent; private readonly CoopRuntimeContext _context; private bool _disposed; private CoopBehaviourRegistry(GameObject parent, CoopRuntimeContext context) { _parent = parent; _context = context; } public static CoopBehaviourRegistry CreateServer(GameObject parent, CoopRuntimeContext context) { if ((Object)(object)parent == (Object)null || context == null) { throw new ArgumentNullException(((Object)(object)parent == (Object)null) ? "parent" : "context"); } return CreateInternal(parent, context, Discover(includeCore: true)); } public static CoopBehaviourRegistry CreateClient(GameObject parent, CoopRuntimeContext context) { if ((Object)(object)parent == (Object)null || context == null) { throw new ArgumentNullException(((Object)(object)parent == (Object)null) ? "parent" : "context"); } return CreateInternal(parent, context, Discover(includeCore: true)); } public static CoopBehaviourRegistry CreatePersistent(GameObject parent, CoopRuntimeContext context) { if ((Object)(object)parent == (Object)null || context == null) { throw new ArgumentNullException(((Object)(object)parent == (Object)null) ? "parent" : "context"); } return CreateInternal(parent, context, Discover(includeCore: true)); } public void AddExternalPersistent() { if (_disposed) { return; } try { foreach (Type item in DiscoverIn(ExternalCoopMods.Instance.Assemblies, typeof(PersistentBehaviourAttribute))) { if (_createdTypes.Contains(item)) { continue; } try { AddType(item, activate: true); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)("[api] created external persistent behaviour " + item.FullName)); } } catch (Exception ex) { ManualLogSource log2 = CoopPlugin.Log; if (log2 != null) { log2.LogError((object)("[api] external persistent behaviour " + item.FullName + " failed to start: " + ex)); } } } } catch (Exception ex2) { ManualLogSource log3 = CoopPlugin.Log; if (log3 != null) { log3.LogError((object)("[api] external persistent behaviour scan failed: " + ex2)); } } } private static CoopBehaviourRegistry CreateInternal(GameObject parent, CoopRuntimeContext context, IEnumerable types) { CoopBehaviourRegistry coopBehaviourRegistry = new CoopBehaviourRegistry(parent, context); try { foreach (Type type in types) { if (type.Assembly == typeof(CoopBehaviourRegistry).Assembly) { coopBehaviourRegistry.AddType(type, activate: false); continue; } try { coopBehaviourRegistry.AddType(type, activate: false); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("[api] external behaviour " + type.FullName + " failed to start; skipping it: " + ex)); } } } coopBehaviourRegistry.ActivateAll(); return coopBehaviourRegistry; } catch (Exception ex2) { try { coopBehaviourRegistry.Dispose(); } catch (Exception ex3) { throw new AggregateException("Co-op behaviour startup and rollback failed.", ex2, ex3); } throw; } } private void AddType(Type type, bool activate) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!_createdTypes.Add(type)) { return; } GameObject val = null; try { val = new GameObject(type.Name); val.transform.SetParent(_parent.transform, false); val.SetActive(false); Component obj = val.AddComponent(type); MonoBehaviour val2 = (MonoBehaviour)(object)((obj is MonoBehaviour) ? obj : null); if ((Object)(object)val2 == (Object)null) { throw new InvalidOperationException(type.FullName + " is not a MonoBehaviour."); } if (val2 is CoopBehaviour coopBehaviour) { coopBehaviour.SetRuntimeContext(_context); } else { CoopBehaviour val3 = (CoopBehaviour)(object)((val2 is CoopBehaviour) ? val2 : null); if (val3 != null) { val3.SetContext((ICoopContext)(object)_context); } } RegisterBehaviourEvents(val2); _behaviours.Add(val2); if (activate) { ((Component)val2).gameObject.SetActive(true); } } catch { _createdTypes.Remove(type); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } throw; } } private void ActivateAll() { foreach (MonoBehaviour behaviour in _behaviours) { if ((Object)(object)behaviour != (Object)null && !((Component)behaviour).gameObject.activeSelf) { ((Component)behaviour).gameObject.SetActive(true); } } } private void RegisterBehaviourEvents(MonoBehaviour behaviour) { MethodInfo[] methods = ((object)behaviour).GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { Attribute attribute = methodInfo.GetCustomAttributes().FirstOrDefault((Attribute a) => a is OnClientJoinedAttribute || a is OnClientDisconnectedAttribute || a is OnFullyJoinedAttribute || a is OnSessionStartedAttribute || a is OnSessionStoppedAttribute); if (attribute != null) { Validate(attribute, methodInfo); Type type = attribute.GetType(); if (!_events.TryGetValue(type, out var value)) { value = new List(); _events.Add(type, value); } if (!value.Contains(methodInfo)) { value.Add(methodInfo); } } } } public void ClientJoined(PeerConnection connection) { Invoke(new object[1] { connection }); } public void ClientDisconnected(PeerConnection connection, DisconnectInfo info) { Invoke(new object[2] { connection, info }); } public void FullyJoined(PeerConnection connection) { Invoke(new object[1] { connection }); } public void SessionStarted() { Invoke(Array.Empty()); } public void SessionStopped() { Invoke(Array.Empty()); } public void Dispose() { if (_disposed) { return; } _disposed = true; List list = null; for (int num = _behaviours.Count - 1; num >= 0; num--) { if ((Object)(object)_behaviours[num] != (Object)null) { try { ((object)_behaviours[num]).GetType().GetMethod("Shutdown", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(_behaviours[num], null); } catch (Exception item) { if (list == null) { list = new List(); } list.Add(item); } try { _context?.Messages.UnregisterAttributedHandlers(_behaviours[num]); } catch (Exception item2) { if (list == null) { list = new List(); } list.Add(item2); } finally { Object.Destroy((Object)(object)((Component)_behaviours[num]).gameObject); } } } _behaviours.Clear(); _events.Clear(); _createdTypes.Clear(); if (list == null) { return; } throw new AggregateException("One or more co-op behaviours failed to shut down.", list); } private void Invoke(params object[] args) where TAttribute : Attribute { if (!_events.TryGetValue(typeof(TAttribute), out var value)) { return; } List list = null; foreach (MethodInfo item2 in value) { foreach (MonoBehaviour behaviour in _behaviours) { if ((Object)(object)behaviour == (Object)null || item2.DeclaringType == null || !item2.DeclaringType.IsAssignableFrom(((object)behaviour).GetType())) { continue; } try { item2.Invoke(behaviour, args); } catch (Exception item) { if (list == null) { list = new List(); } list.Add(item); } } } if (list == null) { return; } throw new AggregateException("One or more co-op behaviour event handlers failed.", list); } private static IEnumerable Discover(bool includeCore) where TAttribute : Attribute { return DiscoverIn(AssembliesToScan(includeCore), typeof(TAttribute)); } private static IEnumerable DiscoverIn(IEnumerable assemblies, Type attributeType) { foreach (Assembly assembly in assemblies) { Type[] array = SafeTypes(assembly); foreach (Type type in array) { if (typeof(MonoBehaviour).IsAssignableFrom(type) && !type.IsAbstract && HasAttribute(type, attributeType) && ModuleCatalog.IsEnabled(type)) { yield return type; } } } } private static bool HasAttribute(Type type, Type attributeType) { object[] customAttributes = type.GetCustomAttributes(inherit: false); foreach (object obj in customAttributes) { if (obj != null) { Type type2 = obj.GetType(); if (type2 == attributeType || type2.FullName == attributeType.FullName) { return true; } } } return false; } private static IEnumerable AssembliesToScan(bool includeCore) { Assembly core = typeof(CoopBehaviourRegistry).Assembly; if (includeCore) { yield return core; } IReadOnlyList external = ExternalCoopMods.Instance.Assemblies; for (int i = 0; i < external.Count; i++) { if (external[i] != null && external[i] != core) { yield return external[i]; } } } private static Type[] SafeTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { Type[] array = ex.Types.Where((Type type) => type != null).ToArray(); CoopPlugin.Log.LogError((object)("Co-op behaviour discovery loaded " + array.Length + " types from " + assembly.GetName().Name + " with reflection errors: " + string.Join(" | ", from error in ex.LoaderExceptions where error != null select error.ToString()))); return array; } } private static void Validate(Attribute attribute, MethodInfo method) { Type[] array; if (!(attribute is OnClientJoinedAttribute)) { if (!(attribute is OnClientDisconnectedAttribute)) { if (!(attribute is OnFullyJoinedAttribute)) { if (!(attribute is OnSessionStartedAttribute)) { if (!(attribute is OnSessionStoppedAttribute)) { throw new InvalidOperationException("Unknown co-op behaviour event attribute."); } array = Type.EmptyTypes; } else { array = Type.EmptyTypes; } } else { array = new Type[1] { typeof(PeerConnection) }; } } else { array = new Type[2] { typeof(PeerConnection), typeof(DisconnectInfo) }; } } else { array = new Type[1] { typeof(PeerConnection) }; } Type[] second = array; ParameterInfo[] parameters = method.GetParameters(); if (method.IsStatic || method.ReturnType != typeof(void) || !parameters.Select((ParameterInfo p) => p.ParameterType).SequenceEqual(second)) { throw new InvalidOperationException("Invalid co-op behaviour event signature: " + method.DeclaringType?.FullName + "." + method.Name); } } } public abstract class CoopModule { public abstract string Name { get; } public virtual void Reset() { } public virtual void ForceResend() { } public virtual void FullUpdate(PeerConnection connection) { } protected void Guarded(string label, Action action) { ModuleGuard.Run(Name + ":" + label, action); } } internal static class ModuleGuard { private const double CooldownSeconds = 5.0; private static readonly Dictionary NextAt = new Dictionary(); public static void Run(string tag, Action action) { try { action(); } catch (Exception e) { Log(tag, e); } } public static void Log(string tag, Exception e) { double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (!NextAt.TryGetValue(tag, out var value) || !(realtimeSinceStartupAsDouble < value)) { NextAt[tag] = realtimeSinceStartupAsDouble + 5.0; CoopPlugin.Log.LogError((object)("[" + tag + "] " + e)); } } } public abstract class CoopRuntimeBehaviour : MonoBehaviour { private CoopBehaviourRegistry _registry; private bool _shutdown; public CoopRuntimeContext Context { get; private set; } public void Initialize(CoopRuntimeContext context) { Context = context ?? throw new ArgumentNullException("context"); CoopBehaviourRegistry registry; if (!(this is ServerRuntimeBehaviour)) { if (!(this is ClientRuntimeBehaviour)) { throw new InvalidOperationException("Session runtime must be a server or client runtime behaviour."); } registry = CoopBehaviourRegistry.CreateClient(((Component)this).gameObject, context); } else { registry = CoopBehaviourRegistry.CreateServer(((Component)this).gameObject, context); } _registry = registry; try { SessionStarted(); } catch (Exception ex) { try { _registry.Dispose(); } catch (Exception ex2) { _registry = null; throw new AggregateException("Co-op session startup and rollback failed.", ex, ex2); } _registry = null; throw; } } public void InitializePersistent(CoopRuntimeContext context) { Context = context ?? throw new ArgumentNullException("context"); _registry = CoopBehaviourRegistry.CreatePersistent(((Component)this).gameObject, context); } internal void Shutdown() { if (_shutdown) { return; } _shutdown = true; Exception ex = null; Exception ex2 = null; try { SessionStopped(); } catch (Exception ex3) { ex = ex3; } try { _registry?.Dispose(); } catch (Exception ex4) { ex2 = ex4; } finally { _registry = null; Context?.PeerPresence.ClearAll(); } if (ex == null && ex2 == null) { return; } List list = new List(); if (ex != null) { list.Add(ex); } if (ex2 != null) { list.Add(ex2); } throw new AggregateException("Co-op runtime shutdown failed.", list); } public T GetBehaviour() where T : MonoBehaviour { if (_registry != null) { return ((Component)this).GetComponentInChildren(true); } return default(T); } internal void IncludeExternalPersistentBehaviours() { _registry?.AddExternalPersistent(); } public void ClientJoined(PeerConnection connection) { Guard("client-joined", delegate { _registry?.ClientJoined(connection); }); } public void ClientDisconnected(PeerConnection connection, DisconnectInfo info) { Context?.PeerPresence.Clear((connection != null) ? connection.Id : 0); Guard("client-disconnected", delegate { _registry?.ClientDisconnected(connection, info); }); } public void FullyJoined(PeerConnection connection) { Guard("fully-joined", delegate { _registry?.FullyJoined(connection); }); } public void SessionStarted() { Guard("session-started", delegate { _registry?.SessionStarted(); }); } public void SessionStopped() { Guard("session-stopped", delegate { _registry?.SessionStopped(); }); } private static void Guard(string label, Action action) { try { action(); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("Co-op " + label + " callback failed: " + ex)); } } } private void OnDestroy() { Shutdown(); } } public sealed class ServerRuntimeBehaviour : CoopRuntimeBehaviour { } public sealed class ClientRuntimeBehaviour : CoopRuntimeBehaviour { } public sealed class PersistentRuntimeBehaviour : CoopRuntimeBehaviour { } public sealed class CoopRuntimeContext : ICoopContext { public MessageRouter Messages { get; } public Func InGame { get; } public Action Broadcast { get; } public Action Send { get; } public Action Relay { get; } public Func> ConnectionIds { get; } public Func PeerName { get; } public Action SetStatusLine { get; } public Func PreloadHold { get; } public Action Disconnect { get; } public IPeerPresence PeerPresence { get; } bool ICoopContext.IsHost => CoopCore.Role == CoopRole.Host; bool ICoopContext.IsClient => CoopCore.Role == CoopRole.Client; bool ICoopContext.InSession => CoopCore.Role != CoopRole.None; bool ICoopContext.InGame { get { if (InGame != null) { return InGame(); } return false; } } int ICoopContext.LocalConnectionId => CoopCore.LocalConnectionId; int ICoopContext.HostConnectionId { get { if (CoopCore.Role != CoopRole.Client) { return -1; } return 1; } } IReadOnlyList ICoopContext.ConnectionIds { get { if (ConnectionIds != null) { return ConnectionIds(); } return Array.Empty(); } } ICoopMessageRegistry ICoopContext.Messages => (ICoopMessageRegistry)(object)Messages; public CoopRuntimeContext(MessageRouter messages, Func inGame, Action broadcast, Action send, Func> connectionIds = null, Func peerName = null, Action setStatusLine = null, Func preloadHold = null, Action disconnect = null, Action relay = null) { Messages = messages ?? throw new ArgumentNullException("messages"); InGame = inGame ?? throw new ArgumentNullException("inGame"); Broadcast = broadcast ?? throw new ArgumentNullException("broadcast"); Send = send ?? throw new ArgumentNullException("send"); ConnectionIds = connectionIds; PeerName = peerName; SetStatusLine = setStatusLine; PreloadHold = preloadHold; Disconnect = disconnect; Relay = relay; PeerPresence = new PeerPresenceProvider(TimeSpan.FromSeconds(10.0)); } string ICoopContext.PeerName(int connectionId) { if (PeerName != null) { return PeerName(connectionId); } return null; } void ICoopContext.Send(int connectionId, INetMessage message) { Send(connectionId, message); } void ICoopContext.Broadcast(INetMessage message) { Broadcast(message); } } internal sealed class ExternalCoopMods : ICoopBinding { private readonly object _gate = new object(); private readonly List _assemblies = new List(); private readonly List _registrations = new List(); private bool _discovered; private bool _persistentScanComplete; public static ExternalCoopMods Instance { get; } = new ExternalCoopMods(); public IReadOnlyList Assemblies { get { lock (_gate) { return _assemblies.ToArray(); } } } public ICoopContext Context { get { if (!((Object)(object)CoopCore.Instance == (Object)null)) { return (ICoopContext)(object)CoopCore.Instance.LiveContext; } return null; } } public bool PredictionActive => PredictionApi.IsActive; public bool IsReconciling => PredictionApi.IsReconciling; public bool IsApplying => PredictionApi.IsApplying; private ExternalCoopMods() { } public void EnsureDiscovered() { lock (_gate) { if (_discovered) { return; } _discovered = true; } try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; if (value == null || value.Metadata == null || string.Equals(value.Metadata.GUID, "dev.meepen.cardshopcoop", StringComparison.Ordinal) || !DependsOnUs(value)) { continue; } BaseUnityPlugin instance = value.Instance; Assembly assembly = (((Object)(object)instance != (Object)null) ? ((object)instance).GetType().Assembly : null); if (assembly == null) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("[api] '" + value.Metadata.GUID + "' depends on CardShopCoopCommunity but has no live instance; skipping integration")); } } else { RegisterCore(assembly, value.Metadata.GUID); } } } catch (Exception ex) { ManualLogSource log2 = CoopPlugin.Log; if (log2 != null) { log2.LogError((object)("[api] external co-op mod discovery failed: " + ex)); } } } public void Register(Assembly assembly) { string ownerId = "manual:" + ((assembly == null) ? "" : assembly.GetName().Name); RegisterCore(assembly, ownerId); if (_persistentScanComplete && assembly != null && HasPersistentBehaviours(assembly)) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("[api] external mod '" + assembly.GetName().Name + "' registered after the persistent scan; its [PersistentBehaviour]s will not be instantiated (register from your plugin's Awake, before the first frame)")); } } } public void NotifyPersistentScanComplete() { _persistentScanComplete = true; } private static bool DependsOnUs(PluginInfo info) { IEnumerable dependencies = info.Dependencies; if (dependencies == null) { return false; } foreach (BepInDependency item in dependencies) { if (item != null && string.Equals(item.DependencyGUID, "dev.meepen.cardshopcoop", StringComparison.Ordinal)) { return true; } } return false; } private void RegisterCore(Assembly assembly, string ownerId) { if (assembly == null) { return; } lock (_gate) { if (_assemblies.Contains(assembly)) { return; } _assemblies.Add(assembly); } string name = assembly.GetName().Name; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)("[api] integrating external co-op mod '" + ownerId + "' (assembly " + name + ")")); } IProtocolRegistration protocolRegistration = null; try { if (HasNetworkMessages(assembly)) { protocolRegistration = MessageRegistry.RegisterAssembly(assembly, "ext:" + ownerId); } ModuleCatalog.BindExternal(name); } catch (Exception ex) { try { protocolRegistration?.Dispose(); } catch (Exception ex2) { ManualLogSource log2 = CoopPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[api] could not roll back message registration for '" + name + "': " + ex2.Message)); } } lock (_gate) { _assemblies.Remove(assembly); } ManualLogSource log3 = CoopPlugin.Log; if (log3 != null) { log3.LogError((object)("[api] external co-op mod '" + name + "' failed integration and will not be registered: " + ex)); } return; } if (protocolRegistration == null) { return; } if (protocolRegistration.State == RegistrationState.PendingNextSession) { ManualLogSource log4 = CoopPlugin.Log; if (log4 != null) { log4.LogInfo((object)("[api] external mod '" + ownerId + "' registered while a session is active; its messages apply from the next session")); } } lock (_gate) { _registrations.Add(protocolRegistration); } } private static bool HasNetworkMessages(Assembly assembly) { Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (type != null && ((MemberInfo)type).GetCustomAttribute(inherit: false) != null) { return true; } } return false; } private static bool HasPersistentBehaviours(Assembly assembly) { Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } string fullName = typeof(PersistentBehaviourAttribute).FullName; Type[] array = types; foreach (Type type in array) { if (type == null) { continue; } object[] customAttributes = type.GetCustomAttributes(inherit: false); foreach (object obj in customAttributes) { if (obj != null && obj.GetType().FullName == fullName) { return true; } } } return false; } public Guid Predict(string scope, Action send, Action apply, Action undo) { return PredictionApi.Predict(scope, send, apply, undo); } public void Ack(Guid predictionId) { PredictionApi.Ack(predictionId); } public void AckOrApply(Guid predictionId, Action apply) { PredictionApi.AckOrApply(predictionId, apply); } public void Confirm(Guid predictionId, Action apply) { PredictionApi.Confirm(predictionId, apply); } public bool IsPending(Guid predictionId) { return PredictionApi.IsPending(predictionId); } public void Rollback(ICoopContext context, int connectionId, Guid predictionId) { PredictionApi.Rollback(context, connectionId, predictionId); } } public sealed class MainThreadDispatcher { private sealed class Work { public readonly int Epoch; public readonly string Stage; public readonly Action Action; public readonly bool Retryable; public int Attempts; public int NotBeforeFrame; public Work(int epoch, string stage, Action action, bool retryable) { Epoch = epoch; Stage = stage; Action = action; Retryable = retryable; } } public const int DefaultFrameBudget = 64; public const int DefaultPendingLimit = 4096; public const byte DefaultMaxRetries = 3; private readonly ConcurrentQueue _queue = new ConcurrentQueue(); private readonly object _lifecycleGate = new object(); private readonly int _pendingLimit; private readonly int _frameBudget; private readonly byte _maxRetries; private int _epoch; private int _pending; private bool _running; public int Epoch => Volatile.Read(in _epoch); public bool IsRunning { get { lock (_lifecycleGate) { return _running; } } } public int PendingCount => Math.Max(0, Volatile.Read(in _pending)); public MainThreadDispatcher(int frameBudget = 64, int pendingLimit = 4096, byte maxRetries = 3) { if (frameBudget < 1) { throw new ArgumentOutOfRangeException("frameBudget"); } if (pendingLimit < 1) { throw new ArgumentOutOfRangeException("pendingLimit"); } _frameBudget = frameBudget; _pendingLimit = pendingLimit; _maxRetries = maxRetries; } public int Start() { lock (_lifecycleGate) { ClearUnsafe(); _epoch++; _running = true; return _epoch; } } public void Stop() { lock (_lifecycleGate) { _running = false; _epoch++; ClearUnsafe(); } } public void Clear() { lock (_lifecycleGate) { ClearUnsafe(); } } public int InvalidateEpoch() { lock (_lifecycleGate) { _epoch++; ClearUnsafe(); return _epoch; } } public bool TryEnqueue(string stage, Action action, bool retryable = false) { return TryEnqueue(Epoch, stage, action, retryable); } public bool TryEnqueue(int epoch, string stage, Action action, bool retryable = false) { if (action == null) { return false; } lock (_lifecycleGate) { if (!_running || epoch != _epoch || _pending >= _pendingLimit) { return false; } _queue.Enqueue(new Work(epoch, stage ?? "unspecified", action, retryable)); _pending++; return true; } } public void Enqueue(string stage, Action action, bool retryable = false) { if (!TryEnqueue(stage, action, retryable)) { throw new InvalidOperationException("MainThreadDispatcher is stopped or full."); } } public int Drain() { return Drain(_frameBudget); } public int Drain(int budget) { if (budget < 1) { return 0; } budget = Math.Min(budget, _frameBudget); int num = 0; for (int i = 0; i < budget; i++) { if (!_queue.TryDequeue(out var result)) { break; } Interlocked.Decrement(ref _pending); lock (_lifecycleGate) { if (_running && result.Epoch == _epoch) { if (Time.frameCount < result.NotBeforeFrame) { RequeueUnsafe(result); continue; } num++; RunUnsafe(result); } } } return num; } private void RunUnsafe(Work work) { try { work.Action(); } catch (Exception arg) { work.Attempts++; CoopPlugin.Log.LogError((object)("main-thread action '" + work.Stage + "' failed " + $"(epoch {work.Epoch}, attempt {work.Attempts}): {arg}")); if (!work.Retryable || work.Attempts > _maxRetries) { CoopPlugin.Log.LogError((object)("main-thread action '" + work.Stage + "' abandoned after " + $"{work.Attempts} attempt(s)")); return; } work.NotBeforeFrame = Time.frameCount + 1 + work.Attempts; if (_running && work.Epoch == _epoch && _pending < _pendingLimit) { RequeueUnsafe(work); } else { CoopPlugin.Log.LogWarning((object)("main-thread action '" + work.Stage + "' retry discarded because its session epoch is no longer active")); } } } private void RequeueUnsafe(Work work) { _queue.Enqueue(work); _pending++; } private void ClearUnsafe() { Work result; while (_queue.TryDequeue(out result)) { Interlocked.Decrement(ref _pending); } if (Volatile.Read(in _pending) < 0) { Interlocked.Exchange(ref _pending, 0); } } } internal static class ModuleCatalog { private const string ModulesRoot = "CardShopCoop.Modules."; private const string Section = "Modules"; private const string CoreModule = "Core"; private static readonly object Gate = new object(); private static Dictionary> _entries; private static List _known; private static ConfigFile _config; internal static string ModuleOf(Type type) { Assembly assembly = type?.Assembly; if (assembly != null && assembly != typeof(ModuleCatalog).Assembly) { return assembly.GetName().Name; } string text = type?.Namespace; if (!string.IsNullOrEmpty(text) && text.StartsWith("CardShopCoop.Modules.", StringComparison.Ordinal)) { string text2 = text.Substring("CardShopCoop.Modules.".Length); int num = text2.IndexOf('.'); if (num >= 0) { return text2.Substring(0, num); } return text2; } return "Core"; } internal static void Bind(ConfigFile config) { if (config == null) { return; } lock (Gate) { if (_entries != null) { return; } _config = config; Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (string item in Names()) { dictionary[item] = config.Bind("Modules", item, true, "Enable the " + item + " co-op module. Turn modules off one at a time to find which one costs frame rate. Applies after rejoining (persistent modules apply after a restart). The join handshake still matches with modules off - you may disable different modules on each PC."); } _entries = dictionary; } } internal static IReadOnlyList Names() { lock (Gate) { return _known ?? (_known = Discover()); } } internal static void BindExternal(string module) { if (string.IsNullOrEmpty(module) || string.Equals(module, "Core", StringComparison.OrdinalIgnoreCase)) { return; } lock (Gate) { if (_config != null && _entries != null && !_entries.ContainsKey(module)) { _entries[module] = _config.Bind("Modules", module, true, "Enable the external co-op mod '" + module + "'. Applies after rejoining."); if (_known != null && !_known.Contains(module)) { _known.Add(module); _known.Sort(StringComparer.OrdinalIgnoreCase); } } } } internal static bool IsEnabled(string module) { if (string.IsNullOrEmpty(module) || string.Equals(module, "Core", StringComparison.OrdinalIgnoreCase)) { return true; } lock (Gate) { if (_entries != null && _entries.TryGetValue(module, out var value) && value != null) { return value.Value; } } return true; } internal static bool IsEnabled(Type type) { return IsEnabled(ModuleOf(type)); } internal static ConfigEntry Entry(string module) { lock (Gate) { ConfigEntry value; return (_entries != null && module != null && _entries.TryGetValue(module, out value)) ? value : null; } } internal static void LogConfiguration() { IReadOnlyList readOnlyList = Names(); List list = readOnlyList.Where((string name) => !IsEnabled(name)).ToList(); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)("[modules] " + readOnlyList.Count + " available: " + string.Join(", ", readOnlyList))); } ManualLogSource log2 = CoopPlugin.Log; if (log2 != null) { log2.LogInfo((object)((list.Count == 0) ? "[modules] all enabled." : ("[modules] disabled: " + string.Join(", ", list) + " (rejoin or restart to apply)"))); } } private static List Discover() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); Type[] array; try { array = typeof(CoopBehaviour).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type type2) => type2 != null).ToArray(); } Type[] array2 = array; foreach (Type type in array2) { if (!(type == null) && !type.IsAbstract && typeof(CoopBehaviour).IsAssignableFrom(type) && (type.GetCustomAttributes(typeof(ServerBehaviourAttribute), inherit: false).Length != 0 || type.GetCustomAttributes(typeof(ClientBehaviourAttribute), inherit: false).Length != 0 || type.GetCustomAttributes(typeof(PersistentBehaviourAttribute), inherit: false).Length != 0)) { hashSet.Add(ModuleOf(type)); } } hashSet.Remove("Core"); return hashSet.OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase).ToList(); } } internal static class SceneRef { private static readonly List Clearers = new List(); private static readonly List> SceneClearers = new List>(); private static readonly List> SceneLoaders = new List>(); private static bool _installed; internal static void Register(Action clear) { Clearers.Add(clear); } internal static void Register(Action clear, Action clearScene, Action sceneLoaded) { Register(clear); SceneClearers.Add(clearScene); SceneLoaders.Add(sceneLoaded); } internal static void Install() { if (!_installed) { _installed = true; Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); Ensure(); RegisterComponentType(); RegisterComponentType(); RegisterComponentType(); RegisterComponentType(); RegisterComponentType(); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; } } private static void Ensure() where T : Component { RuntimeHelpers.RunClassConstructor(typeof(SceneRef).TypeHandle); } private static void RegisterComponentType() where T : Component { SceneLifecycleHooks.Register(typeof(T), delegate(Component instance) { SceneComponentRegistry.Register((T)(object)instance); }, delegate(Component instance) { SceneComponentRegistry.Unregister((T)(object)instance); }); } private static void OnSceneLoaded(Scene scene, LoadSceneMode _) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) NotifySceneLoaded(scene); } private static void OnSceneUnloaded(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) ClearScene(scene); } internal static void NotifySceneLoaded(Scene scene) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < SceneLoaders.Count; i++) { SceneLoaders[i](scene); } } internal static void ClearScene(Scene scene) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < SceneClearers.Count; i++) { SceneClearers[i](scene); } } internal static void ClearAll() { for (int i = 0; i < Clearers.Count; i++) { Clearers[i](); } } } internal static class SceneRef where T : Component { private static T _cached; private static int _lastLookupScene; static SceneRef() { _lastLookupScene = int.MinValue; SceneRef.Register(Clear, ClearScene, SceneLoaded); SceneLifecycleHooks.Register(typeof(T), delegate(Component instance) { Set((T)(object)instance); }, delegate(Component instance) { Unset((T)(object)instance); }); } internal static T Get() { //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) if ((Object)(object)_cached != (Object)null) { return _cached; } Scene activeScene = SceneManager.GetActiveScene(); if (_lastLookupScene == ((Scene)(ref activeScene)).handle) { return default(T); } _lastLookupScene = ((Scene)(ref activeScene)).handle; _cached = Object.FindObjectOfType(); return _cached; } private static void Set(T instance) { if (!((Object)(object)instance == (Object)null)) { _cached = instance; _lastLookupScene = int.MinValue; } } private static void Unset(T instance) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if ((object)_cached == (object)instance) { _cached = default(T); Scene val = (Scene)(((Object)(object)instance != (Object)null && (Object)(object)((Component)instance).gameObject != (Object)null) ? ((Component)instance).gameObject.scene : default(Scene)); _lastLookupScene = (((Scene)(ref val)).IsValid() ? ((Scene)(ref val)).handle : int.MinValue); } } private static void ClearScene(Scene scene) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_cached == (Object)null)) { Scene scene2 = ((Component)_cached).gameObject.scene; if (((Scene)(ref scene2)).IsValid()) { scene2 = ((Component)_cached).gameObject.scene; if (((Scene)(ref scene2)).handle != ((Scene)(ref scene)).handle) { goto IL_0060; } } } _cached = default(T); goto IL_0060; IL_0060: if (_lastLookupScene == ((Scene)(ref scene)).handle) { _lastLookupScene = int.MinValue; } } private static void SceneLoaded(Scene scene) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_cached == (Object)null) { _lastLookupScene = int.MinValue; return; } Scene scene2 = ((Component)_cached).gameObject.scene; if (((Scene)(ref scene2)).IsValid()) { scene2 = ((Component)_cached).gameObject.scene; if (((Scene)(ref scene2)).handle == ((Scene)(ref scene)).handle) { _lastLookupScene = int.MinValue; } } } private static void Clear() { _cached = default(T); _lastLookupScene = int.MinValue; } } internal static class SceneComponentRegistry where T : Component { private static readonly Dictionary> ByScene = new Dictionary>(); private static readonly HashSet All = new HashSet(); internal static void Register(T instance) { //IL_0019: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)instance == (Object)null) { return; } Scene scene = ((Component)instance).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { scene = ((Component)instance).gameObject.scene; int handle = ((Scene)(ref scene)).handle; if (!ByScene.TryGetValue(handle, out var value)) { value = new HashSet(); ByScene.Add(handle, value); } value.Add(instance); All.Add(instance); } } internal static void Unregister(T instance) { if (instance == null) { return; } All.Remove(instance); foreach (HashSet value in ByScene.Values) { value.Remove(instance); } } internal static List Snapshot(Scene scene, bool activeOnly) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!ByScene.TryGetValue(((Scene)(ref scene)).handle, out var value)) { return list; } List list2 = new List(); foreach (T item in value) { if (!((Object)(object)item == (Object)null)) { Scene scene2 = ((Component)item).gameObject.scene; if (((Scene)(ref scene2)).IsValid()) { scene2 = ((Component)item).gameObject.scene; if (((Scene)(ref scene2)).handle == ((Scene)(ref scene)).handle) { if (!activeOnly || ((Component)item).gameObject.activeInHierarchy) { list.Add(item); } continue; } } } list2.Add(item); } RemoveStale(value, list2); return list; } internal static List SnapshotAll(bool activeOnly) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = new List(); foreach (T item in All) { if (!((Object)(object)item == (Object)null)) { Scene scene = ((Component)item).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { if (!activeOnly || ((Component)item).gameObject.activeInHierarchy) { list.Add(item); } continue; } } list2.Add(item); } for (int i = 0; i < list2.Count; i++) { Unregister(list2[i]); } return list; } private static void RemoveStale(HashSet values, List stale) { for (int i = 0; i < stale.Count; i++) { values.Remove(stale[i]); All.Remove(stale[i]); } } } internal static class SceneLifecycleHooks { private static readonly Harmony Harmony = new Harmony("dev.meepen.cardshopcoop.scene-lifecycle"); private static readonly Dictionary>> ReadyHandlers = new Dictionary>>(); private static readonly Dictionary>> GoneHandlers = new Dictionary>>(); private static readonly HashSet Patched = new HashSet(); internal static void Register(Type type, Action ready, Action gone) { if (!ReadyHandlers.TryGetValue(type, out var value)) { value = new List>(); ReadyHandlers.Add(type, value); } value.Add(ready); if (!GoneHandlers.TryGetValue(type, out var value2)) { value2 = new List>(); GoneHandlers.Add(type, value2); } value2.Add(gone); if (Patched.Add(type)) { Patch(type, "Awake", "ReadyPostfix"); Patch(type, "Start", "ReadyPostfix"); Patch(type, "Init", "ReadyPostfix"); Patch(type, "OnEnable", "ReadyPostfix"); Patch(type, "OnOpenScreen", "ReadyPostfix"); Patch(type, "OnDestroy", "GonePostfix"); } } private static void Patch(Type type, string methodName, string postfixName) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (!(methodInfo == null) && !methodInfo.IsStatic) { Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SceneLifecycleHooks), postfixName, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void ReadyPostfix(Component __instance) { Notify(ReadyHandlers, __instance); } private static void GonePostfix(Component __instance) { Notify(GoneHandlers, __instance); } private static void Notify(Dictionary>> handlers, Component instance) { if ((Object)(object)instance == (Object)null) { return; } foreach (KeyValuePair>> handler in handlers) { if (handler.Key.IsInstanceOfType(instance)) { List> value = handler.Value; for (int i = 0; i < value.Count; i++) { value[i](instance); } } } } } } namespace CardShopCoop.Net { public interface ICoopTransport : IDisposable { ConcurrentQueue Disconnects { get; } int ConnectionCount { get; } IReadOnlyList Connections { get; } double TimeoutSeconds { get; } event Action PeerConnected; bool TryDequeueIncoming(out InMsg message); void ActivateMessageIds(PeerConnection connection); void Send(PeerConnection connection, INetMessage message); void Broadcast(INetMessage message); double SecondsSinceLastRecv(PeerConnection connection); void Kick(PeerConnection connection, DisconnectInfo info = null); void GracefulDisconnect(PeerConnection connection, DisconnectInfo info = null); void Stop(); void PumpNetworkThread(); } internal interface ICoopHandshakeTransport { void SendHandshake(PeerConnection connection, INetMessage message); } internal interface ICoopStartable { void Start(); } internal interface ICoopRelayTransport { void Relay(INetMessage message, int exceptConnectionId); } public static class InviteCode { public const int MaxPasswordBytes = 32; private const byte Version = 1; private const int HeaderBytes = 8; private const int MinBase32Chars = 13; private const string Alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; private const int GroupSize = 4; public const int MaxCodeLength = 128; public static string Encode(string ip, int port, string password) { try { if (!IPAddress.TryParse((ip ?? "").Trim(), out IPAddress address)) { return null; } if (address.AddressFamily != AddressFamily.InterNetwork) { return null; } if (port <= 0 || port > 65535) { return null; } byte[] bytes = Encoding.UTF8.GetBytes(password ?? ""); if (bytes.Length > 32) { return null; } byte[] addressBytes = address.GetAddressBytes(); if (addressBytes.Length != 4) { return null; } byte[] array = new byte[8 + bytes.Length]; array[0] = 1; Buffer.BlockCopy(addressBytes, 0, array, 1, 4); array[5] = (byte)((port >> 8) & 0xFF); array[6] = (byte)(port & 0xFF); array[7] = (byte)bytes.Length; Buffer.BlockCopy(bytes, 0, array, 8, bytes.Length); return Group(ToBase32(array) + CheckChar(array)); } catch (Exception e) { Swallow.Log(e, "Encode", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/InviteCode.cs", 117); return null; } } public static bool TryParse(string code, out string ip, out int port, out string password) { ip = null; port = 0; password = ""; try { if (code == null || code.Length > 128) { return false; } StringBuilder stringBuilder = new StringBuilder(code.Length); for (int i = 0; i < code.Length; i++) { char c = char.ToUpperInvariant(code[i]); if (c != '-' && c != '_' && c != ' ' && c != '\t' && c != '\r' && c != '\n') { switch (c) { case 'O': c = '0'; break; case 'I': case 'L': c = '1'; break; } if ("0123456789ABCDEFGHJKMNPQRSTVWXYZ".IndexOf(c) < 0) { return false; } stringBuilder.Append(c); } } if (stringBuilder.Length < 14) { return false; } char c2 = stringBuilder[stringBuilder.Length - 1]; List list = new List(48); int num = 0; int num2 = 0; for (int j = 0; j < stringBuilder.Length - 1; j++) { num = (num << 5) | "0123456789ABCDEFGHJKMNPQRSTVWXYZ".IndexOf(stringBuilder[j]); num2 += 5; if (num2 >= 8) { num2 -= 8; list.Add((byte)((num >> num2) & 0xFF)); if (list.Count > 40) { return false; } } } byte[] array = list.ToArray(); if (CheckChar(array) != c2) { return false; } if (array.Length < 8) { return false; } if (array[0] != 1) { return false; } int num3 = (array[5] << 8) | array[6]; if (num3 <= 0 || num3 > 65535) { return false; } int num4 = array[7]; if (num4 > 32) { return false; } if (array.Length - 8 < num4) { return false; } string text = ""; if (num4 > 0) { text = Encoding.UTF8.GetString(array, 8, num4); } ip = $"{array[1]}.{array[2]}.{array[3]}.{array[4]}"; port = num3; password = text; return true; } catch { ip = null; port = 0; password = ""; return false; } } private static char CheckChar(byte[] payload) { uint num = 2166136261u; foreach (byte b in payload) { num ^= b; num *= 16777619; } return "0123456789ABCDEFGHJKMNPQRSTVWXYZ"[(int)(num >> 27)]; } private static string ToBase32(byte[] data) { StringBuilder stringBuilder = new StringBuilder((data.Length * 8 + 4) / 5); int num = 0; int num2 = 0; foreach (byte b in data) { num = (num << 8) | b; num2 += 8; while (num2 >= 5) { num2 -= 5; stringBuilder.Append("0123456789ABCDEFGHJKMNPQRSTVWXYZ"[(num >> num2) & 0x1F]); } } if (num2 > 0) { stringBuilder.Append("0123456789ABCDEFGHJKMNPQRSTVWXYZ"[(num << 5 - num2) & 0x1F]); } return stringBuilder.ToString(); } private static string Group(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + s.Length / 4); for (int i = 0; i < s.Length; i++) { if (i > 0 && i % 4 == 0) { stringBuilder.Append('-'); } stringBuilder.Append(s[i]); } return stringBuilder.ToString(); } } public struct LobbyRow { public ulong Id; public string Name; public int Players; public int Max; public bool HasPw; public string Ver; } public static class SteamLobbyLimits { public const int DefaultPlayers = 4; public const int MinPlayers = 2; public const int MaxPlayers = 250; } public interface ISteamBridge { string LocalPersonaName { get; } ulong LocalSteamId { get; } bool ListRefreshing { get; } List Lobbies { get; } Action OnError { get; set; } Action OnLobbyLive { get; set; } Action OnConnectedToHost { get; set; } Action OnJoinFailed { get; set; } Action OnSessionFailed { get; set; } Action OnInviteAccepted { get; set; } void Init(); bool SteamAvailable(); string FriendNickname(ulong steamId); ICoopTransport CreateTransport(bool isHost); void Host(bool isPublic, string lobbyName, bool hasPassword, int maxPlayers); long Join(ulong lobbyId); void Leave(); void OpenInviteDialog(); void RefreshList(); } public static class PlatformProbe { private static int _state; private static Assembly _gameAsm; private static bool _gameAsmSearched; private static FieldInfo _saveIndexField; private static FieldInfo _saveCycleField; private static bool _counterSearched; private static int _gpState; public static bool SteamworksPresent { get { if (_state == 0) { _state = (Detect() ? 1 : 2); } return _state == 1; } } public static bool GamePassBuild { get { if (_gpState == 0) { _gpState = (DetectGamecore() ? 1 : 2); } return _gpState == 1; } } public static string SaveBackendDescription { get { int index; int cycle; bool flag = TrySampleSaveCounter(out index, out cycle); return (GamePassBuild ? "Xbox containers (Game Pass)" : "local files") + (flag ? " [save-completion counter available]" : " [save-completion counter UNAVAILABLE - unknown build, falling back to the Gamecore type probe]"); } } private static bool Detect() { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { if (string.Equals(assemblies[i].GetName().Name, "com.rlabrecque.steamworks.net", StringComparison.OrdinalIgnoreCase)) { return true; } } } catch (Exception e) { Swallow.Log(e, "Detect", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/ISteamBridge.cs", 210); } try { if (Type.GetType("Steamworks.SteamAPI, com.rlabrecque.steamworks.net", throwOnError: false) != null) { return true; } } catch (Exception e2) { Swallow.Log(e2, "Detect", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/ISteamBridge.cs", 224); } return false; } private static Assembly GameAssembly() { if (!_gameAsmSearched) { _gameAsmSearched = true; try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (string.Equals(assembly.GetName().Name, "Assembly-CSharp", StringComparison.OrdinalIgnoreCase)) { _gameAsm = assembly; break; } } } catch (Exception e) { Swallow.Log(e, "GameAssembly", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/ISteamBridge.cs", 282); } } return _gameAsm; } public static bool TrySampleSaveCounter(out int index, out int cycle) { index = 0; cycle = 0; try { if (!_counterSearched) { _counterSearched = true; Type type = GameAssembly()?.GetType("CPlayerData"); if (type != null) { _saveIndexField = ReflectionSurface.OptionalField(type, "m_SaveIndex"); _saveCycleField = ReflectionSurface.OptionalField(type, "m_SaveCycle"); } } if (_saveIndexField == null || _saveCycleField == null) { return false; } index = (int)_saveIndexField.GetValue(null); cycle = (int)_saveCycleField.GetValue(null); return true; } catch { index = 0; cycle = 0; return false; } } private static bool DetectGamecore() { Assembly assembly = GameAssembly(); if (assembly == null) { return false; } Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } catch (Exception e) { Swallow.Log(e, "DetectGamecore", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/ISteamBridge.cs", 374); return false; } if (types == null) { return false; } Type[] array = types; foreach (Type type in array) { if (type == null) { continue; } try { if (type.Name.IndexOf("Gamecore", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } catch (Exception e2) { Swallow.Log(e2, "DetectGamecore", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/ISteamBridge.cs", 394); } } return false; } } public static class SteamBridge { [MethodImpl(MethodImplOptions.NoInlining)] public static ISteamBridge TryCreate() { if (!PlatformProbe.SteamworksPresent) { return null; } try { return Create(); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Steam bridge unavailable: " + ex.Message)); return null; } } [MethodImpl(MethodImplOptions.NoInlining)] private static ISteamBridge Create() { return new SteamBridgeImpl(); } } public sealed class LagTransport : ICoopTransport, IDisposable, ICoopHandshakeTransport, ICoopRelayTransport, ICoopStartable { private struct Pending { public long ReleaseAt; public InMsg Message; public int Bytes; } private struct ReadyMessage { public InMsg Message; public int Bytes; } private const int MaxDelayMs = 60000; private const int MaxBufferedFrames = 20000; private const long MaxBufferedBytes = 67108864L; private static readonly double TicksPerMs = (double)Stopwatch.Frequency / 1000.0; private readonly ICoopTransport _inner; private readonly ConcurrentQueue _incoming = new ConcurrentQueue(); private readonly object _incomingGate = new object(); private readonly Queue _delay = new Queue(); private readonly Random _random = new Random(); private bool _overflowWarned; private long _delayBytes; private long _incomingBytes; internal int DelayCount => _delay.Count; internal int InnerReassemblyCount => 0; internal int InnerReliableOutboxCount => 0; internal int InnerTransientOutboxCount => 0; public ConcurrentQueue Disconnects => _inner.Disconnects; public int ConnectionCount => _inner.ConnectionCount; public double TimeoutSeconds => _inner.TimeoutSeconds; public IReadOnlyList Connections => _inner.Connections; public event Action PeerConnected { add { _inner.PeerConnected += value; } remove { _inner.PeerConnected -= value; } } private LagTransport(ICoopTransport inner) { if (inner == null) { throw new ArgumentNullException("inner"); } _inner = inner; } public static ICoopTransport Wrap(ICoopTransport inner) { if (inner == null || inner is LagTransport) { return inner; } return new LagTransport(inner); } public bool TryDequeueIncoming(out InMsg message) { lock (_incomingGate) { if (!_incoming.TryDequeue(out var result)) { message = default(InMsg); return false; } _incomingBytes -= result.Bytes; if (_incomingBytes < 0) { _incomingBytes = 0L; } message = result.Message; return true; } } public void Send(PeerConnection connection, INetMessage message) { _inner.Send(connection, message); } public void Broadcast(INetMessage message) { _inner.Broadcast(message); } void ICoopHandshakeTransport.SendHandshake(PeerConnection connection, INetMessage message) { ((_inner as ICoopHandshakeTransport) ?? throw new InvalidOperationException("The wrapped transport has no handshake seam")).SendHandshake(connection, message); } void ICoopRelayTransport.Relay(INetMessage message, int exceptConnectionId) { ((_inner as ICoopRelayTransport) ?? throw new InvalidOperationException("The wrapped transport has no relay seam")).Relay(message, exceptConnectionId); } public void ActivateMessageIds(PeerConnection connection) { _inner.ActivateMessageIds(connection); } public double SecondsSinceLastRecv(PeerConnection connection) { return _inner.SecondsSinceLastRecv(connection); } public void Kick(PeerConnection connection, DisconnectInfo info = null) { _inner.Kick(connection, info); } public void GracefulDisconnect(PeerConnection connection, DisconnectInfo info = null) { _inner.GracefulDisconnect(connection, info); } public void Start() { if (_inner is ICoopStartable coopStartable) { coopStartable.Start(); return; } throw new InvalidOperationException("The wrapped transport cannot be started."); } public void Stop() { _inner.Stop(); lock (_incomingGate) { ReadyMessage result; while (_incoming.TryDequeue(out result)) { } _incomingBytes = 0L; } _delay.Clear(); _delayBytes = 0L; _overflowWarned = false; } public void PumpNetworkThread() { _inner.PumpNetworkThread(); int num = ClampMs((CoopPlugin.ArtificialLagMs != null) ? CoopPlugin.ArtificialLagMs.Value : 0); int num2 = ClampMs((CoopPlugin.ArtificialJitterMs != null) ? CoopPlugin.ArtificialJitterMs.Value : 0); if (num == 0 && num2 == 0) { while (_delay.Count > 0) { Pending pending = _delay.Dequeue(); _delayBytes = Math.Max(0L, _delayBytes - pending.Bytes); EnqueueReady(pending.Message, pending.Bytes); } _delayBytes = 0L; _overflowWarned = false; InMsg message; while (_inner.TryDequeueIncoming(out message)) { EnqueueReceived(message); } return; } long timestamp = Stopwatch.GetTimestamp(); InMsg message2; while (_inner.TryDequeueIncoming(out message2)) { if (message2.Message is DisconnectMessage) { DropDelayedConnection(message2.Connection); EnqueueReceived(message2); continue; } if (_delay.Count >= 20000 || _delayBytes >= 67108864) { if (!_overflowWarned) { _overflowWarned = true; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("LagTransport: delay buffer reached " + 20000 + " frames - releasing the oldest frames without delay")); } } Pending pending2 = _delay.Dequeue(); EnqueueReady(pending2.Message, pending2.Bytes); _delayBytes = Math.Max(0L, _delayBytes - pending2.Bytes); } int num3 = ((num2 > 0) ? _random.Next(-num2, num2 + 1) : 0); int num4 = num + num3; if (num4 < 0) { num4 = 0; } int num5 = EstimateBytes(message2); _delay.Enqueue(new Pending { ReleaseAt = timestamp + (long)((double)num4 * TicksPerMs), Message = message2, Bytes = num5 }); _delayBytes += num5; } while (_delay.Count > 0 && _delay.Peek().ReleaseAt <= timestamp) { Pending pending3 = _delay.Dequeue(); EnqueueReady(pending3.Message, pending3.Bytes); _delayBytes = Math.Max(0L, _delayBytes - pending3.Bytes); } if (_delay.Count < 10000 && _delayBytes < 33554432) { _overflowWarned = false; } } private void EnqueueReceived(InMsg message) { EnqueueReady(message, EstimateBytes(message)); } private void EnqueueReady(InMsg message, int bytes) { lock (_incomingGate) { bool flag = message.Message is DisconnectMessage || message.Message is ByeMessage; ReadyMessage result; while (flag && _incoming.Count > 0 && (_incoming.Count >= 20000 || _incomingBytes > 67108864L - (long)bytes) && _incoming.TryDequeue(out result)) { _incomingBytes = Math.Max(0L, _incomingBytes - result.Bytes); } if (_incoming.Count >= 20000 || bytes < 7 || _incomingBytes > 67108864L - (long)bytes) { if (!_overflowWarned) { _overflowWarned = true; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)"LagTransport: ready incoming queue reached its bounded admission budget"); } } } else { _incoming.Enqueue(new ReadyMessage { Message = message, Bytes = bytes }); _incomingBytes += bytes; } } } private void DropDelayedConnection(PeerConnection connection) { if (connection == null || _delay.Count == 0) { return; } Queue queue = new Queue(_delay.Count); while (_delay.Count > 0) { Pending item = _delay.Dequeue(); if (item.Message.Connection == null || item.Message.Connection != connection) { queue.Enqueue(item); } else { _delayBytes = Math.Max(0L, _delayBytes - item.Bytes); } } while (queue.Count > 0) { _delay.Enqueue(queue.Dequeue()); } } private static int ClampMs(int value) { if (value < 0) { return 0; } if (value <= 60000) { return value; } return 60000; } private static int EstimateBytes(InMsg message) { if (message.Message != null) { return NetMessageCodec.Encode(message.Message).Length; } return 7; } public void Dispose() { lock (_incomingGate) { ReadyMessage result; while (_incoming.TryDequeue(out result)) { } _incomingBytes = 0L; } _delay.Clear(); _delayBytes = 0L; _inner.Dispose(); } } public static class MessageRegistry { internal const int MaxSerializedPayloadBytes = 67100672; public static ProtocolSnapshot CurrentSnapshot { get { EnsureInitialized(); return ProtocolRegistry.Default.CurrentSnapshot; } } static MessageRegistry() { ProtocolRegistry.EnsureCoreAssemblyRegistered(typeof(MessageRegistry).Assembly); } public static IProtocolRegistration RegisterAssembly(Assembly assembly, ProtocolRegistrationOwner owner) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterAssembly(assembly, owner); } public static IProtocolRegistration RegisterAssembly(Assembly assembly, string ownerId) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterAssembly(assembly, ownerId); } public static IProtocolRegistration RegisterMessage(MessageDescriptor descriptor, ProtocolRegistrationOwner owner) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterMessage(descriptor, owner); } public static IProtocolRegistration RegisterMessage(MessageDescriptor descriptor, string ownerId) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterMessage(descriptor, ownerId); } public static IProtocolRegistration RegisterHandler(Type messageType, ProtocolMessageHandler handler, ProtocolRegistrationOwner owner) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterHandler(messageType, handler, owner); } public static IProtocolRegistration RegisterHandler(Type messageType, ProtocolMessageHandler handler, string ownerId) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterHandler(messageType, handler, ownerId); } public static IProtocolRegistration RegisterHandlers(IReadOnlyList registrations, ProtocolRegistrationOwner owner) { EnsureInitialized(); return ProtocolRegistry.Default.RegisterHandlers(registrations, owner); } public static IProtocolSessionLease BeginSession() { EnsureInitialized(); return ProtocolRegistry.Default.BeginSession(); } internal static void EnsureInitialized() { } public static INetMessage Deserialize(string name, byte[] payload, int offset, int count, out Type type) { EnsureInitialized(); if (!ProtocolRegistry.Default.CurrentSnapshot.TryGet(name, out var descriptor)) { Msg.LogDecodeWarning(name, "network: no DTO registered for message " + name + " - frame dropped"); throw new InvalidDataException("No DTO registered for " + name); } type = descriptor.MessageType; return Deserialize(descriptor, payload, offset, count); } internal static INetMessage Deserialize(MessageDescriptor descriptor, byte[] payload, int offset, int count) { EnsureInitialized(); if (descriptor == null) { throw new ArgumentNullException("descriptor"); } if (payload == null || offset < 0 || count < 0 || offset > payload.Length - count) { throw new InvalidDataException("Invalid payload bounds for " + descriptor.WireName); } if (count > 67100672) { Msg.LogDecodeWarning(descriptor.WireName, "network: payload for " + descriptor.WireName + " exceeds the protocol limit - frame dropped"); throw new InvalidDataException("Payload exceeds the protocol limit for " + descriptor.WireName); } return WireCodec.Deserialize(descriptor.MessageType, payload, offset, count); } public static byte[] Serialize(INetMessage message) { EnsureInitialized(); if (message == null) { throw new ArgumentNullException("message"); } if (!ProtocolRegistry.Default.CurrentSnapshot.TryGet(((object)message).GetType(), out var descriptor)) { throw new InvalidDataException("No network message registration for " + ((object)message).GetType().FullName); } byte[] array = WireCodec.Serialize(message); if (array.Length > 67100672) { throw new InvalidDataException("Payload exceeds the protocol limit for " + descriptor.WireName); } return array; } public static string NameFor(INetMessage message) { EnsureInitialized(); if (message == null || !ProtocolRegistry.Default.CurrentSnapshot.TryGet(((object)message).GetType(), out var descriptor)) { throw new InvalidDataException("No network message registration for " + ((message == null) ? "null" : ((object)message).GetType().FullName)); } return descriptor.WireName; } public static bool TryGetDescriptor(string name, out MessageDescriptor descriptor) { EnsureInitialized(); return ProtocolRegistry.Default.CurrentSnapshot.TryGet(name, out descriptor); } internal static bool TryGetMessageType(string name, out Type type) { EnsureInitialized(); if (ProtocolRegistry.Default.CurrentSnapshot.TryGet(name, out var descriptor)) { type = descriptor.MessageType; return true; } type = null; return false; } } public sealed class MessageContext { public PeerConnection Connection; public bool InGame; public ICoopTransport Transport; internal int WorkCost = 1; public int ConnectionId { get { if (Connection != null) { return Connection.Id; } return 0; } } public bool IsAuthenticated { get { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if (Connection != null) { if ((int)Connection.State != 2) { return (int)Connection.State == 3; } return true; } return false; } } } internal sealed class ReliableMessageHandlerException : Exception { internal Type MessageType { get; } internal ReliableMessageHandlerException(Type messageType, Exception innerException) : base("Reliable handler failed for " + (messageType?.FullName ?? ""), innerException) { MessageType = messageType; } } public sealed class MessageRouter : ICoopMessageRegistry { private sealed class TargetRegistration { public object Target; public IProtocolRegistration Registration; } private readonly List _targetRegistrations = new List(); private readonly object _targetLock = new object(); public event Action Rejected; public MessageRouter() { MessageRegistry.EnsureInitialized(); } public void RegisterAttributedHandlers(object target) { RegisterAttributedHandlers(target, coreOwner: false); } internal void RegisterCoreAttributedHandlers(object target) { RegisterAttributedHandlers(target, coreOwner: true); } private void RegisterAttributedHandlers(object target, bool coreOwner) { if (target == null) { throw new ArgumentNullException("target"); } MessageRegistry.EnsureInitialized(); MethodInfo[] methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); List list = new List(); HashSet hashSet = new HashSet(); foreach (MethodInfo method in methods) { MessageHandlerAttribute customAttribute = ((MemberInfo)method).GetCustomAttribute(); if (customAttribute != null) { ParameterInfo[] parameters = method.GetParameters(); bool flag = parameters.Length != 0 && parameters[0].ParameterType == typeof(CoopMessageContext); if (method.IsStatic || method.ReturnType != typeof(void) || parameters.Length != 2 || (parameters[0].ParameterType != typeof(MessageContext) && !flag) || !typeof(INetMessage).IsAssignableFrom(parameters[1].ParameterType) || parameters[1].ParameterType != customAttribute.MessageType) { throw new InvalidOperationException("Invalid network handler signature: " + method.DeclaringType?.FullName + "." + method.Name); } Type parameterType = parameters[1].ParameterType; if (!hashSet.Add(parameterType)) { throw new InvalidOperationException("Duplicate network handler for " + parameterType.FullName + " in one target"); } if (!ProtocolRegistry.Default.TryGetRegisteredDescriptor(parameterType, out var _)) { throw new InvalidOperationException("No registered network descriptor for " + parameterType.FullName); } list.Add(new ProtocolHandlerRegistration(parameterType, flag ? ((ProtocolMessageHandler)delegate(MessageContext context, INetMessage message) { method.Invoke(target, new object[2] { ToPublicContext(context), message }); }) : ((ProtocolMessageHandler)delegate(MessageContext context, INetMessage message) { method.Invoke(target, new object[2] { context, message }); }))); } } if (list.Count == 0) { return; } ProtocolRegistrationOwner owner = (coreOwner ? ProtocolRegistry.CoreOwner : ProtocolRegistry.CreateOwner("handlers:" + target.GetType().Assembly.GetName().Name)); lock (_targetLock) { IProtocolRegistration registration = ProtocolRegistry.Default.RegisterHandlers(list, owner); _targetRegistrations.Add(new TargetRegistration { Target = target, Registration = registration }); } } public IProtocolRegistration RegisterHandler(Type messageType, ProtocolMessageHandler handler, ProtocolRegistrationOwner owner = null) { if (owner == null) { owner = ProtocolRegistry.CreateOwner("router:explicit"); } return ProtocolRegistry.Default.RegisterHandler(messageType, handler, owner); } public void UnregisterAttributedHandlers(object target) { if (target == null) { return; } List list = null; lock (_targetLock) { for (int num = _targetRegistrations.Count - 1; num >= 0; num--) { if (_targetRegistrations[num].Target == target) { if (list == null) { list = new List(); } list.Add(_targetRegistrations[num].Registration); _targetRegistrations.RemoveAt(num); } } } if (list != null) { for (int i = 0; i < list.Count; i++) { list[i].Dispose(); } } } private static CoopMessageContext ToPublicContext(MessageContext context) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new CoopMessageContext(context.Connection, context.InGame); } public bool Dispatch(MessageContext context, INetMessage message) { return Dispatch(context, message, corePath: false); } internal bool DispatchCore(MessageContext context, INetMessage message) { return Dispatch(context, message, corePath: true); } public bool IsRegistered(INetMessage message) { if (message == null) { return false; } ProtocolHandlerBinding handler; return ProtocolRegistry.Default.CurrentSnapshot.TryGetHandler(((object)message).GetType(), out handler); } private bool Dispatch(MessageContext context, INetMessage message, bool corePath) { //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Expected O, but got Unknown if (context == null) { return Reject(null, null, "message context is null", context); } if (message == null) { return Reject(null, null, "message is null", context); } ProtocolSnapshot currentSnapshot = ProtocolRegistry.Default.CurrentSnapshot; if (!currentSnapshot.TryGet(((object)message).GetType(), out var descriptor)) { return Reject(((object)message).GetType().FullName, ((object)message).GetType(), "no descriptor in the current protocol snapshot", context); } if (!currentSnapshot.TryGetHandler(((object)message).GetType(), out var handler)) { Msg.LogDecodeWarning(descriptor.WireName, "coop: no handler bound for " + descriptor.WireName + " on connection " + context.ConnectionId + "; message dropped (module disabled or RegisterAttributedHandlers was not called)"); return Reject(descriptor.WireName, descriptor.MessageType, "no runtime handler is bound", context); } ProtocolAuthorizationResult protocolAuthorizationResult = ProtocolAuthorization.Authorize(context, corePath && handler.IsCoreOwner); if (!protocolAuthorizationResult.Allowed) { return Reject(descriptor.WireName, descriptor.MessageType, protocolAuthorizationResult.Reason, context); } long start = PerfProbe.StartHandlerMetric(); bool flag = false; try { handler.Invoke(context, message); flag = true; return true; } catch (Exception ex) { string wireName = descriptor.WireName; if ((int)descriptor.Reliability == 0) { CoopPlugin.Log.LogError((object)("reliable handler failed for " + wireName + " on connection " + context.ConnectionId + "; requesting session recovery: " + ex)); context.Transport?.GracefulDisconnect(context.Connection, new DisconnectInfo("reliable message handler failed; session recovery required", false, "handler_failed", true, context.Connection.State)); throw new ReliableMessageHandlerException(descriptor.MessageType, ex); } CoopPlugin.Log.LogWarning((object)("transient handler failed for " + wireName + " on connection " + context.ConnectionId + "; dropping message: " + ex)); return false; } finally { PerfProbe.EndHandlerMetric(descriptor.MessageType, context.WorkCost, start, !flag); } } private bool Reject(string wireName, Type messageType, string reason, MessageContext context) { MessageRejection messageRejection = new MessageRejection(wireName, messageType, reason, context); this.Rejected?.Invoke(messageRejection); ProtocolRegistry.Default.RaiseRejection(messageRejection); return false; } } public struct InMsg { public PeerConnection Connection; public INetMessage Message; internal bool ReceivedBeforeMessageIdActivation; public Type MessageType => ((object)Message)?.GetType(); } public static class Msg { public const int FrameHeaderSize = 4; public const int TokenKindSize = 1; public const int NameLengthSize = 4; public const int CompactIdSize = 2; public const int MinimumDeclaredFrameSize = 3; public const int MinimumFrameSize = 7; public const byte NamedTokenKind = 0; public const byte CompactTokenKind = 1; public const int MaxMessageNameBytes = 4096; public const int MaxFrameSize = 67108864; public static readonly int WireVersion = DeriveWireVersion("2.0.6"); private const int DecodeWarningCooldownMs = 2000; private const int DecodeWarningBucketCount = 64; private static readonly int[] LastDecodeWarning = CreateDecodeWarningBuckets(); private static readonly byte[] EmptyGzip = new byte[20] { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private static int[] CreateDecodeWarningBuckets() { int[] array = new int[64]; for (int i = 0; i < array.Length; i++) { array[i] = int.MinValue; } return array; } internal static void LogDecodeWarning(string name, string warning) { int decodeWarningBucket = GetDecodeWarningBucket(name); int tickCount = Environment.TickCount; int num; do { num = Thread.VolatileRead(ref LastDecodeWarning[decodeWarningBucket]); if (num != int.MinValue && tickCount - num < 2000) { return; } } while (Interlocked.CompareExchange(ref LastDecodeWarning[decodeWarningBucket], tickCount, num) != num); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)warning); } } private static int GetDecodeWarningBucket(string name) { uint num = 2166136261u; if (name != null) { for (int i = 0; i < name.Length; i++) { num ^= name[i]; num *= 16777619; } } return (int)(num % 64); } private static int DeriveWireVersion(string version) { if (!Version.TryParse(version, out Version result) || result.Major < 0 || result.Minor < 0 || result.Major > 999 || result.Minor > 999) { throw new InvalidOperationException("CardShopCoopCommunity version must have major and minor components from 0 to 999: " + version); } return result.Major * 100 + result.Minor; } public static bool TryDecodeFrame(byte[] frame, int offset, int count, PeerConnection connection, int maxFrame, out InMsg message) { return TryDecodeFrame(frame, offset, count, connection, maxFrame, MessageRegistry.CurrentSnapshot, compactIds: false, out message); } public static bool TryDecodeFrame(byte[] frame, int offset, int count, PeerConnection connection, int maxFrame, ProtocolSnapshot snapshot, bool compactIds, out InMsg message) { message = default(InMsg); if (!TryResolveFrame(frame, offset, count, maxFrame, out var descriptor, out var name, out var payloadOffset, out var payloadLength, snapshot, compactIds)) { return false; } message = new InMsg { Connection = connection }; long start = PerfProbe.StartThreadMetric(); try { message.Message = MessageRegistry.Deserialize(descriptor, frame, payloadOffset, payloadLength); } catch (Exception ex) { LogDecodeWarning(name, "discarding malformed " + name + " frame: " + ex.GetType().Name + ": " + ex.Message); return false; } finally { PerfProbe.EndThreadMetric("net.decode.", descriptor.MessageType, start); } return true; } public static bool TryGetMessageType(byte[] frame, out Type type) { return TryGetMessageType(frame, compactIds: false, out type); } public static bool TryGetMessageType(byte[] frame, bool compactIds, out Type type) { type = null; if (!TryResolveFrame(frame, 0, (frame != null) ? frame.Length : 0, 67108864, out var descriptor, out var _, out var _, out var _, MessageRegistry.CurrentSnapshot, compactIds)) { return false; } type = descriptor.MessageType; return true; } private static bool TryResolveFrame(byte[] frame, int offset, int count, int maxFrame, out MessageDescriptor descriptor, out string name, out int payloadOffset, out int payloadLength, ProtocolSnapshot snapshot, bool compactIds) { descriptor = null; name = null; payloadOffset = 0; payloadLength = 0; if (snapshot == null || frame == null || offset < 0 || count < 7 || offset > frame.Length - count || maxFrame < 3 || maxFrame > 67108864) { return false; } int num = BitConverter.ToInt32(frame, offset); if (num < 3 || num > maxFrame || num != count - 4) { return false; } int num2 = offset + 4; byte b = frame[num2]; int num3 = num2 + 1; switch (b) { case 0: { int num4 = 5; if (num < num4) { return false; } int num5 = BitConverter.ToInt32(frame, num3); if (num5 <= 0 || num5 > 4096 || num5 > num - num4) { return false; } int num6 = num3 + 4; try { name = WireCodec.StrictUtf8.GetString(frame, num6, num5); } catch (DecoderFallbackException) { return false; } if (!snapshot.TryGet(name, out descriptor)) { LogDecodeWarning(name, "network: no DTO registered for message " + name + " - frame dropped"); return false; } payloadOffset = num6 + num5; payloadLength = num - num4 - num5; if (!IsPayloadWithinLimit(name, payloadLength)) { return false; } return true; } case 1: { if (num < 3) { return false; } ushort id = BitConverter.ToUInt16(frame, num3); if (!compactIds) { LogDecodeWarning("compact:" + id, "network: compact message ID " + id + " received before message IDs were activated - frame dropped"); return false; } if (!snapshot.IsFrozen) { LogDecodeWarning("compact", "network: compact message ID " + id + " received before the protocol snapshot was frozen - frame dropped"); return false; } if (!snapshot.TryGet(id, out descriptor)) { LogDecodeWarning("compact:" + id, "network: unknown compact message ID " + id + " for protocol snapshot generation " + snapshot.Generation + " - frame dropped"); return false; } name = descriptor.WireName; payloadOffset = num3 + 2; payloadLength = num - 3; if (!IsPayloadWithinLimit(name, payloadLength)) { return false; } return true; } default: LogDecodeWarning("token:" + b, "network: unknown message token kind " + b + " - frame dropped"); return false; } } private static bool IsPayloadWithinLimit(string name, int payloadLength) { if (payloadLength >= 0 && payloadLength <= 67100672) { return true; } LogDecodeWarning(name, "network: payload for " + (name ?? "") + " exceeds the protocol limit - frame dropped"); return false; } public static byte[] Gzip(byte[] data) { if (data == null) { throw new ArgumentNullException("data"); } if (data.Length == 0) { return EmptyGzip; } using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Fastest)) { gZipStream.Write(data, 0, data.Length); } return memoryStream.ToArray(); } public static byte[] Gunzip(byte[] data) { using MemoryStream stream = new MemoryStream(data, writable: false); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); gZipStream.CopyTo(memoryStream); return memoryStream.ToArray(); } } public static class NetHelpers { private const string Description = "CardShopCoopCommunity"; private const int LeaseSeconds = 86400; private const int HttpTimeoutMs = 4000; private const int MaxHttpBytes = 524288; private const int SsdpTimeoutMs = 3000; private const int StunTimeoutMs = 2000; private const string StunHost = "stun.l.google.com"; private const string StunFallbackIp = "74.125.250.129"; private const int StunDnsTimeoutMs = 2000; private static readonly byte[] MagicCookie = new byte[4] { 33, 18, 164, 66 }; private static readonly object MapLock = new object(); private static volatile string _controlUrl; private static volatile string _serviceType; private static volatile int _mappedPort; private static long _mapEpoch; private static volatile bool _noGateway; public static bool HasMapping => _mappedPort != 0; public static void BeginDiscovery() { if (_controlUrl == null) { _noGateway = false; } } public static bool TryMapPort(int port, out string reason) { long epoch; return TryMapPort(port, out reason, out epoch); } public static bool TryMapPort(int port, out string reason, out long epoch) { reason = null; epoch = 0L; lock (MapLock) { try { if (port <= 0 || port > 65535) { reason = "the co-op port is out of range"; return false; } if (!EnsureGateway()) { reason = "no UPnP router answered"; return false; } string text = LocalIPv4ForGateway(); if (text == null) { reason = "couldn't work out this PC's LAN address"; return false; } if (AddMapping(port, text, 86400)) { _mappedPort = port; epoch = ++_mapEpoch; CoopPlugin.Log.LogInfo((object)$"UPnP: asked the router to forward UDP {port} to {text} for {86400}s (this is a REQUEST - double NAT can still block the port)"); return true; } reason = "the router refused the port mapping"; return false; } catch (Exception ex) { reason = ex.Message; CoopPlugin.Log.LogWarning((object)("UPnP map failed: " + ex.GetType().Name + ": " + ex.Message)); return false; } } } public static void RemoveMapping() { RemoveMapping(0L); } public static void RemoveMapping(long onlyEpoch) { lock (MapLock) { if (onlyEpoch != 0L && onlyEpoch != _mapEpoch) { CoopPlugin.Log.LogInfo((object)"UPnP: leaving the port mapping alone - a newer session owns it now"); return; } int mappedPort = _mappedPort; if (mappedPort == 0) { return; } _mappedPort = 0; try { if (_controlUrl != null) { string innerXml = "" + mappedPort + "UDP"; if (Soap("DeletePortMapping", innerXml, out var _)) { CoopPlugin.Log.LogInfo((object)$"UPnP: removed the port {mappedPort} mapping"); } else { CoopPlugin.Log.LogWarning((object)$"UPnP: could not remove the port {mappedPort} mapping - it should expire with its {86400}s lease, but check your router's port-forwarding page if you want it gone now"); } } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("UPnP unmap failed: " + ex.Message + " - the mapping should expire with its lease; check your router if you want it gone now")); } } } private static bool AddMapping(int port, string localIp, int lease) { string innerXml = "" + port + "UDP" + port + "" + localIp + "1CardShopCoopCommunity" + lease + ""; if (!Soap("AddPortMapping", innerXml, out var body)) { return false; } if (body != null) { return body.IndexOf("errorCode", StringComparison.OrdinalIgnoreCase) < 0; } return true; } public static string UpnpExternalIp() { try { if (!EnsureGateway()) { return null; } if (!Soap("GetExternalIPAddress", "", out var body) || body == null) { return null; } string text = TagValue(body, "NewExternalIPAddress"); if (text == null) { return null; } text = text.Trim(); if (!IPAddress.TryParse(text, out IPAddress address)) { return null; } if (address.AddressFamily != AddressFamily.InterNetwork) { return null; } CoopPlugin.Log.LogInfo((object)("UPnP: router reports external address " + text)); return text; } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)("UPnP external address failed: " + ex.Message)); return null; } } public static string StunPublicIp() { IPEndPoint iPEndPoint = ResolveStun(); if (iPEndPoint == null) { return null; } for (int i = 0; i < 2; i++) { UdpClient udpClient = null; try { byte[] array = new byte[12]; using (RNGCryptoServiceProvider rNGCryptoServiceProvider = new RNGCryptoServiceProvider()) { rNGCryptoServiceProvider.GetBytes(array); } byte[] array2 = new byte[20] { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; Buffer.BlockCopy(MagicCookie, 0, array2, 4, 4); Buffer.BlockCopy(array, 0, array2, 8, 12); udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); udpClient.Client.ReceiveTimeout = 2000; udpClient.Send(array2, array2.Length, iPEndPoint); IPEndPoint remoteEP = null; string text = ParseStun(udpClient.Receive(ref remoteEP), array); if (text != null) { CoopPlugin.Log.LogInfo((object)("STUN: public address " + text)); return text; } } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)$"STUN attempt {i + 1} failed: {ex.GetType().Name}: {ex.Message}"); } finally { try { udpClient?.Close(); } catch (Exception e) { Swallow.Log(e, "StunPublicIp", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 350); } } } return null; } private static IPEndPoint ResolveStun() { IPAddress found = null; try { Thread thread = new Thread((ThreadStart)delegate { try { IPAddress[] hostAddresses = Dns.GetHostAddresses("stun.l.google.com"); foreach (IPAddress iPAddress in hostAddresses) { if (iPAddress.AddressFamily == AddressFamily.InterNetwork) { found = iPAddress; break; } } } catch (Exception e) { Swallow.Log(e, "ResolveStun", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 379); } }); thread.IsBackground = true; thread.Name = "CoopStunDns"; thread.Start(); if (!thread.Join(2000)) { CoopPlugin.Log.LogInfo((object)"STUN: DNS did not answer in time - using the fallback address"); } } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)("STUN: DNS lookup could not be started (" + ex.Message + ")")); } IPAddress address = found; if (address == null && !IPAddress.TryParse("74.125.250.129", out address)) { return null; } return new IPEndPoint(address, 19302); } private static string ParseStun(byte[] r, byte[] txn) { if (r == null || r.Length < 20) { return null; } if (r[0] != 1 || r[1] != 1) { return null; } for (int i = 0; i < 4; i++) { if (r[4 + i] != MagicCookie[i]) { return null; } } for (int j = 0; j < 12; j++) { if (r[8 + j] != txn[j]) { return null; } } int num = Math.Min(r.Length, 20 + ((r[2] << 8) | r[3])); int num2 = 20; while (num2 + 4 <= num) { int num3 = (r[num2] << 8) | r[num2 + 1]; int num4 = (r[num2 + 2] << 8) | r[num2 + 3]; int num5 = num2 + 4; if (num4 < 0 || num5 + num4 > num) { break; } if ((num3 == 32 || num3 == 32800) && num4 >= 8 && r[num5 + 1] == 1) { byte[] array = new byte[4]; for (int k = 0; k < 4; k++) { array[k] = (byte)(r[num5 + 4 + k] ^ MagicCookie[k]); } return $"{array[0]}.{array[1]}.{array[2]}.{array[3]}"; } num2 = num5 + num4; num2 += (4 - (num4 & 3)) & 3; } return null; } public static bool IsPublicIPv4(string ip) { try { if (!IPAddress.TryParse((ip ?? "").Trim(), out IPAddress address)) { return false; } if (address.AddressFamily != AddressFamily.InterNetwork) { return false; } byte[] addressBytes = address.GetAddressBytes(); if (addressBytes[0] == 0 || addressBytes[0] == 127) { return false; } if (addressBytes[0] == 10) { return false; } if (addressBytes[0] == 100 && addressBytes[1] >= 64 && addressBytes[1] <= 127) { return false; } if (addressBytes[0] == 172 && addressBytes[1] >= 16 && addressBytes[1] <= 31) { return false; } if (addressBytes[0] == 192 && addressBytes[1] == 168) { return false; } if (addressBytes[0] == 169 && addressBytes[1] == 254) { return false; } if (addressBytes[0] >= 224) { return false; } return true; } catch (Exception e) { Swallow.Log(e, "IsPublicIPv4", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 523); return false; } } public static string LocalIPv4() { try { string result = null; int num = int.MaxValue; NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback) { continue; } foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) { if (unicastAddress.Address.AddressFamily != AddressFamily.InterNetwork) { continue; } string text = unicastAddress.Address.ToString(); if (!text.StartsWith("169.254")) { int num2 = ((!text.StartsWith("192.168.")) ? (text.StartsWith("10.") ? 1 : (text.StartsWith("172.") ? 2 : 3)) : 0); if (num2 < num) { num = num2; result = text; } } } } return result; } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)("LAN address lookup failed: " + ex.Message)); return null; } } public static string LocalIPv4ForGateway() { string controlUrl = _controlUrl; if (controlUrl == null) { return null; } try { Uri uri = new Uri(controlUrl); using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.Connect(new IPEndPoint(IPAddress.Parse(uri.Host), (uri.Port <= 0) ? 80 : uri.Port)); return (socket.LocalEndPoint as IPEndPoint)?.Address.ToString() ?? LocalIPv4(); } catch (Exception e) { Swallow.Log(e, "LocalIPv4ForGateway", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 607); return LocalIPv4(); } } private static bool EnsureGateway() { if (_controlUrl != null) { return true; } if (_noGateway) { return false; } _noGateway = true; string text = SsdpDiscover(); if (text == null) { return false; } if (!IsPrivateHttpUrl(text)) { CoopPlugin.Log.LogWarning((object)("UPnP: ignoring an SSDP reply whose LOCATION is not a private address - " + text)); return false; } string text2 = HttpGet(text); if (text2 == null) { return false; } if (!FindWanService(text2, out var controlUrl, out var serviceType)) { CoopPlugin.Log.LogInfo((object)"UPnP: the router's description has no WANIPConnection service - no automatic port forwarding on this router"); return false; } string text3 = TagValue(text2, "URLBase"); Uri uri; try { uri = new Uri((text3 != null && IsPrivateHttpUrl(text3.Trim())) ? new Uri(text3.Trim()) : new Uri(text), controlUrl); } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)("UPnP: unusable controlURL (" + ex.Message + ")")); return false; } if (!IsPrivateHttpUrl(uri.ToString())) { CoopPlugin.Log.LogWarning((object)("UPnP: ignoring a controlURL that points off the LAN - " + uri)); return false; } _serviceType = serviceType; _controlUrl = uri.ToString(); _noGateway = false; CoopPlugin.Log.LogInfo((object)("UPnP: gateway control URL " + _controlUrl + " (" + serviceType + ")")); return true; } private static string SsdpDiscover() { UdpClient udpClient = null; try { string s = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 2\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\r\n"; byte[] bytes = Encoding.ASCII.GetBytes(s); IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); udpClient.Client.ReceiveTimeout = 500; udpClient.Send(bytes, bytes.Length, endPoint); udpClient.Send(bytes, bytes.Length, endPoint); bool flag = false; DateTime dateTime = DateTime.UtcNow.AddMilliseconds(3000.0); while (DateTime.UtcNow < dateTime) { IPEndPoint remoteEP = null; byte[] bytes2; try { bytes2 = udpClient.Receive(ref remoteEP); } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.TimedOut) { continue; } CoopPlugin.Log.LogInfo((object)("UPnP discovery socket error: " + ex.SocketErrorCode)); break; } string text = Encoding.ASCII.GetString(bytes2); if (text.IndexOf("InternetGatewayDevice", StringComparison.OrdinalIgnoreCase) < 0) { continue; } string text2 = HeaderValue(text, "LOCATION"); if (text2 != null) { if (IsPrivateHttpUrl(text2)) { return text2; } if (!flag) { flag = true; CoopPlugin.Log.LogWarning((object)("UPnP: ignoring an SSDP reply whose LOCATION is not a private address - " + text2)); } } } CoopPlugin.Log.LogInfo((object)"UPnP: no InternetGatewayDevice answered the SSDP search"); } catch (Exception ex2) { CoopPlugin.Log.LogInfo((object)("UPnP discovery failed: " + ex2.GetType().Name + ": " + ex2.Message)); } finally { try { udpClient?.Close(); } catch (Exception e) { Swallow.Log(e, "SsdpDiscover", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 774); } } return null; } private static bool FindWanService(string xml, out string controlUrl, out string serviceType) { controlUrl = null; serviceType = null; if (xml == null) { return false; } string[] array = new string[2] { "urn:schemas-upnp-org:service:WANIPConnection:2", "urn:schemas-upnp-org:service:WANIPConnection:1" }; foreach (string text in array) { for (int num = xml.IndexOf(text, StringComparison.OrdinalIgnoreCase); num >= 0; num = xml.IndexOf(text, num + text.Length, StringComparison.OrdinalIgnoreCase)) { int num2 = LastIndexOfBefore(xml, "", num); int num3 = xml.IndexOf("", num, StringComparison.OrdinalIgnoreCase); if (num2 < 0) { num2 = num; } if (num3 < 0) { num3 = xml.Length; } string text2 = TagValue(xml.Substring(num2, num3 - num2), "controlURL"); if (text2 != null && text2.Trim().Length > 0) { controlUrl = text2.Trim(); serviceType = text; return true; } } } return false; } private static bool IsPrivateHttpUrl(string url) { try { if (string.IsNullOrEmpty(url)) { return false; } Uri uri = new Uri(url); if (uri.Scheme != Uri.UriSchemeHttp) { return false; } if (!IPAddress.TryParse(uri.Host, out IPAddress address)) { return false; } if (address.AddressFamily != AddressFamily.InterNetwork) { return false; } byte[] addressBytes = address.GetAddressBytes(); if (addressBytes[0] == 10) { return true; } if (addressBytes[0] == 172 && addressBytes[1] >= 16 && addressBytes[1] <= 31) { return true; } if (addressBytes[0] == 192 && addressBytes[1] == 168) { return true; } if (addressBytes[0] == 169 && addressBytes[1] == 254) { return true; } return false; } catch (Exception e) { Swallow.Log(e, "IsPrivateHttpUrl", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 889); return false; } } private static string HttpGet(string url) { try { HttpWebRequest obj = (HttpWebRequest)WebRequest.Create(url); obj.Method = "GET"; obj.Timeout = 4000; obj.ReadWriteTimeout = 4000; obj.Proxy = null; obj.KeepAlive = false; obj.AllowAutoRedirect = false; obj.UserAgent = "CardShopCoopCommunity"; using HttpWebResponse resp = (HttpWebResponse)obj.GetResponse(); if (!SameTrustedTarget(resp)) { return null; } return ReadCapped(resp); } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)("UPnP description fetch failed: " + ex.GetType().Name + ": " + ex.Message)); return null; } } private static bool Soap(string action, string innerXml, out string body) { body = null; string controlUrl = _controlUrl; string serviceType = _serviceType; if (controlUrl == null || serviceType == null) { return false; } try { string s = "" + innerXml + ""; byte[] bytes = Encoding.UTF8.GetBytes(s); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(controlUrl); httpWebRequest.Method = "POST"; httpWebRequest.ContentType = "text/xml; charset=\"utf-8\""; httpWebRequest.Headers.Add("SOAPAction", "\"" + serviceType + "#" + action + "\""); httpWebRequest.Timeout = 4000; httpWebRequest.ReadWriteTimeout = 4000; httpWebRequest.Proxy = null; httpWebRequest.KeepAlive = false; httpWebRequest.AllowAutoRedirect = false; httpWebRequest.UserAgent = "CardShopCoopCommunity"; httpWebRequest.ContentLength = bytes.Length; using (Stream stream = httpWebRequest.GetRequestStream()) { stream.Write(bytes, 0, bytes.Length); } using HttpWebResponse resp = (HttpWebResponse)httpWebRequest.GetResponse(); if (!SameTrustedTarget(resp)) { return false; } body = ReadCapped(resp); return true; } catch (WebException ex) { try { if (ex.Response is HttpWebResponse httpWebResponse) { using (httpWebResponse) { if (SameTrustedTarget(httpWebResponse)) { body = ReadCapped(httpWebResponse); } } } } catch (Exception e) { Swallow.Log(e, "Soap", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 986); } CoopPlugin.Log.LogInfo((object)("UPnP " + action + " refused: " + ex.Message + ((body != null) ? (" | " + Snip(body)) : ""))); return false; } catch (Exception ex2) { CoopPlugin.Log.LogInfo((object)("UPnP " + action + " failed: " + ex2.GetType().Name + ": " + ex2.Message)); return false; } } private static bool SameTrustedTarget(HttpWebResponse resp) { string text = null; try { text = ((resp.ResponseUri != null) ? resp.ResponseUri.ToString() : null); } catch (Exception e) { Swallow.Log(e, "SameTrustedTarget", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 1014); } if (text != null && IsPrivateHttpUrl(text)) { return true; } CoopPlugin.Log.LogWarning((object)("UPnP: refusing a reply that came from somewhere other than the LAN address we asked - " + (text ?? "(no URI)"))); return false; } private static string ReadCapped(HttpWebResponse resp) { using Stream stream = resp.GetResponseStream(); if (stream == null) { return null; } MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int num = 0; DateTime utcNow = DateTime.UtcNow; int num2; while ((num2 = stream.Read(array, 0, array.Length)) > 0) { num += num2; if (num > 524288) { break; } memoryStream.Write(array, 0, num2); if ((DateTime.UtcNow - utcNow).TotalMilliseconds > 8000.0) { CoopPlugin.Log.LogInfo((object)("UPnP: the reply was still arriving after " + 8000 + "ms - using what we have")); break; } } return Encoding.UTF8.GetString(memoryStream.ToArray()); } private static int LastIndexOfBefore(string hay, string needle, int before) { int result = -1; int startIndex = 0; while (true) { int num = hay.IndexOf(needle, startIndex, StringComparison.OrdinalIgnoreCase); if (num < 0 || num >= before) { break; } result = num; startIndex = num + needle.Length; } return result; } private static string TagValue(string xml, string tag) { try { int startIndex = 0; int num; while (true) { startIndex = xml.IndexOf("<", startIndex, StringComparison.Ordinal); if (startIndex < 0) { return null; } num = xml.IndexOf('>', startIndex); if (num < 0) { return null; } string text = xml.Substring(startIndex + 1, num - startIndex - 1); int num2 = text.IndexOf(':'); if (num2 >= 0) { text = text.Substring(num2 + 1); } if (text.Equals(tag, StringComparison.OrdinalIgnoreCase)) { break; } startIndex = num + 1; } int num3 = xml.IndexOf("<", num + 1, StringComparison.Ordinal); if (num3 < 0) { return null; } return xml.Substring(num + 1, num3 - num - 1); } catch (Exception e) { Swallow.Log(e, "TagValue", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 1122); return null; } } private static string HeaderValue(string text, string header) { try { string[] array = text.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length <= header.Length || !text2.StartsWith(header, StringComparison.OrdinalIgnoreCase)) { continue; } int num = text2.IndexOf(':'); if (num >= 0) { string text3 = text2.Substring(num + 1).Trim(); if (text3.Length > 0) { return text3; } } } } catch (Exception e) { Swallow.Log(e, "HeaderValue", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/NetHelpers.cs", 1156); } return null; } private static string Snip(string s) { s = (s ?? "").Replace("\r", " ").Replace("\n", " "); if (s.Length <= 200) { return s; } return s.Substring(0, 200); } } public static class NetMessageCodec { public static byte[] Encode(INetMessage message) { return EncodeNamed(message); } public static byte[] EncodeNamed(INetMessage message) { return EncodeNamed(message, MessageRegistry.CurrentSnapshot); } public static byte[] Encode(INetMessage message, ProtocolSnapshot snapshot, bool compactIds) { if (!compactIds) { return EncodeNamed(message, snapshot); } return EncodeCompact(message, snapshot); } private static byte[] EncodeNamed(INetMessage message, ProtocolSnapshot snapshot) { if (message == null) { throw new ArgumentNullException("message"); } if (snapshot == null) { throw new ArgumentNullException("snapshot"); } long start = PerfProbe.StartThreadMetric(); try { if (!snapshot.TryGet(((object)message).GetType(), out var descriptor)) { throw new InvalidDataException("No network message registration for " + ((object)message).GetType().FullName); } byte[] bytes = WireCodec.StrictUtf8.GetBytes(descriptor.WireName); if (bytes.Length == 0 || bytes.Length > 4096) { throw new InvalidDataException("Message name exceeds the protocol limit for " + descriptor.WireName); } byte[] payload = WireCodec.Serialize(message); return CreateFrame(0, bytes, payload, named: true); } finally { PerfProbe.EndThreadMetric("net.encode.", ((object)message).GetType(), start); } } public static byte[] EncodeCompact(INetMessage message, ProtocolSnapshot snapshot) { if (message == null) { throw new ArgumentNullException("message"); } if (snapshot == null) { throw new ArgumentNullException("snapshot"); } if (!snapshot.IsFrozen) { throw new InvalidOperationException("Compact framing requires a frozen protocol snapshot"); } if (!snapshot.TryGetId(((object)message).GetType(), out var id)) { throw new InvalidDataException("No compact message ID for " + ((object)message).GetType().FullName); } long start = PerfProbe.StartThreadMetric(); try { byte[] payload = WireCodec.Serialize(message); return CreateFrame(1, BitConverter.GetBytes(id), payload, named: false); } finally { PerfProbe.EndThreadMetric("net.encode.", ((object)message).GetType(), start); } } public static byte[] EncodeCompact(INetMessage message) { return EncodeCompact(message, MessageRegistry.CurrentSnapshot); } private static byte[] CreateFrame(byte tokenKind, byte[] token, byte[] payload, bool named) { if (token == null || payload == null) { throw new ArgumentNullException((token == null) ? "token" : "payload"); } if (named) { if (tokenKind != 0 || token.Length == 0 || token.Length > 4096 || token.Length > 65535) { throw new InvalidDataException("Encoded message name exceeds the protocol limit"); } } else if (tokenKind != 1 || token.Length != 2) { throw new InvalidDataException("Encoded compact message token is invalid"); } if (payload.Length > 67100672) { throw new InvalidDataException("Encoded payload exceeds the protocol limit"); } long num = (long)(named ? (5 + token.Length) : 3) + (long)payload.Length; if (num < 3 || num > 67108864) { throw new InvalidDataException("Encoded message exceeds the protocol frame limit"); } int num2 = (int)num; byte[] array = new byte[4 + num2]; Buffer.BlockCopy(BitConverter.GetBytes(num2), 0, array, 0, 4); array[4] = tokenKind; int num3 = 5; if (named) { Buffer.BlockCopy(BitConverter.GetBytes(token.Length), 0, array, num3, 4); num3 += 4; } Buffer.BlockCopy(token, 0, array, num3, named ? token.Length : 2); int dstOffset = (named ? (num3 + token.Length) : (num3 + 2)); Buffer.BlockCopy(payload, 0, array, dstOffset, payload.Length); return array; } } public static class WireCodec { internal static readonly Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); [ThreadStatic] private static StringWriter _lengthWriter; [ThreadStatic] private static JsonSerializer _lengthSerializer; [ThreadStatic] private static char[] _lengthChars; public static byte[] Serialize(INetMessage message) { if (message == null) { throw new ArgumentNullException("message"); } return SerializeObject(message, ((object)message).GetType()); } public static byte[] SerializeObject(object value) { if (value == null) { throw new ArgumentNullException("value"); } return SerializeObject(value, value.GetType()); } private static byte[] SerializeObject(object value, Type type) { return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value, type, WireSettings.Settings)); } public static int SerializeUtf8Length(object value) { if (value == null) { throw new ArgumentNullException("value"); } if (_lengthWriter == null) { _lengthWriter = new StringWriter(CultureInfo.InvariantCulture); _lengthSerializer = JsonSerializer.Create(WireSettings.Settings); } StringBuilder stringBuilder = _lengthWriter.GetStringBuilder(); stringBuilder.Length = 0; _lengthSerializer.Serialize((TextWriter)_lengthWriter, value, value.GetType()); int length = stringBuilder.Length; if (length == 0) { return 0; } if (_lengthChars == null || _lengthChars.Length < length) { _lengthChars = new char[length]; } stringBuilder.CopyTo(0, _lengthChars, 0, length); return Encoding.UTF8.GetByteCount(_lengthChars, 0, length); } public static INetMessage Deserialize(Type type, byte[] payload, int offset, int count) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (type == null) { throw new ArgumentNullException("type"); } if (payload == null) { throw new ArgumentNullException("payload"); } if (offset < 0 || count < 0 || offset > payload.Length - count) { throw new ArgumentOutOfRangeException("payload"); } if (count > 67100672) { throw new InvalidDataException("Payload exceeds the protocol limit"); } return (INetMessage)JsonConvert.DeserializeObject(StrictUtf8.GetString(payload, offset, count), type, WireSettings.Settings); } } public sealed class NetworkPump : ICoopTransport, IDisposable, ICoopHandshakeTransport, ICoopRelayTransport, ICoopStartable { private const int TickMilliseconds = 10; private const int ShutdownJoinMilliseconds = 5000; private readonly ICoopTransport _inner; private readonly ConcurrentQueue _control = new ConcurrentQueue(); private readonly SemaphoreSlim _wake = new SemaphoreSlim(0); private readonly ManualResetEventSlim _started = new ManualResetEventSlim(initialState: false); private Thread _thread; private int _pumpThreadId; private int _wakePending; private int _stopRequested; private int _disposeRequested; private int _disposed; private int _innerStopped; private int _innerDisposed; private Exception _startError; public ConcurrentQueue Disconnects => _inner.Disconnects; public int ConnectionCount => _inner.ConnectionCount; public double TimeoutSeconds => _inner.TimeoutSeconds; public IReadOnlyList Connections => _inner.Connections; public event Action PeerConnected { add { _inner.PeerConnected += value; } remove { _inner.PeerConnected -= value; } } private NetworkPump(ICoopTransport inner) { _inner = inner ?? throw new ArgumentNullException("inner"); } public static ICoopTransport Wrap(ICoopTransport inner) { if (inner == null || inner is NetworkPump) { return inner; } return new NetworkPump(inner); } public void Start() { if (Volatile.Read(in _disposed) != 0) { throw new ObjectDisposedException("NetworkPump"); } if (_thread != null) { if (_startError != null) { throw new InvalidOperationException("network pump failed to start the transport", _startError); } return; } _thread = new Thread(Run) { IsBackground = true, Name = "CardShopCoop.Network" }; _thread.Start(); _started.Wait(); if (_startError == null) { return; } throw new InvalidOperationException("network pump failed to start the transport", _startError); } private void Run() { _pumpThreadId = Thread.CurrentThread.ManagedThreadId; try { if (!(_inner is ICoopStartable coopStartable)) { throw new InvalidOperationException("The wrapped transport cannot be started."); } coopStartable.Start(); } catch (Exception startError) { _startError = startError; try { DisposeInner(); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("network pump startup cleanup failed: " + ex)); } } _started.Set(); return; } _started.Set(); while (Volatile.Read(in _stopRequested) == 0) { DrainControl(); if (Volatile.Read(in _stopRequested) != 0) { break; } try { _inner.PumpNetworkThread(); } catch (Exception ex2) { ManualLogSource log2 = CoopPlugin.Log; if (log2 != null) { log2.LogError((object)("network pump step failed: " + ex2)); } } if (Volatile.Read(in _stopRequested) != 0) { break; } Interlocked.Exchange(ref _wakePending, 0); _wake.Wait(10); } try { StopInner(); } catch (Exception ex3) { ManualLogSource log3 = CoopPlugin.Log; if (log3 != null) { log3.LogError((object)("network pump stop failed: " + ex3)); } } if (Volatile.Read(in _disposeRequested) == 0) { return; } try { DisposeInner(); } catch (Exception ex4) { ManualLogSource log4 = CoopPlugin.Log; if (log4 != null) { log4.LogError((object)("network pump dispose failed: " + ex4)); } } } private void DrainControl() { Action result; while (_control.TryDequeue(out result)) { try { result(); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("network pump control action failed: " + ex)); } } } } private void Signal() { if (Interlocked.Exchange(ref _wakePending, 1) == 0) { _wake.Release(); } } private bool IsPumpThread() { if (_pumpThreadId != 0) { return _pumpThreadId == Thread.CurrentThread.ManagedThreadId; } return false; } private void InvokeOnPump(Action action, string name) { if (IsPumpThread() || _thread == null) { action(); return; } ManualResetEventSlim completed = new ManualResetEventSlim(initialState: false); try { Exception failure = null; _control.Enqueue(delegate { try { action(); } catch (Exception ex) { failure = ex; } finally { completed.Set(); } }); Signal(); if (!completed.Wait(5000)) { throw new TimeoutException("network pump did not complete " + name); } if (failure != null) { throw new InvalidOperationException("network pump " + name + " failed", failure); } } finally { if (completed != null) { ((IDisposable)completed).Dispose(); } } } private void StopInner() { if (Interlocked.Exchange(ref _innerStopped, 1) == 0) { _inner.Stop(); } } private void DisposeInner() { if (Interlocked.Exchange(ref _innerDisposed, 1) == 0) { _inner.Dispose(); } } public bool TryDequeueIncoming(out InMsg message) { return _inner.TryDequeueIncoming(out message); } public double SecondsSinceLastRecv(PeerConnection connection) { return _inner.SecondsSinceLastRecv(connection); } public void Send(PeerConnection connection, INetMessage message) { _inner.Send(connection, message); Signal(); } public void Broadcast(INetMessage message) { _inner.Broadcast(message); Signal(); } void ICoopHandshakeTransport.SendHandshake(PeerConnection connection, INetMessage message) { ((_inner as ICoopHandshakeTransport) ?? throw new InvalidOperationException("The wrapped transport has no handshake seam")).SendHandshake(connection, message); Signal(); } void ICoopRelayTransport.Relay(INetMessage message, int exceptConnectionId) { ((_inner as ICoopRelayTransport) ?? throw new InvalidOperationException("The wrapped transport has no relay seam")).Relay(message, exceptConnectionId); Signal(); } public void ActivateMessageIds(PeerConnection connection) { InvokeOnPump(delegate { _inner.ActivateMessageIds(connection); }, "message-id activation"); } public void Kick(PeerConnection connection, DisconnectInfo info = null) { _inner.Kick(connection, info); Signal(); } public void GracefulDisconnect(PeerConnection connection, DisconnectInfo info = null) { _inner.GracefulDisconnect(connection, info); Signal(); } public void PumpNetworkThread() { _inner.PumpNetworkThread(); } public void Stop() { if (Volatile.Read(in _disposed) == 0) { if (_thread == null) { StopInner(); } else { InvokeOnPump(StopInner, "stop"); } } } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { if (_thread == null) { DisposeInner(); } else if (IsPumpThread()) { Volatile.Write(ref _disposeRequested, 1); Volatile.Write(ref _stopRequested, 1); StopInner(); DisposeInner(); } else { Volatile.Write(ref _disposeRequested, 1); Volatile.Write(ref _stopRequested, 1); Signal(); _thread.Join(5000); } } } } public sealed class NetWriter : BinaryWriter { public NetWriter(Stream stream) : base(stream) { } } public sealed class NetReader : BinaryReader { public NetReader(Stream stream) : base(stream) { } } public class SteamLobby { public struct LobbyRow { public CSteamID Id; public string Name; public int Players; public int Max; public bool HasPw; public string Ver; } private CallResult _createLobby; private Callback _cbEnter; private Callback _cbJoinRequested; private CallResult _lobbyList; public CSteamID LobbyId = CSteamID.Nil; private bool _joining; private CSteamID _pendingLobbyId = CSteamID.Nil; private long _operationGeneration; private long _pendingOperationGeneration; private long _activeLobbyOperationGeneration; private long _listOperationGeneration; private bool _pendingPublic; private string _pendingName = ""; private bool _pendingHasPw; public Action OnLobbyCreated; public Action OnEnteredLobby; public Action OnJoinFailed; public Action OnHostFailed; public Action OnInviteAccepted; public Action OnError; public Action OnListUpdated; public readonly List Lobbies = new List(); public bool ListRefreshing { get; private set; } public void Init() { _lobbyList = CallResult.Create((APIDispatchDelegate)delegate(LobbyMatchList_t e, bool ioFail) { //IL_00d9: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (_listOperationGeneration != 0L && _listOperationGeneration == _operationGeneration) { ListRefreshing = false; Lobbies.Clear(); if (ioFail) { OnError?.Invoke("Steam lobby list failed"); } else { for (int i = 0; i < e.m_nLobbiesMatching; i++) { CSteamID lobbyByIndex = SteamMatchmaking.GetLobbyByIndex(i); if (!(lobbyByIndex == CSteamID.Nil)) { Lobbies.Add(new LobbyRow { Id = lobbyByIndex, Name = SteamMatchmaking.GetLobbyData(lobbyByIndex, "name"), Players = SteamMatchmaking.GetNumLobbyMembers(lobbyByIndex), Max = SteamMatchmaking.GetLobbyMemberLimit(lobbyByIndex), HasPw = (SteamMatchmaking.GetLobbyData(lobbyByIndex, "pw") == "1"), Ver = SteamMatchmaking.GetLobbyData(lobbyByIndex, "coopver") }); } } OnListUpdated?.Invoke(); } } }); _cbEnter = Callback.Create((DispatchDelegate)delegate(LobbyEnter_t e) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(e.m_ulSteamIDLobby); if (IsCurrentJoin(val, out var operation)) { if (e.m_EChatRoomEnterResponse != 1) { FailJoin(operation, val, e.m_EChatRoomEnterResponse); } else { _joining = false; _pendingLobbyId = CSteamID.Nil; _pendingOperationGeneration = 0L; LobbyId = val; _activeLobbyOperationGeneration = operation; CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(LobbyId); OnEnteredLobby?.Invoke(val, lobbyOwner, operation); } } }); _cbJoinRequested = Callback.Create((DispatchDelegate)delegate(GameLobbyJoinRequested_t e) { //IL_000b: 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) OnInviteAccepted?.Invoke(e.m_steamIDLobby); }); } public bool SteamAvailable() { try { return SteamAPI.IsSteamRunning(); } catch (Exception e) { Swallow.Log(e, "SteamAvailable", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/SteamNet.cs", 136); return false; } } public unsafe void Host(bool isPublic, string lobbyName, bool hasPassword, int maxPlayers) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) if (maxPlayers < 2 || maxPlayers > 250) { throw new ArgumentOutOfRangeException("maxPlayers", maxPlayers, "Steam lobby player count must be between 2 and 250."); } AbortPendingOperation(); _pendingPublic = isPublic; _pendingName = lobbyName ?? ""; _pendingHasPw = hasPassword; long operation = ++_operationGeneration; _pendingOperationGeneration = operation; string name = _pendingName; bool password = _pendingHasPw; string ownerName = (((Object)(object)CoopCore.Instance == (Object)null) ? CoopPlugin.PlayerName.Value : CoopCore.Instance.EffectivePlayerName); _createLobby = CallResult.Create((APIDispatchDelegate)delegate(LobbyCreated_t e, bool ioFail) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) if (operation == _operationGeneration && _pendingOperationGeneration == operation && !_joining) { _createLobby = null; if (ioFail || (int)e.m_eResult != 1) { string text = "Steam lobby creation failed: " + ((object)(*(EResult*)(&e.m_eResult))/*cast due to .constrained prefix*/).ToString(); _pendingOperationGeneration = 0L; OnError?.Invoke(text); OnHostFailed?.Invoke(operation, text); } else { LobbyId = new CSteamID(e.m_ulSteamIDLobby); _activeLobbyOperationGeneration = operation; SteamMatchmaking.SetLobbyData(LobbyId, "coopmod", "communitymultiplayer"); SteamMatchmaking.SetLobbyData(LobbyId, "coopver", "2.0.6"); SteamMatchmaking.SetLobbyData(LobbyId, "name", string.IsNullOrEmpty(name) ? (ownerName + "'s shop") : name); SteamMatchmaking.SetLobbyData(LobbyId, "pw", password ? "1" : "0"); OnLobbyCreated?.Invoke(LobbyId, operation); } } }); SteamAPICall_t val = SteamMatchmaking.CreateLobby((ELobbyType)((!isPublic) ? 1 : 2), maxPlayers); _createLobby.Set(val, (APIDispatchDelegate)null); } public void RefreshList() { //IL_0045: 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_0051: Unknown result type (might be due to invalid IL or missing references) if (!ListRefreshing) { ListRefreshing = true; _listOperationGeneration = ++_operationGeneration; SteamMatchmaking.AddRequestLobbyListStringFilter("coopmod", "communitymultiplayer", (ELobbyComparison)0); SteamMatchmaking.AddRequestLobbyListResultCountFilter(100); SteamMatchmaking.AddRequestLobbyListDistanceFilter((ELobbyDistanceFilter)3); SteamAPICall_t val = SteamMatchmaking.RequestLobbyList(); _lobbyList.Set(val, (APIDispatchDelegate)null); } } public long Join(CSteamID lobby) { //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_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) AbortPendingOperation(); _joining = true; _pendingLobbyId = lobby; _pendingOperationGeneration = ++_operationGeneration; SteamMatchmaking.JoinLobby(lobby); return _pendingOperationGeneration; } public void OpenInviteDialog() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (LobbyId != CSteamID.Nil) { SteamFriends.ActivateGameOverlayInviteDialog(LobbyId); } } public void Leave() { AbortPendingOperation(); } public bool IsCurrentLobby(CSteamID lobby, long operation) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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 (lobby != CSteamID.Nil && operation != 0L && LobbyId == lobby && _activeLobbyOperationGeneration == operation) { return _operationGeneration == operation; } return false; } public bool IsCurrentOperation(long operation) { if (operation != 0L) { return _operationGeneration == operation; } return false; } public bool IsMember(CSteamID steamId) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (LobbyId == CSteamID.Nil || steamId == CSteamID.Nil) { return false; } int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(LobbyId); for (int i = 0; i < numLobbyMembers; i++) { if (SteamMatchmaking.GetLobbyMemberByIndex(LobbyId, i) == steamId) { return true; } } return false; } private bool IsCurrentJoin(CSteamID lobby, out long operation) { //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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) operation = _pendingOperationGeneration; if (_joining && operation != 0L && operation == _operationGeneration) { if (!(_pendingLobbyId == CSteamID.Nil)) { return _pendingLobbyId == lobby; } return true; } return false; } private void FailJoin(long operation, CSteamID lobby, uint response) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (_joining && operation != 0L && operation == _operationGeneration && _pendingOperationGeneration == operation && !(_pendingLobbyId != lobby)) { CoopPlugin.Log.LogWarning((object)("Steam lobby join rejected (" + DescribeJoinFailure(response) + ").")); _joining = false; _pendingLobbyId = CSteamID.Nil; _pendingOperationGeneration = 0L; _operationGeneration++; try { SteamMatchmaking.LeaveLobby(lobby); } catch (Exception e) { Swallow.Log(e, "FailJoin", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/SteamNet.cs", 291); } if (LobbyId == lobby) { LobbyId = CSteamID.Nil; } OnJoinFailed?.Invoke(lobby, operation, "Unable to join Steam lobby: " + DescribeJoinFailure(response) + "."); } } private static string DescribeJoinFailure(uint response) { return response switch { 2u => "the lobby no longer exists", 3u => "you are not allowed to join this lobby", 4u => "the lobby is full", 6u => "you are banned from this lobby", 7u => "the lobby is temporarily unavailable", _ => "Steam rejected the join (response " + response + ")", }; } private void AbortPendingOperation() { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) _operationGeneration++; _createLobby?.Dispose(); _createLobby = null; _pendingOperationGeneration = 0L; _activeLobbyOperationGeneration = 0L; _listOperationGeneration = 0L; ListRefreshing = false; _joining = false; _pendingLobbyId = CSteamID.Nil; if (LobbyId != CSteamID.Nil) { try { SteamMatchmaking.LeaveLobby(LobbyId); } catch (Exception e) { Swallow.Log(e, "AbortPendingOperation", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/SteamNet.cs", 337); } LobbyId = CSteamID.Nil; } } } internal sealed class DeferredSteamDatagramTransport : IDatagramTransport, IDisposable { private readonly object _gate = new object(); private IDatagramTransport _inner; private bool _started; private bool _disposed; public int MaxDatagramSize { get; } public bool IsRunning { get { lock (_gate) { return _inner != null && _inner.IsRunning; } } } public event Action PeerAvailable; public event Action PeerClosed; public DeferredSteamDatagramTransport(int maxDatagramSize) { if (maxDatagramSize <= 0) { throw new ArgumentOutOfRangeException("maxDatagramSize"); } MaxDatagramSize = maxDatagramSize; } public void Bind(IDatagramTransport inner) { if (inner == null) { throw new ArgumentNullException("inner"); } bool started; lock (_gate) { if (_disposed) { throw new ObjectDisposedException("DeferredSteamDatagramTransport"); } if (_inner != null) { throw new InvalidOperationException("Steam datagram bearer is already bound."); } if (inner.MaxDatagramSize != MaxDatagramSize) { throw new ArgumentException("Steam datagram bearer size does not match the KCP transport.", "inner"); } _inner = inner; _inner.PeerAvailable += OnPeerAvailable; _inner.PeerClosed += OnPeerClosed; started = _started; } if (!started) { return; } try { inner.Start(); } catch (Exception startupError) { bool flag = false; lock (_gate) { _started = false; if (_inner == inner) { _inner = null; flag = true; } } if (flag) { ReleaseInner(inner, startupError); } throw; } } public void Start() { IDatagramTransport inner; lock (_gate) { if (_disposed) { throw new ObjectDisposedException("DeferredSteamDatagramTransport"); } if (_started) { return; } _started = true; inner = _inner; } if (inner == null) { return; } try { inner.Start(); } catch (Exception startupError) { bool flag = false; lock (_gate) { _started = false; if (_inner == inner) { _inner = null; flag = true; } } if (flag) { ReleaseInner(inner, startupError); } throw; } } public int Poll(int maxDatagrams, Action> receive) { if (receive == null) { throw new ArgumentNullException("receive"); } IDatagramTransport inner; lock (_gate) { if (_disposed) { return 0; } inner = _inner; } return inner?.Poll(maxDatagrams, receive) ?? 0; } public void Send(DatagramPeer peer, ArraySegment datagram) { IDatagramTransport inner; lock (_gate) { if (_disposed) { throw new ObjectDisposedException("DeferredSteamDatagramTransport"); } inner = _inner; } if (inner == null) { throw new InvalidOperationException("Steam datagram bearer is not bound"); } inner.Send(peer, datagram); } public void Close(DatagramPeer peer, string reason) { IDatagramTransport inner; lock (_gate) { if (_disposed) { return; } inner = _inner; } inner?.Close(peer, reason); } public void Stop() { IDatagramTransport inner; lock (_gate) { if (!_started) { return; } _started = false; inner = _inner; } inner?.Stop(); } public void Dispose() { IDatagramTransport inner; lock (_gate) { if (_disposed) { return; } _disposed = true; _started = false; inner = _inner; _inner = null; } if (inner != null) { ReleaseInner(inner); } } private void ReleaseInner(IDatagramTransport inner, Exception startupError = null) { List list = new List(); try { inner.PeerAvailable -= OnPeerAvailable; } catch (Exception item) { list.Add(item); } try { inner.PeerClosed -= OnPeerClosed; } catch (Exception item2) { list.Add(item2); } try { inner.Stop(); } catch (Exception item3) { list.Add(item3); } try { inner.Dispose(); } catch (Exception item4) { list.Add(item4); } if (list.Count != 0) { if (startupError != null) { list.Insert(0, startupError); throw new AggregateException("Steam datagram bearer startup cleanup failed.", list); } throw new AggregateException("Steam datagram bearer disposal failed.", list); } } private void OnPeerAvailable(DatagramPeer peer) { this.PeerAvailable?.Invoke(peer); } private void OnPeerClosed(DatagramPeer peer, string reason) { this.PeerClosed?.Invoke(peer, reason); } } internal sealed class SteamBridgeImpl : ISteamBridge { private readonly struct BridgeCallback { public readonly long Generation; public readonly ICoopTransport Transport; public readonly Action Action; public BridgeCallback(long generation, ICoopTransport transport, Action action) { Generation = generation; Transport = transport; Action = action; } } internal const int MaxPendingCallbacks = 512; internal const int MaxCallbacksPerPump = 64; private readonly SteamLobby _lobby = new SteamLobby(); private readonly ConcurrentQueue _callbacks = new ConcurrentQueue(); private readonly object _transportGate = new object(); private readonly List _rows = new List(); private DeferredSteamDatagramTransport _datagrams; private ICoopTransport _tx; private long _transportGeneration; private SteamBridgePump _pump; private int _pendingCallbackCount; internal int PendingCallbackCount => Math.Max(0, Volatile.Read(in _pendingCallbackCount)); public Action OnError { get; set; } public Action OnLobbyLive { get; set; } public Action OnConnectedToHost { get; set; } public Action OnInviteAccepted { get; set; } public Action OnJoinFailed { get; set; } public Action OnSessionFailed { get; set; } public string LocalPersonaName { get { try { return SteamAvailable() ? (SteamFriends.GetPersonaName() ?? "") : ""; } catch (Exception e) { Swallow.Log(e, "LocalPersonaName", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/SteamNet.cs", 809); return ""; } } } public ulong LocalSteamId { get { //IL_000d: Unknown result type (might be due to invalid IL or missing references) try { if (!SteamAvailable()) { return 0uL; } return SteamUser.GetSteamID().m_SteamID; } catch (Exception e) { Swallow.Log(e, "LocalSteamId", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/SteamNet.cs", 826); return 0uL; } } } public bool ListRefreshing => _lobby.ListRefreshing; public List Lobbies { get { //IL_0037: Unknown result type (might be due to invalid IL or missing references) _rows.Clear(); foreach (SteamLobby.LobbyRow lobby in _lobby.Lobbies) { _rows.Add(new LobbyRow { Id = lobby.Id.m_SteamID, Name = lobby.Name, Players = lobby.Players, Max = lobby.Max, HasPw = lobby.HasPw, Ver = lobby.Ver }); } return _rows; } } public SteamBridgeImpl() { try { SteamAPI.IsSteamRunning(); } catch (Exception ex) { throw new InvalidOperationException("Steamworks runtime is not functional on this install (" + ex.GetType().Name + ": " + ex.Message + ")"); } CoopCore instance = CoopCore.Instance; if ((Object)(object)instance != (Object)null) { _pump = ((Component)instance).gameObject.AddComponent(); _pump.Initialize(PumpCallbacks); } } public void Init() { _lobby.Init(); _lobby.OnError = delegate(string error) { QueueGlobal(delegate { OnError?.Invoke(error); }); }; _lobby.OnJoinFailed = delegate(CSteamID lobby, long operation, string error) { //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) CaptureTransport(out var generation, out var transport); if (transport != null) { QueueSession(generation, transport, delegate { OnJoinFailed?.Invoke(lobby.m_SteamID, operation, transport, error); }); } }; _lobby.OnHostFailed = delegate(long operation, string error) { CaptureTransport(out var generation, out var transport); DeferredSteamDatagramTransport datagrams; lock (_transportGate) { datagrams = _datagrams; } if (transport != null && datagrams != null) { QueueSession(generation, transport, delegate { FailHost(generation, transport, datagrams, operation, error, notifyUi: false); }); } }; _lobby.OnLobbyCreated = delegate(CSteamID id, long operation) { //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) CaptureTransport(out var generation, out var transport); DeferredSteamDatagramTransport datagrams; lock (_transportGate) { datagrams = _datagrams; } if (transport != null && datagrams != null) { QueueSession(generation, transport, delegate { //IL_0019: Unknown result type (might be due to invalid IL or missing references) BindHost(generation, transport, datagrams, id, operation); }); } }; _lobby.OnEnteredLobby = delegate(CSteamID lobby, CSteamID owner, long operation) { //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) CaptureTransport(out var generation, out var transport); DeferredSteamDatagramTransport datagrams; lock (_transportGate) { datagrams = _datagrams; } if (transport != null && datagrams != null) { QueueSession(generation, transport, delegate { //IL_0019: 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) BindClient(generation, transport, datagrams, lobby, owner, operation); }); } }; _lobby.OnInviteAccepted = delegate(CSteamID id) { //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) QueueGlobal(delegate { OnInviteAccepted?.Invoke(id.m_SteamID); }); }; _lobby.OnListUpdated = delegate { }; } public bool SteamAvailable() { return _lobby.SteamAvailable(); } public string FriendNickname(ulong steamId) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (steamId == 0L) { return ""; } try { if (!SteamAvailable()) { return ""; } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(steamId); if (!SteamFriends.HasFriend(val, (EFriendFlags)4)) { return ""; } return SteamFriends.GetFriendPersonaName(val) ?? ""; } catch (Exception e) { Swallow.Log(e, "FriendNickname", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Net/SteamNet.cs", 852); return ""; } } public ICoopTransport CreateTransport(bool isHost) { lock (_transportGate) { if (_tx != null) { throw new InvalidOperationException("Steam bridge already has an active transport."); } DeferredSteamDatagramTransport datagrams = new DeferredSteamDatagramTransport(1200); KcpSessionManager kcpSessionManager = new KcpSessionManager(datagrams, isHost); _datagrams = datagrams; _tx = kcpSessionManager; _transportGeneration++; return kcpSessionManager; } } public void Host(bool isPublic, string lobbyName, bool hasPassword, int maxPlayers) { _lobby.Host(isPublic, lobbyName, hasPassword, maxPlayers); } public long Join(ulong lobbyId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return _lobby.Join(new CSteamID(lobbyId)); } public void Leave() { lock (_transportGate) { _transportGeneration++; _tx = null; _datagrams = null; } _lobby.Leave(); } public void OpenInviteDialog() { _lobby.OpenInviteDialog(); } public void RefreshList() { _lobby.RefreshList(); } private void BindHost(long generation, ICoopTransport transport, DeferredSteamDatagramTransport datagrams, CSteamID lobby, long operation) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!IsCurrent(generation, transport, datagrams) || !_lobby.IsCurrentLobby(lobby, operation)) { return; } try { datagrams.Bind(SteamDatagramTransport.CreateHost((ulong steamId) => _lobby.IsMember(new CSteamID(steamId)))); } catch (Exception ex) { FailHost(generation, transport, datagrams, operation, "Steam host transport startup failed: " + ex.Message, notifyUi: true); return; } if (IsCurrent(generation, transport, datagrams) && _lobby.IsCurrentLobby(lobby, operation)) { OnLobbyLive?.Invoke(lobby.m_SteamID); } } private void BindClient(long generation, ICoopTransport transport, DeferredSteamDatagramTransport datagrams, CSteamID lobby, CSteamID owner, long operation) { //IL_0062: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_008e: Unknown result type (might be due to invalid IL or missing references) if (!IsCurrent(generation, transport, datagrams) || !_lobby.IsCurrentLobby(lobby, operation)) { return; } try { if (owner == CSteamID.Nil) { throw new InvalidOperationException("Steam lobby entered without an owner identity."); } datagrams.Bind(SteamDatagramTransport.CreateClient(owner.m_SteamID)); } catch (Exception ex) { FailJoin(generation, transport, datagrams, lobby, operation, "Steam client transport startup failed: " + ex.Message); return; } if (IsCurrent(generation, transport, datagrams) && _lobby.IsCurrentLobby(lobby, operation)) { OnConnectedToHost?.Invoke(transport); } } private void FailHost(long generation, ICoopTransport transport, DeferredSteamDatagramTransport datagrams, long operation, string error, bool notifyUi) { if (IsCurrent(generation, transport, datagrams) && _lobby.IsCurrentOperation(operation)) { if (notifyUi) { OnError?.Invoke(error); } if (IsCurrent(generation, transport, datagrams) && _lobby.IsCurrentOperation(operation)) { OnSessionFailed?.Invoke(transport, error); } } } private void FailJoin(long generation, ICoopTransport transport, DeferredSteamDatagramTransport datagrams, CSteamID lobby, long operation, string error) { //IL_0011: 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_0058: Unknown result type (might be due to invalid IL or missing references) if (IsCurrent(generation, transport, datagrams) && _lobby.IsCurrentLobby(lobby, operation)) { OnError?.Invoke(error); if (IsCurrent(generation, transport, datagrams) && _lobby.IsCurrentLobby(lobby, operation)) { OnJoinFailed?.Invoke(lobby.m_SteamID, operation, transport, error); } } } private void CaptureTransport(out long generation, out ICoopTransport transport) { lock (_transportGate) { generation = _transportGeneration; transport = _tx; } } private bool IsCurrent(long generation, ICoopTransport transport, DeferredSteamDatagramTransport datagrams) { lock (_transportGate) { return generation == _transportGeneration && transport == _tx && datagrams == _datagrams; } } private void QueueGlobal(Action action) { EnqueueCallback(new BridgeCallback(0L, null, action)); } private void QueueSession(long generation, ICoopTransport transport, Action action) { EnqueueCallback(new BridgeCallback(generation, transport, action)); } private void EnqueueCallback(BridgeCallback callback) { if (Interlocked.Increment(ref _pendingCallbackCount) > 512) { Interlocked.Decrement(ref _pendingCallbackCount); InvalidOperationException ex = new InvalidOperationException("Steam bridge callback queue exceeded its " + 512 + " item limit."); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)ex.Message); } throw ex; } _callbacks.Enqueue(callback); } private void PumpCallbacks() { BridgeCallback result; for (int i = 0; i < 64 && _callbacks.TryDequeue(out result); i++) { Interlocked.Decrement(ref _pendingCallbackCount); if (result.Transport != null) { DeferredSteamDatagramTransport datagrams; lock (_transportGate) { datagrams = _datagrams; } if (!IsCurrent(result.Generation, result.Transport, datagrams)) { continue; } } result.Action(); } } } internal sealed class SteamBridgePump : MonoBehaviour { private Action _pump; internal void Initialize(Action pump) { _pump = pump ?? throw new ArgumentNullException("pump"); } private void Update() { using (PerfProbe.Sample("steam.callbacks")) { _pump?.Invoke(); } } } public static class WireSettings { public static readonly JsonSerializerSettings Settings = Create(); private static JsonSerializerSettings Create() { //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_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown return new JsonSerializerSettings { Culture = CultureInfo.InvariantCulture, Formatting = (Formatting)0, NullValueHandling = (NullValueHandling)1, ContractResolver = (IContractResolver)(object)new WireContractResolver(), MissingMemberHandling = (MissingMemberHandling)1, MaxDepth = 128, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.ItemType) }, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.ObjectType) }, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.DecoObject) }, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.CardExpansion) }, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.MonsterType) }, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.Rarity) }, Converters = { (JsonConverter)(object)new EnumWireConverter(EnumKind.CollectionPack) }, Converters = { (JsonConverter)new StringEnumConverter() }, Converters = { (JsonConverter)(object)new Vector3Converter() }, Converters = { (JsonConverter)(object)new QuaternionConverter() } }; } } internal sealed class WireContractResolver : DefaultContractResolver { protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { IList list = ((DefaultContractResolver)this).CreateProperties(type, (MemberSerialization)0); for (int num = list.Count - 1; num >= 0; num--) { JsonProperty val = list[num]; if (val.Ignored || (val.DeclaringType != null && val.DeclaringType.Namespace == "UnityEngine")) { list.RemoveAt(num); } } return list; } } internal sealed class EnumWireConverter : JsonConverter where T : struct { private readonly EnumKind _kind; public EnumWireConverter(EnumKind kind) { _kind = kind; } public override bool CanConvert(Type objectType) { return objectType == typeof(T); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteValue(CatalogIdMap.ToHostValue(_kind, Convert.ToInt32(value, CultureInfo.InvariantCulture))); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if ((int)reader.TokenType == 11) { throw new JsonSerializationException("Null enum " + objectType); } if ((int)reader.TokenType != 7 && (int)reader.TokenType != 8) { throw new JsonSerializationException("Non-numeric " + objectType.Name + " value " + Convert.ToString(reader.Value, CultureInfo.InvariantCulture) + " received: gated enums travel as the host's numeric value"); } int hostValue = Convert.ToInt32(reader.Value, CultureInfo.InvariantCulture); int value = CatalogIdMap.FromHostValue(_kind, hostValue); return (T)Enum.ToObject(typeof(T), value); } } internal sealed class Vector3Converter : JsonConverter { public override bool CanConvert(Type t) { return t == typeof(Vector3); } public override void WriteJson(JsonWriter w, object value, JsonSerializer s) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Vector3 val = (Vector3)value; w.WriteStartArray(); w.WriteValue(val.x); w.WriteValue(val.y); w.WriteValue(val.z); w.WriteEndArray(); } public override object ReadJson(JsonReader r, Type t, object existing, JsonSerializer s) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) List list = ReadArray(r, 3); return (object)new Vector3(Convert.ToSingle(list[0], CultureInfo.InvariantCulture), Convert.ToSingle(list[1], CultureInfo.InvariantCulture), Convert.ToSingle(list[2], CultureInfo.InvariantCulture)); } private static List ReadArray(JsonReader r, int count) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if ((int)r.TokenType != 2) { throw new JsonSerializationException("Expected vector array"); } while (r.Read() && (int)r.TokenType != 14) { if (list.Count >= count) { throw new JsonSerializationException("Too many vector components"); } list.Add(r.Value); } if (list.Count != count) { throw new JsonSerializationException("Wrong vector component count"); } return list; } } internal sealed class QuaternionConverter : JsonConverter { public override bool CanConvert(Type t) { return t == typeof(Quaternion); } public override void WriteJson(JsonWriter w, object value, JsonSerializer s) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) Quaternion val = (Quaternion)value; w.WriteStartArray(); w.WriteValue(val.x); w.WriteValue(val.y); w.WriteValue(val.z); w.WriteValue(val.w); w.WriteEndArray(); } public override object ReadJson(JsonReader r, Type t, object existing, JsonSerializer s) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if ((int)r.TokenType != 2) { throw new JsonSerializationException("Expected quaternion array"); } while (r.Read() && (int)r.TokenType != 14) { if (list.Count >= 4) { throw new JsonSerializationException("Too many quaternion components"); } list.Add(r.Value); } if (list.Count != 4) { throw new JsonSerializationException("Wrong quaternion component count"); } return (object)new Quaternion(Convert.ToSingle(list[0], CultureInfo.InvariantCulture), Convert.ToSingle(list[1], CultureInfo.InvariantCulture), Convert.ToSingle(list[2], CultureInfo.InvariantCulture), Convert.ToSingle(list[3], CultureInfo.InvariantCulture)); } } } namespace CardShopCoop.Net.Protocol { internal static class ProtocolAuthorization { public unsafe static ProtocolAuthorizationResult Authorize(MessageContext context, bool corePreAuthentication) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Invalid comparison between Unknown and I4 if (context == null) { return ProtocolAuthorizationResult.Deny("message context is null"); } if (context.Connection == null) { return ProtocolAuthorizationResult.Deny("a peer connection is required"); } if (context.Connection.IsDisconnectingOrDisconnected) { return ProtocolAuthorizationResult.Deny("connection is closing"); } if (corePreAuthentication) { ConnectionState state = context.Connection.State; if ((int)state != 1 && (int)state != 2 && (int)state != 3) { return ProtocolAuthorizationResult.Deny("control message is not valid in connection phase " + ((object)(*(ConnectionState*)(&state))/*cast due to .constrained prefix*/).ToString()); } } else if (!context.IsAuthenticated) { return ProtocolAuthorizationResult.Deny("authenticated peer is required"); } return ProtocolAuthorizationResult.Allow(); } } public sealed class ProtocolRegistry : IDisposable { private sealed class DescriptorRegistration { public MessageDescriptor Descriptor; public ProtocolRegistrationOwner Owner; public bool Retired; } private sealed class HandlerRegistration { public ProtocolHandlerBinding Binding; public ProtocolRegistrationOwner Owner; public bool Retired; } private sealed class RegistrationHandle : IProtocolRegistration, IDisposable { private readonly Action _dispose; private readonly Func _state; private int _disposed; public ProtocolRegistrationOwner Owner { get; } public RegistrationState State => VolatileState(); public RegistrationHandle(ProtocolRegistrationOwner owner, Action dispose, Func state) { Owner = owner; _dispose = dispose; _state = state; } private RegistrationState VolatileState() { return _state(); } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { _dispose(); } } } private sealed class SessionLease : IProtocolSessionLease, IDisposable { private readonly ProtocolRegistry _registry; private readonly ProtocolSnapshot _snapshot; private int _disposed; public int Generation => _snapshot.Generation; public ProtocolSnapshot Snapshot => _snapshot; public SessionLease(ProtocolRegistry registry, ProtocolSnapshot snapshot) { _registry = registry; _snapshot = snapshot; } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { _registry.EndSession(this); } } } private sealed class CoreOwnerHolder { public readonly ProtocolRegistrationOwner Owner = new ProtocolRegistrationOwner("CardShopCoop", isCoreOwner: true); } private static readonly CoreOwnerHolder Core = new CoreOwnerHolder(); private readonly object _gate = new object(); private readonly Dictionary _activeDescriptors = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _pendingDescriptors = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _handlers = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _pendingHandlers = new Dictionary(StringComparer.Ordinal); private ProtocolSnapshot _snapshot; private bool _frozen; private SessionLease _sessionLease; private int _generation; private bool _coreAssemblyRegistered; private bool _disposed; public static ProtocolRegistry Default { get; } = new ProtocolRegistry(); public bool IsFrozen { get { lock (_gate) { ThrowIfDisposedLocked(); return _frozen; } } } public ProtocolSnapshot CurrentSnapshot { get { lock (_gate) { ThrowIfDisposedLocked(); return _snapshot; } } } internal static ProtocolRegistrationOwner CoreOwner => Core.Owner; public event Action Rejected; public ProtocolRegistry() { _snapshot = new ProtocolSnapshot(0, isFrozen: false, new Dictionary(StringComparer.Ordinal), new Dictionary(StringComparer.Ordinal)); } public static ProtocolRegistrationOwner CreateOwner(string id) { return new ProtocolRegistrationOwner(id); } public IProtocolSessionLease BeginSession() { lock (_gate) { ThrowIfDisposedLocked(); if (_sessionLease != null || _frozen) { throw new InvalidOperationException("A protocol session is already active for this registry"); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair activeDescriptor in _activeDescriptors) { if (!activeDescriptor.Value.Retired) { dictionary2.Add(activeDescriptor.Key, activeDescriptor.Value); dictionary.Add(activeDescriptor.Key, activeDescriptor.Value.Descriptor); } } foreach (KeyValuePair pendingDescriptor in _pendingDescriptors) { if (!pendingDescriptor.Value.Retired) { dictionary2.Add(pendingDescriptor.Key, pendingDescriptor.Value); dictionary.Add(pendingDescriptor.Key, pendingDescriptor.Value.Descriptor); } } if (dictionary.Count > 65536) { throw new ProtocolRegistrationException("A protocol snapshot cannot contain more than " + 65536 + " messages"); } Dictionary dictionary3 = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair handler in _handlers) { if (!handler.Value.Retired && dictionary.ContainsKey(handler.Key)) { dictionary3.Add(handler.Key, handler.Value); } } foreach (KeyValuePair pendingHandler in _pendingHandlers) { if (!pendingHandler.Value.Retired && dictionary.ContainsKey(pendingHandler.Key)) { dictionary3.Add(pendingHandler.Key, pendingHandler.Value); } } _activeDescriptors.Clear(); foreach (KeyValuePair item in dictionary2) { _activeDescriptors.Add(item.Key, item.Value); } _pendingDescriptors.Clear(); _handlers.Clear(); foreach (KeyValuePair item2 in dictionary3) { _handlers.Add(item2.Key, item2.Value); } _pendingHandlers.Clear(); _generation++; _frozen = true; RebuildSnapshotLocked(dictionary); _sessionLease = new SessionLease(this, _snapshot); return _sessionLease; } } private void EndSession(SessionLease lease) { lock (_gate) { if (_sessionLease == lease) { _sessionLease = null; _frozen = false; RebuildSnapshotLocked(); } } } public IProtocolRegistration RegisterAssembly(Assembly assembly, ProtocolRegistrationOwner owner) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown if (assembly == null) { throw new ArgumentNullException("assembly"); } RequireOwner(owner); lock (_gate) { ThrowIfDisposedLocked(); } Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { List list = new List(); if (ex.LoaderExceptions != null) { for (int i = 0; i < ex.LoaderExceptions.Length; i++) { if (ex.LoaderExceptions[i] != null) { list.Add(ex.LoaderExceptions[i].Message); } } } throw new ProtocolRegistrationException("Could not load every type from assembly " + assembly.FullName + "; registration was aborted: " + string.Join(" | ", list), ex); } List list2 = new List(); foreach (Type type in types) { NetworkMessageAttribute val; try { val = (NetworkMessageAttribute)Attribute.GetCustomAttribute(type, typeof(NetworkMessageAttribute)); } catch (Exception error) { throw CoreDiscoveryFailure(assembly, "could not inspect attributes on " + type.FullName, error); } if (val != null) { if (!typeof(INetMessage).IsAssignableFrom(type)) { throw new ProtocolRegistrationException(type.FullName + " has [NetworkMessage] but is not an INetMessage"); } list2.Add(CreateDescriptor(type, val)); } } return RegisterDescriptors(list2, owner); } public IProtocolRegistration RegisterAssembly(Assembly assembly, string ownerId) { return RegisterAssembly(assembly, CreateOwner(ownerId)); } public IProtocolRegistration RegisterMessage(MessageDescriptor descriptor, ProtocolRegistrationOwner owner) { if (descriptor == null) { throw new ArgumentNullException("descriptor"); } RequireOwner(owner); return RegisterDescriptors(new MessageDescriptor[1] { descriptor }, owner); } public IProtocolRegistration RegisterMessage(MessageDescriptor descriptor, string ownerId) { return RegisterMessage(descriptor, CreateOwner(ownerId)); } public IProtocolRegistration RegisterHandler(Type messageType, ProtocolMessageHandler handler, ProtocolRegistrationOwner owner) { return RegisterHandlers(new ProtocolHandlerRegistration[1] { new ProtocolHandlerRegistration(messageType, handler) }, owner); } public IProtocolRegistration RegisterHandler(Type messageType, ProtocolMessageHandler handler, string ownerId) { return RegisterHandler(messageType, handler, CreateOwner(ownerId)); } public IProtocolRegistration RegisterHandlers(IReadOnlyList registrations, ProtocolRegistrationOwner owner) { if (registrations == null) { throw new ArgumentNullException("registrations"); } RequireOwner(owner); if (registrations.Count == 0) { throw new ArgumentException("At least one handler is required", "registrations"); } lock (_gate) { ThrowIfDisposedLocked(); List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < registrations.Count; i++) { ProtocolHandlerRegistration protocolHandlerRegistration = registrations[i] ?? throw new ProtocolRegistrationException("Null handler registration"); if (!typeof(INetMessage).IsAssignableFrom(protocolHandlerRegistration.MessageType) || protocolHandlerRegistration.MessageType.IsInterface || protocolHandlerRegistration.MessageType.IsAbstract || protocolHandlerRegistration.MessageType.IsGenericType) { throw new ProtocolRegistrationException("A handler requires a concrete INetMessage type"); } DescriptorRegistration descriptorRegistration = FindDescriptorLocked(protocolHandlerRegistration.MessageType); if (descriptorRegistration == null || descriptorRegistration.Retired) { throw new ProtocolRegistrationException("No message descriptor is registered for " + protocolHandlerRegistration.MessageType?.FullName); } MessageDescriptor descriptor = descriptorRegistration.Descriptor; HandlerRegistration value; bool flag = _handlers.TryGetValue(descriptor.WireName, out value) && !value.Retired; bool flag2 = _pendingHandlers.ContainsKey(descriptor.WireName); if (!hashSet.Add(descriptor.WireName) || flag || flag2) { throw new ProtocolRegistrationException("Duplicate network handler for " + descriptor.WireName); } list.Add(new HandlerRegistration { Owner = owner, Binding = CreateBinding(descriptor, protocolHandlerRegistration.Handler, owner) }); } for (int j = 0; j < list.Count; j++) { string fullName = list[j].Binding.MessageType.FullName; if (_frozen) { _pendingHandlers.Add(fullName, list[j]); } else { _handlers.Add(fullName, list[j]); } } if (!_frozen) { RebuildSnapshotLocked(); } return CreateHandlerHandle(owner, list); } } public bool TryGetDescriptor(Type messageType, out MessageDescriptor descriptor) { return CurrentSnapshot.TryGet(messageType, out descriptor); } public bool TryGetRegisteredDescriptor(Type messageType, out MessageDescriptor descriptor) { descriptor = null; if (messageType == null || messageType.FullName == null) { return false; } lock (_gate) { ThrowIfDisposedLocked(); DescriptorRegistration descriptorRegistration = FindDescriptorLocked(messageType); if (descriptorRegistration == null || descriptorRegistration.Retired) { return false; } descriptor = descriptorRegistration.Descriptor; return true; } } public bool TryGetDescriptor(string wireName, out MessageDescriptor descriptor) { return CurrentSnapshot.TryGet(wireName, out descriptor); } internal bool TryGetHandler(Type messageType, out ProtocolHandlerBinding handler) { return CurrentSnapshot.TryGetHandler(messageType, out handler); } internal void RaiseRejection(MessageRejection rejection) { this.Rejected?.Invoke(rejection); } internal static void EnsureCoreAssemblyRegistered(Assembly assembly) { if (assembly == null) { throw new ArgumentNullException("assembly"); } lock (Default._gate) { Default.ThrowIfDisposedLocked(); if (!Default._coreAssemblyRegistered) { IList descriptors = Default.DiscoverCoreDescriptors(assembly); Default.RegisterDescriptors(descriptors, Core.Owner); Default._coreAssemblyRegistered = true; } } } public void Dispose() { List list; lock (_gate) { if (_disposed) { return; } list = new List(_handlers.Count + _pendingHandlers.Count); foreach (HandlerRegistration value in _handlers.Values) { list.Add(value.Binding); } foreach (HandlerRegistration value2 in _pendingHandlers.Values) { list.Add(value2.Binding); } _disposed = true; _activeDescriptors.Clear(); _pendingDescriptors.Clear(); _handlers.Clear(); _pendingHandlers.Clear(); _sessionLease = null; _frozen = false; _snapshot = new ProtocolSnapshot(_generation, isFrozen: false, new Dictionary(StringComparer.Ordinal), new Dictionary(StringComparer.Ordinal)); this.Rejected = null; } for (int i = 0; i < list.Count; i++) { list[i].DeactivateAndWait(); } } private IProtocolRegistration RegisterDescriptors(IList descriptors, ProtocolRegistrationOwner owner) { if (descriptors == null || descriptors.Count == 0) { throw new ProtocolRegistrationException("At least one message descriptor is required"); } lock (_gate) { ThrowIfDisposedLocked(); if (CountLiveDescriptorsLocked() + descriptors.Count > 65536) { throw new ProtocolRegistrationException("A protocol snapshot cannot contain more than " + 65536 + " messages"); } HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < descriptors.Count; i++) { MessageDescriptor messageDescriptor = descriptors[i] ?? throw new ProtocolRegistrationException("Null message descriptor"); if (!hashSet.Add(messageDescriptor.WireName)) { throw new ProtocolRegistrationException("Duplicate network message registration: " + messageDescriptor.WireName); } DescriptorRegistration value; bool flag = _activeDescriptors.TryGetValue(messageDescriptor.WireName, out value) && !value.Retired; if (flag || _pendingDescriptors.ContainsKey(messageDescriptor.WireName)) { DescriptorRegistration descriptorRegistration = (flag ? value : _pendingDescriptors[messageDescriptor.WireName]); throw new ProtocolRegistrationException("Duplicate network message registration for " + messageDescriptor.WireName + " (already owned by " + descriptorRegistration.Owner.Id + ")"); } } List list = new List(); bool frozen = _frozen; for (int j = 0; j < descriptors.Count; j++) { DescriptorRegistration descriptorRegistration2 = new DescriptorRegistration { Descriptor = descriptors[j], Owner = owner }; list.Add(descriptorRegistration2); if (frozen) { _pendingDescriptors.Add(descriptors[j].WireName, descriptorRegistration2); continue; } if (_activeDescriptors.TryGetValue(descriptors[j].WireName, out var value2) && value2.Retired) { _activeDescriptors.Remove(descriptors[j].WireName); } if (_handlers.TryGetValue(descriptors[j].WireName, out var value3) && value3.Retired) { _handlers.Remove(descriptors[j].WireName); } _activeDescriptors.Add(descriptors[j].WireName, descriptorRegistration2); } if (!_frozen) { RebuildSnapshotLocked(); } return CreateDescriptorHandle(owner, list); } } private IList DiscoverCoreDescriptors(Assembly assembly) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException error) { array = GetSafeCoreTypes(assembly, error); } List list = new List(); foreach (Type type in array) { if (type == null) { throw CoreDiscoveryFailure(assembly, "a null type remained after validating a partial type load", null); } NetworkMessageAttribute val = (NetworkMessageAttribute)Attribute.GetCustomAttribute(type, typeof(NetworkMessageAttribute)); if (val != null) { if (!typeof(INetMessage).IsAssignableFrom(type)) { throw CoreDiscoveryFailure(assembly, type.FullName + " has [NetworkMessage] but is not an INetMessage", null); } list.Add(CreateDescriptor(type, val)); } } return list; } private static Type[] GetSafeCoreTypes(Assembly assembly, ReflectionTypeLoadException error) { Type[] types = error.Types; if (types == null) { throw CoreDiscoveryFailure(assembly, "ReflectionTypeLoadException did not provide a type list", error); } int num = 0; for (int i = 0; i < types.Length; i++) { if (types[i] == null) { num++; } } Exception[] loaderExceptions = error.LoaderExceptions; if (num == 0 || loaderExceptions == null || loaderExceptions.Length != num) { throw CoreDiscoveryFailure(assembly, "the partial type load could not account for every unloadable type", error); } for (int j = 0; j < loaderExceptions.Length; j++) { if (!IsOptionalSteamworksInfrastructureFailure(assembly, loaderExceptions[j])) { throw CoreDiscoveryFailure(assembly, "an unloadable type may contain a network message", error); } } string text = "Core protocol discovery skipped " + num + " unloadable Steamworks infrastructure type(s); all loadable protocol messages remain subject to normal attributed discovery"; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)text); } List list = new List(types.Length - num); for (int k = 0; k < types.Length; k++) { if (types[k] != null) { list.Add(types[k]); } } return list.ToArray(); } private static bool IsOptionalSteamworksInfrastructureFailure(Assembly assembly, Exception error) { if (assembly != typeof(MessageRegistry).Assembly || !(error is FileNotFoundException ex)) { return false; } if (ContainsOptionalSteamworksAssemblyName(ex.FileName)) { return true; } return ContainsOptionalSteamworksAssemblyName(error.Message); } private static bool ContainsOptionalSteamworksAssemblyName(string value) { if (value != null) { return value.IndexOf("com.rlabrecque.steamworks.net", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static ProtocolRegistrationException CoreDiscoveryFailure(Assembly assembly, string detail, Exception error) { string text = "Could not safely discover core protocol messages from assembly " + assembly.FullName + ": " + detail; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)text); } if (error != null) { return new ProtocolRegistrationException(text, error); } return new ProtocolRegistrationException(text); } private IProtocolRegistration CreateDescriptorHandle(ProtocolRegistrationOwner owner, IList registrations) { bool disposed = false; return new RegistrationHandle(owner, delegate { List list = new List(); lock (_gate) { if (_disposed) { disposed = true; return; } if (disposed) { return; } disposed = true; for (int i = 0; i < registrations.Count; i++) { DescriptorRegistration descriptorRegistration = registrations[i]; descriptorRegistration.Retired = true; if (_handlers.TryGetValue(descriptorRegistration.Descriptor.WireName, out var value)) { value.Retired = true; list.Add(value.Binding); if (!_frozen) { _handlers.Remove(descriptorRegistration.Descriptor.WireName); } } if (_pendingHandlers.TryGetValue(descriptorRegistration.Descriptor.WireName, out var value2)) { value2.Retired = true; list.Add(value2.Binding); _pendingHandlers.Remove(descriptorRegistration.Descriptor.WireName); } if (_pendingDescriptors.TryGetValue(descriptorRegistration.Descriptor.WireName, out var value3) && value3 == descriptorRegistration) { _pendingDescriptors.Remove(descriptorRegistration.Descriptor.WireName); } if (!_frozen && _activeDescriptors.TryGetValue(descriptorRegistration.Descriptor.WireName, out var value4) && value4 == descriptorRegistration) { _activeDescriptors.Remove(descriptorRegistration.Descriptor.WireName); } } if (!_frozen) { RebuildSnapshotLocked(); } } for (int j = 0; j < list.Count; j++) { list[j].DeactivateAndWait(); } }, delegate { lock (_gate) { if (disposed || _disposed) { return RegistrationState.Disposed; } for (int i = 0; i < registrations.Count; i++) { if (_pendingDescriptors.ContainsKey(registrations[i].Descriptor.WireName)) { return RegistrationState.PendingNextSession; } } return RegistrationState.Active; } }); } private IProtocolRegistration CreateHandlerHandle(ProtocolRegistrationOwner owner, IList registrations) { bool disposed = false; return new RegistrationHandle(owner, delegate { List list = new List(); lock (_gate) { if (_disposed) { disposed = true; return; } if (disposed) { return; } disposed = true; for (int i = 0; i < registrations.Count; i++) { HandlerRegistration handlerRegistration = registrations[i]; handlerRegistration.Retired = true; string fullName = handlerRegistration.Binding.MessageType.FullName; if (_handlers.TryGetValue(fullName, out var value) && value == handlerRegistration) { list.Add(handlerRegistration.Binding); if (!_frozen) { _handlers.Remove(fullName); } } if (_pendingHandlers.TryGetValue(fullName, out var value2) && value2 == handlerRegistration) { list.Add(value2.Binding); _pendingHandlers.Remove(fullName); } } if (!_frozen) { RebuildSnapshotLocked(); } } for (int j = 0; j < list.Count; j++) { list[j].DeactivateAndWait(); } }, delegate { lock (_gate) { if (disposed || _disposed) { return RegistrationState.Disposed; } for (int i = 0; i < registrations.Count; i++) { HandlerRegistration handlerRegistration = registrations[i]; if (handlerRegistration.Retired) { return RegistrationState.Disposed; } string fullName = handlerRegistration.Binding.MessageType.FullName; if (_pendingHandlers.TryGetValue(fullName, out var value) && value == handlerRegistration && !value.Retired) { return RegistrationState.PendingNextSession; } } return RegistrationState.Active; } }); } private void RebuildSnapshotLocked(IDictionary frozenDescriptors = null) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (frozenDescriptors != null) { foreach (KeyValuePair frozenDescriptor in frozenDescriptors) { dictionary[frozenDescriptor.Key] = frozenDescriptor.Value; } } else if (_frozen) { foreach (KeyValuePair descriptor in _snapshot.Descriptors) { dictionary[descriptor.Key] = descriptor.Value; } } else { foreach (KeyValuePair activeDescriptor in _activeDescriptors) { if (!activeDescriptor.Value.Retired) { dictionary[activeDescriptor.Key] = activeDescriptor.Value.Descriptor; } } } Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair handler in _handlers) { if (dictionary.ContainsKey(handler.Key) && !handler.Value.Retired) { dictionary2[handler.Key] = handler.Value.Binding; } } _snapshot = new ProtocolSnapshot(_generation, _frozen, dictionary, dictionary2); } private DescriptorRegistration FindDescriptorLocked(Type messageType) { if (messageType == null || messageType.FullName == null) { return null; } if (_activeDescriptors.TryGetValue(messageType.FullName, out var value) && !value.Retired && value.Descriptor.MessageType == messageType) { return value; } if (_pendingDescriptors.TryGetValue(messageType.FullName, out var value2) && !value2.Retired && value2.Descriptor.MessageType == messageType) { return value2; } return null; } private int CountLiveDescriptorsLocked() { int num = 0; foreach (KeyValuePair activeDescriptor in _activeDescriptors) { if (!activeDescriptor.Value.Retired) { num++; } } foreach (KeyValuePair pendingDescriptor in _pendingDescriptors) { if (!pendingDescriptor.Value.Retired) { num++; } } return num; } private static MessageDescriptor CreateDescriptor(Type type, NetworkMessageAttribute attribute) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return new MessageDescriptor(type, attribute.Reliability); } private static ProtocolHandlerBinding CreateBinding(MessageDescriptor descriptor, ProtocolMessageHandler handler, ProtocolRegistrationOwner owner) { return new ProtocolHandlerBinding(descriptor.MessageType, handler, owner); } private void ThrowIfDisposedLocked() { if (_disposed) { throw new ObjectDisposedException("ProtocolRegistry"); } } private static void RequireOwner(ProtocolRegistrationOwner owner) { if (owner == null) { throw new ArgumentNullException("owner"); } } } public sealed class ProtocolRegistrationException : InvalidOperationException { public ProtocolRegistrationException(string message) : base(message) { } public ProtocolRegistrationException(string message, Exception innerException) : base(message, innerException) { } } public sealed class MessageDescriptor { public Type MessageType { get; } public string WireName { get; } public Reliability Reliability { get; } public MessageDescriptor(Type messageType, Reliability reliability = (Reliability)0) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if (messageType == null) { throw new ArgumentNullException("messageType"); } if (!typeof(INetMessage).IsAssignableFrom(messageType) || messageType.IsInterface || messageType.IsAbstract || messageType.IsGenericType) { throw new ArgumentException("A message descriptor requires a concrete INetMessage type", "messageType"); } if (string.IsNullOrEmpty(messageType.FullName)) { throw new ArgumentException("A message descriptor requires a type with a FullName", "messageType"); } if (!Enum.IsDefined(typeof(Reliability), reliability)) { throw new ArgumentOutOfRangeException("reliability"); } MessageType = messageType; WireName = messageType.FullName; Reliability = reliability; } public override string ToString() { //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) return WireName + " (" + ((object)Reliability/*cast due to .constrained prefix*/).ToString() + ")"; } } public sealed class ProtocolRegistrationOwner { private readonly bool _isCoreOwner; public string Id { get; } internal bool IsCoreOwner => _isCoreOwner; public static ProtocolRegistrationOwner Create(string id) { return new ProtocolRegistrationOwner(id); } internal ProtocolRegistrationOwner(string id) : this(id, isCoreOwner: false) { } internal ProtocolRegistrationOwner(string id, bool isCoreOwner) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentException("A protocol owner needs an id", "id"); } Id = id.Trim(); _isCoreOwner = isCoreOwner; } } public delegate void ProtocolMessageHandler(MessageContext context, INetMessage message); public enum RegistrationState { Active, PendingNextSession, Disposed } public interface IProtocolRegistration : IDisposable { ProtocolRegistrationOwner Owner { get; } RegistrationState State { get; } } public interface IProtocolSessionLease : IDisposable { int Generation { get; } ProtocolSnapshot Snapshot { get; } } public sealed class ProtocolHandlerRegistration { public Type MessageType { get; } public ProtocolMessageHandler Handler { get; } public ProtocolHandlerRegistration(Type messageType, ProtocolMessageHandler handler) { MessageType = messageType ?? throw new ArgumentNullException("messageType"); Handler = handler ?? throw new ArgumentNullException("handler"); } } public sealed class ProtocolHandlerBinding { private readonly ProtocolMessageHandler _handler; private readonly object _lifetimeGate = new object(); private readonly Dictionary _inFlightByThread = new Dictionary(); private bool _active = true; private int _inFlight; public Type MessageType { get; } public string OwnerId { get; } internal bool IsCoreOwner { get; } internal ProtocolHandlerBinding(Type messageType, ProtocolMessageHandler handler, ProtocolRegistrationOwner owner) { MessageType = messageType; _handler = handler; OwnerId = owner.Id; IsCoreOwner = owner.IsCoreOwner; } internal bool Invoke(MessageContext context, INetMessage message) { int managedThreadId = Thread.CurrentThread.ManagedThreadId; lock (_lifetimeGate) { if (!_active) { return false; } _inFlight++; _inFlightByThread.TryGetValue(managedThreadId, out var value); _inFlightByThread[managedThreadId] = value + 1; } try { _handler(context, message); return true; } finally { lock (_lifetimeGate) { _inFlight--; _inFlightByThread[managedThreadId] -= 1; if (_inFlightByThread[managedThreadId] == 0) { _inFlightByThread.Remove(managedThreadId); } if (_inFlight == 0) { Monitor.PulseAll(_lifetimeGate); } } } } internal void DeactivateAndWait() { int managedThreadId = Thread.CurrentThread.ManagedThreadId; lock (_lifetimeGate) { _active = false; _inFlightByThread.TryGetValue(managedThreadId, out var value); while (_inFlight > value) { Monitor.Wait(_lifetimeGate); } } } } public sealed class MessageRejection { public string WireName { get; } public Type MessageType { get; } public string Reason { get; } public MessageContext Context { get; } internal MessageRejection(string wireName, Type messageType, string reason, MessageContext context) { WireName = wireName; MessageType = messageType; Reason = reason; Context = context; } public override string ToString() { return "Rejected " + (WireName ?? "") + ": " + Reason; } } internal sealed class ProtocolAuthorizationResult { public bool Allowed { get; } public string Reason { get; } private ProtocolAuthorizationResult(bool allowed, string reason) { Allowed = allowed; Reason = reason; } public static ProtocolAuthorizationResult Allow() { return new ProtocolAuthorizationResult(allowed: true, null); } public static ProtocolAuthorizationResult Deny(string reason) { return new ProtocolAuthorizationResult(allowed: false, reason ?? "authorization failed"); } } public sealed class ProtocolSnapshot { public const int MaxMessageCount = 65536; private readonly IReadOnlyDictionary _descriptors; private readonly IReadOnlyDictionary _handlers; private readonly IReadOnlyList _orderedWireNames; private readonly IReadOnlyDictionary _idsByName; private readonly IReadOnlyDictionary _descriptorsById; public int Generation { get; } public bool IsFrozen { get; } public IReadOnlyDictionary Descriptors => _descriptors; public IReadOnlyList OrderedWireNames => _orderedWireNames; internal ProtocolSnapshot(int generation, bool isFrozen, IDictionary descriptors, IDictionary handlers) { if (descriptors == null) { throw new ArgumentNullException("descriptors"); } if (descriptors.Count > 65536) { throw new ProtocolRegistrationException("A protocol snapshot cannot contain more than " + 65536 + " messages"); } Generation = generation; IsFrozen = isFrozen; _descriptors = new ReadOnlyDictionary(new Dictionary(descriptors, StringComparer.Ordinal)); _handlers = new ReadOnlyDictionary(new Dictionary(handlers, StringComparer.Ordinal)); List list = new List(descriptors.Keys); list.Sort(StringComparer.Ordinal); _orderedWireNames = new ReadOnlyCollection(list); Dictionary dictionary = new Dictionary(list.Count, StringComparer.Ordinal); Dictionary dictionary2 = new Dictionary(list.Count); for (int i = 0; i < list.Count; i++) { ushort num = checked((ushort)i); string key = list[i]; dictionary.Add(key, num); dictionary2.Add(num, _descriptors[key]); } _idsByName = new ReadOnlyDictionary(dictionary); _descriptorsById = new ReadOnlyDictionary(dictionary2); } public bool TryGet(string wireName, out MessageDescriptor descriptor) { descriptor = null; if (wireName != null) { return _descriptors.TryGetValue(wireName, out descriptor); } return false; } public bool TryGet(Type messageType, out MessageDescriptor descriptor) { descriptor = null; if (messageType != null && messageType.FullName != null && _descriptors.TryGetValue(messageType.FullName, out descriptor)) { return descriptor.MessageType == messageType; } return false; } public bool TryGet(ushort id, out MessageDescriptor descriptor) { return _descriptorsById.TryGetValue(id, out descriptor); } public bool TryGetId(string wireName, out ushort id) { id = 0; if (wireName != null) { return _idsByName.TryGetValue(wireName, out id); } return false; } public bool TryGetId(Type messageType, out ushort id) { id = 0; if (messageType != null && messageType.FullName != null && _idsByName.TryGetValue(messageType.FullName, out id)) { return _descriptorsById[id].MessageType == messageType; } return false; } public bool TryGetName(ushort id, out string wireName) { wireName = null; if (!_descriptorsById.TryGetValue(id, out var value)) { return false; } wireName = value.WireName; return true; } public bool TryGetMessageType(ushort id, out Type messageType) { messageType = null; if (!_descriptorsById.TryGetValue(id, out var value)) { return false; } messageType = value.MessageType; return true; } public bool TryGetHandler(Type messageType, out ProtocolHandlerBinding handler) { handler = null; if (messageType != null && messageType.FullName != null && _handlers.TryGetValue(messageType.FullName, out handler)) { return handler.MessageType == messageType; } return false; } } } namespace CardShopCoop.Net.Messages { [NetworkMessage] public sealed class FullyJoinedMessage : INetMessage { } [NetworkMessage] public sealed class FullyJoinedAckMessage : INetMessage { } [NetworkMessage] public sealed class DisconnectMessage : INetMessage { private const int MaxReasonLength = 1024; private const int MaxCodeLength = 32; private string _reason; private string _code; public string Reason { get { return _reason; } set { _reason = (string.IsNullOrWhiteSpace(value) ? "connection closed" : value.Trim().Substring(0, Math.Min(1024, value.Trim().Length))); } } public string Code { get { return _code; } set { string text = (string.IsNullOrWhiteSpace(value) ? "closed" : value.Trim()); _code = text.Substring(0, Math.Min(32, text.Length)); } } public bool Retryable { get; set; } public int Phase { get; set; } } [NetworkMessage] public sealed class ByeMessage : INetMessage { public string Reason; } public sealed class EnumMemberDto { public string Name; public long Value; } public sealed class EnumKindIdentityDto { public int Kind; public List Members = new List(); } [NetworkMessage] public sealed class HelloMessage : INetMessage { public int WireVersion; public string Version; public string PlayerName; public string Password; public string PluginHash; public string EnumHash; public string CardsHash; public List PluginList = new List(); public List CardsList = new List(); public List EnumIdentity = new List(); public string GameVersion; public string UnityVersion; public ulong SteamId; public List MessageCatalog = new List(); public byte[] MessageReliability = new byte[0]; } [NetworkMessage] public sealed class WelcomeMessage : INetMessage { public int WireVersion; public string Version; public string HostName; public int SaveLength; public int BundleLength; public bool SidecarsComplete; public string SidecarWarning; public int SelfId; public List HostEnumIdentity = new List(); public byte[] HostCardsBlob = new byte[0]; public ulong SteamId; public List MessageCatalog = new List(); public byte[] MessageReliability = new byte[0]; } } namespace CardShopCoop.Net.Kcp { public class KcpSession : KcpPeer { private readonly bool isServer; private readonly int mtu; private bool started; private readonly Action> rawSendCallback; private readonly Action, KcpChannel> dataCallback; private readonly Action authenticatedCallback; private readonly Action disconnectedCallback; private readonly Action errorCallback; private uint pendingCookie; public bool IsServer => isServer; public bool IsClient => !isServer; public bool IsStarted => started; public bool IsAuthenticated => state == KcpState.Authenticated; public bool IsDisconnected => state == KcpState.Disconnected; public KcpState State => state; public uint Cookie => cookie; public int Mtu => mtu; public new int ReliableMaxMessageSize => reliableMax; public new int UnreliableMaxMessageSize => unreliableMax; public int ReliableMax => reliableMax; public int UnreliableMax => unreliableMax; public int PendingReliableSegments => kcp.WaitSnd; public event Action Authenticated; public event Action, KcpChannel> DataReceived; public event Action Disconnected; public event Action Error; public KcpSession(KcpConfig config, bool isServer, uint cookie, Action> rawSend, Action, KcpChannel> onData = null, Action onAuthenticated = null, Action onDisconnected = null, Action onError = null, uint initialTime = 0u) : base(ValidateConfig(config), ValidateCookie(isServer, cookie), initialTime) { if (rawSend == null) { throw new ArgumentNullException("rawSend"); } this.isServer = isServer; mtu = config.Mtu; rawSendCallback = rawSend; dataCallback = onData; authenticatedCallback = onAuthenticated; disconnectedCallback = onDisconnected; errorCallback = onError; } public KcpSession(KcpConfig config, bool isServer, uint cookie, Action> rawSend, uint initialTime) : this(config, isServer, cookie, rawSend, null, null, null, null, initialTime) { } private static KcpConfig ValidateConfig(KcpConfig config) { if (config == null) { throw new ArgumentNullException("config"); } if (config.Mtu < 55) { throw new ArgumentOutOfRangeException("Mtu", $"KCP MTU must leave room for channel, cookie, and a KCP MTU of at least {50} bytes."); } if (config.Interval == 0) { throw new ArgumentOutOfRangeException("Interval"); } if (config.ReceiveWindowSize < 2) { throw new ArgumentOutOfRangeException("ReceiveWindowSize"); } return config; } private static uint ValidateCookie(bool isServer, uint cookie) { if (isServer && cookie == 0) { throw new ArgumentOutOfRangeException("cookie", "A server KCP cookie must be non-zero."); } return cookie; } public void FlushOutgoing() { if (!IsDisconnected) { kcp.Flush(); } } public bool Start(uint now) { if (started || IsDisconnected) { return false; } SetInitialTime(now); started = true; if (IsClient) { SendHello(); } return true; } public bool Start() { return Start(0u); } public override void TickIncoming(uint now) { EnsureStarted(now); base.TickIncoming(now); } public override void TickOutgoing(uint now) { EnsureStarted(now); base.TickOutgoing(now); } public override void Tick(uint now) { EnsureStarted(now); base.Tick(now); } private void EnsureStarted(uint now) { if (!started && !IsDisconnected) { Start(now); } } public bool InputDatagram(ArraySegment datagram) { if (IsDisconnected || datagram.Array == null || datagram.Count <= 5) { return false; } if (datagram.Count > mtu) { ReportError(ErrorCode.InvalidReceive, $"{GetType()}: datagram size {datagram.Count} exceeds outer MTU {mtu}."); return false; } int offset = datagram.Offset; byte b = datagram.Array[offset]; Utils.Decode32U(datagram.Array, offset + 1, out var value); if (b != 1 && b != 2) { Log.Warning($"[KCP] {GetType()}: invalid channel header {b}."); return false; } if (isServer) { if (state == KcpState.Authenticated && value != cookie) { return false; } } else { if (cookie == 0 && value == 0) { return false; } if (cookie != 0 && value != cookie) { return false; } } ArraySegment message = new ArraySegment(datagram.Array, offset + 5, datagram.Count - 5); switch (b) { case 1: { bool flag = OnRawInputReliable(message); if (!isServer && cookie == 0 && value != 0 && flag && IsReliableHelloPayload(message)) { pendingCookie = value; } return true; } case 2: OnRawInputUnreliable(message); return true; default: return false; } } private static bool IsReliableHelloPayload(ArraySegment message) { int num = message.Offset; int num2 = message.Count; bool result = false; while (num2 > 0) { if (num2 < 24) { return false; } byte b = message.Array[num + 4]; byte b2 = message.Array[num + 5]; Utils.Decode32U(message.Array, num + 20, out var value); if (value > (uint)(num2 - 24)) { return false; } if (b == 81 && b2 == 0 && value == 1 && message.Array[num + 24] == 1) { result = true; } int num3 = (int)(24 + value); num += num3; num2 -= num3; } return result; } public bool InputDatagram(byte[] datagram, int offset, int count) { if (datagram == null) { throw new ArgumentNullException("datagram"); } return InputDatagram(new ArraySegment(datagram, offset, count)); } public bool InputDatagram(byte[] datagram) { if (datagram == null) { throw new ArgumentNullException("datagram"); } return InputDatagram(new ArraySegment(datagram)); } public bool Send(ArraySegment data, KcpChannel channel) { if (!IsAuthenticated || data.Array == null) { return false; } if (data.Count == 0) { return SendData(data, channel); } if (channel == KcpChannel.Reliable && data.Count > reliableMax) { return RejectOversize(data.Count, reliableMax, reliable: true); } if (channel == KcpChannel.Unreliable && data.Count > unreliableMax) { return RejectOversize(data.Count, unreliableMax, reliable: false); } return SendData(data, channel); } public bool Send(byte[] data, KcpChannel channel) { if (data == null) { throw new ArgumentNullException("data"); } return Send(new ArraySegment(data), channel); } public bool SendReliable(ArraySegment data) { return Send(data, KcpChannel.Reliable); } public bool SendReliable(byte[] data) { if (data == null) { throw new ArgumentNullException("data"); } return SendReliable(new ArraySegment(data)); } public bool SendReliable(byte[] data, int offset, int count) { if (data == null) { throw new ArgumentNullException("data"); } return SendReliable(new ArraySegment(data, offset, count)); } public bool SendUnreliable(ArraySegment data) { return Send(data, KcpChannel.Unreliable); } public bool SendUnreliable(byte[] data) { if (data == null) { throw new ArgumentNullException("data"); } return SendUnreliable(new ArraySegment(data)); } public bool SendUnreliable(byte[] data, int offset, int count) { if (data == null) { throw new ArgumentNullException("data"); } return SendUnreliable(new ArraySegment(data, offset, count)); } private bool RejectOversize(int actual, int limit, bool reliable) { ReportError(ErrorCode.InvalidSend, string.Format("{0}: {1} payload size {2} exceeds limit {3}.", GetType(), reliable ? "reliable" : "unreliable", actual, limit)); return false; } protected override void OnAuthenticated() { if (isServer) { SendHello(); } authenticatedCallback?.Invoke(); this.Authenticated?.Invoke(); } protected override bool OnReliableHelloReceived() { if (isServer) { return true; } if (pendingCookie == 0) { OnError(ErrorCode.InvalidReceive, $"[KCP] {GetType()}: reliable Hello did not arrive with a valid non-zero cookie."); return false; } cookie = pendingCookie; pendingCookie = 0u; Log.Info($"[KCP] {GetType()}: accepted server cookie={cookie} from reliable Hello."); return true; } protected override void OnData(ArraySegment message, KcpChannel channel) { dataCallback?.Invoke(message, channel); this.DataReceived?.Invoke(message, channel); } protected override void OnDisconnected() { disconnectedCallback?.Invoke(); this.Disconnected?.Invoke(); } protected override void OnError(ErrorCode error, string message) { errorCallback?.Invoke(error, message); this.Error?.Invoke(error, message); } protected override void RawSend(ArraySegment data) { rawSendCallback(data); } private void ReportError(ErrorCode error, string message) { OnError(error, message); } } public sealed class KcpSessionManager : ICoopTransport, IDisposable, ICoopHandshakeTransport, ICoopRelayTransport, ICoopStartable { private enum FrameAdmission { Admitted, FrameTooLarge, PeerCapacity } private sealed class SessionState { internal readonly DatagramPeer DatagramPeer; internal readonly PeerConnection Connection; internal readonly Queue Reliable = new Queue(); internal readonly Queue Transient = new Queue(); internal readonly Queue PendingCompact = new Queue(); internal KcpSession Session; internal byte[] Scratch; internal int FragmentPayloadLimit; internal bool Authenticated; internal bool MessageIdsActivated; internal bool Terminal; internal bool DisconnectRequested; internal bool DisconnectRequestQueued; internal DisconnectInfo DisconnectInfo; internal bool PendingTerminalDisconnect; internal uint TerminalDisconnectDeadline; internal bool PendingDisconnectCompletion; internal uint DisconnectCompletionDeadline; internal bool HasError; internal string LastErrorDetail; internal uint LastReceiveTime; internal uint HandshakeStarted; internal uint ReassemblyLastReceive; internal uint NextFrameId = 1u; internal uint NextTransientFrameId = 1u; internal uint NextInboundFrameId = 1u; internal uint ReassemblyFrameId; internal uint ReassemblyTotal; internal uint ReassemblyOffset; internal int ReassemblyReservedBytes; internal byte[] Reassembly; internal int ReliableFrameCount; internal int ReliableBytes; internal int TransientFrameCount; internal int TransientBytes; internal OutboundFrame ActiveReliable; internal SessionState(DatagramPeer peer, int id) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown DatagramPeer = peer; Connection = new PeerConnection(id); } } private sealed class OutboundFrame { internal readonly byte[] Frame; internal uint Id; internal int Offset; internal OutboundFrame(byte[] frame, uint id) { Frame = frame; Id = id; } } private sealed class PendingCompactFrame { internal readonly byte[] Frame; internal readonly bool Reliable; internal PendingCompactFrame(byte[] frame, bool reliable) { Frame = frame; Reliable = reliable; } } private struct QueueAdmission { internal readonly SessionState State; internal readonly byte[] Frame; internal readonly MessageDescriptor Descriptor; internal QueueAdmission(SessionState state, byte[] frame, MessageDescriptor descriptor) { State = state; Frame = frame; Descriptor = descriptor; } } private struct IncomingItem { internal readonly InMsg Message; internal readonly int EncodedBytes; internal IncomingItem(InMsg message, int encodedBytes) { Message = message; EncodedBytes = encodedBytes; } } private sealed class DecodeWork { internal byte[] Frame; internal SessionState State; internal bool Reliable; internal bool CompactIds; internal bool BeforeActivation; internal ProtocolSnapshot Snapshot; internal int FrameLimit; internal int EncodedBytes; } private struct DecodeResult { internal SessionState State; internal InMsg Message; internal bool Reliable; internal bool BeforeActivation; internal ProtocolSnapshot Snapshot; internal bool Ok; internal int EncodedBytes; } private struct PeerSignal { internal readonly DatagramPeer Peer; internal readonly bool Closed; internal readonly string Reason; internal PeerSignal(DatagramPeer peer, bool closed, string reason) { Peer = peer; Closed = closed; Reason = reason; } } private struct DisconnectRequest { internal readonly SessionState State; internal readonly DisconnectInfo Info; internal DisconnectRequest(SessionState state, DisconnectInfo info) { State = state; Info = info; } } private struct Envelope { internal byte Kind; internal uint FrameId; internal uint TotalLength; internal uint Offset; internal int PayloadLength; } private const byte EnvelopeMagic0 = 75; private const byte EnvelopeMagic1 = 67; private const byte EnvelopeVersion = 1; private const byte EnvelopeComplete = 1; private const byte EnvelopeFragment = 2; private const byte EnvelopeUnreliable = 3; private const int EnvelopeSize = 20; private readonly IDatagramTransport _datagrams; private readonly bool _isHost; private readonly KcpConfig _config; private readonly KcpSessionManagerOptions _options; private readonly object _gate = new object(); private readonly Dictionary _states = new Dictionary(); private readonly Dictionary _connections = new Dictionary(); private readonly LinkedList _peerSignals = new LinkedList(); private readonly Dictionary> _pendingPeerAvailableSignals = new Dictionary>(); private readonly HashSet _peerTerminalSignals = new HashSet(); private readonly ConcurrentQueue _disconnectRequests = new ConcurrentQueue(); private readonly Stopwatch _clockStopwatch = Stopwatch.StartNew(); private readonly object _clockGate = new object(); private readonly Func _clock; private uint _lastClock; private bool _hasClock; private int _pumpThreadId; private int _pumpActive; private int _disconnectRequestCount; private bool _pumping; private bool _started; private bool _stopRequested; private bool _disposeRequested; private bool _disposed; private bool _resourcesDisposed; private int _nextHostConnectionId = 1; private const int TerminalFrameAckGraceMs = 1000; private const int DisconnectDecodeGraceMs = 250; private int _reassemblyBytes; private int _incomingBytes; private int _pendingActivationFrames; private int _pendingActivationBytes; private IProtocolSessionLease _sessionLease; private ProtocolSnapshot _messageSnapshot; private readonly ConcurrentQueue _incoming = new ConcurrentQueue(); private readonly ConcurrentQueue _decodeQueue = new ConcurrentQueue(); private readonly ConcurrentQueue _decodeResults = new ConcurrentQueue(); private SemaphoreSlim _decodeSignal; private Thread _decodeThread; private bool _decodeStop; private int _decodePendingFrames; private int _decodePendingBytes; private int _datagramsThisPump; private int _framesThisPump; private readonly Queue _pendingDisconnectResults = new Queue(); public ConcurrentQueue Disconnects { get; } = new ConcurrentQueue(); internal int PendingPeerSignalCount { get { lock (_gate) { return _peerSignals.Count; } } } internal int PendingDisconnectRequestCount => Math.Max(0, Volatile.Read(in _disconnectRequestCount)); internal int PendingDisconnectResultCount { get { lock (_gate) { return _pendingDisconnectResults.Count + Disconnects.Count; } } } public bool IsHost => _isHost; public KcpConfig Config => CopyConfig(_config); public double TimeoutSeconds => (double)_config.Timeout / 1000.0; public IReadOnlyList Connections { get { lock (_gate) { List list = new List(_connections.Count); foreach (SessionState value in _connections.Values) { list.Add(value.Connection); } return list; } } } public int ConnectionCount { get { lock (_gate) { return _connections.Count; } } } public bool IsRunning { get { lock (_gate) { return Volatile.Read(in _started) && !Volatile.Read(in _stopRequested) && !Volatile.Read(in _disposed); } } } public event Action PeerConnected; public bool TryDequeueIncoming(out InMsg message) { lock (_gate) { if (!_incoming.TryDequeue(out var result)) { message = default(InMsg); return false; } _incomingBytes -= result.EncodedBytes; if (_incomingBytes < 0) { LogError("KCP incoming-byte accounting underflow"); _incomingBytes = 0; } message = result.Message; return true; } } public KcpSessionManager(IDatagramTransport datagrams, bool isHost) : this(datagrams, isHost, new KcpConfig(), null) { } public KcpSessionManager(IDatagramTransport datagrams, bool isHost, KcpConfig config) : this(datagrams, isHost, config, null) { } public KcpSessionManager(IDatagramTransport datagrams, bool isHost, KcpSessionManagerOptions options) : this(datagrams, isHost, new KcpConfig(), options) { } public KcpSessionManager(IDatagramTransport datagrams, bool isHost, KcpConfig config, KcpSessionManagerOptions options) { _datagrams = datagrams ?? throw new ArgumentNullException("datagrams"); _isHost = isHost; _config = ValidateConfig(CopyConfig(config ?? new KcpConfig()), datagrams.MaxDatagramSize); _options = (options ?? new KcpSessionManagerOptions()).Copy(); _options.Validate(); _clock = _options.Clock ?? new Func(ClockNow); _datagrams.PeerAvailable += OnPeerAvailable; _datagrams.PeerClosed += OnPeerClosed; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)("[net] datagram transport=" + _datagrams.GetType().Name + " host=" + isHost)); } } public KcpSessionManager(bool isHost, IDatagramTransport datagrams) : this(datagrams, isHost) { } public KcpSessionManager(bool isHost, IDatagramTransport datagrams, KcpConfig config, KcpSessionManagerOptions options = null) : this(datagrams, isHost, config, options) { } public KcpSessionManager(bool isHost, IDatagramTransport datagrams, KcpSessionManagerOptions options) : this(datagrams, isHost, new KcpConfig(), options) { } public void Start() { EnsureNotDisposed(); lock (_gate) { if (_pumpThreadId == 0) { _pumpThreadId = Thread.CurrentThread.ManagedThreadId; } else if (_pumpThreadId != Thread.CurrentThread.ManagedThreadId) { throw new InvalidOperationException("KCP transport lifecycle must run on the pump thread"); } if (Volatile.Read(in _disposed) || Volatile.Read(in _disposeRequested)) { throw new ObjectDisposedException("KcpSessionManager"); } if (Volatile.Read(in _started)) { return; } IProtocolSessionLease protocolSessionLease = null; bool flag = false; try { protocolSessionLease = (_sessionLease = MessageRegistry.BeginSession()); _messageSnapshot = protocolSessionLease.Snapshot; Volatile.Write(ref _stopRequested, value: false); Volatile.Write(ref _started, value: true); flag = true; _datagrams.Start(); } catch (Exception innerException) { Volatile.Write(ref _started, value: false); Volatile.Write(ref _stopRequested, value: false); if (_sessionLease == protocolSessionLease) { _sessionLease = null; _messageSnapshot = null; } ClearPeerSignalsLocked(); DisconnectRequest result; while (_disconnectRequests.TryDequeue(out result)) { } Volatile.Write(ref _disconnectRequestCount, 0); try { if (flag) { _datagrams.Stop(); } } catch (Exception ex) { LogError("KCP bearer startup cleanup failed: " + ex); } finally { protocolSessionLease?.Dispose(); } ClearPeerSignalsLocked(); while (_disconnectRequests.TryDequeue(out result)) { } Volatile.Write(ref _disconnectRequestCount, 0); throw new InvalidOperationException("KCP transport startup failed after acquiring its protocol session", innerException); } } } public void PumpNetworkThread() { if (Interlocked.Exchange(ref _pumpActive, 1) != 0) { throw new InvalidOperationException("KCP transport cannot be pumped concurrently"); } int managedThreadId = Thread.CurrentThread.ManagedThreadId; if (_pumpThreadId == 0) { _pumpThreadId = managedThreadId; } else if (_pumpThreadId != managedThreadId) { Volatile.Write(ref _pumpActive, 0); throw new InvalidOperationException("KCP transport must use one pump thread"); } _pumping = true; int budget = _options.MaxDisconnectResultsPerPump; try { if (Volatile.Read(in _stopRequested)) { StopOnPump(); int budget2 = int.MaxValue; DrainDisconnectResults(ref budget2); } else { if (!Volatile.Read(in _started) || Volatile.Read(in _disposed)) { return; } uint num = MonotonicNow(); int budget3 = _options.MaxPeerSignalsPerPump; int budget4 = _options.MaxDisconnectRequestsPerPump; using (PerfProbe.ThreadSample("net.signal")) { DrainPeerSignals(ref budget3); DrainDisconnectRequests(ref budget4); } _datagramsThisPump = 0; _framesThisPump = 0; using (PerfProbe.ThreadSample("net.poll")) { _datagrams.Poll(_options.MaxDatagramsPerPump, OnDatagram); PerfProbe.RecordQueueDepth("net.datagrams", _datagramsThisPump, _options.MaxDatagramsPerPump); } using (PerfProbe.ThreadSample("net.signal")) { DrainPeerSignals(ref budget3); DrainDisconnectRequests(ref budget4); } using (PerfProbe.ThreadSample("net.sessions")) { SessionState[] array; lock (_gate) { array = new SessionState[_states.Count]; _states.Values.CopyTo(array, 0); } foreach (SessionState sessionState in array) { if (sessionState.Terminal) { continue; } if (!sessionState.MessageIdsActivated && num - sessionState.HandshakeStarted > _options.PendingHandshakeTimeoutMs) { DisconnectForProtocol(sessionState, "application handshake timed out before message-id activation"); continue; } if (sessionState.Reassembly != null && num - sessionState.ReassemblyLastReceive > _options.ReassemblyTimeoutMs) { ClearReassembly(sessionState); DisconnectForProtocol(sessionState, "reliable frame reassembly timed out"); continue; } sessionState.Session.TickIncoming(num); if (sessionState.Terminal) { continue; } FlushTransient(sessionState); FlushReliable(sessionState); sessionState.Session.TickOutgoing(num); if (sessionState.DisconnectRequested && !sessionState.Terminal) { DisconnectOnPump(sessionState); } if (sessionState.PendingTerminalDisconnect && !sessionState.Terminal) { bool flag = sessionState.Session.PendingReliableSegments == 0; if (flag || (int)(num - sessionState.TerminalDisconnectDeadline) >= 0) { LogInfo("coop: completing held disconnect for peer " + sessionState.Connection.Id + " (acked=" + flag + ")."); FinishDisconnectOnPump(sessionState); } } if (sessionState.PendingDisconnectCompletion && !sessionState.Terminal && (int)(num - sessionState.DisconnectCompletionDeadline) >= 0) { DrainDecodeResults(); if (!sessionState.Terminal) { string[] obj = new string[5] { "coop: completing deferred disconnect for peer ", sessionState.Connection.Id.ToString(), " with fallback reason '", null, null }; DisconnectInfo disconnectInfo = sessionState.DisconnectInfo; obj[3] = ((disconnectInfo != null) ? disconnectInfo.Reason : null) ?? ""; obj[4] = "'."; LogInfo(string.Concat(obj)); CompleteDisconnect(sessionState, sessionState.DisconnectInfo); } } } } PerfProbe.RecordQueueDepth("net.frames", _framesThisPump, _options.MaxIncomingFrames); bool flag2 = Volatile.Read(in _disposeRequested); using (PerfProbe.ThreadSample("net.decode-drain")) { DrainDecodeResults(); } if (flag2) { StopOnPump(); int budget5 = int.MaxValue; using (PerfProbe.ThreadSample("net.disconnect-drain")) { DrainDisconnectResults(ref budget5); return; } } using (PerfProbe.ThreadSample("net.disconnect-drain")) { DrainDisconnectResults(ref budget); return; } } } finally { _pumping = false; Volatile.Write(ref _pumpActive, 0); } } public void Send(PeerConnection connection, INetMessage message) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 SessionState value = null; string text = null; bool flag = false; lock (_gate) { byte[] frame; MessageDescriptor descriptor; if (connection == null || !_connections.TryGetValue(connection.Id, out value) || value.Connection != connection) { text = "connection is not owned by this transport"; } else if (value.Terminal || (int)value.Connection.State == 1 || !value.MessageIdsActivated || !Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { text = "connection is not ready for application traffic (phase " + ((object)value.Connection.State/*cast due to .constrained prefix*/).ToString() + ")"; } else if (!TryEncode(message, compactIds: true, out frame, out descriptor)) { text = "message is not registered in the active session catalog (late registrations apply next session) or exceeds the encoded frame limit"; } else { switch (CanQueueFrameLocked(value, frame, descriptor)) { case FrameAdmission.Admitted: EnqueueFrameLocked(value, frame, descriptor); break; case FrameAdmission.PeerCapacity: flag = true; break; default: text = "message exceeds the lane frame limits"; break; } } } if (text != null) { FailApplicationSend(connection, message, value, text); } else if (flag) { FailPeerAdmission(connection, message, value, "bounded outbound queue is full"); } } void ICoopHandshakeTransport.SendHandshake(PeerConnection connection, INetMessage message) { SessionState value = null; string failure = null; lock (_gate) { if (connection == null || !_connections.TryGetValue(connection.Id, out value) || value.Connection != connection) { failure = "connection is not owned by this transport"; } else { QueueHandshake(message, value, out failure); } } if (failure != null) { FailApplicationSend(connection, message, value, "handshake " + failure); } } public void ActivateMessageIds(PeerConnection connection) { if (connection == null) { throw new ArgumentNullException("connection"); } if (!IsPumpThread()) { throw new InvalidOperationException("KCP message-id activation must run on the pump thread"); } lock (_gate) { if (!_connections.TryGetValue(connection.Id, out var value) || value.Connection != connection || value.Terminal || !value.Authenticated || value.Session == null || !value.Session.IsAuthenticated || connection.IsDisconnectingOrDisconnected || _messageSnapshot == null || !Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { throw new InvalidOperationException("Cannot activate message ids for an unknown or disconnected KCP peer: " + (object)connection); } if (!value.MessageIdsActivated) { value.MessageIdsActivated = true; DrainPendingCompactLocked(value); } } } public void Broadcast(INetMessage message) { if (message == null) { throw new ArgumentNullException("message"); } FanOut(message, null, "broadcast"); } void ICoopRelayTransport.Relay(INetMessage message, int exceptConnectionId) { if (message == null) { throw new ArgumentNullException("message"); } FanOut(message, exceptConnectionId, "relay"); } private void FanOut(INetMessage message, int? exceptConnectionId, string operation) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Invalid comparison between Unknown and I4 string text = null; List list = null; lock (_gate) { if (!Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { text = "transport is not running"; } else { byte[] frame = null; MessageDescriptor descriptor = null; List list2 = new List(_connections.Count); foreach (SessionState value in _connections.Values) { if (!value.Terminal && !value.Connection.IsDisconnectingOrDisconnected && (int)value.Connection.State != 1 && value.MessageIdsActivated && (!exceptConnectionId.HasValue || value.Connection.Id != exceptConnectionId.Value)) { if (frame == null && !TryEncode(message, compactIds: true, out frame, out descriptor)) { text = "message is not registered in the active session catalog (late registrations apply next session) or exceeds the encoded frame limit"; break; } switch (CanQueueFrameLocked(value, frame, descriptor)) { case FrameAdmission.PeerCapacity: (list ?? (list = new List())).Add(value); break; case FrameAdmission.FrameTooLarge: text = "message exceeds the lane frame limits"; goto end_IL_0136; default: list2.Add(new QueueAdmission(value, frame, descriptor)); break; } } continue; end_IL_0136: break; } if (text == null) { for (int i = 0; i < list2.Count; i++) { QueueAdmission queueAdmission = list2[i]; EnqueueFrameLocked(queueAdmission.State, queueAdmission.Frame, queueAdmission.Descriptor); } } } } if (text != null) { FailApplicationSend(null, message, null, operation + " " + text); } else if (list != null) { for (int j = 0; j < list.Count; j++) { FailPeerAdmission(list[j].Connection, message, list[j], operation + " bounded outbound queue is full"); } } } public double SecondsSinceLastRecv(PeerConnection connection) { if (connection == null) { return double.MaxValue; } SessionState value; lock (_gate) { if (!_connections.TryGetValue(connection.Id, out value) || value.Connection != connection) { return double.MaxValue; } } return (double)(MonotonicNow() - value.LastReceiveTime) / 1000.0; } public void Kick(PeerConnection connection, DisconnectInfo info = null) { GracefulDisconnect(connection, info); } public void GracefulDisconnect(PeerConnection connection, DisconnectInfo info = null) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (connection == null) { return; } SessionState value; lock (_gate) { if (!_connections.TryGetValue(connection.Id, out value) || value.Connection != connection || !Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { return; } } DisconnectInfo val = (DisconnectInfo)(((object)info) ?? ((object)new DisconnectInfo("connection closed", false, "closed", false, (ConnectionState)4))); DisconnectRequest request = new DisconnectRequest(value, val); if (IsPumpThread() && _pumping) { lock (_gate) { value.DisconnectInfo = val; } RequestDisconnectOnPump(request); return; } lock (_gate) { if (value.Terminal || value.DisconnectRequested || value.DisconnectRequestQueued) { if (!value.Terminal) { value.DisconnectInfo = val; } return; } value.DisconnectRequestQueued = true; value.DisconnectInfo = val; } try { EnqueueDisconnectRequest(request); } catch { lock (_gate) { value.DisconnectRequestQueued = false; } throw; } } public void Stop() { lock (_gate) { if (!Volatile.Read(in _started) || Volatile.Read(in _disposed)) { return; } Volatile.Write(ref _stopRequested, value: true); } if (IsPumpThread()) { StopOnPump(); if (!_pumping) { int budget = int.MaxValue; DrainDisconnectResults(ref budget); } } } public void Dispose() { IProtocolSessionLease protocolSessionLease = null; bool flag = false; lock (_gate) { if (Volatile.Read(in _disposed)) { return; } Volatile.Write(ref _disposeRequested, value: true); Volatile.Write(ref _stopRequested, value: true); if (!Volatile.Read(in _started)) { Volatile.Write(ref _disposed, value: true); Volatile.Write(ref _stopRequested, value: false); protocolSessionLease = _sessionLease; _sessionLease = null; _messageSnapshot = null; flag = true; } } if (flag) { protocolSessionLease?.Dispose(); DisposeBearerResources(); } else if (IsPumpThread()) { StopOnPump(); if (!_pumping) { int budget = int.MaxValue; DrainDisconnectResults(ref budget); } } } private bool QueueHandshake(INetMessage message, SessionState state, out string failure) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 failure = null; if (state.Terminal || ((int)state.Connection.State != 1 && (int)state.Connection.State != 2) || state.MessageIdsActivated || !Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { failure = "connection is not in a pre-activation phase"; return false; } if (!TryEncode(message, compactIds: false, out var frame, out var descriptor)) { failure = "message is not registered in the active session catalog (late registrations apply next session) or exceeds the encoded frame limit"; return false; } if (!QueueFrameLocked(state, frame, descriptor)) { failure = "bounded outbound queue is full"; return false; } return true; } private bool TryEncode(INetMessage message, bool compactIds, out byte[] frame, out MessageDescriptor descriptor) { frame = null; descriptor = null; if (message == null) { throw new ArgumentNullException("message"); } ProtocolSnapshot messageSnapshot = _messageSnapshot; if (messageSnapshot == null) { return false; } if (!messageSnapshot.TryGet(((object)message).GetType(), out descriptor)) { return false; } frame = NetMessageCodec.Encode(message, messageSnapshot, compactIds); if (frame.Length < 7 || frame.Length > _options.MaxEncodedFrameBytes) { frame = null; return false; } return true; } private bool QueueFrameLocked(SessionState state, byte[] frame, MessageDescriptor descriptor) { if (CanQueueFrameLocked(state, frame, descriptor) != FrameAdmission.Admitted) { return false; } EnqueueFrameLocked(state, frame, descriptor); return true; } private FrameAdmission CanQueueFrameLocked(SessionState state, byte[] frame, MessageDescriptor descriptor) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Invalid comparison between Unknown and I4 if (frame == null || descriptor == null) { return FrameAdmission.FrameTooLarge; } if (state.Terminal) { return FrameAdmission.PeerCapacity; } if (!state.MessageIdsActivated && frame.Length > _options.MaxHandshakeFrameBytes) { LogWarning("KCP peer " + state.Connection.Id + " rejected a pre-activation frame of " + frame.Length + " bytes; limit is " + _options.MaxHandshakeFrameBytes); return FrameAdmission.FrameTooLarge; } if ((int)descriptor.Reliability == 0) { if (frame.Length > _options.MaxEncodedFrameBytes || frame.Length > _options.MaxReliableBytes) { return FrameAdmission.FrameTooLarge; } if (state.ReliableFrameCount >= _options.MaxReliableFrames || state.ReliableBytes > _options.MaxReliableBytes - frame.Length) { return FrameAdmission.PeerCapacity; } return FrameAdmission.Admitted; } if (frame.Length > _options.MaxEncodedFrameBytes || frame.Length > _options.MaxTransientBytes || frame.Length > state.Session.UnreliableMax - 20 || frame.Length > _options.MaxTransientBytes - 20) { return FrameAdmission.FrameTooLarge; } if (state.TransientFrameCount >= _options.MaxTransientFrames || state.TransientBytes > _options.MaxTransientBytes - frame.Length) { return FrameAdmission.PeerCapacity; } return FrameAdmission.Admitted; } private static void EnqueueFrameLocked(SessionState state, byte[] frame, MessageDescriptor descriptor) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if ((int)descriptor.Reliability == 0) { state.Reliable.Enqueue(new OutboundFrame(frame, 0u)); state.ReliableFrameCount++; state.ReliableBytes += frame.Length; } else { OutboundFrame item = new OutboundFrame(frame, state.NextTransientFrameId); state.NextTransientFrameId = NextFrameId(state.NextTransientFrameId); state.Transient.Enqueue(item); state.TransientFrameCount++; state.TransientBytes += frame.Length; } } private void FlushTransient(SessionState state) { if (!state.Authenticated || state.Terminal) { return; } int num; do { OutboundFrame outboundFrame; lock (_gate) { if (state.Transient.Count == 0) { break; } outboundFrame = state.Transient.Dequeue(); state.TransientFrameCount--; state.TransientBytes -= outboundFrame.Frame.Length; } num = 20 + outboundFrame.Frame.Length; if (num > state.Session.UnreliableMax) { DisconnectForProtocol(state, "unreliable frame exceeds KCP limit"); break; } EnsureScratch(state, num); WriteEnvelope(state.Scratch, 0, 3, outboundFrame.Id, outboundFrame.Frame.Length, 0, outboundFrame.Frame.Length); Buffer.BlockCopy(outboundFrame.Frame, 0, state.Scratch, 20, outboundFrame.Frame.Length); } while (state.Session.SendUnreliable(new ArraySegment(state.Scratch, 0, num))); } private void FlushReliable(SessionState state) { if (!state.Authenticated || state.Terminal) { return; } int fragmentPayloadLimit = state.FragmentPayloadLimit; while (!state.Terminal && state.Session.PendingReliableSegments < _options.MaxKcpQueuedSegments) { if (state.ActiveReliable == null) { lock (_gate) { if (state.Reliable.Count == 0) { break; } state.ActiveReliable = state.Reliable.Dequeue(); state.ActiveReliable.Id = state.NextFrameId; state.NextFrameId = NextFrameId(state.NextFrameId); } } OutboundFrame activeReliable = state.ActiveReliable; int val = activeReliable.Frame.Length - activeReliable.Offset; int num = Math.Min(fragmentPayloadLimit, val); byte kind = (byte)((activeReliable.Frame.Length <= fragmentPayloadLimit && activeReliable.Offset == 0) ? 1 : 2); int num2 = 20 + num; EnsureScratch(state, num2); WriteEnvelope(state.Scratch, 0, kind, activeReliable.Id, activeReliable.Frame.Length, activeReliable.Offset, num); Buffer.BlockCopy(activeReliable.Frame, activeReliable.Offset, state.Scratch, 20, num); if (!state.Session.SendReliable(new ArraySegment(state.Scratch, 0, num2))) { break; } activeReliable.Offset += num; if (activeReliable.Offset == activeReliable.Frame.Length) { state.ActiveReliable = null; lock (_gate) { state.ReliableFrameCount--; state.ReliableBytes -= activeReliable.Frame.Length; } } } } private void OnDatagram(DatagramPeer peer, ArraySegment datagram) { _datagramsThisPump++; SessionState value; lock (_gate) { if (!_states.TryGetValue(peer, out value) || value.Terminal) { return; } } bool flag; using (PerfProbe.ThreadSample("net.dgram")) { flag = value.Session.InputDatagram(datagram); } if (flag) { value.LastReceiveTime = _lastClock; } } private void OnPeerAvailable(DatagramPeer peer) { if (Volatile.Read(in _started) && !Volatile.Read(in _stopRequested) && !Volatile.Read(in _disposed)) { if (IsPumpThread() && _pumping) { CreateSessionOnPump(peer); } else { EnqueuePeerSignal(new PeerSignal(peer, closed: false, null)); } } } private void OnPeerClosed(DatagramPeer peer, string reason) { if (Volatile.Read(in _started) && !Volatile.Read(in _stopRequested) && !Volatile.Read(in _disposed)) { if (IsPumpThread() && _pumping) { CloseSessionOnPump(peer, reason); } else { EnqueuePeerSignal(new PeerSignal(peer, closed: true, reason)); } } } private void EnqueuePeerSignal(PeerSignal signal) { lock (_gate) { if (!Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { return; } if (signal.Closed) { if (!_peerTerminalSignals.Contains(signal.Peer)) { EnsurePeerSignalCapacityLocked("terminal peer signal"); _peerTerminalSignals.Add(signal.Peer); _peerSignals.AddLast(signal); } } else if (!_peerTerminalSignals.Contains(signal.Peer)) { if (_pendingPeerAvailableSignals.TryGetValue(signal.Peer, out var value)) { value.Value = signal; return; } EnsurePeerSignalCapacityLocked("peer signal"); _pendingPeerAvailableSignals.Add(signal.Peer, _peerSignals.AddLast(signal)); } } } private void DrainPeerSignals(ref int budget) { while (budget > 0) { PeerSignal value; lock (_gate) { if (_peerSignals.First == null) { break; } LinkedListNode first = _peerSignals.First; value = first.Value; _peerSignals.RemoveFirst(); if (!value.Closed && _pendingPeerAvailableSignals.TryGetValue(value.Peer, out var value2) && first == value2) { _pendingPeerAvailableSignals.Remove(value.Peer); } } budget--; if (value.Closed) { try { CloseSessionOnPump(value.Peer, value.Reason); } finally { lock (_gate) { _peerTerminalSignals.Remove(value.Peer); } } } else { CreateSessionOnPump(value.Peer); } } } private void EnsurePeerSignalCapacityLocked(string kind) { if (_peerSignals.Count < _options.MaxPendingPeerSignals) { return; } InvalidOperationException ex = new InvalidOperationException("KCP peer-signal queue exceeded its " + _options.MaxPendingPeerSignals + " item limit while admitting " + kind + "; transport cannot safely continue."); LogError(ex.Message); throw ex; } private void ClearPeerSignalsLocked() { _peerSignals.Clear(); _pendingPeerAvailableSignals.Clear(); _peerTerminalSignals.Clear(); } private void EnqueueDisconnectRequest(DisconnectRequest request) { lock (_gate) { if (!Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { request.State.DisconnectRequestQueued = false; return; } if (Volatile.Read(in _disconnectRequestCount) >= _options.MaxPendingDisconnectRequests) { InvalidOperationException ex = new InvalidOperationException("KCP disconnect-request queue exceeded its " + _options.MaxPendingDisconnectRequests + " item limit; transport cannot safely continue."); LogError(ex.Message); throw ex; } _disconnectRequests.Enqueue(request); Interlocked.Increment(ref _disconnectRequestCount); } } private void DrainDisconnectRequests(ref int budget) { DisconnectRequest result; while (budget > 0 && _disconnectRequests.TryDequeue(out result)) { budget--; Interlocked.Decrement(ref _disconnectRequestCount); RequestDisconnectOnPump(result); } } private void RequestDisconnectOnPump(DisconnectRequest request) { lock (_gate) { request.State.DisconnectRequestQueued = false; if (request.State.Terminal) { return; } if (request.State.DisconnectInfo == null) { request.State.DisconnectInfo = request.Info; } request.State.DisconnectRequested = true; } DisconnectOnPump(request.State); } private void CreateSessionOnPump(DatagramPeer peer) { if (!peer.IsValid || !Volatile.Read(in _started) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed)) { return; } bool flag = false; lock (_gate) { if (_states.ContainsKey(peer)) { return; } int num = 0; foreach (SessionState value in _states.Values) { if (!value.Terminal && !value.MessageIdsActivated) { num++; } } flag = num >= _options.MaxPendingHandshakeSessions; } if (flag) { LogWarning("KCP peer " + peer.ToString() + " rejected: pending application handshake limit " + _options.MaxPendingHandshakeSessions + " reached"); if (_datagrams.IsRunning) { _datagrams.Close(peer, "pending application handshake limit reached"); } return; } uint cookie = (_isHost ? NewCookie() : 0u); SessionState state = new SessionState(peer, AllocateConnectionId()) { LastReceiveTime = _lastClock, HandshakeStarted = _lastClock }; state.Session = new KcpSession(_config, _isHost, cookie, delegate(ArraySegment datagram) { RawSend(state, datagram); }, delegate(ArraySegment message, KcpChannel channel) { OnSessionData(state, message, channel); }, delegate { OnSessionAuthenticated(state); }, delegate { OnSessionDisconnected(state); }, delegate(ErrorCode error, string detail) { OnSessionError(state, error, detail); }, _lastClock); state.FragmentPayloadLimit = ComputeFragmentPayloadLimit(state.Session); if (state.FragmentPayloadLimit <= 0) { throw new InvalidOperationException("KCP MTU leaves no room for the private frame envelope"); } state.Scratch = new byte[Math.Max(20 + state.FragmentPayloadLimit, 20 + state.Session.UnreliableMax)]; bool flag2 = false; lock (_gate) { flag2 = _states.ContainsKey(peer) || Volatile.Read(in _stopRequested) || Volatile.Read(in _disposed); if (!flag2) { _states.Add(peer, state); } } if (!flag2) { state.Session.Start(_lastClock); } } private void CloseSessionOnPump(DatagramPeer peer, string reason) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown SessionState value; lock (_gate) { if (!_states.TryGetValue(peer, out value) || value.Terminal) { return; } } if (value.DisconnectInfo == null) { value.DisconnectInfo = new DisconnectInfo(string.IsNullOrWhiteSpace(reason) ? "datagram peer closed" : reason, true, "peer_closed", true, value.Connection.State); } value.DisconnectRequested = true; DisconnectOnPump(value); } private void OnSessionAuthenticated(SessionState state) { if (state.Terminal || !state.Session.IsAuthenticated) { return; } state.Authenticated = true; lock (_gate) { if (state.Terminal) { return; } _connections[state.Connection.Id] = state; } this.PeerConnected?.Invoke(state.Connection); } private void OnSessionData(SessionState state, ArraySegment data, KcpChannel channel) { if (state.Terminal || !state.Authenticated) { return; } if (channel == KcpChannel.Unreliable) { if (!TryReadAndValidateEnvelope(data, 3, out var envelope)) { DisconnectForProtocol(state, "invalid unreliable frame envelope"); return; } int num = (state.MessageIdsActivated ? _options.MaxEncodedFrameBytes : _options.MaxHandshakeFrameBytes); if (envelope.PayloadLength > num) { DisconnectForProtocol(state, "unreliable application frame exceeds its size limit"); } else { DeliverFrame(state, data.Array, data.Offset + 20, envelope.PayloadLength, reliable: false); } return; } if (!TryReadEnvelope(data, out var envelope2)) { DisconnectForProtocol(state, "invalid reliable frame envelope"); return; } if (envelope2.Kind == 1) { int num2 = (state.MessageIdsActivated ? _options.MaxEncodedFrameBytes : _options.MaxHandshakeFrameBytes); if (envelope2.TotalLength != envelope2.PayloadLength || envelope2.Offset != 0 || envelope2.TotalLength > (uint)num2 || !AcceptReliableId(state, envelope2.FrameId)) { DisconnectForProtocol(state, "invalid complete reliable frame"); return; } if (state.Reassembly != null && state.ReassemblyOffset != 0) { DisconnectForProtocol(state, "partial reliable frame delivery"); return; } DeliverFrame(state, data.Array, data.Offset + 20, envelope2.PayloadLength, reliable: true); state.NextInboundFrameId = NextFrameId(envelope2.FrameId); return; } if (envelope2.Kind != 2 || envelope2.PayloadLength == 0 || envelope2.TotalLength <= envelope2.PayloadLength || envelope2.TotalLength > (uint)(state.MessageIdsActivated ? _options.MaxEncodedFrameBytes : _options.MaxHandshakeFrameBytes) || envelope2.Offset > envelope2.TotalLength || envelope2.PayloadLength > envelope2.TotalLength - envelope2.Offset) { DisconnectForProtocol(state, "invalid reliable fragment envelope"); return; } if (envelope2.FrameId != state.NextInboundFrameId) { DisconnectForProtocol(state, "reliable frame order violation"); return; } if (state.Reassembly == null) { if (envelope2.Offset != 0) { DisconnectForProtocol(state, "reliable fragment did not start at zero"); return; } if (!TryReserveReassembly(state, envelope2.TotalLength)) { DisconnectForProtocol(state, "aggregate reliable reassembly budget exceeded"); return; } try { state.Reassembly = new byte[envelope2.TotalLength]; } catch { ReleaseReassembly(state); throw; } state.ReassemblyFrameId = envelope2.FrameId; state.ReassemblyTotal = envelope2.TotalLength; state.ReassemblyOffset = 0u; state.ReassemblyLastReceive = _lastClock; } if (state.ReassemblyFrameId != envelope2.FrameId || state.ReassemblyTotal != envelope2.TotalLength || state.ReassemblyOffset != envelope2.Offset) { DisconnectForProtocol(state, "reliable fragment sequence violation"); return; } Buffer.BlockCopy(data.Array, data.Offset + 20, state.Reassembly, (int)state.ReassemblyOffset, envelope2.PayloadLength); state.ReassemblyOffset += (uint)envelope2.PayloadLength; state.ReassemblyLastReceive = _lastClock; if (state.ReassemblyOffset == state.ReassemblyTotal) { byte[] reassembly = state.Reassembly; uint reassemblyFrameId = state.ReassemblyFrameId; ClearReassembly(state); DeliverFrame(state, reassembly, 0, reassembly.Length, reliable: true); state.NextInboundFrameId = NextFrameId(reassemblyFrameId); } } private void DeliverFrame(SessionState state, byte[] frame, int offset, int count, bool reliable) { _framesThisPump++; if (frame == null || offset < 0 || count < 7 || offset > frame.Length - count) { DisconnectForProtocol(state, "invalid application frame bounds"); return; } int num = (state.MessageIdsActivated ? _options.MaxEncodedFrameBytes : _options.MaxHandshakeFrameBytes); if (count > num) { DisconnectForProtocol(state, "application frame exceeds the pre-activation limit"); return; } bool flag = IsCompactFrame(frame, offset, count); if (!state.MessageIdsActivated && flag) { if (!TryQueuePendingCompact(state, frame, offset, count, reliable)) { if (reliable) { DisconnectForProtocol(state, "pending compact application queue budget exhausted"); } else { LogWarning("KCP transient compact frame dropped while awaiting message-id activation"); } } return; } if (state.MessageIdsActivated && !flag) { DisconnectForProtocol(state, "named application frame received after message-id activation"); return; } if (!TryReserveDecodeBudget(count)) { if (reliable) { DisconnectForProtocol(state, "incoming application queue budget exhausted"); } return; } byte[] array = new byte[count]; Buffer.BlockCopy(frame, offset, array, 0, count); _decodeQueue.Enqueue(new DecodeWork { Frame = array, State = state, Reliable = reliable, CompactIds = state.MessageIdsActivated, BeforeActivation = !state.MessageIdsActivated, Snapshot = _messageSnapshot, FrameLimit = num - 4, EncodedBytes = count }); EnsureDecodeWorker(); _decodeSignal.Release(); } private bool TryQueuePendingCompact(SessionState state, byte[] frame, int offset, int count, bool reliable) { lock (_gate) { if (state.Terminal || state.MessageIdsActivated || state.PendingCompact.Count >= _options.MaxIncomingFrames || _incoming.Count + _pendingActivationFrames + _decodePendingFrames >= _options.MaxIncomingFrames || _incomingBytes + _pendingActivationBytes + _decodePendingBytes > _options.MaxIncomingBytes - count) { return false; } byte[] array = new byte[count]; Buffer.BlockCopy(frame, offset, array, 0, count); state.PendingCompact.Enqueue(new PendingCompactFrame(array, reliable)); _pendingActivationFrames++; _pendingActivationBytes += count; return true; } } private void DrainPendingCompactLocked(SessionState state) { while (!state.Terminal && state.PendingCompact.Count > 0) { PendingCompactFrame pendingCompactFrame = state.PendingCompact.Dequeue(); _pendingActivationFrames--; _pendingActivationBytes -= pendingCompactFrame.Frame.Length; if (_pendingActivationFrames < 0 || _pendingActivationBytes < 0) { LogError("KCP pending compact-frame accounting underflow"); _pendingActivationFrames = Math.Max(0, _pendingActivationFrames); _pendingActivationBytes = Math.Max(0, _pendingActivationBytes); } DeliverFrame(state, pendingCompactFrame.Frame, 0, pendingCompactFrame.Frame.Length, pendingCompactFrame.Reliable); } if (state.Terminal) { ClearPendingCompactLocked(state); } } private static bool IsCompactFrame(byte[] frame, int offset, int count) { if (frame != null && count >= 7 && offset >= 0 && offset <= frame.Length - count) { return frame[offset + 4] == 1; } return false; } private bool TryEnqueueIncoming(InMsg message, int encodedBytes) { lock (_gate) { if (_incoming.Count + _pendingActivationFrames + _decodePendingFrames >= _options.MaxIncomingFrames || encodedBytes < 7 || _incomingBytes + _pendingActivationBytes + _decodePendingBytes > _options.MaxIncomingBytes - encodedBytes) { return false; } _incoming.Enqueue(new IncomingItem(message, encodedBytes)); _incomingBytes += encodedBytes; return true; } } private bool TryReserveDecodeBudget(int encodedBytes) { lock (_gate) { if (_incoming.Count + _pendingActivationFrames + _decodePendingFrames >= _options.MaxIncomingFrames || encodedBytes < 7 || _incomingBytes + _pendingActivationBytes + _decodePendingBytes > _options.MaxIncomingBytes - encodedBytes) { return false; } _decodePendingFrames++; _decodePendingBytes += encodedBytes; return true; } } private void ReleaseDecodeBudget(int encodedBytes) { lock (_gate) { _decodePendingFrames--; _decodePendingBytes -= encodedBytes; if (_decodePendingFrames < 0) { LogError("KCP decode-queue frame accounting underflow"); _decodePendingFrames = 0; } if (_decodePendingBytes < 0) { LogError("KCP decode-queue byte accounting underflow"); _decodePendingBytes = 0; } } } private void EnsureDecodeWorker() { if (_decodeThread == null) { _decodeStop = false; _decodeSignal = new SemaphoreSlim(0); (_decodeThread = new Thread(DecodeWorkerLoop) { IsBackground = true, Name = "CardShopCoop.KcpDecode" }).Start(); } } private void DecodeWorkerLoop() { SemaphoreSlim decodeSignal = _decodeSignal; int managedThreadId = Thread.CurrentThread.ManagedThreadId; while (!Volatile.Read(in _decodeStop)) { decodeSignal.Wait(); DecodeWork result; while (!Volatile.Read(in _decodeStop) && _decodeQueue.TryDequeue(out result)) { _decodeResults.Enqueue(Decode(result, managedThreadId)); } } } private static DecodeResult Decode(DecodeWork work, int workerThreadId) { DecodeResult result = new DecodeResult { State = work.State, Reliable = work.Reliable, BeforeActivation = work.BeforeActivation, Snapshot = work.Snapshot, EncodedBytes = work.EncodedBytes }; try { if (Msg.TryDecodeFrame(work.Frame, 0, work.Frame.Length, work.State.Connection, work.FrameLimit, work.Snapshot, work.CompactIds, out var message)) { result.Message = message; result.Ok = true; } } catch (Exception error) { LogDecodeWorkerError(workerThreadId, error); } return result; } private static void LogDecodeWorkerError(int workerThreadId, Exception error) { try { LogError("KCP decode worker (thread " + workerThreadId + ") faulted: " + error); } catch { } } private void DrainDecodeResults() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Invalid comparison between Unknown and I4 DecodeResult result; while (_decodeResults.TryDequeue(out result)) { ReleaseDecodeBudget(result.EncodedBytes); SessionState state = result.State; if (state == null || state.Terminal) { continue; } if (!result.Ok || result.Message.Message == null) { DisconnectForProtocol(state, "invalid application frame"); continue; } InMsg message = result.Message; message.ReceivedBeforeMessageIdActivation = result.BeforeActivation; if (result.Snapshot == null || !result.Snapshot.TryGet(message.MessageType, out var descriptor) || (result.Reliable ? ((int)descriptor.Reliability > 0) : ((int)descriptor.Reliability != 1))) { DisconnectForProtocol(state, "application delivery does not match descriptor"); continue; } if (message.Message is DisconnectMessage message2) { ApplyRemoteDisconnect(state, message2); } if (!TryEnqueueIncoming(message, result.EncodedBytes) && result.Reliable) { DisconnectForProtocol(state, "incoming application queue budget exhausted"); } } } private void StopDecodeWorker() { Thread decodeThread = _decodeThread; SemaphoreSlim decodeSignal = _decodeSignal; _decodeThread = null; _decodeSignal = null; if (decodeThread != null) { Volatile.Write(ref _decodeStop, value: true); decodeSignal?.Release(); try { if (decodeThread.IsAlive && decodeThread != Thread.CurrentThread) { decodeThread.Join(500); } } catch (Exception ex) { LogError("KCP decode worker join failed: " + ex); } } DecodeWork result; while (_decodeQueue.TryDequeue(out result)) { } DecodeResult result2; while (_decodeResults.TryDequeue(out result2)) { } _decodePendingFrames = 0; _decodePendingBytes = 0; Volatile.Write(ref _decodeStop, value: false); } private void OnSessionDisconnected(SessionState state) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown if (!state.Terminal) { DisconnectInfo disconnectInfo = state.DisconnectInfo; if (disconnectInfo == null) { bool flag = !state.HasError; disconnectInfo = new DisconnectInfo(flag ? "remote disconnected" : (string.IsNullOrWhiteSpace(state.LastErrorDetail) ? "KCP session closed" : ("KCP session closed: " + state.LastErrorDetail)), flag, flag ? "remote_closed" : "kcp_closed", flag, state.Connection.State); state.DisconnectInfo = disconnectInfo; state.PendingDisconnectCompletion = true; state.DisconnectCompletionDeadline = MonotonicNow() + 250; LogInfo("coop: session disconnected for peer " + state.Connection.Id + "; holding completion " + 250 + "ms for a terminal reason frame."); } else { CompleteDisconnect(state, disconnectInfo); } } } private void OnSessionError(SessionState state, ErrorCode error, string detail) { state.HasError = true; state.LastErrorDetail = detail; if (error == ErrorCode.InvalidReceive || error == ErrorCode.Unexpected || error == ErrorCode.Congestion) { LogWarning("KCP peer " + state.Connection.Id + ": " + detail); } } private void DisconnectForProtocol(SessionState state, string reason) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown if (!state.Terminal) { if (state.DisconnectInfo == null) { state.DisconnectInfo = new DisconnectInfo(reason, false, "protocol_error", false, state.Connection.State); } LogWarning("coop: protocol disconnect for peer " + state.Connection.Id + ": " + reason + "."); state.DisconnectRequested = true; DisconnectOnPump(state); } } private void DisconnectOnPump(SessionState state) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (!state.Terminal) { state.DisconnectRequested = false; DisconnectInfo val = (state.DisconnectInfo = (DisconnectInfo)(((object)state.DisconnectInfo) ?? ((object)new DisconnectInfo("connection closed", false, "closed", false, (ConnectionState)4)))); state.Connection.BeginDisconnect(val); if (TrySendTerminalFrame(state, val)) { state.PendingTerminalDisconnect = true; state.TerminalDisconnectDeadline = MonotonicNow() + 1000; LogInfo("coop: holding disconnect for peer " + state.Connection.Id + " until the terminal reason frame is acknowledged."); } else { FinishDisconnectOnPump(state); } } } private void FinishDisconnectOnPump(SessionState state) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) DisconnectInfo info = (DisconnectInfo)(((object)state.DisconnectInfo) ?? ((object)new DisconnectInfo("connection closed", false, "closed", false, (ConnectionState)4))); if (!state.Session.IsDisconnected) { state.Session.Disconnect(); } else { CompleteDisconnect(state, info); } } private bool TrySendTerminalFrame(SessionState state, DisconnectInfo info) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected I4, but got Unknown if (info.Remote || state.Session == null || state.Session.IsDisconnected || !state.Session.IsAuthenticated) { return false; } if (state.ActiveReliable != null || state.Session.PendingReliableSegments != 0) { return false; } if (state.Reliable.Count != 0) { lock (_gate) { state.Reliable.Clear(); state.ReliableFrameCount = 0; state.ReliableBytes = 0; } } DisconnectMessage message = new DisconnectMessage { Reason = info.Reason, Code = info.Code, Retryable = info.Retryable, Phase = (int)info.Phase }; if (!TryEncode((INetMessage)(object)message, state.MessageIdsActivated, out var frame, out var _) || frame.Length > state.Session.ReliableMax - 20) { return false; } int num = 20 + frame.Length; EnsureScratch(state, num); uint nextFrameId = state.NextFrameId; WriteEnvelope(state.Scratch, 0, 1, nextFrameId, frame.Length, 0, frame.Length); Buffer.BlockCopy(frame, 0, state.Scratch, 20, frame.Length); if (!state.Session.SendReliable(new ArraySegment(state.Scratch, 0, num))) { return false; } state.NextFrameId = NextFrameId(nextFrameId); try { state.Session.TickOutgoing(MonotonicNow()); state.Session.FlushOutgoing(); } catch (Exception ex) { LogWarning("KCP terminal disconnect frame could not be flushed for peer " + state.Connection.Id + ": " + ex.Message); } return true; } private void ApplyRemoteDisconnect(SessionState state, DisconnectMessage message) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown ConnectionState val = (ConnectionState)(Enum.IsDefined(typeof(ConnectionState), message.Phase) ? message.Phase : 4); DisconnectInfo val2 = new DisconnectInfo(message.Reason, true, message.Code, message.Retryable, val); LogInfo("coop: remote terminal reason for peer " + state.Connection.Id + ": " + val2.Code + " / " + val2.Reason); if (!state.Connection.RecordRemoteDisconnect(val2)) { string[] obj = new string[5] { "coop: ignoring remote terminal reason for peer ", state.Connection.Id.ToString(), " (already claimed: ", null, null }; DisconnectInfo disconnectReason = state.Connection.DisconnectReason; obj[3] = ((disconnectReason != null) ? disconnectReason.Code : null) ?? "?"; obj[4] = ")."; LogInfo(string.Concat(obj)); } else { state.DisconnectInfo = state.Connection.DisconnectReason ?? val2; if (!state.Session.IsDisconnected) { state.Session.Disconnect(); } else { CompleteDisconnect(state, state.DisconnectInfo); } } } private void CompleteDisconnect(SessionState state, DisconnectInfo info) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown DisconnectInfo val = state.Connection.DisconnectReason ?? info; lock (_gate) { if (state.Terminal) { return; } state.Terminal = true; state.DisconnectInfo = val; _states.Remove(state.DatagramPeer); if (state.Authenticated) { _connections.Remove(state.Connection.Id); } state.Reliable.Clear(); state.Transient.Clear(); state.ActiveReliable = null; ClearReassemblyLocked(state); ClearPendingCompactLocked(state); state.ReliableBytes = 0; state.TransientBytes = 0; } bool authenticated = state.Authenticated; state.Connection.BeginDisconnect(val); state.Connection.TryMarkDisconnected(); Exception ex = null; if (authenticated) { try { EnqueueDisconnectResult(new ConnectionEvent(state.Connection, val)); } catch (InvalidOperationException ex2) { ex = ex2; } } if (_datagrams.IsRunning) { _datagrams.Close(state.DatagramPeer, val.Reason); } if (ex != null) { throw ex; } } private void EnqueueDisconnectResult(ConnectionEvent result) { lock (_gate) { if (_pendingDisconnectResults.Count + Disconnects.Count >= _options.MaxPendingDisconnectResults) { InvalidOperationException ex = new InvalidOperationException("KCP disconnect-result queue exceeded its " + _options.MaxPendingDisconnectResults + " item limit; a terminal disconnect cannot be published safely."); LogError(ex.Message); throw ex; } _pendingDisconnectResults.Enqueue(result); } } private void DrainDisconnectResults(ref int budget) { while (budget > 0) { lock (_gate) { if (_pendingDisconnectResults.Count == 0) { break; } if (Disconnects.Count >= _options.MaxPendingDisconnectResults) { InvalidOperationException ex = new InvalidOperationException("KCP public disconnect-result queue is full; a terminal disconnect cannot be published safely."); LogError(ex.Message); throw ex; } ConnectionEvent item = _pendingDisconnectResults.Dequeue(); Disconnects.Enqueue(item); } budget--; } } private void StopOnPump() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown if (!Volatile.Read(in _started) && !Volatile.Read(in _disposeRequested)) { return; } Volatile.Write(ref _stopRequested, value: true); IProtocolSessionLease protocolSessionLease = null; SessionState[] array; lock (_gate) { array = new SessionState[_states.Count]; _states.Values.CopyTo(array, 0); } try { foreach (SessionState sessionState in array) { if (!sessionState.Terminal) { if (sessionState.DisconnectInfo == null) { sessionState.DisconnectInfo = new DisconnectInfo("transport stopped", false, "stopped", true, sessionState.Connection.State); } DisconnectOnPump(sessionState); } } _datagrams.Stop(); } finally { try { if (_datagrams.IsRunning) { _datagrams.Stop(); } } catch (Exception ex) { LogError("KCP bearer shutdown cleanup failed: " + ex); } lock (_gate) { foreach (SessionState value in _states.Values) { value.Terminal = true; ClearReassemblyLocked(value); ClearPendingCompactLocked(value); } _states.Clear(); _connections.Clear(); _reassemblyBytes = 0; _pendingActivationFrames = 0; _pendingActivationBytes = 0; IncomingItem result; while (_incoming.TryDequeue(out result)) { } _incomingBytes = 0; protocolSessionLease = _sessionLease; _sessionLease = null; _messageSnapshot = null; Volatile.Write(ref _started, value: false); Volatile.Write(ref _stopRequested, value: false); } lock (_gate) { ClearPeerSignalsLocked(); DisconnectRequest result2; while (_disconnectRequests.TryDequeue(out result2)) { } Volatile.Write(ref _disconnectRequestCount, 0); } StopDecodeWorker(); try { protocolSessionLease?.Dispose(); } finally { if (Volatile.Read(in _disposeRequested)) { DisposeBearerResources(); } } } } private void DisposeBearerResources() { lock (_gate) { if (_resourcesDisposed) { return; } _resourcesDisposed = true; IncomingItem result; while (_incoming.TryDequeue(out result)) { } _incomingBytes = 0; _pendingActivationFrames = 0; _pendingActivationBytes = 0; Volatile.Write(ref _disposed, value: true); } StopDecodeWorker(); _datagrams.PeerAvailable -= OnPeerAvailable; _datagrams.PeerClosed -= OnPeerClosed; _datagrams.Dispose(); } private int AllocateConnectionId() { if (!_isHost) { return 1; } lock (_gate) { for (int i = 0; i < int.MaxValue; i++) { int num = _nextHostConnectionId++; if (num <= 0) { _nextHostConnectionId = 2; num = 1; } if (!_connections.ContainsKey(num)) { return num; } } } throw new InvalidOperationException("No connection ids remain"); } private void RawSend(SessionState state, ArraySegment datagram) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown if (state.Terminal || !_datagrams.IsRunning) { return; } try { _datagrams.Send(state.DatagramPeer, datagram); } catch (Exception ex) { string text = "KCP bearer send failed for peer " + state.Connection.Id + ": " + ex.Message; state.HasError = true; state.LastErrorDetail = text; LogError(text); if (state.DisconnectInfo == null) { state.DisconnectInfo = new DisconnectInfo("transport send failed; session recovery required", false, "transport_send_failed", true, state.Connection.State); } state.DisconnectRequested = true; throw; } } private void EnsureScratch(SessionState state, int length) { if (state.Scratch == null || state.Scratch.Length < length) { state.Scratch = new byte[length]; } } private static int ComputeFragmentPayloadLimit(KcpSession session) { int num = session.Mtu - 5 - 24 - 1; return Math.Min(session.ReliableMax - 20, num - 20); } private static uint NextFrameId(uint id) { uint num = id + 1; if (num != 0) { return num; } return 1u; } private static bool AcceptReliableId(SessionState state, uint id) { return id == state.NextInboundFrameId; } private bool TryReserveReassembly(SessionState state, uint totalLength) { if (totalLength == 0 || totalLength > int.MaxValue) { return false; } lock (_gate) { if (state.ReassemblyReservedBytes != 0 || _reassemblyBytes > _options.MaxReassemblyBytes - (int)totalLength) { return false; } state.ReassemblyReservedBytes = (int)totalLength; _reassemblyBytes += (int)totalLength; return true; } } private void ReleaseReassembly(SessionState state) { lock (_gate) { ClearReassemblyLocked(state); } } private void ClearReassembly(SessionState state) { lock (_gate) { ClearReassemblyLocked(state); } } private void ClearReassemblyLocked(SessionState state) { if (state.ReassemblyReservedBytes != 0) { _reassemblyBytes -= state.ReassemblyReservedBytes; if (_reassemblyBytes < 0) { LogError("KCP reassembly accounting underflow for peer " + state.Connection.Id); _reassemblyBytes = 0; } state.ReassemblyReservedBytes = 0; } state.Reassembly = null; state.ReassemblyTotal = 0u; state.ReassemblyOffset = 0u; state.ReassemblyFrameId = 0u; } private void ClearPendingCompactLocked(SessionState state) { while (state.PendingCompact.Count > 0) { PendingCompactFrame pendingCompactFrame = state.PendingCompact.Dequeue(); _pendingActivationFrames--; _pendingActivationBytes -= pendingCompactFrame.Frame.Length; } if (_pendingActivationFrames < 0 || _pendingActivationBytes < 0) { LogError("KCP pending compact-frame accounting underflow while clearing peer"); _pendingActivationFrames = Math.Max(0, _pendingActivationFrames); _pendingActivationBytes = Math.Max(0, _pendingActivationBytes); } } private static void WriteEnvelope(byte[] buffer, int offset, byte kind, uint id, int totalLength, int payloadOffset, int payloadLength) { buffer[offset] = 75; buffer[offset + 1] = 67; buffer[offset + 2] = 1; buffer[offset + 3] = kind; WriteUInt32(buffer, offset + 4, id); WriteUInt32(buffer, offset + 8, (uint)totalLength); WriteUInt32(buffer, offset + 12, (uint)payloadOffset); WriteUInt32(buffer, offset + 16, (uint)payloadLength); } private static bool TryReadEnvelope(ArraySegment data, out Envelope envelope) { envelope = default(Envelope); if (data.Array == null || data.Count < 20) { return false; } int offset = data.Offset; byte[] array = data.Array; if (array[offset] != 75 || array[offset + 1] != 67 || array[offset + 2] != 1) { return false; } uint num = ReadUInt32(array, offset + 16); if (num > int.MaxValue || num != data.Count - 20) { return false; } envelope = new Envelope { Kind = array[offset + 3], FrameId = ReadUInt32(array, offset + 4), TotalLength = ReadUInt32(array, offset + 8), Offset = ReadUInt32(array, offset + 12), PayloadLength = (int)num }; if (envelope.FrameId != 0) { return envelope.TotalLength != 0; } return false; } private static bool TryReadAndValidateEnvelope(ArraySegment data, byte kind, out Envelope envelope) { if (!TryReadEnvelope(data, out envelope) || envelope.Kind != kind || envelope.Offset != 0 || envelope.TotalLength != envelope.PayloadLength) { envelope = default(Envelope); return false; } return true; } private static uint ReadUInt32(byte[] buffer, int offset) { return (uint)(buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)); } private static void WriteUInt32(byte[] buffer, int offset, uint value) { buffer[offset] = (byte)value; buffer[offset + 1] = (byte)(value >> 8); buffer[offset + 2] = (byte)(value >> 16); buffer[offset + 3] = (byte)(value >> 24); } private static uint NewCookie() { byte[] array = new byte[4]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } uint num = BitConverter.ToUInt32(array, 0); if (num != 0) { return num; } return 1u; } private uint ClockNow() { return (uint)(_clockStopwatch.Elapsed.Ticks / 10000); } private uint MonotonicNow() { lock (_clockGate) { uint num = _clock(); if (_hasClock) { uint num2 = _lastClock - num; if (num2 != 0 && num2 < 2147483648u) { num = _lastClock; } } _lastClock = num; _hasClock = true; return num; } } private bool IsPumpThread() { if (_pumpThreadId != 0) { return _pumpThreadId == Thread.CurrentThread.ManagedThreadId; } return false; } private void EnsureNotDisposed() { if (Volatile.Read(in _disposed) || Volatile.Read(in _disposeRequested)) { throw new ObjectDisposedException("KcpSessionManager"); } } private static KcpConfig ValidateConfig(KcpConfig config, int maxDatagramSize) { if (maxDatagramSize <= 0) { throw new ArgumentOutOfRangeException("maxDatagramSize"); } if (config.RecvBufferSize <= 0 || config.SendBufferSize <= 0 || config.Mtu < 55 || config.Interval == 0 || config.FastResend < 0 || config.SendWindowSize == 0 || config.ReceiveWindowSize < 2 || config.Timeout <= 0 || config.MaxRetransmits == 0) { throw new ArgumentOutOfRangeException("config", "KCP configuration contains an invalid buffer, MTU, window, interval, or timeout"); } if (maxDatagramSize < config.Mtu) { throw new ArgumentException("The datagram bearer is smaller than the KCP MTU", "maxDatagramSize"); } return config; } private static KcpConfig CopyConfig(KcpConfig config) { return new KcpConfig { DualMode = config.DualMode, RecvBufferSize = config.RecvBufferSize, SendBufferSize = config.SendBufferSize, Mtu = config.Mtu, NoDelay = config.NoDelay, Interval = config.Interval, FastResend = config.FastResend, CongestionWindow = config.CongestionWindow, SendWindowSize = config.SendWindowSize, ReceiveWindowSize = config.ReceiveWindowSize, Timeout = config.Timeout, MaxRetransmits = config.MaxRetransmits }; } private static void LogWarning(string message) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)message); } } private static void LogInfo(string message) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)message); } } private static void LogError(string message) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)message); } } private void FailApplicationSend(PeerConnection connection, INetMessage message, SessionState state, string reason) { string text = DescribeSendFailure(connection, message, reason); LogError(text); if (state != null) { DisconnectAfterFailedSend(connection, text); } throw new InvalidOperationException(text); } private void FailPeerAdmission(PeerConnection connection, INetMessage message, SessionState state, string reason) { string text = DescribeSendFailure(connection, message, reason); LogError(text); if (state != null) { DisconnectAfterFailedSend(connection, text); } } private static string DescribeSendFailure(PeerConnection connection, INetMessage message, string reason) { return "KCP application send failed for peer " + (((connection != null) ? connection.Id.ToString() : null) ?? "") + " message " + (((object)message)?.GetType().FullName ?? "") + ": " + reason; } private void DisconnectAfterFailedSend(PeerConnection connection, string text) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown try { GracefulDisconnect(connection, new DisconnectInfo("application send failed; session recovery required", false, "send_failed", true, connection.State)); } catch (Exception innerException) { throw new InvalidOperationException(text + "; disconnect failed", innerException); } } } public sealed class KcpSessionManagerOptions { public int MaxDatagramsPerPump = 256; public int MaxPendingPeerSignals = 256; public int MaxPeerSignalsPerPump = 64; public int MaxPendingDisconnectRequests = 256; public int MaxDisconnectRequestsPerPump = 64; public int MaxPendingDisconnectResults = 256; public int MaxDisconnectResultsPerPump = 64; public int MaxReliableFrames = 2048; public int MaxReliableBytes = 33554432; public int MaxTransientFrames = 256; public int MaxTransientBytes = 8388608; public int MaxIncomingFrames = 10000; public int MaxIncomingBytes = 33554432; public int MaxKcpQueuedSegments = 256; public int MaxEncodedFrameBytes = 16777216; public int MaxPendingHandshakeSessions = 8; public int MaxHandshakeFrameBytes = 16777216; public int MaxReassemblyBytes = 33554432; public uint PendingHandshakeTimeoutMs = 60000u; public uint ReassemblyTimeoutMs = 30000u; public Func Clock; internal KcpSessionManagerOptions Copy() { return new KcpSessionManagerOptions { MaxDatagramsPerPump = MaxDatagramsPerPump, MaxPendingPeerSignals = MaxPendingPeerSignals, MaxPeerSignalsPerPump = MaxPeerSignalsPerPump, MaxPendingDisconnectRequests = MaxPendingDisconnectRequests, MaxDisconnectRequestsPerPump = MaxDisconnectRequestsPerPump, MaxPendingDisconnectResults = MaxPendingDisconnectResults, MaxDisconnectResultsPerPump = MaxDisconnectResultsPerPump, MaxReliableFrames = MaxReliableFrames, MaxReliableBytes = MaxReliableBytes, MaxTransientFrames = MaxTransientFrames, MaxTransientBytes = MaxTransientBytes, MaxIncomingFrames = MaxIncomingFrames, MaxIncomingBytes = MaxIncomingBytes, MaxKcpQueuedSegments = MaxKcpQueuedSegments, MaxEncodedFrameBytes = MaxEncodedFrameBytes, MaxPendingHandshakeSessions = MaxPendingHandshakeSessions, MaxHandshakeFrameBytes = MaxHandshakeFrameBytes, MaxReassemblyBytes = MaxReassemblyBytes, PendingHandshakeTimeoutMs = PendingHandshakeTimeoutMs, ReassemblyTimeoutMs = ReassemblyTimeoutMs, Clock = Clock }; } internal void Validate() { if (MaxDatagramsPerPump <= 0 || MaxPendingPeerSignals <= 0 || MaxPeerSignalsPerPump <= 0 || MaxPendingDisconnectRequests <= 0 || MaxDisconnectRequestsPerPump <= 0 || MaxPendingDisconnectResults <= 0 || MaxDisconnectResultsPerPump <= 0 || MaxReliableFrames <= 0 || MaxReliableBytes <= 0 || MaxTransientFrames <= 0 || MaxTransientBytes <= 0 || MaxIncomingFrames <= 0 || MaxIncomingBytes < 7 || MaxKcpQueuedSegments <= 0 || MaxEncodedFrameBytes < 7 || MaxEncodedFrameBytes > 67108868 || MaxPendingHandshakeSessions <= 0 || MaxPendingHandshakeSessions > 1024 || MaxHandshakeFrameBytes < 7 || MaxHandshakeFrameBytes > MaxEncodedFrameBytes || MaxReassemblyBytes < MaxHandshakeFrameBytes || MaxReassemblyBytes < MaxEncodedFrameBytes || PendingHandshakeTimeoutMs == 0 || ReassemblyTimeoutMs == 0) { throw new ArgumentOutOfRangeException("KcpSessionManagerOptions", "KCP manager limits must be positive and within the protocol frame ceiling"); } } } } namespace CardShopCoop.Net.Datagrams { public readonly struct DatagramPeer : IEquatable { internal readonly long Token; public bool IsValid => Token > 0; internal DatagramPeer(long token) { if (token <= 0) { throw new ArgumentOutOfRangeException("token"); } Token = token; } public bool Equals(DatagramPeer other) { return Token == other.Token; } public override bool Equals(object obj) { if (obj is DatagramPeer other) { return Equals(other); } return false; } public override int GetHashCode() { long token = Token; return token.GetHashCode(); } public static bool operator ==(DatagramPeer left, DatagramPeer right) { return left.Equals(right); } public static bool operator !=(DatagramPeer left, DatagramPeer right) { return !left.Equals(right); } } public interface IDatagramTransport : IDisposable { int MaxDatagramSize { get; } bool IsRunning { get; } event Action PeerAvailable; event Action PeerClosed; void Start(); int Poll(int maxDatagrams, Action> receive); void Send(DatagramPeer peer, ArraySegment datagram); void Close(DatagramPeer peer, string reason); void Stop(); } public sealed class SteamDatagramTransport : IDatagramTransport, IDisposable { private sealed class PeerState { public readonly HSteamNetConnection Connection; public readonly ulong SteamId; public readonly DatagramPeer Peer; public bool Available; public bool Published; public PeerState(HSteamNetConnection connection, ulong steamId, DatagramPeer peer) { //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) Connection = connection; SteamId = steamId; Peer = peer; } } private enum PendingChangeKind { Status, Available, Closed } private readonly struct PendingChange { public readonly PendingChangeKind Kind; public readonly long Generation; public readonly HSteamNetConnection Connection; public readonly SteamNetworkingIdentity RemoteIdentity; public readonly ESteamNetworkingConnectionState State; public readonly int EndReason; public readonly string EndDebug; public readonly DatagramPeer Peer; public readonly string Reason; public readonly bool Authorized; private PendingChange(PendingChangeKind kind, long generation, HSteamNetConnection connection, SteamNetworkingIdentity remoteIdentity, ESteamNetworkingConnectionState state, int endReason, string endDebug, DatagramPeer peer, string reason, bool authorized) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Kind = kind; Generation = generation; Connection = connection; RemoteIdentity = remoteIdentity; State = state; EndReason = endReason; EndDebug = endDebug; Peer = peer; Reason = reason; Authorized = authorized; } public static PendingChange Status(long generation, HSteamNetConnection connection, SteamNetworkingIdentity remoteIdentity, ESteamNetworkingConnectionState state, int endReason, string endDebug, bool authorized) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) return new PendingChange(PendingChangeKind.Status, generation, connection, remoteIdentity, state, endReason, endDebug, default(DatagramPeer), null, authorized); } public static PendingChange Closed(long generation, DatagramPeer peer, string reason) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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) return new PendingChange(PendingChangeKind.Closed, generation, HSteamNetConnection.Invalid, default(SteamNetworkingIdentity), (ESteamNetworkingConnectionState)0, 0, null, peer, reason, authorized: true); } public static PendingChange Available(long generation, DatagramPeer peer) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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) return new PendingChange(PendingChangeKind.Available, generation, HSteamNetConnection.Invalid, default(SteamNetworkingIdentity), (ESteamNetworkingConnectionState)0, 0, null, peer, null, authorized: true); } } public const int DefaultMaxDatagramSize = 1200; public const int DefaultMaxPendingStatusChanges = 256; public const int DefaultMaxPendingLifecycleChanges = 512; public const int DefaultMaxPendingChangesPerPoll = 64; private const int DefaultReceiveSlotCount = 64; private const int DefaultVirtualPort = 0; private readonly object _lifecycleGate = new object(); private readonly object _nativeGate = new object(); private readonly object _sendGate = new object(); private readonly object _notificationGate = new object(); private readonly object _pollGate = new object(); private readonly bool _isHost; private readonly Func _hostAuthorization; private readonly ulong _expectedHostSteamId; private readonly int _virtualPort; private readonly int _maxDatagramSize; private readonly int _maxPendingStatusChanges; private readonly int _maxPendingLifecycleChanges; private readonly int _maxPendingChangesPerPoll; private readonly byte[] _receiveBuffer; private readonly IntPtr[] _receiveSlots; private readonly byte[] _sendBuffer; private GCHandle _sendBufferHandle; private IntPtr _sendBufferPointer; private readonly Queue _pendingLifecycleChanges = new Queue(); private readonly Dictionary> _pendingStatusChanges = new Dictionary>(); private readonly LinkedList _pendingStatusOrder = new LinkedList(); private readonly HashSet _terminalStatusQueued = new HashSet(); private readonly Dictionary _peersByConnection = new Dictionary(); private readonly Dictionary _peersByToken = new Dictionary(); private readonly Dictionary _peersBySteamId = new Dictionary(); private readonly HashSet _acceptedConnections = new HashSet(); private HSteamListenSocket _listenSocket = HSteamListenSocket.Invalid; private HSteamNetConnection _clientConnection = HSteamNetConnection.Invalid; private HSteamNetPollGroup _pollGroup = HSteamNetPollGroup.Invalid; private Callback _statusCallback; private long _nextPeerToken = 1L; private long _generation; private int _pendingClosedNotifications; private bool _running; private bool _disposed; private bool _dispatchingNotification; private bool _polling; public int MaxDatagramSize => _maxDatagramSize; public bool IsRunning { get { lock (_lifecycleGate) { return _running; } } } internal int PendingStatusChangeCount { get { lock (_lifecycleGate) { return _pendingStatusChanges.Count; } } } internal int PendingLifecycleChangeCount { get { lock (_lifecycleGate) { return _pendingLifecycleChanges.Count; } } } public event Action PeerAvailable; public event Action PeerClosed; public SteamDatagramTransport(bool isHost, Func hostAuthorization, ulong expectedHostSteamId = 0uL, int maxDatagramSize = 1200, int virtualPort = 0, int maxPendingStatusChanges = 256, int maxPendingLifecycleChanges = 512, int maxPendingChangesPerPoll = 64) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (maxDatagramSize <= 0) { throw new ArgumentOutOfRangeException("maxDatagramSize"); } if (virtualPort < 0) { throw new ArgumentOutOfRangeException("virtualPort"); } if (maxPendingStatusChanges <= 0) { throw new ArgumentOutOfRangeException("maxPendingStatusChanges"); } if (maxPendingLifecycleChanges <= 0) { throw new ArgumentOutOfRangeException("maxPendingLifecycleChanges"); } if (maxPendingChangesPerPoll <= 0) { throw new ArgumentOutOfRangeException("maxPendingChangesPerPoll"); } if (isHost && hostAuthorization == null) { throw new ArgumentNullException("hostAuthorization", "A host must authorize lobby members before accepting P2P connections."); } if (!isHost && expectedHostSteamId == 0L) { throw new ArgumentOutOfRangeException("expectedHostSteamId", "A client must be given the expected host SteamID64."); } if (isHost && expectedHostSteamId != 0L) { throw new ArgumentException("A host cannot have an expected client host identity.", "expectedHostSteamId"); } _isHost = isHost; _hostAuthorization = hostAuthorization; _expectedHostSteamId = expectedHostSteamId; _virtualPort = virtualPort; _maxDatagramSize = maxDatagramSize; _maxPendingStatusChanges = maxPendingStatusChanges; _maxPendingLifecycleChanges = maxPendingLifecycleChanges; _maxPendingChangesPerPoll = maxPendingChangesPerPoll; _receiveBuffer = new byte[maxDatagramSize]; _receiveSlots = new IntPtr[64]; _sendBuffer = new byte[maxDatagramSize]; _sendBufferHandle = GCHandle.Alloc(_sendBuffer, GCHandleType.Pinned); _sendBufferPointer = _sendBufferHandle.AddrOfPinnedObject(); } public static SteamDatagramTransport CreateHost(Func hostAuthorization, int maxDatagramSize = 1200, int virtualPort = 0, int maxPendingStatusChanges = 256, int maxPendingLifecycleChanges = 512, int maxPendingChangesPerPoll = 64) { return new SteamDatagramTransport(isHost: true, hostAuthorization, 0uL, maxDatagramSize, virtualPort, maxPendingStatusChanges, maxPendingLifecycleChanges, maxPendingChangesPerPoll); } public static SteamDatagramTransport CreateClient(ulong expectedHostSteamId, int maxDatagramSize = 1200, int virtualPort = 0, int maxPendingStatusChanges = 256, int maxPendingLifecycleChanges = 512, int maxPendingChangesPerPoll = 64) { return new SteamDatagramTransport(isHost: false, null, expectedHostSteamId, maxDatagramSize, virtualPort, maxPendingStatusChanges, maxPendingLifecycleChanges, maxPendingChangesPerPoll); } public void Start() { //IL_00b5: 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) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: 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_00fd: Unknown result type (might be due to invalid IL or missing references) Callback val = null; ExceptionDispatchInfo exceptionDispatchInfo = null; lock (_pollGate) { lock (_notificationGate) { lock (_lifecycleGate) { ThrowIfDisposedLocked(); if (_running) { throw new InvalidOperationException("Steam datagram transport is already running."); } if (_pendingClosedNotifications != 0) { throw new InvalidOperationException("Poll must deliver all peer closures before the transport can restart."); } long generation = NextGenerationLocked(); DrainPendingChangesLocked(); _statusCallback = Callback.Create((DispatchDelegate)delegate(SteamNetConnectionStatusChangedCallback_t change) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) OnConnectionStatusChanged(change, generation); }); try { lock (_nativeGate) { _pollGroup = SteamNetworkingSockets.CreatePollGroup(); if (_pollGroup == HSteamNetPollGroup.Invalid) { throw new InvalidOperationException("SteamNetworkingSockets.CreatePollGroup failed."); } if (_isHost) { _listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(_virtualPort, 0, (SteamNetworkingConfigValue_t[])null); if (_listenSocket == HSteamListenSocket.Invalid) { throw new InvalidOperationException("SteamNetworkingSockets.CreateListenSocketP2P failed."); } } else { SteamNetworkingIdentity val2 = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref val2)).SetSteamID64(_expectedHostSteamId); _clientConnection = SteamNetworkingSockets.ConnectP2P(ref val2, _virtualPort, 0, (SteamNetworkingConfigValue_t[])null); if (_clientConnection == HSteamNetConnection.Invalid) { throw new InvalidOperationException("SteamNetworkingSockets.ConnectP2P failed."); } } } _running = true; } catch (Exception source) { _running = false; val = _statusCallback; _statusCallback = null; lock (_nativeGate) { CloseNativeResourcesLocked("transport start failed"); } exceptionDispatchInfo = ExceptionDispatchInfo.Capture(source); } } } } val?.Dispose(); exceptionDispatchInfo?.Throw(); } public int Poll(int maxDatagrams, Action> receive) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (maxDatagrams < 0) { throw new ArgumentOutOfRangeException("maxDatagrams"); } if (receive == null) { throw new ArgumentNullException("receive"); } lock (_pollGate) { if (_polling) { throw new InvalidOperationException("Steam datagram transport cannot be polled reentrantly."); } _polling = true; try { lock (_lifecycleGate) { ThrowIfDisposedLocked(); } int budget = _maxPendingChangesPerPoll; ProcessPendingChanges(ref budget); if (maxDatagrams == 0) { return 0; } long generation; int num2; lock (_lifecycleGate) { if (!_running) { return 0; } generation = _generation; lock (_nativeGate) { int num = Math.Min(maxDatagrams, _receiveSlots.Length); num2 = SteamNetworkingSockets.ReceiveMessagesOnPollGroup(_pollGroup, _receiveSlots, num); if (num2 <= 0) { return 0; } num2 = Math.Min(num2, num); } } int result = ProcessReceivedMessages(generation, num2, receive); ProcessPendingChanges(ref budget); return result; } finally { _polling = false; } } } public unsafe void Send(DatagramPeer peer, ArraySegment datagram) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Invalid comparison between Unknown and I4 if (!IsValidDatagram(datagram) || datagram.Count > _maxDatagramSize) { throw new ArgumentException("Steam datagram is invalid or exceeds the configured limit", "datagram"); } lock (_lifecycleGate) { if (_disposed || !_running || !_peersByToken.TryGetValue(peer.Token, out var value) || !value.Available) { long token = peer.Token; throw new InvalidOperationException("Steam datagram send has no live peer " + token); } lock (_nativeGate) { lock (_sendGate) { if (datagram.Count != 0) { Buffer.BlockCopy(datagram.Array, datagram.Offset, _sendBuffer, 0, datagram.Count); } long num = 0L; EResult val = SteamNetworkingSockets.SendMessageToConnection(value.Connection, _sendBufferPointer, (uint)datagram.Count, 1, ref num); if ((int)val != 1) { long token = peer.Token; InvalidOperationException ex = new InvalidOperationException("Steam datagram send failed for peer " + token + ": " + ((object)(*(EResult*)(&val))/*cast due to .constrained prefix*/).ToString()); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)ex.Message); } throw ex; } } } } } public void Close(DatagramPeer peer, string reason) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) reason = (string.IsNullOrEmpty(reason) ? "closed by transport" : reason); lock (_lifecycleGate) { ThrowIfDisposedLocked(); if (!_running || !_peersByToken.TryGetValue(peer.Token, out var value) || !value.Available) { return; } try { ClosePeerLocked(value, _generation, reason); } catch { RemovePeerLocked(value); lock (_nativeGate) { SteamNetworkingSockets.CloseConnection(value.Connection, 0, reason, false); } throw; } lock (_nativeGate) { SteamNetworkingSockets.CloseConnection(value.Connection, 0, reason, false); } } } public void Stop() { lock (_pollGate) { lock (_notificationGate) { Callback val = null; lock (_lifecycleGate) { if (_disposed) { return; } if (_running) { val = StopLocked("transport stopped"); } } val?.Dispose(); if (!_dispatchingNotification) { DrainPendingChangesToCompletion(); } } } } public void Dispose() { lock (_pollGate) { lock (_notificationGate) { Callback val = null; lock (_lifecycleGate) { if (_disposed) { return; } if (_running) { val = StopLocked("transport disposed"); } _disposed = true; lock (_sendGate) { if (_sendBufferHandle.IsAllocated) { _sendBufferHandle.Free(); _sendBufferPointer = IntPtr.Zero; } } } val?.Dispose(); if (!_dispatchingNotification) { DrainPendingChangesToCompletion(); } } } } private unsafe void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t change, long callbackGeneration) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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_001b: Invalid comparison between Unknown and I4 //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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_0024: Invalid comparison between Unknown and I4 //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) SteamNetConnectionInfo_t info = change.m_info; bool flag = true; if (_isHost && ((int)info.m_eState == 1 || (int)info.m_eState == 3)) { flag = AuthorizeHostConnection(info.m_identityRemote); if (!flag && TryGetSteamId(info.m_identityRemote, out var steamId)) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)("[steam] host authorization not yet confirmed for state " + ((object)(*(ESteamNetworkingConnectionState*)(&info.m_eState))/*cast due to .constrained prefix*/).ToString() + ", id " + steamId + " (re-checked at Connected)")); } } } lock (_lifecycleGate) { if (_disposed || !_running || callbackGeneration != _generation) { return; } try { QueueStatusChangeLocked(PendingChange.Status(callbackGeneration, change.m_hConn, info.m_identityRemote, info.m_eState, info.m_eEndReason, ((SteamNetConnectionInfo_t)(ref info)).m_szEndDebug, flag)); } catch { CloseConnectionLocked(change.m_hConn, "Steam status queue overflow"); throw; } } } private bool AuthorizeHostConnection(SteamNetworkingIdentity identity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!TryGetSteamId(identity, out var steamId)) { return true; } if (_hostAuthorization == null) { return true; } try { return _hostAuthorization(steamId); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)("Steam host authorization failed for an incoming connection: " + ex)); } return false; } } private void ProcessPendingChanges(ref int budget) { PendingChange change; while (budget > 0 && TryTakePendingChange(out change)) { budget--; if (change.Kind == PendingChangeKind.Closed) { ProcessClosedNotification(change); } else if (change.Kind == PendingChangeKind.Available) { ProcessAvailableNotification(change); } else { ProcessStatusChange(change); } } } private bool TryTakePendingChange(out PendingChange change) { //IL_0091: 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_004b: Unknown result type (might be due to invalid IL or missing references) lock (_lifecycleGate) { if (_pendingLifecycleChanges.Count != 0) { change = _pendingLifecycleChanges.Dequeue(); if (change.Kind == PendingChangeKind.Status && IsTerminalState(change.State)) { _terminalStatusQueued.Remove(change.Connection); } return true; } if (_pendingStatusOrder.First != null) { LinkedListNode first = _pendingStatusOrder.First; _pendingStatusOrder.RemoveFirst(); change = first.Value; _pendingStatusChanges.Remove(change.Connection); return true; } } change = default(PendingChange); return false; } private void QueueStatusChangeLocked(PendingChange change) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (IsTerminalState(change.State)) { if (!_terminalStatusQueued.Contains(change.Connection)) { if (_pendingLifecycleChanges.Count >= _maxPendingLifecycleChanges) { throw PendingChangeOverflow("terminal Steam connection status"); } if (_pendingStatusChanges.TryGetValue(change.Connection, out var value)) { _pendingStatusOrder.Remove(value); _pendingStatusChanges.Remove(change.Connection); } _terminalStatusQueued.Add(change.Connection); _pendingLifecycleChanges.Enqueue(change); } } else { if (_terminalStatusQueued.Contains(change.Connection)) { return; } if (_pendingStatusChanges.TryGetValue(change.Connection, out var value2)) { value2.Value = change; return; } if (_pendingStatusChanges.Count >= _maxPendingStatusChanges) { throw PendingChangeOverflow("replaceable Steam connection status"); } LinkedListNode value3 = _pendingStatusOrder.AddLast(change); _pendingStatusChanges.Add(change.Connection, value3); } } private static InvalidOperationException PendingChangeOverflow(string kind) { InvalidOperationException ex = new InvalidOperationException("Steam datagram pending " + kind + " queue overflowed; transport cannot safely continue."); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogError((object)ex.Message); } return ex; } private void ProcessAvailableNotification(PendingChange change) { lock (_notificationGate) { Action action; lock (_lifecycleGate) { if (_disposed || !_running || change.Generation != _generation || !_peersByToken.TryGetValue(change.Peer.Token, out var value) || !value.Available || value.Published) { return; } value.Published = true; action = this.PeerAvailable; } _dispatchingNotification = true; try { action?.Invoke(change.Peer); } finally { _dispatchingNotification = false; } } } private void ProcessClosedNotification(PendingChange change) { lock (_notificationGate) { Action action; lock (_lifecycleGate) { if (_pendingClosedNotifications <= 0) { throw new InvalidOperationException("Steam datagram close notification accounting was corrupted."); } _pendingClosedNotifications--; action = this.PeerClosed; } _dispatchingNotification = true; try { action?.Invoke(change.Peer, change.Reason); } finally { _dispatchingNotification = false; } } } private void ProcessStatusChange(PendingChange change) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 //IL_0063: 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_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) lock (_lifecycleGate) { if (_disposed || !_running || change.Generation != _generation) { return; } } if ((int)change.State == 1) { ProcessConnecting(change); } else if ((int)change.State == 3) { ProcessConnected(change); } else { if (!IsTerminalState(change.State)) { return; } lock (_lifecycleGate) { if (!_disposed && _running && change.Generation == _generation) { if (_peersByConnection.TryGetValue(change.Connection, out var value)) { ClosePeerLocked(value, change.Generation, GetCloseReason(change)); } _acceptedConnections.Remove(change.Connection); if (_clientConnection == change.Connection) { _clientConnection = HSteamNetConnection.Invalid; } } } } } private void ProcessConnecting(PendingChange change) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Invalid comparison between Unknown and I4 //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) if (!TryGetSteamId(change.RemoteIdentity, out var steamId)) { RejectConnection(change.Generation, change.Connection, "remote Steam identity is invalid"); return; } if (!_isHost) { lock (_lifecycleGate) { if (!_disposed && _running && change.Generation == _generation && (steamId != _expectedHostSteamId || change.Connection != _clientConnection)) { CloseConnectionLocked(change.Connection, "remote Steam identity is not the expected host connection"); } return; } } lock (_lifecycleGate) { if (!_disposed && _running && change.Generation == _generation && !_acceptedConnections.Contains(change.Connection)) { EResult val; lock (_nativeGate) { val = SteamNetworkingSockets.AcceptConnection(change.Connection); } if ((int)val == 1) { _acceptedConnections.Add(change.Connection); } else { CloseConnectionLocked(change.Connection, "Steam rejected connection acceptance"); } } } } private void ProcessConnected(PendingChange change) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) if (!TryGetSteamId(change.RemoteIdentity, out var steamId)) { RejectConnection(change.Generation, change.Connection, "remote Steam identity is invalid"); return; } lock (_lifecycleGate) { if (_disposed || !_running || change.Generation != _generation) { return; } if (!_isHost && (steamId != _expectedHostSteamId || change.Connection != _clientConnection)) { CloseConnectionLocked(change.Connection, "connected peer is not the expected host"); } else if (_isHost && !change.Authorized) { _acceptedConnections.Remove(change.Connection); CloseConnectionLocked(change.Connection, "remote Steam identity is not a lobby member"); } else if (_isHost && !_acceptedConnections.Contains(change.Connection)) { CloseConnectionLocked(change.Connection, "connection was not accepted"); } else { if (_peersByConnection.ContainsKey(change.Connection)) { return; } if (_peersBySteamId.ContainsKey(steamId)) { CloseConnectionLocked(change.Connection, "Steam identity already has a connection"); return; } bool flag; lock (_nativeGate) { flag = SteamNetworkingSockets.SetConnectionPollGroup(change.Connection, _pollGroup); } if (!flag) { CloseConnectionLocked(change.Connection, "failed to assign Steam poll group"); return; } if (_nextPeerToken != long.MaxValue) { DatagramPeer peer = new DatagramPeer(_nextPeerToken++); PeerState peerState = new PeerState(change.Connection, steamId, peer); _peersByConnection.Add(change.Connection, peerState); _peersByToken.Add(peer.Token, peerState); _peersBySteamId.Add(steamId, peerState); _acceptedConnections.Remove(change.Connection); peerState.Available = true; try { QueueLifecycleChangeLocked(PendingChange.Available(_generation, peer), "available peer notification"); return; } catch { RemovePeerLocked(peerState); CloseConnectionLocked(change.Connection, "available peer notification overflow"); throw; } } CloseConnectionLocked(change.Connection, "peer token space exhausted"); } } } private void RejectConnection(long generation, HSteamNetConnection connection, string reason) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) lock (_lifecycleGate) { if (!_disposed && _running && generation == _generation) { CloseConnectionLocked(connection, reason); } } } private int ProcessReceivedMessages(long generation, int received, Action> receive) { //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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) int num = 0; int i = 0; try { for (; i < received; i++) { IntPtr intPtr = _receiveSlots[i]; if (intPtr == IntPtr.Zero) { continue; } try { SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(intPtr); if (val.m_cbSize < 0 || val.m_cbSize > _maxDatagramSize) { CloseMalformedConnection(generation, val.m_conn, "Steam datagram exceeds MaxDatagramSize"); continue; } if (val.m_cbSize != 0 && val.m_pData == IntPtr.Zero) { CloseMalformedConnection(generation, val.m_conn, "Steam datagram has no native payload"); continue; } PeerState value; lock (_lifecycleGate) { if (!_running || generation != _generation || !_peersByConnection.TryGetValue(val.m_conn, out value) || !value.Available) { continue; } goto IL_00df; } IL_0152: receive(value.Peer, new ArraySegment(_receiveBuffer, 0, val.m_cbSize)); num++; goto end_IL_0023; IL_00df: if (val.m_cbSize != 0) { Marshal.Copy(val.m_pData, _receiveBuffer, 0, val.m_cbSize); } lock (_lifecycleGate) { if (!_running || generation != _generation || !_peersByConnection.TryGetValue(val.m_conn, out value) || !value.Available) { continue; } goto IL_0152; } end_IL_0023:; } finally { ReleaseNativeMessage(intPtr); _receiveSlots[i] = IntPtr.Zero; } } return num; } catch { for (int j = i + 1; j < received; j++) { IntPtr intPtr2 = _receiveSlots[j]; if (intPtr2 != IntPtr.Zero) { ReleaseNativeMessage(intPtr2); _receiveSlots[j] = IntPtr.Zero; } } throw; } } private void ReleaseNativeMessage(IntPtr nativeMessage) { if (nativeMessage == IntPtr.Zero) { return; } lock (_nativeGate) { SteamNetworkingMessage_t.Release(nativeMessage); } } private void CloseMalformedConnection(long generation, HSteamNetConnection connection, string reason) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) lock (_lifecycleGate) { if (_disposed || !_running || generation != _generation) { return; } if (_peersByConnection.TryGetValue(connection, out var value)) { try { ClosePeerLocked(value, _generation, reason); } catch { RemovePeerLocked(value); CloseConnectionLocked(connection, reason); throw; } } CloseConnectionLocked(connection, reason); } } private void CloseNativeResourcesLocked(string reason) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) foreach (PeerState value in _peersByConnection.Values) { CloseConnectionLocked(value.Connection, reason); } foreach (HSteamNetConnection acceptedConnection in _acceptedConnections) { if (!_peersByConnection.ContainsKey(acceptedConnection)) { CloseConnectionLocked(acceptedConnection, reason); } } if (_clientConnection != HSteamNetConnection.Invalid && !_peersByConnection.ContainsKey(_clientConnection)) { CloseConnectionLocked(_clientConnection, reason); } _clientConnection = HSteamNetConnection.Invalid; _acceptedConnections.Clear(); if (_listenSocket != HSteamListenSocket.Invalid) { SteamNetworkingSockets.CloseListenSocket(_listenSocket); _listenSocket = HSteamListenSocket.Invalid; } if (_pollGroup != HSteamNetPollGroup.Invalid) { SteamNetworkingSockets.DestroyPollGroup(_pollGroup); _pollGroup = HSteamNetPollGroup.Invalid; } } private Callback StopLocked(string reason) { List list = new List(_peersByConnection.Values); int num = 0; for (int i = 0; i < list.Count; i++) { if (list[i].Available && list[i].Published) { num++; } } if (_pendingLifecycleChanges.Count + num > _maxPendingLifecycleChanges) { throw PendingChangeOverflow("transport-stop peer closures"); } long generation = NextGenerationLocked(); _running = false; Callback statusCallback = _statusCallback; _statusCallback = null; foreach (PeerState item in list) { if (item.Available) { bool published = item.Published; item.Available = false; if (published) { QueueClosedLocked(generation, item.Peer, reason); } } } lock (_nativeGate) { CloseNativeResourcesLocked(reason); } _peersByConnection.Clear(); _peersByToken.Clear(); _peersBySteamId.Clear(); _acceptedConnections.Clear(); return statusCallback; } private void CloseConnectionLocked(HSteamNetConnection connection, string reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (connection != HSteamNetConnection.Invalid) { lock (_nativeGate) { SteamNetworkingSockets.CloseConnection(connection, 0, reason, false); } } } private void RemovePeerLocked(PeerState state) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) _peersByConnection.Remove(state.Connection); _peersByToken.Remove(state.Peer.Token); _peersBySteamId.Remove(state.SteamId); _acceptedConnections.Remove(state.Connection); state.Available = false; } private void ClosePeerLocked(PeerState state, long generation, string reason) { if (state.Available) { if (state.Published) { QueueClosedLocked(generation, state.Peer, reason); } RemovePeerLocked(state); } } private void QueueClosedLocked(long generation, DatagramPeer peer, string reason) { QueueLifecycleChangeLocked(PendingChange.Closed(generation, peer, reason), "terminal peer notification"); _pendingClosedNotifications++; } private void QueueLifecycleChangeLocked(PendingChange change, string kind) { if (_pendingLifecycleChanges.Count >= _maxPendingLifecycleChanges) { throw PendingChangeOverflow(kind); } _pendingLifecycleChanges.Enqueue(change); } private long NextGenerationLocked() { _generation++; return _generation; } private void DrainPendingChangesLocked() { _pendingLifecycleChanges.Clear(); _pendingStatusChanges.Clear(); _pendingStatusOrder.Clear(); _terminalStatusQueued.Clear(); } private void DrainPendingChangesToCompletion() { int budget = int.MaxValue; ProcessPendingChanges(ref budget); } private void ThrowIfDisposedLocked() { if (_disposed) { throw new ObjectDisposedException("SteamDatagramTransport"); } } private static bool TryGetSteamId(SteamNetworkingIdentity identity, out ulong steamId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)identity.m_eType != 16) { steamId = 0uL; return false; } steamId = ((SteamNetworkingIdentity)(ref identity)).GetSteamID64(); return steamId != 0; } private static bool IsTerminalState(ESteamNetworkingConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 if ((int)state != 4 && (int)state != 5) { return (int)state == -3; } return true; } private static string GetCloseReason(PendingChange change) { //IL_0025: 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 (!string.IsNullOrEmpty(change.EndDebug)) { return change.EndDebug; } string[] obj = new string[5] { "Steam connection closed (", ((object)change.State/*cast due to .constrained prefix*/).ToString(), ", reason ", null, null }; int endReason = change.EndReason; obj[3] = endReason.ToString(); obj[4] = ")"; return string.Concat(obj); } private static bool IsValidDatagram(ArraySegment datagram) { if (datagram.Count < 0 || datagram.Offset < 0) { return false; } if (datagram.Array == null) { if (datagram.Count == 0) { return datagram.Offset == 0; } return false; } return datagram.Offset <= datagram.Array.Length - datagram.Count; } } public sealed class UdpDatagramTransport : IDatagramTransport, IDisposable { private sealed class PeerState { internal readonly DatagramPeer Handle; internal readonly IPEndPoint Endpoint; internal string CloseReason; internal PeerState(DatagramPeer handle, IPEndPoint endpoint) { Handle = handle; Endpoint = endpoint; } } private readonly struct ReceivedDatagram { internal readonly IPEndPoint Endpoint; internal readonly byte[] Bytes; internal ReceivedDatagram(IPEndPoint endpoint, byte[] bytes) { Endpoint = endpoint; Bytes = bytes; } } public const int DefaultMaxDatagramSize = 1200; private const int MaximumDatagramSize = 65507; private const long WindowsUdpConnResetIoControlCode = 2550136844L; private readonly object _gate = new object(); private readonly bool _isHost; private readonly IPEndPoint _bindEndpoint; private readonly IPEndPoint _remoteEndpoint; private readonly int _maxDatagramSize; private const int ReceivePollMicroseconds = 20000; private const int MaxQueuedDatagrams = 8192; private const int MaxQueuedBytes = 8388608; private readonly ConcurrentQueue _received = new ConcurrentQueue(); private readonly byte[] _workerBuffer; private Thread _receiveThread; private bool _receiveStop; private int _receivedBytes; private readonly Dictionary _peersByEndpoint = new Dictionary(); private readonly Dictionary _peersByHandle = new Dictionary(); private readonly List _pendingClosed = new List(); private Socket _socket; private bool _running; private bool _disposed; private long _nextToken = 1L; private int _pendingClosedIndex; public int MaxDatagramSize => _maxDatagramSize; public bool IsRunning { get { lock (_gate) { return _running; } } } public event Action PeerAvailable; public event Action PeerClosed; public UdpDatagramTransport(IPEndPoint bindEndpoint, int maxDatagramSize = 1200) { ValidateMaxDatagramSize(maxDatagramSize); _isHost = true; _bindEndpoint = CloneAndValidateEndpoint(bindEndpoint, "bindEndpoint", allowPortZero: true); _maxDatagramSize = maxDatagramSize; _workerBuffer = new byte[maxDatagramSize + 1]; } public UdpDatagramTransport(IPEndPoint localBindEndpoint, IPEndPoint remoteEndpoint, int maxDatagramSize = 1200) { ValidateMaxDatagramSize(maxDatagramSize); _isHost = false; _remoteEndpoint = CloneAndValidateEndpoint(remoteEndpoint, "remoteEndpoint", allowPortZero: false); _bindEndpoint = CloneAndValidateLocalEndpoint(localBindEndpoint, _remoteEndpoint.AddressFamily); _maxDatagramSize = maxDatagramSize; _workerBuffer = new byte[maxDatagramSize + 1]; } public static UdpDatagramTransport CreateHost(IPEndPoint bindEndpoint, int maxDatagramSize = 1200) { return new UdpDatagramTransport(bindEndpoint, maxDatagramSize); } public static UdpDatagramTransport CreateHost(int port, int maxDatagramSize = 1200) { return CreateHost(new IPEndPoint(IPAddress.Any, port), maxDatagramSize); } public static UdpDatagramTransport CreateClient(IPEndPoint remoteEndpoint, int maxDatagramSize = 1200) { return new UdpDatagramTransport(null, remoteEndpoint, maxDatagramSize); } public static UdpDatagramTransport CreateClient(IPEndPoint remoteEndpoint, IPEndPoint localBindEndpoint, int maxDatagramSize = 1200) { return new UdpDatagramTransport(localBindEndpoint, remoteEndpoint, maxDatagramSize); } public void Start() { DrainPendingClosed(); PeerState peerState = null; lock (_gate) { ThrowIfDisposedLocked(); if (_running) { return; } Socket socket = CreateSocket(_bindEndpoint.AddressFamily); try { socket.Bind(_bindEndpoint); } catch { CloseSocket(socket); throw; } _socket = socket; _running = true; if (!_isHost) { peerState = AddPeerLocked(_remoteEndpoint); } } if (peerState != null) { RaisePeerAvailable(peerState); } StartReceiveThread(); } public int Poll(int maxDatagrams, Action> receive) { if (maxDatagrams < 0) { throw new ArgumentOutOfRangeException("maxDatagrams"); } if (receive == null) { throw new ArgumentNullException("receive"); } DrainPendingClosed(); if (maxDatagrams == 0) { return 0; } int num = 0; ReceivedDatagram result; while (num < maxDatagrams && _received.TryDequeue(out result)) { Interlocked.Add(ref _receivedBytes, -result.Bytes.Length); bool flag = false; Socket socket; PeerState value; lock (_gate) { if (!_running || _socket == null) { break; } socket = _socket; if (_isHost) { if (!_peersByEndpoint.TryGetValue(result.Endpoint, out value)) { value = AddPeerLocked(result.Endpoint); flag = true; } } else if (!_remoteEndpoint.Equals(result.Endpoint) || !_peersByEndpoint.TryGetValue(_remoteEndpoint, out value)) { continue; } goto IL_00f5; } IL_00f5: if (flag) { RaisePeerAvailable(value); } lock (_gate) { if (!_running || socket != _socket || !_peersByHandle.ContainsKey(value.Handle)) { continue; } goto IL_014b; } IL_014b: receive(value.Handle, new ArraySegment(result.Bytes)); num++; } DrainPendingClosed(); return num; } public void Send(DatagramPeer peer, ArraySegment datagram) { if (!IsValidSegment(datagram) || datagram.Count > _maxDatagramSize) { throw new ArgumentException("UDP datagram is invalid or exceeds the configured limit", "datagram"); } PeerState value; Socket socket; lock (_gate) { if (!_running || _socket == null || !_peersByHandle.TryGetValue(peer, out value)) { throw new InvalidOperationException("UDP datagram send has no live peer " + peer); } socket = _socket; } try { int num = socket.SendTo(datagram.Array, datagram.Offset, datagram.Count, SocketFlags.None, value.Endpoint); if (num != datagram.Count) { string text = "UDP send wrote " + num + " of " + datagram.Count + " bytes to peer " + peer; FailSocket(socket, text); throw new InvalidOperationException(text); } } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.WouldBlock || ex.SocketErrorCode == SocketError.IOPending || ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable) { string text2 = SocketFailureReason("send", ex); FailSocket(socket, text2); throw new InvalidOperationException(text2, ex); } string text3 = SocketFailureReason("send", ex); FailSocket(socket, text3); throw new InvalidOperationException(text3, ex); } catch (ObjectDisposedException innerException) { throw new InvalidOperationException("UDP socket was disposed during send", innerException); } } public void Close(DatagramPeer peer, string reason) { lock (_gate) { if (_peersByHandle.TryGetValue(peer, out var value)) { RemovePeerLocked(value, NormalizeReason(reason, "peer closed")); } } DrainPendingClosed(); } public void Stop() { Socket socket; lock (_gate) { socket = _socket; _socket = null; _running = false; foreach (PeerState item in new List(_peersByHandle.Values)) { RemovePeerLocked(item, "transport stopped"); } } Volatile.Write(ref _receiveStop, value: true); CloseSocket(socket); StopReceiveThread(); DrainPendingClosed(); } public void Dispose() { Stop(); lock (_gate) { _disposed = true; } } private Socket CreateSocket(AddressFamily family) { Socket socket = new Socket(family, SocketType.Dgram, ProtocolType.Udp); try { DisableUdpConnReset(socket, family); socket.Blocking = false; if (family == AddressFamily.InterNetworkV6) { socket.DualMode = false; } return socket; } catch { CloseSocket(socket); throw; } } private static void DisableUdpConnReset(Socket socket, AddressFamily family) { if (Environment.OSVersion.Platform != PlatformID.Win32NT) { return; } try { socket.IOControl((IOControlCode)2550136844L, new byte[1], null); } catch (PlatformNotSupportedException error) { LogUnsupportedUdpConnReset(family, error); } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationNotSupported || ex.SocketErrorCode == SocketError.ProtocolOption) { LogUnsupportedUdpConnReset(family, ex); } } private static void LogUnsupportedUdpConnReset(AddressFamily family, Exception error) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("UDP SIO_UDP_CONNRESET is unavailable for " + family.ToString() + "; continuing without it: " + error.Message)); } } private PeerState AddPeerLocked(IPEndPoint endpoint) { IPEndPoint iPEndPoint = CloneAndValidateEndpoint(endpoint, "endpoint", allowPortZero: false); if (_nextToken <= 0 || _nextToken == long.MaxValue) { throw new InvalidOperationException("UDP peer handle space is exhausted"); } PeerState peerState = new PeerState(new DatagramPeer(_nextToken++), iPEndPoint); _peersByEndpoint.Add(iPEndPoint, peerState); _peersByHandle.Add(peerState.Handle, peerState); return peerState; } private void RemovePeerLocked(PeerState peer, string reason) { if (_peersByHandle.Remove(peer.Handle)) { _peersByEndpoint.Remove(peer.Endpoint); peer.CloseReason = NormalizeReason(reason, "peer closed"); _pendingClosed.Add(peer); } } private void RaisePeerAvailable(PeerState peer) { Action action; lock (_gate) { if (!_peersByHandle.TryGetValue(peer.Handle, out var value) || value != peer) { return; } action = this.PeerAvailable; } action?.Invoke(peer.Handle); } private void RaisePeerClosed(PeerState peer) { this.PeerClosed?.Invoke(peer.Handle, peer.CloseReason); } private void DrainPendingClosed() { while (true) { PeerState peer; lock (_gate) { if (_pendingClosedIndex >= _pendingClosed.Count) { _pendingClosed.Clear(); _pendingClosedIndex = 0; break; } peer = _pendingClosed[_pendingClosedIndex++]; } RaisePeerClosed(peer); } } private void StartReceiveThread() { _receiveStop = false; ReceivedDatagram result; while (_received.TryDequeue(out result)) { } _receivedBytes = 0; (_receiveThread = new Thread(ReceiveLoop) { IsBackground = true, Name = "CardShopCoop.UdpReceive" }).Start(); } private void StopReceiveThread() { Thread receiveThread = _receiveThread; _receiveThread = null; Volatile.Write(ref _receiveStop, value: true); if (receiveThread != null && receiveThread.IsAlive && receiveThread != Thread.CurrentThread) { try { receiveThread.Join(250); } catch (ThreadStateException) { } } ReceivedDatagram result; while (_received.TryDequeue(out result)) { } _receivedBytes = 0; } private void ReceiveLoop() { AddressFamily addressFamily = _bindEndpoint.AddressFamily; while (!Volatile.Read(in _receiveStop)) { Socket socket; lock (_gate) { if (!_running || _socket == null) { break; } socket = _socket; } try { if (!socket.Poll(20000, SelectMode.SelectRead)) { continue; } } catch (SocketException) { if (Volatile.Read(in _receiveStop)) { break; } continue; } catch (ObjectDisposedException) { break; } EndPoint remoteEP = CreateReceiveEndpoint(addressFamily); int num; try { num = socket.ReceiveFrom(_workerBuffer, 0, _workerBuffer.Length, SocketFlags.None, ref remoteEP); } catch (SocketException ex3) { if (ex3.SocketErrorCode == SocketError.WouldBlock || ex3.SocketErrorCode == SocketError.IOPending || ex3.SocketErrorCode == SocketError.MessageSize) { continue; } if (!Volatile.Read(in _receiveStop)) { FailSocket(socket, SocketFailureReason("receive", ex3)); } break; } catch (ObjectDisposedException) { break; } if (num >= 0 && num <= _maxDatagramSize && remoteEP is IPEndPoint endpoint && IsUsableRemoteEndpoint(endpoint, addressFamily)) { byte[] array = new byte[num]; Buffer.BlockCopy(_workerBuffer, 0, array, 0, num); TryQueueReceived(endpoint, array); } } } private bool TryQueueReceived(IPEndPoint endpoint, byte[] bytes) { if (_received.Count >= 8192 || Volatile.Read(in _receivedBytes) + bytes.Length > 8388608) { return false; } _received.Enqueue(new ReceivedDatagram(endpoint, bytes)); Interlocked.Add(ref _receivedBytes, bytes.Length); return true; } private void FailSocket(Socket failedSocket, string reason) { lock (_gate) { if (!_running || _socket != failedSocket) { return; } _socket = null; _running = false; foreach (PeerState item in new List(_peersByHandle.Values)) { RemovePeerLocked(item, reason); } } CloseSocket(failedSocket); } private void ThrowIfDisposedLocked() { if (_disposed) { throw new ObjectDisposedException(GetType().FullName); } } private static bool IsValidSegment(ArraySegment segment) { if (segment.Array != null && segment.Offset >= 0 && segment.Count >= 0) { return segment.Offset <= segment.Array.Length - segment.Count; } return false; } private static bool IsUsableRemoteEndpoint(IPEndPoint endpoint, AddressFamily family) { if (endpoint != null && endpoint.AddressFamily == family && !endpoint.Address.IsIPv4MappedToIPv6 && endpoint.Port > 0) { return endpoint.Port <= 65535; } return false; } private static EndPoint CreateReceiveEndpoint(AddressFamily family) { if (family != AddressFamily.InterNetwork) { return new IPEndPoint(IPAddress.IPv6Any, 0); } return new IPEndPoint(IPAddress.Any, 0); } private static IPEndPoint CloneAndValidateEndpoint(IPEndPoint endpoint, string parameterName, bool allowPortZero) { if (endpoint == null) { throw new ArgumentNullException(parameterName); } if (endpoint.AddressFamily != AddressFamily.InterNetwork && endpoint.AddressFamily != AddressFamily.InterNetworkV6) { throw new ArgumentException("UDP requires an IPv4 or IPv6 endpoint", parameterName); } if (endpoint.Address.IsIPv4MappedToIPv6) { throw new ArgumentException("IPv4-mapped IPv6 endpoints are not supported", parameterName); } if (endpoint.Port < 0 || endpoint.Port > 65535 || (!allowPortZero && endpoint.Port == 0)) { throw new ArgumentOutOfRangeException(parameterName, "The UDP port is outside the configured range"); } return new IPEndPoint(endpoint.Address, endpoint.Port); } private static IPEndPoint CloneAndValidateLocalEndpoint(IPEndPoint endpoint, AddressFamily requiredFamily) { if (endpoint == null) { if (requiredFamily != AddressFamily.InterNetwork) { return new IPEndPoint(IPAddress.IPv6Any, 0); } return new IPEndPoint(IPAddress.Any, 0); } IPEndPoint iPEndPoint = CloneAndValidateEndpoint(endpoint, "endpoint", allowPortZero: true); if (iPEndPoint.AddressFamily != requiredFamily) { throw new ArgumentException("The local and remote UDP endpoints must use the same address family", "endpoint"); } return iPEndPoint; } private static void ValidateMaxDatagramSize(int maxDatagramSize) { if (maxDatagramSize <= 0 || maxDatagramSize > 65507) { throw new ArgumentOutOfRangeException("maxDatagramSize", "MaxDatagramSize must be between 1 and 65507 bytes"); } } private static string NormalizeReason(string reason, string fallback) { if (!string.IsNullOrEmpty(reason)) { return reason; } return fallback; } private static string SocketFailureReason(string operation, SocketException error) { return "UDP " + operation + " failed (" + error.SocketErrorCode.ToString() + "): " + error.Message; } private static void CloseSocket(Socket socket) { if (socket == null) { return; } try { socket.Close(); } catch (ObjectDisposedException) { } } } } namespace CardShopCoop.Net.Connection { public interface IConnectionProvider : IDisposable { ICoopTransport Transport { get; } event Action TransportChanged; event Action PeerConnected; event Action Connected; event Action Failed; void Host(int port, bool isPublic = false, string lobbyName = "", string password = "", int maxPlayers = 0); void Join(string address, int port, string password = ""); } public sealed class LanConnectionProvider : IConnectionProvider, IDisposable { private KcpSessionManager _manager; public ICoopTransport Transport { get; private set; } public event Action Connected; public event Action TransportChanged; public event Action PeerConnected; public event Action Failed; public void Host(int port, bool isPublic = false, string lobbyName = "", string password = "", int maxPlayers = 0) { try { EnsureUnused(); UdpDatagramTransport datagrams = UdpDatagramTransport.CreateHost(port); PublishAndStart(datagrams, isHost: true); } catch (Exception ex) { this.Failed?.Invoke(ex.Message); throw; } } public void Join(string address, int port, string password = "") { try { EnsureUnused(); UdpDatagramTransport datagrams = UdpDatagramTransport.CreateClient(ResolveEndpoint(address, port)); PublishAndStart(datagrams, isHost: false); } catch (Exception ex) { this.Failed?.Invoke(ex.Message); throw; } } private void EnsureUnused() { if (Transport != null || _manager != null) { throw new InvalidOperationException("LAN provider already has an active transport."); } } public void Dispose() { KcpSessionManager manager = _manager; if (manager == null && Transport == null) { return; } if (manager != null) { manager.PeerConnected -= OnPeerConnected; } try { Transport?.Dispose(); } finally { _manager = null; Transport = null; this.TransportChanged?.Invoke(); } } private void OnPeerConnected(PeerConnection connection) { this.PeerConnected?.Invoke(connection); } private void PublishAndStart(UdpDatagramTransport datagrams, bool isHost) { KcpSessionManager kcpSessionManager = null; NetworkPump networkPump = null; bool flag = false; try { kcpSessionManager = new KcpSessionManager(datagrams, isHost); kcpSessionManager.PeerConnected += OnPeerConnected; networkPump = (NetworkPump)NetworkPump.Wrap(LagTransport.Wrap(kcpSessionManager)); _manager = kcpSessionManager; Transport = networkPump; this.TransportChanged?.Invoke(); flag = true; networkPump.Start(); if (!isHost) { this.Connected?.Invoke(); } } catch { if (kcpSessionManager != null) { kcpSessionManager.PeerConnected -= OnPeerConnected; try { if (networkPump != null) { networkPump.Dispose(); } else { kcpSessionManager.Dispose(); } } finally { datagrams.Dispose(); } } else { datagrams.Dispose(); } if (_manager == kcpSessionManager) { _manager = null; Transport = null; } if (flag) { this.TransportChanged?.Invoke(); } throw; } } private static IPEndPoint ResolveEndpoint(string address, int port) { if (string.IsNullOrWhiteSpace(address)) { throw new ArgumentException("LAN address must not be empty.", "address"); } if (port <= 0 || port > 65535) { throw new ArgumentOutOfRangeException("port", "The LAN port is outside the valid range."); } string text = address.Trim(); IPAddress address2; IPAddress[] array = ((!IPAddress.TryParse(text, out address2)) ? Dns.GetHostAddresses(text) : new IPAddress[1] { address2 }); IPAddress[] array2 = array; foreach (IPAddress iPAddress in array2) { if (iPAddress.AddressFamily == AddressFamily.InterNetwork) { return new IPEndPoint(iPAddress, port); } } throw new ArgumentException("LAN address must be an IPv4 address or resolve to one.", "address"); } } public sealed class SteamConnectionProvider : IConnectionProvider, IDisposable { private readonly ISteamBridge _steam; private long _joinOperation; private ICoopTransport _joinTransport; public ICoopTransport Transport { get; private set; } public event Action Connected; public event Action TransportChanged; public event Action PeerConnected; public event Action Failed; public SteamConnectionProvider(ISteamBridge steam) { _steam = steam ?? throw new ArgumentNullException("steam"); _steam.OnConnectedToHost = delegate(ICoopTransport transport) { if (transport == Transport) { this.Connected?.Invoke(); } }; _steam.OnJoinFailed = delegate(ulong lobby, long operation, ICoopTransport transport, string error) { if (operation == _joinOperation && transport == _joinTransport) { this.Failed?.Invoke(error); } }; _steam.OnSessionFailed = delegate(ICoopTransport transport, string error) { if (transport == Transport) { this.Failed?.Invoke(error); } }; } public void Host(int port, bool isPublic = false, string lobbyName = "", string password = "", int maxPlayers = 0) { EnsureUnused(); Transport = NetworkPump.Wrap(LagTransport.Wrap(_steam.CreateTransport(isHost: true))); Transport.PeerConnected += OnPeerConnected; this.TransportChanged?.Invoke(); StartKcpTransport(Transport); _steam.Host(isPublic, lobbyName, !string.IsNullOrEmpty(password), maxPlayers); } public void Join(string address, int port, string password = "") { EnsureUnused(); if (!ulong.TryParse(address, out var result)) { throw new ArgumentException("Steam lobby id must be numeric.", "address"); } Transport = NetworkPump.Wrap(LagTransport.Wrap(_steam.CreateTransport(isHost: false))); Transport.PeerConnected += OnPeerConnected; this.TransportChanged?.Invoke(); StartKcpTransport(Transport); _joinTransport = Transport; _joinOperation = _steam.Join(result); } private void EnsureUnused() { if (Transport != null) { throw new InvalidOperationException("Steam provider already has an active transport."); } } public void Dispose() { ICoopTransport transport = Transport; Transport = null; _joinTransport = null; _joinOperation = 0L; _steam.Leave(); if (transport != null) { transport.PeerConnected -= OnPeerConnected; } transport?.Dispose(); this.TransportChanged?.Invoke(); } private void OnPeerConnected(PeerConnection connection) { this.PeerConnected?.Invoke(connection); } private static void StartKcpTransport(ICoopTransport transport) { if (transport is ICoopStartable coopStartable) { coopStartable.Start(); return; } throw new InvalidOperationException("Steam bridge returned a transport that cannot be started."); } } } namespace CardShopCoop.Modules.World { internal sealed class WorldTransferManifest { private readonly Dictionary _boxSlots = new Dictionary(); private readonly Dictionary _placementSlots = new Dictionary(); internal int BoxSlotCount => _boxSlots.Count; internal int PlacementSlotCount => _placementSlots.Count; internal void Reset() { _boxSlots.Clear(); _placementSlots.Clear(); } internal void SetBoxSlot(Guid boxId, int slot) { if (boxId != Guid.Empty) { _boxSlots[boxId] = slot; } } internal bool TryGetBoxSlot(Guid boxId, out int slot) { return _boxSlots.TryGetValue(boxId, out slot); } internal void SetPlacementSlot(int kind, ushort placementId, int slot) { if (placementId != 0) { _placementSlots[(kind << 24) | placementId] = slot; } } internal bool TryGetPlacementSlot(int kind, ushort placementId, out int slot) { return _placementSlots.TryGetValue((kind << 24) | placementId, out slot); } } internal sealed class BoxNetworkInteraction { private sealed class HostDeliveryEntry { internal int Remaining; internal readonly Queue Ids = new Queue(); } internal sealed class DeliveryQueueState { internal int[] Remaining; internal Guid[][] Ids; } internal sealed class ClientDestroyCapture { internal Guid Id; internal BoxCreatedMessage Descriptor; } private const int MaxItemCount = 4096; private static readonly FieldInfo CompartmentItemsField = AccessTools.Field(typeof(ShelfCompartment), "m_StoredItemList"); private static readonly FieldInfo ItemAmountToSpawnField = AccessTools.Field(typeof(InteractablePackagingBox_Item), "m_ItemAmountToSpawn"); private static readonly FieldInfo BoxOpenedField = AccessTools.Field(typeof(InteractablePackagingBox_Item), "m_IsBoxOpened"); private readonly bool _host; private readonly Action _broadcast; private readonly Dictionary _boxesById = new Dictionary(); private readonly Dictionary _idsByBox = new Dictionary(); private readonly Dictionary _pendingFurniture = new Dictionary(); private Guid _pendingHostCreatedId; private readonly List _hostDeliveryEntries = new List(); private readonly List _stagedHostDeliveryIds = new List(); private readonly Queue _pendingClientDeliveryIds = new Queue(); private Guid _pendingClientCreatedId; private readonly HashSet _clientOwnedIds = new HashSet(); private readonly Dictionary _pendingClientDescriptors = new Dictionary(); private readonly Dictionary _pendingLocalMoves = new Dictionary(); private int _deliverySpawns; private readonly Dictionary> _sceneSlots = new Dictionary>(); private readonly List _baselineSaveBoxes = new List(); private bool _sceneSlotsCaptured; private Transform _cardSpawnAnchor; private int _materializing; private int _applyingRemote; private int _suppressDestroyForward; internal Guid HostPredictionId; private static readonly FieldInfo OutOfBoundsTimer = AccessTools.Field(typeof(RestockManager), "m_OutofBoundCheckTimer"); private static readonly FieldInfo PendingRestockRows = AccessTools.Field(typeof(RestockManager), "m_SpawnBoxItemWaitingList"); private static readonly FieldInfo BoxedFurniture = AccessTools.Field(typeof(InteractablePackagingBox_Shelf), "m_BoxedObject"); private static readonly FieldInfo BeingHoldField = AccessTools.Field(typeof(InteractableObject), "m_IsBeingHold"); internal bool IsMaterializing => _materializing > 0; internal BoxNetworkInteraction(bool host, Action broadcast, Action send) { _host = host; _broadcast = broadcast ?? throw new ArgumentNullException("broadcast"); if (send == null) { throw new ArgumentNullException("send"); } if (!_host) { PlacementApi.StructureChanged += RetryPendingFurniture; } } internal bool IsKnownBox(InteractablePackagingBox box) { if ((Object)(object)box != (Object)null) { return _idsByBox.ContainsKey(box); } return false; } internal void SuppressOutOfBoundsSweep(RestockManager manager) { if (!_host && (Object)(object)manager != (Object)null) { OutOfBoundsTimer?.SetValue(manager, 0f); } } internal void Reset() { _boxesById.Clear(); _idsByBox.Clear(); _pendingFurniture.Clear(); _pendingHostCreatedId = Guid.Empty; ClearHostDeliveryEntries(); _pendingClientDeliveryIds.Clear(); _clientOwnedIds.Clear(); _pendingClientDescriptors.Clear(); _pendingLocalMoves.Clear(); _deliverySpawns = 0; _sceneSlots.Clear(); _baselineSaveBoxes.Clear(); _sceneSlotsCaptured = false; _materializing = 0; _applyingRemote = 0; _suppressDestroyForward = 0; HostPredictionId = Guid.Empty; if ((Object)(object)_cardSpawnAnchor != (Object)null) { Object.Destroy((Object)(object)((Component)_cardSpawnAnchor).gameObject); _cardSpawnAnchor = null; } } internal void Dispose() { if (!_host) { PlacementApi.StructureChanged -= RetryPendingFurniture; } Reset(); } internal void ClientBeginBaseline() { if (!_host) { _boxesById.Clear(); _idsByBox.Clear(); _pendingFurniture.Clear(); _pendingClientDeliveryIds.Clear(); _pendingClientCreatedId = Guid.Empty; _clientOwnedIds.Clear(); _pendingClientDescriptors.Clear(); _pendingLocalMoves.Clear(); _deliverySpawns = 0; CaptureSceneSlots(); } } internal void ClientInvalidateSceneSlots() { if (!_host) { _sceneSlots.Clear(); _baselineSaveBoxes.Clear(); _sceneSlotsCaptured = false; } } private void CaptureSceneSlots() { _sceneSlots.Clear(); _sceneSlots[1] = SnapshotBoxes((IList)RestockManager.GetItemPackagingBoxList(), PlacementInterop.IsRecordWarehouse); _sceneSlots[2] = SnapshotBoxes((IList)RestockManager.GetCardPackagingBoxList(), skipStored: false); _sceneSlotsCaptured = true; _baselineSaveBoxes.Clear(); foreach (List value in _sceneSlots.Values) { _baselineSaveBoxes.AddRange(value); } } private static List SnapshotBoxes(IList boxes, bool skipStored) where T : InteractablePackagingBox { List list = new List(); for (int i = 0; boxes != null && i < boxes.Count; i++) { T val = boxes[i]; if ((Object)(object)val == (Object)null) { continue; } if (skipStored) { object obj = val; InteractablePackagingBox_Item val2 = (InteractablePackagingBox_Item)((obj is InteractablePackagingBox_Item) ? obj : null); if (val2 != null && val2.m_IsStored) { continue; } } list.Add((InteractablePackagingBox)(object)val); } return list; } internal void CaptureTransferManifest(WorldTransferManifest manifest) { if (_host && manifest != null) { manifest.Reset(); CaptureTransferKindSlots((IList)RestockManager.GetItemPackagingBoxList(), PlacementInterop.IsRecordWarehouse, manifest); CaptureTransferKindSlots((IList)RestockManager.GetCardPackagingBoxList(), skipStored: false, manifest); } } private void CaptureTransferKindSlots(IList boxes, bool skipStored, WorldTransferManifest manifest) where T : InteractablePackagingBox { int num = 0; for (int i = 0; boxes != null && i < boxes.Count; i++) { T val = boxes[i]; if ((Object)(object)val == (Object)null) { continue; } if (skipStored) { object obj = val; InteractablePackagingBox_Item val2 = (InteractablePackagingBox_Item)((obj is InteractablePackagingBox_Item) ? obj : null); if (val2 != null && val2.m_IsStored) { continue; } } if (!TryGetId((InteractablePackagingBox)(object)val, out var id)) { id = EnsureHostId((InteractablePackagingBox)(object)val); } manifest.SetBoxSlot(id, num); num++; } } private bool TryGetLiveBoxSlot(InteractablePackagingBox box, out int slot) { slot = -1; if ((Object)(object)box == (Object)null) { return false; } if (box is InteractablePackagingBox_Card) { return TrySlotInList((IList)RestockManager.GetCardPackagingBoxList(), box, skipStored: false, out slot); } return TrySlotInList((IList)RestockManager.GetItemPackagingBoxList(), box, PlacementInterop.IsRecordWarehouse, out slot); } private static bool TrySlotInList(IList boxes, InteractablePackagingBox box, bool skipStored, out int slot) where T : InteractablePackagingBox { slot = 0; for (int i = 0; boxes != null && i < boxes.Count; i++) { T val = boxes[i]; if ((Object)(object)val == (Object)null) { continue; } if (skipStored) { object obj = val; InteractablePackagingBox_Item val2 = (InteractablePackagingBox_Item)((obj is InteractablePackagingBox_Item) ? obj : null); if (val2 != null && val2.m_IsStored) { continue; } } if ((object)val == box) { return true; } slot++; } slot = -1; return false; } internal void RegisterHostSceneBoxes() { if (_host) { RegisterHostBoxes((IList)RestockManager.GetItemPackagingBoxList()); RegisterHostBoxes((IList)RestockManager.GetCardPackagingBoxList()); RegisterHostBoxes((IList)RestockManager.GetShelfPackagingBoxList()); } } internal void RegisterHostCreated(InteractablePackagingBox box) { if (_host && !((Object)(object)box == (Object)null) && !IsMaterializing) { Guid guid = ResolveHostCreatedId(box); BoxCreatedMessage boxCreatedMessage = CreateMessage(guid, box); boxCreatedMessage.PredictionId = HostPredictionId; _broadcast((INetMessage)(object)boxCreatedMessage); ManualLogSource log = CoopPlugin.Log; Guid guid2 = guid; log.LogInfo((object)("[box-id] created id=" + guid2.ToString() + " kind=" + ((object)box).GetType().Name)); } } internal Guid EnsureHostId(InteractablePackagingBox box) { if (!_host) { throw new InvalidOperationException("Only the host may assign a box network ID."); } if ((Object)(object)box == (Object)null) { throw new ArgumentNullException("box"); } if (_idsByBox.TryGetValue(box, out var value)) { return value; } Guid guid; if (_pendingHostCreatedId != Guid.Empty) { guid = _pendingHostCreatedId; _pendingHostCreatedId = Guid.Empty; } else { guid = ((_deliverySpawns > 0) ? ConsumeHostDeliveryId() : Guid.Empty); } if (guid == Guid.Empty || IsIdInUse(guid)) { if (guid != Guid.Empty) { ManualLogSource log = CoopPlugin.Log; Guid guid2 = guid; log.LogWarning((object)("[box-id] creator id=" + guid2.ToString() + " was claimed before spawn; minting a fresh host id.")); } guid = Guid.NewGuid(); } Bind(guid, box); return guid; } private Guid ResolveHostCreatedId(InteractablePackagingBox box) { return EnsureHostId(box); } internal bool PushHostCreatedId(Guid id, InteractablePackagingBox reassigningFrom = null) { if (!_host) { return false; } if (id == Guid.Empty) { return true; } if (!IsFreshHostCreatorId(id, reassigningFrom)) { ManualLogSource log = CoopPlugin.Log; Guid guid = id; log.LogWarning((object)("[box-id] rejected creator-supplied host box id=" + guid.ToString() + ": it is already bound to a live box or already pending for another creation.")); return false; } _pendingHostCreatedId = id; return true; } internal void CancelHostCreatedId(Guid id) { if (_host && id != Guid.Empty && _pendingHostCreatedId == id) { _pendingHostCreatedId = Guid.Empty; } } internal bool StageHostDeliveryIds(IReadOnlyList ids) { _stagedHostDeliveryIds.Clear(); if (!_host || ids == null) { return _host; } for (int i = 0; i < ids.Count; i++) { if (!(ids[i] == Guid.Empty)) { if (!IsFreshHostCreatorId(ids[i], null)) { CoopPlugin.Log.LogWarning((object)("[box-id] rejected staged delivery id=" + ids[i].ToString() + ": it is already bound to a live box or already pending; the intent must not adopt it.")); _stagedHostDeliveryIds.Clear(); return false; } _stagedHostDeliveryIds.Add(ids[i]); } } return true; } internal void CommitHostDeliveryEntry(int count) { if (_host) { HostDeliveryEntry hostDeliveryEntry = new HostDeliveryEntry { Remaining = count }; for (int i = 0; i < count && i < _stagedHostDeliveryIds.Count; i++) { hostDeliveryEntry.Ids.Enqueue(_stagedHostDeliveryIds[i]); } _stagedHostDeliveryIds.Clear(); _hostDeliveryEntries.Add(hostDeliveryEntry); } } private Guid ConsumeHostDeliveryId() { if (_hostDeliveryEntries.Count == 0) { return Guid.Empty; } HostDeliveryEntry hostDeliveryEntry = _hostDeliveryEntries[0]; Guid result = ((hostDeliveryEntry.Ids.Count > 0) ? hostDeliveryEntry.Ids.Dequeue() : Guid.Empty); if (--hostDeliveryEntry.Remaining <= 0) { _hostDeliveryEntries.RemoveAt(0); } return result; } internal void ClearHostDeliveryEntries() { _hostDeliveryEntries.Clear(); _stagedHostDeliveryIds.Clear(); } internal void CancelStagedHostDeliveryIds() { _stagedHostDeliveryIds.Clear(); } internal void ResyncHostDeliveryEntries() { ClearHostDeliveryEntries(); List spawnBoxRestockIndexWaitingList = CPlayerData.m_SpawnBoxRestockIndexWaitingList; List spawnBoxItemCountWaitingList = CPlayerData.m_SpawnBoxItemCountWaitingList; int num = 0; while (spawnBoxRestockIndexWaitingList != null && num < spawnBoxRestockIndexWaitingList.Count) { _hostDeliveryEntries.Add(new HostDeliveryEntry { Remaining = ((spawnBoxItemCountWaitingList != null && num < spawnBoxItemCountWaitingList.Count) ? spawnBoxItemCountWaitingList[num] : 0) }); num++; } } internal DeliveryQueueState CaptureDeliveryQueueState() { DeliveryQueueState deliveryQueueState = new DeliveryQueueState { Remaining = new int[_hostDeliveryEntries.Count], Ids = new Guid[_hostDeliveryEntries.Count][] }; for (int i = 0; i < _hostDeliveryEntries.Count; i++) { deliveryQueueState.Remaining[i] = _hostDeliveryEntries[i].Remaining; deliveryQueueState.Ids[i] = _hostDeliveryEntries[i].Ids.ToArray(); } return deliveryQueueState; } internal void RestoreDeliveryQueueState(DeliveryQueueState state) { ClearHostDeliveryEntries(); if (state?.Ids == null || state.Remaining == null) { return; } for (int i = 0; i < state.Ids.Length; i++) { HostDeliveryEntry hostDeliveryEntry = new HostDeliveryEntry { Remaining = state.Remaining[i] }; Guid[] array = state.Ids[i]; int num = 0; while (array != null && num < array.Length) { hostDeliveryEntry.Ids.Enqueue(array[num]); num++; } _hostDeliveryEntries.Add(hostDeliveryEntry); } } internal void BeginDeliverySpawns() { _deliverySpawns++; } internal void EndDeliverySpawns() { if (_deliverySpawns > 0) { _deliverySpawns--; } } internal Guid AssignClientCreated(InteractablePackagingBox box) { if (_host) { throw new InvalidOperationException("Only a client assigns a client-created box id."); } if ((Object)(object)box == (Object)null) { throw new ArgumentNullException("box"); } if (_idsByBox.TryGetValue(box, out var value)) { return value; } if (IsMaterializing) { return Guid.Empty; } Guid guid; if (!(_pendingClientCreatedId != Guid.Empty)) { guid = ((_deliverySpawns <= 0 || _pendingClientDeliveryIds.Count <= 0) ? Guid.NewGuid() : _pendingClientDeliveryIds.Dequeue()); } else { guid = _pendingClientCreatedId; _pendingClientCreatedId = Guid.Empty; } Bind(guid, box); _clientOwnedIds.Remove(guid); if (_pendingClientDescriptors.TryGetValue(guid, out var value2)) { _pendingClientDescriptors.Remove(guid); ApplyCreatedToBox(box, value2); } return guid; } internal void PushClientDeliveryIds(IEnumerable ids) { if (_host || ids == null) { return; } foreach (Guid id in ids) { if (id != Guid.Empty) { _pendingClientDeliveryIds.Enqueue(id); _clientOwnedIds.Add(id); } } } internal void PushClientCreatedId(Guid id) { if (!_host && !(id == Guid.Empty)) { _pendingClientCreatedId = id; _clientOwnedIds.Add(id); } } internal int PendingDeliveryCount() { return CPlayerData.m_SpawnBoxItemCountWaitingList?.Count ?? 0; } internal void RollbackClientCreated(IReadOnlyList ids) { if (_host || ids == null) { return; } for (int i = 0; i < ids.Count; i++) { Guid guid = ids[i]; if (!(guid == Guid.Empty)) { if (TryGetBox(guid, out var box)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] discarding rejected-checkout box id=", null, null, null, null }; Guid guid2 = guid; obj[1] = guid2.ToString(); obj[2] = " name="; obj[3] = ((Object)box).name; obj[4] = "."; log.LogInfo((object)string.Concat(obj)); Unbind(guid, box); DestroyWithoutNotification(box); } _clientOwnedIds.Remove(guid); _pendingClientDescriptors.Remove(guid); if (_pendingClientCreatedId == guid) { _pendingClientCreatedId = Guid.Empty; } RemoveQueued(_pendingClientDeliveryIds, guid); } } } internal void ReleaseUnspawnedClientIds(IReadOnlyList ids) { if (_host || ids == null) { return; } for (int i = 0; i < ids.Count; i++) { Guid guid = ids[i]; if (!(guid == Guid.Empty) && _clientOwnedIds.Remove(guid)) { _pendingClientDescriptors.Remove(guid); if (_pendingClientCreatedId == guid) { _pendingClientCreatedId = Guid.Empty; } RemoveQueued(_pendingClientDeliveryIds, guid); } } } private static void RemoveQueued(Queue queue, Guid id) { if (queue.Count == 0) { return; } List list = new List(queue.Count); while (queue.Count > 0) { Guid guid = queue.Dequeue(); if (guid != id) { list.Add(guid); } } for (int i = 0; i < list.Count; i++) { queue.Enqueue(list[i]); } } internal void CancelPendingClientDeliveries(int appendedDeliveries) { if (_host || appendedDeliveries <= 0) { return; } List spawnBoxItemCountWaitingList = CPlayerData.m_SpawnBoxItemCountWaitingList; List spawnBoxRestockIndexWaitingList = CPlayerData.m_SpawnBoxRestockIndexWaitingList; RestockManager val = SceneRef.Get(); IList list = (((Object)(object)val == (Object)null) ? null : (PendingRestockRows?.GetValue(val) as IList)); if (spawnBoxItemCountWaitingList == null || spawnBoxRestockIndexWaitingList == null || list == null) { CoopPlugin.Log.LogWarning((object)("[box-id] could not cancel " + appendedDeliveries + " pending client deliveries; the restock queue is unavailable.")); return; } int num = Math.Min(appendedDeliveries, spawnBoxItemCountWaitingList.Count); if (num > 0) { CoopPlugin.Log.LogInfo((object)("[box-id] cancelling " + num + " pending client deliveries for a rejected purchase.")); for (int i = 0; i < num; i++) { RemoveLast(spawnBoxItemCountWaitingList); RemoveLast(spawnBoxRestockIndexWaitingList); RemoveLast(list); } } } private static void RemoveLast(IList list) { if (list != null && list.Count > 0) { list.RemoveAt(list.Count - 1); } } internal bool TryGetId(InteractablePackagingBox box, out Guid id) { if ((Object)(object)box != (Object)null && _idsByBox.TryGetValue(box, out id)) { return true; } id = Guid.Empty; return false; } internal void HostRefresh(Guid id) { if (_host && !(id == Guid.Empty) && TryGetBox(id, out var box)) { _broadcast((INetMessage)(object)CreateMessage(id, box)); } } internal void HostRefreshReleasedWorkerBox(Guid id) { if (_host && !(id == Guid.Empty) && TryGetBox(id, out var box)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val == null || !val.m_IsStored) { _broadcast((INetMessage)(object)CreateMessage(id, box)); } } } internal bool ApplyHostBoxState(InteractablePackagingBox_Item item, BoxStateRequestMessage message) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null || message == null) { return false; } ShelfCompartment itemCompartment = item.m_ItemCompartment; bool flag = message.ContentsChanged || (Object)(object)itemCompartment == (Object)null; if (flag && !IsValidItem(message.ItemType, message.ItemCount)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[7] { "[box-id] rejected box state request id=", null, null, null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[1] = boxNetworkId.ToString(); obj[2] = " type="; obj[3] = ((object)Unsafe.As(ref message.ItemType)/*cast due to .constrained prefix*/).ToString(); obj[4] = " count="; obj[5] = message.ItemCount.ToString(); obj[6] = ": the content payload is not a valid item."; log.LogWarning((object)string.Concat(obj)); return false; } ApplyItemState(item, new BoxNetworkState { Kind = BoxNetworkKind.Item, ItemType = (flag ? message.ItemType : itemCompartment.GetItemType()), ItemCount = (flag ? message.ItemCount : itemCompartment.GetItemCount()), IsBoxOpened = message.IsBoxOpened }); return true; } internal void ClientApplyBoxState(BoxStateMessage message) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) if (TryGetBox(message.BoxNetworkId, out var box)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { ApplyItemState(val, new BoxNetworkState { Kind = BoxNetworkKind.Item, ItemType = message.ItemType, ItemCount = message.ItemCount, IsBoxOpened = message.IsBoxOpened }); } } } internal BoxNetworkState DescribeAuthoritative(Guid id) { if (!_host || !(id != Guid.Empty) || !TryGetBox(id, out var box)) { return null; } return Describe(id, box); } internal bool TryGetBox(Guid id, out InteractablePackagingBox box) { box = null; if (id != Guid.Empty && _boxesById.TryGetValue(id, out box)) { return (Object)(object)box != (Object)null; } return false; } internal InteractablePackagingBox_Item FindItemBox(ShelfCompartment compartment) { if (!((Object)(object)compartment == (Object)null)) { return ((Component)compartment).GetComponentInParent(true); } return null; } internal bool IsStored(Guid id) { if (id != Guid.Empty && _boxesById.TryGetValue(id, out var value)) { return IsStoredBox(value); } return false; } internal void NotePendingLocalMove(Guid boxNetworkId, Guid predictionId) { if (boxNetworkId != Guid.Empty && predictionId != Guid.Empty) { _pendingLocalMoves[boxNetworkId] = predictionId; } } internal bool HasPendingLocalMove(Guid boxNetworkId) { if (!_pendingLocalMoves.TryGetValue(boxNetworkId, out var value)) { return false; } if (PredictionApi.IsPending(value)) { return true; } _pendingLocalMoves.Remove(boxNetworkId); return false; } private static bool IsStoredBox(InteractablePackagingBox box) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val == null) { return false; } if (val.m_IsStored) { return true; } ShelfCompartment boxStoredCompartment = val.GetBoxStoredCompartment(); return ((boxStoredCompartment != null) ? boxStoredCompartment.GetInteractablePackagingBoxList() : null)?.Contains(val) ?? false; } internal void ClientConvertToStoredRecord(Guid id) { if (_host) { throw new InvalidOperationException("The host cannot apply a client stored-box state."); } if (id == Guid.Empty) { throw new InvalidOperationException("A client stored-box state requires an ID."); } if (TryGetBox(id, out var box)) { Unbind(id, box); DestroyWithoutNotification(box); } } internal void BindStoredLiveBox(Guid id, InteractablePackagingBox_Item box, WarehouseBoxState state) { if (id == Guid.Empty || (Object)(object)box == (Object)null || state == null) { throw new InvalidOperationException("A live warehouse box requires its ID and state."); } if (!_host && TryGetBox(id, out var box2) && (object)box2 != box) { Unbind(id, box2); DestroyWithoutNotification(box2); } ((InteractablePackagingBox)box).SetPhysicsEnabled(false); Bind(id, (InteractablePackagingBox)(object)box); } internal void DetachForWarehouseTake(Guid id) { if (!(id == Guid.Empty) && TryGetBox(id, out var box)) { Unbind(id, box); } } internal bool ClientEnsureWarehouseTake(Guid id, EItemType itemType, int amount, bool isBig, out InteractablePackagingBox_Item box) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) box = null; if (_host) { return false; } if (TryGetBox(id, out var box2)) { box = (InteractablePackagingBox_Item)(object)((box2 is InteractablePackagingBox_Item) ? box2 : null); if ((Object)(object)box == (Object)null) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] warehouse take id=", null, null, null, null }; Guid guid = id; obj[1] = guid.ToString(); obj[2] = " is already a "; obj[3] = ((object)box2).GetType().Name; obj[4] = ", not an item box; ignoring."; log.LogWarning((object)string.Concat(obj)); return false; } return true; } BoxNetworkState state = ItemDescriptor(id, itemType, amount, isBig, Vector3.zero, Quaternion.identity); box = MaterializeItem(state); if ((Object)(object)box == (Object)null) { ManualLogSource log2 = CoopPlugin.Log; Guid guid = id; log2.LogWarning((object)("[box-id] could not materialize warehouse take box id=" + guid.ToString() + " (missing prefab or interrupted object); ignoring the delta.")); return false; } Bind(id, (InteractablePackagingBox)(object)box); return true; } internal InteractablePackagingBox_Item MaterializeWarehouseStored(EItemType itemType, int amount, bool isBig) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) _materializing++; try { return RestockManager.SpawnPackageBoxItem(itemType, amount, isBig); } finally { _materializing--; } } internal void HostNotifyDestroyed(InteractablePackagingBox box) { if (_host && !((Object)(object)box == (Object)null) && _idsByBox.TryGetValue(box, out var value)) { bool flag = IsStoredBox(box); Unbind(value, box); ManualLogSource log = CoopPlugin.Log; string[] obj = new string[7] { "[box-id] host box destroyed id=", null, null, null, null, null, null }; Guid guid = value; obj[1] = guid.ToString(); obj[2] = " name="; obj[3] = ((Object)box).name; obj[4] = " stored="; obj[5] = flag.ToString(); obj[6] = "."; log.LogInfo((object)string.Concat(obj)); if (!flag) { _broadcast((INetMessage)(object)new BoxDestroyedMessage { PredictionId = HostPredictionId, BoxNetworkId = value }); ManualLogSource log2 = CoopPlugin.Log; guid = value; log2.LogInfo((object)("[box-id] destroyed id=" + guid.ToString() + ".")); } } } internal void BeginContainerConsume() { _suppressDestroyForward++; } internal void EndContainerConsume() { if (_suppressDestroyForward > 0) { _suppressDestroyForward--; } } internal ClientDestroyCapture CaptureClientDestroyed(InteractablePackagingBox box) { if (_host || _applyingRemote > 0 || (Object)(object)box == (Object)null) { return null; } if (!_idsByBox.TryGetValue(box, out var value)) { return null; } if (value == Guid.Empty) { return null; } if (_suppressDestroyForward > 0) { Unbind(value, box); return null; } InteractablePackagingBox_Shelf val = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); Guid guid; if (val != null && !TryGetBoxedFurniture(val, out var _)) { ManualLogSource log = CoopPlugin.Log; guid = value; log.LogInfo((object)("[box-id] unbox teardown for id=" + guid.ToString() + "; the placement flow retires the box.")); return null; } BoxCreatedMessage boxCreatedMessage = DescribeKnown(value, box); InteractablePackagingBox_Shelf val2 = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); if (val2 != null) { val2.EmptyBoxShelf(); } DetachStoredBox(box); Unbind(value, box); ManualLogSource log2 = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] captured client destroy-forward id=", null, null, null, null }; guid = value; obj[1] = guid.ToString(); obj[2] = " kind="; obj[3] = boxCreatedMessage.Box.Kind.ToString(); obj[4] = "."; log2.LogInfo((object)string.Concat(obj)); return new ClientDestroyCapture { Id = value, Descriptor = boxCreatedMessage }; } internal void ForwardClientDestroyed(ClientDestroyCapture capture) { if (capture == null) { return; } Guid id = capture.Id; BoxCreatedMessage descriptor = capture.Descriptor; ManualLogSource log = CoopPlugin.Log; Guid guid = id; log.LogInfo((object)("[box-id] forwarding client destroy id=" + guid.ToString() + ".")); BoxDestroyRequestMessage intent = new BoxDestroyRequestMessage { BoxNetworkId = id }; WorldPrediction.Predict("world.boxes", intent, delegate { if (TryGetBox(id, out var box)) { Unbind(id, box); DestroyWithoutNotification(box); } }, delegate { ClientApplyCreated(descriptor); }); } internal BoxCreatedMessage DescribeKnown(Guid id, InteractablePackagingBox box) { return CreateMessage(id, box); } internal bool HostDestroyRequested(Guid id, Guid predictionId) { if (!_host || id == Guid.Empty) { return false; } if (TryGetBox(id, out var box)) { HostPredictionId = predictionId; try { DetachStoredBox(box); ((InteractableObject)box).OnDestroyed(); } finally { HostPredictionId = Guid.Empty; } return true; } return false; } internal void ClientApplyCreated(BoxCreatedMessage message) { if (!_host && !_sceneSlotsCaptured) { return; } BoxNetworkState box = message.Box; if (box.Kind == BoxNetworkKind.Furniture && !PlacementApi.IsPlacementIdentityReady) { _pendingFurniture[box.BoxNetworkId] = message; return; } if (TryGetBox(box.BoxNetworkId, out var box2)) { ApplyCreatedToBox(box2, message); ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] refreshed authoritative box id=", null, null, null, null }; Guid boxNetworkId = box.BoxNetworkId; obj[1] = boxNetworkId.ToString(); obj[2] = " kind="; obj[3] = box.Kind.ToString(); obj[4] = "."; log.LogInfo((object)string.Concat(obj)); return; } if (_clientOwnedIds.Contains(box.BoxNetworkId)) { _pendingClientDescriptors[box.BoxNetworkId] = message; return; } InteractablePackagingBox val = ((message.SnapshotSlot >= 0) ? ResolveSceneSlot(box.Kind, message.SnapshotSlot, box.BoxNetworkId) : null); if ((Object)(object)val == (Object)null) { if (message.SnapshotSlot >= 0) { ManualLogSource log2 = CoopPlugin.Log; string[] obj2 = new string[7] { "[box-id] snapshot slot ", message.SnapshotSlot.ToString(), " did not resolve (kind=", box.Kind.ToString(), "); materializing box id=", null, null }; Guid boxNetworkId = box.BoxNetworkId; obj2[5] = boxNetworkId.ToString(); obj2[6] = " from its descriptor."; log2.LogWarning((object)string.Concat(obj2)); } val = Materialize(box, message.StableEntityId); } if ((Object)(object)val == (Object)null) { ManualLogSource log3 = CoopPlugin.Log; string[] obj3 = new string[7] { "[box-id] could not materialize authoritative box id=", null, null, null, null, null, null }; Guid boxNetworkId = box.BoxNetworkId; obj3[1] = boxNetworkId.ToString(); obj3[2] = " kind="; obj3[3] = box.Kind.ToString(); obj3[4] = " slot="; obj3[5] = message.SnapshotSlot.ToString(); obj3[6] = "; descriptor dropped."; log3.LogWarning((object)string.Concat(obj3)); } else { Bind(box.BoxNetworkId, val); ManualLogSource log4 = CoopPlugin.Log; string[] obj4 = new string[9] { "[box-id] bound authoritative box id=", null, null, null, null, null, null, null, null }; Guid boxNetworkId = box.BoxNetworkId; obj4[1] = boxNetworkId.ToString(); obj4[2] = " kind="; obj4[3] = box.Kind.ToString(); obj4[4] = " slot="; obj4[5] = message.SnapshotSlot.ToString(); obj4[6] = " object="; obj4[7] = ((Object)val).name; obj4[8] = "."; log4.LogInfo((object)string.Concat(obj4)); ApplyCreatedToBox(val, message); } } private void ApplyCreatedToBox(InteractablePackagingBox box, BoxCreatedMessage message) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) BoxNetworkState box2 = message.Box; bool num = box2.Kind == BoxNetworkKind.Furniture && AdoptFurnitureIdentity(box, message); InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { ApplyItemState(val, box2); } if (num) { WorldClientBehaviour.RetryDeferredPlacementDeltas(); } if (!IsBeingHeld(box) && !HasPendingLocalMove(box2.BoxNetworkId)) { ApplyPose(box, box2.Position, box2.Rotation); } } private static bool AdoptFurnitureIdentity(InteractablePackagingBox box, BoxCreatedMessage message) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) InteractablePackagingBox_Shelf val = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); if (val != null && TryGetBoxedFurniture(val, out var furniture)) { return PlacementIdentity.TryAdoptBoxableFurnitureIdentity(furniture, message.StableEntityId, 1L, message.Box.FurnitureObjectType); } return false; } private InteractablePackagingBox ResolveSceneSlot(BoxNetworkKind kind, int slot, Guid boxNetworkId) { if (!_sceneSlots.TryGetValue((byte)kind, out var value) || slot < 0 || slot >= value.Count) { return null; } InteractablePackagingBox val = value[slot]; if ((Object)(object)val == (Object)null) { return null; } if (_idsByBox.TryGetValue(val, out var value2) && value2 != boxNetworkId) { Guid guid; if (_boxesById.ContainsKey(value2)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[9] { "[box-id] snapshot slot ", slot.ToString(), " for kind=", kind.ToString(), " is bound to live id=", null, null, null, null }; guid = value2; obj[5] = guid.ToString(); obj[6] = " (wanted "; guid = boxNetworkId; obj[7] = guid.ToString(); obj[8] = "); materializing the authoritative box."; log.LogWarning((object)string.Concat(obj)); return null; } ManualLogSource log2 = CoopPlugin.Log; string[] obj2 = new string[9] { "[box-id] snapshot slot ", slot.ToString(), " for kind=", kind.ToString(), " had stale id=", null, null, null, null }; guid = value2; obj2[5] = guid.ToString(); obj2[6] = "; rebinding to "; guid = boxNetworkId; obj2[7] = guid.ToString(); obj2[8] = "."; log2.LogWarning((object)string.Concat(obj2)); } return val; } internal void ClientApplyDestroyed(BoxDestroyedMessage message) { bool flag = _pendingFurniture.Remove(message.BoxNetworkId); _clientOwnedIds.Remove(message.BoxNetworkId); _pendingClientDescriptors.Remove(message.BoxNetworkId); if (!TryGetBox(message.BoxNetworkId, out var box)) { if (_boxesById.TryGetValue(message.BoxNetworkId, out var value)) { Unbind(message.BoxNetworkId, value); } else if (message.BoxNetworkId != Guid.Empty) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] authoritative destroy for already-gone box id=", null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[1] = boxNetworkId.ToString(); obj[2] = " pending="; obj[3] = flag.ToString(); obj[4] = "."; log.LogInfo((object)string.Concat(obj)); } } else { Unbind(message.BoxNetworkId, box); DestroyWithoutNotification(box); } } internal void ClientCompleteBaseline() { SweepUnrepresentedSaveBoxes(); RetryPendingFurniture(); } private void SweepUnrepresentedSaveBoxes() { if (_host || _baselineSaveBoxes.Count == 0) { return; } int num = 0; for (int i = 0; i < _baselineSaveBoxes.Count; i++) { InteractablePackagingBox val = _baselineSaveBoxes[i]; if ((Object)(object)val == (Object)null) { continue; } Guid value; bool flag = _idsByBox.TryGetValue(val, out value); if (!flag || !_boxesById.ContainsKey(value)) { if (flag) { Unbind(value, val); } CoopPlugin.Log.LogInfo((object)("[box-id] sweeping save box " + ((Object)val).name + " the host no longer represents.")); DestroyWithoutNotification(val); num++; } } _baselineSaveBoxes.Clear(); if (num > 0) { CoopPlugin.Log.LogInfo((object)("[box-id] swept " + num + " unbound save box(es) after the world baseline.")); } } internal void ClientForgetPhysical(Guid id, InteractablePackagingBox box) { if (!_host && !(id == Guid.Empty) && !((Object)(object)box == (Object)null)) { Unbind(id, box); DestroyWithoutNotification(box); } } internal void AppendBaselineMessages(WorldTransferManifest manifest, Action append) { if (!_host || append == null) { return; } foreach (KeyValuePair item in _boxesById) { InteractablePackagingBox value = item.Value; if ((Object)(object)value == (Object)null) { continue; } InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((value is InteractablePackagingBox_Item) ? value : null); if (val != null && val.m_IsStored) { continue; } BoxCreatedMessage boxCreatedMessage = CreateMessage(item.Key, value); if (manifest != null) { if (manifest.TryGetBoxSlot(item.Key, out var slot)) { boxCreatedMessage.SnapshotSlot = slot; } } else { if (TryGetLiveBoxSlot(value, out var slot2)) { boxCreatedMessage.SnapshotSlot = slot2; } CoopPlugin.Log.LogWarning((object)("[box-id] no frozen snapshot manifest for box id=" + item.Key.ToString() + "; this connection's baseline is using live slot " + boxCreatedMessage.SnapshotSlot + ".")); } append((INetMessage)(object)boxCreatedMessage); } } private BoxCreatedMessage CreateMessage(Guid id, InteractablePackagingBox box) { BoxCreatedMessage boxCreatedMessage = new BoxCreatedMessage { Box = Describe(id, box) }; InteractablePackagingBox_Shelf val = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); if (val == null) { return boxCreatedMessage; } if (TryGetBoxedFurniture(val, out var furniture) && PlacementApi.TryMakeBoxableFurnitureEntityId(furniture, 1L, out var entityId)) { boxCreatedMessage.StableEntityId = entityId; } else { ManualLogSource log = CoopPlugin.Log; Guid guid = id; log.LogWarning((object)("[box-furniture] could not assign placement identity to box id=" + guid.ToString() + ".")); } return boxCreatedMessage; } private static bool TryGetBoxedFurniture(InteractablePackagingBox_Shelf box, out InteractableObject furniture) { object? obj = BoxedFurniture?.GetValue(box); furniture = (InteractableObject)((obj is InteractableObject) ? obj : null); return (Object)(object)furniture != (Object)null; } internal bool IsBeingHeld(InteractablePackagingBox box) { bool flag = default(bool); int num; if ((Object)(object)box != (Object)null) { object obj = BeingHoldField?.GetValue(box); if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private void RetryPendingFurniture(int _) { RetryPendingFurniture(); } private void RetryPendingFurniture() { if (!_host && _pendingFurniture.Count != 0 && PlacementApi.IsPlacementIdentityReady) { List list = new List(_pendingFurniture.Values); for (int i = 0; i < list.Count; i++) { BoxCreatedMessage boxCreatedMessage = list[i]; _pendingFurniture.Remove(boxCreatedMessage.Box.BoxNetworkId); ClientApplyCreated(boxCreatedMessage); } } } private void RegisterHostBoxes(IList boxes) where T : InteractablePackagingBox { if (boxes == null) { return; } for (int i = 0; i < boxes.Count; i++) { if ((Object)(object)boxes[i] != (Object)null) { EnsureHostId((InteractablePackagingBox)(object)boxes[i]); } } } private bool IsIdInUse(Guid id) { return IsIdInUseByOtherThan(id, null); } private bool IsIdInUseByOtherThan(Guid id, InteractablePackagingBox reassigningFrom) { if (id == Guid.Empty) { return false; } if (_boxesById.TryGetValue(id, out var value) && (Object)(object)value != (Object)null && value != reassigningFrom) { return true; } foreach (KeyValuePair item in _idsByBox) { if (item.Value == id && item.Key != reassigningFrom) { return true; } } return false; } private bool IsFreshHostCreatorId(Guid id, InteractablePackagingBox reassigningFrom) { if (id == Guid.Empty || IsIdInUseByOtherThan(id, reassigningFrom) || _pendingHostCreatedId == id) { return false; } for (int i = 0; i < _stagedHostDeliveryIds.Count; i++) { if (_stagedHostDeliveryIds[i] == id) { return false; } } for (int j = 0; j < _hostDeliveryEntries.Count; j++) { foreach (Guid id2 in _hostDeliveryEntries[j].Ids) { if (id2 == id) { return false; } } } return true; } private void Bind(Guid id, InteractablePackagingBox box) { if (id == Guid.Empty || (Object)(object)box == (Object)null) { throw new InvalidOperationException("Cannot bind an invalid box network ID."); } if (_boxesById.TryGetValue(id, out var value) && (Object)(object)value != (Object)null && value != box) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[7] { "[box-id] rebinding live id=", null, null, null, null, null, null }; Guid guid = id; obj[1] = guid.ToString(); obj[2] = " from "; obj[3] = ((Object)value).name; obj[4] = " to "; obj[5] = ((Object)box).name; obj[6] = "."; log.LogWarning((object)string.Concat(obj)); _idsByBox.Remove(value); } if (_idsByBox.TryGetValue(box, out var value2) && value2 != id) { _boxesById.Remove(value2); } _boxesById[id] = box; _idsByBox[box] = id; } private void Unbind(Guid id, InteractablePackagingBox box) { _boxesById.Remove(id); if ((Object)(object)box != (Object)null) { _idsByBox.Remove(box); } } private static BoxNetworkState Describe(Guid id, InteractablePackagingBox box) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { ShelfCompartment itemCompartment = val.m_ItemCompartment; return ItemDescriptor(id, (EItemType)(((Object)(object)itemCompartment == (Object)null) ? (-1) : ((int)itemCompartment.GetItemType())), (!((Object)(object)itemCompartment == (Object)null)) ? itemCompartment.GetItemCount() : 0, val.m_IsBigBox, ((Component)val).transform.position, ((Component)val).transform.rotation, IsBoxOpen(val)); } InteractablePackagingBox_Card val2 = (InteractablePackagingBox_Card)(object)((box is InteractablePackagingBox_Card) ? box : null); if (val2 != null) { BoxNetworkState boxNetworkState = new BoxNetworkState { BoxNetworkId = id, Kind = BoxNetworkKind.Card, Position = ((Component)val2).transform.position, Rotation = ((Component)val2).transform.rotation }; List cardDataList = val2.GetCardDataList(); if (cardDataList != null) { for (int i = 0; i < cardDataList.Count; i++) { if (cardDataList[i] != null) { boxNetworkState.Cards.Add(ToState(cardDataList[i])); } } } return boxNetworkState; } InteractablePackagingBox_Shelf val3 = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); if (val3 != null) { return new BoxNetworkState { BoxNetworkId = id, Kind = BoxNetworkKind.Furniture, Position = ((Component)val3).transform.position, Rotation = ((Component)val3).transform.rotation, FurnitureObjectType = val3.GetBoxedObjectType() }; } throw new InvalidOperationException("Unsupported packaging box type: " + ((object)box).GetType().FullName); } internal static BoxNetworkState ItemDescriptor(Guid id, EItemType itemType, int itemCount, bool isBig, Vector3 position, Quaternion rotation, bool isBoxOpened = false) { //IL_0014: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) return new BoxNetworkState { BoxNetworkId = id, Kind = BoxNetworkKind.Item, ItemType = itemType, ItemCount = itemCount, IsBig = isBig, IsBoxOpened = isBoxOpened, Position = position, Rotation = rotation }; } private InteractablePackagingBox Materialize(BoxNetworkState state, string furnitureEntityId) { _materializing++; try { switch (state.Kind) { case BoxNetworkKind.Item: return (InteractablePackagingBox)(object)MaterializeItem(state); case BoxNetworkKind.Card: return (InteractablePackagingBox)(object)MaterializeCard(state); case BoxNetworkKind.Furniture: return (InteractablePackagingBox)(object)MaterializeFurniture(state, furnitureEntityId); default: { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] unsupported authoritative box kind ", state.Kind.ToString(), " id=", null, null }; Guid boxNetworkId = state.BoxNetworkId; obj[3] = boxNetworkId.ToString(); obj[4] = "."; log.LogWarning((object)string.Concat(obj)); return null; } } } finally { _materializing--; } } private InteractablePackagingBox_Item MaterializeItem(BoxNetworkState state) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) _materializing++; try { return RestockManager.SpawnPackageBoxItem(state.ItemType, state.ItemCount, state.IsBig); } finally { _materializing--; } } private InteractablePackagingBox_Card MaterializeCard(BoxNetworkState state) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_cardSpawnAnchor == (Object)null) { _cardSpawnAnchor = new GameObject("CardShopCoop.BoxNetworkCardSpawn").transform; } _cardSpawnAnchor.SetPositionAndRotation(state.Position, state.Rotation); List list = new List(state.Cards.Count); for (int i = 0; i < state.Cards.Count; i++) { CardData val = FromState(state.Cards[i]); if (val.cardGrade > 10) { GradingApi.Remember(val); } list.Add(val); } return RestockManager.SpawnPackageBoxCard(list, _cardSpawnAnchor); } private InteractablePackagingBox_Shelf MaterializeFurniture(BoxNetworkState state, string furnitureEntityId) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) Guid boxNetworkId; if (PlacementApi.TryResolveBoxableFurnitureEntityId(furnitureEntityId, 1L, state.FurnitureObjectType, out var result)) { InteractablePackagingBox_Shelf packagingBoxShelf = result.GetPackagingBoxShelf(); if ((Object)(object)packagingBoxShelf == (Object)null) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-furniture] applying authoritative box-up to exact ", ((object)Unsafe.As(ref state.FurnitureObjectType)/*cast due to .constrained prefix*/).ToString(), " placement for box id=", null, null }; boxNetworkId = state.BoxNetworkId; obj[3] = boxNetworkId.ToString(); obj[4] = "."; log.LogInfo((object)string.Concat(obj)); result.BoxUpObject(false); packagingBoxShelf = result.GetPackagingBoxShelf(); } return packagingBoxShelf; } if (!PlacementApi.TryCreateBoxableFurnitureForIdentity(furnitureEntityId, 1L, state.FurnitureObjectType, state.Position, state.Rotation, out var created)) { ManualLogSource log2 = CoopPlugin.Log; string[] obj2 = new string[5] { "[box-furniture] could not resolve or create authoritative furniture identity ", furnitureEntityId, " for box id=", null, null }; boxNetworkId = state.BoxNetworkId; obj2[3] = boxNetworkId.ToString(); obj2[4] = "; descriptor dropped."; log2.LogWarning((object)string.Concat(obj2)); return null; } ManualLogSource log3 = CoopPlugin.Log; string[] obj3 = new string[5] { "[box-furniture] created host-spawned furniture for box id=", null, null, null, null }; boxNetworkId = state.BoxNetworkId; obj3[1] = boxNetworkId.ToString(); obj3[2] = " entity="; obj3[3] = furnitureEntityId; obj3[4] = "."; log3.LogInfo((object)string.Concat(obj3)); return created.GetPackagingBoxShelf(); } internal void DestroyWithoutNotification(InteractablePackagingBox box) { if ((Object)(object)box == (Object)null) { return; } _applyingRemote++; try { InteractablePackagingBox_Shelf val = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); if (val != null) { val.EmptyBoxShelf(); } DetachStoredBox(box); ((InteractableObject)box).OnDestroyed(); } finally { _applyingRemote--; } } private static void DetachStoredBox(InteractablePackagingBox box) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val == null) { return; } ShelfCompartment boxStoredCompartment = val.GetBoxStoredCompartment(); if (!((Object)(object)boxStoredCompartment == (Object)null)) { List interactablePackagingBoxList = boxStoredCompartment.GetInteractablePackagingBoxList(); if (interactablePackagingBoxList != null && interactablePackagingBoxList.Contains(val)) { CoopPlugin.Log.LogInfo((object)("[warehouse] detaching destroyed stored box " + ((Object)val).name + " from shelf=" + boxStoredCompartment.GetWarehouseIndex() + " comp=" + boxStoredCompartment.GetIndex() + "; the slot would otherwise stay blocked.")); boxStoredCompartment.RemoveBox(val); } } } private static void ApplyPose(InteractablePackagingBox box, Vector3 position, Quaternion rotation) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_002a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)box == (Object)null)) { if ((Object)(object)box.m_Rigidbody != (Object)null) { box.m_Rigidbody.position = position; box.m_Rigidbody.rotation = rotation; } ((Component)box).transform.SetPositionAndRotation(position, rotation); } } private static bool IsValidItem(EItemType type, int count) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if (count >= 0 && count <= 4096) { if (count != 0) { return (int)type != -1; } return true; } return false; } internal static bool IsBoxOpen(InteractablePackagingBox_Item item) { if ((Object)(object)item == (Object)null) { return false; } object obj = BoxOpenedField?.GetValue(item); if (obj is bool) { return (bool)obj; } return ((InteractablePackagingBox)item).IsBoxOpened(); } private static void ApplyItemState(InteractablePackagingBox_Item item, BoxNetworkState state) { //IL_0012: 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_006c: Unknown result type (might be due to invalid IL or missing references) ShelfCompartment itemCompartment = item.m_ItemCompartment; if ((Object)(object)itemCompartment == (Object)null) { return; } bool flag = itemCompartment.GetItemType() != state.ItemType || itemCompartment.GetItemCount() != state.ItemCount; bool flag2 = IsBoxOpen(item) != state.IsBoxOpened; if (flag || flag2) { if (flag2) { item.ForceSetOpenCloseInstant(state.IsBoxOpened); } if (flag || (flag2 && state.IsBoxOpened)) { ApplyItemBoxContents(item, state.ItemType, state.ItemCount); } } } private unsafe static void ApplyItemBoxContents(InteractablePackagingBox_Item item, EItemType itemType, int itemCount) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) ShelfCompartment itemCompartment = item.m_ItemCompartment; if ((Object)(object)itemCompartment == (Object)null) { return; } if (CompartmentItemsField?.GetValue(itemCompartment) is List list) { for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null) { list[i].DisableItem(); } } list.Clear(); } itemCompartment.PreSpawnItemUpdate(0); if ((int)itemType == -1 || itemCount <= 0) { itemCompartment.SetCompartmentItemType((EItemType)(-1)); itemCompartment.CalculatePositionList(); item.SetItemType((EItemType)(-1)); } else { itemCompartment.SetCompartmentItemType(itemType); itemCompartment.CalculatePositionList(); if (IsBoxOpen(item)) { itemCompartment.SpawnItem(itemCount, false); } else { itemCompartment.PreSpawnItemUpdate(itemCount); } item.SetItemType(itemType); } ItemAmountToSpawnField?.SetValue(item, itemCompartment.GetItemCount()); ((Component)itemCompartment).gameObject.SetActive(IsBoxOpen(item)); CoopPlugin.Log.LogInfo((object)("[box-id] applied box contents id=" + ((Object)item).name + " type=" + ((object)(*(EItemType*)(&itemType))/*cast due to .constrained prefix*/).ToString() + " count=" + itemCount + ".")); } internal static BoxCardState ToState(CardData card) { //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_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) return new BoxCardState { ExpansionType = card.expansionType, MonsterType = card.monsterType, BorderType = ((object)Unsafe.As(ref card.borderType)/*cast due to .constrained prefix*/).ToString(), IsFoil = card.isFoil, IsDestiny = card.isDestiny, IsChampionCard = card.isChampionCard, IsNew = card.isNew, CardGrade = card.cardGrade, GradedCardIndex = card.gradedCardIndex }; } internal static CardData FromState(BoxCardState state) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0027: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown ECardBorderType borderType = (ECardBorderType)Enum.Parse(typeof(ECardBorderType), state.BorderType); return new CardData { expansionType = state.ExpansionType, monsterType = state.MonsterType, borderType = borderType, isFoil = state.IsFoil, isDestiny = state.IsDestiny, isChampionCard = state.IsChampionCard, isNew = state.IsNew, cardGrade = state.CardGrade, gradedCardIndex = state.GradedCardIndex }; } } internal static class PlacementHoldKey { private const long DecorationFlag = 4611686018427387904L; private const long PendingDecorationFlag = 2305843009213693952L; private const int PendingTypeShift = 40; private const int PendingConnectionShift = 32; internal static bool TryGet(InteractableObject obj, out long key) { key = 0L; if ((Object)(object)obj == (Object)null) { return false; } if (DecorationInterop.IsDecoration(obj)) { if (DecorationInterop.TryGetHoldId(obj, out var id) && id > 0) { key = 0x4000000000000000L | id; return true; } return TryGetPendingKey(obj, out key); } if (!PlacementApi.TryMakeObjectKey(PlacementInterop.FindKind(obj), obj, out var key2)) { return false; } key = key2; return true; } private static bool TryGetPendingKey(InteractableObject obj, out long key) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) key = 0L; long num = (long)obj.m_DecoObjectType; if (num <= 0 || num >= 184) { return false; } key = 0x2000000000000000L | (num << 40) | ((long)(CoopCore.LocalConnectionId & 0xFF) << 32) | (uint)((Object)obj).GetInstanceID(); return true; } internal static bool IsPendingDecoration(long key) { return (key & 0x2000000000000000L) != 0; } internal static bool TryGetPendingDecorationType(long key, out EDecoObject type) { type = (EDecoObject)((key >> 40) & 0xFF); if ((int)type > 0 && (int)type < 184) { return Enum.IsDefined(typeof(EDecoObject), type); } return false; } internal static InteractableObject Resolve(long key) { if ((key & 0x4000000000000000L) != 0L) { if (!DecorationInterop.TryResolveHoldId(key & -4611686018427387905L, out var obj)) { return null; } return obj; } Component obj2 = PlacementApi.ResolveObjectByKey((int)key); return (InteractableObject)(object)((obj2 is InteractableObject) ? obj2 : null); } } internal sealed class PlacementHoldInteraction { private sealed class RemoteHold { internal InteractableObject Obj; internal GameObject Ghost; internal Material Material; internal int MoverConnectionId; internal int OriginalLayer; internal ShelfMoveStateValidArea OriginalShelfMove; internal GameObject Package; internal readonly List HiddenRenderers = new List(); } private const float PreviewDistance = 3f; private const float PreviewOffsetUp = -0.1f; private static readonly FieldInfo MovingObjectField = AccessTools.Field(typeof(InteractableObject), "m_IsMovingObject"); private static readonly FieldInfo SnappingPosField = AccessTools.Field(typeof(InteractableObject), "m_IsSnappingPos"); private static readonly MethodInfo OnPlacedMovedObjectMethod = AccessTools.Method(typeof(InteractableObject), "OnPlacedMovedObject", (Type[])null, (Type[])null); private static readonly int ColorId = Shader.PropertyToID("_Color"); private readonly bool _host; private readonly Action _broadcast; private readonly Action _send; private readonly Dictionary _holders = new Dictionary(); private readonly Dictionary _remote = new Dictionary(); private long _localKey; private InteractableObject _localObject; private Vector3 _localBeforePosition; private Quaternion _localBeforeRotation; private Quaternion _localSentRotation; private static int _suppressedLocalStarts; internal PlacementHoldInteraction(bool host, Action broadcast, Action send) { _host = host; _broadcast = broadcast; _send = send; } internal static void BeginSuppressLocalStarts() { _suppressedLocalStarts++; } internal static void EndSuppressLocalStarts() { if (_suppressedLocalStarts > 0) { _suppressedLocalStarts--; } } internal bool IsAllowed(InteractableObject obj) { if ((Object)(object)obj == (Object)null || !PlacementHoldKey.TryGet(obj, out var key)) { return true; } if (_holders.TryGetValue(key, out var value) && !IsLocalMover(value)) { CoopPlugin.Log.LogInfo((object)("[placement-hold] blocked key=" + key + " held by mover=" + value + ".")); return false; } return true; } internal bool LocalStarted(InteractableObject obj) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) if (_suppressedLocalStarts > 0 || (Object)(object)obj == (Object)null || _localKey != 0L || !PlacementHoldKey.TryGet(obj, out var key)) { return false; } _localKey = key; _localObject = obj; _localBeforePosition = ((Component)obj).transform.position; _localBeforeRotation = ((Component)obj).transform.rotation; if (_holders.TryGetValue(key, out var value) && !IsLocalMover(value)) { CoopPlugin.Log.LogInfo((object)("[placement-hold] local start refused key=" + key + " held by mover=" + value + ".")); CancelLocalMove(); return false; } ManualLogSource log = CoopPlugin.Log; string[] obj2 = new string[5] { "[placement-hold] local begin key=", key.ToString(), " host=", null, null }; bool host = _host; obj2[3] = host.ToString(); obj2[4] = "."; log.LogInfo((object)string.Concat(obj2)); if (_host) { _holders[key] = 0; _broadcast((INetMessage)(object)new PlacementHoldBeginMessage { HoldKey = key, MoverConnectionId = 0 }); } else { _send(1, (INetMessage)(object)new PlacementHoldBeginRequestMessage { HoldKey = key }); } _localSentRotation = ((Component)obj).transform.rotation; PublishRotation(_localSentRotation); return true; } internal void LocalEnded(InteractableObject obj) { if (_localKey == 0L || obj != _localObject) { return; } long localKey = _localKey; _localKey = 0L; _localObject = null; ManualLogSource log = CoopPlugin.Log; string[] obj2 = new string[5] { "[placement-hold] local end key=", localKey.ToString(), " host=", null, null }; bool host = _host; obj2[3] = host.ToString(); obj2[4] = "."; log.LogInfo((object)string.Concat(obj2)); if (_host) { if (_holders.TryGetValue(localKey, out var value) && value == 0) { _holders.Remove(localKey); } _broadcast((INetMessage)(object)new PlacementHoldEndMessage { HoldKey = localKey }); } else { _send(1, (INetMessage)(object)new PlacementHoldEndRequestMessage { HoldKey = localKey }); } } internal void LocalRotated(InteractableObject obj) { //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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (_localKey != 0L && obj == _localObject) { Quaternion rotation = ((Component)obj).transform.rotation; if (!(Quaternion.Angle(rotation, _localSentRotation) < 0.01f)) { _localSentRotation = rotation; CoopPlugin.Log.LogInfo((object)("[placement-hold] local rotate key=" + _localKey + " yaw=" + ((Quaternion)(ref rotation)).eulerAngles.y.ToString("F1") + ".")); PublishRotation(rotation); } } } private void PublishRotation(Quaternion rotation) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_002a: Unknown result type (might be due to invalid IL or missing references) if (_localKey != 0L) { if (_host) { _broadcast((INetMessage)(object)new PlacementHoldRotationMessage { HoldKey = _localKey, Rotation = rotation }); } else { _send(1, (INetMessage)(object)new PlacementHoldRotationRequestMessage { HoldKey = _localKey, Rotation = rotation }); } } } internal void ApplyRotation(PlacementHoldRotationMessage message) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (message != null && _remote.TryGetValue(message.HoldKey, out var value)) { if ((Object)(object)value.Obj != (Object)null) { ((Component)value.Obj).transform.rotation = message.Rotation; } else if ((Object)(object)value.Ghost != (Object)null) { value.Ghost.transform.rotation = message.Rotation; } } } internal bool IsHeldBy(long key, int connectionId) { if (_holders.TryGetValue(key, out var value)) { return value == connectionId; } return false; } internal bool HostGrant(long key, int connectionId, out PlacementHoldBeginMessage granted) { granted = null; if (_holders.TryGetValue(key, out var value) && value != connectionId) { return false; } _holders[key] = connectionId; granted = new PlacementHoldBeginMessage { HoldKey = key, MoverConnectionId = connectionId }; return true; } internal bool HostRelease(long key, int connectionId) { if (!_holders.TryGetValue(key, out var value) || value != connectionId) { return false; } _holders.Remove(key); return true; } internal bool EndIfHeldBy(long key, int connectionId) { if (!_holders.TryGetValue(key, out var value) || value != connectionId) { return false; } ApplyEnd(key); _broadcast((INetMessage)(object)new PlacementHoldEndMessage { HoldKey = key }); return true; } internal void ReleaseMover(int connectionId, List released) { released.Clear(); foreach (KeyValuePair holder in _holders) { if (holder.Value == connectionId) { released.Add(holder.Key); } } for (int i = 0; i < released.Count; i++) { ApplyEnd(released[i]); _broadcast((INetMessage)(object)new PlacementHoldEndMessage { HoldKey = released[i] }); } } internal void ApplyBegin(PlacementHoldBeginMessage message) { //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (message == null) { return; } long holdKey = message.HoldKey; bool flag = IsLocalMover(message.MoverConnectionId); _holders[holdKey] = message.MoverConnectionId; CoopPlugin.Log.LogInfo((object)("[placement-hold] begin key=" + holdKey + " mover=" + message.MoverConnectionId + " local=" + flag + ".")); if (flag) { return; } if (_localKey == holdKey) { CoopPlugin.Log.LogInfo((object)("[placement-hold] losing race for key=" + holdKey + ".")); CancelLocalMove(); } if (_remote.ContainsKey(holdKey)) { return; } if (PlacementHoldKey.IsPendingDecoration(holdKey)) { if (!PlacementHoldKey.TryGetPendingDecorationType(holdKey, out var type)) { CoopPlugin.Log.LogWarning((object)("[placement-hold] pending begin with undecodable decoration type key=" + holdKey + ".")); } else { StartPendingGhost(holdKey, type, message.MoverConnectionId); } return; } InteractableObject val = PlacementHoldKey.Resolve(holdKey); if ((Object)(object)val != (Object)null && !val.GetIsMovingObject()) { StartRemote(val, holdKey, message.MoverConnectionId); return; } CoopPlugin.Log.LogWarning((object)("[placement-hold] could not start remote hold key=" + holdKey + " object=" + ((Object)(object)val != (Object)null) + ".")); } internal void ApplyEnd(long key) { CoopPlugin.Log.LogInfo((object)("[placement-hold] end key=" + key + ".")); _holders.Remove(key); if (_remote.TryGetValue(key, out var value)) { StopRemote(value); _remote.Remove(key); } } internal void Tick() { //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) if (_remote.Count == 0) { return; } ShelfManager val = PlacementInterop.FindShelfManager(); foreach (KeyValuePair item in _remote) { RemoteHold value = item.Value; if ((Object)(object)value.Obj == (Object)null) { if (!((Object)(object)value.Ghost == (Object)null) && PresenceApi.TryGetPeerCameraForHolder(value.MoverConnectionId, out var position, out var rotation)) { Vector3 val2 = position + rotation * Vector3.forward * 3f + rotation * Vector3.up * -0.1f; if (val2.y < 0f) { val2.y = 0f; } value.Ghost.transform.position = val2; if ((Object)(object)value.Material != (Object)null && (Object)(object)val != (Object)null) { value.Material.SetColor(ColorId, val.m_PreviewMeshValidColor); } } continue; } HidePackage(value); if (PresenceApi.TryGetPeerCameraForHolder(value.MoverConnectionId, out var position2, out var rotation2)) { Vector3 val3 = position2 + rotation2 * Vector3.forward * 3f + rotation2 * Vector3.up * -0.1f; if (val3.y < 0f) { val3.y = 0f; } SnappingPosField?.SetValue(value.Obj, false); value.Obj.SetTargetMovePosition(val3, rotation2 * Vector3.up, (Transform)null); if ((Object)(object)value.Material != (Object)null && (Object)(object)val != (Object)null) { value.Material.SetColor(ColorId, PlacementInterop.ReadMoveValidity(value.Obj) ? val.m_PreviewMeshValidColor : val.m_PreviewMeshInvalidColor); } } } } internal void Reset() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair item in _remote) { StopRemote(item.Value); } _remote.Clear(); _holders.Clear(); _localKey = 0L; _localObject = null; _localSentRotation = Quaternion.identity; } private static bool IsLocalMover(int connectionId) { return connectionId == CoopCore.LocalConnectionId; } private void StartRemote(InteractableObject obj, long key, int moverConnectionId) { MovingObjectField?.SetValue(obj, true); int layer = ((Component)obj).gameObject.layer; SetColliders(obj, enabled: false); int num = LayerMask.NameToLayer("Ignore Raycast"); if (num >= 0) { ((Component)obj).gameObject.layer = num; } List list = new List(); Renderer[] componentsInChildren = ((Component)obj).GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null && componentsInChildren[i].enabled) { componentsInChildren[i].enabled = false; list.Add(componentsInChildren[i]); } } ((Component)obj).gameObject.SetActive(true); InteractablePackagingBox_Shelf val = PlacementInterop.FindPackagingBoxFor(obj); if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(false); } ShelfManager manager = PlacementInterop.FindShelfManager(); Material material; GameObject val2 = CreateGhost(obj, manager, out material); CoopPlugin.Log.LogInfo((object)("[placement-hold] remote begin key=" + key + " mover=" + moverConnectionId + " ghost=" + ((Object)(object)val2 != (Object)null) + " pickupMesh=" + ((Object)(object)obj.m_PickupObjectMesh != (Object)null) + " material=" + ((Object)(object)material != (Object)null) + " package=" + ((Object)(object)val != (Object)null) + ".")); RemoteHold remoteHold = new RemoteHold { Obj = obj, Ghost = val2, Material = material, MoverConnectionId = moverConnectionId, OriginalLayer = layer, OriginalShelfMove = obj.m_ShelfMoveStateValidArea, Package = (((Object)(object)val != (Object)null) ? ((Component)val).gameObject : null) }; obj.m_ShelfMoveStateValidArea = null; remoteHold.HiddenRenderers.AddRange(list); _remote[key] = remoteHold; } private unsafe void StartPendingGhost(long key, EDecoObject type, int moverConnectionId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) InteractableObject spawnDecoObjectPrefab = InventoryBase.GetSpawnDecoObjectPrefab(type); if ((Object)(object)spawnDecoObjectPrefab == (Object)null) { CoopPlugin.Log.LogWarning((object)("[placement-hold] pending ghost could not be built type=" + ((object)(*(EDecoObject*)(&type))/*cast due to .constrained prefix*/).ToString() + " (no prefab).")); return; } ShelfManager manager = PlacementInterop.FindShelfManager(); GameObject val = null; Material material = null; InteractableObject val2 = Object.Instantiate(spawnDecoObjectPrefab); try { ((Component)val2).gameObject.SetActive(false); val = CreateGhostMesh(val2, manager, out material, out var anchor); if ((Object)(object)val != (Object)null) { val.transform.position = anchor.position; val.transform.rotation = anchor.rotation; } } finally { Object.Destroy((Object)(object)((Component)val2).gameObject); } if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)("[placement-hold] pending ghost could not be built type=" + ((object)(*(EDecoObject*)(&type))/*cast due to .constrained prefix*/).ToString() + ".")); return; } _remote[key] = new RemoteHold { Obj = null, Ghost = val, Material = material, MoverConnectionId = moverConnectionId }; CoopPlugin.Log.LogInfo((object)("[placement-hold] pending remote begin key=" + key + " type=" + ((object)(*(EDecoObject*)(&type))/*cast due to .constrained prefix*/).ToString() + " mover=" + moverConnectionId + " material=" + ((Object)(object)material != (Object)null) + ".")); } private static void StopRemote(RemoteHold hold) { if ((Object)(object)hold.Obj != (Object)null) { MovingObjectField?.SetValue(hold.Obj, false); SnappingPosField?.SetValue(hold.Obj, false); hold.Obj.m_ShelfMoveStateValidArea = hold.OriginalShelfMove; ((Component)hold.Obj).gameObject.layer = hold.OriginalLayer; SetColliders(hold.Obj, enabled: true); for (int i = 0; i < hold.HiddenRenderers.Count; i++) { if ((Object)(object)hold.HiddenRenderers[i] != (Object)null) { hold.HiddenRenderers[i].enabled = true; } } hold.HiddenRenderers.Clear(); ((Component)hold.Obj).gameObject.SetActive(!hold.Obj.GetIsBoxedUp()); } if ((Object)(object)hold.Obj != (Object)null) { bool isBoxedUp = hold.Obj.GetIsBoxedUp(); InteractablePackagingBox_Shelf packagingBoxShelf = hold.Obj.GetPackagingBoxShelf(); if ((Object)(object)packagingBoxShelf != (Object)null) { ((Component)packagingBoxShelf).gameObject.SetActive(isBoxedUp); } else if ((Object)(object)hold.Package != (Object)null) { hold.Package.SetActive(isBoxedUp); } } if ((Object)(object)hold.Ghost != (Object)null) { Object.Destroy((Object)(object)hold.Ghost); } if ((Object)(object)hold.Material != (Object)null) { Object.Destroy((Object)(object)hold.Material); hold.Material = null; } } private static void HidePackage(RemoteHold hold) { if ((Object)(object)hold.Obj == (Object)null) { return; } InteractablePackagingBox_Shelf val = PlacementInterop.FindPackagingBoxFor(hold.Obj); if (!((Object)(object)val == (Object)null)) { hold.Package = ((Component)val).gameObject; if (((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(false); } } } private static void SetColliders(InteractableObject obj, bool enabled) { if ((Object)(object)obj.m_BoxCollider != (Object)null) { ((Collider)obj.m_BoxCollider).enabled = enabled; } int num = 0; while (obj.m_BoxColliderList != null && num < obj.m_BoxColliderList.Count) { if ((Object)(object)obj.m_BoxColliderList[num] != (Object)null) { ((Collider)obj.m_BoxColliderList[num]).enabled = enabled; } num++; } } private void CancelLocalMove() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) InteractableObject localObject = _localObject; Vector3 localBeforePosition = _localBeforePosition; Quaternion localBeforeRotation = _localBeforeRotation; _localKey = 0L; _localObject = null; if ((Object)(object)localObject == (Object)null) { return; } try { OnPlacedMovedObjectMethod?.Invoke(localObject, null); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("placement hold cancel failed: " + ex.Message)); } if (!((Object)(object)localObject == (Object)null)) { ((Component)localObject).transform.SetPositionAndRotation(localBeforePosition, localBeforeRotation); Rigidbody component = ((Component)localObject).GetComponent(); if ((Object)(object)component != (Object)null) { component.position = localBeforePosition; component.rotation = localBeforeRotation; } } } private static GameObject CreateGhost(InteractableObject obj, ShelfManager manager, out Material material) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) Transform anchor; GameObject val = CreateGhostMesh(obj, manager, out material, out anchor); if ((Object)(object)val == (Object)null) { return null; } val.transform.position = anchor.position; val.transform.rotation = anchor.rotation; val.transform.SetParent(((Component)obj).transform, true); return val; } private static GameObject CreateGhostMesh(InteractableObject source, ShelfManager manager, out Material material, out Transform anchor) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown material = null; MeshFilter val = source.m_PickupObjectMesh; MeshFilter val2 = default(MeshFilter); if (((Object)(object)val == (Object)null || (Object)(object)val.sharedMesh == (Object)null) && ((Component)source).TryGetComponent(ref val2)) { val = val2; } Mesh val3 = (((Object)(object)val != (Object)null) ? val.sharedMesh : null); if ((Object)(object)val3 == (Object)null) { anchor = null; return null; } anchor = (((Object)(object)val != (Object)null) ? ((Component)val).transform : ((Component)source).transform); GameObject val4 = new GameObject("CardShopCoop.PlacementGhost"); val4.AddComponent().sharedMesh = val3; MeshRenderer val5 = val4.AddComponent(); MeshRenderer val6 = (((Object)(object)manager != (Object)null) ? manager.m_MoveObjectPreviewRenderer : null); if ((Object)(object)val6 != (Object)null && (Object)(object)((Renderer)val6).sharedMaterial != (Object)null) { material = new Material(((Renderer)val6).sharedMaterial); ((Renderer)val5).sharedMaterial = material; if (material.HasProperty("_ZWrite")) { material.SetFloat("_ZWrite", 0f); } if (material.HasProperty("_ZTest")) { material.SetFloat("_ZTest", 8f); } material.renderQueue = 4000; } ((Renderer)val5).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val5).receiveShadows = false; ((Renderer)val5).sortingOrder = 32767; val4.transform.localScale = anchor.lossyScale + Vector3.one * 0.001f; return val4; } } [NetworkMessage] public class PlacementHoldBeginMessage : INetMessage { public long HoldKey; public int MoverConnectionId; } [NetworkMessage] public sealed class PlacementHoldBeginRequestMessage : PlacementHoldBeginMessage { } [NetworkMessage] public class PlacementHoldEndMessage : INetMessage { public long HoldKey; } [NetworkMessage] public sealed class PlacementHoldEndRequestMessage : PlacementHoldEndMessage { } [NetworkMessage] public class PlacementHoldRotationMessage : INetMessage { public long HoldKey; public Quaternion Rotation; } [NetworkMessage] public sealed class PlacementHoldRotationRequestMessage : PlacementHoldRotationMessage { } public static class PlacementApi { public const int KindCount = 16; public const int DecorationKind = 5; public const int PlayTableKind = 6; public static bool IsPlacementIdentityReady => WorldClientBehaviour.IsPlacementIdentityReady; public static event Action StructureChanged { add { PlacementInterop.StructureChanged += value; } remove { PlacementInterop.StructureChanged -= value; } } public static IList GetList(ShelfManager shelfManager, int kind) { return PlacementInterop.GetList(shelfManager, kind); } public static bool TryMakeCompartmentKey(int kind, InteractableObject obj, int compartment, out int key) { return PlacementIdentity.TryMakeCompartmentKey(kind, obj, compartment, out key, (Object)(object)WorldHostBehaviour.Active != (Object)null); } public static bool TryMakeObjectKey(int kind, InteractableObject obj, out int key) { return PlacementIdentity.TryMakeObjectKey(kind, obj, out key, (Object)(object)WorldHostBehaviour.Active != (Object)null); } public static bool TryMakeBoxableFurnitureEntityId(InteractableObject obj, long worldEpoch, out string entityId) { return PlacementIdentity.TryMakeBoxableFurnitureEntityId(obj, worldEpoch, out entityId, (Object)(object)WorldHostBehaviour.Active != (Object)null); } public static bool TryResolveBoxableFurnitureEntityId(string entityId, long worldEpoch, EObjectType expectedType, out InteractableObject result, bool requireUnboxed = false) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return PlacementIdentity.TryResolveBoxableFurnitureEntityId(entityId, worldEpoch, expectedType, out result, requireUnboxed); } public static bool TryCreateBoxableFurnitureForIdentity(string entityId, long worldEpoch, EObjectType expectedType, Vector3 position, Quaternion rotation, out InteractableObject created) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) return PlacementIdentity.TryCreateBoxableFurnitureForIdentity(entityId, worldEpoch, expectedType, position, rotation, out created); } public static bool TryResolve(ShelfManager shelfManager, int kind, ushort id, out InteractableObject result) { return PlacementIdentity.TryResolve(shelfManager, kind, id, out result); } public static ushort ObjectIdFromCompartmentKey(int key) { return PlacementIdentity.ObjectIdFromCompartmentKey(key); } public static ushort ObjectIdFromObjectKey(int key) { return PlacementIdentity.ObjectIdFromObjectKey(key); } public static Component ResolveObjectByKey(int key) { return PlacementMoveState.ResolveObjectByKey(key); } } internal static class PlacementInterop { internal const int NoType = int.MinValue; private static readonly FieldInfo FiMoveValid = AccessTools.Field(typeof(InteractableObject), "m_IsMovingObjectValidState"); private static readonly FieldInfo FiTagGroup = AccessTools.Field(typeof(InteractableObject), "m_Shelf_WorldUIGrp"); private static readonly FieldInfo FiPackagingBoxShelf = AccessTools.Field(typeof(InteractableObject), "m_InteractablePackagingBox_Shelf"); private static readonly FieldInfo FiBeingHold = AccessTools.Field(typeof(InteractableObject), "m_IsBeingHold"); private static readonly FieldInfo FiBoxedObject = AccessTools.Field(typeof(InteractablePackagingBox_Shelf), "m_BoxedObject"); private static readonly FieldInfo FiCashScreen = AccessTools.Field(typeof(InteractableCashierCounter), "m_UICashCounterScreen"); private static readonly FieldInfo FiCreditScreen = AccessTools.Field(typeof(InteractableCashierCounter), "m_UICreditCardScreen"); private static readonly MethodInfo MiOpenerSetUi = AccessTools.Method(typeof(InteractableAutoPackOpener), "SetUITransform", (Type[])null, (Type[])null); private static bool _betaProbed; private static Type _storedBoxRecordType; private static Type _packageBoxCandidateType; internal static bool IsRecordWarehouse { get { ProbeBetaSurface(); return _storedBoxRecordType != null; } } internal static event Action StructureChanged; internal static void ProbeBetaSurface() { if (!_betaProbed) { _betaProbed = true; Assembly assembly = typeof(ShelfCompartment).Assembly; _storedBoxRecordType = assembly.GetType("StoredBoxRecord", throwOnError: false); _packageBoxCandidateType = assembly.GetType("PackageBoxCandidate", throwOnError: false); if (_storedBoxRecordType != null) { CoopPlugin.Log.LogDebug((object)"placement: record-backed warehouse symbols detected"); } else if (_packageBoxCandidateType != null) { CoopPlugin.Log.LogDebug((object)"placement: package candidate symbol detected without record type"); } } } internal static IList GetList(ShelfManager shelfManager, int kind) { if ((Object)(object)shelfManager == (Object)null) { return null; } return kind switch { 0 => shelfManager.m_ShelfList, 1 => shelfManager.m_WarehouseShelfList, 2 => shelfManager.m_CardShelfList, 3 => shelfManager.m_CardItemCombiShelfList, 4 => shelfManager.m_CashierCounterList, 5 => null, 6 => shelfManager.m_PlayTableList, 7 => shelfManager.m_WorkbenchList, 8 => shelfManager.m_TrashBinList, 9 => shelfManager.m_CardStorageShelfList, 10 => shelfManager.m_AutoCleanserList, 11 => shelfManager.m_AutoPackOpenerList, 12 => shelfManager.m_EmptyBoxStorageList, 13 => shelfManager.m_BulkDonationBoxList, 14 => shelfManager.m_TournamentPrizeShelfList, 15 => shelfManager.m_InteractableObjectList, _ => null, }; } internal static ShelfManager FindShelfManager() { return SceneRef.Get(); } internal static int TypeIdOf(Component component) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected I4, but got Unknown InteractableObject val = (InteractableObject)(object)((component is InteractableObject) ? component : null); if (val == null) { return int.MinValue; } return (int)val.m_ObjectType; } internal static bool IsBoxed(InteractableObject obj) { if ((Object)(object)obj != (Object)null) { return obj.GetIsBoxedUp(); } return false; } internal static void NotifyStructureChanged(int kind) { try { PlacementInterop.StructureChanged?.Invoke(kind); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("placement structure subscriber failed: " + ex.Message)); } } internal static void SyncTagGroup(Transform objectTransform) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) InteractableObject val = ((objectTransform != null) ? ((Component)objectTransform).GetComponent() : null); if (!((Object)(object)val == (Object)null)) { object? obj = FiTagGroup?.GetValue(val); Transform val2 = (Transform)((obj is Transform) ? obj : null); if (val2 != null) { val2.SetPositionAndRotation(objectTransform.position, objectTransform.rotation); } } } internal static void InvokeOpenerUi(InteractableAutoPackOpener opener) { MiOpenerSetUi?.Invoke(opener, null); } internal static void PlaceBoxedObject(InteractableObject obj, Vector3 position, Quaternion rotation) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)obj == (Object)null) { return; } ((Component)obj).gameObject.SetActive(true); Quaternion val = (obj.m_IsDecorationVertical ? rotation : Quaternion.Euler(0f, ((Quaternion)(ref rotation)).eulerAngles.y, 0f)); ((Component)obj).transform.SetPositionAndRotation(position, val); FiMoveValid?.SetValue(obj, true); obj.PlaceMovedObject(); if (obj.m_IsDecorationVertical) { obj.SetVerticalSnapToWarehouseWall(obj.GetIsVerticalSnapToWarehouseWall(), obj.GetVerticalSnapWallIndex()); } InteractableCashierCounter val2 = (InteractableCashierCounter)(object)((obj is InteractableCashierCounter) ? obj : null); if (val2 != null) { object? obj2 = FiCashScreen?.GetValue(val2); object? obj3 = ((obj2 is Component) ? obj2 : null); if (obj3 != null) { ((Component)obj3).gameObject.SetActive(true); } object? obj4 = FiCreditScreen?.GetValue(val2); object? obj5 = ((obj4 is Component) ? obj4 : null); if (obj5 != null) { ((Component)obj5).gameObject.SetActive(true); } } SyncTagGroup(((Component)obj).transform); DetachPackagingBox(obj); } internal static void BoxUpPlacedObject(InteractableObject obj) { if ((Object)(object)obj != (Object)null && !IsBoxed(obj) && !obj.GetIsMovingObject()) { obj.BoxUpObject(false); } } internal static void DetachPackagingBox(InteractableObject obj) { if ((Object)(object)obj != (Object)null) { FiPackagingBoxShelf?.SetValue(obj, null); } } internal static bool ReadMoveValidity(InteractableObject obj) { bool flag = default(bool); int num; if ((Object)(object)obj != (Object)null) { object obj2 = FiMoveValid?.GetValue(obj); if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static bool IsBeingHeld(InteractableObject obj) { bool flag = default(bool); int num; if ((Object)(object)obj != (Object)null) { object obj2 = FiBeingHold?.GetValue(obj); if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static InteractablePackagingBox_Shelf FindPackagingBoxFor(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return null; } InteractablePackagingBox_Shelf packagingBoxShelf = obj.GetPackagingBoxShelf(); if ((Object)(object)packagingBoxShelf != (Object)null) { return packagingBoxShelf; } List shelfPackagingBoxList = RestockManager.GetShelfPackagingBoxList(); int num = 0; while (shelfPackagingBoxList != null && num < shelfPackagingBoxList.Count) { InteractablePackagingBox_Shelf val = shelfPackagingBoxList[num]; if ((Object)(object)val != (Object)null) { object? obj2 = FiBoxedObject?.GetValue(val); InteractableObject val2 = (InteractableObject)((obj2 is InteractableObject) ? obj2 : null); if (val2 != null && val2 == obj) { return val; } } num++; } return null; } internal static bool IsBoxEngineOwned(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return false; } InteractablePackagingBox_Shelf packagingBoxShelf = obj.GetPackagingBoxShelf(); if (!WorldClientBehaviour.IsKnownPackagingBox((InteractablePackagingBox)(object)packagingBoxShelf)) { return WorldHostBehaviour.IsKnownPackagingBox((InteractablePackagingBox)(object)packagingBoxShelf); } return true; } internal static InteractableObject SpawnBoxedObject(int objectType, Vector3 position, Quaternion rotation) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) InteractableObject val = ShelfManager.SpawnInteractableObject((EObjectType)objectType); if ((Object)(object)val == (Object)null) { return null; } val.Init(); val.BoxUpObject(false); InteractablePackagingBox_Shelf packagingBoxShelf = val.GetPackagingBoxShelf(); if ((Object)(object)packagingBoxShelf != (Object)null) { ((Component)packagingBoxShelf).transform.SetPositionAndRotation(position, rotation); } return val; } internal static InteractableObject SpawnPlacedObject(int objectType, Vector3 position, Quaternion rotation) { //IL_001e: 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) InteractableObject val = ShelfManager.SpawnInteractableObject((EObjectType)objectType); if ((Object)(object)val == (Object)null) { return null; } val.Init(); ((Component)val).transform.SetPositionAndRotation(position, rotation); return val; } internal static int FindKind(InteractableObject obj) { ShelfManager shelfManager = FindShelfManager(); for (int i = 0; i < 16; i++) { IList list = GetList(shelfManager, i); if (list != null && list.Contains(obj)) { return i; } } return -1; } } internal static class PlacementIdentity { private sealed class ReferenceComparer : IEqualityComparer { public bool Equals(InteractableObject left, InteractableObject right) { return left == right; } public int GetHashCode(InteractableObject value) { return RuntimeHelpers.GetHashCode(value); } } internal const ushort Invalid = 0; private static readonly Dictionary ByObject = new Dictionary(new ReferenceComparer()); private static readonly Dictionary ById = new Dictionary(); private static ushort _next = 1; internal static void Reset() { ByObject.Clear(); ById.Clear(); _next = 1; } internal static bool TryGet(InteractableObject obj, out ushort id) { if ((Object)(object)obj != (Object)null && ByObject.TryGetValue(obj, out id) && id != 0) { return true; } id = 0; return false; } internal static bool IsIdentified(InteractableObject obj) { if ((Object)(object)obj != (Object)null && ByObject.TryGetValue(obj, out var value)) { return value != 0; } return false; } internal static ushort AssignHost(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return 0; } if (TryGet(obj, out var id)) { return id; } ushort num; do { num = _next++; if (_next == 0) { _next = 1; } } while (num == 0 || ById.ContainsKey(num)); Bind(obj, num); return num; } internal static void Bind(InteractableObject obj, ushort id) { if (!((Object)(object)obj == (Object)null) && id != 0) { if (ByObject.TryGetValue(obj, out var value) && value != id) { CoopPlugin.Log.LogWarning((object)("[placement] identity moved " + value + "->" + id + " on " + ((Object)obj).name + "; a placement object must keep one identity for its lifetime.")); ById.Remove(value); } if (ById.TryGetValue(id, out var value2) && value2 != obj) { CoopPlugin.Log.LogWarning((object)("[placement] identity " + id + " moved off " + (((Object)(object)value2 == (Object)null) ? "" : ((Object)value2).name) + " onto " + ((Object)obj).name + "; two objects must not share one identity.")); ByObject.Remove(value2); } ByObject[obj] = id; ById[id] = obj; } } internal static void Forget(InteractableObject obj) { if (!((Object)(object)obj == (Object)null) && ByObject.TryGetValue(obj, out var value)) { ByObject.Remove(obj); if (ById.TryGetValue(value, out var value2) && value2 == obj) { ById.Remove(value); } } } internal static bool TryMakeCompartmentKey(int kind, InteractableObject obj, int compartment, out int key, bool host) { key = 0; if (kind == 5 || !TryGetId(kind, obj, out var id, host)) { return false; } key = (kind << 24) | (id << 8) | (compartment & 0xFF); return true; } internal static bool TryMakeObjectKey(int kind, InteractableObject obj, out int key, bool host) { key = 0; if (kind == 5 || !TryGetId(kind, obj, out var id, host)) { return false; } key = (kind << 24) | id; return true; } internal static bool TryMakeBoxableFurnitureEntityId(InteractableObject obj, long worldEpoch, out string entityId, bool host) { entityId = null; if ((Object)(object)obj == (Object)null || worldEpoch <= 0) { return false; } int num = PlacementInterop.FindKind(obj); if (num < 0 || !TryMakeObjectKey(num, obj, out var key, host)) { return false; } entityId = "furniture-placement:" + worldEpoch.ToString(CultureInfo.InvariantCulture) + ":" + key.ToString(CultureInfo.InvariantCulture); return true; } internal static bool TryResolveBoxableFurnitureEntityId(string entityId, long worldEpoch, EObjectType expectedType, out InteractableObject result, bool requireUnboxed) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) result = null; if ((int)expectedType == -1 || !TryParseFurnitureEntityId(entityId, worldEpoch, out var key)) { return false; } int num = key >> 24; if (num < 0 || num >= 16 || num == 5 || ObjectIdFromObjectKey(key) == 0) { return false; } if (!TryResolve(PlacementInterop.FindShelfManager(), num, ObjectIdFromObjectKey(key), out result) || result.m_ObjectType != expectedType || (requireUnboxed && (result.GetIsBoxedUp() || result.GetIsMovingObject()))) { result = null; return false; } return true; } internal static bool TryCreateBoxableFurnitureForIdentity(string entityId, long worldEpoch, EObjectType expectedType, Vector3 position, Quaternion rotation, out InteractableObject created) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected I4, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) created = null; if ((int)expectedType == -1 || !TryParseFurnitureEntityId(entityId, worldEpoch, out var key)) { return false; } int num = key >> 24; ushort num2 = ObjectIdFromObjectKey(key); if (num < 0 || num >= 16 || num == 5 || num2 == 0) { return false; } if (TryResolve(PlacementInterop.FindShelfManager(), num, num2, out created)) { return created.m_ObjectType == expectedType; } created = PlacementInterop.SpawnBoxedObject((int)expectedType, position, rotation); if ((Object)(object)created == (Object)null) { return false; } Bind(created, num2); return true; } internal unsafe static bool TryAdoptBoxableFurnitureIdentity(InteractableObject obj, string entityId, long worldEpoch, EObjectType expectedType) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0039: 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)obj == (Object)null || (int)expectedType == -1 || !TryParseFurnitureEntityId(entityId, worldEpoch, out var key)) { return false; } int num = key >> 24; ushort num2 = ObjectIdFromObjectKey(key); if (num < 0 || num >= 16 || num == 5 || num2 == 0) { return false; } if (obj.m_ObjectType != expectedType) { CoopPlugin.Log.LogWarning((object)("[placement] furniture identity " + num2 + " names " + ((object)(*(EObjectType*)(&expectedType))/*cast due to .constrained prefix*/).ToString() + " but the boxed object is " + ((object)Unsafe.As(ref obj.m_ObjectType)/*cast due to .constrained prefix*/).ToString() + "; not adopting it.")); return false; } if (TryGet(obj, out var id)) { if (id == num2) { return true; } CoopPlugin.Log.LogWarning((object)("[placement] furniture identity " + num2 + " conflicts with the boxed object's own identity " + id + "; the descriptor cannot adopt it.")); return false; } if (ById.TryGetValue(num2, out var value) && (Object)(object)value != (Object)null && value != obj) { CoopPlugin.Log.LogWarning((object)("[placement] furniture identity " + num2 + " is already bound to " + ((Object)value).name + "; the descriptor cannot adopt it onto " + ((Object)obj).name + ".")); return false; } int num3 = PlacementInterop.FindKind(obj); if (num3 != num) { CoopPlugin.Log.LogWarning((object)("[placement] furniture identity " + num2 + " is kind " + num + " but the boxed object " + ((Object)obj).name + " lives in kind " + num3 + "; not adopting it.")); return false; } Bind(obj, num2); CoopPlugin.Log.LogInfo((object)("[placement] adopted authoritative furniture identity " + num2 + " for boxed object " + ((Object)obj).name + ".")); return true; } private static bool TryParseFurnitureEntityId(string entityId, long worldEpoch, out int key) { key = 0; if (string.IsNullOrEmpty(entityId) || worldEpoch <= 0) { return false; } string[] array = entityId.Split(new char[1] { ':' }); if (array.Length == 3 && array[0] == "furniture-placement" && long.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result == worldEpoch) { return int.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out key); } return false; } private static bool TryGetId(int kind, InteractableObject obj, out ushort id, bool host) { id = 0; if ((Object)(object)obj == (Object)null || PlacementInterop.GetList(PlacementInterop.FindShelfManager(), kind) == null) { return false; } if (host) { id = AssignHost(obj); return id != 0; } return TryGet(obj, out id); } internal static bool TryResolve(ShelfManager manager, int kind, ushort id, out InteractableObject result) { result = null; IList list = ((kind == 5) ? null : PlacementInterop.GetList(manager, kind)); if (list == null || id == 0) { return false; } if (ById.TryGetValue(id, out var value) && (Object)(object)value != (Object)null && list.Contains(value)) { result = value; return true; } return false; } internal static ushort ObjectIdFromCompartmentKey(int key) { return (ushort)((key >> 8) & 0xFFFF); } internal static ushort ObjectIdFromObjectKey(int key) { return (ushort)(key & 0xFFFF); } } internal sealed class PlacementPopulationState { internal void Reset() { PlacementIdentity.Reset(); } internal PlacementPopulationMessage BuildMessage(WorldTransferManifest manifest) { ShelfManager val = PlacementInterop.FindShelfManager(); if ((Object)(object)val == (Object)null) { return null; } if (manifest == null) { CoopPlugin.Log.LogWarning((object)"[placement] building a baseline without a frozen save-time manifest; falling back to live list indices. A guest that joined before this baseline may bind the wrong object if its list order differs."); } PlacementPopulationMessage placementPopulationMessage = new PlacementPopulationMessage(); for (int i = 0; i < 16; i++) { placementPopulationMessage.Entries.Add(BuildKind(val, i, manifest)); } return placementPopulationMessage; } internal void CaptureTransferManifest(WorldTransferManifest manifest) { if (manifest == null) { return; } ShelfManager shelfManager = PlacementInterop.FindShelfManager(); for (int i = 0; i < 16; i++) { if (i == 5) { continue; } IList list = PlacementInterop.GetList(shelfManager, i); int num = 0; int num2 = 0; while (list != null && num2 < list.Count) { object? obj = list[num2]; InteractableObject val = (InteractableObject)((obj is InteractableObject) ? obj : null); if (val != null) { manifest.SetPlacementSlot(i, PlacementIdentity.AssignHost(val), num); num++; } num2++; } } } internal bool Apply(PlacementPopulationMessage message) { ShelfManager val = PlacementInterop.FindShelfManager(); for (int i = 0; i < message.Entries.Count; i++) { if (i != 5 && PlacementInterop.GetList(val, i) == null) { return false; } } for (int j = 0; j < message.Entries.Count; j++) { ApplyKind(val, j, message.Entries[j]); } return true; } private static List BuildKind(ShelfManager manager, int kind, WorldTransferManifest manifest) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected I4, but got Unknown //IL_007e: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) IList list = PlacementInterop.GetList(manager, kind); List list2 = new List(); int num = 0; int num2 = 0; while (list != null && num2 < list.Count) { object? obj = list[num2]; InteractableObject val = (InteractableObject)((obj is InteractableObject) ? obj : null); if (val != null) { ushort num3 = PlacementIdentity.AssignHost(val); int slot = -1; if (manifest != null) { if (!manifest.TryGetPlacementSlot(kind, num3, out slot)) { slot = -1; } } else { slot = num; } num++; PlacementPopulationEntry placementPopulationEntry = new PlacementPopulationEntry { Id = num3, Slot = slot, ObjType = (int)val.m_ObjectType, Pos = ((Component)val).transform.position, Rot = ((Component)val).transform.rotation }; if (PlacementInterop.IsBoxed(val) && (Object)(object)val.GetPackagingBoxShelf() != (Object)null) { placementPopulationEntry.IsBoxed = true; placementPopulationEntry.BoxedPos = ((Component)val.GetPackagingBoxShelf()).transform.position; placementPopulationEntry.BoxedRot = ((Component)val.GetPackagingBoxShelf()).transform.rotation; } list2.Add(placementPopulationEntry); } num2++; } return list2; } private static void ApplyKind(ShelfManager manager, int kind, List wanted) { IList list = PlacementInterop.GetList(manager, kind); if (list == null) { return; } for (int i = 0; i < wanted.Count; i++) { PlacementPopulationEntry placementPopulationEntry = wanted[i]; InteractableObject val = ResolveBaselineObject(manager, kind, list, placementPopulationEntry); if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)("[placement] baseline id=" + placementPopulationEntry.Id + " kind=" + kind + " type=" + placementPopulationEntry.ObjType + " boxed=" + placementPopulationEntry.IsBoxed + " did not resolve and could not be recreated.")); } else { ReconcileBaselineObject(val, placementPopulationEntry); } } } private static InteractableObject ResolveBaselineObject(ShelfManager manager, int kind, IList list, PlacementPopulationEntry entry) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 if (PlacementIdentity.TryResolve(manager, kind, entry.Id, out var result)) { return result; } if (entry.Slot >= 0 && entry.Slot < list.Count) { object? obj = list[entry.Slot]; InteractableObject val = (InteractableObject)((obj is InteractableObject) ? obj : null); if (val != null && (int)val.m_ObjectType == entry.ObjType && !PlacementIdentity.IsIdentified(val)) { PlacementIdentity.Bind(val, entry.Id); return val; } } return RecreateBaselineObject(entry); } private static InteractableObject RecreateBaselineObject(PlacementPopulationEntry entry) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_0024: Unknown result type (might be due to invalid IL or missing references) if (entry.ObjType == int.MinValue) { return null; } InteractableObject val = (entry.IsBoxed ? PlacementInterop.SpawnBoxedObject(entry.ObjType, entry.BoxedPos, entry.BoxedRot) : PlacementInterop.SpawnPlacedObject(entry.ObjType, entry.Pos, entry.Rot)); if ((Object)(object)val == (Object)null) { return null; } PlacementIdentity.Bind(val, entry.Id); CoopPlugin.Log.LogInfo((object)("[placement] baseline recreated missing id=" + entry.Id + " type=" + entry.ObjType + " boxed=" + entry.IsBoxed + " object=" + ((Object)val).name + ".")); return val; } private static void ReconcileBaselineObject(InteractableObject obj, PlacementPopulationEntry entry) { //IL_0047: 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_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (PlacementInterop.IsBoxed(obj) != entry.IsBoxed) { if (entry.IsBoxed) { if (!PlacementInterop.IsBoxEngineOwned(obj)) { PlacementInterop.BoxUpPlacedObject(obj); } } else { PlacementInterop.PlaceBoxedObject(obj, entry.Pos, entry.Rot); } } if (!entry.IsBoxed) { ((Component)obj).transform.SetPositionAndRotation(entry.Pos, entry.Rot); PlacementInterop.SyncTagGroup(((Component)obj).transform); } } } internal static class PlacementMoveState { internal static List Build(ShelfManager manager) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: 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_0105: Unknown result type (might be due to invalid IL or missing references) List list = new List(); for (int i = 0; i < 16; i++) { IList list2 = PlacementInterop.GetList(manager, i); int num = 0; while (list2 != null && num < list2.Count) { object? obj = list2[num]; InteractableObject val = (InteractableObject)((obj is InteractableObject) ? obj : null); if (val != null && !val.GetIsMovingObject() && PlacementIdentity.TryMakeObjectKey(i, val, out var key, (Object)(object)WorldHostBehaviour.Active != (Object)null)) { list.Add(new PlacementMoveEntry { Key = key, Type = PlacementInterop.TypeIdOf((Component)(object)val), Pos = ((Component)val).transform.position, Rot = ((Component)val).transform.rotation, IsBoxed = PlacementInterop.IsBoxed(val), BoxedPos = ((PlacementInterop.IsBoxed(val) && (Object)(object)val.GetPackagingBoxShelf() != (Object)null) ? ((Component)val.GetPackagingBoxShelf()).transform.position : Vector3.zero), BoxedRot = ((PlacementInterop.IsBoxed(val) && (Object)(object)val.GetPackagingBoxShelf() != (Object)null) ? ((Component)val.GetPackagingBoxShelf()).transform.rotation : Quaternion.identity) }); } num++; } } return list; } internal static bool Apply(PlacementMoveEntry entry, bool settle) { return PlacementEntityState.Apply(entry, settle); } internal static Component ResolveObjectByKey(int key) { ShelfManager val = PlacementInterop.FindShelfManager(); int num = key >> 24; if (!((Object)(object)val == (Object)null) && num >= 0 && num < 16) { if (!PlacementIdentity.TryResolve(val, num, PlacementIdentity.ObjectIdFromObjectKey(key), out var result)) { return null; } return (Component)(object)result; } return null; } } internal sealed class PlacementEntitySnapshot { internal int Key; internal int Type; internal Vector3 Pos; internal Quaternion Rot; internal bool IsBoxed; internal Vector3 BoxedPos; internal Quaternion BoxedRot; internal PlacementMoveEntry ToEntry() { //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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) return new PlacementMoveEntry { Key = Key, Type = Type, Pos = Pos, Rot = Rot, IsBoxed = IsBoxed, BoxedPos = BoxedPos, BoxedRot = BoxedRot }; } internal bool SameValue(PlacementEntitySnapshot other) { //IL_0012: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_004c: 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_005f: Unknown result type (might be due to invalid IL or missing references) if (other != null && Type == other.Type && Pos == other.Pos && Rot == other.Rot && IsBoxed == other.IsBoxed && BoxedPos == other.BoxedPos) { return BoxedRot == other.BoxedRot; } return false; } } internal static class PlacementEntityState { internal static PlacementEntitySnapshot Capture(InteractableObject obj, int key) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) bool flag = PlacementInterop.IsBoxed(obj); InteractablePackagingBox_Shelf val = ((obj != null) ? obj.GetPackagingBoxShelf() : null); return new PlacementEntitySnapshot { Key = key, Type = PlacementInterop.TypeIdOf((Component)(object)obj), Pos = (((Object)(object)obj == (Object)null) ? Vector3.zero : ((Component)obj).transform.position), Rot = (((Object)(object)obj == (Object)null) ? Quaternion.identity : ((Component)obj).transform.rotation), IsBoxed = flag, BoxedPos = ((flag && (Object)(object)val != (Object)null) ? ((Component)val).transform.position : Vector3.zero), BoxedRot = ((flag && (Object)(object)val != (Object)null) ? ((Component)val).transform.rotation : Quaternion.identity) }; } internal static bool Apply(PlacementMoveEntry entry, bool settle) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (entry == null) { return false; } Component obj = PlacementMoveState.ResolveObjectByKey(entry.Key); InteractableObject val = (InteractableObject)(object)((obj is InteractableObject) ? obj : null); if ((Object)(object)val == (Object)null || (entry.Type != int.MinValue && PlacementInterop.TypeIdOf((Component)(object)val) != entry.Type)) { return false; } if (entry.IsBoxed != PlacementInterop.IsBoxed(val)) { if (entry.IsBoxed) { if (!PlacementInterop.IsBoxEngineOwned(val)) { PlacementInterop.BoxUpPlacedObject(val); } } else { PlacementInterop.PlaceBoxedObject(val, entry.Pos, entry.Rot); } } if (entry.IsBoxed && (Object)(object)val.GetPackagingBoxShelf() != (Object)null) { InteractablePackagingBox_Shelf packagingBoxShelf = val.GetPackagingBoxShelf(); if (!PlacementInterop.IsBeingHeld((InteractableObject)(object)packagingBoxShelf)) { ((Component)packagingBoxShelf).transform.SetPositionAndRotation(entry.BoxedPos, entry.BoxedRot); } } else { ((Component)val).transform.SetPositionAndRotation(entry.Pos, entry.Rot); PlacementInterop.SyncTagGroup(((Component)val).transform); InteractableAutoPackOpener val2 = (InteractableAutoPackOpener)(object)((val is InteractableAutoPackOpener) ? val : null); if (val2 != null) { PlacementInterop.InvokeOpenerUi(val2); } } if (settle && val.GetIsMovingObject()) { val.PlaceMovedObject(); } return true; } internal static bool Apply(PlacementDeltaMessage message) { if (message == null || message.Entity == null) { return false; } if (message.Operation == 3) { Component obj = PlacementMoveState.ResolveObjectByKey(message.Entity.Key); InteractableObject val = (InteractableObject)(object)((obj is InteractableObject) ? obj : null); if ((Object)(object)val != (Object)null) { DestroyRemovedEntity(val); } return true; } if (message.Operation != 1 && message.Operation != 2 && message.Operation != 4) { return false; } PlacementMoveEntry entity = message.Entity; Component obj2 = PlacementMoveState.ResolveObjectByKey(entity.Key); if ((Object)(object)((obj2 is InteractableObject) ? obj2 : null) == (Object)null) { return false; } return Apply(entity, settle: false); } private static void DestroyRemovedEntity(InteractableObject obj) { PlacementIdentity.Forget(obj); obj.OnDestroyed(); } } [NetworkMessage] public sealed class PlacementBaselineMessage : INetMessage { public PlacementPopulationMessage Population = new PlacementPopulationMessage(); public List Moves = new List(); } [NetworkMessage] public sealed class PlacementDeltaMessage : IPredictedMessage, INetMessage { public const byte Add = 1; public const byte Update = 2; public const byte Remove = 3; public const byte Pose = 4; public byte Operation; public PlacementMoveEntry Entity; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PlacementMoveIntentMessage : IPredictedMessage, INetMessage { public PlacementMoveEntry Move; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PlacementBaselineRequestMessage : INetMessage { } [JsonConverter(typeof(PlacementPopulationMessageConverter))] public sealed class PlacementPopulationMessage { public List> Entries = new List>(); } public sealed class PlacementPopulationEntry { public ushort Id; public int Slot; public int ObjType; public Vector3 Pos; public Quaternion Rot; public bool IsBoxed; public Vector3 BoxedPos; public Quaternion BoxedRot; } [JsonConverter(typeof(PlacementMoveEntryConverter))] public sealed class PlacementMoveEntry { public int Key; public int Type; public Vector3 Pos; public Quaternion Rot; public bool IsBoxed; public Vector3 BoxedPos; public Quaternion BoxedRot; } public sealed class PlacementPopulationMessageConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(PlacementPopulationMessage); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) PlacementPopulationMessage placementPopulationMessage = (PlacementPopulationMessage)value; writer.WriteStartArray(); int num = 0; while (placementPopulationMessage?.Entries != null && num < placementPopulationMessage.Entries.Count) { writer.WriteStartArray(); List list = placementPopulationMessage.Entries[num]; if (list != null) { for (int i = 0; i < list.Count; i++) { PlacementPopulationEntry placementPopulationEntry = list[i]; if (placementPopulationEntry != null) { writer.WriteStartObject(); writer.WritePropertyName("Id"); writer.WriteValue(placementPopulationEntry.Id); writer.WritePropertyName("Slot"); writer.WriteValue(placementPopulationEntry.Slot); writer.WritePropertyName("ObjType"); writer.WriteValue(PlacementWire.EnumValue(num, placementPopulationEntry.ObjType)); writer.WritePropertyName("Pos"); serializer.Serialize(writer, (object)placementPopulationEntry.Pos); writer.WritePropertyName("Rot"); serializer.Serialize(writer, (object)placementPopulationEntry.Rot); writer.WritePropertyName("IsBoxed"); writer.WriteValue(placementPopulationEntry.IsBoxed); writer.WritePropertyName("BoxedPos"); serializer.Serialize(writer, (object)placementPopulationEntry.BoxedPos); writer.WritePropertyName("BoxedRot"); serializer.Serialize(writer, (object)placementPopulationEntry.BoxedRot); writer.WriteEndObject(); } } } writer.WriteEndArray(); num++; } writer.WriteEndArray(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { PlacementPopulationMessage placementPopulationMessage = new PlacementPopulationMessage(); JArray val = JArray.Load(reader); int num = 0; while (val != null && num < ((JContainer)val).Count) { List> entries = placementPopulationMessage.Entries; JToken obj = val[num]; entries.Add(ReadEntries((JArray)(object)((obj is JArray) ? obj : null), num, serializer)); num++; } return placementPopulationMessage; } private static List ReadEntries(JArray row, int kind, JsonSerializer serializer) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) List list = new List(); int num = 0; while (row != null && num < ((JContainer)row).Count) { JToken obj = row[num]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val != null && val["Id"] != null) { JToken obj2 = val["ObjType"]; int hostValue = ((obj2 != null) ? Extensions.Value((IEnumerable)obj2) : 0); int objType = CatalogIdMap.FromHostValue(PlacementWire.EnumKindFor(kind), hostValue); PlacementPopulationEntry obj3 = new PlacementPopulationEntry { Id = (ushort)Extensions.Value((IEnumerable)val["Id"]) }; JToken obj4 = val["Slot"]; obj3.Slot = ((obj4 != null) ? Extensions.Value((IEnumerable)obj4) : 0); obj3.ObjType = objType; obj3.Pos = PlacementWire.ReadVector(val, "Pos", serializer); obj3.Rot = PlacementWire.ReadQuaternion(val, "Rot", serializer); JToken obj5 = val["IsBoxed"]; obj3.IsBoxed = obj5 != null && Extensions.Value((IEnumerable)obj5); obj3.BoxedPos = PlacementWire.ReadVector(val, "BoxedPos", serializer); obj3.BoxedRot = PlacementWire.ReadQuaternion(val, "BoxedRot", serializer); list.Add(obj3); } num++; } return list; } } public sealed class PlacementMoveEntryConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(PlacementMoveEntry); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) PlacementMoveEntry placementMoveEntry = (PlacementMoveEntry)value; writer.WriteStartObject(); writer.WritePropertyName("Key"); writer.WriteValue(placementMoveEntry.Key); writer.WritePropertyName("Type"); writer.WriteValue(PlacementWire.EnumValue(placementMoveEntry.Key >> 24, placementMoveEntry.Type)); writer.WritePropertyName("Pos"); serializer.Serialize(writer, (object)placementMoveEntry.Pos); writer.WritePropertyName("Rot"); serializer.Serialize(writer, (object)placementMoveEntry.Rot); writer.WritePropertyName("IsBoxed"); writer.WriteValue(placementMoveEntry.IsBoxed); writer.WritePropertyName("BoxedPos"); serializer.Serialize(writer, (object)placementMoveEntry.BoxedPos); writer.WritePropertyName("BoxedRot"); serializer.Serialize(writer, (object)placementMoveEntry.BoxedRot); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //IL_005c: 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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) JObject val = JObject.Load(reader); int num = Extensions.Value((IEnumerable)val["Key"]); JToken obj = val["Type"]; int hostValue = ((obj != null) ? Extensions.Value((IEnumerable)obj) : 0); int type = CatalogIdMap.FromHostValue(PlacementWire.EnumKindFor(num >> 24), hostValue); PlacementMoveEntry obj2 = new PlacementMoveEntry { Key = num, Type = type, Pos = PlacementWire.ReadVector(val, "Pos", serializer), Rot = PlacementWire.ReadQuaternion(val, "Rot", serializer) }; JToken obj3 = val["IsBoxed"]; obj2.IsBoxed = obj3 != null && Extensions.Value((IEnumerable)obj3); obj2.BoxedPos = PlacementWire.ReadVector(val, "BoxedPos", serializer); obj2.BoxedRot = PlacementWire.ReadQuaternion(val, "BoxedRot", serializer); return obj2; } } internal static class PlacementWire { internal static EnumKind EnumKindFor(int kind) { if (kind != 5) { return EnumKind.ObjectType; } return EnumKind.DecoObject; } internal static int EnumValue(int kind, int value) { return CatalogIdMap.ToHostValue(EnumKindFor(kind), value); } internal static Vector3 ReadVector(JObject value, string name, JsonSerializer serializer) { //IL_0019: 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_0011: Unknown result type (might be due to invalid IL or missing references) if (value[name] != null) { return value[name].ToObject(serializer); } return default(Vector3); } internal static Quaternion ReadQuaternion(JObject value, string name, JsonSerializer serializer) { //IL_0017: 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 (value[name] != null) { return value[name].ToObject(serializer); } return Quaternion.identity; } } internal sealed class WarehouseShelfInteraction { internal sealed class WarehouseTakeCapture { internal ShelfCompartment Compartment; internal int ShelfIndex; internal int CompartmentIndex; internal Guid BoxNetworkId; internal WarehouseBoxState State; internal int RecordCount; internal bool Covered; } internal readonly struct StoredRecordKey : IEquatable { internal readonly EItemType ItemType; internal readonly int Amount; internal readonly bool IsBig; internal StoredRecordKey(EItemType itemType, int amount, bool isBig) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) ItemType = itemType; Amount = amount; IsBig = isBig; } public bool Equals(StoredRecordKey other) { //IL_0001: 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) if (ItemType == other.ItemType && Amount == other.Amount) { return IsBig == other.IsBig; } return false; } public override bool Equals(object obj) { if (obj is StoredRecordKey other) { return Equals(other); } return false; } public override int GetHashCode() { //IL_0001: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected I4, but got Unknown return (((ItemType * 397) ^ Amount) * 397) ^ IsBig; } } private const int MaxWarehouseAmount = 4096; private static bool _probed; private static bool _usesRecords; private static bool _usesLiveBoxes; private static Type _recordType; private static Type _recordItemType; private static MethodInfo _recordCount; private static MethodInfo _recordPeek; private static MethodInfo _recordPop; private static MethodInfo _recordAdd; private static MethodInfo _recordRebuild; private static FieldInfo _recordItem; private static FieldInfo _recordAmount; private static FieldInfo _recordBig; private readonly bool _host; private readonly Action _broadcast; private readonly BoxNetworkInteraction _boxes; private readonly PlayerBoxInteraction _playerBox; private readonly Dictionary> _recordIds = new Dictionary>(); private InteractablePackagingBox_Item _currentStoreBox; private readonly Dictionary> _pendingApplyRecordIds = new Dictionary>(); private WarehouseStateMessage _pendingClientState; private readonly List _pendingClientDeltas = new List(); private bool _hostApplyingCommand; private static readonly Vector3 ParkedWorkerBoxPosition = new Vector3(10000f, 10000f, 10000f); internal bool IsApplyingRemote { get; private set; } internal static bool UsesRecords { get { Probe(); return _usesRecords; } } internal WarehouseShelfInteraction(bool host, Action broadcast, Action send, BoxNetworkInteraction boxes, PlayerBoxInteraction playerBox = null) { _host = host; _broadcast = broadcast ?? throw new ArgumentNullException("broadcast"); if (send == null) { throw new ArgumentNullException("send"); } _boxes = boxes ?? throw new ArgumentNullException("boxes"); _playerBox = playerBox; Probe(); } private void TakeIntoLocalHand(InteractablePackagingBox box) { if ((Object)(object)box == (Object)null) { return; } if (_playerBox != null) { _playerBox.TakeCoveredIntoLocalHand(box); return; } InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { box.StartHoldBox(true, val.m_HoldItemPos); } } private void RemoveListedBox(ShelfCompartment compartment, Guid boxNetworkId, InteractablePackagingBox_Item bound) { List list = ((compartment != null) ? compartment.GetInteractablePackagingBoxList() : null); if (list == null) { return; } for (int num = list.Count - 1; num >= 0; num--) { InteractablePackagingBox_Item val = list[num]; if (!((Object)(object)val == (Object)null) && (val == bound || (_boxes.TryGetId((InteractablePackagingBox)(object)val, out var id) && id == boxNetworkId))) { compartment.RemoveBox(val); } } } internal static bool Available() { Probe(); if (!(_recordType != null)) { return _usesLiveBoxes; } return _usesRecords; } internal void Reset() { IsApplyingRemote = false; _recordIds.Clear(); _currentStoreBox = null; _pendingApplyRecordIds.Clear(); _pendingClientState = null; _pendingClientDeltas.Clear(); _hostApplyingCommand = false; } internal void FlushClientState() { if (_pendingClientState != null) { WarehouseStateMessage pendingClientState = _pendingClientState; _pendingClientState = null; ClientApplyState(pendingClientState); } if (_pendingClientDeltas.Count != 0) { List list = new List(_pendingClientDeltas); _pendingClientDeltas.Clear(); for (int i = 0; i < list.Count; i++) { ClientApplyDelta(list[i]); } } } internal void AppendBaselineMessages(Action append) { if (_host && append != null && Available()) { append((INetMessage)(object)BuildFullState()); } } private void RegisterClientStorePrediction(ShelfCompartment compartment, WarehouseBoxState state) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (state != null && !(state.BoxNetworkId == Guid.Empty) && TryGetAddress(compartment, out var shelfIndex, out var compartmentIndex)) { WarehouseStoreMessage intent = new WarehouseStoreMessage { BoxNetworkId = state.BoxNetworkId, ShelfIndex = shelfIndex, CompartmentIndex = compartmentIndex, ItemType = state.ItemType, Amount = state.Amount, IsBig = state.IsBig }; WarehouseDeltaMessage delta = new WarehouseDeltaMessage { IsStore = true, ShelfIndex = shelfIndex, CompartmentIndex = compartmentIndex, BoxNetworkId = state.BoxNetworkId, ItemType = state.ItemType, Amount = state.Amount, IsBig = state.IsBig }; WorldPrediction.Predict("world.warehouse", intent, delegate { ClientApplyDelta(delta); }, delegate { ClientUndoStore(delta); }); } } internal void BeginLocalRecordStore(InteractablePackagingBox_Item box) { _currentStoreBox = (_usesRecords ? box : null); } internal void EndLocalRecordStore() { _currentStoreBox = null; } private void ClientUndoStore(WarehouseDeltaMessage delta) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) ShelfCompartment val = ResolveCompartment(delta.ShelfIndex, delta.CompartmentIndex); if ((Object)(object)val == (Object)null) { return; } InteractablePackagingBox box; if (_usesRecords) { int num = RecordCount(val); if (num == 0 || GetRecordId(val, num - 1) != delta.BoxNetworkId) { return; } object[] parameters = new object[1]; if (!(bool)_recordPop.Invoke(val, parameters)) { return; } } else if (_boxes.TryGetBox(delta.BoxNetworkId, out box)) { InteractablePackagingBox_Item val2 = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val2 != null) { RemoveListedBox(val, delta.BoxNetworkId, val2); } } if (!_boxes.ClientEnsureWarehouseTake(delta.BoxNetworkId, delta.ItemType, delta.Amount, delta.IsBig, out var box2)) { ManualLogSource log = CoopPlugin.Log; Guid boxNetworkId = delta.BoxNetworkId; log.LogWarning((object)("[warehouse] store rollback could not restore box ID " + boxNetworkId.ToString() + "; skipping the hold.")); } else { TakeIntoLocalHand((InteractablePackagingBox)(object)box2); } } internal WarehouseTakeCapture CaptureClientTake(InteractableStorageCompartment storage) { if (_host || IsApplyingRemote || !Available() || (Object)(object)storage == (Object)null) { return null; } ShelfCompartment shelfCompartment = storage.GetShelfCompartment(); if (!IsWarehouse(shelfCompartment) || !TryGetAddress(shelfCompartment, out var shelfIndex, out var compartmentIndex)) { return null; } if (!TryGetTopBoxNetworkId(shelfCompartment, out var id) || !TryReadTopClientBox(shelfCompartment, id, out var state)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[9] { "[warehouse] take could not resolve the top box on shelf [", shelfIndex.ToString(), ",", compartmentIndex.ToString(), "]; leaving the box in place. top=", null, null, null, null }; InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(shelfCompartment); obj[5] = ((lastBoxOrNull != null) ? ((Object)lastBoxOrNull).name : null) ?? "none"; obj[6] = " localHeld="; obj[7] = (_playerBox?.LocalHeldBoxId ?? Guid.Empty).ToString(); obj[8] = "."; log.LogWarning((object)string.Concat(obj)); return null; } _playerBox?.BeginCoveredHold(); return new WarehouseTakeCapture { Compartment = shelfCompartment, ShelfIndex = shelfIndex, CompartmentIndex = compartmentIndex, BoxNetworkId = id, State = state, RecordCount = RecordCount(shelfCompartment), Covered = true }; } internal void ObserveClientTake(WarehouseTakeCapture capture) { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)capture?.Compartment == (Object)null || !DidClientTakeHappen(capture)) { return; } if (_usesRecords) { InteractablePackagingBox obj = _playerBox?.LocalHeldBox; InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((obj is InteractablePackagingBox_Item) ? obj : null); if (val != null) { _boxes.BindStoredLiveBox(capture.BoxNetworkId, val, capture.State); } else { ManualLogSource log = CoopPlugin.Log; Guid boxNetworkId = capture.BoxNetworkId; log.LogWarning((object)("[warehouse] records take box=" + boxNetworkId.ToString() + " has no live box in hand; later place/throw may not sync.")); } } WarehouseTakeMessage intent = new WarehouseTakeMessage { BoxNetworkId = capture.BoxNetworkId, ShelfIndex = capture.ShelfIndex, CompartmentIndex = capture.CompartmentIndex }; WarehouseDeltaMessage takeDelta = new WarehouseDeltaMessage { ShelfIndex = capture.ShelfIndex, CompartmentIndex = capture.CompartmentIndex, BoxNetworkId = capture.BoxNetworkId, ItemType = capture.State.ItemType, Amount = capture.State.Amount, IsBig = capture.State.IsBig }; WorldPrediction.Predict("world.warehouse", intent, delegate { ClientTakeIntoHand(takeDelta, capture.Compartment); }, delegate { ClientUndoTakePrediction(takeDelta); }); } internal void EndCoveredTake(WarehouseTakeCapture capture) { if (capture != null && capture.Covered) { capture.Covered = false; _playerBox?.EndCoveredHold(); } } private bool DidClientTakeHappen(WarehouseTakeCapture capture) { if (_usesRecords) { return RecordCount(capture.Compartment) < capture.RecordCount; } if (_playerBox != null) { return _playerBox.LocalHeldBoxId == capture.BoxNetworkId; } return false; } private void ClientTakeIntoHand(WarehouseDeltaMessage delta, ShelfCompartment compartment) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)compartment == (Object)null) { ManualLogSource log = CoopPlugin.Log; Guid boxNetworkId = delta.BoxNetworkId; log.LogWarning((object)("[warehouse] take for box=" + boxNetworkId.ToString() + " references a destroyed compartment; skipping.")); return; } if (_usesRecords) { PopClientWarehouseRecord(compartment, delta.BoxNetworkId); } else { if (_boxes.TryGetBox(delta.BoxNetworkId, out var box)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { RemoveListedBox(compartment, delta.BoxNetworkId, val); goto IL_00a9; } } ManualLogSource log2 = CoopPlugin.Log; Guid boxNetworkId = delta.BoxNetworkId; log2.LogWarning((object)("[warehouse] client take box=" + boxNetworkId.ToString() + " had no bound live box; materializing a replacement (the shelf may keep its original, which then fails later takes).")); } goto IL_00a9; IL_00a9: if (!_boxes.ClientEnsureWarehouseTake(delta.BoxNetworkId, delta.ItemType, delta.Amount, delta.IsBig, out var box2)) { ManualLogSource log3 = CoopPlugin.Log; Guid boxNetworkId = delta.BoxNetworkId; log3.LogWarning((object)("[warehouse] take could not materialize box ID " + boxNetworkId.ToString() + "; skipping the hold.")); } else { TakeIntoLocalHand((InteractablePackagingBox)(object)box2); } } internal void ClientTakeOwnConfirmed(WarehouseDeltaMessage message) { ShelfCompartment val = ResolveCompartment(message.ShelfIndex, message.CompartmentIndex); if ((Object)(object)val == (Object)null) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[7] { "[warehouse] own take confirm could not resolve shelf [", message.ShelfIndex.ToString(), ",", message.CompartmentIndex.ToString(), "] box=", null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[5] = boxNetworkId.ToString(); obj[6] = "."; log.LogWarning((object)string.Concat(obj)); } else { ClientTakeIntoHand(message, val); } } private void ClientApplyRemoteTake(WarehouseDeltaMessage message, ShelfCompartment compartment) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) Guid boxNetworkId; if (_usesRecords) { PopClientWarehouseRecord(compartment, message.BoxNetworkId); if (!_boxes.ClientEnsureWarehouseTake(message.BoxNetworkId, message.ItemType, message.Amount, message.IsBig, out var _)) { ManualLogSource log = CoopPlugin.Log; boxNetworkId = message.BoxNetworkId; log.LogWarning((object)("[warehouse] remote take could not materialize box ID " + boxNetworkId.ToString() + ".")); } return; } if (_boxes.TryGetBox(message.BoxNetworkId, out var box2)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box2 is InteractablePackagingBox_Item) ? box2 : null); if (val != null) { RemoveListedBox(compartment, message.BoxNetworkId, val); val.m_IsStored = false; return; } } ManualLogSource log2 = CoopPlugin.Log; boxNetworkId = message.BoxNetworkId; log2.LogWarning((object)("[warehouse] remote take box=" + boxNetworkId.ToString() + " had no bound live box; its shelf entry could not be removed.")); } internal void ApplyWorkerHeldBox(Guid boxNetworkId) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (_host || boxNetworkId == Guid.Empty || !Available() || !_boxes.TryGetBox(boxNetworkId, out var box)) { return; } InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val == null) { return; } ShelfCompartment boxStoredCompartment = val.GetBoxStoredCompartment(); if ((Object)(object)boxStoredCompartment != (Object)null) { List interactablePackagingBoxList = boxStoredCompartment.GetInteractablePackagingBoxList(); if (interactablePackagingBoxList != null && interactablePackagingBoxList.Contains(val)) { boxStoredCompartment.RemoveBox(val); } } val.m_IsStored = false; ((InteractablePackagingBox)val).SetPhysicsEnabled(false); ((Component)val).transform.position = ParkedWorkerBoxPosition; } internal void RestoreWorkerDroppedBox(Guid boxNetworkId) { if (!_host && !(boxNetworkId == Guid.Empty) && Available() && _boxes.TryGetBox(boxNetworkId, out var box)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null && !val.m_IsStored) { ((InteractablePackagingBox)val).SetPhysicsEnabled(true); } } } private void ClientUndoTakePrediction(WarehouseDeltaMessage delta) { if (delta != null && _boxes.TryGetBox(delta.BoxNetworkId, out var box) && !((Object)(object)box == (Object)null) && (_playerBox == null || !_playerBox.HasMirroredHold(box, delta.BoxNetworkId))) { _boxes.IsStored(delta.BoxNetworkId); } } internal void OnStoredBoxRecordAdded(ShelfCompartment compartment) { if ((Object)(object)compartment == (Object)null) { return; } Guid guid = AppendAddedRecordId(compartment); if (_host) { WarehouseBoxState state = null; if (TryReadRecord(compartment, RecordCount(compartment) - 1, out state)) { state.BoxNetworkId = guid; } if (!_hostApplyingCommand) { BroadcastWarehouseDelta(compartment, isStore: true, state); } } else if (!IsApplyingRemote && Available() && IsWarehouse(compartment)) { ClientObserveRecordStore(compartment, guid); } } private void ClientObserveRecordStore(ShelfCompartment compartment, Guid id) { int num = RecordCount(compartment); if (num != 0 && TryReadRecord(compartment, num - 1, out var state, validate: false)) { if (id == Guid.Empty) { CoopPlugin.Log.LogWarning((object)("[warehouse] client record store had no captured box for shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "].")); } else { state.BoxNetworkId = id; _boxes.ClientConvertToStoredRecord(id); RegisterClientStorePrediction(compartment, state); } } } internal WarehouseBoxState CapturePoppedRecord(ShelfCompartment compartment) { if (!_host || !_usesRecords || (Object)(object)compartment == (Object)null) { return null; } int num = RecordCount(compartment); if (num <= 0 || !TryReadRecord(compartment, num - 1, out var state, validate: false)) { return null; } return state; } internal void OnStoredBoxRecordPopped(ShelfCompartment compartment, bool didPop, WarehouseBoxState removed) { if (!didPop || (Object)(object)compartment == (Object)null) { return; } Guid guid = RemovePoppedRecordId(compartment); if (_host && !_hostApplyingCommand && guid != Guid.Empty) { if (!_boxes.PushHostCreatedId(guid)) { ManualLogSource log = CoopPlugin.Log; Guid guid2 = guid; log.LogWarning((object)("[warehouse] popped record id=" + guid2.ToString() + " is not a fresh creator id; the respawned box will mint a new host id.")); } if (removed != null) { removed.BoxNetworkId = guid; BroadcastWarehouseDelta(compartment, isStore: false, removed); } } } internal void OnBoxAdded(ShelfCompartment compartment) { if (!TryGetLastLiveBoxState(compartment, out var state)) { return; } if (!_host && !IsApplyingRemote && Available() && IsWarehouse(compartment)) { InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(compartment); if ((Object)(object)lastBoxOrNull != (Object)null) { _boxes.BindStoredLiveBox(state.BoxNetworkId, lastBoxOrNull, state); } RegisterClientStorePrediction(compartment, state); } if (!_hostApplyingCommand) { BroadcastWarehouseDelta(compartment, isStore: true, state); } } internal void OnBoxRemoved(ShelfCompartment compartment, InteractablePackagingBox_Item box) { //IL_0070: 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) if (!_hostApplyingCommand && _host && !IsApplyingRemote && Available() && IsWarehouse(compartment) && !((Object)(object)box == (Object)null) && _boxes.TryGetId((InteractablePackagingBox)(object)box, out var id) && !(id == Guid.Empty)) { WarehouseBoxState state = new WarehouseBoxState { BoxNetworkId = id, ItemType = (EItemType)(((Object)(object)box.m_ItemCompartment == (Object)null) ? (-1) : ((int)box.m_ItemCompartment.GetItemType())), Amount = ((!((Object)(object)box.m_ItemCompartment == (Object)null)) ? box.m_ItemCompartment.GetItemCount() : 0), IsBig = box.m_IsBigBox }; ManualLogSource log = CoopPlugin.Log; string[] obj = new string[6] { "[warehouse] remove id=", null, null, null, null, null }; Guid guid = id; obj[1] = guid.ToString(); obj[2] = " shelf="; obj[3] = compartment.GetWarehouseIndex().ToString(); obj[4] = ":"; obj[5] = compartment.GetIndex().ToString(); log.LogInfo((object)string.Concat(obj)); BroadcastWarehouseDelta(compartment, isStore: false, state); if ((object)_playerBox?.LocalHeldBox == box) { ManualLogSource log2 = CoopPlugin.Log; guid = id; log2.LogInfo((object)("[warehouse] re-announcing the local take hold for id=" + guid.ToString() + " after the shelf removal.")); _playerBox.PublishHostHeld((InteractablePackagingBox)(object)box, id); } } } internal bool HostApplyStore(WarehouseStoreMessage message, int connectionId, Action response = null) { //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) if (!_host || !Validate(message)) { return false; } bool flag = false; ShelfCompartment val = ResolveCompartment(message.ShelfIndex, message.CompartmentIndex); InteractablePackagingBox box; InteractablePackagingBox_Item val2 = (InteractablePackagingBox_Item)(object)(_boxes.TryGetBox(message.BoxNetworkId, out box) ? ((box is InteractablePackagingBox_Item) ? box : null) : null); if ((Object)(object)val2 == (Object)null) { ManualLogSource log = CoopPlugin.Log; Guid boxNetworkId = message.BoxNetworkId; log.LogWarning((object)("Warehouse store rejected: unknown box ID " + boxNetworkId.ToString() + ".")); } else if ((Object)(object)val2.m_ItemCompartment == (Object)null) { ManualLogSource log2 = CoopPlugin.Log; Guid boxNetworkId = message.BoxNetworkId; log2.LogWarning((object)("Warehouse store rejected: box ID " + boxNetworkId.ToString() + " has no item compartment.")); } else if (val2.m_ItemCompartment.GetItemType() != message.ItemType || val2.m_ItemCompartment.GetItemCount() != message.Amount || val2.m_IsBigBox != message.IsBig) { ManualLogSource log3 = CoopPlugin.Log; Guid boxNetworkId = message.BoxNetworkId; log3.LogWarning((object)("Warehouse store rejected: payload disagrees with box ID " + boxNetworkId.ToString() + ".")); } else if (IsAlreadyShelved(val, val2, message.BoxNetworkId)) { ManualLogSource log4 = CoopPlugin.Log; Guid boxNetworkId = message.BoxNetworkId; log4.LogWarning((object)("Warehouse store rejected: box ID " + boxNetworkId.ToString() + " is already stored.")); } else if (CanStore(val, message.ItemType, message.Amount, message.IsBig)) { try { _hostApplyingCommand = true; if (_usesRecords) { PrepareAppliedRecordId(val, message.BoxNetworkId); flag = AddRecord(val, message.ItemType, message.Amount, message.IsBig); if (flag) { ((InteractableObject)val2).OnDestroyed(); } } else { List interactablePackagingBoxList = val.GetInteractablePackagingBoxList(); flag = (interactablePackagingBoxList != null && interactablePackagingBoxList.Contains(val2)) || StoreLiveBox(val, val2); } } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Warehouse store failed: " + ex)); throw; } finally { _hostApplyingCommand = false; } } else { ManualLogSource log5 = CoopPlugin.Log; string[] obj = new string[13] { "Warehouse store rejected: shelf [", message.ShelfIndex.ToString(), ",", message.CompartmentIndex.ToString(), "] will not take box ", null, null, null, null, null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[5] = boxNetworkId.ToString(); obj[6] = " ("; obj[7] = ((object)Unsafe.As(ref message.ItemType)/*cast due to .constrained prefix*/).ToString(); obj[8] = " x"; obj[9] = message.Amount.ToString(); obj[10] = ", big="; obj[11] = message.IsBig.ToString(); obj[12] = ")."; log5.LogWarning((object)string.Concat(obj)); } if (!flag) { return false; } BroadcastDelta(new WarehouseDeltaMessage { PredictionId = message.PredictionId, IsStore = true, ShelfIndex = message.ShelfIndex, CompartmentIndex = message.CompartmentIndex, BoxNetworkId = message.BoxNetworkId, ItemType = message.ItemType, Amount = message.Amount, IsBig = message.IsBig }); return flag; } internal bool HostApplyTake(WarehouseTakeMessage message, int connectionId, Action response = null) { //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) if (!_host || message == null || message.BoxNetworkId == Guid.Empty) { return false; } WarehouseBoxState warehouseBoxState = new WarehouseBoxState { BoxNetworkId = message.BoxNetworkId }; bool flag = false; ShelfCompartment val = ResolveCompartment(message.ShelfIndex, message.CompartmentIndex); Guid id = Guid.Empty; bool flag2 = (Object)(object)val != (Object)null && TryGetTopBoxNetworkId(val, out id); InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(val); if (flag2 && id == message.BoxNetworkId) { if (!_boxes.PushHostCreatedId(message.BoxNetworkId, (InteractablePackagingBox)(object)lastBoxOrNull)) { ManualLogSource log = CoopPlugin.Log; Guid boxNetworkId = message.BoxNetworkId; log.LogWarning((object)("[warehouse] host take rejected: box id " + boxNetworkId.ToString() + " is not a fresh creator id.")); WarehouseDeltaMessage warehouseDeltaMessage = BuildStoredRefresh(val, message.BoxNetworkId, message.ShelfIndex, message.CompartmentIndex); if (warehouseDeltaMessage != null) { response?.Invoke((INetMessage)(object)warehouseDeltaMessage); } return false; } try { _hostApplyingCommand = true; flag = (_usesRecords ? TryTakeRecord(val, warehouseBoxState) : TryTakeLiveBox(val, warehouseBoxState)); if (flag) { RestockManager.SpawnPackageBoxItem(warehouseBoxState.ItemType, warehouseBoxState.Amount, warehouseBoxState.IsBig); } else { _boxes.CancelHostCreatedId(message.BoxNetworkId); } } catch (Exception ex) { _boxes.CancelHostCreatedId(message.BoxNetworkId); CoopPlugin.Log.LogError((object)("Warehouse take failed: " + ex)); throw; } finally { _hostApplyingCommand = false; } } if (!flag) { ManualLogSource log2 = CoopPlugin.Log; string[] obj = new string[11] { "[warehouse] host take REJECTED pred=", message.PredictionId.ToString(), " box=", null, null, null, null, null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[3] = boxNetworkId.ToString(); obj[4] = " hasTop="; obj[5] = flag2.ToString(); obj[6] = " top="; boxNetworkId = id; obj[7] = boxNetworkId.ToString(); obj[8] = " canPickup="; obj[9] = ((Object)(object)lastBoxOrNull != (Object)null && ((InteractableObject)lastBoxOrNull).CanPickup()).ToString(); obj[10] = "."; log2.LogWarning((object)string.Concat(obj)); WarehouseDeltaMessage warehouseDeltaMessage2 = BuildStoredRefresh(val, message.BoxNetworkId, message.ShelfIndex, message.CompartmentIndex); if (warehouseDeltaMessage2 != null) { response?.Invoke((INetMessage)(object)warehouseDeltaMessage2); } return false; } BroadcastDelta(new WarehouseDeltaMessage { PredictionId = message.PredictionId, IsStore = false, ShelfIndex = message.ShelfIndex, CompartmentIndex = message.CompartmentIndex, BoxNetworkId = warehouseBoxState.BoxNetworkId, ItemType = warehouseBoxState.ItemType, Amount = warehouseBoxState.Amount, IsBig = warehouseBoxState.IsBig }); _playerBox?.AnnounceGrantedHold(warehouseBoxState.BoxNetworkId, connectionId); return flag; } private WarehouseDeltaMessage BuildStoredRefresh(ShelfCompartment compartment, Guid boxNetworkId, int shelfIndex, int compartmentIndex) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)compartment == (Object)null || boxNetworkId == Guid.Empty) { return null; } if (_usesRecords) { int num = RecordCount(compartment); for (int i = 0; i < num; i++) { if (!(GetRecordId(compartment, i) != boxNetworkId) && TryReadRecord(compartment, i, out var state)) { return new WarehouseDeltaMessage { IsStore = true, ShelfIndex = shelfIndex, CompartmentIndex = compartmentIndex, BoxNetworkId = boxNetworkId, ItemType = state.ItemType, Amount = state.Amount, IsBig = state.IsBig }; } } return null; } List interactablePackagingBoxList = compartment.GetInteractablePackagingBoxList(); int num2 = 0; while (interactablePackagingBoxList != null && num2 < interactablePackagingBoxList.Count) { InteractablePackagingBox_Item val = interactablePackagingBoxList[num2]; if (!((Object)(object)val == (Object)null) && _boxes.TryGetId((InteractablePackagingBox)(object)val, out var id) && !(id != boxNetworkId)) { return new WarehouseDeltaMessage { IsStore = true, ShelfIndex = shelfIndex, CompartmentIndex = compartmentIndex, BoxNetworkId = boxNetworkId, ItemType = (EItemType)(((Object)(object)val.m_ItemCompartment == (Object)null) ? (-1) : ((int)val.m_ItemCompartment.GetItemType())), Amount = ((!((Object)(object)val.m_ItemCompartment == (Object)null)) ? val.m_ItemCompartment.GetItemCount() : 0), IsBig = val.m_IsBigBox }; } num2++; } return null; } internal void ClientApplyState(WarehouseStateMessage message) { if (message == null) { throw new InvalidOperationException("Authoritative warehouse state is missing."); } if (!Available()) { _pendingClientState = message; return; } IsApplyingRemote = true; try { for (int i = 0; i < message.Compartments.Count; i++) { WarehouseCompartmentState warehouseCompartmentState = message.Compartments[i]; if ((Object)(object)ResolveCompartment(warehouseCompartmentState.ShelfIndex, warehouseCompartmentState.CompartmentIndex) == (Object)null) { _pendingClientState = message; return; } } for (int j = 0; j < message.Compartments.Count; j++) { WarehouseCompartmentState warehouseCompartmentState2 = message.Compartments[j]; ShelfCompartment compartment = ResolveCompartment(warehouseCompartmentState2.ShelfIndex, warehouseCompartmentState2.CompartmentIndex); ApplyState(compartment, warehouseCompartmentState2.Boxes); } } finally { IsApplyingRemote = false; } } internal void ClientApplyDelta(WarehouseDeltaMessage message) { if (message != null) { ShelfCompartment val = ResolveCompartment(message.ShelfIndex, message.CompartmentIndex); if ((Object)(object)val == (Object)null) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[9] { "[warehouse] deferring delta for unresolved compartment [", message.ShelfIndex.ToString(), ",", message.CompartmentIndex.ToString(), "] box=", null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[5] = boxNetworkId.ToString(); obj[6] = " isStore="; obj[7] = message.IsStore.ToString(); obj[8] = "."; log.LogWarning((object)string.Concat(obj)); _pendingClientDeltas.Add(message); } else if (message.IsStore) { ClientApplyStoreDelta(message, val); } else { ClientApplyRemoteTake(message, val); } } } private void ClientApplyStoreDelta(WarehouseDeltaMessage message, ShelfCompartment compartment) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) IsApplyingRemote = true; try { if (_usesRecords) { if (!HasRecordId(compartment, message.BoxNetworkId)) { if (_boxes.TryGetBox(message.BoxNetworkId, out var box)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { _playerBox?.DetachMirroredHold((InteractablePackagingBox)(object)val, message.BoxNetworkId); _playerBox?.ReleaseHeldObject((InteractablePackagingBox)(object)val); } } PrepareAppliedRecordId(compartment, message.BoxNetworkId); AddRecord(compartment, message.ItemType, message.Amount, message.IsBig); _boxes.ClientConvertToStoredRecord(message.BoxNetworkId); } } else { if (_boxes.TryGetBox(message.BoxNetworkId, out var box2)) { InteractablePackagingBox_Item val2 = (InteractablePackagingBox_Item)(object)((box2 is InteractablePackagingBox_Item) ? box2 : null); if (val2 != null) { List interactablePackagingBoxList = compartment.GetInteractablePackagingBoxList(); if (interactablePackagingBoxList == null || !interactablePackagingBoxList.Contains(val2)) { _playerBox?.DetachMirroredHold((InteractablePackagingBox)(object)val2, message.BoxNetworkId); _playerBox?.ReleaseHeldObject((InteractablePackagingBox)(object)val2); RemoveListedBox(compartment, message.BoxNetworkId, val2); if (!StoreLiveBox(compartment, val2) && StoreLiveBox(compartment, message.ItemType, message.Amount, message.IsBig, out var stored)) { val2 = stored; } } _boxes.BindStoredLiveBox(message.BoxNetworkId, val2, new WarehouseBoxState { BoxNetworkId = message.BoxNetworkId, ItemType = message.ItemType, Amount = message.Amount, IsBig = message.IsBig }); goto IL_01c0; } } ManualLogSource log = CoopPlugin.Log; Guid boxNetworkId = message.BoxNetworkId; log.LogWarning((object)("[warehouse] client store box=" + boxNetworkId.ToString() + " had no bound live box; it could not be placed on the shelf.")); } goto IL_01c0; IL_01c0: _playerBox?.ReleaseRemoteHoldForBox(message.BoxNetworkId); } finally { IsApplyingRemote = false; } } private void PopClientWarehouseRecord(ShelfCompartment compartment, Guid boxNetworkId) { if ((Object)(object)compartment == (Object)null) { return; } IsApplyingRemote = true; try { int num = RecordCount(compartment); if (num != 0 && !(GetRecordId(compartment, num - 1) != boxNetworkId)) { object[] parameters = new object[1]; if (!(bool)_recordPop.Invoke(compartment, parameters)) { throw new InvalidOperationException("Warehouse record take could not be applied."); } } } finally { IsApplyingRemote = false; } } private WarehouseStateMessage BuildFullState() { WarehouseStateMessage warehouseStateMessage = new WarehouseStateMessage(); List warehouseCompartments = GetWarehouseCompartments(); for (int i = 0; i < warehouseCompartments.Count; i++) { warehouseStateMessage.Compartments.Add(BuildState(warehouseCompartments[i])); } return warehouseStateMessage; } private void BroadcastDelta(WarehouseDeltaMessage message) { if (_host && message != null) { _broadcast((INetMessage)(object)message); } } private void BroadcastWarehouseDelta(ShelfCompartment compartment, bool isStore, WarehouseBoxState state) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (_host && !IsApplyingRemote && Available() && IsWarehouse(compartment) && state != null && !(state.BoxNetworkId == Guid.Empty)) { BroadcastDelta(new WarehouseDeltaMessage { IsStore = isStore, ShelfIndex = compartment.GetWarehouseIndex(), CompartmentIndex = compartment.GetIndex(), BoxNetworkId = state.BoxNetworkId, ItemType = state.ItemType, Amount = state.Amount, IsBig = state.IsBig }); } } private bool TryGetLastLiveBoxState(ShelfCompartment compartment, out WarehouseBoxState state) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) state = null; InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(compartment); if ((Object)(object)lastBoxOrNull?.m_ItemCompartment == (Object)null || !_boxes.TryGetId((InteractablePackagingBox)(object)lastBoxOrNull, out var id)) { return false; } state = new WarehouseBoxState { BoxNetworkId = id, ItemType = lastBoxOrNull.m_ItemCompartment.GetItemType(), Amount = lastBoxOrNull.m_ItemCompartment.GetItemCount(), IsBig = lastBoxOrNull.m_IsBigBox }; return true; } private WarehouseCompartmentState BuildState(ShelfCompartment compartment) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) WarehouseCompartmentState warehouseCompartmentState = new WarehouseCompartmentState { StableEntityId = WorldMessageMetadata.WarehouseEntityId(compartment.GetWarehouseIndex(), compartment.GetIndex()), ShelfIndex = compartment.GetWarehouseIndex(), CompartmentIndex = compartment.GetIndex() }; if (_usesRecords) { int num = RecordCount(compartment); for (int i = 0; i < num; i++) { if (TryReadRecord(compartment, i, out var state)) { state.BoxNetworkId = GetRecordId(compartment, i); warehouseCompartmentState.Boxes.Add(state); } } } else { List interactablePackagingBoxList = compartment.GetInteractablePackagingBoxList(); if (interactablePackagingBoxList != null) { for (int j = 0; j < interactablePackagingBoxList.Count; j++) { InteractablePackagingBox_Item val = interactablePackagingBoxList[j]; if (!((Object)(object)val?.m_ItemCompartment == (Object)null)) { int itemCount = val.m_ItemCompartment.GetItemCount(); if (itemCount > 0) { WarehouseBoxState item = new WarehouseBoxState { BoxNetworkId = _boxes.EnsureHostId((InteractablePackagingBox)(object)val), ItemType = val.m_ItemCompartment.GetItemType(), Amount = itemCount, IsBig = val.m_IsBigBox }; warehouseCompartmentState.Boxes.Add(item); } } } } } return warehouseCompartmentState; } private void ApplyState(ShelfCompartment compartment, List wanted) { //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (_usesRecords) { while (RecordCount(compartment) > 0) { object[] parameters = new object[1]; if (!(bool)_recordPop.Invoke(compartment, parameters)) { throw new InvalidOperationException("Warehouse record storage could not be cleared."); } } for (int i = 0; i < wanted.Count; i++) { WarehouseBoxState warehouseBoxState = wanted[i]; PrepareAppliedRecordId(compartment, warehouseBoxState.BoxNetworkId); AddRecord(compartment, warehouseBoxState.ItemType, warehouseBoxState.Amount, warehouseBoxState.IsBig); } _recordRebuild?.Invoke(null, new object[1] { compartment }); for (int j = 0; j < wanted.Count; j++) { _boxes.ClientConvertToStoredRecord(wanted[j].BoxNetworkId); } return; } List interactablePackagingBoxList = compartment.GetInteractablePackagingBoxList(); if (interactablePackagingBoxList != null) { List list = new List(interactablePackagingBoxList); for (int k = 0; k < list.Count; k++) { if (_boxes.TryGetId((InteractablePackagingBox)(object)list[k], out var id)) { _boxes.ClientForgetPhysical(id, (InteractablePackagingBox)(object)list[k]); } else if ((Object)(object)list[k] != (Object)null) { _boxes.DestroyWithoutNotification((InteractablePackagingBox)(object)list[k]); } if (interactablePackagingBoxList.Contains(list[k])) { compartment.RemoveBox(list[k]); } } } for (int l = 0; l < wanted.Count; l++) { WarehouseBoxState warehouseBoxState2 = wanted[l]; if (StoreLiveBox(compartment, warehouseBoxState2.ItemType, warehouseBoxState2.Amount, warehouseBoxState2.IsBig, out var stored)) { _boxes.BindStoredLiveBox(warehouseBoxState2.BoxNetworkId, stored, warehouseBoxState2); } } } private unsafe bool StoreLiveBox(ShelfCompartment compartment, EItemType type, int amount, bool isBig, out InteractablePackagingBox_Item stored) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) stored = _boxes.MaterializeWarehouseStored(type, amount, isBig); if ((Object)(object)stored == (Object)null) { CoopPlugin.Log.LogWarning((object)("Could not materialize an authoritative warehouse box (type=" + ((object)(*(EItemType*)(&type))/*cast due to .constrained prefix*/).ToString() + " amount=" + amount + "); skipping the store.")); return false; } ((InteractablePackagingBox)stored).SetPhysicsEnabled(false); stored.DispenseItem(false, compartment); if (!stored.m_IsStored) { _boxes.DestroyWithoutNotification((InteractablePackagingBox)(object)stored); stored = null; CoopPlugin.Log.LogWarning((object)"Materialized warehouse box was not stored; skipping."); return false; } return true; } private static bool StoreLiveBox(ShelfCompartment compartment, InteractablePackagingBox_Item box) { if ((Object)(object)box == (Object)null) { return false; } ((InteractableObject)box).StopLerpToTransform(); ((InteractablePackagingBox)box).SetPhysicsEnabled(false); box.DispenseItem(false, compartment); return box.m_IsStored; } private bool IsAlreadyShelved(ShelfCompartment target, InteractablePackagingBox_Item box, Guid id) { if ((Object)(object)box == (Object)null || (!box.m_IsStored && !_boxes.IsStored(id))) { return false; } List list = ((target != null) ? target.GetInteractablePackagingBoxList() : null); if (list != null) { return !list.Contains(box); } return true; } private bool TryTakeLiveBox(ShelfCompartment compartment, WarehouseBoxState result) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(compartment); if ((Object)(object)lastBoxOrNull?.m_ItemCompartment == (Object)null) { return false; } if (!((InteractableObject)lastBoxOrNull).CanPickup()) { ((InteractableObject)lastBoxOrNull).StopLerpToTransform(); } int itemCount = lastBoxOrNull.m_ItemCompartment.GetItemCount(); if (itemCount <= 0) { return false; } result.ItemType = lastBoxOrNull.m_ItemCompartment.GetItemType(); result.Amount = itemCount; result.IsBig = lastBoxOrNull.m_IsBigBox; if (!_boxes.TryGetId((InteractablePackagingBox)(object)lastBoxOrNull, out var id)) { return false; } result.BoxNetworkId = id; ManualLogSource log = CoopPlugin.Log; string[] obj = new string[7] { "[warehouse] host live take destroying stored box id=", null, null, null, null, null, null }; Guid guid = id; obj[1] = guid.ToString(); obj[2] = " name="; obj[3] = ((Object)lastBoxOrNull).name; obj[4] = " amount="; obj[5] = itemCount.ToString(); obj[6] = "."; log.LogInfo((object)string.Concat(obj)); _boxes.DetachForWarehouseTake(id); compartment.RemoveBox(lastBoxOrNull); _boxes.DestroyWithoutNotification((InteractablePackagingBox)(object)lastBoxOrNull); return true; } private static bool AddRecord(ShelfCompartment compartment, EItemType type, int amount, bool isBig) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected I4, but got Unknown object obj = Activator.CreateInstance(_recordType); _recordItem.SetValue(obj, Enum.ToObject(_recordItemType, (int)type)); _recordAmount.SetValue(obj, amount); _recordBig.SetValue(obj, isBig); _recordAdd.Invoke(compartment, new object[1] { obj }); return true; } private bool TryTakeRecord(ShelfCompartment compartment, WarehouseBoxState result) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) int num = RecordCount(compartment); if (num == 0 || !TryReadRecord(compartment, num - 1, out var state)) { return false; } Guid recordId = GetRecordId(compartment, num - 1); if (recordId == Guid.Empty) { return false; } object[] parameters = new object[1]; if (!(bool)_recordPop.Invoke(compartment, parameters)) { return false; } result.ItemType = state.ItemType; result.Amount = state.Amount; result.IsBig = state.IsBig; result.BoxNetworkId = recordId; return true; } private static int RecordCount(ShelfCompartment compartment) { if (!(_recordCount == null)) { return (int)_recordCount.Invoke(compartment, null); } return 0; } private static bool TryReadRecord(ShelfCompartment compartment, int index, out WarehouseBoxState state, bool validate = true) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) state = null; object obj = _recordPeek.Invoke(compartment, new object[1] { index }); if (obj == null) { return false; } int amount = (int)_recordAmount.GetValue(obj); EItemType val = (EItemType)Convert.ToInt32(_recordItem.GetValue(obj)); if (validate && !IsValidBox(val, amount)) { return false; } state = new WarehouseBoxState { ItemType = val, Amount = amount, IsBig = (bool)_recordBig.GetValue(obj) }; return true; } private List RecordIdsFor(ShelfCompartment compartment) { long key = CompartmentKey(compartment); if (!_recordIds.TryGetValue(key, out var value)) { value = (_recordIds[key] = new List()); } return value; } private void EnsureRecordIds(ShelfCompartment compartment, int index) { if (_host) { List list = RecordIdsFor(compartment); while (list.Count <= index) { list.Add(Guid.NewGuid()); } } } private bool HasRecordId(ShelfCompartment compartment, Guid id) { if (id == Guid.Empty || !_recordIds.TryGetValue(CompartmentKey(compartment), out var value)) { return false; } for (int i = 0; i < value.Count; i++) { if (value[i] == id) { return true; } } return false; } private Guid GetRecordId(ShelfCompartment compartment, int index) { if (index < 0) { return Guid.Empty; } List list = RecordIdsFor(compartment); if (list.Count <= index) { if (!_host) { return Guid.Empty; } EnsureRecordIds(compartment, index); } return list[index]; } private Guid AppendAddedRecordId(ShelfCompartment compartment) { int num = RecordCount(compartment) - 1; EnsureRecordIds(compartment, num - 1); List list = RecordIdsFor(compartment); while (list.Count < num) { list.Add(Guid.Empty); } Guid guid = TakePreparedRecordId(compartment); if (guid == Guid.Empty) { if (_host) { guid = Guid.NewGuid(); } else { CoopPlugin.Log.LogWarning((object)("[warehouse] client stored record had no captured id on shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "]; a placeholder keeps the mirror aligned until the host baseline arrives.")); } } if (list.Count > num) { CoopPlugin.Log.LogError((object)("[warehouse] record id mirror longer than game list on add: ids=" + list.Count + " game=" + RecordCount(compartment) + " shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "].")); list.RemoveRange(num, list.Count - num); } list.Add(guid); VerifyRecordMirror(compartment, "add"); return guid; } private Guid RemovePoppedRecordId(ShelfCompartment compartment) { int num = RecordCount(compartment); EnsureRecordIds(compartment, num); List list = RecordIdsFor(compartment); while (list.Count <= num) { list.Add(Guid.Empty); } Guid result = list[num]; list.RemoveAt(num); VerifyRecordMirror(compartment, "pop"); return result; } private void PrepareAppliedRecordId(ShelfCompartment compartment, Guid id) { if (id == Guid.Empty) { throw new InvalidOperationException("Authoritative warehouse store requires a box network ID."); } long key = CompartmentKey(compartment); if (!_pendingApplyRecordIds.TryGetValue(key, out var value)) { value = (_pendingApplyRecordIds[key] = new Queue()); } value.Enqueue(id); } private Guid TakePendingApplyRecordId(ShelfCompartment compartment) { long key = CompartmentKey(compartment); if (!_pendingApplyRecordIds.TryGetValue(key, out var value) || value.Count == 0) { return Guid.Empty; } Guid result = value.Dequeue(); if (value.Count == 0) { _pendingApplyRecordIds.Remove(key); } return result; } private Guid TakePreparedRecordId(ShelfCompartment compartment) { if (IsApplyingRemote || _hostApplyingCommand) { return TakePendingApplyRecordId(compartment); } InteractablePackagingBox_Item currentStoreBox = _currentStoreBox; if ((Object)(object)currentStoreBox != (Object)null && (Object)(object)currentStoreBox.GetBoxStoredCompartment() == (Object)(object)compartment && _boxes.TryGetId((InteractablePackagingBox)(object)currentStoreBox, out var id) && id != Guid.Empty) { return id; } return Guid.Empty; } private void VerifyRecordMirror(ShelfCompartment compartment, string context) { int num = RecordCount(compartment); List value; int num2 = (_recordIds.TryGetValue(CompartmentKey(compartment), out value) ? value.Count : 0); if (num2 != num) { CoopPlugin.Log.LogError((object)("[warehouse] record id mirror drifted on " + context + ": ids=" + num2 + " game=" + num + " shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "].")); } } internal List CaptureRecordOrder(ShelfCompartment compartment) { if (!_usesRecords || (Object)(object)compartment == (Object)null) { return null; } int num = RecordCount(compartment); if (num <= 1) { return null; } List list = new List(num); for (int i = 0; i < num; i++) { list.Add(ReadRecordKey(compartment, i)); } return list; } internal void ApplyRecordOrder(ShelfCompartment compartment, List before) { if (!_usesRecords || (Object)(object)compartment == (Object)null || before == null || before.Count <= 1) { return; } int num = RecordCount(compartment); long key = CompartmentKey(compartment); if (!_recordIds.TryGetValue(key, out var value)) { CoopPlugin.Log.LogError((object)("[warehouse] record reorder had no id mirror for shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "].")); return; } if (num != before.Count || value.Count != before.Count) { CoopPlugin.Log.LogError((object)("[warehouse] record id mirror drifted on reorder: after=" + num + " before=" + before.Count + " ids=" + value.Count + " on shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "].")); return; } Dictionary> dictionary = new Dictionary>(before.Count); for (int i = 0; i < before.Count; i++) { StoredRecordKey key2 = before[i]; if (!dictionary.TryGetValue(key2, out var value2)) { value2 = (dictionary[key2] = new Queue()); } value2.Enqueue(i); } List list = new List(num); for (int j = 0; j < num; j++) { StoredRecordKey key3 = ReadRecordKey(compartment, j); if (!dictionary.TryGetValue(key3, out var value3) || value3.Count == 0) { CoopPlugin.Log.LogError((object)("[warehouse] record reorder found no before image for after index " + j + " on shelf [" + compartment.GetWarehouseIndex() + "," + compartment.GetIndex() + "]; leaving ids unchanged.")); return; } list.Add(value[value3.Dequeue()]); } _recordIds[key] = list; } private static StoredRecordKey ReadRecordKey(ShelfCompartment compartment, int index) { object obj = _recordPeek.Invoke(compartment, new object[1] { index }); return new StoredRecordKey((EItemType)Convert.ToInt32(_recordItem.GetValue(obj)), (int)_recordAmount.GetValue(obj), (bool)_recordBig.GetValue(obj)); } private static InteractablePackagingBox_Item GetLastBoxOrNull(ShelfCompartment compartment) { if ((Object)(object)compartment == (Object)null) { return null; } List interactablePackagingBoxList = compartment.GetInteractablePackagingBoxList(); if (interactablePackagingBoxList == null || interactablePackagingBoxList.Count == 0) { return null; } return interactablePackagingBoxList[interactablePackagingBoxList.Count - 1]; } private bool TryGetTopBoxNetworkId(ShelfCompartment compartment, out Guid id) { id = Guid.Empty; if ((Object)(object)compartment == (Object)null) { return false; } if (_usesRecords) { int num = RecordCount(compartment); if (num > 0 && TryReadRecord(compartment, num - 1, out var _, _host)) { return (id = GetRecordId(compartment, num - 1)) != Guid.Empty; } return false; } InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(compartment); if ((Object)(object)lastBoxOrNull != (Object)null) { return _boxes.TryGetId((InteractablePackagingBox)(object)lastBoxOrNull, out id); } return false; } private bool TryReadTopClientBox(ShelfCompartment compartment, Guid expectedId, out WarehouseBoxState state) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) state = null; if ((Object)(object)compartment == (Object)null) { return false; } if (_usesRecords) { int num = RecordCount(compartment); if (num > 0 && TryReadRecord(compartment, num - 1, out state, validate: false)) { return GetRecordId(compartment, num - 1) == expectedId; } return false; } InteractablePackagingBox_Item lastBoxOrNull = GetLastBoxOrNull(compartment); if ((Object)(object)lastBoxOrNull?.m_ItemCompartment == (Object)null || !_boxes.TryGetId((InteractablePackagingBox)(object)lastBoxOrNull, out var id) || id != expectedId) { return false; } state = new WarehouseBoxState { BoxNetworkId = id, ItemType = lastBoxOrNull.m_ItemCompartment.GetItemType(), Amount = lastBoxOrNull.m_ItemCompartment.GetItemCount(), IsBig = lastBoxOrNull.m_IsBigBox }; return true; } private static long CompartmentKey(ShelfCompartment compartment) { return ((long)compartment.GetWarehouseIndex() << 32) | (uint)compartment.GetIndex(); } private static List GetWarehouseCompartments() { List list = new List(); List list2 = FindShelfManager()?.m_WarehouseShelfList; if (list2 == null) { return list; } for (int i = 0; i < list2.Count; i++) { WarehouseShelf obj = list2[i]; List list3 = ((obj != null) ? obj.GetStorageCompartmentList() : null); if (list3 == null) { continue; } for (int j = 0; j < list3.Count; j++) { InteractableStorageCompartment obj2 = list3[j]; ShelfCompartment val = ((obj2 != null) ? obj2.GetShelfCompartment() : null); if ((Object)(object)val != (Object)null) { list.Add(val); } } } return list; } private static ShelfManager FindShelfManager() { return SceneRef.Get(); } private static ShelfCompartment ResolveCompartment(int shelfIndex, int compartmentIndex) { List warehouseCompartments = GetWarehouseCompartments(); for (int i = 0; i < warehouseCompartments.Count; i++) { ShelfCompartment val = warehouseCompartments[i]; if (val.GetWarehouseIndex() == shelfIndex && val.GetIndex() == compartmentIndex) { return val; } } return null; } private static bool TryGetAddress(ShelfCompartment compartment, out int shelfIndex, out int compartmentIndex) { shelfIndex = 0; compartmentIndex = 0; if (!IsWarehouse(compartment)) { return false; } shelfIndex = compartment.GetWarehouseIndex(); compartmentIndex = compartment.GetIndex(); return true; } private static bool IsWarehouse(ShelfCompartment compartment) { if ((Object)(object)compartment != (Object)null) { return (Object)(object)compartment.GetWarehouseShelf() != (Object)null; } return false; } private static bool WouldVanillaStore(ShelfCompartment compartment, EItemType itemType, int amount, bool isBig) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) if ((int)itemType != -1 && amount > 0 && (Object)(object)compartment != (Object)null && compartment.m_CanPutBox && compartment.CheckBoxType(isBig) == isBig && compartment.HasEnoughSlot()) { return compartment.CheckBoxItemType(itemType); } return false; } private static bool CanStore(ShelfCompartment compartment, EItemType itemType, int amount, bool isBig) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (WouldVanillaStore(compartment, itemType, amount, isBig) && IsWarehouse(compartment)) { return amount <= 4096; } return false; } private static bool IsValidBox(WarehouseBoxState state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (state != null && state.BoxNetworkId != Guid.Empty) { return IsValidBox(state.ItemType, state.Amount); } return false; } private static bool IsValidBox(EItemType type, int amount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)type != -1 && amount > 0) { return amount <= 4096; } return false; } private bool Validate(WarehouseStoreMessage message) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (message != null && message.BoxNetworkId != Guid.Empty && message.ShelfIndex >= 0 && message.CompartmentIndex >= 0) { return IsValidBox(message.ItemType, message.Amount); } return false; } private static void Probe() { if (!_probed) { _probed = true; Assembly assembly = typeof(ShelfCompartment).Assembly; _recordType = assembly.GetType("StoredBoxRecord", throwOnError: false); _recordCount = AccessTools.Method(typeof(ShelfCompartment), "GetStoredBoxRecordCount", (Type[])null, (Type[])null); _recordPeek = AccessTools.Method(typeof(ShelfCompartment), "PeekStoredBoxRecord", (Type[])null, (Type[])null); _recordPop = AccessTools.Method(typeof(ShelfCompartment), "TryPopLastStoredBoxRecord", (Type[])null, (Type[])null); _recordAdd = AccessTools.Method(typeof(ShelfCompartment), "AddStoredBoxRecord", (Type[])null, (Type[])null); Type type = assembly.GetType("StoredBoxVisualBatcher", throwOnError: false); _recordRebuild = ((type == null) ? null : AccessTools.Method(type, "RebuildImmediate", (Type[])null, (Type[])null)); if (_recordType != null) { _recordItem = AccessTools.Field(_recordType, "itemType"); _recordAmount = AccessTools.Field(_recordType, "amount"); _recordBig = AccessTools.Field(_recordType, "isBigBox"); _recordItemType = _recordItem?.FieldType; } _usesRecords = _recordType != null && _recordCount != null && _recordPeek != null && _recordPop != null && _recordAdd != null && _recordItem != null && _recordAmount != null && _recordBig != null && _recordItemType != null; _usesLiveBoxes = AccessTools.Method(typeof(ShelfCompartment), "GetInteractablePackagingBoxList", (Type[])null, (Type[])null) != null && AccessTools.Method(typeof(ShelfCompartment), "GetLastInteractablePackagingBox", (Type[])null, (Type[])null) != null && AccessTools.Method(typeof(ShelfCompartment), "AddBox", (Type[])null, (Type[])null) != null && AccessTools.Method(typeof(ShelfCompartment), "RemoveBox", (Type[])null, (Type[])null) != null && AccessTools.Method(typeof(WarehouseShelf), "GetStorageCompartmentList", (Type[])null, (Type[])null) != null; if (_usesRecords) { CoopPlugin.Log.LogInfo((object)"World warehouse uses record-backed storage."); } else if (_recordType == null && _usesLiveBoxes) { CoopPlugin.Log.LogInfo((object)"World warehouse uses live-box storage."); } else { CoopPlugin.Log.LogError((object)"World warehouse disabled: its storage surface is incomplete."); } } } } internal static class WorldCardDisplay { internal const int MaxCompartments = 256; private static readonly FieldInfo HoldingCardsField = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingCard3dList"); internal static bool TryMakeKey(InteractableCardCompartment compartment, out int shelfKey, out int compartmentIndex) { shelfKey = 0; compartmentIndex = -1; if ((Object)(object)compartment == (Object)null) { return false; } CardShelf cardShelf = compartment.GetCardShelf(); if ((Object)(object)cardShelf == (Object)null) { return false; } int num = PlacementInterop.FindKind((InteractableObject)(object)cardShelf); if (num < 0 || !PlacementApi.TryMakeObjectKey(num, (InteractableObject)(object)cardShelf, out shelfKey)) { return false; } List cardCompartmentList = cardShelf.GetCardCompartmentList(); if (cardCompartmentList == null) { return false; } compartmentIndex = cardCompartmentList.IndexOf(compartment); if (compartmentIndex >= 0) { return compartmentIndex < 256; } return false; } internal static bool TryResolve(int shelfKey, int compartmentIndex, out InteractableCardCompartment compartment) { compartment = null; if (compartmentIndex >= 0 && compartmentIndex < 256) { Component obj = PlacementApi.ResolveObjectByKey(shelfKey); CardShelf val = (CardShelf)(object)((obj is CardShelf) ? obj : null); if (val != null) { List cardCompartmentList = val.GetCardCompartmentList(); if (cardCompartmentList == null || compartmentIndex >= cardCompartmentList.Count) { return false; } compartment = cardCompartmentList[compartmentIndex]; return (Object)(object)compartment != (Object)null; } } return false; } internal static CardData Read(InteractableCardCompartment compartment) { List list = compartment?.m_StoredCardList; if (list == null || list.Count == 0 || (Object)(object)list[0] == (Object)null || (Object)(object)list[0].m_Card3dUI?.m_CardUI == (Object)null) { return null; } return list[0].m_Card3dUI.m_CardUI.GetCardData(); } internal static bool Matches(InteractableCardCompartment compartment, CardData card, int encodedGrade) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) CardData val = Read(compartment); if (val == null || card == null) { return false; } if (val.expansionType == card.expansionType && val.monsterType == card.monsterType && val.borderType == card.borderType && val.isFoil == card.isFoil && val.isDestiny == card.isDestiny && val.isChampionCard == card.isChampionCard) { return EncodedGradeOf(val) == encodedGrade; } return false; } internal static int EncodedGradeOf(CardData card) { if (card != null) { if (!GradingApi.Present) { return card.cardGrade; } return GradingApi.Encoded(card); } return -1; } internal static BoxCardState ToState(CardData card) { if (card != null) { return BoxNetworkInteraction.ToState(card); } return null; } internal static CardData FromState(BoxCardState state) { if (state != null) { return BoxNetworkInteraction.FromState(state); } return null; } internal static void Clear(InteractableCardCompartment compartment) { if ((Object)(object)compartment == (Object)null) { return; } compartment.DisableAllCard(); if (compartment.m_StoredCardList != null) { compartment.m_StoredCardList.Clear(); } if (compartment.m_InteractablePriceTagList != null) { for (int i = 0; i < compartment.m_InteractablePriceTagList.Count; i++) { InteractableCardPriceTag obj = compartment.m_InteractablePriceTagList[i]; if (obj != null) { obj.SetPriceChecked(false); } } } compartment.SetPriceTagCardData((CardData)null); compartment.SetPriceTagVisibility(false); CardShelf cardShelf = compartment.GetCardShelf(); if ((Object)(object)cardShelf != (Object)null && (Object)(object)cardShelf.m_ElectronicCardListener != (Object)null) { cardShelf.m_ElectronicCardListener.UpdateCardUI((CardData)null); } } internal static bool Place(InteractableCardCompartment compartment, CardData card) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)compartment == (Object)null || card == null || compartment.m_StoredCardList == null || compartment.m_StoredCardList.Count > 0) { return false; } Card3dUISpawner val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)"card display: Card3dUISpawner is unavailable."); return false; } Card3dUIGroup val2 = null; InteractableCard3d val3 = null; try { val2 = val.GetCardUI(); InteractableObject obj = ShelfManager.SpawnInteractableObject((EObjectType)9); val3 = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { return false; } val2.m_IgnoreCulling = true; val2.m_CardUI.SetFoilCullListVisibility(true); val2.SetSimplifyCardDistanceCull(false); val2.m_CardUI.ResetFarDistanceCull(); if (card.cardGrade > 10) { GradingApi.Remember(card); } val2.m_CardUI.SetCardUI(card); ((Component)val2).transform.position = ((Component)val3).transform.position; ((Component)val2).transform.rotation = ((Component)val3).transform.rotation; val3.SetCardUIFollow(val2); val3.SetEnableCollision(false); compartment.SetCardOnShelf(val3); val2.m_IgnoreCulling = false; return true; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("card display: could not place " + ((object)Unsafe.As(ref card.monsterType)/*cast due to .constrained prefix*/).ToString() + ": " + ex.Message)); if ((Object)(object)val3 != (Object)null) { Object.Destroy((Object)(object)((Component)val3).gameObject); } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)((Component)val2).gameObject); } return false; } } internal static bool IsHeld(InteractableCard3d card3d) { if ((Object)(object)card3d == (Object)null || !Object.op_Implicit((Object)(object)card3d) || HoldingCardsField == null) { return false; } InteractionPlayerController val = SceneRef.Get(); return (((Object)(object)val == (Object)null) ? null : (HoldingCardsField.GetValue(val) as List))?.Contains(card3d) ?? false; } internal static List HeldCards() { if (HoldingCardsField == null) { return null; } InteractionPlayerController val = SceneRef.Get(); if (!((Object)(object)val == (Object)null)) { return HoldingCardsField.GetValue(val) as List; } return null; } internal static bool DespawnHeldCard(InteractableCard3d card3d) { if ((Object)(object)card3d == (Object)null || !Object.op_Implicit((Object)(object)card3d)) { return false; } List list = HeldCards(); int num = list?.IndexOf(card3d) ?? (-1); if (list == null || num < 0) { return false; } List holdCardDataList = CPlayerData.m_HoldCardDataList; if (holdCardDataList == null || holdCardDataList.Count != list.Count) { CoopPlugin.Log.LogWarning((object)("card despawn: held-card lists are out of sync (objects=" + list.Count + ", data=" + (holdCardDataList?.Count ?? (-1)) + "); refusing to remove the card through RemoveCurrentCard.")); return false; } if (num != 0) { list.RemoveAt(num); list.Insert(0, card3d); CardData item = holdCardDataList[num]; holdCardDataList.RemoveAt(num); holdCardDataList.Insert(0, item); } InteractionPlayerController.RemoveCurrentCard(); ((InteractableObject)card3d).OnDestroyed(); return true; } internal static bool ReturnHeldCardToShelf(InteractableCardCompartment compartment, InteractableCard3d card3d) { if ((Object)(object)compartment == (Object)null || (Object)(object)card3d == (Object)null || !Object.op_Implicit((Object)(object)card3d) || HoldingCardsField == null) { return false; } InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return false; } List list = HoldingCardsField.GetValue(val) as List; int num = list?.IndexOf(card3d) ?? (-1); if (list == null || num < 0) { return false; } List holdCardDataList = CPlayerData.m_HoldCardDataList; if (holdCardDataList == null || holdCardDataList.Count != list.Count) { CoopPlugin.Log.LogWarning((object)("card display: held-card lists are out of sync (objects=" + list.Count + ", data=" + (holdCardDataList?.Count ?? (-1)) + "); refusing to return the card through RemoveCurrentCard.")); return false; } if (num != 0) { list.RemoveAt(num); list.Insert(0, card3d); CardData item = holdCardDataList[num]; holdCardDataList.RemoveAt(num); holdCardDataList.Insert(0, item); } InteractionPlayerController.RemoveCurrentCard(); if (compartment.m_StoredCardList != null && compartment.m_StoredCardList.Count > 0) { Clear(compartment); } compartment.SetCardOnShelf(card3d); return true; } internal static bool TakeCardIntoHand(InteractableCardCompartment compartment, InteractableCard3d card3d) { if ((Object)(object)compartment == (Object)null || (Object)(object)card3d == (Object)null || !Object.op_Implicit((Object)(object)card3d) || compartment.m_StoredCardList == null || !compartment.m_StoredCardList.Contains(card3d) || !InteractionPlayerController.HasEnoughSlotToHoldCard()) { return false; } InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return false; } InteractionPlayerController.AddHoldCard(card3d); val.EnterHoldCardMode(); compartment.RemoveCardFromShelf((Transform)null, (Transform)null); return true; } } [NetworkMessage] public sealed class CardDisplayRequestMessage : WorldMessage { public int ShelfKey; public int Compartment; public bool Occupied; public BoxCardState Card; public int EncodedGrade; } [NetworkMessage] public sealed class CardDisplayMessage : WorldMessage { public int ShelfKey; public int Compartment; public bool Occupied; public BoxCardState Card; public int EncodedGrade; } internal sealed class WorldCardInteraction { private sealed class ChangedBinderCard { internal ECardExpansionType Expansion; internal bool IsDestiny; internal int SaveIndex; internal int EncodedGrade; internal int Added; internal int Removed; } private sealed class BinderTakeBinding { internal InteractableCard3d Card3d; } private sealed class BinderTakeScope { internal BinderTakeScope Previous; internal List HeldBefore; internal readonly List Bindings = new List(); } private sealed class GradedRemovalGuard { internal Guid PredictionId; internal bool Superseded; } private sealed class CardForwardingScope : IDisposable { private readonly bool _previous; private bool _disposed; internal CardForwardingScope(bool previous) { _previous = previous; } public void Dispose() { if (!_disposed) { _disposed = true; SuppressClientCardForwarding = _previous; } } } private sealed class ReservedGradingCard { internal CardData Card; internal bool FromTransferredOwnership; } private sealed class GradingCardReservation : IGradingCardReservation, IDisposable { private readonly WorldCardInteraction _owner; private readonly int _connectionId; private readonly List _cards; private readonly Dictionary _claimCountsBefore; private readonly Dictionary _claimCardsBefore; private bool _committed; private bool _rolledBack; private bool _disposed; internal GradingCardReservation(WorldCardInteraction owner, int connectionId, List cards, Dictionary claimCountsBefore, Dictionary claimCardsBefore) { _owner = owner; _connectionId = connectionId; _cards = cards; _claimCountsBefore = claimCountsBefore; _claimCardsBefore = claimCardsBefore; } public void Commit() { if (_disposed || _committed || _rolledBack) { return; } Dictionary dictionary = null; for (int i = 0; i < _cards.Count; i++) { if (!_cards[i].FromTransferredOwnership) { continue; } if (dictionary == null) { if (!_owner._gradingOwnership.TryGetValue(_connectionId, out var value)) { throw new InvalidOperationException("grading ownership transfer disappeared before commit"); } dictionary = new Dictionary(value, StringComparer.Ordinal); } string key = CardOwnershipKey(_cards[i].Card); if (!dictionary.TryGetValue(key, out var value2) || value2 <= 0) { throw new InvalidOperationException("grading ownership transfer disappeared before commit"); } if (value2 == 1) { dictionary.Remove(key); } else { dictionary[key] = value2 - 1; } } if (dictionary != null) { Dictionary dictionary2 = _owner._gradingOwnership[_connectionId]; dictionary2.Clear(); foreach (KeyValuePair item in dictionary) { dictionary2[item.Key] = item.Value; } } _committed = true; } public void Rollback() { if (_rolledBack) { return; } _rolledBack = true; Exception ex = null; if (_committed) { try { if (_claimCountsBefore == null || _claimCountsBefore.Count == 0) { _owner._gradingOwnership.Remove(_connectionId); } else { _owner._gradingOwnership[_connectionId] = new Dictionary(_claimCountsBefore, StringComparer.Ordinal); } if (_claimCardsBefore == null || _claimCardsBefore.Count == 0) { _owner._gradingOwnershipCards.Remove(_connectionId); } else { _owner._gradingOwnershipCards[_connectionId] = CloneClaimCards(_claimCardsBefore); } } catch (Exception ex2) { ex = ex2; CoopPlugin.Log.LogError((object)("grading ownership rollback failed: " + ex2)); } } try { _owner.RollbackGradingCards(_cards.FindAll((ReservedGradingCard card) => !card.FromTransferredOwnership)); } catch (Exception ex3) { CoopPlugin.Log.LogError((object)("grading card rollback failed: " + ex3)); if (ex == null) { ex = ex3; } } if (ex == null) { return; } throw ex; } public void Dispose() { if (!_disposed) { _disposed = true; if (!_committed) { Rollback(); } } } } private sealed class RemoteCardApplyScope : IDisposable { private readonly bool _previous; private bool _disposed; internal RemoteCardApplyScope(bool previous) { _previous = previous; } public void Dispose() { if (!_disposed) { _disposed = true; ApplyingRemoteCards = _previous; } } } private struct PendingCard { public int ConnectionId; public Guid PredictionId; public bool IsAdd; public bool IsGradedRemoval; public int Amount; public CardData Card; } private static readonly Dictionary _changedBinderCards = new Dictionary(StringComparer.Ordinal); private static bool _binderRefreshPending; private static bool _binderFullResync; private static readonly FieldInfo FiBinderSortedIndexList = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_SortedIndexList"); private static readonly FieldInfo FiBinderIndex = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_Index"); private static readonly FieldInfo FiBinderSortingType = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_SortingType"); private static readonly FieldInfo FiBinderInteractableCardList = AccessTools.Field(typeof(CollectionBinderFlipAnimCtrl), "m_InteractableCard3dList"); private static BinderTakeScope _binderTakeScope; private static readonly Dictionary> _gradedRemovalGuards = new Dictionary>(StringComparer.Ordinal); private readonly CoopRuntimeContext _context; private readonly bool _host; internal Action BroadcastOverride; internal Action RelayOverride; internal Action SendOverride; internal readonly WorldContainerInteraction Containers; internal readonly WorldMarketInteraction Market; private readonly WorldGradingBridge _gradingBridge; internal static bool ApplyingRemoteCards; internal static bool SuppressClientCardForwarding; private List _clientCardBatch; private readonly List _pendingCardDeltas = new List(); private readonly Dictionary> _gradingOwnership = new Dictionary>(); private readonly Dictionary> _gradingOwnershipCards = new Dictionary>(); internal const int CardDeltaBatchMax = 200; internal const int CardDeltaAmountMax = 1024; private readonly List _batchRelayBuf = new List(); private static readonly List _deltaLogBuf = new List(); private static int _deltaAppliedThisFrame; private static int _deltaNetThisFrame; private static readonly Dictionary> _shownMonsters = new Dictionary>(); private static readonly HashSet _cardWarnedKeys = new HashSet(StringComparer.Ordinal); private const int CardWarnedKeysMax = 512; 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"); internal bool IsHost => _host; internal bool IsClient => !_host; private int ConnectionCount => (_context.ConnectionIds?.Invoke()?.Count).GetValueOrDefault(); internal bool HasClientCardBatch => _clientCardBatch != null; internal static void RecordCardChanged(CardData card, int amount, bool isAdd) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (card == null || amount <= 0) { return; } string text = CardPriceKey(card); if (text != null) { if (!_changedBinderCards.TryGetValue(text, out var value)) { value = new ChangedBinderCard { Expansion = card.expansionType, IsDestiny = card.isDestiny, SaveIndex = CPlayerData.GetCardSaveIndex(card), EncodedGrade = GradingApi.Encoded(card) }; _changedBinderCards.Add(text, value); } if (isAdd) { value.Added += ((card.cardGrade > 0) ? 1 : amount); } else { value.Removed += amount; } _binderRefreshPending = true; } } internal static void ClearBinderMirrorFrame() { _changedBinderCards.Clear(); _binderFullResync = false; } internal static void ResetBinderMirror() { ClearBinderMirrorFrame(); _binderRefreshPending = false; } private static void ApplyOpenBinderCardChanges(CollectionBinderFlipAnimCtrl ctrl) { if (FiBinderIsGradedAlbum != null && (bool)FiBinderIsGradedAlbum.GetValue(ctrl)) { ApplyGradedBinderRemovals(ctrl); } else { ApplyUngradedBinderRemovals(ctrl); } } private static void ApplyUngradedBinderRemovals(CollectionBinderFlipAnimCtrl ctrl) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Invalid comparison between Unknown and I4 //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Invalid comparison between Unknown and I4 ECardExpansionType val = (ECardExpansionType)((!(FiBinderExpansionType != null)) ? (-1) : ((int)(ECardExpansionType)FiBinderExpansionType.GetValue(ctrl))); List list = ((FiBinderSortedIndexList != null) ? (FiBinderSortedIndexList.GetValue(ctrl) as List) : null); List binderPageGrpList = ctrl.m_BinderPageGrpList; if (list == null || binderPageGrpList == null || binderPageGrpList.Count == 0) { return; } int num = ((FiBinderIndex != null) ? ((int)FiBinderIndex.GetValue(ctrl)) : 0); ECollectionSortingType val2 = (ECollectionSortingType)((FiBinderSortingType != null) ? ((int)(ECollectionSortingType)FiBinderSortingType.GetValue(ctrl)) : 0); Dictionary dictionary = null; foreach (ChangedBinderCard value2 in _changedBinderCards.Values) { if (value2.EncodedGrade != 0 || value2.Expansion != val) { continue; } bool flag = !_binderFullResync && value2.Removed <= value2.Added; if (dictionary == null) { dictionary = BuildBinderPositionIndex(list); } if (!dictionary.TryGetValue(BinderLookupIndex(value2), out var value)) { continue; } int num2 = value / 12 + 1; int num3 = ((num2 != num) ? ((num2 == num + 1) ? 1 : ((num2 == num - 1) ? 2 : (-1))) : 0); if (num3 < 0 || num3 >= binderPageGrpList.Count) { continue; } BinderPageGrp val3 = binderPageGrpList[num3]; if ((Object)(object)val3 == (Object)null || val3.m_CardList == null) { continue; } int num4 = value % 12; if (num4 >= val3.m_CardList.Count) { continue; } int cardAmountByIndex = CPlayerData.GetCardAmountByIndex(value2.SaveIndex, val, value2.IsDestiny); if (flag) { Card3dUIGroup val4 = val3.m_CardList[num4]; if ((Object)(object)val4 == (Object)null || !((Component)val4).gameObject.activeSelf) { continue; } bool flag2 = (int)val2 == 5; int num5 = (flag2 ? (cardAmountByIndex - 1) : cardAmountByIndex); if (num5 > 0) { val4.SetCardCountText(num5, flag2); if (num3 == 0) { SyncBinderSlotProxy(ctrl, num4, visible: true); } } } else { CardData cardData = CPlayerData.GetCardData(value2.SaveIndex, val, value2.IsDestiny); val3.SetSingleCard(num4, cardData, cardAmountByIndex, val2); if (num3 == 0) { bool visible = cardAmountByIndex > (((int)val2 == 5) ? 1 : 0); SyncBinderSlotProxy(ctrl, num4, visible); } } } } private static void ApplyGradedBinderRemovals(CollectionBinderFlipAnimCtrl ctrl) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) List binderPageGrpList = ctrl.m_BinderPageGrpList; if (binderPageGrpList == null || binderPageGrpList.Count == 0) { return; } ECollectionSortingType sorting = (ECollectionSortingType)((FiBinderSortingType != null) ? ((int)(ECollectionSortingType)FiBinderSortingType.GetValue(ctrl)) : 0); foreach (ChangedBinderCard value in _changedBinderCards.Values) { if (value.EncodedGrade == 0) { continue; } int num = value.Removed - value.Added; while (num > 0 && HideOneVisibleGradedCard(ctrl, binderPageGrpList, value, sorting)) { num--; } if (_binderFullResync) { int num2 = value.Added - value.Removed; while (num2 > 0 && ShowOneHiddenGradedCard(ctrl, binderPageGrpList, value, sorting)) { num2--; } } } } private static bool ShowOneHiddenGradedCard(CollectionBinderFlipAnimCtrl ctrl, List groups, ChangedBinderCard change, ECollectionSortingType sorting) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 2; i++) { bool requireExactGrade = i == 0; for (int j = 0; j < groups.Count && j < 3; j++) { BinderPageGrp val = groups[j]; if ((Object)(object)val == (Object)null || val.m_CardList == null) { continue; } for (int k = 0; k < val.m_CardList.Count; k++) { Card3dUIGroup val2 = val.m_CardList[k]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.m_CardUI == (Object)null) && !((Component)val2).gameObject.activeSelf && GradedSlotMatches(val2.m_CardUI.GetCardData(), change, requireExactGrade)) { val.SetSingleCard(k, val2.m_CardUI.GetCardData(), 1, sorting); if (j == 0) { SyncBinderSlotProxy(ctrl, k, visible: true); } return true; } } } } return false; } private static bool HideOneVisibleGradedCard(CollectionBinderFlipAnimCtrl ctrl, List groups, ChangedBinderCard change, ECollectionSortingType sorting) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 2; i++) { bool requireExactGrade = i == 0; for (int j = 0; j < groups.Count && j < 3; j++) { BinderPageGrp val = groups[j]; if ((Object)(object)val == (Object)null || val.m_CardList == null) { continue; } for (int k = 0; k < val.m_CardList.Count; k++) { Card3dUIGroup val2 = val.m_CardList[k]; if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.m_CardUI == (Object)null) && ((Component)val2).gameObject.activeSelf && GradedSlotMatches(val2.m_CardUI.GetCardData(), change, requireExactGrade)) { val.SetSingleCard(k, (CardData)null, 0, sorting); if (j == 0) { SyncBinderSlotProxy(ctrl, k, visible: false); } return true; } } } } return false; } private static bool GradedSlotMatches(CardData shown, ChangedBinderCard change, bool requireExactGrade) { //IL_000d: 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) if (shown == null || shown.cardGrade <= 0 || shown.expansionType != change.Expansion || shown.isDestiny != change.IsDestiny || CPlayerData.GetCardSaveIndex(shown) != change.SaveIndex) { return false; } if (requireExactGrade) { return GradingApi.Encoded(shown) == change.EncodedGrade; } return true; } private static Dictionary BuildBinderPositionIndex(List sorted) { Dictionary dictionary = new Dictionary(sorted.Count); for (int i = 0; i < sorted.Count; i++) { dictionary[sorted[i]] = i; } return dictionary; } private static int BinderLookupIndex(ChangedBinderCard change) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if ((int)change.Expansion != 2 || !change.IsDestiny) { return change.SaveIndex; } List shownMonsterList = InventoryBase.GetShownMonsterList(change.Expansion); if (shownMonsterList == null) { return change.SaveIndex; } return change.SaveIndex + shownMonsterList.Count * CPlayerData.GetCardAmountPerMonsterType(change.Expansion, true); } private static void SyncBinderSlotProxy(CollectionBinderFlipAnimCtrl ctrl, int slot, bool visible) { List list = ((FiBinderInteractableCardList != null) ? (FiBinderInteractableCardList.GetValue(ctrl) as List) : null); if (list == null || slot < 0 || slot >= list.Count) { return; } InteractableCard3d val = list[slot]; if ((Object)(object)val == (Object)null) { return; } if (visible) { if (!((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(true); } } else if (((Component)val).gameObject.activeSelf) { if (ctrl.m_CurrentRaycastedInteractableCard3d == val) { ((InteractableObject)val).OnRaycastEnded(); ctrl.m_CurrentRaycastedInteractableCard3d = null; } ((Component)val).gameObject.SetActive(false); } } internal static void BeginBinderTake() { BinderTakeScope obj = new BinderTakeScope { Previous = _binderTakeScope }; List list = WorldCardDisplay.HeldCards(); obj.HeldBefore = ((list != null) ? new List(list) : null); _binderTakeScope = obj; } internal static void EndBinderTake() { BinderTakeScope binderTakeScope = _binderTakeScope; if (binderTakeScope == null) { return; } _binderTakeScope = binderTakeScope.Previous; if (binderTakeScope.Bindings.Count == 0 || binderTakeScope.HeldBefore == null) { return; } List list = WorldCardDisplay.HeldCards(); if (list == null) { return; } int i = 0; for (int j = 0; j < binderTakeScope.Bindings.Count; j++) { for (; i < list.Count && binderTakeScope.HeldBefore.Contains(list[i]); i++) { } if (i < list.Count) { binderTakeScope.Bindings[j].Card3d = list[i]; i++; continue; } break; } } private static BinderTakeBinding CreateBinderTakeBinding() { BinderTakeScope binderTakeScope = _binderTakeScope; if (binderTakeScope == null) { return null; } BinderTakeBinding binderTakeBinding = new BinderTakeBinding(); binderTakeScope.Bindings.Add(binderTakeBinding); return binderTakeBinding; } private static void DespawnRejectedBinderTake(BinderTakeBinding binding, CardData card) { if (binding == null) { return; } InteractableCard3d card3d = binding.Card3d; binding.Card3d = null; if (!((Object)(object)card3d == (Object)null) && Object.op_Implicit((Object)(object)card3d) && WorldCardDisplay.IsHeld(card3d)) { if (!WorldCardDisplay.DespawnHeldCard(card3d)) { CoopPlugin.Log.LogWarning((object)"binder pick refused, but its card could not be removed from the hand through the game's hold teardown; the player may keep a copy the album no longer owns."); } else { CoopPlugin.Log.LogInfo((object)("binder pick refused: removed " + CardIdent(card) + " from the hand.")); } } } private static void RegisterGradedRemovalGuard(Guid predictionId, GradedRemovalGuard guard, CardData card) { string text = CardPriceKey(card); if (text != null) { guard.PredictionId = predictionId; if (!_gradedRemovalGuards.TryGetValue(text, out var value)) { value = new List(); _gradedRemovalGuards.Add(text, value); } value.Add(guard); } } internal static void MarkRemoteGradedRemoval(CardData card) { string text = CardPriceKey(card); if (text == null || !_gradedRemovalGuards.TryGetValue(text, out var value)) { return; } for (int i = 0; i < value.Count; i++) { if (!value[i].Superseded) { value[i].Superseded = true; break; } } } internal static void OnPredictionRetired(Guid predictionId) { if (predictionId == Guid.Empty || _gradedRemovalGuards.Count == 0) { return; } List list = null; foreach (KeyValuePair> gradedRemovalGuard in _gradedRemovalGuards) { List value = gradedRemovalGuard.Value; for (int num = value.Count - 1; num >= 0; num--) { if (value[num].PredictionId == predictionId) { value.RemoveAt(num); } } if (value.Count == 0) { if (list == null) { list = new List(); } list.Add(gradedRemovalGuard.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { _gradedRemovalGuards.Remove(list[i]); } } } private static void ClearBinderTakeState() { _binderTakeScope = null; _gradedRemovalGuards.Clear(); } internal static IDisposable SuppressCardForwarding() { bool suppressClientCardForwarding = SuppressClientCardForwarding; SuppressClientCardForwarding = true; return new CardForwardingScope(suppressClientCardForwarding); } internal WorldCardInteraction(CoopRuntimeContext context, bool host, BoxNetworkInteraction boxes = null, PlayerBoxInteraction playerBox = null) { _context = context ?? throw new ArgumentNullException("context"); _host = host; Containers = new WorldContainerInteraction(boxes, playerBox); Market = new WorldMarketInteraction(context, host); _gradingBridge = new WorldGradingBridge(this); GradingWorldBridge.Attach(_gradingBridge); Market.BroadcastState = Broadcast; } internal bool InGameLevel() { return _context.InGame(); } private List ConnectionIds() { IReadOnlyList readOnlyList = _context.ConnectionIds?.Invoke(); if (readOnlyList != null) { return new List(readOnlyList); } return new List(); } internal static InventoryBase Inv() { return SceneRef.Get(); } private void Send(int connectionId, INetMessage message) { if (SendOverride != null) { SendOverride(connectionId, message); } else { _context.Send(connectionId, message); } } private void Broadcast(INetMessage message) { if (BroadcastOverride != null) { BroadcastOverride(message); } else { _context.Broadcast(message); } } internal void FlushWorldReady() { if (_gradingOwnership.Count > 0 && RestoreAllGradingOwnership()) { _gradingOwnership.Clear(); _gradingOwnershipCards.Clear(); } FlushPendingCardWork(); Containers.FlushClientState(); Market.FlushClientState(); FlushFrameCardWork(); } internal void AppendBaselineMessages(Action append) { if (_host && append != null) { Containers.AppendBaselineMessages(append); Market.AppendBaselineMessages(append); } } internal void Tick() { if (InGameLevel()) { FlushPendingCardWork(); Market.Tick(); FlushFrameCardWork(); } } internal void ForwardCardDelta(CardData card, int amount, bool isAdd) { if (card != null && amount > 0) { if (_host) { Broadcast((INetMessage)(object)new CardDeltaMessage { IsAdd = isAdd, Amount = amount, Card = SnapshotCard(card) }); } else { Send(1, (INetMessage)(object)new CardDeltaRequestMessage { IsAdd = isAdd, Amount = amount, Card = SnapshotCard(card) }); } } } internal bool PredictClientCardDelta(CardData card, int amount, bool isAdd) { if (_host || ApplyingRemoteCards || card == null || amount <= 0 || SaveTransferApi.PreloadHold) { return false; } CardData snapshot = SnapshotCard(card); if (GradingApi.Present) { snapshot.cardGrade = GradingApi.Encoded(card); } CardDeltaRequestMessage intent = new CardDeltaRequestMessage { IsAdd = isAdd, Amount = amount, Card = snapshot }; BinderTakeBinding binding = (isAdd ? null : CreateBinderTakeBinding()); WorldPrediction.Predict("world.cards", intent, delegate { ApplyPredictedCardDelta(snapshot, amount, isAdd); NotifyCardsChanged(); }, delegate { ApplyPredictedCardDelta(snapshot, amount, !isAdd); NotifyCardsChanged(); }, delegate { if (!isAdd) { DespawnRejectedBinderTake(binding, snapshot); GradingApi.OnCardRemovalRefused(snapshot, amount); } }); return true; } internal bool PredictClientGradedRemoval(CardData card, bool removedLocally = true) { if (_host || ApplyingRemoteCards || card == null || SaveTransferApi.PreloadHold) { return false; } CardData snapshot = SnapshotCard(card); if (GradingApi.Present) { snapshot.cardGrade = GradingApi.Encoded(card); } GradedRemoveRequestMessage intent = new GradedRemoveRequestMessage { Card = snapshot }; BinderTakeBinding binding = CreateBinderTakeBinding(); GradedRemovalGuard guard = new GradedRemovalGuard { Superseded = !removedLocally }; RegisterGradedRemovalGuard(WorldPrediction.Predict("world.cards", intent, delegate { if (!guard.Superseded) { ApplyPredictedGradedRemoval(snapshot); NotifyCardsChanged(); } }, delegate { if (!guard.Superseded) { ApplyPredictedCardDelta(snapshot, 1, isAdd: true); NotifyCardsChanged(); } }, delegate { DespawnRejectedBinderTake(binding, snapshot); GradingApi.OnCardRemovalRefused(snapshot, 1); }), guard, snapshot); return true; } internal static void ApplyPredictedCardDelta(CardData card, int amount, bool isAdd) { bool applyingRemoteCards = ApplyingRemoteCards; ApplyingRemoteCards = true; try { if (isAdd) { if (card.cardGrade > 10) { GradingApi.Remember(card); } CPlayerData.AddCard(card, amount); } else if (card.cardGrade > 0) { for (int i = 0; i < amount; i++) { CPlayerData.RemoveGradedCard(card, true); } } else { CPlayerData.ReduceCard(card, amount); } } finally { ApplyingRemoteCards = applyingRemoteCards; } } private static void ApplyPredictedGradedRemoval(CardData card) { bool applyingRemoteCards = ApplyingRemoteCards; ApplyingRemoteCards = true; try { CPlayerData.RemoveGradedCard(card, true); } finally { ApplyingRemoteCards = applyingRemoteCards; } } internal void BeginClientCardBatch() { if (!_host) { if (_clientCardBatch != null) { CoopPlugin.Log.LogWarning((object)"world cards: a client card batch was already open; restarting it."); } _clientCardBatch = new List(); } } internal void AddClientCardBatch(CardData card, int amount, bool isAdd) { if (_clientCardBatch != null && card != null && amount > 0) { CardData val = SnapshotCard(card); if (GradingApi.Present) { val.cardGrade = GradingApi.Encoded(card); } _clientCardBatch.Add(new CardDeltaEntry { IsAdd = isAdd, Amount = amount, Card = val }); } } internal void CommitClientCardBatch() { List clientCardBatch = _clientCardBatch; _clientCardBatch = null; if (clientCardBatch == null || clientCardBatch.Count == 0) { return; } for (int i = 0; i < clientCardBatch.Count; i += 200) { int count = Math.Min(200, clientCardBatch.Count - i); List chunk = clientCardBatch.GetRange(i, count); CardDeltaBatchRequestMessage intent = new CardDeltaBatchRequestMessage { Deltas = chunk }; WorldPrediction.Predict("world.cards", intent, delegate { ApplyPredictedCardBatch(chunk, reverse: false); }, delegate { ApplyPredictedCardBatch(chunk, reverse: true); }); } } private static void ApplyPredictedCardBatch(List entries, bool reverse) { if (reverse) { for (int num = entries.Count - 1; num >= 0; num--) { ApplyPredictedCardDelta(entries[num].Card, entries[num].Amount, !entries[num].IsAdd); } } else { for (int i = 0; i < entries.Count; i++) { ApplyPredictedCardDelta(entries[i].Card, entries[i].Amount, entries[i].IsAdd); } } } internal void SendCardDeltaTo(int connectionId, CardData card, int amount, bool isAdd) { if (_host && ConnectionCount != 0 && card != null && amount > 0) { Send(connectionId, (INetMessage)(object)new CardDeltaMessage { IsAdd = isAdd, Amount = amount, Card = SnapshotCard(card) }); } } internal void ForwardGradedRemoval(CardData card) { if (card != null && card.cardGrade > 0) { if (_host) { Broadcast((INetMessage)(object)new GradedRemoveMessage { Card = SnapshotCard(card) }); } else { Send(1, (INetMessage)(object)new GradedRemoveRequestMessage { Card = SnapshotCard(card) }); } } } internal bool HandleCardDelta(int connectionId, CardDeltaMessage message) { if (_host && message == null) { return false; } bool relayAnyway; bool flag = ApplyOrHoldCardDelta(connectionId, message.IsAdd, message.Amount, message.Card, out relayAnyway, message.PredictionId); if (!flag && !relayAnyway) { return false; } if (_host && !message.IsAdd && !flag && GradedRemoveUncovered(message.Card)) { Broadcast((INetMessage)(object)new CardDeltaMessage { PredictionId = Guid.Empty, IsAdd = false, Amount = message.Amount, Card = SnapshotCard(message.Card) }); return false; } if (_host && message.IsAdd) { ConsumeGradingOwnership(connectionId, message.Card, message.Amount); } else if (_host && !relayAnyway && !message.IsAdd) { RecordGradingOwnership(connectionId, message.Card, message.Amount); } if (_host) { Broadcast((INetMessage)(object)new CardDeltaMessage { PredictionId = message.PredictionId, IsAdd = message.IsAdd, Amount = message.Amount, Card = SnapshotCard(message.Card) }); } return true; } internal bool HandleCardDeltaBatch(int connectionId, CardDeltaBatchMessage message) { return HandleCardDeltaBatch(connectionId, message, requireAllApplied: false); } internal bool HandleCardDeltaBatch(int connectionId, CardDeltaBatchMessage message, bool requireAllApplied) { if (_host && message == null) { return false; } if (message.Deltas == null) { throw new InvalidOperationException("Card delta batch has no delta list."); } int count = message.Deltas.Count; if (_host && count > 200) { throw new InvalidOperationException("Incoming card delta batch is too large."); } if (!_host) { for (int i = 0; i < count; i++) { ApplyOrHoldCardDelta(connectionId, message.Deltas[i].IsAdd, message.Deltas[i].Amount, message.Deltas[i].Card, out var _, message.PredictionId); } return true; } bool flag = _host && ConnectionCount > 1; _batchRelayBuf.Clear(); List list = new List(count); List list2 = new List(count); int num = 0; int num2 = 0; for (int j = 0; j < count; j++) { CardDeltaEntry cardDeltaEntry = message.Deltas[j]; CardData card = (flag ? SnapshotCard(cardDeltaEntry.Card) : null); bool flag2; bool relayAnyway2; try { flag2 = ApplyOrHoldCardDelta(connectionId, cardDeltaEntry.IsAdd, cardDeltaEntry.Amount, cardDeltaEntry.Card, out relayAnyway2, message.PredictionId); } catch (Exception innerException) { throw new InvalidOperationException($"card delta batch: delta {j + 1}/{count} failed to apply.", innerException); } if (requireAllApplied && (!flag2 || relayAnyway2)) { RetractCardDeltas(list); return false; } if (!flag2 && !relayAnyway2) { CoopPlugin.Log.LogWarning((object)("card delta batch from connection " + connectionId + " refused at delta " + (j + 1) + "/" + count + "; rolling the batch back.")); RetractCardDeltas(list); return false; } PendingCard item = new PendingCard { IsAdd = cardDeltaEntry.IsAdd, Amount = cardDeltaEntry.Amount, Card = SnapshotCard(cardDeltaEntry.Card) }; if (flag2) { list.Add(item); num++; } else { list2.Add(item); num2++; } if (flag) { _batchRelayBuf.Add(new PendingCard { IsAdd = cardDeltaEntry.IsAdd, Amount = cardDeltaEntry.Amount, Card = card }); } } for (int k = 0; k < list.Count; k++) { if (list[k].IsAdd) { ConsumeGradingOwnership(connectionId, list[k].Card, list[k].Amount); } else { RecordGradingOwnership(connectionId, list[k].Card, list[k].Amount); } } for (int l = 0; l < list2.Count; l++) { if (list2[l].IsAdd) { ConsumeGradingOwnership(connectionId, list2[l].Card, list2[l].Amount); } } if (num == count && num2 == 0 && count > 0) { CardDeltaBatchMessage cardDeltaBatchMessage = ToAuthoritative(message); cardDeltaBatchMessage.PredictionId = (_host ? message.PredictionId : Guid.Empty); Broadcast((INetMessage)(object)cardDeltaBatchMessage); } else if (_batchRelayBuf.Count > 0) { RelayCardDeltaBatchToOthers(connectionId, _batchRelayBuf); } if (num <= 0) { return num2 > 0; } return true; } private static void RetractCardDeltas(List applied) { for (int num = applied.Count - 1; num >= 0; num--) { PendingCard pendingCard = applied[num]; if (!ApplyCardDelta(!pendingCard.IsAdd, pendingCard.Amount, pendingCard.Card, out var relayAnyway) && !relayAnyway) { CoopPlugin.Log.LogError((object)("trade card movement could not be retracted for " + CardIdent(pendingCard.Card) + " (" + (pendingCard.IsAdd ? "add" : "remove") + " x" + pendingCard.Amount + "); the host inventory may be inconsistent.")); } } } internal bool HandleGradedRemove(int connectionId, GradedRemoveMessage message) { if (_host && message == null) { return false; } CardData card = message.Card; if (!InGameLevel()) { _pendingCardDeltas.Add(new PendingCard { ConnectionId = connectionId, PredictionId = message.PredictionId, IsAdd = false, IsGradedRemoval = true, Amount = 1, Card = SnapshotCard(card) }); return true; } bool relayAnyway = false; bool flag = (_host ? ApplyCardDelta(isAdd: false, 1, card, out relayAnyway) : ApplyTrustedCardDelta(isAdd: false, 1, card)); if (!flag && !relayAnyway) { return false; } if (_host && !flag && GradedRemoveUncovered(card)) { Broadcast((INetMessage)(object)new GradedRemoveMessage { PredictionId = Guid.Empty, Card = SnapshotCard(message.Card) }); return false; } if (_host && !relayAnyway) { RecordGradingOwnership(connectionId, card, 1); } if (ConnectionCount == 0) { return true; } if (_host) { Broadcast((INetMessage)(object)new GradedRemoveMessage { PredictionId = message.PredictionId, Card = SnapshotCard(message.Card) }); } return true; } internal void Reset() { Containers.Reset(); Market.Reset(); _pendingCardDeltas.Clear(); if (RestoreAllGradingOwnership()) { _gradingOwnership.Clear(); _gradingOwnershipCards.Clear(); } else if (_gradingOwnership.Count > 0) { CoopPlugin.Log.LogWarning((object)"World reset retained grading card claims because the live inventory is not ready; they will be restored before the claims are cleared."); } _shownMonsters.Clear(); _deltaAppliedThisFrame = 0; _deltaNetThisFrame = 0; _deltaLogBuf.Clear(); ResetBinderMirror(); ClearBinderTakeState(); ApplyingRemoteCards = false; _clientCardBatch = null; WorldContainerInteraction.ApplyingRemote = false; ClearCardSetCache(); GradingApi.Reset(); } internal void Shutdown() { Reset(); GradingWorldBridge.Detach(_gradingBridge); } internal void ForgetGradingOwnership(int connectionId) { if (connectionId > 0) { RestoreGradingOwnership(connectionId); } } private bool RestoreAllGradingOwnership() { if (_gradingOwnership.Count == 0) { return true; } if (!InGameLevel() || (Object)(object)Inv() == (Object)null) { return false; } List list = new List(); foreach (KeyValuePair> item in _gradingOwnership) { if (!_gradingOwnershipCards.TryGetValue(item.Key, out var value)) { return false; } foreach (KeyValuePair item2 in item.Value) { if (!value.TryGetValue(item2.Key, out var value2) || value2 == null || item2.Value <= 0 || item2.Value > 1024) { return false; } list.Add(new PendingCard { IsAdd = true, Amount = item2.Value, Card = SnapshotCard(value2) }); } } return ApplyRestoredCards(list); } private bool RestoreGradingOwnership(int connectionId) { if (!_gradingOwnership.ContainsKey(connectionId)) { return true; } if (!InGameLevel() || (Object)(object)Inv() == (Object)null || !_gradingOwnershipCards.TryGetValue(connectionId, out var value)) { return false; } List list = new List(); foreach (KeyValuePair item in _gradingOwnership[connectionId]) { if (!value.TryGetValue(item.Key, out var value2) || value2 == null || item.Value <= 0 || item.Value > 1024) { return false; } list.Add(new PendingCard { IsAdd = true, Amount = item.Value, Card = SnapshotCard(value2) }); } if (!ApplyRestoredCards(list)) { return false; } _gradingOwnership.Remove(connectionId); _gradingOwnershipCards.Remove(connectionId); CoopPlugin.Log.LogInfo((object)("restored " + list.Count + " grading card claim(s) before clearing connection " + connectionId)); return true; } private bool ApplyRestoredCards(List restored) { List list = new List(); try { using (BeginRemoteCardApply()) { for (int i = 0; i < restored.Count; i++) { PendingCard item = restored[i]; int num = ((item.Card.cardGrade > 0) ? CountGradedCards(item.Card) : CPlayerData.GetCardAmount(item.Card)); if (num < 0 || num > int.MaxValue - item.Amount) { throw new InvalidOperationException("card count overflowed before claim restoration"); } GradingApi.Remember(item.Card); CPlayerData.AddCard(SnapshotCard(item.Card), item.Amount); if (((item.Card.cardGrade > 0) ? CountGradedCards(item.Card) : CPlayerData.GetCardAmount(item.Card)) != num + item.Amount) { throw new InvalidOperationException("claim restoration did not update inventory by the expected amount"); } list.Add(item); } } } catch (Exception ex) { if (!RollbackRestoredCards(list)) { CoopPlugin.Log.LogError((object)("grading claim restoration compensation failed: " + ex)); } else { CoopPlugin.Log.LogWarning((object)("grading claim restoration failed and was compensated: " + ex.Message)); } return false; } for (int j = 0; j < restored.Count; j++) { ForwardCardDelta(restored[j].Card, restored[j].Amount, isAdd: true); } if (restored.Count > 0) { NotifyCardsChanged(); } return true; } private bool RollbackRestoredCards(List applied) { try { using (BeginRemoteCardApply()) { for (int num = applied.Count - 1; num >= 0; num--) { PendingCard pendingCard = applied[num]; int num2 = ((pendingCard.Card.cardGrade > 0) ? CountGradedCards(pendingCard.Card) : CPlayerData.GetCardAmount(pendingCard.Card)); if (num2 < pendingCard.Amount) { return false; } if (pendingCard.Card.cardGrade > 0) { for (int i = 0; i < pendingCard.Amount; i++) { CPlayerData.RemoveGradedCard(pendingCard.Card, true); } } else { CPlayerData.ReduceCard(pendingCard.Card, pendingCard.Amount); } if (((pendingCard.Card.cardGrade > 0) ? CountGradedCards(pendingCard.Card) : CPlayerData.GetCardAmount(pendingCard.Card)) != num2 - pendingCard.Amount) { return false; } } } } catch (Exception ex) { CoopPlugin.Log.LogError((object)("claim restoration rollback failed: " + ex)); return false; } return true; } internal IDisposable BeginRemoteCardApply() { bool applyingRemoteCards = ApplyingRemoteCards; ApplyingRemoteCards = true; return new RemoteCardApplyScope(applyingRemoteCards); } internal bool TryReserveGradingCards(int connectionId, IList cards, out IGradingCardReservation reservation, out string reason) { reservation = null; reason = null; if (!_host || connectionId <= 0 || cards == null || cards.Count == 0) { reason = "the host card owner is not ready"; return false; } Dictionary value; Dictionary dictionary = (_gradingOwnership.TryGetValue(connectionId, out value) ? new Dictionary(value, StringComparer.Ordinal) : null); List list = new List(cards.Count); Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); Dictionary dictionary3 = new Dictionary(StringComparer.Ordinal); try { for (int i = 0; i < cards.Count; i++) { CardData val = cards[i]; if (val == null || !CardSetInstalledHere(val)) { reason = "the host cannot represent one or more submitted cards"; return false; } string text = CardOwnershipKey(val); if (text == null) { reason = "one submitted card has no stable identity"; return false; } bool flag = val.cardGrade > 0; if (dictionary != null && dictionary.TryGetValue(text, out var value2) && value2 > 0) { dictionary[text] = value2 - 1; list.Add(new ReservedGradingCard { Card = SnapshotCard(val), FromTransferredOwnership = true }); continue; } int num = (flag ? GetCachedGradedCount(val, dictionary3) : GetCachedUngradedCount(val, dictionary2)); if (num > 0) { SetCachedCount(flag ? dictionary3 : dictionary2, text, num - 1); list.Add(new ReservedGradingCard { Card = SnapshotCard(val) }); continue; } if (dictionary == null || !dictionary.TryGetValue(text, out value2) || value2 <= 0) { reason = "the host does not own every submitted card"; return false; } dictionary[text] = value2 - 1; list.Add(new ReservedGradingCard { Card = SnapshotCard(val), FromTransferredOwnership = true }); } } catch (Exception ex) { reason = "host card ownership could not be checked: " + ex.Message; CoopPlugin.Log.LogError((object)("grading card ownership check failed for connection " + connectionId + ": " + ex)); return false; } List list2 = new List(); try { using (BeginRemoteCardApply()) { for (int j = 0; j < list.Count; j++) { ReservedGradingCard reservedGradingCard = list[j]; if (reservedGradingCard.FromTransferredOwnership) { continue; } if (reservedGradingCard.Card.cardGrade > 0) { int num2 = CountGradedCards(reservedGradingCard.Card); if (num2 <= 0) { throw new InvalidOperationException("graded card disappeared before reservation"); } CPlayerData.RemoveGradedCard(reservedGradingCard.Card, true); if (CountGradedCards(reservedGradingCard.Card) != num2 - 1) { throw new InvalidOperationException("host graded-card removal did not change inventory"); } } else { int cardAmount = CPlayerData.GetCardAmount(reservedGradingCard.Card); if (cardAmount <= 0) { throw new InvalidOperationException("card disappeared before reservation"); } CPlayerData.ReduceCard(reservedGradingCard.Card, 1); if (CPlayerData.GetCardAmount(reservedGradingCard.Card) != cardAmount - 1) { throw new InvalidOperationException("host card removal did not change inventory"); } } list2.Add(reservedGradingCard); } } for (int k = 0; k < list2.Count; k++) { CardData card = list2[k].Card; if (card.cardGrade > 0) { ForwardGradedRemoval(card); } else { ForwardCardDelta(card, 1, isAdd: false); } } NotifyCardsChanged(); reservation = new GradingCardReservation(this, connectionId, list, _gradingOwnership.TryGetValue(connectionId, out var value3) ? new Dictionary(value3, StringComparer.Ordinal) : null, _gradingOwnershipCards.TryGetValue(connectionId, out var value4) ? CloneClaimCards(value4) : null); return true; } catch (Exception ex2) { RollbackGradingCards(list2); reason = "host card reservation failed: " + ex2.Message; CoopPlugin.Log.LogError((object)("grading card reservation failed for connection " + connectionId + ": " + ex2)); return false; } } private void RollbackGradingCards(List applied) { using (BeginRemoteCardApply()) { for (int num = applied.Count - 1; num >= 0; num--) { CardData card = applied[num].Card; if (card.cardGrade > 0) { GradingApi.Remember(card); CPlayerData.AddCard(card, 1); } else { CPlayerData.AddCard(card, 1); } } } for (int i = 0; i < applied.Count; i++) { CardData card2 = applied[i].Card; ForwardCardDelta(card2, 1, isAdd: true); } if (applied.Count > 0) { NotifyCardsChanged(); } } private int GetCachedUngradedCount(CardData card, Dictionary cache) { string key = CardOwnershipKey(card); if (cache.TryGetValue(key, out var value)) { return value; } return cache[key] = CPlayerData.GetCardAmount(card); } private int GetCachedGradedCount(CardData card, Dictionary cache) { string text = CardOwnershipKey(card); if (cache.TryGetValue(text, out var value)) { return value; } value = 0; List gradedCardInventoryList = CPlayerData.m_GradedCardInventoryList; if (gradedCardInventoryList != null) { for (int i = 0; i < gradedCardInventoryList.Count; i++) { CompactCardDataAmount val = gradedCardInventoryList[i]; if (val != null && CardOwnershipKey(CPlayerData.GetGradedCardData(val)) == text) { value++; } } } cache[text] = value; return value; } private static int CountGradedCards(CardData card) { string text = CardOwnershipKey(card); int num = 0; List gradedCardInventoryList = CPlayerData.m_GradedCardInventoryList; if (text == null || gradedCardInventoryList == null) { return num; } for (int i = 0; i < gradedCardInventoryList.Count; i++) { CompactCardDataAmount val = gradedCardInventoryList[i]; if (val != null && CardOwnershipKey(CPlayerData.GetGradedCardData(val)) == text) { num++; } } return num; } private static void SetCachedCount(Dictionary cache, string key, int count) { cache[key] = count; } private static Dictionary CloneClaimCards(Dictionary cards) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (cards == null) { return dictionary; } foreach (KeyValuePair card in cards) { dictionary[card.Key] = ((card.Value == null) ? null : SnapshotCard(card.Value)); } return dictionary; } private Dictionary GetOrCreateOwnership(int connectionId) { if (!_gradingOwnership.TryGetValue(connectionId, out var value)) { value = new Dictionary(StringComparer.Ordinal); _gradingOwnership[connectionId] = value; } return value; } private void RecordGradingOwnership(int connectionId, CardData card, int amount) { if (!_host || connectionId <= 0 || card == null || amount <= 0) { return; } string text = CardOwnershipKey(card); if (text != null) { Dictionary orCreateOwnership = GetOrCreateOwnership(connectionId); orCreateOwnership.TryGetValue(text, out var value); orCreateOwnership[text] = Math.Min(1024, (value > 1024 - amount) ? 1024 : (value + amount)); if (!_gradingOwnershipCards.TryGetValue(connectionId, out var value2)) { value2 = new Dictionary(StringComparer.Ordinal); _gradingOwnershipCards[connectionId] = value2; } value2[text] = SnapshotCard(card); } } private bool ConsumeGradingOwnership(int connectionId, CardData card, int amount) { if (!_host || connectionId <= 0 || card == null || amount <= 0 || !_gradingOwnership.TryGetValue(connectionId, out var value)) { return false; } string text = CardOwnershipKey(card); if (text == null || !value.TryGetValue(text, out var value2) || value2 < amount) { return false; } value2 -= amount; if (value2 > 0) { value[text] = value2; } else { value.Remove(text); if (_gradingOwnershipCards.TryGetValue(connectionId, out var value3)) { value3.Remove(text); } } if (value.Count == 0) { _gradingOwnership.Remove(connectionId); _gradingOwnershipCards.Remove(connectionId); } return true; } internal void NotifyCardsChanged() { _binderFullResync = true; _binderRefreshPending = true; } private static bool ApplyCardDelta(bool isAdd, int amount, CardData card, out bool relayAnyway) { relayAnyway = false; if (!TryValidateCardDelta(card, amount, out var reason)) { CoopPlugin.Log.LogWarning((object)("card delta refused: " + reason)); return false; } if (card.cardGrade != 0 && (card.cardGrade < 1 || card.cardGrade > 10) && !GradingApi.Present) { CoopPlugin.Log.LogWarning((object)$"card delta: dropping corrupt graded card {CardIdent(card)} (grade {card.cardGrade}) - not applied (Grading Overhaul absent)"); return false; } bool applyingRemoteCards = ApplyingRemoteCards; ApplyingRemoteCards = true; try { if (!CardSetInstalledHere(card)) { relayAnyway = true; if (MarkCardWarned("missing:" + 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 (!CanAddCardAmount(card, amount)) { CoopPlugin.Log.LogWarning((object)("card delta would overflow the local inventory for " + CardIdent(card))); return false; } if (card.cardGrade > 10) { GradingApi.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?)"); relayAnyway = true; 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 { ApplyingRemoteCards = applyingRemoteCards; } _deltaAppliedThisFrame++; _deltaNetThisFrame += (isAdd ? amount : (-amount)); if (_deltaLogBuf.Count < 5) { _deltaLogBuf.Add(new PendingCard { IsAdd = isAdd, Amount = amount, Card = SnapshotCard(card) }); } return true; } private static bool ApplyTrustedCardDelta(bool isAdd, int amount, CardData card) { bool applyingRemoteCards = ApplyingRemoteCards; ApplyingRemoteCards = true; try { if (isAdd) { if (card.cardGrade > 10) { GradingApi.Remember(card); } CPlayerData.AddCard(card, amount); } else if (card.cardGrade > 0) { int num = CPlayerData.m_GradedCardInventoryList?.Count ?? 0; for (int i = 0; i < amount; i++) { CPlayerData.RemoveGradedCard(card, true); } if ((CPlayerData.m_GradedCardInventoryList?.Count ?? 0) == num) { MarkRemoteGradedRemoval(card); } } else { CPlayerData.ReduceCard(card, amount); } } finally { ApplyingRemoteCards = applyingRemoteCards; } _deltaAppliedThisFrame++; _deltaNetThisFrame += (isAdd ? amount : (-amount)); if (_deltaLogBuf.Count < 5) { _deltaLogBuf.Add(new PendingCard { IsAdd = isAdd, Amount = amount, Card = SnapshotCard(card) }); } return true; } internal bool ApplyOrHoldCardDelta(int connectionId, bool isAdd, int amount, CardData card, out bool relayAnyway, Guid predictionId = default(Guid)) { relayAnyway = false; if (_host && !TryValidateCardDelta(card, amount, out var reason)) { CoopPlugin.Log.LogWarning((object)("card delta from connection " + connectionId + " refused: " + reason)); return false; } if (!InGameLevel()) { _pendingCardDeltas.Add(new PendingCard { ConnectionId = connectionId, PredictionId = predictionId, IsAdd = isAdd, Amount = amount, Card = SnapshotCard(card) }); return false; } if (!_host) { return ApplyTrustedCardDelta(isAdd, amount, card); } return ApplyCardDelta(isAdd, amount, card, out relayAnyway); } private static bool GradedRemoveUncovered(CardData card) { if (card != null && card.cardGrade > 0) { return CardSetInstalledHere(card); } return false; } internal static CardData SnapshotCard(CardData c) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0016: 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_0022: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown if (c == null) { return null; } 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 bool TryValidateCardDelta(CardData card, int amount, out string reason) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) reason = null; if (card == null) { reason = "card is null"; return false; } if (amount <= 0 || amount > 1024) { reason = "amount is outside the bounded positive range"; return false; } if ((int)card.expansionType == -1 || (int)card.monsterType == 0 || !Enum.IsDefined(typeof(ECardBorderType), card.borderType)) { reason = "card enum fields are invalid"; return false; } if (card.isChampionCard || card.gradedCardIndex < 0 || card.gradedCardIndex > 1000000 || card.cardGrade < 0) { reason = "card identity fields are invalid"; return false; } try { int num = GradingApi.Encoded(card); if (num < 0 || (num > 10 && !GradingApi.Present) || (num > 10 && (GradingApi.Actual(num) < 1 || GradingApi.Actual(num) > 10))) { reason = "card grade is invalid for the installed grading backend"; return false; } if (num == 0 && card.gradedCardIndex != 0) { reason = "an ungraded card carried a graded-card index"; return false; } if (!CardSetInstalledHere(card)) { return true; } int cardSaveIndex = CPlayerData.GetCardSaveIndex(card); List cardCollectedList = CPlayerData.GetCardCollectedList(card.expansionType, card.isDestiny); if (cardSaveIndex < 0 || cardCollectedList == null || cardSaveIndex >= cardCollectedList.Count) { reason = "card save index is outside the live collection"; return false; } CardData cardData = CPlayerData.GetCardData(cardSaveIndex, card.expansionType, card.isDestiny); if (cardData == null || cardData.expansionType != card.expansionType || cardData.monsterType != card.monsterType) { reason = "card fields do not match their save index (index=" + cardSaveIndex + " sent=" + CardFieldDump(card) + " canonical=" + CardFieldDump(cardData) + ")"; return false; } if (cardData.borderType != card.borderType || cardData.isFoil != card.isFoil || cardData.isDestiny != card.isDestiny || cardData.isChampionCard != card.isChampionCard) { if (IsVanillaCardExpansion(card.expansionType)) { reason = "card fields do not match their save index (index=" + cardSaveIndex + " sent=" + CardFieldDump(card) + " canonical=" + CardFieldDump(cardData) + ")"; return false; } WarnCardDefinitionDivergence(card, cardData, cardSaveIndex); } } catch (Exception ex) { reason = "card save identity could not be validated: " + ex.Message; return false; } return true; } private static bool CanAddCardAmount(CardData card, int amount) { if (card == null || amount <= 0) { return false; } if (card.cardGrade <= 0) { int cardAmount = CPlayerData.GetCardAmount(card); if (cardAmount >= 0) { return cardAmount <= int.MaxValue - amount; } return false; } List gradedCardInventoryList = CPlayerData.m_GradedCardInventoryList; if (gradedCardInventoryList != null) { return gradedCardInventoryList.Count <= int.MaxValue - amount; } return false; } private static string CardPriceKey(CardData card) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected I4, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected I4, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected I4, but got Unknown if (card == null) { return null; } int num = GradingApi.Encoded(card); return (int)card.expansionType + ":" + (int)card.monsterType + ":" + (int)card.borderType + ":" + (card.isFoil ? 1 : 0) + (card.isDestiny ? 1 : 0) + (card.isChampionCard ? 1 : 0) + ":" + num; } private static string CardOwnershipKey(CardData card) { return CardPriceKey(card); } internal static void ClearCardSetCache() { _shownMonsters.Clear(); _cardWarnedKeys.Clear(); } internal static bool IsVanillaCardExpansion(ECardExpansionType expansion) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)expansion >= 0) { return (int)expansion < 8; } return false; } private static bool MarkCardWarned(string key) { if (key == null || _cardWarnedKeys.Contains(key) || _cardWarnedKeys.Count >= 512) { return false; } _cardWarnedKeys.Add(key); return true; } private static void WarnCardDefinitionDivergence(CardData card, CardData canonical, int saveIndex) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected I4, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected I4, but got Unknown if (MarkCardWarned("diverged:" + (int)card.expansionType + ":" + (int)card.monsterType + ":" + (card.isFoil ? 1 : 0))) { CoopPlugin.Log.LogWarning((object)("card delta: " + CardIdent(card) + " is defined differently on this host (index=" + saveIndex + " sent=" + CardFieldDump(card) + " canonical=" + CardFieldDump(canonical) + "); the card is stored as this host defines that slot - make sure both players installed the same version of the pack")); } } 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 shownMonsterList = InventoryBase.GetShownMonsterList(expansion); if (shownMonsterList == null || shownMonsterList.Count == 0) { return false; } value = new HashSet(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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) 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) { 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected I4, but got Unknown //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected I4, but got Unknown if (c == null) { return "(null card)"; } if ((int)c.expansionType == -1) { return "unknown-pack card #" + (int)c.monsterType; } if ((int)c.expansionType < 8) { return ((object)Unsafe.As(ref c.monsterType)/*cast due to .constrained prefix*/).ToString(); } return ((object)Unsafe.As(ref c.expansionType)/*cast due to .constrained prefix*/).ToString() + "#" + (int)c.monsterType; } private static string CardFieldDump(CardData c) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected I4, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected I4, but got Unknown if (c == null) { return "(null card)"; } return CardIdent(c) + "[exp=" + (int)c.expansionType + " monster=" + (int)c.monsterType + " border=" + (int)c.borderType + (c.isFoil ? " foil" : "") + (c.isDestiny ? " destiny" : "") + (c.isChampionCard ? " champion" : "") + " grade=" + c.cardGrade + " gidx=" + c.gradedCardIndex + "]"; } internal static void WarnRefusedCard(CardData c, string context) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected I4, but got Unknown if (c != null) { 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 void RefreshOpenBinder() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Invalid comparison between Unknown and I4 //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) try { InteractionPlayerController val = SceneRef.Get(); CollectionBinderFlipAnimCtrl val2 = (((Object)(object)val != (Object)null) ? val.m_CollectionBinderFlipAnimCtrl : null); if ((Object)(object)val2 == (Object)null) { return; } val2.SetCanUpdateSort(true); int num; if (FiBinderIsBookOpen != null) { num = (((bool)FiBinderIsBookOpen.GetValue(val2)) ? 1 : 0); if (num != 0) { ApplyOpenBinderCardChanges(val2); } } else { num = 0; } if (num == 0 || !(FiBinderUI != null)) { return; } object? value = FiBinderUI.GetValue(val2); CollectionBinderUI val3 = (CollectionBinderUI)((value is CollectionBinderUI) ? value : null); if (!((Object)(object)val3 != (Object)null)) { return; } bool num2 = FiBinderIsGradedAlbum != null && (bool)FiBinderIsGradedAlbum.GetValue(val2); ECardExpansionType val4 = (ECardExpansionType)((!(FiBinderExpansionType != null)) ? (-1) : ((int)(ECardExpansionType)FiBinderExpansionType.GetValue(val2))); if (num2) { bool present = GradingApi.Present; float num3 = 0f; for (int i = 0; i < CPlayerData.m_GradedCardInventoryList.Count; i++) { CompactCardDataAmount val5 = CPlayerData.m_GradedCardInventoryList[i]; if (val5 == null) { continue; } try { CardData gradedCardData = CPlayerData.GetGradedCardData(val5); if (!present && gradedCardData.cardGrade > 10) { gradedCardData.cardGrade = 10; } num3 += CPlayerData.GetCardMarketPrice(gradedCardData); } catch { } } val3.SetTotalValue(num3); } else if ((int)val4 == 2) { val3.SetTotalValue(CPlayerData.GetCardAlbumTotalValue(val4, false) + CPlayerData.GetCardAlbumTotalValue(val4, true)); } else { val3.SetTotalValue(CPlayerData.GetCardAlbumTotalValue(val4, false)); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("binder relayout after card change failed: " + ex.Message)); } } private void FlushPendingCardWork() { if (!InGameLevel() || _pendingCardDeltas.Count == 0) { return; } List list = (_host ? new List(_pendingCardDeltas.Count) : null); List list2 = (_host ? new List() : null); foreach (PendingCard pendingCardDelta in _pendingCardDeltas) { bool relayAnyway = false; bool flag = (_host ? ApplyCardDelta(pendingCardDelta.IsAdd, pendingCardDelta.Amount, pendingCardDelta.Card, out relayAnyway) : ApplyTrustedCardDelta(pendingCardDelta.IsAdd, pendingCardDelta.Amount, pendingCardDelta.Card)); if (_host && !flag && relayAnyway && GradedRemoveUncovered(pendingCardDelta.Card)) { list2.Add(pendingCardDelta); continue; } if (_host && (flag || relayAnyway) && pendingCardDelta.IsAdd) { ConsumeGradingOwnership(pendingCardDelta.ConnectionId, pendingCardDelta.Card, pendingCardDelta.Amount); } else if (_host && flag && !relayAnyway) { RecordGradingOwnership(pendingCardDelta.ConnectionId, pendingCardDelta.Card, pendingCardDelta.Amount); } if (_host && (flag || relayAnyway)) { list.Add(pendingCardDelta); } } if (_pendingCardDeltas.Count > 0) { CoopPlugin.Log.LogInfo((object)$"applied {_pendingCardDeltas.Count} card change(s) held during loading"); } _pendingCardDeltas.Clear(); if (list2 != null) { for (int i = 0; i < list2.Count; i++) { PendingCard pendingCard = list2[i]; CoopPlugin.Log.LogWarning((object)("graded removal held across loading has no album row left; refused for connection " + pendingCard.ConnectionId + ".")); if (pendingCard.IsGradedRemoval) { Broadcast((INetMessage)(object)new GradedRemoveMessage { PredictionId = Guid.Empty, Card = SnapshotCard(pendingCard.Card) }); } else { Broadcast((INetMessage)(object)new CardDeltaMessage { PredictionId = Guid.Empty, IsAdd = false, Amount = pendingCard.Amount, Card = SnapshotCard(pendingCard.Card) }); } PredictionHost.Reject(_context, pendingCard.ConnectionId, pendingCard.PredictionId); } } if (list == null) { return; } for (int j = 0; j < list.Count; j++) { PendingCard pending = list[j]; if (pending.IsGradedRemoval) { GradedRemoveMessage message = new GradedRemoveMessage { PredictionId = pending.PredictionId, Card = SnapshotCard(pending.Card) }; SendAcceptedPendingCard(pending, message); } else { CardDeltaMessage message2 = new CardDeltaMessage { PredictionId = pending.PredictionId, IsAdd = pending.IsAdd, Amount = pending.Amount, Card = SnapshotCard(pending.Card) }; SendAcceptedPendingCard(pending, message2); } } } private void SendAcceptedPendingCard(PendingCard pending, WorldMessage message) { if (pending.PredictionId != Guid.Empty && pending.ConnectionId > 0) { Send(pending.ConnectionId, (INetMessage)(object)message); } RelayToOthers(pending.ConnectionId, (INetMessage)(object)message); } internal void RelayToOthers(int senderConn, INetMessage message) { if (!_host || ConnectionCount <= 1) { return; } if (RelayOverride != null) { RelayOverride(senderConn, message); return; } foreach (int item in ConnectionIds()) { if (item != senderConn) { Send(item, message); } } } private static CardDeltaBatchMessage ToAuthoritative(CardDeltaBatchMessage message) { CardDeltaBatchMessage cardDeltaBatchMessage = new CardDeltaBatchMessage(); if (message?.Deltas == null) { return cardDeltaBatchMessage; } for (int i = 0; i < message.Deltas.Count; i++) { CardDeltaEntry cardDeltaEntry = message.Deltas[i]; cardDeltaBatchMessage.Deltas.Add(new CardDeltaEntry { IsAdd = cardDeltaEntry.IsAdd, Amount = cardDeltaEntry.Amount, Card = SnapshotCard(cardDeltaEntry.Card) }); } return cardDeltaBatchMessage; } private void RelayCardDeltaBatchToOthers(int senderConn, List deltas) { if (!_host || ConnectionCount <= 1 || deltas.Count == 0) { return; } CardDeltaBatchMessage cardDeltaBatchMessage = new CardDeltaBatchMessage(); for (int i = 0; i < deltas.Count; i++) { cardDeltaBatchMessage.Deltas.Add(new CardDeltaEntry { IsAdd = deltas[i].IsAdd, Amount = deltas[i].Amount, Card = deltas[i].Card }); } if (RelayOverride != null) { RelayOverride(senderConn, (INetMessage)(object)cardDeltaBatchMessage); return; } foreach (int item in ConnectionIds()) { if (item != senderConn) { Send(item, (INetMessage)(object)cardDeltaBatchMessage); } } } 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 (net " + ((_deltaNetThisFrame >= 0) ? "+" : "") + _deltaNetThisFrame + " cards)")); } _deltaLogBuf.Clear(); _deltaAppliedThisFrame = 0; _deltaNetThisFrame = 0; } if (_binderRefreshPending) { _binderRefreshPending = false; RefreshOpenBinder(); ClearBinderMirrorFrame(); } } } internal static class WorldCardInteractionPatches { [HarmonyPatch(typeof(CPlayerData), "AddCard")] private static class AddCardPatch { [HarmonyPostfix] private static void Postfix(CardData cardData, int addAmount) { WorldCardInteraction current = Current; if (current == null) { return; } if (WorldCardInteraction.ApplyingRemoteCards) { WorldCardInteraction.RecordCardChanged(cardData, addAmount, isAdd: true); } else { if (WorldCardInteraction.SuppressClientCardForwarding || SaveTransferApi.PreloadHold) { return; } if (current.HasClientCardBatch) { current.AddClientCardBatch(cardData, addAmount, isAdd: true); return; } if (current.IsClient) { current.PredictClientCardDelta(cardData, addAmount, isAdd: true); return; } int num = (GradingApi.Present ? GradingApi.Encoded(cardData) : cardData.cardGrade); if (num > 10 && cardData.cardGrade <= 10 && cardData.cardGrade != 0) { int cardGrade = cardData.cardGrade; cardData.cardGrade = num; try { current.ForwardCardDelta(cardData, addAmount, isAdd: true); return; } finally { cardData.cardGrade = cardGrade; } } current.ForwardCardDelta(cardData, addAmount, isAdd: true); } } } [HarmonyPatch(typeof(CPlayerData), "ReduceCard")] private static class ReduceCardPatch { [HarmonyPostfix] private static void Postfix(CardData cardData, int reduceAmount) { WorldCardInteraction current = Current; if (current == null) { return; } if (WorldCardInteraction.ApplyingRemoteCards) { WorldCardInteraction.RecordCardChanged(cardData, reduceAmount, isAdd: false); } else if (!WorldCardInteraction.SuppressClientCardForwarding && !SaveTransferApi.PreloadHold) { if (current.IsClient) { current.PredictClientCardDelta(cardData, reduceAmount, isAdd: false); } else { current.ForwardCardDelta(cardData, reduceAmount, isAdd: false); } } } } [HarmonyPatch(typeof(CPlayerData), "ReduceCardUsingIndex")] private static class ReduceCardIndexPatch { [HarmonyPostfix] private static void Postfix(int index, ECardExpansionType expansionType, bool isDestiny, int reduceAmount) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) WorldCardInteraction current = Current; if (current == null) { return; } if (WorldCardInteraction.ApplyingRemoteCards) { CardData cardData = CPlayerData.GetCardData(index, expansionType, isDestiny); if (cardData != null) { WorldCardInteraction.RecordCardChanged(cardData, reduceAmount, isAdd: false); } } else { if (WorldCardInteraction.SuppressClientCardForwarding || SaveTransferApi.PreloadHold) { return; } CardData cardData2 = CPlayerData.GetCardData(index, expansionType, isDestiny); if (cardData2 != null) { if (current.HasClientCardBatch) { current.AddClientCardBatch(cardData2, reduceAmount, isAdd: false); } else if (current.IsClient) { current.PredictClientCardDelta(cardData2, reduceAmount, isAdd: false); } else { current.ForwardCardDelta(cardData2, reduceAmount, isAdd: false); } } } } } [HarmonyPatch(typeof(CPlayerData), "RemoveGradedCard")] private static class RemoveGradedCardPatch { [HarmonyPrefix] private static void Prefix(out int __state) { __state = CPlayerData.m_GradedCardInventoryList?.Count ?? 0; } [HarmonyPostfix] private static void Postfix(CardData cardData, int __state) { WorldCardInteraction current = Current; if (current == null) { return; } if (WorldCardInteraction.ApplyingRemoteCards) { if (cardData != null && cardData.cardGrade > 0) { WorldCardInteraction.RecordCardChanged(cardData, 1, isAdd: false); } } else { if (WorldCardInteraction.SuppressClientCardForwarding || SaveTransferApi.PreloadHold || cardData == null || cardData.cardGrade <= 0) { return; } bool flag = (CPlayerData.m_GradedCardInventoryList?.Count ?? 0) < __state; if (current.IsClient) { current.PredictClientGradedRemoval(cardData, flag); } else { if (!flag) { return; } int num = (GradingApi.Present ? GradingApi.Encoded(cardData) : cardData.cardGrade); if (num > 10 && cardData.cardGrade <= 10) { int cardGrade = cardData.cardGrade; cardData.cardGrade = num; try { current.ForwardGradedRemoval(cardData); return; } finally { cardData.cardGrade = cardGrade; } } current.ForwardGradedRemoval(cardData); } } } } [HarmonyPatch(typeof(CollectionBinderFlipAnimCtrl), "OnRightMouseButtonUp")] private static class BinderTakeScopePatch { [HarmonyPrefix] private static void Prefix() { WorldCardInteraction.BeginBinderTake(); } [HarmonyFinalizer] private static void Finalizer() { WorldCardInteraction.EndBinderTake(); } } [HarmonyPatch(typeof(CollectionBinderFlipAnimCtrl), "HideCardAlbum")] private static class BinderCloseTakeScopePatch { [HarmonyPrefix] private static void Prefix() { WorldCardInteraction.BeginBinderTake(); } [HarmonyFinalizer] private static void Finalizer() { WorldCardInteraction.EndBinderTake(); } } private static WorldCardInteraction Current => WorldHostBehaviour.ActiveCards ?? WorldClientBehaviour.ActiveCards; internal static void Apply(Harmony harmony) { harmony.CreateClassProcessor(typeof(AddCardPatch)).Patch(); harmony.CreateClassProcessor(typeof(ReduceCardPatch)).Patch(); harmony.CreateClassProcessor(typeof(ReduceCardIndexPatch)).Patch(); harmony.CreateClassProcessor(typeof(RemoveGradedCardPatch)).Patch(); harmony.CreateClassProcessor(typeof(BinderTakeScopePatch)).Patch(); harmony.CreateClassProcessor(typeof(BinderCloseTakeScopePatch)).Patch(); } } [NetworkMessage] public class CardDeltaMessage : WorldMessage { public bool IsAdd; public int Amount; public CardData Card; } [NetworkMessage] public sealed class CardDeltaRequestMessage : CardDeltaMessage { } public struct CardDeltaEntry { public bool IsAdd; public int Amount; public CardData Card; } [NetworkMessage] public class CardDeltaBatchMessage : WorldMessage { public List Deltas = new List(); } [NetworkMessage] public sealed class CardDeltaBatchRequestMessage : CardDeltaBatchMessage { } [NetworkMessage] public class GradedRemoveMessage : WorldMessage { public CardData Card; } [NetworkMessage] public sealed class GradedRemoveRequestMessage : GradedRemoveMessage { } [ClientBehaviour] public sealed class WorldClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(RestockManager), "Update")] private static class RestockUpdatePatch { [HarmonyPrefix] private static void Prefix(RestockManager __instance) { _instance?._boxNetworkInteraction?.SuppressOutOfBoundsSweep(__instance); _instance?._boxNetworkInteraction?.BeginDeliverySpawns(); } [HarmonyPostfix] private static void Postfix() { _instance?._boxNetworkInteraction?.EndDeliverySpawns(); } } [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxItem")] private static class ItemBoxCreatedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __result) { _instance?.RejectCreatedBox((InteractablePackagingBox)(object)__result); } } [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxCard")] private static class CardBoxCreatedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Card __result) { _instance?.RejectCreatedBox((InteractablePackagingBox)(object)__result); } } [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxShelf")] private static class FurnitureBoxCreatedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Shelf __result) { _instance?.RejectCreatedBox((InteractablePackagingBox)(object)__result); } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnDestroyed")] private static class ItemBoxDestroyedPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item __instance, out BoxNetworkInteraction.ClientDestroyCapture __state) { __state = _instance?.CaptureDestroyedBox((InteractablePackagingBox)(object)__instance); } [HarmonyPostfix] private static void Postfix(BoxNetworkInteraction.ClientDestroyCapture __state) { _instance?.ForwardDestroyedBox(__state); } } [HarmonyPatch(typeof(InteractablePackagingBox_Card), "OnDestroyed")] private static class CardBoxDestroyedPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Card __instance, out BoxNetworkInteraction.ClientDestroyCapture __state) { __state = _instance?.CaptureDestroyedBox((InteractablePackagingBox)(object)__instance); } [HarmonyPostfix] private static void Postfix(BoxNetworkInteraction.ClientDestroyCapture __state) { _instance?.ForwardDestroyedBox(__state); } } [HarmonyPatch(typeof(InteractablePackagingBox_Shelf), "OnDestroyed")] private static class FurnitureBoxDestroyedPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Shelf __instance, out BoxNetworkInteraction.ClientDestroyCapture __state) { __state = _instance?.CaptureDestroyedBox((InteractablePackagingBox)(object)__instance); } [HarmonyPostfix] private static void Postfix(BoxNetworkInteraction.ClientDestroyCapture __state) { _instance?.ForwardDestroyedBox(__state); } } [HarmonyPatch(typeof(InteractableCardCompartment), "OnMouseButtonUp")] private static class CardDisplayPlacePatch { [HarmonyPrefix] private static void Prefix(InteractableCardCompartment __instance, out CardData __state) { __state = WorldCardDisplay.Read(__instance); } [HarmonyPostfix] private static void Postfix(InteractableCardCompartment __instance, CardData __state) { if (__state == null && WorldCardDisplay.Read(__instance) != null) { _instance?.PublishCardDisplayPlace(__instance); } } } [HarmonyPatch(typeof(InteractableCardCompartment), "OnRightMouseButtonUp")] private static class CardDisplayRemovePatch { [HarmonyPrefix] private static void Prefix(InteractableCardCompartment __instance, out InteractableCard3d __state) { List list = __instance?.m_StoredCardList; __state = ((list != null && list.Count > 0) ? list[0] : null); } [HarmonyPostfix] private static void Postfix(InteractableCardCompartment __instance, InteractableCard3d __state) { if ((Object)(object)__state != (Object)null && Object.op_Implicit((Object)(object)__state) && WorldCardDisplay.Read(__instance) == null) { _instance?.PublishCardDisplayRemove(__instance, __state); } } } [HarmonyPatch(typeof(ShowCardObtainedPage), "ShowCardObtained")] private static class CardObtainedBatchScopePatch { [HarmonyPrefix] private static void Prefix() { _instance?._cardInteraction?.BeginClientCardBatch(); } [HarmonyFinalizer] private static void Finalizer() { _instance?._cardInteraction?.CommitClientCardBatch(); } } [HarmonyPatch(typeof(InteractableObject), "BoxUpObject")] private static class PlacementHoldBoxUpPatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { _instance?._placementHold?.LocalEnded(__instance); } } [HarmonyPatch(typeof(InteractableObject), "AddObjectRotation")] private static class PlacementHoldRotatePatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { _instance?._placementHold?.LocalRotated(__instance); } } [HarmonyPatch(typeof(InteractableObject), "Flip")] private static class PlacementHoldFlipPatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { _instance?._placementHold?.LocalRotated(__instance); } } private sealed class FurnitureBoxUpCapture { internal InteractableObject Object; internal string EntityId; internal Vector3 Position; } [HarmonyPatch(typeof(InteractableObject), "BoxUpObject")] private static class FurnitureBoxUpPatch { [HarmonyPrefix] private static void Prefix(InteractableObject __instance, bool holdBox, out FurnitureBoxUpCapture __state) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!((Object)(object)_instance == (Object)null) && !((Object)(object)__instance == (Object)null) && holdBox && !_instance._applyingFurniturePrediction && !(__instance is InteractablePlayTable)) { _instance._placementHold?.LocalEnded(__instance); if (!PlacementApi.TryMakeBoxableFurnitureEntityId(__instance, 1L, out var entityId)) { CoopPlugin.Log.LogWarning((object)("[box-furniture] box-up has no placement identity; type=" + ((object)Unsafe.As(ref __instance.m_ObjectType)/*cast due to .constrained prefix*/).ToString() + "; not forwarding.")); return; } __state = new FurnitureBoxUpCapture { Object = __instance, EntityId = entityId, Position = ((Component)__instance).transform.position }; } } [HarmonyPostfix] private static void Postfix(FurnitureBoxUpCapture __state) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (__state != null && !((Object)(object)_instance == (Object)null)) { InteractableObject obj = __state.Object; InteractablePackagingBox_Shelf packagingBoxShelf = obj.GetPackagingBoxShelf(); Guid boxNetworkId = (((Object)(object)packagingBoxShelf != (Object)null && _instance._boxNetworkInteraction != null) ? _instance._boxNetworkInteraction.AssignClientCreated((InteractablePackagingBox)(object)packagingBoxShelf) : Guid.Empty); FurnitureBoxUpRequestMessage intent = new FurnitureBoxUpRequestMessage { ObjectType = obj.m_ObjectType, Position = __state.Position, StableEntityId = __state.EntityId, BoxNetworkId = boxNetworkId }; WorldPrediction.Predict("world.boxes", intent, delegate { _instance.ApplyPredictedFurnitureBoxUp(obj); }, delegate { _instance.UndoPredictedFurnitureBoxUp(obj); }); } } } [HarmonyPatch(typeof(InteractablePackagingBox), "StartHoldBox")] private static class PlayerBoxPickupPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox __instance, bool isPlayer, out PlayerBoxInteraction.LocalAction __state) { __state = (((Object)(object)_instance == (Object)null) ? default(PlayerBoxInteraction.LocalAction) : _instance.CapturePlayerBoxAction(__instance, isPlayer, alignBody: false)); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox __instance, PlayerBoxInteraction.LocalAction __state) { _instance?.PublishPlayerBoxPickup(__instance, __state); } } [HarmonyPatch(typeof(InteractablePackagingBox), "ThrowBox")] private static class PlayerBoxThrowPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox __instance, bool isPlayer, out PlayerBoxInteraction.LocalAction __state) { if (isPlayer && __instance != null) { ((InteractableObject)__instance).StopLerpToTransform(); } __state = (((Object)(object)_instance == (Object)null) ? default(PlayerBoxInteraction.LocalAction) : _instance.CapturePlayerBoxAction(__instance, isPlayer, alignBody: true)); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox __instance, PlayerBoxInteraction.LocalAction __state) { _instance?.PublishPlayerBoxThrow(__instance, __state); } } [HarmonyPatch(typeof(InteractablePackagingBox), "DropBox")] private static class PlayerBoxDropPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox __instance, bool isPlayer) { if (isPlayer && __instance != null) { ((InteractableObject)__instance).StopLerpToTransform(); } } } [HarmonyPatch(typeof(InteractableObject), "PlaceMovedObject")] private static class PlayerBoxPlacementPatch { [HarmonyPrefix] private static void Prefix(InteractableObject __instance, out PlayerBoxInteraction.LocalAction __state) { PlayerBoxInteraction.LocalAction obj; if ((Object)(object)_instance != (Object)null) { InteractablePackagingBox val = (InteractablePackagingBox)(object)((__instance is InteractablePackagingBox) ? __instance : null); if (val != null && PlacementInterop.ReadMoveValidity((InteractableObject)(object)val)) { obj = _instance.CapturePlayerBoxAction(val, isPlayer: true, alignBody: false); goto IL_0038; } } obj = default(PlayerBoxInteraction.LocalAction); goto IL_0038; IL_0038: __state = obj; } [HarmonyPostfix] private static void Postfix(InteractableObject __instance, PlayerBoxInteraction.LocalAction __state) { InteractablePackagingBox val = (InteractablePackagingBox)(object)((__instance is InteractablePackagingBox) ? __instance : null); if (val != null) { _instance?.PublishPlayerBoxPlacement(val, __state); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "SetOpenCloseBox")] private static class ItemBoxOpenStatePatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance, bool isPlayer) { if (isPlayer) { _instance?.PublishItemBoxState(__instance, contentsChanged: false); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "DispenseItem")] private static class ItemBoxContentsPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item __instance, out int __state) { int? obj; if (__instance == null) { obj = null; } else { ShelfCompartment itemCompartment = __instance.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance, bool isPlayer, int __state) { if (isPlayer && (Object)(object)__instance?.m_ItemCompartment != (Object)null && __instance.m_ItemCompartment.GetItemCount() != __state) { _instance?.PublishItemBoxState(__instance, contentsChanged: true); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "RemoveItemFromShelf")] private static class ItemBoxRemoveFromShelfPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance, bool isPlayer) { if (isPlayer) { _instance?.PublishItemBoxState(__instance, contentsChanged: true); } } } [HarmonyPatch(typeof(InteractableAutoPackOpener), "DispenseItemFromBox")] private static class PackOpenerDispenseFromBoxPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item itemBox, out int __state) { int? obj; if (itemBox == null) { obj = null; } else { ShelfCompartment itemCompartment = itemBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item itemBox, int __state) { PublishDispensedSourceBox(itemBox, __state); } } [HarmonyPatch(typeof(InteractableAutoCleanser), "DispenseItemFromBox")] private static class CleanserDispenseFromBoxPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item itemBox, out int __state) { int? obj; if (itemBox == null) { obj = null; } else { ShelfCompartment itemCompartment = itemBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item itemBox, int __state) { PublishDispensedSourceBox(itemBox, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "AddItem")] private static class AddItemPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, Item item, bool addToFront, out ShelfInteraction.LocalMutation __state) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) __state = (((Object)(object)_instance == (Object)null) ? default(ShelfInteraction.LocalMutation) : _instance.CaptureShelfMutation(__instance, add: true, (EItemType)(((Object)(object)item == (Object)null) ? (-1) : ((int)item.GetItemType())), item)); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, ShelfInteraction.LocalMutation __state) { _instance?.PublishShelfAdd(__instance, __state); WorldClientBehaviour instance = _instance; InteractablePackagingBox_Item val = instance?.FindItemBox(__instance); if ((Object)(object)val != (Object)null && !instance.IsBoxHeld((InteractablePackagingBox)(object)val)) { instance.PublishItemBoxState(val, contentsChanged: true); } } } [HarmonyPatch(typeof(InteractionPlayerController), "EvaluatePutItemOnShelf")] private static class PutItemOnShelfScopePatch { [HarmonyPrefix] private static void Prefix(out bool __state) { __state = (Object)(object)_instance != (Object)null; if (__state) { _instance.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(InteractionPlayerController), "EvaluateTakeItemFromShelf")] private static class TakeItemFromShelfScopePatch { [HarmonyPrefix] private static void Prefix(out bool __state) { __state = (Object)(object)_instance != (Object)null; if (__state) { _instance.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnHoldStateLeftMousePress")] private static class BoxAddToShelfScopePatch { [HarmonyPrefix] private static void Prefix(bool isPlayer, out bool __state) { __state = isPlayer; if (__state) { _instance?.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnHoldStateRightMousePress")] private static class BoxRemoveFromShelfScopePatch { [HarmonyPrefix] private static void Prefix(bool isPlayer, out bool __state) { __state = isPlayer; if (__state) { _instance?.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(ShelfCompartment), "RemoveItem")] private static class RemoveItemPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, Item item, out ShelfInteraction.LocalMutation __state) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) __state = (((Object)(object)_instance == (Object)null) ? default(ShelfInteraction.LocalMutation) : _instance.CaptureShelfMutation(__instance, add: false, (EItemType)(((Object)(object)item == (Object)null) ? (-1) : ((int)item.GetItemType())), item)); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, ShelfInteraction.LocalMutation __state) { _instance?.PublishShelfRemove(__instance, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "TakeItemToHand")] private static class TakeItemToHandPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, bool getLastItem, out ShelfInteraction.LocalMutation __state) { __state = (((Object)(object)_instance == (Object)null) ? default(ShelfInteraction.LocalMutation) : _instance.CaptureShelfMutation(__instance, add: false, (EItemType)(-1), null)); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, Item __result, ShelfInteraction.LocalMutation __state) { if ((Object)(object)__result != (Object)null) { __state.Removed = __result; _instance?.PublishShelfRemove(__instance, __state); WorldClientBehaviour instance = _instance; InteractablePackagingBox_Item val = instance?.FindItemBox(__instance); if ((Object)(object)val != (Object)null) { instance.PublishItemBoxState(val, contentsChanged: true); } } } } [HarmonyPatch(typeof(ShelfCompartment), "SetCompartmentItemType")] private static class SetCompartmentItemTypePatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out EItemType __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) __state = (EItemType)(((Object)(object)__instance == (Object)null) ? (-1) : ((int)__instance.GetItemType())); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, EItemType __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _instance?.PublishLabelChange(__instance, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "RemoveLabel")] private static class RemoveLabelPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out EItemType __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) __state = (EItemType)(((Object)(object)__instance == (Object)null) ? (-1) : ((int)__instance.GetItemType())); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, EItemType __state) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.GetWarehouseShelf() != (Object)null) { _instance?.PublishLabelChange(__instance, __state, allowWarehouse: true); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnFinishLerp")] private static class BoxRecordStoreCompletePatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item __instance) { _instance?.Warehouse?.BeginLocalRecordStore(__instance); } [HarmonyPostfix] private static void Postfix() { _instance?.Warehouse?.EndLocalRecordStore(); } [HarmonyFinalizer] private static void Finalizer() { _instance?.Warehouse?.EndLocalRecordStore(); } } [HarmonyPatch(typeof(InteractableStorageCompartment), "OnMouseButtonUp")] private static class WarehouseTakeObservePatch { [HarmonyPrefix] private static void Prefix(InteractableStorageCompartment __instance, out WarehouseShelfInteraction.WarehouseTakeCapture __state) { __state = _instance?.Warehouse?.CaptureClientTake(__instance); } [HarmonyPostfix] private static void Postfix(WarehouseShelfInteraction.WarehouseTakeCapture __state) { if ((Object)(object)_instance == (Object)null || __state == null) { return; } try { _instance.Warehouse.ObserveClientTake(__state); } finally { _instance.Warehouse.EndCoveredTake(__state); } } [HarmonyFinalizer] private static void Finalizer(WarehouseShelfInteraction.WarehouseTakeCapture __state) { _instance?.Warehouse?.EndCoveredTake(__state); } } [HarmonyPatch(typeof(ShelfCompartment), "AddStoredBoxRecord")] private static class StoredBoxRecordAddedPatch { [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance) { _instance?.NotifyStoredBoxRecordAdded(__instance); } } [HarmonyPatch(typeof(ShelfCompartment), "ArrangeBoxItemBasedOnItemCount")] private static class StoredBoxRecordArrangePatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out List __state) { __state = _instance?.Warehouse?.CaptureRecordOrder(__instance); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, List __state) { _instance?.Warehouse?.ApplyRecordOrder(__instance, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "TryPopLastStoredBoxRecord")] private static class StoredBoxRecordPoppedPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out WarehouseBoxState __state) { __state = _instance?.Warehouse?.CapturePoppedRecord(__instance); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, bool __result, WarehouseBoxState __state) { _instance?.NotifyStoredBoxRecordPopped(__instance, __result, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "AddBox")] private static class BoxAddedPatch { [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance) { _instance?.NotifyWarehouseBoxAdded(__instance); } } [HarmonyPatch(typeof(ShelfCompartment), "RemoveBox")] private static class BoxRemovedPatch { [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, InteractablePackagingBox_Item __0) { _instance?.NotifyWarehouseBoxRemoved(__instance, __0); } } [HarmonyPatch(typeof(InteractableWorkbench), "AddItem")] private static class ClientWorkbenchAddItemPatch { [HarmonyPrefix] private static void Prefix(InteractableWorkbench __instance, out List __state) { __state = (((Object)(object)_instance == (Object)null) ? null : WorldWorkbenchInteraction.TypesOf(__instance)); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, List __state) { _instance?.PublishWorkbenchStorage(__instance, __state); } } [HarmonyPatch(typeof(InteractableWorkbench), "RemoveItem")] private static class ClientWorkbenchRemoveItemPatch { [HarmonyPrefix] private static void Prefix(InteractableWorkbench __instance, out List __state) { __state = (((Object)(object)_instance == (Object)null) ? null : WorldWorkbenchInteraction.TypesOf(__instance)); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, List __state) { _instance?.PublishWorkbenchStorage(__instance, __state); } } [HarmonyPatch(typeof(InteractableWorkbench), "TakeItemToHand")] private static class ClientWorkbenchTakeItemToHandPatch { [HarmonyPrefix] private static void Prefix(InteractableWorkbench __instance, out List __state) { __state = (((Object)(object)_instance == (Object)null) ? null : WorldWorkbenchInteraction.TypesOf(__instance)); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, List __state) { _instance?.PublishWorkbenchStorage(__instance, __state); } } [HarmonyPatch(typeof(InteractableWorkbench), "DispenseItemFromBox")] private static class ClientWorkbenchDispenseFromBoxPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item itemBox, out int __state) { int? obj; if (itemBox == null) { obj = null; } else { ShelfCompartment itemCompartment = itemBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, InteractablePackagingBox_Item itemBox, int __state) { if (!WorldWorkbenchInteraction.ApplyingRemote && (Object)(object)itemBox?.m_ItemCompartment != (Object)null && itemBox.m_ItemCompartment.GetItemCount() != __state) { _instance?.PublishItemBoxState(itemBox, contentsChanged: true); } } } [HarmonyPatch(typeof(InteractableWorkbench), "RemoveItemFromShelf")] private static class ClientWorkbenchRemoveFromShelfPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item packageBox, out int __state) { int? obj; if (packageBox == null) { obj = null; } else { ShelfCompartment itemCompartment = packageBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, InteractablePackagingBox_Item packageBox, int __state) { if (!WorldWorkbenchInteraction.ApplyingRemote && (Object)(object)packageBox?.m_ItemCompartment != (Object)null && packageBox.m_ItemCompartment.GetItemCount() != __state) { _instance?.PublishItemBoxState(packageBox, contentsChanged: true); } } } [HarmonyPatch(typeof(WorkbenchUIScreen), "OpenBundleCardScreen")] private static class ClientWorkbenchBundleScopePatch { [HarmonyPrefix] private static void Prefix() { _instance?._cardInteraction?.BeginClientCardBatch(); } [HarmonyPostfix] private static void Postfix() { _instance?._cardInteraction?.CommitClientCardBatch(); } } [HarmonyPatch(typeof(InteractableWorkbench), "PlayBundlingCardBoxSequence")] private static class ClientWorkbenchBundleStartPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?._workbenchInteraction?.ClientBundleStarted(__instance); } } [HarmonyPatch(typeof(InteractableWorkbench), "OnTaskCompleted")] private static class ClientWorkbenchTaskCompletedPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?._workbenchInteraction?.ClientCompleteBundle(__instance); } } private bool _cardDisplayApplying; private readonly Dictionary _pendingCardDisplay = new Dictionary(); private static WorldClientBehaviour _instance; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private BoxNetworkInteraction _boxNetworkInteraction; private PlayerBoxInteraction _playerBoxInteraction; private ShelfInteraction _shelfInteraction; private WarehouseShelfInteraction _warehouseShelfInteraction; private WorldCardInteraction _cardInteraction; private const string PredictionScope = "placement"; private readonly PlacementPopulationState _placementPopulation = new PlacementPopulationState(); private readonly Dictionary _pendingDeltas = new Dictionary(); private PlacementBaselineMessage _latestBaseline; private InteractableObject _movingObject; private PlacementMoveEntry _movingBefore; private bool _identityReady; private int _applyingState; private PlacementHoldInteraction _placementHold; private bool _applyingFurniturePrediction; private WorldWorkbenchInteraction _workbenchInteraction; internal static WorldCardInteraction ActiveCards => _instance?._cardInteraction; internal bool IdentityReady => _identityReady; internal static bool IsPlacementIdentityReady { get { if (!((Object)(object)_instance == (Object)null)) { return _instance._identityReady; } return true; } } private WarehouseShelfInteraction Warehouse => _warehouseShelfInteraction; private void InstallBoxNetworkInteractionPatches() { _harmony.CreateClassProcessor(typeof(ItemBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CardBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(FurnitureBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ItemBoxDestroyedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CardBoxDestroyedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(FurnitureBoxDestroyedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(RestockUpdatePatch)).Patch(); } [MessageHandler(typeof(BoxCreatedMessage))] private void HandleBoxCreated(MessageContext context, BoxCreatedMessage message) { WorldPrediction.Ack(message); WorldPrediction.AckOrApply(message, delegate { _boxNetworkInteraction.ClientApplyCreated(message); }); } [MessageHandler(typeof(BoxDestroyedMessage))] private void HandleBoxDestroyed(MessageContext context, BoxDestroyedMessage message) { WorldPrediction.AckOrApply(message, delegate { _boxNetworkInteraction.ClientApplyDestroyed(message); }); _playerBoxInteraction?.ForgetDestroyed(message.BoxNetworkId); } [MessageHandler(typeof(BoxStateMessage))] private void HandleBoxState(MessageContext context, BoxStateMessage message) { _boxNetworkInteraction?.ClientApplyBoxState(message); } [MessageHandler(typeof(BoxBaselineCompleteMessage))] private void HandleBoxBaselineComplete(MessageContext context, BoxBaselineCompleteMessage message) { _boxNetworkInteraction.ClientCompleteBaseline(); } [MessageHandler(typeof(WorldBaselineCompleteMessage))] private void HandleWorldBaselineComplete(MessageContext context, WorldBaselineCompleteMessage message) { CompleteWorldBaseline(message.BaselineId); } [MessageHandler(typeof(WorldBaselineStartMessage))] private void HandleWorldBaselineStart(MessageContext context, WorldBaselineStartMessage message) { BeginWorldBaseline(message.BaselineId); } private void RejectCreatedBox(InteractablePackagingBox box) { CoopRuntimeContext context = _context; if (context == null || context.PreloadHold?.Invoke() != true) { _boxNetworkInteraction?.AssignClientCreated(box); } } private BoxNetworkInteraction.ClientDestroyCapture CaptureDestroyedBox(InteractablePackagingBox box) { _playerBoxInteraction?.ReleaseHeldObject(box); return _boxNetworkInteraction?.CaptureClientDestroyed(box); } private void ForwardDestroyedBox(BoxNetworkInteraction.ClientDestroyCapture capture) { _boxNetworkInteraction?.ForwardClientDestroyed(capture); } internal static void PushClientDeliveryIds(IEnumerable ids) { _instance?._boxNetworkInteraction?.PushClientDeliveryIds(ids); } internal static void PushClientCreatedId(Guid id) { _instance?._boxNetworkInteraction?.PushClientCreatedId(id); } internal static bool TryGetBoxId(InteractablePackagingBox box, out Guid id) { id = Guid.Empty; if (_instance?._boxNetworkInteraction != null) { return _instance._boxNetworkInteraction.TryGetId(box, out id); } return false; } internal static void RollbackClientCreated(IReadOnlyList ids) { _instance?._boxNetworkInteraction?.RollbackClientCreated(ids); } internal static void ReleaseUnspawnedClientIds(IReadOnlyList ids) { _instance?._boxNetworkInteraction?.ReleaseUnspawnedClientIds(ids); } internal static int PendingClientDeliveryCount() { return (_instance?._boxNetworkInteraction?.PendingDeliveryCount()).GetValueOrDefault(); } internal static void CancelPendingClientDeliveries(int appended) { _instance?._boxNetworkInteraction?.CancelPendingClientDeliveries(appended); } private void InstallCardDisplayPatches() { _harmony.CreateClassProcessor(typeof(CardDisplayPlacePatch)).Patch(); _harmony.CreateClassProcessor(typeof(CardDisplayRemovePatch)).Patch(); PlacementApi.StructureChanged += OnCardDisplayStructureChanged; } private void ShutdownCardDisplay() { PlacementApi.StructureChanged -= OnCardDisplayStructureChanged; _pendingCardDisplay.Clear(); } internal void ResetCardDisplayState() { foreach (CardDisplayMessage value in _pendingCardDisplay.Values) { WorldPrediction.Ack(value); } _pendingCardDisplay.Clear(); _cardDisplayApplying = false; } private void OnCardDisplayStructureChanged(int kind) { RetryPendingCardDisplay(); } [MessageHandler(typeof(CardDisplayMessage))] private void HandleCardDisplay(MessageContext context, CardDisplayMessage message) { if (_shutdown || message == null) { return; } if (!CanApplyCardDisplay(message)) { long key = CardDisplayKey(message); if (_pendingCardDisplay.TryGetValue(key, out var value)) { WorldPrediction.Ack(value); } _pendingCardDisplay[key] = message; } else { WorldPrediction.Confirm(message, delegate { ApplyCardDisplay(message); }); } } private void RetryPendingCardDisplay() { if (_shutdown || _pendingCardDisplay.Count == 0) { return; } List list = new List(_pendingCardDisplay.Values); for (int i = 0; i < list.Count; i++) { CardDisplayMessage message = list[i]; if (CanApplyCardDisplay(message)) { _pendingCardDisplay.Remove(CardDisplayKey(message)); WorldPrediction.Confirm(message, delegate { ApplyCardDisplay(message); }); } } } private static long CardDisplayKey(CardDisplayMessage message) { return ((long)message.ShelfKey << 32) ^ (uint)message.Compartment; } private static string CardDisplayPredictionKey(int shelfKey, int compartment) { return "world.carddisplay:" + shelfKey + ":" + compartment; } private static bool CanApplyCardDisplay(CardDisplayMessage message) { InteractableCardCompartment compartment; return WorldCardDisplay.TryResolve(message.ShelfKey, message.Compartment, out compartment); } private void ApplyCardDisplay(CardDisplayMessage message) { if (!WorldCardDisplay.TryResolve(message.ShelfKey, message.Compartment, out var compartment)) { return; } _cardDisplayApplying = true; try { if (!message.Occupied) { WorldCardDisplay.Clear(compartment); return; } CardData val = WorldCardDisplay.FromState(message.Card); if (val != null && !WorldCardDisplay.Matches(compartment, val, message.EncodedGrade)) { if (compartment.m_StoredCardList != null && compartment.m_StoredCardList.Count > 0) { WorldCardDisplay.Clear(compartment); } WorldCardDisplay.Place(compartment, val); } } finally { _cardDisplayApplying = false; } } private void PublishCardDisplayPlace(InteractableCardCompartment compartment) { if (_shutdown || _cardDisplayApplying || (Object)(object)compartment == (Object)null || !_context.InGame()) { return; } if (!WorldCardDisplay.TryMakeKey(compartment, out var shelfKey, out var compartmentIndex)) { CoopPlugin.Log.LogWarning((object)"[card-display] placement dropped: display shelf has no placement identity."); return; } CardData card = WorldCardDisplay.Read(compartment); BoxCardState boxCardState = WorldCardDisplay.ToState(card); if (card != null && boxCardState != null) { int encodedGrade = WorldCardDisplay.EncodedGradeOf(card); CoopPlugin.Log.LogInfo((object)("[card-display] forwarding placement shelf=" + shelfKey + " compartment=" + compartmentIndex + " card=" + ((object)Unsafe.As(ref card.monsterType)/*cast due to .constrained prefix*/).ToString() + ".")); WorldPrediction.Predict(CardDisplayPredictionKey(shelfKey, compartmentIndex), new CardDisplayRequestMessage { ShelfKey = shelfKey, Compartment = compartmentIndex, Occupied = true, Card = boxCardState, EncodedGrade = encodedGrade }, delegate { }, delegate { UndoCardDisplayPlace(compartment, card); }); } } private void PublishCardDisplayRemove(InteractableCardCompartment compartment, InteractableCard3d card3d) { if (_shutdown || _cardDisplayApplying || (Object)(object)compartment == (Object)null || (Object)(object)card3d == (Object)null || !Object.op_Implicit((Object)(object)card3d) || !_context.InGame()) { return; } Card3dUIGroup card3dUI = card3d.m_Card3dUI; object obj; if (card3dUI == null) { obj = null; } else { CardUI cardUI = card3dUI.m_CardUI; obj = ((cardUI != null) ? cardUI.GetCardData() : null); } CardData card = (CardData)obj; if (card == null) { return; } if (!WorldCardDisplay.TryMakeKey(compartment, out var shelfKey, out var compartmentIndex)) { CoopPlugin.Log.LogWarning((object)"[card-display] removal dropped: display shelf has no placement identity."); return; } BoxCardState boxCardState = WorldCardDisplay.ToState(card); if (boxCardState != null) { int encodedGrade = WorldCardDisplay.EncodedGradeOf(card); CoopPlugin.Log.LogInfo((object)("[card-display] forwarding removal shelf=" + shelfKey + " compartment=" + compartmentIndex + " card=" + ((object)Unsafe.As(ref card.monsterType)/*cast due to .constrained prefix*/).ToString() + ".")); WorldPrediction.Predict(CardDisplayPredictionKey(shelfKey, compartmentIndex), new CardDisplayRequestMessage { ShelfKey = shelfKey, Compartment = compartmentIndex, Occupied = false, Card = boxCardState, EncodedGrade = encodedGrade }, delegate { RedoCardDisplayRemove(compartment, card3d); }, delegate { UndoCardDisplayRemove(compartment, card3d, card); }); } } private static void UndoCardDisplayPlace(InteractableCardCompartment compartment, CardData card) { WorldCardDisplay.Clear(compartment); if (card != null) { CPlayerData.AddCard(card, 1); } } private static void RedoCardDisplayRemove(InteractableCardCompartment compartment, InteractableCard3d card3d) { if (!((Object)(object)compartment == (Object)null) && !((Object)(object)card3d == (Object)null) && Object.op_Implicit((Object)(object)card3d)) { WorldCardDisplay.TakeCardIntoHand(compartment, card3d); } } private static void UndoCardDisplayRemove(InteractableCardCompartment compartment, InteractableCard3d card3d, CardData card) { if ((Object)(object)compartment == (Object)null) { return; } if ((Object)(object)card3d != (Object)null && Object.op_Implicit((Object)(object)card3d)) { if (WorldCardDisplay.ReturnHeldCardToShelf(compartment, card3d)) { return; } if (WorldCardDisplay.IsHeld(card3d)) { CoopPlugin.Log.LogWarning((object)"[card-display] removal undo skipped: the held card could not be returned through the game's hold teardown."); return; } } if (card != null) { WorldCardDisplay.Clear(compartment); WorldCardDisplay.Place(compartment, card); } } private void InstallCardInteractionPatches() { WorldCardInteractionPatches.Apply(_harmony); WorldContainerInteraction.ApplyClientPatches(_harmony); WorldMarketInteraction.ApplyPatches(_harmony); _harmony.CreateClassProcessor(typeof(CardObtainedBatchScopePatch)).Patch(); } [MessageHandler(typeof(MarketStateMessage))] private void HandleMarketState(MessageContext context, MarketStateMessage message) { _cardInteraction.Market.ClientApplyOrBuffer(message, _context.InGame()); } [MessageHandler(typeof(CardDeltaMessage))] private void HandleCardDelta(MessageContext context, CardDeltaMessage message) { WorldPrediction.AckOrApply(message, delegate { _cardInteraction.HandleCardDelta(context.ConnectionId, message); }); } [MessageHandler(typeof(CardDeltaBatchMessage))] private void HandleCardDeltaBatch(MessageContext context, CardDeltaBatchMessage message) { WorldPrediction.AckOrApply(message, delegate { _cardInteraction.HandleCardDeltaBatch(context.ConnectionId, message); }); } [MessageHandler(typeof(GradedRemoveMessage))] private void HandleGradedRemove(MessageContext context, GradedRemoveMessage message) { WorldPrediction.AckOrApply(message, delegate { _cardInteraction.HandleGradedRemove(context.ConnectionId, message); }); } [MessageHandler(typeof(ContainerStateMessage))] private void HandleContainerState(MessageContext context, ContainerStateMessage message) { _cardInteraction.Containers.ClientApplyState(message); } [MessageHandler(typeof(ContainerPackClaimMessage))] private void HandleContainerPackClaim(MessageContext context, ContainerPackClaimMessage message) { WorldPrediction.Confirm(message, delegate { _cardInteraction.Containers.ClientApplyPackClaim(message); }); } [MessageHandler(typeof(ContainerDeltaMessage))] private void HandleContainerDelta(MessageContext context, ContainerDeltaMessage message) { _cardInteraction.Containers.ClientApplyDelta(message); } private void OnEnable() { //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Expected O, but got Unknown if (_context == null) { _context = base.RuntimeContext; _instance = this; CEventManager.AddListener((EventDelegate)OnWorldReady); _context.Messages.RegisterAttributedHandlers(this); _boxNetworkInteraction = new BoxNetworkInteraction(host: false, delegate { }, SendWorldCommand); _playerBoxInteraction = new PlayerBoxInteraction(_boxNetworkInteraction, SendWorldCommand); _shelfInteraction = new ShelfInteraction(SendWorldCommand); _warehouseShelfInteraction = new WarehouseShelfInteraction(host: false, delegate { }, SendWorldCommand, _boxNetworkInteraction, _playerBoxInteraction); _cardInteraction = new WorldCardInteraction(_context, host: false, _boxNetworkInteraction, _playerBoxInteraction) { SendOverride = SendWorldCommand }; PredictionApi.PredictionRetired += WorldCardInteraction.OnPredictionRetired; _cardInteraction.Containers.SendOp = delegate(INetMessage message) { SendWorldCommand(1, message); }; _cardInteraction.Containers.InGameProvider = _context.InGame; _cardInteraction.Containers.ReloadingProvider = _context.PreloadHold; _harmony = new Harmony("dev.meepen.cardshopcoop.world.client"); InstallBoxNetworkInteractionPatches(); InstallPlayerBoxInteractionPatches(); InstallPlayerBoxPresenceHook(); InstallPlayerShelfInteractionPatches(); InstallWarehouseShelfInteractionPatches(); InstallCardInteractionPatches(); InstallCardDisplayPatches(); InstallPlacement(); InstallPlacementHold(); InstallWorkbench(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; UninstallPlayerBoxPresenceHook(); ShutdownPlacementHold(); ShutdownPlacement(); ShutdownCardDisplay(); ShutdownWorkbench(); ResetPlayerBoxInteractionState(); ResetPlayerShelfInteractionState(); _boxNetworkInteraction?.Dispose(); _boxNetworkInteraction = null; _warehouseShelfInteraction?.Reset(); _warehouseShelfInteraction = null; PredictionApi.PredictionRetired -= WorldCardInteraction.OnPredictionRetired; _cardInteraction?.Shutdown(); _cardInteraction = null; CEventManager.RemoveListener((EventDelegate)OnWorldReady); _context.Messages.UnregisterAttributedHandlers(this); if (_instance == this) { _instance = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnDestroy() { Shutdown(); } private void Update() { if (!_shutdown) { _cardInteraction?.Tick(); TickPlacementHold(); } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { WorldMarketInteraction.InvalidateContentCache(); _cardInteraction?.FlushWorldReady(); _warehouseShelfInteraction?.FlushClientState(); FlushPlayerShelfState(); } [OnFullyJoined] private void SendWorldReady(PeerConnection connection) { if (_context.InGame()) { _cardInteraction?.FlushWorldReady(); _warehouseShelfInteraction?.FlushClientState(); RequestPlacementBaseline(connection); } } private void SendWorldCommand(int connectionId, INetMessage message) { if (!(message is WorldMessage worldMessage)) { throw new InvalidOperationException("World client can only send WorldMessage values."); } if (worldMessage.PredictionId == Guid.Empty) { worldMessage.PredictionId = Guid.NewGuid(); } WorldMessageMetadata.PrepareIntent(worldMessage); _context.Send(connectionId, (INetMessage)(object)worldMessage); } internal static bool SendClientIntent(WorldMessage command) { return SendClientIntent(command, (command == null || command.PredictionId == Guid.Empty) ? Guid.NewGuid() : command.PredictionId); } internal static bool SendClientIntent(WorldMessage command, Guid predictionId) { if ((Object)(object)_instance == (Object)null || command == null) { return false; } command.PredictionId = predictionId; WorldMessageMetadata.PrepareIntent(command); _instance._context.Send(1, (INetMessage)(object)command); return true; } internal static void BeginWorldBaseline(long baselineId) { if (!((Object)(object)_instance == (Object)null)) { _instance._playerBoxInteraction?.Reset(); _instance._shelfInteraction?.Reset(); _instance._warehouseShelfInteraction?.Reset(); _instance._workbenchInteraction?.Reset(); _instance._boxNetworkInteraction?.ClientBeginBaseline(); _instance.ResetCardDisplayState(); } } internal static void CompleteWorldBaseline(long baselineId) { if (!((Object)(object)_instance == (Object)null)) { _instance._boxNetworkInteraction?.ClientCompleteBaseline(); } } internal static bool IsKnownPackagingBox(InteractablePackagingBox box) { WorldClientBehaviour instance = _instance; if (instance == null) { return false; } return instance._boxNetworkInteraction?.IsKnownBox(box) == true; } internal static void ApplyWorkerHeldBox(Guid boxNetworkId) { _instance?._warehouseShelfInteraction?.ApplyWorkerHeldBox(boxNetworkId); } internal static void RestoreWorkerDroppedBox(Guid boxNetworkId) { _instance?._warehouseShelfInteraction?.RestoreWorkerDroppedBox(boxNetworkId); } internal static void ResetCardState() { _instance?._cardInteraction?.Reset(); } internal void InstallPlacement() { PlacementInterop.ProbeBetaSurface(); SceneManager.sceneLoaded += OnPlacementSceneLoaded; InstallPlacementPatches(); } internal void ShutdownPlacement() { SceneManager.sceneLoaded -= OnPlacementSceneLoaded; _placementPopulation.Reset(); ClearPendingDeltas(); _latestBaseline = null; _identityReady = false; _movingObject = null; _movingBefore = null; } [MessageHandler(typeof(PlacementBaselineMessage))] private void HandleBaseline(MessageContext _, PlacementBaselineMessage message) { _latestBaseline = message; ApplyLatestBaseline(); } private void RequestPlacementBaseline(PeerConnection connection) { if (connection != null) { CoopPlugin.Log.LogInfo((object)"[placement] fully connected; requesting a fresh baseline."); _context.Send(connection.Id, (INetMessage)(object)new PlacementBaselineRequestMessage()); } } [MessageHandler(typeof(PlacementDeltaMessage))] private void HandleDelta(MessageContext _, PlacementDeltaMessage message) { if (!_shutdown && message != null && message.Entity != null) { if (!TryApplyDelta(message)) { CoopPlugin.Log.LogInfo((object)("[placement] defer delta op=" + message.Operation + " key=" + message.Entity.Key + ".")); DeferDelta(message); } else { DiscardSupersededDelta(message.Entity.Key); CoopPlugin.Log.LogInfo((object)("[placement] applied delta op=" + message.Operation + " key=" + message.Entity.Key + ".")); } } } private void DiscardSupersededDelta(int key) { if (_pendingDeltas.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); _pendingDeltas.Remove(key); } } private void ApplyLatestBaseline() { if (_latestBaseline == null || (Object)(object)PlacementInterop.FindShelfManager() == (Object)null) { return; } _applyingState++; try { if (!_placementPopulation.Apply(_latestBaseline.Population)) { return; } for (int i = 0; i < _latestBaseline.Moves.Count; i++) { PlacementMoveEntry placementMoveEntry = _latestBaseline.Moves[i]; if (!PlacementMoveState.Apply(placementMoveEntry, settle: false)) { CoopPlugin.Log.LogWarning((object)("[placement] baseline move key=" + (placementMoveEntry?.Key ?? (-1)) + " type=" + (placementMoveEntry?.Type ?? (-1)) + " did not resolve; skipping.")); } } _identityReady = true; _latestBaseline = null; } finally { _applyingState--; } ApplyPendingDeltas(); PlacementInterop.NotifyStructureChanged(-1); } private void ApplyPendingDeltas() { if (!_identityReady) { return; } List list = new List(_pendingDeltas.Values); for (int i = 0; i < list.Count; i++) { if (TryApplyDelta(list[i])) { _pendingDeltas.Remove(list[i].Entity.Key); } } } internal static void RetryDeferredPlacementDeltas() { _instance?.ApplyPendingDeltas(); } private bool TryApplyDelta(PlacementDeltaMessage message) { if ((Object)(object)PlacementInterop.FindShelfManager() == (Object)null) { return false; } if (message.Operation != 3 && !_identityReady) { return false; } if (message.Operation != 3 && !CanResolveStableKey(message.Entity)) { return false; } _applyingState++; try { PredictionApi.AckOrApply(message.PredictionId, delegate { if (!PlacementEntityState.Apply(message)) { throw new InvalidOperationException("placement delta could not be applied for key=" + message.Entity.Key.ToString("X")); } }); } finally { _applyingState--; } PlacementInterop.NotifyStructureChanged(-1); return true; } private void DeferDelta(PlacementDeltaMessage message) { int key = message.Entity.Key; if (_pendingDeltas.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas[key] = message; } private static bool CanResolveStableKey(PlacementMoveEntry entry) { if (entry != null) { return (Object)(object)PlacementMoveState.ResolveObjectByKey(entry.Key) != (Object)null; } return false; } private void ObserveMoveIntent(InteractableObject obj) { if ((Object)(object)obj == (Object)null || obj.GetIsMovingObject() || _applyingState != 0 || PredictionApi.IsReconciling) { return; } _placementHold?.LocalEnded(obj); if (!_context.InGame() || _movingObject != obj || _movingBefore == null) { _movingObject = null; _movingBefore = null; return; } PlacementMoveEntry after = PlacementEntityState.Capture(obj, _movingBefore.Key).ToEntry(); PlacementMoveEntry before = _movingBefore; _movingObject = null; _movingBefore = null; PredictionApi.Predict("placement", delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new PlacementMoveIntentMessage { PredictionId = predictionId, Move = after }); }, delegate { ApplyMovePrediction(after, settle: true); }, delegate { ApplyMovePrediction(before, settle: false); }); } private void ApplyMovePrediction(PlacementMoveEntry entry, bool settle) { _applyingState++; try { if (!PlacementMoveState.Apply(entry, settle) && settle) { throw new InvalidOperationException("predicted placement move could not be applied"); } } finally { _applyingState--; } } private void OnPlacementSceneLoaded(Scene _, LoadSceneMode __) { _placementHold?.Reset(); _placementPopulation.Reset(); ClearPendingDeltas(); _latestBaseline = null; _identityReady = false; _movingObject = null; _movingBefore = null; PlacementIdentity.Reset(); _boxNetworkInteraction?.ClientInvalidateSceneSlots(); } private void InstallPlacementPatches() { Patch(typeof(InteractableObject), "StartMoveObject", "MoveStartPrefix", "MoveStartPostfix"); Patch(typeof(InteractableObject), "PlaceMovedObject", null, "MoveIntentPostfix"); Patch(typeof(InteractableObject), "OnDestroyed", null, "InteractableObjectDestroyedPostfix"); PatchInit(typeof(ShelfManager), "InitInteractableObject", typeof(InteractableObject)); PatchInit(typeof(ShelfManager), "InitDecoObject", typeof(InteractableObject)); PatchInit(typeof(ShelfManager), "InitShelf", typeof(Shelf)); PatchInit(typeof(ShelfManager), "InitWarehouseShelf", typeof(WarehouseShelf)); PatchInit(typeof(ShelfManager), "InitCardShelf", typeof(CardShelf)); PatchInit(typeof(ShelfManager), "InitCardItemCombiShelf", typeof(CardItemCombiShelf)); PatchInit(typeof(ShelfManager), "InitTournamentPrizeShelf", typeof(TournamentPrizeShelf)); PatchInit(typeof(ShelfManager), "InitPlayTable", typeof(InteractablePlayTable)); PatchInit(typeof(ShelfManager), "InitAutoCleanser", typeof(InteractableAutoCleanser)); PatchInit(typeof(ShelfManager), "InitAutoPackOpener", typeof(InteractableAutoPackOpener)); PatchInit(typeof(ShelfManager), "InitWorkbench", typeof(InteractableWorkbench)); PatchInit(typeof(ShelfManager), "InitBulkDonationBox", typeof(InteractableBulkDonationBox)); PatchInit(typeof(ShelfManager), "InitCardStorageShelf", typeof(InteractableCardStorageShelf)); PatchInit(typeof(ShelfManager), "InitTrashBin", typeof(InteractableTrashBin)); PatchInit(typeof(ShelfManager), "InitEmptyBoxStorage", typeof(InteractableEmptyBoxStorage)); PatchInit(typeof(ShelfManager), "InitCashierCounter", typeof(InteractableCashierCounter)); } private void PatchInit(Type target, string method, Type argument) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(target, method, new Type[1] { argument }, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("placement client init patch target missing: " + target.Name + "." + method)); } else { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(WorldClientBehaviour), "ShelfInitPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private void Patch(Type target, string method, string prefix, string postfix) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = AccessTools.Method(target, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("placement client patch target missing: " + target.Name + "." + method)); } else { _harmony.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(WorldClientBehaviour), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(WorldClientBehaviour), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } public static void MoveStartPrefix(InteractableObject __instance, out PlacementMoveEntry __state) { __state = null; if (!((Object)(object)_instance == (Object)null) && !((Object)(object)__instance == (Object)null) && _instance._applyingState == 0 && PlacementApi.TryMakeObjectKey(PlacementInterop.FindKind(__instance), __instance, out var key)) { __state = PlacementEntityState.Capture(__instance, key).ToEntry(); } } public static void MoveStartPostfix(InteractableObject __instance, PlacementMoveEntry __state) { if (!((Object)(object)_instance == (Object)null) && !((Object)(object)__instance == (Object)null) && __instance.GetIsMovingObject()) { if (__state != null) { _instance._movingObject = __instance; _instance._movingBefore = __state; } PlacementHoldInteraction placementHold = _instance._placementHold; if (placementHold != null && !placementHold.LocalStarted(__instance)) { _instance._movingObject = null; _instance._movingBefore = null; } } } public static void MoveIntentPostfix(InteractableObject __instance) { _instance?.ObserveMoveIntent(__instance); } public static void ShelfInitPostfix() { if (!((Object)(object)_instance == (Object)null) && _instance._applyingState == 0) { _instance.ApplyLatestBaseline(); _instance.ApplyPendingDeltas(); } } public static void InteractableObjectDestroyedPostfix(InteractableObject __instance) { if ((Object)(object)__instance != (Object)null) { _instance?._placementHold?.LocalEnded(__instance); PlacementIdentity.Forget(__instance); } } private void ClearPendingDeltas() { foreach (PlacementDeltaMessage value in _pendingDeltas.Values) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas.Clear(); } internal void InstallPlacementHold() { _placementHold = new PlacementHoldInteraction(host: false, delegate { }, _context.Send); _harmony.CreateClassProcessor(typeof(PlacementHoldBoxUpPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlacementHoldRotatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlacementHoldFlipPatch)).Patch(); } internal void ShutdownPlacementHold() { _placementHold?.Reset(); _placementHold = null; } internal void TickPlacementHold() { _placementHold?.Tick(); } [MessageHandler(typeof(PlacementHoldBeginMessage))] private void HandlePlacementHoldBegin(MessageContext _, PlacementHoldBeginMessage message) { _placementHold?.ApplyBegin(message); } [MessageHandler(typeof(PlacementHoldEndMessage))] private void HandlePlacementHoldEnd(MessageContext _, PlacementHoldEndMessage message) { _placementHold?.ApplyEnd(message.HoldKey); } [MessageHandler(typeof(PlacementHoldRotationMessage))] private void HandlePlacementHoldRotation(MessageContext _, PlacementHoldRotationMessage message) { _placementHold?.ApplyRotation(message); } private PlayerBoxInteraction.LocalAction CapturePlayerBoxAction(InteractablePackagingBox box, bool isPlayer, bool alignBody) { if (_playerBoxInteraction != null) { return _playerBoxInteraction.CaptureLocalAction(box, isPlayer, alignBody); } return default(PlayerBoxInteraction.LocalAction); } private void InstallPlayerBoxPresenceHook() { PresenceApi.RemoteAvatarsChanged += OnRemoteAvatarsChanged; } private void UninstallPlayerBoxPresenceHook() { PresenceApi.RemoteAvatarsChanged -= OnRemoteAvatarsChanged; } private void OnRemoteAvatarsChanged() { _playerBoxInteraction?.ReattachRemoteHolds(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { _playerBoxInteraction?.Reset(); } } private void InstallPlayerBoxInteractionPatches() { _harmony.CreateClassProcessor(typeof(PlayerBoxPickupPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlayerBoxThrowPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlayerBoxDropPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlayerBoxPlacementPatch)).Patch(); _harmony.CreateClassProcessor(typeof(FurnitureBoxUpPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ItemBoxOpenStatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(ItemBoxContentsPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ItemBoxRemoveFromShelfPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PackOpenerDispenseFromBoxPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CleanserDispenseFromBoxPatch)).Patch(); } private void ApplyPredictedFurnitureBoxUp(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return; } _applyingFurniturePrediction = true; try { obj.BoxUpObject(false); } finally { _applyingFurniturePrediction = false; } } private void UndoPredictedFurnitureBoxUp(InteractableObject obj) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)obj == (Object)null) { return; } _applyingFurniturePrediction = true; try { PlacementInterop.PlaceBoxedObject(obj, ((Component)obj).transform.position, ((Component)obj).transform.rotation); } finally { _applyingFurniturePrediction = false; } } internal void ResetPlayerBoxInteractionState() { _playerBoxInteraction?.Reset(); } [MessageHandler(typeof(PlayerBoxPickupMessage))] private void HandlePlayerBoxPickup(MessageContext context, PlayerBoxPickupMessage message) { ApplyPlayerBoxAction(message); } [MessageHandler(typeof(PlayerBoxPlacementMessage))] private void HandlePlayerBoxPlacement(MessageContext context, PlayerBoxPlacementMessage message) { ApplyPlayerBoxAction(message); } [MessageHandler(typeof(PlayerBoxThrowMessage))] private void HandlePlayerBoxThrow(MessageContext context, PlayerBoxThrowMessage message) { ApplyPlayerBoxAction(message); } private void PublishPlayerBoxPickup(InteractablePackagingBox box, PlayerBoxInteraction.LocalAction action) { _playerBoxInteraction?.PublishPickup(box, action); } private void PublishPlayerBoxPlacement(InteractablePackagingBox box, PlayerBoxInteraction.LocalAction action) { _playerBoxInteraction?.PublishPlacement(box, action); } private void PublishPlayerBoxThrow(InteractablePackagingBox box, PlayerBoxInteraction.LocalAction action) { _playerBoxInteraction?.PublishThrow(box, action); } private bool ApplyPlayerBoxAction(PlayerBoxInteractionMessage message) { if (_playerBoxInteraction == null) { return false; } WorldPrediction.AckOrApply(message, delegate { _playerBoxInteraction.ApplyIncoming(message); }); return true; } internal void PublishItemBoxState(InteractablePackagingBox_Item item, bool contentsChanged) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) PlayerBoxInteraction playerBoxInteraction = _playerBoxInteraction; if ((playerBoxInteraction == null || !playerBoxInteraction.IsCoveredHold) && !((Object)(object)item == (Object)null) && _boxNetworkInteraction != null && _boxNetworkInteraction.TryGetId((InteractablePackagingBox)(object)item, out var id)) { ShelfCompartment itemCompartment = item.m_ItemCompartment; SendClientIntent(new BoxStateRequestMessage { BoxNetworkId = id, IsBoxOpened = BoxNetworkInteraction.IsBoxOpen(item), ContentsChanged = contentsChanged, ItemType = (EItemType)(((Object)(object)itemCompartment == (Object)null) ? (-1) : ((int)itemCompartment.GetItemType())), ItemCount = ((!((Object)(object)itemCompartment == (Object)null)) ? itemCompartment.GetItemCount() : 0) }); } } private static void PublishDispensedSourceBox(InteractablePackagingBox_Item itemBox, int priorCount) { if ((Object)(object)itemBox?.m_ItemCompartment != (Object)null && itemBox.m_ItemCompartment.GetItemCount() != priorCount) { _instance?.PublishItemBoxState(itemBox, contentsChanged: true); } } private void InstallPlayerShelfInteractionPatches() { _harmony.CreateClassProcessor(typeof(AddItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(RemoveItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(TakeItemToHandPatch)).Patch(); _harmony.CreateClassProcessor(typeof(SetCompartmentItemTypePatch)).Patch(); _harmony.CreateClassProcessor(typeof(PutItemOnShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(TakeItemFromShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(BoxAddToShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(BoxRemoveFromShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(RemoveLabelPatch)).Patch(); } [MessageHandler(typeof(ShelfItemAddMessage))] private void HandleShelfItemAdd(MessageContext context, ShelfItemAddMessage message) { if (PredictionApi.IsPending(message.PredictionId)) { PredictionApi.Ack(message.PredictionId); } else { ApplyShelfAction(message); } } [MessageHandler(typeof(ShelfItemRemoveMessage))] private void HandleShelfItemRemove(MessageContext context, ShelfItemRemoveMessage message) { ApplyShelfAction(message); } internal void ResetPlayerShelfInteractionState() { _shelfInteraction?.Reset(); } private void FlushPlayerShelfState() { _shelfInteraction?.FlushClientState(); } private ShelfInteraction.LocalMutation CaptureShelfMutation(ShelfCompartment compartment, bool add, EItemType itemType, Item item) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (_shelfInteraction != null) { return _shelfInteraction.CaptureLocalMutation(compartment, add, itemType, item); } return default(ShelfInteraction.LocalMutation); } private void PublishShelfAdd(ShelfCompartment compartment, ShelfInteraction.LocalMutation mutation) { _shelfInteraction?.PublishAdd(compartment, mutation); } private void PublishShelfRemove(ShelfCompartment compartment, ShelfInteraction.LocalMutation mutation) { _shelfInteraction?.PublishRemove(compartment, mutation); } private InteractablePackagingBox_Item FindItemBox(ShelfCompartment compartment) { return _boxNetworkInteraction?.FindItemBox(compartment); } private bool IsBoxHeld(InteractablePackagingBox box) { if (_boxNetworkInteraction != null) { return _boxNetworkInteraction.IsBeingHeld(box); } return false; } private unsafe void PublishLabelChange(ShelfCompartment compartment, EItemType previousType, bool allowWarehouse = false) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 if (_shelfInteraction != null && !((Object)(object)compartment == (Object)null) && _context.InGame() && compartment.GetItemCount() <= 0 && (int)compartment.GetItemType() == -1 && (int)previousType != -1) { CoopPlugin.Log.LogInfo((object)("[shelf] label removed on " + ((Object)compartment).name + " (" + ((object)(*(EItemType*)(&previousType))/*cast due to .constrained prefix*/).ToString() + ").")); _shelfInteraction.PublishRemove(compartment, _shelfInteraction.CaptureLabelChange(compartment, allowWarehouse)); } } private bool ApplyShelfAction(ShelfInteractionMessage message) { if (_shelfInteraction == null) { return false; } WorldPrediction.AckOrApply(message, delegate { _shelfInteraction.ApplyIncoming(message); }); return true; } private void EnterPlayerShelfMutation() { _shelfInteraction?.EnterPlayerMutation(); } private void ExitPlayerShelfMutation() { _shelfInteraction?.ExitPlayerMutation(); } private void InstallWarehouseShelfInteractionPatches() { if (WarehouseShelfInteraction.Available()) { _harmony.CreateClassProcessor(typeof(WarehouseTakeObservePatch)).Patch(); if (WarehouseShelfInteraction.UsesRecords) { _harmony.CreateClassProcessor(typeof(BoxRecordStoreCompletePatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoredBoxRecordAddedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoredBoxRecordPoppedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoredBoxRecordArrangePatch)).Patch(); } else { _harmony.CreateClassProcessor(typeof(BoxAddedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BoxRemovedPatch)).Patch(); } } } [MessageHandler(typeof(WarehouseStateMessage))] private void HandleWarehouseState(MessageContext context, WarehouseStateMessage message) { Warehouse.ClientApplyState(message); } [MessageHandler(typeof(WarehouseDeltaMessage))] private void HandleWarehouseDelta(MessageContext context, WarehouseDeltaMessage message) { if (!message.IsStore && !PredictionApi.IsPending(message.PredictionId)) { Warehouse.ClientApplyDelta(message); } else if (message.IsStore) { WorldPrediction.Confirm(message, delegate { Warehouse.ClientApplyDelta(message); }); } else { WorldPrediction.Confirm(message, delegate { Warehouse.ClientTakeOwnConfirmed(message); }); } } private void NotifyStoredBoxRecordAdded(ShelfCompartment compartment) { Warehouse?.OnStoredBoxRecordAdded(compartment); } private void NotifyStoredBoxRecordPopped(ShelfCompartment compartment, bool didPop, WarehouseBoxState removed) { Warehouse?.OnStoredBoxRecordPopped(compartment, didPop, removed); } private void NotifyWarehouseBoxAdded(ShelfCompartment compartment) { Warehouse?.OnBoxAdded(compartment); } private void NotifyWarehouseBoxRemoved(ShelfCompartment compartment, InteractablePackagingBox_Item box) { Warehouse?.OnBoxRemoved(compartment, box); } private void InstallWorkbench() { _workbenchInteraction = new WorldWorkbenchInteraction(host: false, delegate { }, SendWorldCommand); _harmony.CreateClassProcessor(typeof(ClientWorkbenchAddItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchRemoveItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchTakeItemToHandPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchDispenseFromBoxPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchRemoveFromShelfPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchBundleScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchBundleStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ClientWorkbenchTaskCompletedPatch)).Patch(); } private void ShutdownWorkbench() { _workbenchInteraction?.Reset(); _workbenchInteraction = null; } private void PublishWorkbenchStorage(InteractableWorkbench bench, List before) { if (!WorldWorkbenchInteraction.ApplyingRemote) { _workbenchInteraction?.ClientStorageChanged(bench, before); } } [MessageHandler(typeof(WorkbenchStateMessage))] private void HandleWorkbenchState(MessageContext context, WorkbenchStateMessage message) { if (message.PredictionId == Guid.Empty || !PredictionApi.IsPending(message.PredictionId)) { _workbenchInteraction?.MarkAuthorityAdvanced(message.Index); } WorldPrediction.ApplyAuthoritative(message, delegate { _workbenchInteraction?.ClientApplyState(message); }); } } public class WorldContainerInteraction : CoopModule { private sealed class PendingPackCollection { public int ClaimToken; public List Cards; } internal sealed class PackOpenerObserve { internal int Index; internal bool PriorProcessing; internal int PriorStored; internal int PriorOutput; internal int PriorOpened; internal int PriorState; internal float PriorTimer; } internal sealed class WorkerTakeObserve { internal int Index; internal bool Prior; internal bool Desired; } internal sealed class CleanserToggleObserve { internal int Index; internal bool Prior; internal bool PriorCooldown; internal float PriorTimer; } internal sealed class EmptyBoxStoreObserve { internal WorldContainerInteraction Self; internal InteractableEmptyBoxStorage Storage; internal int Index; internal Guid BoxNetworkId; internal int PriorCount; internal bool IsPlayer; internal bool Consuming; internal bool Ended; internal void EndConsume() { if (Consuming && !Ended) { Ended = true; Self._boxes?.EndContainerConsume(); } } } internal sealed class EmptyBoxTakeObserve { internal bool Covered; internal int PriorCount; } private const int KindCardStorage = 9; private const int KindCleanser = 10; private const int KindPackOpener = 11; private const int KindEmptyBoxStorage = 12; private const int KindDonation = 13; private const byte OpContentSet = 1; private const byte OpPackInsert = 2; private const byte OpPackTurnOn = 3; private const byte OpPackCollect = 4; private const byte OpPackClaim = 6; private const byte OpCleanserToggle = 7; private const byte OpCleanserRefill = 8; private const byte OpWorkerTakeFlag = 9; private const byte OpEmptyBoxTake = 10; private const byte OpEmptyBoxStore = 11; public Action SendOp; public Action BroadcastState; public Action SendToClient; internal Action RelayState; internal Func InGameProvider; internal Func ReloadingProvider; public static bool ApplyingRemote; private static readonly FieldInfo FiPoIsProcessing = ReflectionSurface.RequiredField(typeof(InteractableAutoPackOpener), "m_IsProcessing"); private static readonly FieldInfo FiPoOpenTimer = ReflectionSurface.RequiredField(typeof(InteractableAutoPackOpener), "m_PackOpenTimer"); private static readonly FieldInfo FiPoOpenedCount = ReflectionSurface.RequiredField(typeof(InteractableAutoPackOpener), "m_PackOpenedCount"); private static readonly FieldInfo FiPoUI = ReflectionSurface.RequiredField(typeof(InteractableAutoPackOpener), "m_AutoCardOpenerUI"); private static readonly FieldInfo FiClTurnedOn = ReflectionSurface.RequiredField(typeof(InteractableAutoCleanser), "m_IsTurnedOn"); private static readonly FieldInfo FiClNeedRefill = ReflectionSurface.RequiredField(typeof(InteractableAutoCleanser), "m_IsNeedRefill"); private static readonly FieldInfo FiClCooldown = ReflectionSurface.RequiredField(typeof(InteractableAutoCleanser), "m_IsSprayOnCooldown"); private static readonly FieldInfo FiClTimer = ReflectionSurface.RequiredField(typeof(InteractableAutoCleanser), "m_Timer"); private static readonly FieldInfo FiClItemAmount = ReflectionSurface.RequiredField(typeof(InteractableAutoCleanser), "m_ItemAmount"); private static readonly FieldInfo FiEmptyStoredCount = ReflectionSurface.RequiredField(typeof(InteractableEmptyBoxStorage), "m_StoredBoxCount"); private static readonly MethodInfo MiEmptyEvaluateStack = ReflectionSurface.RequiredMethod(typeof(InteractableEmptyBoxStorage), "EvaluateStoredBoxStackHeight"); private static readonly FieldInfo FiScreenShelf = AccessTools.Field(typeof(BulkDonationBoxUIScreen), "m_InteractableCardStorageShelf"); private static readonly FieldInfo FiScreenDonation = AccessTools.Field(typeof(BulkDonationBoxUIScreen), "m_InteractableBulkDonationBox"); private static readonly FieldInfo FiScreenPage = AccessTools.Field(typeof(BulkDonationBoxUIScreen), "m_PageIndex"); private static readonly FieldInfo FiScreenPageLimit = AccessTools.Field(typeof(BulkDonationBoxUIScreen), "m_PageLimitIndex"); private static readonly FieldInfo FiScreenCurrentSlot = AccessTools.Field(typeof(BulkDonationBoxUIScreen), "m_CurrentSelectedSlotIndex"); private static readonly FieldInfo FiScreenPanels = AccessTools.Field(typeof(BulkDonationBoxUIScreen), "m_BulkDonationBoxCardPanelUIList"); private static readonly MethodInfo MiEvaluateCardPanelUI = AccessTools.Method(typeof(BulkDonationBoxUIScreen), "EvaluateCardPanelUI", new Type[1] { typeof(int) }, (Type[])null); private static readonly FieldInfo FiModalParent = AccessTools.Field(typeof(BulkDonationBoxPlusMinusScreen), "m_BulkDonationBoxUIScreen"); private static readonly FieldInfo FiModalCardData = AccessTools.Field(typeof(BulkDonationBoxPlusMinusScreen), "m_CardData"); private static readonly FieldInfo FiModalInput = AccessTools.Field(typeof(BulkDonationBoxPlusMinusScreen), "m_CardAmountInput"); private static readonly FieldInfo FiModalStackCount = AccessTools.Field(typeof(BulkDonationBoxPlusMinusScreen), "m_StackCardCount"); private static readonly FieldInfo FiModalBoxTotal = AccessTools.Field(typeof(BulkDonationBoxPlusMinusScreen), "m_BoxTotalCardCount"); private static readonly MethodInfo MiModalIsOpened = AccessTools.Method(typeof(UIScreenBase), "IsScreenOpened", (Type[])null, (Type[])null); private static readonly MethodInfo MiModalClose = AccessTools.Method(typeof(UIScreenBase), "CloseScreen", (Type[])null, (Type[])null); private readonly Dictionary _hostKeys = new Dictionary(); private BulkDonationBoxUIScreen _cachedContainerScreen; private BulkDonationBoxPlusMinusScreen _cachedAmountModal; private readonly Dictionary _pendingPackCollections = new Dictionary(); private readonly Dictionary _packClaimOwner = new Dictionary(); private readonly Dictionary _packClaimToken = new Dictionary(); private readonly HashSet _claimedPackIndices = new HashSet(); private readonly HashSet _pendingPackInserts = new HashSet(); private int _nextPackClaimToken = 1; private ContainerStateMessage _pendingClientState; private readonly BoxNetworkInteraction _boxes; private readonly PlayerBoxInteraction _playerBox; private Guid _hostPredictionId; private int _hostActorConnectionId; private BoxNetworkState _hostDeltaBox; private bool _hostDeltaTakeIntoHand; private bool _hostDeltaReleaseHold; private bool _hostCompletePackCollection; private byte _hostPackIndex; private static WorldContainerInteraction Current { get { object obj = WorldHostBehaviour.ActiveCards?.Containers; if (obj == null) { WorldCardInteraction activeCards = WorldClientBehaviour.ActiveCards; if (activeCards == null) { return null; } obj = activeCards.Containers; } return (WorldContainerInteraction)obj; } } private static bool InGame { get { if (Current != null && Current.InGameProvider != null) { return Current.InGameProvider(); } return false; } } private static bool Reloading { get { if (Current != null && Current.ReloadingProvider != null) { return Current.ReloadingProvider(); } return false; } } public override string Name => "containers"; internal WorldContainerInteraction(BoxNetworkInteraction boxes = null, PlayerBoxInteraction playerBox = null) { _boxes = boxes; _playerBox = playerBox; } private void TakeIntoLocalHand(InteractablePackagingBox box) { if ((Object)(object)box == (Object)null) { return; } if (_playerBox != null) { _playerBox.TakeCoveredIntoLocalHand(box); return; } InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { box.StartHoldBox(true, val.m_HoldItemPos); } } public override void Reset() { _hostKeys.Clear(); _cachedContainerScreen = null; _cachedAmountModal = null; _claimedPackIndices.Clear(); _pendingPackInserts.Clear(); _pendingPackCollections.Clear(); _packClaimOwner.Clear(); _packClaimToken.Clear(); _nextPackClaimToken = 1; _pendingClientState = null; _hostPredictionId = Guid.Empty; _hostActorConnectionId = 0; _hostDeltaBox = null; _hostDeltaTakeIntoHand = false; _hostDeltaReleaseHold = false; _hostCompletePackCollection = false; _hostPackIndex = 0; } internal void FlushClientState() { if (_pendingClientState != null && InGame) { ContainerStateMessage pendingClientState = _pendingClientState; _pendingClientState = null; ClientApplyState(pendingClientState); } } internal void AppendBaselineMessages(Action append) { if (SendToClient != null && append != null && InGame) { append((INetMessage)(object)BuildBaselineMessage()); } } private ContainerStateMessage BuildBaselineMessage() { List list = new List(); List containerKeys = GetContainerKeys(); for (int i = 0; i < containerKeys.Count; i++) { list.Add(BuildRecord(containerKeys[i] >> 8, containerKeys[i] & 0xFF)); } return new ContainerStateMessage { Records = list }; } public void HostReleaseConn(int connId) { List list = new List(); foreach (KeyValuePair item in _packClaimOwner) { if (item.Value == connId) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { int key = list[i]; _packClaimOwner.Remove(key); _packClaimToken.Remove(key); } if (list.Count > 0) { CoopPlugin.Log.LogInfo((object)$"WorldContainerInteraction: released {list.Count} pack claim(s) from disconnected client {connId}"); } } private ShelfManager Sm() { return SceneRef.Get(); } private int IndexOf(int kind, object obj) { ShelfManager val = Sm(); if ((Object)(object)val == (Object)null) { return -1; } IList list = PlacementApi.GetList(val, kind); if (list == null) { return -1; } int num = list.IndexOf(obj); if (num >= 250) { return -1; } return num; } private T Get(int kind, int idx) where T : class { ShelfManager val = Sm(); if ((Object)(object)val == (Object)null) { return null; } IList list = PlacementApi.GetList(val, kind); if (list == null || idx < 0 || idx >= list.Count) { return null; } return list[idx] as T; } private static bool IsSyncedDonationBox(InteractableBulkDonationBox box) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected I4, but got Unknown if ((Object)(object)box != (Object)null && (int)((InteractableObject)box).m_ObjectType != -1) { return CatalogIdMap.IsDefined(EnumKind.ObjectType, (int)((InteractableObject)box).m_ObjectType); } return false; } private List GetContainerKeys() { List result = new List(); ShelfManager val = Sm(); if ((Object)(object)val == (Object)null) { return result; } _hostKeys.Clear(); AddKeys(result, val, 9); AddKeys(result, val, 13); AddKeys(result, val, 11); AddKeys(result, val, 12); AddKeys(result, val, 10); return result; } private void AddKeys(List result, ShelfManager sm, int kind) { IList list = PlacementApi.GetList(sm, kind); if (list == null) { return; } for (int i = 0; i < list.Count && i < 250; i++) { if (list[i] != null && (kind != 13 || IsSyncedDonationBox((InteractableBulkDonationBox)/*isinst with value type is only supported in some contexts*/))) { int num = (kind << 8) | i; result.Add(num); _hostKeys[list[i]] = num; } } } private void SendHostRecord(int key) { ContainerRecord record = BuildRecord(key >> 8, key & 0xFF); ContainerDeltaMessage containerDeltaMessage = new ContainerDeltaMessage { PredictionId = _hostPredictionId, Key = key, Record = record, HasBox = (_hostDeltaBox != null), Box = _hostDeltaBox, TakeIntoHand = _hostDeltaTakeIntoHand, ReleaseHold = _hostDeltaReleaseHold, CompletePackCollection = _hostCompletePackCollection, PackIndex = _hostPackIndex }; int hostActorConnectionId = _hostActorConnectionId; if (hostActorConnectionId > 0 && (containerDeltaMessage.TakeIntoHand || containerDeltaMessage.ReleaseHold) && SendToClient != null && RelayState != null) { SendToClient(hostActorConnectionId, (INetMessage)(object)containerDeltaMessage); RelayState(hostActorConnectionId, (INetMessage)(object)new ContainerDeltaMessage { PredictionId = containerDeltaMessage.PredictionId, Key = containerDeltaMessage.Key, Record = containerDeltaMessage.Record, HasBox = containerDeltaMessage.HasBox, Box = containerDeltaMessage.Box, TakeIntoHand = false, ReleaseHold = false, CompletePackCollection = containerDeltaMessage.CompletePackCollection, PackIndex = containerDeltaMessage.PackIndex }); } else { BroadcastState?.Invoke((INetMessage)(object)containerDeltaMessage); } } private void HostChanged(int kind, object container) { if (InGame && container != null && BroadcastState != null && TryResolveHostKey(kind, container, out var key)) { Guarded("change", delegate { SendHostRecord(key); }); } } private bool TryResolveHostKey(int kind, object container, out int key) { key = 0; try { if (_hostKeys.TryGetValue(container, out key) && key >> 8 == kind && Get(kind, key & 0xFF) == container) { return true; } GetContainerKeys(); return _hostKeys.TryGetValue(container, out key) && key >> 8 == kind && Get(kind, key & 0xFF) == container; } catch (Exception e) { ModuleGuard.Log("containers:resolve-key", e); return false; } } private ContainerRecord BuildRecord(int kind, int idx) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) ContainerRecord result = new ContainerRecord { StableEntityId = WorldMessageMetadata.ContainerEntityId(kind, idx), Kind = (byte)kind, Index = (byte)idx }; switch (kind) { case 9: { InteractableCardStorageShelf val5 = Get(kind, idx); result.CanWorkerTake = (Object)(object)val5 == (Object)null || val5.CanWorkerTake(); result.Cards = ((val5 != null) ? val5.GetCompactCardDataAmountList() : null) ?? new List(); break; } case 13: { InteractableBulkDonationBox val4 = Get(kind, idx); if (IsSyncedDonationBox(val4)) { result.Cards = val4.GetCompactCardDataAmountList(); } break; } case 11: { InteractableAutoPackOpener val2 = Get(kind, idx); List list2 = ((val2 != null) ? val2.GetStoredItemList() : null); int num2 = Mathf.Min(list2?.Count ?? 0, 250); result.StoredTypes = new List(num2); for (int j = 0; j < num2; j++) { result.StoredTypes.Add((EItemType)(((Object)(object)list2[j] != (Object)null) ? ((int)list2[j].GetItemType()) : (-1))); } result.Processing = (Object)(object)val2 != (Object)null && val2.GetIsProcessing(); result.Timer = (((Object)(object)val2 != (Object)null) ? (FiPoOpenTimer?.GetValue(val2) as float?).GetValueOrDefault() : 0f); result.OpenedCount = (((Object)(object)val2 != (Object)null) ? val2.GetPackOpenedCount() : 0); result.Cards = ((val2 != null) ? val2.GetCompactCardDataAmountList() : null) ?? new List(); result.CurrentState = (((Object)(object)val2 != (Object)null && val2.GetIsProcessing()) ? (((list2?.Count ?? 0) > 0) ? 1 : 2) : 0); result.CollectClaimed = _packClaimOwner.ContainsKey((kind << 8) | idx); break; } case 12: { InteractableEmptyBoxStorage val3 = Get(kind, idx); result.Count = ((val3 != null) ? val3.GetBoxStoredCount() : 0); break; } case 10: { InteractableAutoCleanser val = Get(kind, idx); byte b = 0; if ((Object)(object)val != (Object)null && val.IsTurnedOn()) { b |= 1; } if ((Object)(object)val == (Object)null || val.IsNeedRefill()) { b |= 2; } result.Flags = b; List list = ((val != null) ? val.GetStoredItemList() : null); int num = Mathf.Min(list?.Count ?? 0, 32); result.Fills = new List(num); for (int i = 0; i < num; i++) { result.Fills.Add(((Object)(object)list[i] != (Object)null) ? list[i].GetContentFill() : 0f); } break; } } return result; } public bool HostApplyOp(ContainerOpMessage message, int connId, Action response = null) { //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Invalid comparison between Unknown and I4 //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) if (message == null || connId <= 0) { return false; } byte op = message.Op; bool flag = false; string reason = null; BoxNetworkState descriptor = null; int num = message.Kind; int index = message.Index; switch (op) { case 9: num = 9; break; case 2: case 3: case 4: case 6: num = 11; break; case 7: case 8: num = 10; break; case 10: case 11: num = 12; break; } _hostPredictionId = message.PredictionId; _hostActorConnectionId = connId; try { switch (op) { case 1: { bool canWorkerTake3 = message.CanWorkerTake; List cards = message.Cards; if ((num != 9 && num != 13) || Get(num, index) == null) { reason = "container does not exist"; break; } if (num == 13 && !IsSyncedDonationBox(Get(num, index))) { reason = "donation box is not synced"; break; } if (cards == null) { reason = "container card payload is missing"; break; } List collection = ((num == 9) ? new List(((InteractableCardStorageShelf)Get(num, index)).GetCompactCardDataAmountList()) : new List(((InteractableBulkDonationBox)Get(num, index)).GetCompactCardDataAmountList())); bool canWorkerTake4 = num == 9 && ((InteractableCardStorageShelf)Get(num, index)).CanWorkerTake(); try { ApplyContent(num, index, cards, num == 9, canWorkerTake3); HostChanged(num, Get(num, index)); } catch { object obj5 = Get(num, index); InteractableCardStorageShelf val7 = (InteractableCardStorageShelf)((obj5 is InteractableCardStorageShelf) ? obj5 : null); if (val7 != null) { List compactCardDataAmountList = val7.GetCompactCardDataAmountList(); compactCardDataAmountList.Clear(); compactCardDataAmountList.AddRange(collection); val7.SetCanWorkerTake(canWorkerTake4); } else { InteractableBulkDonationBox val8 = (InteractableBulkDonationBox)((obj5 is InteractableBulkDonationBox) ? obj5 : null); if (val8 != null) { List compactCardDataAmountList2 = val8.GetCompactCardDataAmountList(); compactCardDataAmountList2.Clear(); compactCardDataAmountList2.AddRange(collection); } } throw; } flag = true; break; } case 9: { bool canWorkerTake = message.CanWorkerTake; InteractableCardStorageShelf val2 = Get(9, index); if ((Object)(object)val2 == (Object)null) { reason = "card storage does not exist"; break; } ApplyingRemote = true; try { bool canWorkerTake2 = val2.CanWorkerTake(); try { val2.SetCanWorkerTake(canWorkerTake); val2.OnCardStorageShelfSettingDone(); } catch { val2.SetCanWorkerTake(canWorkerTake2); throw; } } finally { ApplyingRemote = false; } HostChanged(9, val2); flag = true; break; } case 2: { EItemType itemType = message.ItemType; InteractableAutoPackOpener val9 = Get(11, index); if ((Object)(object)val9 == (Object)null) { reason = "pack opener does not exist"; break; } if ((int)itemType == -1) { CoopPlugin.Log.LogWarning((object)"WorldContainerInteraction pack insert: item type has no counterpart here (one-sided content pack)"); reason = "pack type is unavailable on host"; break; } if (val9.GetIsProcessing()) { reason = "pack opener is already running"; break; } if (!val9.HasEnoughSlot()) { reason = "pack opener is full (" + val9.GetStoredItemList().Count + "+" + val9.GetPackOpenedCount() + "/" + val9.m_MaxPackCount + ")"; break; } List stored = new List(val9.GetStoredItemList()); bool isProcessing = val9.GetIsProcessing(); float valueOrDefault3 = (FiPoOpenTimer?.GetValue(val9) as float?).GetValueOrDefault(); int packOpenedCount = val9.GetPackOpenedCount(); int currentState = val9.m_CurrentState; Item val10 = null; try { val10 = SpawnItem(itemType, val9.m_PosInside); ApplyingRemote = true; try { val9.AddItem(val10, true, false); } finally { ApplyingRemote = false; } HostChanged(11, val9); flag = true; } catch { RestorePackOpener(val9, stored, isProcessing, valueOrDefault3, packOpenedCount, currentState); throw; } break; } case 3: { InteractableAutoPackOpener val11 = Get(11, index); if ((Object)(object)val11 != (Object)null && !val11.GetIsProcessing() && val11.GetStoredItemList().Count > 0) { List stored2 = new List(val11.GetStoredItemList()); bool isProcessing2 = val11.GetIsProcessing(); float valueOrDefault4 = (FiPoOpenTimer?.GetValue(val11) as float?).GetValueOrDefault(); int packOpenedCount2 = val11.GetPackOpenedCount(); int currentState2 = val11.m_CurrentState; try { ((InteractableObject)val11).OnMouseButtonUp(); flag = val11.GetIsProcessing(); } catch { RestorePackOpener(val11, stored2, isProcessing2, valueOrDefault4, packOpenedCount2, currentState2); throw; } } if (!flag) { reason = (((Object)(object)val11 == (Object)null) ? "pack opener does not exist" : "pack opener state changed"); } break; } case 6: flag = HostApplyPackClaim(message.Index, connId, out reason, response); break; case 4: { InteractableAutoPackOpener container = Get(11, index); flag = HostApplyPackCollect(index, message.ClaimToken, connId, out reason); if (flag) { _hostCompletePackCollection = true; _hostPackIndex = message.Index; HostChanged(11, container); } break; } case 7: { bool turnedOn = message.TurnedOn; InteractableAutoCleanser val3 = Get(10, index); if ((Object)(object)val3 == (Object)null) { reason = "cleanser does not exist"; break; } bool flag2 = val3.IsTurnedOn(); bool valueOrDefault = FiClCooldown?.GetValue(val3) as bool? == true; float valueOrDefault2 = (FiClTimer?.GetValue(val3) as float?).GetValueOrDefault(); try { FiClTurnedOn?.SetValue(val3, turnedOn); if (!turnedOn) { FiClCooldown?.SetValue(val3, true); FiClTimer?.SetValue(val3, 0f); } HostChanged(10, val3); } catch { FiClTurnedOn?.SetValue(val3, flag2); FiClCooldown?.SetValue(val3, valueOrDefault); FiClTimer?.SetValue(val3, valueOrDefault2); throw; } flag = true; break; } case 8: { float fill = message.Fill; InteractableAutoCleanser val4 = Get(10, index); if (!IsFinite(fill) || fill < 0f || fill > 1f) { reason = "invalid cleanser fill"; break; } if ((Object)(object)val4 == (Object)null || !val4.HasEnoughSlot()) { reason = (((Object)(object)val4 == (Object)null) ? "cleanser does not exist" : "cleanser has no slot"); break; } List list = new List(val4.GetStoredItemList()); int itemAmount = (FiClItemAmount?.GetValue(val4) as int?) ?? list.Count; bool needRefill = val4.IsNeedRefill(); Item val5 = null; try { val5 = SpawnItem((EItemType)23, val4.m_PosList[0], fill); ApplyingRemote = true; try { val4.AddItem(val5, true); } finally { ApplyingRemote = false; } HostChanged(10, val4); flag = true; } catch { RestoreCleanser(val4, list, itemAmount, needRefill); throw; } break; } case 10: { InteractableEmptyBoxStorage val6 = Get(12, index); if ((Object)(object)val6 == (Object)null) { reason = "empty-box storage does not exist"; break; } _boxes.HostPredictionId = _hostPredictionId; try { flag = HostTakeEmptyBox(val6, message.BoxNetworkId, out reason, out descriptor); } finally { _boxes.HostPredictionId = Guid.Empty; } if (flag) { _hostDeltaBox = descriptor; _hostDeltaTakeIntoHand = message.IsPlayer; HostChanged(12, val6); if (message.IsPlayer && descriptor != null) { _playerBox?.AnnounceGrantedHold(descriptor.BoxNetworkId, connId); } } break; } case 11: { InteractableEmptyBoxStorage val = Get(12, index); if ((Object)(object)val == (Object)null) { reason = "empty-box storage does not exist"; break; } flag = HostStoreEmptyBox(val, message.BoxNetworkId, out reason); if (flag) { _hostDeltaReleaseHold = message.IsPlayer; HostChanged(12, val); } break; } default: reason = "unknown container operation"; break; } } catch (Exception ex) { CoopPlugin.Log.LogError((object)("WorldContainerInteraction op=" + op + " kind=" + num + " index=" + index + " failed: " + ex)); reason = "container operation failed"; } if (!flag && reason != null) { CoopPlugin.Log.LogWarning((object)("WorldContainerInteraction rejected op=" + op + " kind=" + num + " index=" + index + ": " + reason)); } _hostPredictionId = Guid.Empty; _hostActorConnectionId = 0; _hostDeltaBox = null; _hostDeltaTakeIntoHand = false; _hostDeltaReleaseHold = false; _hostCompletePackCollection = false; _hostPackIndex = 0; return flag; } private bool HostTakeEmptyBox(InteractableEmptyBoxStorage storage, Guid creatorId, out string reason, out BoxNetworkState descriptor) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) reason = null; descriptor = null; int boxStoredCount = storage.GetBoxStoredCount(); if (boxStoredCount <= 0) { reason = "empty-box storage is empty"; return false; } if (!_boxes.PushHostCreatedId(creatorId)) { reason = "empty box creator id is not fresh"; return false; } InteractablePackagingBox_Item val = RestockManager.SpawnPackageBoxItem((EItemType)(-1), 0, true); if ((Object)(object)val == (Object)null) { _boxes.CancelHostCreatedId(creatorId); reason = "host could not create an empty box"; return false; } if ((Object)(object)storage.m_EmptyBoxSpawnLoc == (Object)null) { ((InteractableObject)val).OnDestroyed(); reason = "empty-box storage has no spawn location"; return false; } ((Component)val).transform.SetPositionAndRotation(storage.m_EmptyBoxSpawnLoc.position, storage.m_EmptyBoxSpawnLoc.rotation); if (!((InteractableObject)val).CanPickup()) { ((InteractableObject)val).StopLerpToTransform(); } val.ForceSetOpenCloseInstant(true); val.SetOpenCloseBox(false, false); if (!_boxes.TryGetId((InteractablePackagingBox)(object)val, out var id)) { ((InteractableObject)val).OnDestroyed(); reason = "created empty box has no network identity"; return false; } try { FiEmptyStoredCount.SetValue(storage, boxStoredCount - 1); MiEmptyEvaluateStack.Invoke(storage, null); _boxes.HostRefresh(id); descriptor = _boxes.DescribeAuthoritative(id); if (descriptor != null) { return true; } FiEmptyStoredCount.SetValue(storage, boxStoredCount); MiEmptyEvaluateStack.Invoke(storage, null); ((InteractableObject)val).OnDestroyed(); reason = "created empty box has no authoritative descriptor"; return false; } catch (Exception ex) { FiEmptyStoredCount.SetValue(storage, boxStoredCount); MiEmptyEvaluateStack.Invoke(storage, null); if ((Object)(object)val != (Object)null) { ((InteractableObject)val).OnDestroyed(); } _boxes.CancelHostCreatedId(creatorId); reason = "empty box take failed"; CoopPlugin.Log.LogError((object)("HostTakeEmptyBox failed: " + ex)); return false; } } private bool HostStoreEmptyBox(InteractableEmptyBoxStorage storage, Guid boxId, out string reason) { reason = null; if (!(boxId == Guid.Empty) && _boxes.TryGetBox(boxId, out var box)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null && !((Object)(object)val.m_ItemCompartment == (Object)null)) { if (val.m_ItemCompartment.GetItemCount() != 0 || !val.m_IsBigBox) { reason = "only empty big boxes can be stored"; return false; } if (!storage.HasStorageSpace()) { reason = "empty-box storage is full"; return false; } if (val.IsTogglingOpenClose()) { val.ForceSetOpenCloseInstant(false); } int boxStoredCount = storage.GetBoxStoredCount(); storage.StoreBox(val, false); if (storage.GetBoxStoredCount() != boxStoredCount + 1) { reason = "empty box could not be stored"; return false; } return true; } } reason = "empty box identity is unknown"; return false; } private bool HostApplyPackClaim(int idx, int connId, out string reason, Action response = null) { reason = null; InteractableAutoPackOpener val = Get(11, idx); List list = ((val != null) ? val.GetCompactCardDataAmountList() : null); int key = 0xB00 | idx; if ((Object)(object)val == (Object)null || !val.GetIsProcessing() || val.GetStoredItemList().Count > 0 || list == null || list.Count == 0 || _packClaimOwner.ContainsKey(key)) { reason = (((Object)(object)val == (Object)null) ? "pack opener does not exist" : "pack opener has no claimable output"); return false; } int num = _nextPackClaimToken++; if (num == 0) { num = _nextPackClaimToken++; } ContainerPackClaimMessage containerPackClaimMessage = new ContainerPackClaimMessage { PredictionId = _hostPredictionId, Index = (byte)idx, ClaimToken = num, Cards = new List(list) }; int num2 = 0; for (int i = 0; i < list.Count; i++) { if (list[i] != null) { num2 += list[i].amount; } } CoopPlugin.Log.LogInfo((object)("[pack] claim opener=" + idx + ": " + num2 + " card(s) in " + list.Count + " entr(ies).")); if (response != null) { response((INetMessage)(object)containerPackClaimMessage); } else { SendToClient?.Invoke(connId, (INetMessage)(object)containerPackClaimMessage); } _packClaimOwner[key] = connId; _packClaimToken[key] = num; return true; } private bool HostApplyPackCollect(int idx, int token, int connId, out string reason) { reason = null; InteractableAutoPackOpener val = Get(11, idx); if ((Object)(object)val == (Object)null) { reason = "pack opener does not exist"; return false; } List compactCardDataAmountList = val.GetCompactCardDataAmountList(); int key = 0xB00 | idx; if (!_packClaimOwner.TryGetValue(key, out var value) || value != connId || !_packClaimToken.TryGetValue(key, out var value2) || value2 != token) { CoopPlugin.Log.LogWarning((object)$"WorldContainerInteraction: rejected pack collect opener={idx} client={connId}"); reason = "pack claim is stale or does not match host output"; return false; } List collection = new List(compactCardDataAmountList); int packOpenedCount = val.GetPackOpenedCount(); bool isProcessing = val.GetIsProcessing(); int currentState = val.m_CurrentState; int value3 = value; int value4 = value2; int cardPackOpened = CPlayerData.m_GameReportDataCollect.cardPackOpened; int cardPackOpened2 = CPlayerData.m_GameReportDataCollectPermanent.cardPackOpened; CoopPlugin.Log.LogInfo((object)("[pack] collect opener=" + idx + ": " + packOpenedCount + " pack(s) opened.")); try { if (compactCardDataAmountList != null && compactCardDataAmountList.Count > 0) { int packOpenedCount2 = val.GetPackOpenedCount(); CPlayerData.m_GameReportDataCollect.cardPackOpened += packOpenedCount2; CPlayerData.m_GameReportDataCollectPermanent.cardPackOpened += packOpenedCount2; AchievementManager.OnCardPackOpened(CPlayerData.m_GameReportDataCollectPermanent.cardPackOpened); compactCardDataAmountList.Clear(); } FiPoOpenedCount?.SetValue(val, 0); FiPoIsProcessing?.SetValue(val, false); val.m_CurrentState = 0; object? obj = FiPoUI?.GetValue(val); AutoCardOpenerUI val2 = (AutoCardOpenerUI)((obj is AutoCardOpenerUI) ? obj : null); if (val2 != null) { val2.SetUIState(0); val2.UpdatePackCountText(0, val.m_MaxPackCount); } _packClaimOwner.Remove(key); _packClaimToken.Remove(key); HostChanged(11, val); } catch { compactCardDataAmountList.Clear(); compactCardDataAmountList.AddRange(collection); FiPoOpenedCount?.SetValue(val, packOpenedCount); FiPoIsProcessing?.SetValue(val, isProcessing); val.m_CurrentState = currentState; _packClaimOwner[key] = value3; _packClaimToken[key] = value4; CPlayerData.m_GameReportDataCollect.cardPackOpened = cardPackOpened; CPlayerData.m_GameReportDataCollectPermanent.cardPackOpened = cardPackOpened2; throw; } return true; } public void ClientApplyState(ContainerStateMessage message) { if (message == null) { throw new InvalidOperationException("Authoritative container state is missing."); } if (!InGame) { _pendingClientState = message; return; } CacheContainerScreens(); List records = message.Records; for (int i = 0; i < records.Count; i++) { ContainerRecord rec = records[i]; int kind = rec.Kind; int index = rec.Index; switch (kind) { case 9: ApplyContentInPlace(Get(kind, index), rec.Cards, rec.CanWorkerTake); break; case 13: { InteractableBulkDonationBox box = Get(kind, index); if (IsSyncedDonationBox(box)) { ApplyContentInPlace(box, rec.Cards); } break; } case 11: ApplyPackState(Get(kind, index), rec); if (rec.CollectClaimed) { _claimedPackIndices.Add(index); } else { _claimedPackIndices.Remove(index); } break; case 10: { InteractableAutoCleanser c = Get(kind, index); ApplyCleanserState(index, c, (rec.Flags & 1) != 0, (rec.Flags & 2) != 0, rec.Fills); break; } case 12: { InteractableEmptyBoxStorage obj = Get(kind, index); FiEmptyStoredCount.SetValue(obj, rec.Count); MiEmptyEvaluateStack.Invoke(obj, null); break; } default: throw new InvalidOperationException("Unknown authoritative container kind " + kind + "."); } } } public void ClientApplyDelta(ContainerDeltaMessage message) { if (message == null) { throw new InvalidOperationException("Authoritative container delta is missing."); } if (_pendingPackInserts.Remove(message.PredictionId)) { WorldPrediction.ApplyAuthoritative(message, delegate { ApplyDeltaRecord(message); }); } else { WorldPrediction.Confirm(message, delegate { ApplyDeltaRecord(message); }); } if (message.HasBox) { _boxes.ClientApplyCreated(new BoxCreatedMessage { Box = message.Box }); if (!_boxes.TryGetBox(message.Box.BoxNetworkId, out var box)) { throw new InvalidOperationException("Container delta box was not materialized."); } if (message.TakeIntoHand) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { TakeIntoLocalHand((InteractablePackagingBox)(object)val); } } } if (message.ReleaseHold) { _playerBox?.ReleaseLocalHold(); } if (message.CompletePackCollection && _pendingPackCollections.ContainsKey(message.PackIndex)) { CompletePackCollection(message.PackIndex); } } private void ApplyDeltaRecord(ContainerDeltaMessage message) { ClientApplyState(new ContainerStateMessage { Records = new List { message.Record } }); } private void CompletePackCollection(int idx) { _pendingPackCollections.Remove(idx); _claimedPackIndices.Remove(idx); } private void ApplyContentInPlace(InteractableCardStorageShelf shelf, List cards, bool canWorkerTake) { ApplyingRemote = true; try { List compactCardDataAmountList = shelf.GetCompactCardDataAmountList(); compactCardDataAmountList.Clear(); compactCardDataAmountList.AddRange(cards); shelf.SetCanWorkerTake(canWorkerTake); shelf.OnCardStorageShelfSettingDone(); RefreshOpenContainerUI(shelf, null); } finally { ApplyingRemote = false; } } private void ApplyContentInPlace(InteractableBulkDonationBox box, List cards) { if (!IsSyncedDonationBox(box)) { return; } ApplyingRemote = true; try { List compactCardDataAmountList = box.GetCompactCardDataAmountList(); compactCardDataAmountList.Clear(); compactCardDataAmountList.AddRange(cards); box.UpdateFillPercent(Mathf.Clamp01((float)box.GetTotalCardAmount() / (float)box.GetBoxTotalCardCountMax())); RefreshOpenContainerUI(null, box); } finally { ApplyingRemote = false; } } private void CacheContainerScreens() { if ((Object)(object)_cachedContainerScreen == (Object)null) { _cachedContainerScreen = Object.FindObjectOfType(); } if ((Object)(object)_cachedAmountModal == (Object)null) { _cachedAmountModal = Object.FindObjectOfType(); } } private void RefreshOpenContainerUI(InteractableCardStorageShelf shelf, InteractableBulkDonationBox donation) { CacheContainerScreens(); BulkDonationBoxUIScreen cachedContainerScreen = _cachedContainerScreen; if ((Object)(object)cachedContainerScreen == (Object)null || MiEvaluateCardPanelUI == null) { return; } object? obj = FiScreenShelf?.GetValue(cachedContainerScreen); object obj2 = FiScreenDonation?.GetValue(cachedContainerScreen); if (obj != shelf || obj2 != donation) { return; } List list = (((Object)(object)donation != (Object)null) ? donation.GetCompactCardDataAmountList() : shelf.GetCompactCardDataAmountList()); int num = (FiScreenPanels?.GetValue(cachedContainerScreen) as IList)?.Count ?? 0; if (num <= 0) { return; } int num2 = (FiScreenPageLimit?.GetValue(cachedContainerScreen) as int?) ?? 1; int valueOrDefault = (FiScreenPage?.GetValue(cachedContainerScreen) as int?).GetValueOrDefault(); int num3 = Mathf.Max(0, list.Count / num); num3 = Mathf.Min(num3, Mathf.Max(0, num2 - 1)); valueOrDefault = Mathf.Clamp(valueOrDefault, 0, num3); FiScreenPage?.SetValue(cachedContainerScreen, valueOrDefault); int valueOrDefault2 = (FiScreenCurrentSlot?.GetValue(cachedContainerScreen) as int?).GetValueOrDefault(); BulkDonationBoxPlusMinusScreen cachedAmountModal = _cachedAmountModal; if ((Object)(object)cachedAmountModal != (Object)null && FiModalParent?.GetValue(cachedAmountModal) == cachedContainerScreen && MiModalIsOpened != null && (bool)MiModalIsOpened.Invoke(cachedAmountModal, null)) { object? obj3 = FiModalCardData?.GetValue(cachedAmountModal); CardData card = (CardData)((obj3 is CardData) ? obj3 : null); int num4 = FindCardIndex(list, card); if (num4 < 0) { if (MiModalClose == null || MiModalIsOpened == null || FiModalInput == null || FiModalStackCount == null || FiScreenCurrentSlot == null) { CoopPlugin.Log.LogWarning((object)"WorldContainerInteraction: could not safely close stale card amount modal; leaving its slot valid"); return; } object? value = FiModalInput.GetValue(cachedAmountModal); TMP_InputField val = (TMP_InputField)((value is TMP_InputField) ? value : null); if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)"WorldContainerInteraction: card amount modal input was unavailable; leaving its slot valid"); return; } bool applyingRemoteCards = WorldCardInteraction.ApplyingRemoteCards; try { WorldCardInteraction.ApplyingRemoteCards = true; FiModalStackCount.SetValue(cachedAmountModal, 0); val.text = "0"; if (val.isFocused) { val.DeactivateInputField(false); } int num5 = valueOrDefault2; FiScreenCurrentSlot.SetValue(cachedContainerScreen, -1); try { MiModalClose.Invoke(cachedAmountModal, null); if ((bool)MiModalIsOpened.Invoke(cachedAmountModal, null)) { FiScreenCurrentSlot.SetValue(cachedContainerScreen, (list.Count == 0) ? (-1) : Mathf.Clamp(num5, 0, list.Count - 1)); CoopPlugin.Log.LogWarning((object)"WorldContainerInteraction: card amount modal did not close; restored its slot"); } } catch (Exception ex) { FiScreenCurrentSlot.SetValue(cachedContainerScreen, (list.Count == 0) ? (-1) : Mathf.Clamp(num5, 0, list.Count - 1)); CoopPlugin.Log.LogWarning((object)("WorldContainerInteraction: card amount modal close failed: " + ex.Message)); } } finally { WorldCardInteraction.ApplyingRemoteCards = applyingRemoteCards; } } else { FiScreenCurrentSlot?.SetValue(cachedContainerScreen, num4); int num6 = ((list[num4].gradedCardIndex > 0) ? 1 : list[num4].amount); FiModalStackCount?.SetValue(cachedAmountModal, num6); FiModalBoxTotal?.SetValue(cachedAmountModal, GetTotalCardAmount(list)); } } else { FiScreenCurrentSlot?.SetValue(cachedContainerScreen, (list.Count == 0) ? (-1) : Mathf.Clamp(valueOrDefault2, 0, list.Count - 1)); } MiEvaluateCardPanelUI.Invoke(cachedContainerScreen, new object[1] { valueOrDefault }); } private static int FindCardIndex(List list, CardData card) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (card == null || list == null) { return -1; } for (int i = 0; i < list.Count; i++) { CompactCardDataAmount val = list[i]; if (val == null) { continue; } if (val.gradedCardIndex > 0) { if (val.gradedCardIndex == card.gradedCardIndex) { return i; } continue; } CardData cardData = CPlayerData.GetCardData(val.cardSaveIndex, val.expansionType, val.isDestiny); if (cardData != null && cardData.IsSameCardDataType(card) && cardData.expansionType == card.expansionType && cardData.isDestiny == card.isDestiny) { return i; } } return -1; } private static int GetTotalCardAmount(List list) { int num = 0; if (list == null) { return num; } for (int i = 0; i < list.Count; i++) { CompactCardDataAmount val = list[i]; num += ((val == null || val.gradedCardIndex > 0) ? 100 : val.amount); } return num; } private void ApplyContent(int kind, int idx, List cards, bool hasFlag, bool canWorkerTake) { ApplyingRemote = true; try { switch (kind) { case 9: { InteractableCardStorageShelf val2 = Get(kind, idx); if (!((Object)(object)val2 == (Object)null)) { if (hasFlag) { ApplyContentInPlace(val2, cards, canWorkerTake); } else { ApplyContentInPlace(val2, cards, val2.CanWorkerTake()); } } break; } case 13: { InteractableBulkDonationBox val = Get(kind, idx); if (!((Object)(object)val == (Object)null)) { ApplyContentInPlace(val, cards); } break; } } } finally { ApplyingRemote = false; } } private void ApplyPackState(InteractableAutoPackOpener p, ContainerRecord rec) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected I4, but got Unknown if ((Object)(object)p == (Object)null) { return; } List list = new List(rec.StoredTypes.Count); for (int i = 0; i < rec.StoredTypes.Count; i++) { list.Add((int)rec.StoredTypes[i]); } ApplyingRemote = true; try { SyncLocalPackItems(p, list.Count, list); FiPoIsProcessing?.SetValue(p, rec.Processing); FiPoOpenedCount?.SetValue(p, rec.OpenedCount); p.m_CurrentState = rec.CurrentState; if (rec.CurrentState != 1) { FiPoOpenTimer?.SetValue(p, 0f); } else if (!p.GetIsProcessing()) { FiPoOpenTimer?.SetValue(p, rec.Timer); } PaintPackUI(p); } finally { ApplyingRemote = false; } } private static void SyncLocalPackItems(InteractableAutoPackOpener opener, int targetCount, List storedTypes) { List storedItemList = opener.GetStoredItemList(); if (storedItemList == null) { throw new InvalidOperationException("Pack opener has no local storage list."); } while (storedItemList.Count > targetCount) { Item val = storedItemList[storedItemList.Count - 1]; storedItemList.RemoveAt(storedItemList.Count - 1); if ((Object)(object)val != (Object)null) { val.DisableItem(); } } while (storedItemList.Count < targetCount) { Item item = SpawnItem((EItemType)((storedItemList.Count < storedTypes.Count) ? storedTypes[storedItemList.Count] : (-1)), opener.m_PosInside); storedItemList.Add(item); } } private static void PaintPackUI(InteractableAutoPackOpener p) { if ((Object)(object)p == (Object)null) { return; } object? obj = FiPoUI?.GetValue(p); AutoCardOpenerUI val = (AutoCardOpenerUI)((obj is AutoCardOpenerUI) ? obj : null); if (val != null) { int num = p.GetStoredItemList()?.Count ?? 0; switch (p.m_CurrentState) { case 1: { float num2 = Mathf.Max(0.001f, p.m_PackOpenTime); float valueOrDefault = (FiPoOpenTimer?.GetValue(p) as float?).GetValueOrDefault(); val.SetUIState(1); val.UpdateProcessingFillBar(Mathf.Clamp01(1f - (float)num / (float)Mathf.Max(1, p.m_MaxPackCount))); val.UpdateProcessingTimeLeftText(Mathf.Max(0f, num2 * (float)num - valueOrDefault)); break; } case 2: val.SetUIState(2); break; default: val.SetUIState(0); val.UpdatePackCountText(num, p.m_MaxPackCount); break; } } } private void ApplyCleanserState(int idx, InteractableAutoCleanser c, bool on, bool needRefill, List fills) { ApplyingRemote = true; try { List storedItemList = c.GetStoredItemList(); while ((storedItemList?.Count ?? 0) > fills.Count) { Item val = storedItemList[storedItemList.Count - 1]; if ((Object)(object)val == (Object)null) { storedItemList.RemoveAt(storedItemList.Count - 1); continue; } c.RemoveItem(val); val.DisableItem(); } while (storedItemList.Count < fills.Count) { Item val2 = SpawnItem((EItemType)23, c.m_PosList[0], 1f); c.AddItem(val2, true); } if (storedItemList != null) { for (int i = 0; i < storedItemList.Count && i < fills.Count; i++) { if ((Object)(object)storedItemList[i] != (Object)null) { storedItemList[i].SetContentFill(fills[i]); } } } FiClItemAmount?.SetValue(c, storedItemList?.Count ?? 0); FiClTurnedOn?.SetValue(c, on); FiClNeedRefill?.SetValue(c, needRefill); } finally { ApplyingRemote = false; } } private void ClientPackOpenerClick(InteractableAutoPackOpener p, PackOpenerObserve observe) { if ((Object)(object)p == (Object)null || observe == null) { return; } int idx = observe.Index; if (!observe.PriorProcessing) { if (observe.PriorStored > 0) { ContainerOpMessage intent = new ContainerOpMessage { Op = 3, Kind = 11, Index = (byte)idx }; WorldPrediction.Predict("world.containers", intent, delegate { ApplyPackRunning(p); }, delegate { ApplyPackSnapshot(p, observe.PriorProcessing, observe.PriorState, observe.PriorOpened, observe.PriorTimer); }); } else { NotEnoughResourceTextPopup.ShowText((ENotEnoughResourceText)38); } } else if (observe.PriorOutput > 0 && observe.PriorStored <= 0) { if (_claimedPackIndices.Contains(idx)) { if (_pendingPackCollections.TryGetValue(idx, out var value)) { TrySubmitPendingPackCollection(idx, value); } NotEnoughResourceTextPopup.ShowText((ENotEnoughResourceText)39); return; } _claimedPackIndices.Add(idx); ContainerOpMessage intent2 = new ContainerOpMessage { Op = 6, Kind = 11, Index = (byte)idx }; WorldPrediction.Predict("world.containers", intent2, delegate { _claimedPackIndices.Add(idx); }, delegate { _claimedPackIndices.Remove(idx); }); } else { NotEnoughResourceTextPopup.ShowText((ENotEnoughResourceText)39); } } private static void ApplyPackRunning(InteractableAutoPackOpener p) { ApplyingRemote = true; try { FiPoIsProcessing?.SetValue(p, true); p.m_CurrentState = 1; PaintPackUI(p); } finally { ApplyingRemote = false; } } private static void ApplyPackSnapshot(InteractableAutoPackOpener p, bool processing, int state, int opened, float timer) { ApplyingRemote = true; try { FiPoIsProcessing?.SetValue(p, processing); FiPoOpenedCount?.SetValue(p, opened); p.m_CurrentState = state; FiPoOpenTimer?.SetValue(p, timer); PaintPackUI(p); } finally { ApplyingRemote = false; } } private void ClientEmptyBoxTake(InteractableEmptyBoxStorage storage) { int num = IndexOf(12, storage); if (num >= 0) { Guid id; ContainerOpMessage intent = new ContainerOpMessage { Op = 10, Kind = 12, Index = (byte)num, BoxNetworkId = ((_playerBox != null && (Object)(object)_playerBox.LocalHeldBox != (Object)null && _boxes.TryGetId(_playerBox.LocalHeldBox, out id)) ? id : Guid.Empty), IsPlayer = true }; WorldPrediction.Predict("world.containers", intent, delegate { int boxStoredCount = storage.GetBoxStoredCount(); FiEmptyStoredCount.SetValue(storage, boxStoredCount - 1); MiEmptyEvaluateStack.Invoke(storage, null); }, delegate { int boxStoredCount = storage.GetBoxStoredCount(); FiEmptyStoredCount.SetValue(storage, boxStoredCount + 1); MiEmptyEvaluateStack.Invoke(storage, null); }); } } private void ApplyEmptyBoxStored(InteractableEmptyBoxStorage storage, Guid boxId) { if ((Object)(object)storage == (Object)null) { return; } InteractablePackagingBox_Item box2; if (_boxes.TryGetBox(boxId, out var box)) { box2 = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (box2 != null) { goto IL_0060; } } if (!_boxes.ClientEnsureWarehouseTake(boxId, (EItemType)(-1), 0, isBig: true, out box2)) { ManualLogSource log = CoopPlugin.Log; Guid guid = boxId; log.LogWarning((object)("Empty-box store replay could not restore box " + guid.ToString() + "; skipping.")); return; } goto IL_0060; IL_0060: ApplyingRemote = true; _boxes.BeginContainerConsume(); try { storage.StoreBox(box2, false); } finally { _boxes.EndContainerConsume(); ApplyingRemote = false; } } private void ApplyEmptyBoxTaken(InteractableEmptyBoxStorage storage, Guid boxId) { if (!((Object)(object)storage == (Object)null)) { int boxStoredCount = storage.GetBoxStoredCount(); if (boxStoredCount > 0) { FiEmptyStoredCount.SetValue(storage, boxStoredCount - 1); MiEmptyEvaluateStack.Invoke(storage, null); } if (!_boxes.ClientEnsureWarehouseTake(boxId, (EItemType)(-1), 0, isBig: true, out var box)) { ManualLogSource log = CoopPlugin.Log; Guid guid = boxId; log.LogWarning((object)("Empty-box store rollback could not restore box " + guid.ToString() + "; skipping.")); } else { TakeIntoLocalHand((InteractablePackagingBox)(object)box); } } } public void ClientApplyPackClaim(ContainerPackClaimMessage message) { int index = message.Index; _claimedPackIndices.Add(index); _pendingPackCollections[index] = new PendingPackCollection { ClaimToken = message.ClaimToken, Cards = new List(message.Cards) }; TrySubmitPendingPackCollection(index, _pendingPackCollections[index]); } private bool TrySubmitPendingPackCollection(int idx, PendingPackCollection pending) { if (pending == null) { return false; } ContainerOpMessage intent = new ContainerOpMessage { Op = 4, Kind = 11, Index = (byte)idx, ClaimToken = pending.ClaimToken, Cards = new List(pending.Cards) }; WorldPrediction.Predict("world.containers", intent, delegate { }, delegate { }, delegate { AbandonPackCollection(idx); }); return true; } private void AbandonPackCollection(int idx) { CoopPlugin.Log.LogWarning((object)("WorldContainerInteraction: pack collect rejected opener=" + idx + "; clearing local claim.")); _pendingPackCollections.Remove(idx); _claimedPackIndices.Remove(idx); } public static void ApplyHostPatches(Harmony h) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Expected O, but got Unknown //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Expected O, but got Unknown //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Expected O, but got Unknown //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Expected O, but got Unknown //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Expected O, but got Unknown //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Expected O, but got Unknown //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Expected O, but got Unknown //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Expected O, but got Unknown //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Expected O, but got Unknown Try(h, typeof(InteractableCardStorageShelf), "SetCompactCardDataAmountList", null, new HarmonyMethod(typeof(WorldContainerInteraction), "HostStorageContentPostfix", (Type[])null)); Try(h, typeof(InteractableBulkDonationBox), "SetCompactCardDataAmountList", null, new HarmonyMethod(typeof(WorldContainerInteraction), "HostDonationContentPostfix", (Type[])null)); Try(h, typeof(InteractableCardStorageShelf), "SetCanWorkerTake", null, new HarmonyMethod(typeof(WorldContainerInteraction), "HostWorkerTakePostfix", (Type[])null)); Try(h, typeof(InteractableCardStorageShelf), "GetRandomCard", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CardStorageRandomCardPostfix", (Type[])null)); Try(h, typeof(InteractableCardStorageShelf), "RemoveRandomCardFromShelf", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CardStorageRandomCardPostfix", (Type[])null)); Try(h, typeof(InteractableBulkDonationBox), "RemoveRandomCardFromShelf", null, new HarmonyMethod(typeof(WorldContainerInteraction), "DonationRandomCardPostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "OnMouseButtonUp", null, new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "AddItem", null, new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "RemoveItem", null, new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "OpenPack", null, new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "TakeItemToHand", null, new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "OnMouseButtonUp", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "AddItem", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "RemoveItem", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "Spray", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserChangedPostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "TakeItemToHand", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserChangedPostfix", (Type[])null)); Try(h, typeof(InteractableEmptyBoxStorage), "TakeBox", null, new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxTakeChangedPostfix", (Type[])null)); Try(h, typeof(InteractableEmptyBoxStorage), "StoreBox", null, new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxStoreChangedPostfix", (Type[])null)); } public static void ApplyClientPatches(Harmony h) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_0098: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown //IL_00d8: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_0144: Expected O, but got Unknown //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown //IL_01c4: Expected O, but got Unknown //IL_01c4: Expected O, but got Unknown //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Expected O, but got Unknown //IL_0218: Expected O, but got Unknown //IL_0218: Expected O, but got Unknown Try(h, typeof(InteractableCardStorageShelf), "SetCompactCardDataAmountList", null, new HarmonyMethod(typeof(WorldContainerInteraction), "StorageContentPostfix", (Type[])null)); Try(h, typeof(InteractableBulkDonationBox), "SetCompactCardDataAmountList", null, new HarmonyMethod(typeof(WorldContainerInteraction), "DonationContentPostfix", (Type[])null)); Try(h, typeof(InteractableCardStorageShelf), "SetCanWorkerTake", new HarmonyMethod(typeof(WorldContainerInteraction), "WorkerTakePrefix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "WorkerTakePostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "OnMouseButtonUp", new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerClickPrefix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerClickPostfix", (Type[])null)); Try(h, typeof(InteractableAutoPackOpener), "AddItem", null, new HarmonyMethod(typeof(WorldContainerInteraction), "PackOpenerAddItemPostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "OnMouseButtonUp", new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserTogglePrefix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserTogglePostfix", (Type[])null)); Try(h, typeof(InteractableAutoCleanser), "AddItem", null, new HarmonyMethod(typeof(WorldContainerInteraction), "CleanserAddItemPostfix", (Type[])null)); Try(h, typeof(InteractableEmptyBoxStorage), "OnMouseButtonUp", new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxTakePrefix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxTakePostfix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxTakeFinalizer", (Type[])null)); Try(h, typeof(InteractableEmptyBoxStorage), "StoreBox", new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxStorePrefix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxStorePostfix", (Type[])null), new HarmonyMethod(typeof(WorldContainerInteraction), "EmptyBoxStoreFinalizer", (Type[])null)); if (FiClItemAmount == null) { CoopPlugin.Log.LogWarning((object)"Field missing: InteractableAutoCleanser.m_ItemAmount - cleanser can count cannot self-heal"); } } internal static void StorageContentPostfix(InteractableCardStorageShelf __instance) { WorldContainerInteraction self = Current; if (ApplyingRemote || (Object)(object)__instance == (Object)null || self == null) { return; } int num = self.IndexOf(9, __instance); if (num >= 0) { List desired = CloneCards(__instance.GetCompactCardDataAmountList()); bool desiredCanTake = __instance.CanWorkerTake(); ContainerOpMessage intent = new ContainerOpMessage { Op = 1, Kind = 9, Index = (byte)num, CanWorkerTake = desiredCanTake, Cards = CloneCards(desired) }; WorldPrediction.Predict("world.containers", intent, delegate { self.ApplyContentInPlace(__instance, CloneCards(desired), desiredCanTake); }, delegate { }); } } internal static void DonationContentPostfix(InteractableBulkDonationBox __instance) { WorldContainerInteraction self = Current; if (ApplyingRemote || (Object)(object)__instance == (Object)null || self == null || !IsSyncedDonationBox(__instance)) { return; } int num = self.IndexOf(13, __instance); if (num >= 0) { List desired = CloneCards(__instance.GetCompactCardDataAmountList()); ContainerOpMessage intent = new ContainerOpMessage { Op = 1, Kind = 13, Index = (byte)num, Cards = CloneCards(desired) }; WorldPrediction.Predict("world.containers", intent, delegate { self.ApplyContentInPlace(__instance, CloneCards(desired)); }, delegate { }); } } internal static void WorkerTakePrefix(InteractableCardStorageShelf __instance, bool canWorkerTake, out WorkerTakeObserve __state) { __state = null; WorldContainerInteraction current = Current; if (!ApplyingRemote && !((Object)(object)__instance == (Object)null) && current != null) { int num = current.IndexOf(9, __instance); if (num >= 0) { __state = new WorkerTakeObserve { Index = num, Prior = __instance.CanWorkerTake(), Desired = canWorkerTake }; } } } internal static void WorkerTakePostfix(InteractableCardStorageShelf __instance, WorkerTakeObserve __state) { if (__state != null && !((Object)(object)__instance == (Object)null) && __state.Desired != __state.Prior) { ContainerOpMessage intent = new ContainerOpMessage { Op = 9, Kind = 9, Index = (byte)__state.Index, CanWorkerTake = __state.Desired }; WorldPrediction.Predict("world.containers", intent, delegate { ApplyWorkerTake(__instance, __state.Desired); }, delegate { ApplyWorkerTake(__instance, __state.Prior); }); } } internal static void CleanserTogglePrefix(InteractableAutoCleanser __instance, out CleanserToggleObserve __state) { __state = null; WorldContainerInteraction current = Current; if (!ApplyingRemote && !((Object)(object)__instance == (Object)null) && current != null) { int num = current.IndexOf(10, __instance); if (num >= 0) { __state = new CleanserToggleObserve { Index = num, Prior = __instance.IsTurnedOn(), PriorCooldown = (FiClCooldown?.GetValue(__instance) as bool? == true), PriorTimer = (FiClTimer?.GetValue(__instance) as float?).GetValueOrDefault() }; } } } internal static void CleanserTogglePostfix(InteractableAutoCleanser __instance, CleanserToggleObserve __state) { if (__state == null || (Object)(object)__instance == (Object)null) { return; } bool desired = __instance.IsTurnedOn(); if (desired != __state.Prior) { ContainerOpMessage intent = new ContainerOpMessage { Op = 7, Kind = 10, Index = (byte)__state.Index, TurnedOn = desired }; WorldPrediction.Predict("world.containers", intent, delegate { ApplyCleanserToggle(__instance, desired); }, delegate { FiClTurnedOn?.SetValue(__instance, __state.Prior); FiClCooldown?.SetValue(__instance, __state.PriorCooldown); FiClTimer?.SetValue(__instance, __state.PriorTimer); }); } } private static void ApplyWorkerTake(InteractableCardStorageShelf shelf, bool canWorkerTake) { ApplyingRemote = true; try { shelf.SetCanWorkerTake(canWorkerTake); shelf.OnCardStorageShelfSettingDone(); } finally { ApplyingRemote = false; } } private static void ApplyCleanserToggle(InteractableAutoCleanser cleanser, bool turnedOn) { ApplyingRemote = true; try { FiClTurnedOn?.SetValue(cleanser, turnedOn); if (!turnedOn) { FiClCooldown?.SetValue(cleanser, true); FiClTimer?.SetValue(cleanser, 0f); } } finally { ApplyingRemote = false; } } public static void HostStorageContentPostfix(InteractableCardStorageShelf __instance) { Current?.HostChanged(9, __instance); } public static void HostDonationContentPostfix(InteractableBulkDonationBox __instance) { if (IsSyncedDonationBox(__instance)) { Current?.HostChanged(13, __instance); } } public static void HostWorkerTakePostfix(InteractableCardStorageShelf __instance) { Current?.HostChanged(9, __instance); } public static void CardStorageRandomCardPostfix(InteractableCardStorageShelf __instance) { Current?.HostChanged(9, __instance); } public static void DonationRandomCardPostfix(InteractableBulkDonationBox __instance) { if (IsSyncedDonationBox(__instance)) { Current?.HostChanged(13, __instance); } } public static void PackOpenerChangedPostfix(InteractableAutoPackOpener __instance) { if (!ApplyingRemote) { Current?.HostChanged(11, __instance); } } public static void CleanserChangedPostfix(InteractableAutoCleanser __instance) { Current?.HostChanged(10, __instance); } internal static void PackOpenerClickPrefix(InteractableAutoPackOpener __instance, out PackOpenerObserve __state) { __state = null; WorldContainerInteraction current = Current; if (!ApplyingRemote && !((Object)(object)__instance == (Object)null) && current != null) { int num = current.IndexOf(11, __instance); if (num >= 0) { __state = new PackOpenerObserve { Index = num, PriorProcessing = __instance.GetIsProcessing(), PriorStored = (__instance.GetStoredItemList()?.Count ?? 0), PriorOutput = (__instance.GetCompactCardDataAmountList()?.Count ?? 0), PriorOpened = __instance.GetPackOpenedCount(), PriorState = __instance.m_CurrentState, PriorTimer = (FiPoOpenTimer?.GetValue(__instance) as float?).GetValueOrDefault() }; } } } internal static void PackOpenerClickPostfix(InteractableAutoPackOpener __instance, PackOpenerObserve __state) { Current?.ClientPackOpenerClick(__instance, __state); } internal static void EmptyBoxTakePostfix(InteractableEmptyBoxStorage __instance, EmptyBoxTakeObserve __state) { WorldContainerInteraction current = Current; if (current != null && (__state == null || !((Object)(object)__instance != (Object)null) || __instance.GetBoxStoredCount() < __state.PriorCount)) { current.ClientEmptyBoxTake(__instance); } } internal static void EmptyBoxTakePrefix(InteractableEmptyBoxStorage __instance, out EmptyBoxTakeObserve __state) { __state = null; WorldContainerInteraction current = Current; if (current?._playerBox != null && !((Object)(object)__instance == (Object)null)) { current._playerBox.BeginCoveredHold(); __state = new EmptyBoxTakeObserve { Covered = true, PriorCount = __instance.GetBoxStoredCount() }; } } internal static void EmptyBoxTakeFinalizer(EmptyBoxTakeObserve __state) { if (__state != null && __state.Covered) { Current?._playerBox.EndCoveredHold(); } } internal static void EmptyBoxStorePrefix(InteractableEmptyBoxStorage __instance, InteractablePackagingBox_Item packagingBox, bool isPlayer, out EmptyBoxStoreObserve __state) { __state = null; if (ApplyingRemote || (Object)(object)__instance == (Object)null || (Object)(object)packagingBox == (Object)null) { return; } WorldContainerInteraction current = Current; if (current?._boxes == null) { return; } if (!isPlayer || !current._boxes.TryGetId((InteractablePackagingBox)(object)packagingBox, out var id)) { if (isPlayer) { CoopPlugin.Log.LogWarning((object)("WorldContainerInteraction: empty-box store of box " + ((Object)packagingBox).name + " has no authoritative id; not forwarding.")); } return; } int num = current.IndexOf(12, __instance); if (num >= 0) { current._boxes.BeginContainerConsume(); __state = new EmptyBoxStoreObserve { Self = current, Storage = __instance, Index = num, BoxNetworkId = id, PriorCount = __instance.GetBoxStoredCount(), IsPlayer = isPlayer, Consuming = true }; } } internal static void EmptyBoxStorePostfix(InteractableEmptyBoxStorage __instance, EmptyBoxStoreObserve __state) { if (__state == null) { return; } __state.EndConsume(); if (!((Object)(object)__instance == (Object)null) && __instance.GetBoxStoredCount() == __state.PriorCount + 1) { WorldContainerInteraction self = __state.Self; ContainerOpMessage intent = new ContainerOpMessage { Op = 11, Kind = 12, Index = (byte)__state.Index, BoxNetworkId = __state.BoxNetworkId, IsPlayer = __state.IsPlayer }; WorldPrediction.Predict("world.containers", intent, delegate { self.ApplyEmptyBoxStored(__state.Storage, __state.BoxNetworkId); }, delegate { self.ApplyEmptyBoxTaken(__state.Storage, __state.BoxNetworkId); }); } } internal static Exception EmptyBoxStoreFinalizer(Exception __exception, EmptyBoxStoreObserve __state) { __state?.EndConsume(); return null; } public static void EmptyBoxTakeChangedPostfix(InteractableEmptyBoxStorage __instance) { Current?.HostChanged(12, __instance); } public static void EmptyBoxStoreChangedPostfix(InteractableEmptyBoxStorage __instance) { Current?.HostChanged(12, __instance); } public static void PackOpenerAddItemPostfix(InteractableAutoPackOpener __instance, Item item) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if (ApplyingRemote || (Object)(object)__instance == (Object)null || (Object)(object)item == (Object)null || Reloading) { return; } WorldContainerInteraction self = Current; int num = self?.IndexOf(11, __instance) ?? (-1); if (num < 0) { CoopPlugin.Log.LogWarning((object)"WorldContainerInteraction: pack insert had no placement identity; not forwarding."); return; } ContainerOpMessage intent = new ContainerOpMessage { Op = 2, Kind = 11, Index = (byte)num, ItemType = item.GetItemType() }; Guid predictionId = Guid.Empty; predictionId = WorldPrediction.Predict(PackInsertPredictionKey(num), intent, delegate { ApplyingRemote = true; try { if (!__instance.GetStoredItemList().Contains(item)) { ((Component)item).gameObject.SetActive(true); __instance.AddItem(item, true, false); } } finally { ApplyingRemote = false; } }, delegate { if (!PredictionApi.IsPending(predictionId) || !__instance.GetStoredItemList().Contains(item)) { return; } ApplyingRemote = true; try { __instance.RemoveItem(item); } finally { ApplyingRemote = false; } }, delegate { self._pendingPackInserts.Remove(predictionId); ApplyingRemote = true; try { if (__instance.GetStoredItemList().Contains(item)) { __instance.RemoveItem(item); } } finally { ApplyingRemote = false; } RestoreHeldPack(item); }); self._pendingPackInserts.Add(predictionId); } internal static void CleanserAddItemPostfix(InteractableAutoCleanser __instance, Item item) { if (ApplyingRemote || (Object)(object)__instance == (Object)null || (Object)(object)item == (Object)null || Reloading) { return; } int num = Current?.IndexOf(10, __instance) ?? (-1); if (num < 0) { CoopPlugin.Log.LogWarning((object)"WorldContainerInteraction: cleanser refill had no placement identity; not forwarding."); return; } ContainerOpMessage intent = new ContainerOpMessage { Op = 8, Kind = 10, Index = (byte)num, Fill = item.GetContentFill() }; WorldPrediction.Predict("world.containers", intent, delegate { ApplyingRemote = true; try { if (!__instance.GetStoredItemList().Contains(item)) { __instance.AddItem(item, true); } } finally { ApplyingRemote = false; } }, delegate { ApplyingRemote = true; try { if (__instance.GetStoredItemList().Contains(item)) { __instance.RemoveItem(item); } } finally { ApplyingRemote = false; } }); } private static string PackInsertPredictionKey(int idx) { return "world.containers:pack:" + idx; } private static void RestoreHeldPack(Item item) { if (!((Object)(object)item == (Object)null)) { ((Component)item).gameObject.SetActive(true); InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { val.AddHoldItemToFront(item); } } } private static void RestorePackOpener(InteractableAutoPackOpener opener, List stored, bool processing, float timer, int openedCount, int currentState) { List storedItemList = opener.GetStoredItemList(); if (storedItemList != null) { for (int num = storedItemList.Count - 1; num >= 0; num--) { if (!stored.Contains(storedItemList[num])) { Item val = storedItemList[num]; storedItemList.RemoveAt(num); if ((Object)(object)val != (Object)null) { val.DisableItem(); } } } storedItemList.Clear(); storedItemList.AddRange(stored); } FiPoIsProcessing?.SetValue(opener, processing); FiPoOpenTimer?.SetValue(opener, timer); FiPoOpenedCount?.SetValue(opener, openedCount); opener.m_CurrentState = currentState; } private static void RestoreCleanser(InteractableAutoCleanser cleanser, List stored, int itemAmount, bool needRefill) { List storedItemList = cleanser.GetStoredItemList(); if (storedItemList != null) { for (int num = storedItemList.Count - 1; num >= 0; num--) { if (!stored.Contains(storedItemList[num])) { Item val = storedItemList[num]; storedItemList.RemoveAt(num); if ((Object)(object)val != (Object)null) { val.DisableItem(); } } } storedItemList.Clear(); storedItemList.AddRange(stored); } FiClItemAmount?.SetValue(cleanser, itemAmount); FiClNeedRefill?.SetValue(cleanser, needRefill); } private static Item SpawnItem(EItemType itemType, Transform parent, float contentFill = -1f) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0002: 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_0036: 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) Item val = null; try { ItemMeshData itemMeshData = InventoryBase.GetItemMeshData(itemType); val = ItemSpawnManager.GetItem(parent); val.SetMesh(itemMeshData.mesh, itemMeshData.material, itemType, itemMeshData.meshSecondary, itemMeshData.materialSecondary, (List)null); ((Component)val).transform.localPosition = Vector3.zero; ((Component)val).transform.localRotation = Quaternion.identity; if (contentFill >= 0f) { val.SetContentFill(contentFill); } ((Component)val).gameObject.SetActive(true); return val; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)$"WorldContainerInteraction spawn {itemType}: {ex.Message}"); if ((Object)(object)val != (Object)null) { val.DisableItem(); } throw; } } private static List CloneCards(List cards) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) List list = new List(cards?.Count ?? 0); int num = 0; while (cards != null && num < cards.Count) { CompactCardDataAmount val = cards[num]; list.Add((val == null) ? ((CompactCardDataAmount)null) : new CompactCardDataAmount { expansionType = val.expansionType, cardSaveIndex = val.cardSaveIndex, amount = val.amount, gradedCardIndex = val.gradedCardIndex, isDestiny = val.isDestiny }); num++; } return list; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool IsFinite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } private static void Try(Harmony h, Type type, string method, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod finalizer = null) { try { MethodInfo methodInfo = ReflectionSurface.RequiredMethod(type, method); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("Patch target missing: " + type.Name + "." + method)); } else { h.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, finalizer, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Patch failed for " + type.Name + "." + method + ": " + ex.Message)); } } } [NetworkMessage] public sealed class ContainerOpMessage : WorldMessage { public byte Op; public byte Kind; public byte Index; public bool CanWorkerTake; public List Cards = new List(); public EItemType ItemType; public ushort StorageId; public Vector3 Position; public Guid BoxNetworkId; public bool IsBig; public bool TurnedOn; public bool IsPlayer; public float Fill; public int ClaimToken; } [NetworkMessage] public sealed class ContainerStateMessage : WorldMessage { public List Records = new List(); } [NetworkMessage] public sealed class ContainerDeltaMessage : WorldMessage { public int Key; public ContainerRecord Record; public bool HasBox; public BoxNetworkState Box; public bool TakeIntoHand; public bool ReleaseHold; public bool CompletePackCollection; public byte PackIndex; } public struct ContainerRecord { public string StableEntityId; public byte Kind; public byte Index; public bool CanWorkerTake; public List Cards; public List StoredTypes; public bool Processing; public float Timer; public int OpenedCount; public ushort StorageId; public int Count; public byte Flags; public List Fills; public int CurrentState; public bool CollectClaimed; } [NetworkMessage] public sealed class ContainerPackClaimMessage : WorldMessage { public byte Index; public int ClaimToken; public List Cards = new List(); } internal sealed class WorldGradingBridge : IGradingWorldBridge { private const float DeliveryFee = 10f; private readonly WorldCardInteraction _world; public bool SceneReady { get { if (!_world.InGameLevel()) { return false; } InventoryBase obj = WorldCardInteraction.Inv(); ShelfManager val = SceneRef.Get(); if ((Object)(object)obj != (Object)null && (Object)(object)val != (Object)null) { return val.m_FinishLoadingObjectData; } return false; } } internal WorldGradingBridge(WorldCardInteraction world) { _world = world ?? throw new ArgumentNullException("world"); } public bool CardSetInstalled(CardData card) { return WorldCardInteraction.CardSetInstalledHere(card); } public bool TryGetVanillaServiceCost(int serviceLevel, int cardCount, out float total) { total = 0f; if (cardCount < 0) { return false; } GradeCardServiceData service = GetService(serviceLevel); if (service == null) { return false; } total = 10f + service.m_CostPerCard * (float)cardCount; if (!float.IsNaN(total) && !float.IsInfinity(total)) { return total >= 0f; } return false; } public bool TryReserveCards(int connectionId, IList cards, out IGradingCardReservation reservation, out string reason) { return _world.TryReserveGradingCards(connectionId, cards, out reservation, out reason); } public bool TryAddCard(CardData card, int amount) { if (card == null || amount <= 0) { return false; } int num = CountGraded(card); using (_world.BeginRemoteCardApply()) { CPlayerData.AddCard(WorldCardInteraction.SnapshotCard(card), amount); int num2 = CountGraded(card); if (num2 == num + amount) { return true; } for (int i = 0; i < Math.Max(0, num2 - num); i++) { if (CPlayerData.HasGradedCardInAlbum(card)) { CPlayerData.RemoveGradedCard(card, true); } } } return false; } public void NotifyCardsChanged() { _world.NotifyCardsChanged(); } public void WarnRefused(CardData card, string context) { WorldCardInteraction.WarnRefusedCard(card, context); } private GradeCardServiceData GetService(int serviceLevel) { MonsterData_ScriptableObject val = WorldCardInteraction.Inv()?.m_MonsterData_SO; if ((Object)(object)val == (Object)null || serviceLevel < 0) { return null; } try { return val.GetGradeCardServiceData(serviceLevel); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("World grading bridge: service level " + serviceLevel + " could not be resolved on the host - " + ex.Message)); return null; } } private static int CountGraded(CardData card) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) List gradedCardInventoryList = CPlayerData.m_GradedCardInventoryList; if (gradedCardInventoryList == null || card == null) { return 0; } int num = GradingApi.Encoded(card); int num2 = 0; for (int i = 0; i < gradedCardInventoryList.Count; i++) { CompactCardDataAmount val = gradedCardInventoryList[i]; CardData val2 = ((val == null) ? null : CPlayerData.GetGradedCardData(val)); if (val2 != null && val2.expansionType == card.expansionType && val2.monsterType == card.monsterType && val2.borderType == card.borderType && val2.isFoil == card.isFoil && val2.isDestiny == card.isDestiny && GradingApi.Encoded(val2) == num) { num2++; } } return num2; } } [ServerBehaviour] public sealed class WorldHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxItem")] private static class ItemBoxCreatedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __result) { _instance?.RegisterCreatedBox((InteractablePackagingBox)(object)__result); } } [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxCard")] private static class CardBoxCreatedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Card __result) { _instance?.RegisterCreatedBox((InteractablePackagingBox)(object)__result); } } [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxShelf")] private static class FurnitureBoxCreatedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Shelf __result) { _instance?.RegisterCreatedBox((InteractablePackagingBox)(object)__result); } } [HarmonyPatch(typeof(ShelfManager), "SpawnInteractableObjectInPackageBox")] private static class FurniturePurchaseBoxCreatedPatch { [HarmonyPrefix] private static void Prefix() { _instance?.BeginFurniturePurchaseBox(); } [HarmonyPostfix] private static void Postfix(EObjectType objType, Vector3 spawnPos) { //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) _instance?.CompleteFurniturePurchaseBox(objType, spawnPos); } } [HarmonyPatch(typeof(RestockManager), "Update")] private static class HostRestockUpdatePatch { [HarmonyPrefix] private static void Prefix() { _instance?._boxNetworkInteraction?.BeginDeliverySpawns(); } [HarmonyPostfix] private static void Postfix() { _instance?._boxNetworkInteraction?.EndDeliverySpawns(); } } [HarmonyPatch(typeof(RestockManager), "SpawnPackageBoxItemMultipleFrame")] private static class HostRestockEnqueuePatch { [HarmonyPostfix] private static void Postfix(int count) { _instance?._boxNetworkInteraction?.CommitHostDeliveryEntry(count); } } [HarmonyPatch(typeof(RestockManager), "Init")] private static class HostRestockInitPatch { [HarmonyPostfix] private static void Postfix() { _instance?._boxNetworkInteraction?.ResyncHostDeliveryEntries(); } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnDestroyed")] private static class ItemBoxDestroyedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance) { _instance?.NotifyDestroyedBox((InteractablePackagingBox)(object)__instance); } } [HarmonyPatch(typeof(InteractablePackagingBox_Card), "OnDestroyed")] private static class CardBoxDestroyedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Card __instance) { _instance?.NotifyDestroyedBox((InteractablePackagingBox)(object)__instance); } } [HarmonyPatch(typeof(InteractablePackagingBox_Shelf), "OnDestroyed")] private static class FurnitureBoxDestroyedPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Shelf __instance) { _instance?.NotifyDestroyedBox((InteractablePackagingBox)(object)__instance); } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "SetOpenCloseBox")] private static class HostItemBoxOpenStatePatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance, bool isPlayer) { if (isPlayer) { _instance?.BroadcastItemBoxState(__instance); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "DispenseItem")] private static class HostItemBoxContentsPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item __instance, out int __state) { int? obj; if (__instance == null) { obj = null; } else { ShelfCompartment itemCompartment = __instance.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance, bool isPlayer, int __state) { if (isPlayer && (Object)(object)__instance?.m_ItemCompartment != (Object)null && __instance.m_ItemCompartment.GetItemCount() != __state) { _instance?.BroadcastItemBoxState(__instance); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "RemoveItemFromShelf")] private static class HostItemBoxRemoveFromShelfPatch { [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item __instance, bool isPlayer) { if (isPlayer) { _instance?.BroadcastItemBoxState(__instance); } } } [HarmonyPatch(typeof(InteractableAutoPackOpener), "DispenseItemFromBox")] private static class HostPackOpenerDispenseFromBoxPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item itemBox, out int __state) { int? obj; if (itemBox == null) { obj = null; } else { ShelfCompartment itemCompartment = itemBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item itemBox, int __state) { BroadcastDispensedSourceBox(itemBox, __state); } } [HarmonyPatch(typeof(InteractableAutoCleanser), "DispenseItemFromBox")] private static class HostCleanserDispenseFromBoxPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item itemBox, out int __state) { int? obj; if (itemBox == null) { obj = null; } else { ShelfCompartment itemCompartment = itemBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox_Item itemBox, int __state) { BroadcastDispensedSourceBox(itemBox, __state); } } [HarmonyPatch(typeof(InteractableCardCompartment), "SetCardOnShelf")] private static class CardDisplayPlacePatch { [HarmonyPostfix] private static void Postfix(InteractableCardCompartment __instance) { _instance?.PublishHostCardDisplay(__instance, occupied: true); } } [HarmonyPatch(typeof(InteractableCardCompartment), "RemoveCardFromShelf")] private static class CardDisplayRemovePatch { [HarmonyPostfix] private static void Postfix(InteractableCardCompartment __instance) { _instance?.PublishHostCardDisplay(__instance, occupied: false); } } private sealed class AppliedIntentLedger { private const int Capacity = 512; private readonly HashSet _ids = new HashSet(); private readonly Queue _order = new Queue(); internal IEnumerable Ids => _order; internal void Add(Guid id, Action onEvicted) { if (_ids.Add(id)) { _order.Enqueue(id); while (_order.Count > 512) { Guid guid = _order.Dequeue(); _ids.Remove(guid); onEvicted(guid); } } } } [HarmonyPatch(typeof(InteractableObject), "StartMoveObject")] private static class PlacementHoldStartPatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { _instance?._placementHold?.LocalStarted(__instance); } } [HarmonyPatch(typeof(InteractableObject), "AddObjectRotation")] private static class PlacementHoldRotatePatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { _instance?._placementHold?.LocalRotated(__instance); } } [HarmonyPatch(typeof(InteractableObject), "Flip")] private static class PlacementHoldFlipPatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { _instance?._placementHold?.LocalRotated(__instance); } } [HarmonyPatch(typeof(InteractablePackagingBox), "StartHoldBox")] private static class PlayerBoxPickupPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox __instance, bool isPlayer, out PlayerBoxInteraction.LocalAction __state) { __state = (((Object)(object)_instance == (Object)null) ? default(PlayerBoxInteraction.LocalAction) : _instance.CapturePlayerBoxAction(__instance, isPlayer, alignBody: false)); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox __instance, PlayerBoxInteraction.LocalAction __state) { _instance?.PublishPlayerBoxPickup(__instance, __state); } } [HarmonyPatch(typeof(InteractablePackagingBox), "ThrowBox")] private static class PlayerBoxThrowPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox __instance, bool isPlayer, out PlayerBoxInteraction.LocalAction __state) { if (isPlayer && __instance != null) { ((InteractableObject)__instance).StopLerpToTransform(); } __state = (((Object)(object)_instance == (Object)null) ? default(PlayerBoxInteraction.LocalAction) : _instance.CapturePlayerBoxAction(__instance, isPlayer, alignBody: true)); } [HarmonyPostfix] private static void Postfix(InteractablePackagingBox __instance, PlayerBoxInteraction.LocalAction __state) { _instance?.PublishPlayerBoxThrow(__instance, __state); } } [HarmonyPatch(typeof(InteractablePackagingBox), "DropBox")] private static class PlayerBoxDropPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox __instance, bool isPlayer) { if (isPlayer && __instance != null) { ((InteractableObject)__instance).StopLerpToTransform(); } } } [HarmonyPatch(typeof(InteractableObject), "PlaceMovedObject")] private static class PlayerBoxPlacementPatch { [HarmonyPrefix] private static void Prefix(InteractableObject __instance, out PlayerBoxInteraction.LocalAction __state) { PlayerBoxInteraction.LocalAction obj; if ((Object)(object)_instance != (Object)null) { InteractablePackagingBox val = (InteractablePackagingBox)(object)((__instance is InteractablePackagingBox) ? __instance : null); if (val != null && PlacementInterop.ReadMoveValidity((InteractableObject)(object)val)) { obj = _instance.CapturePlayerBoxAction(val, isPlayer: true, alignBody: false); goto IL_0038; } } obj = default(PlayerBoxInteraction.LocalAction); goto IL_0038; IL_0038: __state = obj; } [HarmonyPostfix] private static void Postfix(InteractableObject __instance, PlayerBoxInteraction.LocalAction __state) { InteractablePackagingBox val = (InteractablePackagingBox)(object)((__instance is InteractablePackagingBox) ? __instance : null); if (val != null) { _instance?.PublishPlayerBoxPlacement(val, __state); } } } [HarmonyPatch(typeof(ShelfCompartment), "AddItem")] private static class AddItemPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, Item item, bool addToFront, out ShelfInteraction.LocalMutation __state) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) __state = (((Object)(object)_instance == (Object)null) ? default(ShelfInteraction.LocalMutation) : _instance.CaptureShelfMutation(__instance, add: true, (EItemType)(((Object)(object)item == (Object)null) ? (-1) : ((int)item.GetItemType())), item)); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, ShelfInteraction.LocalMutation __state) { _instance?.PublishShelfAdd(__instance, __state); WorldHostBehaviour instance = _instance; InteractablePackagingBox_Item val = instance?.FindItemBox(__instance); if ((Object)(object)val != (Object)null && !instance.IsBoxHeld((InteractablePackagingBox)(object)val)) { instance.BroadcastItemBoxState(val); } } } [HarmonyPatch(typeof(InteractionPlayerController), "EvaluatePutItemOnShelf")] private static class PutItemOnShelfScopePatch { [HarmonyPrefix] private static void Prefix(out bool __state) { __state = (Object)(object)_instance != (Object)null; if (__state) { _instance.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(InteractionPlayerController), "EvaluateTakeItemFromShelf")] private static class TakeItemFromShelfScopePatch { [HarmonyPrefix] private static void Prefix(out bool __state) { __state = (Object)(object)_instance != (Object)null; if (__state) { _instance.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnHoldStateLeftMousePress")] private static class BoxAddToShelfScopePatch { [HarmonyPrefix] private static void Prefix(bool isPlayer, out bool __state) { __state = isPlayer; if (__state) { _instance?.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnHoldStateRightMousePress")] private static class BoxRemoveFromShelfScopePatch { [HarmonyPrefix] private static void Prefix(bool isPlayer, out bool __state) { __state = isPlayer; if (__state) { _instance?.EnterPlayerShelfMutation(); } } [HarmonyFinalizer] private static void Finalizer(bool __state) { if (__state) { _instance?.ExitPlayerShelfMutation(); } } } [HarmonyPatch(typeof(ShelfCompartment), "RemoveItem")] private static class RemoveItemPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, Item item, out ShelfInteraction.LocalMutation __state) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) __state = (((Object)(object)_instance == (Object)null) ? default(ShelfInteraction.LocalMutation) : _instance.CaptureShelfMutation(__instance, add: false, (EItemType)(((Object)(object)item == (Object)null) ? (-1) : ((int)item.GetItemType())), item)); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, ShelfInteraction.LocalMutation __state) { _instance?.PublishShelfRemove(__instance, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "TakeItemToHand")] private static class TakeItemToHandPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, bool getLastItem, out ShelfInteraction.LocalMutation __state) { __state = (((Object)(object)_instance == (Object)null) ? default(ShelfInteraction.LocalMutation) : _instance.CaptureShelfMutation(__instance, add: false, (EItemType)(-1), null)); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, Item __result, ShelfInteraction.LocalMutation __state) { if ((Object)(object)__result != (Object)null) { __state.Removed = __result; _instance?.PublishShelfRemove(__instance, __state); WorldHostBehaviour instance = _instance; InteractablePackagingBox_Item val = instance?.FindItemBox(__instance); if ((Object)(object)val != (Object)null) { instance.BroadcastItemBoxState(val); } } } } [HarmonyPatch(typeof(ShelfCompartment), "SetCompartmentItemType")] private static class SetCompartmentItemTypePatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out EItemType __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) __state = (EItemType)(((Object)(object)__instance == (Object)null) ? (-1) : ((int)__instance.GetItemType())); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, EItemType __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _instance?.PublishLabelChange(__instance, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "RemoveLabel")] private static class RemoveLabelPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out EItemType __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) __state = (EItemType)(((Object)(object)__instance == (Object)null) ? (-1) : ((int)__instance.GetItemType())); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, EItemType __state) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.GetWarehouseShelf() != (Object)null) { _instance?.PublishLabelChange(__instance, __state, allowWarehouse: true); } } } [HarmonyPatch(typeof(InteractablePackagingBox_Item), "OnFinishLerp")] private static class BoxRecordStoreCompletePatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item __instance) { _instance?.Warehouse?.BeginLocalRecordStore(__instance); } [HarmonyPostfix] private static void Postfix() { _instance?.Warehouse?.EndLocalRecordStore(); } [HarmonyFinalizer] private static void Finalizer() { _instance?.Warehouse?.EndLocalRecordStore(); } } [HarmonyPatch(typeof(ShelfCompartment), "AddStoredBoxRecord")] private static class StoredBoxRecordAddedPatch { [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance) { _instance?.NotifyStoredBoxRecordAdded(__instance); } } [HarmonyPatch(typeof(ShelfCompartment), "ArrangeBoxItemBasedOnItemCount")] private static class StoredBoxRecordArrangePatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out List __state) { __state = _instance?.Warehouse?.CaptureRecordOrder(__instance); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, List __state) { _instance?.Warehouse?.ApplyRecordOrder(__instance, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "TryPopLastStoredBoxRecord")] private static class StoredBoxRecordPoppedPatch { [HarmonyPrefix] private static void Prefix(ShelfCompartment __instance, out WarehouseBoxState __state) { __state = _instance?.Warehouse?.CapturePoppedRecord(__instance); } [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, bool __result, WarehouseBoxState __state) { _instance?.NotifyStoredBoxRecordPopped(__instance, __result, __state); } } [HarmonyPatch(typeof(ShelfCompartment), "AddBox")] private static class BoxAddedPatch { [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance) { _instance?.NotifyWarehouseBoxAdded(__instance); } } [HarmonyPatch(typeof(ShelfCompartment), "RemoveBox")] private static class BoxRemovedPatch { [HarmonyPostfix] private static void Postfix(ShelfCompartment __instance, InteractablePackagingBox_Item __0) { _instance?.NotifyWarehouseBoxRemoved(__instance, __0); } } [HarmonyPatch(typeof(InteractableWorkbench), "AddItem")] private static class HostWorkbenchAddItemPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?.PublishWorkbenchStorage(__instance); } } [HarmonyPatch(typeof(InteractableWorkbench), "RemoveItem")] private static class HostWorkbenchRemoveItemPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?.PublishWorkbenchStorage(__instance); } } [HarmonyPatch(typeof(InteractableWorkbench), "TakeItemToHand")] private static class HostWorkbenchTakeItemToHandPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?.PublishWorkbenchStorage(__instance); } } [HarmonyPatch(typeof(InteractableWorkbench), "DispenseItemFromBox")] private static class HostWorkbenchDispenseFromBoxPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item itemBox, out int __state) { int? obj; if (itemBox == null) { obj = null; } else { ShelfCompartment itemCompartment = itemBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, InteractablePackagingBox_Item itemBox, int __state) { if (!WorldWorkbenchInteraction.ApplyingRemote && (Object)(object)itemBox?.m_ItemCompartment != (Object)null && itemBox.m_ItemCompartment.GetItemCount() != __state) { _instance?.BroadcastItemBoxState(itemBox); } } } [HarmonyPatch(typeof(InteractableWorkbench), "RemoveItemFromShelf")] private static class HostWorkbenchRemoveFromShelfPatch { [HarmonyPrefix] private static void Prefix(InteractablePackagingBox_Item packageBox, out int __state) { int? obj; if (packageBox == null) { obj = null; } else { ShelfCompartment itemCompartment = packageBox.m_ItemCompartment; obj = ((itemCompartment != null) ? new int?(itemCompartment.GetItemCount()) : ((int?)null)); } int? num = obj; __state = num.GetValueOrDefault(); } [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance, InteractablePackagingBox_Item packageBox, int __state) { if (!WorldWorkbenchInteraction.ApplyingRemote && (Object)(object)packageBox?.m_ItemCompartment != (Object)null && packageBox.m_ItemCompartment.GetItemCount() != __state) { _instance?.BroadcastItemBoxState(packageBox); } } } [HarmonyPatch(typeof(InteractableWorkbench), "PlayBundlingCardBoxSequence")] private static class HostWorkbenchBundleStartPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?._workbenchInteraction?.HostBundleStarted(__instance); } } [HarmonyPatch(typeof(InteractableWorkbench), "OnTaskCompleted")] private static class HostWorkbenchTaskCompletedPatch { [HarmonyPostfix] private static void Postfix(InteractableWorkbench __instance) { _instance?._workbenchInteraction?.HostBundleCompleted(__instance); } } private bool _cardDisplayApplying; private static WorldHostBehaviour _instance; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private BoxNetworkInteraction _boxNetworkInteraction; private PlayerBoxInteraction _playerBoxInteraction; private ShelfInteraction _shelfInteraction; private WarehouseShelfInteraction _warehouseShelfInteraction; private WorldCardInteraction _cardInteraction; private long _nextBaselineId = 1L; private readonly Dictionary _transferManifests = new Dictionary(); private int _furniturePurchaseSpawnDepth; private readonly Stack _pendingFurniturePurchaseBoxes = new Stack(); private readonly Dictionary _appliedPredictions = new Dictionary(); private readonly Dictionary _predictionOwners = new Dictionary(); private const float MaxPlacementCoordinate = 1000f; private readonly PlacementPopulationState _placementPopulation = new PlacementPopulationState(); private readonly Dictionary _known = new Dictionary(); private readonly Dictionary _knownKeyById = new Dictionary(); private readonly HashSet _fullyJoined = new HashSet(); private int _applyingIntent; private Guid _nextMutationPrediction; private const float PlacementReach = 6f; private PlacementHoldInteraction _placementHold; private readonly List _releasedHolds = new List(); private WorldWorkbenchInteraction _workbenchInteraction; internal static WorldHostBehaviour Active => _instance; internal static WorldCardInteraction ActiveCards => _instance?._cardInteraction; private WarehouseShelfInteraction Warehouse => _warehouseShelfInteraction; private void InstallBoxNetworkInteractionPatches() { //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Expected O, but got Unknown _harmony.CreateClassProcessor(typeof(ItemBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CardBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(FurnitureBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(FurniturePurchaseBoxCreatedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ItemBoxDestroyedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CardBoxDestroyedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(FurnitureBoxDestroyedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostItemBoxOpenStatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostItemBoxContentsPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostItemBoxRemoveFromShelfPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostPackOpenerDispenseFromBoxPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostCleanserDispenseFromBoxPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostRestockUpdatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostRestockEnqueuePatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostRestockInitPatch)).Patch(); MethodInfo methodInfo = AccessTools.Method(typeof(RestockManager), "SpawnPackageBoxItemAtTransform", (Type[])null, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(WorldHostBehaviour), "RecordTakeBoxCreated", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } [MessageHandler(typeof(BoxDestroyRequestMessage))] private void HandleBoxDestroyRequest(MessageContext context, BoxDestroyRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context) && message != null) { ExecuteWorldCommand(context, message, () => _boxNetworkInteraction != null && _boxNetworkInteraction.HostDestroyRequested(message.BoxNetworkId, message.PredictionId)); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(FurnitureBoxUpRequestMessage))] private void HandleFurnitureBoxUp(MessageContext context, FurnitureBoxUpRequestMessage message) { if (!_context.InGame() || !IsJoinPhaseSender(context) || message == null) { RejectWorldIntent(context, message); return; } ExecuteWorldCommand(context, message, delegate { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!PlacementApi.TryResolveBoxableFurnitureEntityId(message.StableEntityId, 1L, message.ObjectType, out var result)) { CoopPlugin.Log.LogWarning((object)("[box-furniture] rejected box-up request entity=" + (message.StableEntityId ?? "") + " type=" + ((object)Unsafe.As(ref message.ObjectType)/*cast due to .constrained prefix*/).ToString() + ".")); return false; } if (PlacementApi.TryMakeObjectKey(PlacementInterop.FindKind(result), result, out var key)) { _placementHold?.EndIfHeldBy(key, context.ConnectionId); } if (result.GetIsBoxedUp() || result.GetIsMovingObject()) { CoopPlugin.Log.LogWarning((object)("[box-furniture] rejected box-up request entity=" + (message.StableEntityId ?? "") + " type=" + ((object)Unsafe.As(ref message.ObjectType)/*cast due to .constrained prefix*/).ToString() + ".")); return false; } if (!_boxNetworkInteraction.PushHostCreatedId(message.BoxNetworkId)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[7] { "[box-furniture] rejected box-up request entity=", message.StableEntityId ?? "", " type=", ((object)Unsafe.As(ref message.ObjectType)/*cast due to .constrained prefix*/).ToString(), " id=", null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[5] = boxNetworkId.ToString(); obj[6] = ": the creator id is not fresh."; log.LogWarning((object)string.Concat(obj)); return false; } CoopPlugin.Log.LogInfo((object)("[box-furniture] applying host box-up entity=" + message.StableEntityId + " type=" + ((object)Unsafe.As(ref message.ObjectType)/*cast due to .constrained prefix*/).ToString() + ".")); _boxNetworkInteraction.HostPredictionId = message.PredictionId; try { result.BoxUpObject(false); } finally { _boxNetworkInteraction.HostPredictionId = Guid.Empty; } if ((Object)(object)result.GetPackagingBoxShelf() == (Object)null) { _boxNetworkInteraction.CancelHostCreatedId(message.BoxNetworkId); CoopPlugin.Log.LogWarning((object)("[box-furniture] box-up produced no package box entity=" + (message.StableEntityId ?? "") + "; released creator id.")); return false; } GrantFurnitureHold(result, context.ConnectionId); return true; }); } private void GrantFurnitureHold(InteractableObject furniture, int holderConnectionId) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (_playerBoxInteraction != null && _boxNetworkInteraction != null && !((Object)(object)furniture == (Object)null)) { InteractablePackagingBox_Shelf packagingBoxShelf = furniture.GetPackagingBoxShelf(); if ((Object)(object)packagingBoxShelf == (Object)null || !_boxNetworkInteraction.TryGetId((InteractablePackagingBox)(object)packagingBoxShelf, out var id)) { CoopPlugin.Log.LogWarning((object)"[box-furniture] boxed furniture has no network id; cannot hand off the hold."); return; } PlayerBoxPickupMessage message = new PlayerBoxPickupMessage { BoxNetworkId = id, HeldPosition = ((Component)packagingBoxShelf).transform.position, HeldRotation = ((Component)packagingBoxShelf).transform.rotation, HolderConnectionId = holderConnectionId }; _playerBoxInteraction.ApplyIncoming(message); BroadcastWorld((INetMessage)(object)message); } } [MessageHandler(typeof(BoxStateRequestMessage))] private void HandleBoxStateRequest(MessageContext context, BoxStateRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, delegate { InteractablePackagingBox_Item val; if (_boxNetworkInteraction.TryGetBox(message.BoxNetworkId, out var box)) { val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { bool flag = _boxNetworkInteraction.IsBeingHeld((InteractablePackagingBox)(object)val); if (message.ContentsChanged) { int holderConnectionId = 0; bool flag2 = _playerBoxInteraction != null && _playerBoxInteraction.TryGetRemoteHolder(message.BoxNetworkId, out holderConnectionId); bool num; if (!flag2) { if (flag) { goto IL_0165; } num = BoxNetworkInteraction.IsBoxOpen(val); } else { num = holderConnectionId == context.ConnectionId; } if (!num) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[13] { "[box-id] rejected box state request id=", null, null, null, null, null, null, null, null, null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[1] = boxNetworkId.ToString(); obj[2] = " name="; obj[3] = ((Object)val).name; obj[4] = " from conn="; obj[5] = context.ConnectionId.ToString(); obj[6] = ": the sender does not hold the box (isHeld="; obj[7] = flag.ToString(); obj[8] = " open="; obj[9] = BoxNetworkInteraction.IsBoxOpen(val).ToString(); obj[10] = " mirroredHolder="; obj[11] = (flag2 ? holderConnectionId.ToString() : "none"); obj[12] = ")."; log.LogWarning((object)string.Concat(obj)); return false; } } goto IL_0165; } } return false; IL_0165: if (!_boxNetworkInteraction.ApplyHostBoxState(val, message)) { return false; } RelayItemBoxState(val, context.ConnectionId); return true; }); } else { RejectWorldIntent(context, message); } } private void BroadcastItemBoxState(InteractablePackagingBox_Item item) { BoxStateMessage boxStateMessage = BuildItemBoxState(item); if (boxStateMessage != null) { BroadcastWorld((INetMessage)(object)boxStateMessage); } } private void RelayItemBoxState(InteractablePackagingBox_Item item, int excludeConnectionId) { BoxStateMessage boxStateMessage = BuildItemBoxState(item); if (boxStateMessage != null) { RelayWorldExcept(excludeConnectionId, (INetMessage)(object)boxStateMessage); } } private BoxStateMessage BuildItemBoxState(InteractablePackagingBox_Item item) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (_boxNetworkInteraction == null || (Object)(object)item == (Object)null || !_boxNetworkInteraction.TryGetId((InteractablePackagingBox)(object)item, out var id)) { return null; } ShelfCompartment itemCompartment = item.m_ItemCompartment; return new BoxStateMessage { BoxNetworkId = id, IsBoxOpened = BoxNetworkInteraction.IsBoxOpen(item), ContentsChanged = true, ItemType = (EItemType)(((Object)(object)itemCompartment == (Object)null) ? (-1) : ((int)itemCompartment.GetItemType())), ItemCount = ((!((Object)(object)itemCompartment == (Object)null)) ? itemCompartment.GetItemCount() : 0) }; } private void RegisterCreatedBox(InteractablePackagingBox box) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown if (box is InteractablePackagingBox_Shelf && _furniturePurchaseSpawnDepth > 0) { _boxNetworkInteraction?.EnsureHostId(box); _pendingFurniturePurchaseBoxes.Push((InteractablePackagingBox_Shelf)box); return; } _boxNetworkInteraction?.RegisterHostCreated(box); InteractablePackagingBox_Shelf val = (InteractablePackagingBox_Shelf)(object)((box is InteractablePackagingBox_Shelf) ? box : null); if (val == null) { return; } BoxNetworkInteraction boxNetworkInteraction = _boxNetworkInteraction; if (boxNetworkInteraction != null && boxNetworkInteraction.TryGetId((InteractablePackagingBox)(object)val, out var id)) { ManualLogSource log = CoopPlugin.Log; Guid guid = id; log.LogInfo((object)("[box-furniture] authoritative package id=" + guid.ToString() + " assigned before vanilla hold transition.")); if (_boxNetworkInteraction.IsBeingHeld((InteractablePackagingBox)(object)val)) { ManualLogSource log2 = CoopPlugin.Log; guid = id; log2.LogInfo((object)("[box-furniture] announcing host furniture hold id=" + guid.ToString() + ".")); _playerBoxInteraction?.PublishHostHeld((InteractablePackagingBox)(object)val, id); } } } private void BeginFurniturePurchaseBox() { _furniturePurchaseSpawnDepth++; } private unsafe void CompleteFurniturePurchaseBox(EObjectType objectType, Vector3 spawnPosition) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (_furniturePurchaseSpawnDepth <= 0) { throw new InvalidOperationException("Furniture package spawn completed without a matching start."); } _furniturePurchaseSpawnDepth--; if (_furniturePurchaseSpawnDepth != 0) { return; } if (_pendingFurniturePurchaseBoxes.Count == 0) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] furniture purchase completed without a captured package box: ", ((object)(*(EObjectType*)(&objectType))/*cast due to .constrained prefix*/).ToString(), " at ", null, null }; Vector3 val = spawnPosition; obj[3] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[4] = "."; log.LogWarning((object)string.Concat(obj)); return; } InteractablePackagingBox_Shelf val2 = _pendingFurniturePurchaseBoxes.Pop(); if ((Object)(object)val2 == (Object)null || val2.GetBoxedObjectType() != objectType) { ManualLogSource log2 = CoopPlugin.Log; string[] obj2 = new string[5] { "[box-id] furniture purchase completed with an unexpected package box: ", ((object)(*(EObjectType*)(&objectType))/*cast due to .constrained prefix*/).ToString(), " at ", null, null }; Vector3 val = spawnPosition; obj2[3] = ((object)(*(Vector3*)(&val))/*cast due to .constrained prefix*/).ToString(); obj2[4] = "."; log2.LogWarning((object)string.Concat(obj2)); } else { RegisterCreatedBox((InteractablePackagingBox)(object)val2); NotifyStructureChanged(); } } private static void RecordTakeBoxCreated(InteractablePackagingBox_Item __result) { _instance?.RegisterCreatedBox((InteractablePackagingBox)(object)__result); } private void NotifyDestroyedBox(InteractablePackagingBox box) { _playerBoxInteraction?.ReleaseHeldObject(box); _boxNetworkInteraction?.HostNotifyDestroyed(box); } internal static bool IsKnownPackagingBox(InteractablePackagingBox box) { WorldHostBehaviour instance = _instance; if (instance == null) { return false; } return instance._boxNetworkInteraction?.IsKnownBox(box) == true; } private static void BroadcastDispensedSourceBox(InteractablePackagingBox_Item itemBox, int priorCount) { if ((Object)(object)itemBox?.m_ItemCompartment != (Object)null && itemBox.m_ItemCompartment.GetItemCount() != priorCount) { _instance?.BroadcastItemBoxState(itemBox); } } private void InstallCardDisplayPatches() { _harmony.CreateClassProcessor(typeof(CardDisplayPlacePatch)).Patch(); _harmony.CreateClassProcessor(typeof(CardDisplayRemovePatch)).Patch(); } [MessageHandler(typeof(CardDisplayRequestMessage))] private void HandleCardDisplayRequest(MessageContext context, CardDisplayRequestMessage message) { if (!_context.InGame() || !IsJoinPhaseSender(context) || message == null) { RejectWorldIntent(context, message); return; } CardDisplayMessage echo = null; if (ExecuteWorldCommand(context, message, delegate { if (!WorldCardDisplay.TryResolve(message.ShelfKey, message.Compartment, out var compartment)) { return false; } _cardDisplayApplying = true; try { if (!message.Occupied) { CardData val = WorldCardDisplay.FromState(message.Card); if (val == null || !WorldCardDisplay.Matches(compartment, val, message.EncodedGrade)) { CoopPlugin.Log.LogWarning((object)("[card-display] rejected removal shelf=" + message.ShelfKey + " compartment=" + message.Compartment + ": the slot does not hold the named card.")); return false; } WorldCardDisplay.Clear(compartment); } else { CardData val2 = WorldCardDisplay.FromState(message.Card); if (val2 == null) { return false; } if (!WorldCardDisplay.Matches(compartment, val2, message.EncodedGrade)) { if (compartment.m_StoredCardList.Count > 0) { WorldCardDisplay.Clear(compartment); } if (!WorldCardDisplay.Place(compartment, val2)) { return false; } } } } finally { _cardDisplayApplying = false; } echo = new CardDisplayMessage { PredictionId = message.PredictionId, ShelfKey = message.ShelfKey, Compartment = message.Compartment, Occupied = message.Occupied, Card = (message.Occupied ? message.Card : null), EncodedGrade = (message.Occupied ? message.EncodedGrade : (-1)) }; return true; }) && echo != null) { BroadcastWorld((INetMessage)(object)echo); } } private void PublishHostCardDisplay(InteractableCardCompartment compartment, bool occupied) { if (!_shutdown && !_cardDisplayApplying && !((Object)(object)compartment == (Object)null) && _context.InGame() && WorldCardDisplay.TryMakeKey(compartment, out var shelfKey, out var compartmentIndex)) { CardData card = (occupied ? WorldCardDisplay.Read(compartment) : null); int encodedGrade = (occupied ? WorldCardDisplay.EncodedGradeOf(card) : (-1)); BroadcastWorld((INetMessage)(object)new CardDisplayMessage { ShelfKey = shelfKey, Compartment = compartmentIndex, Occupied = occupied, Card = (occupied ? WorldCardDisplay.ToState(card) : null), EncodedGrade = encodedGrade }); } } private void AppendCardDisplayBaseline(Action append) { if (append != null) { AppendCardDisplayShelves(append, (IList)ShelfManager.GetCardShelfList()); AppendCardDisplayShelves(append, (IList)ShelfManager.GetCardItemCombiShelfList()); AppendCardDisplayShelves(append, (IList)ShelfManager.GetTournamentPrizeShelfList()); } } private static void AppendCardDisplayShelves(Action append, IList shelves) where T : CardShelf { int num = 0; while (shelves != null && num < shelves.Count) { T val = shelves[num]; if (!((Object)(object)val == (Object)null)) { int num2 = PlacementInterop.FindKind((InteractableObject)(object)val); if (num2 >= 0 && PlacementApi.TryMakeObjectKey(num2, (InteractableObject)(object)val, out var key)) { List cardCompartmentList = ((CardShelf)val).GetCardCompartmentList(); int num3 = 0; while (cardCompartmentList != null && num3 < cardCompartmentList.Count) { CardData val2 = WorldCardDisplay.Read(cardCompartmentList[num3]); if (val2 != null) { append((INetMessage)(object)new CardDisplayMessage { ShelfKey = key, Compartment = num3, Occupied = true, Card = WorldCardDisplay.ToState(val2), EncodedGrade = WorldCardDisplay.EncodedGradeOf(val2) }); } num3++; } } } num++; } } private void InstallCardInteractionPatches() { WorldCardInteractionPatches.Apply(_harmony); WorldContainerInteraction.ApplyHostPatches(_harmony); WorldMarketInteraction.ApplyPatches(_harmony); } [OnClientDisconnected] private void ReleaseCardClaims(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _cardInteraction?.Containers.HostReleaseConn(connection.Id); _cardInteraction?.ForgetGradingOwnership(connection.Id); } } [MessageHandler(typeof(CardDeltaRequestMessage))] private void HandleCardDelta(MessageContext context, CardDeltaRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, () => _cardInteraction != null && _cardInteraction.HandleCardDelta(context.ConnectionId, message)); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(CardDeltaBatchRequestMessage))] private void HandleCardDeltaBatch(MessageContext context, CardDeltaBatchRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, () => ApplyCardDeltaBatchCommand(context, message)); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(GradedRemoveRequestMessage))] private void HandleGradedRemove(MessageContext context, GradedRemoveRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, () => ApplyGradedRemoveCommand(context, message)); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(ContainerOpMessage))] private void HandleContainerOp(MessageContext context, ContainerOpMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, () => ApplyContainerCommand(context, message)); } else { RejectWorldIntent(context, message); } } private bool ApplyCardDeltaBatchCommand(MessageContext context, CardDeltaBatchRequestMessage message) { if (_cardInteraction != null) { return _cardInteraction.HandleCardDeltaBatch(context.ConnectionId, message); } return false; } private bool ApplyGradedRemoveCommand(MessageContext context, GradedRemoveRequestMessage message) { if (_cardInteraction != null) { return _cardInteraction.HandleGradedRemove(context.ConnectionId, message); } return false; } private bool ApplyContainerCommand(MessageContext context, ContainerOpMessage message) { if (_cardInteraction == null) { return false; } return _cardInteraction.Containers.HostApplyOp(message, context.ConnectionId, delegate(INetMessage response) { SendWorldTo(context.ConnectionId, response); }); } private void OnEnable() { //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Expected O, but got Unknown if (_context == null) { _context = base.RuntimeContext; _instance = this; CEventManager.AddListener((EventDelegate)OnWorldReady); _context.Messages.RegisterAttributedHandlers(this); _boxNetworkInteraction = new BoxNetworkInteraction(host: true, BroadcastWorld, SendWorldTo); _playerBoxInteraction = new PlayerBoxInteraction(_boxNetworkInteraction, BroadcastWorld); _shelfInteraction = new ShelfInteraction(BroadcastWorld, () => _context.InGame()); _warehouseShelfInteraction = new WarehouseShelfInteraction(host: true, BroadcastWorld, SendWorldTo, _boxNetworkInteraction, _playerBoxInteraction); _cardInteraction = new WorldCardInteraction(_context, host: true, _boxNetworkInteraction, _playerBoxInteraction) { BroadcastOverride = BroadcastWorld, RelayOverride = RelayWorldExcept, SendOverride = SendWorldTo }; _cardInteraction.Containers.BroadcastState = BroadcastWorld; _cardInteraction.Containers.SendToClient = SendWorldTo; _cardInteraction.Containers.RelayState = RelayWorldExcept; _cardInteraction.Containers.InGameProvider = _context.InGame; _harmony = new Harmony("dev.meepen.cardshopcoop.world.host"); InstallBoxNetworkInteractionPatches(); _boxNetworkInteraction.RegisterHostSceneBoxes(); InstallPlayerBoxInteractionPatches(); InstallPlayerBoxPresenceHook(); InstallPlayerShelfInteractionPatches(); InstallWarehouseShelfInteractionPatches(); InstallCardInteractionPatches(); InstallCardDisplayPatches(); InstallPlacement(); InstallPlacementHold(); InstallWorkbench(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; UninstallPlayerBoxPresenceHook(); ShutdownPlacementHold(); ShutdownPlacement(); ShutdownWorkbench(); ResetPlayerBoxInteractionState(); ResetPlayerShelfInteractionState(); _boxNetworkInteraction?.Dispose(); _boxNetworkInteraction = null; _warehouseShelfInteraction?.Reset(); _warehouseShelfInteraction = null; _cardInteraction?.Shutdown(); _cardInteraction = null; CEventManager.RemoveListener((EventDelegate)OnWorldReady); _context?.Messages.UnregisterAttributedHandlers(this); _nextBaselineId = 1L; _furniturePurchaseSpawnDepth = 0; _pendingFurniturePurchaseBoxes.Clear(); _transferManifests.Clear(); _appliedPredictions.Clear(); _predictionOwners.Clear(); if (_instance == this) { _instance = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnDestroy() { Shutdown(); } private void Update() { if (!_shutdown) { _cardInteraction?.Tick(); TickPlacementHold(); } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { WorldMarketInteraction.InvalidateContentCache(); _cardInteraction?.FlushWorldReady(); } internal static void ResetCardState() { _instance?._cardInteraction?.Reset(); } internal static bool TryGetHeldBoxId(InteractablePackagingBox box, out Guid boxNetworkId) { boxNetworkId = Guid.Empty; if ((Object)(object)box != (Object)null && _instance?._boxNetworkInteraction != null) { return _instance._boxNetworkInteraction.TryGetId(box, out boxNetworkId); } return false; } internal static bool PushCreatedBoxId(Guid boxNetworkId) { WorldHostBehaviour instance = _instance; if (instance == null) { return false; } return instance._boxNetworkInteraction?.PushHostCreatedId(boxNetworkId) == true; } internal static bool StageCreatedBoxIds(IReadOnlyList boxNetworkIds) { WorldHostBehaviour instance = _instance; if (instance == null) { return false; } return instance._boxNetworkInteraction?.StageHostDeliveryIds(boxNetworkIds) == true; } internal static void CancelCreatedBoxId(Guid boxNetworkId) { _instance?._boxNetworkInteraction?.CancelHostCreatedId(boxNetworkId); } internal static void CancelStagedDeliveryIds() { _instance?._boxNetworkInteraction?.CancelStagedHostDeliveryIds(); } internal static BoxNetworkInteraction.DeliveryQueueState CaptureDeliveryQueueState() { return _instance?._boxNetworkInteraction?.CaptureDeliveryQueueState(); } internal static void RestoreDeliveryQueueState(BoxNetworkInteraction.DeliveryQueueState state) { _instance?._boxNetworkInteraction?.RestoreDeliveryQueueState(state); } internal static bool IsHeldBoxOpen(InteractablePackagingBox box) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { return BoxNetworkInteraction.IsBoxOpen(val); } return false; } internal static void NotifyWorkerReleasedBox(Guid boxNetworkId) { _instance?._boxNetworkInteraction?.HostRefreshReleasedWorkerBox(boxNetworkId); } internal static void CaptureTransferManifest(int connectionId) { WorldHostBehaviour instance = _instance; if ((Object)(object)instance == (Object)null) { CoopPlugin.Log.LogWarning((object)"World transfer manifest could not be captured: the world host is not active."); } else if (connectionId > 0) { WorldTransferManifest worldTransferManifest = new WorldTransferManifest(); instance._boxNetworkInteraction?.CaptureTransferManifest(worldTransferManifest); instance._placementPopulation.CaptureTransferManifest(worldTransferManifest); instance._transferManifests[connectionId] = worldTransferManifest; CoopPlugin.Log.LogInfo((object)("[transfer] captured identity manifest for conn " + connectionId + " (boxes=" + worldTransferManifest.BoxSlotCount + ", placements=" + worldTransferManifest.PlacementSlotCount + ").")); } } private WorldTransferManifest TransferManifestFor(int connectionId) { if (connectionId <= 0 || !_transferManifests.TryGetValue(connectionId, out var value)) { return null; } return value; } [OnClientDisconnected] private void ForgetTransferManifest(PeerConnection connection, DisconnectInfo _) { if (connection != null) { _transferManifests.Remove(connection.Id); } } internal bool ExecuteWorldCommand(MessageContext context, WorldMessage command, Func apply) { if (context?.Connection == null || !WorldMessageMetadata.IsValidIntent(command)) { CoopPlugin.Log.LogWarning((object)("Rejected world intent with an invalid identity: " + DescribeIntent(command) + ".")); if (_context != null && context?.Connection != null) { PredictionHost.Reject(_context, context.ConnectionId, command?.PredictionId ?? Guid.Empty); } return false; } int connectionId = context.ConnectionId; if (_predictionOwners.TryGetValue(command.PredictionId, out var value)) { if (value != connectionId) { CoopPlugin.Log.LogWarning((object)("Rejected world intent naming another connection's prediction: " + DescribeIntent(command) + " owner=" + value + " sender=" + connectionId + ".")); PredictionHost.Reject(_context, connectionId, command.PredictionId); return false; } CoopPlugin.Log.LogInfo((object)("Dropped duplicate world intent from conn " + connectionId + ": " + DescribeIntent(command) + ".")); return false; } _predictionOwners[command.PredictionId] = connectionId; if (!_appliedPredictions.TryGetValue(connectionId, out var value2)) { value2 = new AppliedIntentLedger(); _appliedPredictions[connectionId] = value2; } value2.Add(command.PredictionId, delegate(Guid evicted) { if (_predictionOwners.TryGetValue(evicted, out var value3) && value3 == connectionId) { _predictionOwners.Remove(evicted); } }); return PredictionHost.Resolve(_context, connectionId, command.PredictionId, apply); } [OnClientDisconnected] private void ForgetAppliedPredictions(PeerConnection connection, DisconnectInfo _) { if (connection == null || !_appliedPredictions.TryGetValue(connection.Id, out var value)) { return; } foreach (Guid id in value.Ids) { if (_predictionOwners.TryGetValue(id, out var value2) && value2 == connection.Id) { _predictionOwners.Remove(id); } } _appliedPredictions.Remove(connection.Id); } internal void RejectWorldIntent(MessageContext context, WorldMessage command) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (_context != null && context?.Connection != null && !(command?.PredictionId == Guid.Empty)) { CoopPlugin.Log.LogWarning((object)("Rejected world intent before host execution: " + DescribeIntent(command) + " inGame=" + _context.InGame() + " connState=" + ((object)context.Connection.State/*cast due to .constrained prefix*/).ToString() + ".")); PredictionHost.Reject(_context, context.ConnectionId, command.PredictionId); } } private static string DescribeIntent(WorldMessage command) { if (command != null) { return command.GetType().Name + " op=" + command.OperationKind.ToString() + " entity=" + (command.StableEntityId ?? ""); } return ""; } [OnFullyJoined] private void SendWarehouseBaseline(PeerConnection connection) { if (connection != null && _context.InGame()) { SendWorldBaseline(connection.Id); } } internal bool IsJoinPhaseSender(MessageContext context) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 if (context?.Connection != null) { if ((int)context.Connection.State != 2) { return (int)context.Connection.State == 3; } return true; } return false; } private void SendWorldBaseline(int connectionId) { if (connectionId > 0) { long baselineId = _nextBaselineId++; if (_nextBaselineId <= 0) { _nextBaselineId = 1L; } SendWorldTo(connectionId, (INetMessage)(object)new WorldBaselineStartMessage { BaselineId = baselineId }); WorldTransferManifest manifest = TransferManifestFor(connectionId); _boxNetworkInteraction?.AppendBaselineMessages(manifest, delegate(INetMessage message) { SendWorldTo(connectionId, message); }); AppendPlayerBoxHoldBaseline(delegate(INetMessage message) { SendWorldTo(connectionId, message); }); _warehouseShelfInteraction?.AppendBaselineMessages(delegate(INetMessage message) { SendWorldTo(connectionId, message); }); SendWorldTo(connectionId, (INetMessage)(object)new BoxBaselineCompleteMessage()); _cardInteraction?.AppendBaselineMessages(delegate(INetMessage message) { SendWorldTo(connectionId, message); }); AppendWorkbenchBaseline(connectionId); AppendCardDisplayBaseline(delegate(INetMessage message) { SendWorldTo(connectionId, message); }); SendWorldTo(connectionId, (INetMessage)(object)new WorldBaselineCompleteMessage { BaselineId = baselineId }); } } private void BroadcastWorld(INetMessage message) { if (!(message is WorldMessage worldMessage)) { throw new InvalidOperationException("World host can only broadcast WorldMessage values."); } PrepareState(worldMessage); _context.Broadcast((INetMessage)(object)worldMessage); } private void SendWorldTo(int connectionId, INetMessage message) { if (!(message is WorldMessage worldMessage)) { throw new InvalidOperationException("World host can only send WorldMessage values."); } PrepareState(worldMessage); _context.Send(connectionId, (INetMessage)(object)worldMessage); } private void RelayWorldExcept(int excludedConnectionId, INetMessage message) { if (!(message is WorldMessage worldMessage)) { throw new InvalidOperationException("World host can only relay WorldMessage values."); } PrepareState(worldMessage); _context.Relay(excludedConnectionId, (INetMessage)(object)worldMessage); } private void PrepareState(WorldMessage message) { if (message.StableEntityId == null) { message.StableEntityId = WorldMessageMetadata.StableEntityId(message); } } private static bool IsSanePose(Vector3 position, Quaternion rotation) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (IsFiniteCoordinate(position.x) && IsFiniteCoordinate(position.y) && IsFiniteCoordinate(position.z) && IsFinite(rotation.x) && IsFinite(rotation.y) && IsFinite(rotation.z)) { return IsFinite(rotation.w); } return false; } private static bool IsFiniteCoordinate(float value) { if (IsFinite(value)) { return Mathf.Abs(value) <= 1000f; } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool IsWithinPlacementReach(Vector3 player, Vector3 target) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: 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) Vector3 val = player - target; return ((Vector3)(ref val)).sqrMagnitude <= 36f; } internal void InstallPlacement() { PlacementInterop.ProbeBetaSurface(); SceneManager.sceneLoaded += OnPlacementSceneLoaded; InstallPlacementPatches(); RefreshKnown(); } internal void ShutdownPlacement() { SceneManager.sceneLoaded -= OnPlacementSceneLoaded; _placementPopulation.Reset(); _known.Clear(); _knownKeyById.Clear(); _fullyJoined.Clear(); } [OnFullyJoined] private void SendPlacementJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State)) { _fullyJoined.Add(connection.Id); SendBaseline(connection.Id); } } [MessageHandler(typeof(PlacementBaselineRequestMessage))] private void HandlePlacementBaselineRequest(MessageContext context, PlacementBaselineRequestMessage message) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && context?.Connection != null && IsJoinPhase(context.Connection.State)) { CoopPlugin.Log.LogInfo((object)("[placement] re-sending baseline to connection " + context.ConnectionId + " on request.")); SendBaseline(context.Connection.Id); } } [OnClientDisconnected] private void ForgetPlacementConnection(PeerConnection connection, DisconnectInfo _) { if (connection != null) { _fullyJoined.Remove(connection.Id); } } [MessageHandler(typeof(PlacementMoveIntentMessage))] private void HandleMoveIntent(MessageContext messageContext, PlacementMoveIntentMessage message) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) if (!IsPeerMessage(messageContext) || message == null) { return; } if (message.Move == null) { Reject(messageContext.Connection.Id, message.PredictionId, "placement intent has no entity"); return; } if (!IsSanePose(message.Move.Pos, message.Move.Rot) || (message.Move.IsBoxed && !IsSanePose(message.Move.BoxedPos, message.Move.BoxedRot))) { Reject(messageContext.Connection.Id, message.PredictionId, "placement pose is not finite/bounded"); return; } Component obj = PlacementApi.ResolveObjectByKey(message.Move.Key); InteractablePlayTable val = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if ((Object)(object)val != (Object)null && !PlayTableHostBehaviour.IsPlacementMoveAllowed((InteractableObject)(object)val)) { Reject(messageContext.Connection.Id, message.PredictionId, "play table is occupied or reserved"); return; } Component obj2 = PlacementApi.ResolveObjectByKey(message.Move.Key); InteractableObject val2 = (InteractableObject)(object)((obj2 is InteractableObject) ? obj2 : null); if ((Object)(object)val2 == (Object)null || (message.Move.Type != int.MinValue && PlacementInterop.TypeIdOf((Component)(object)val2) != message.Move.Type)) { Reject(messageContext.Connection.Id, message.PredictionId, "placement identity or type is stale"); return; } if (!_context.PeerPresence.TryGet(messageContext.Connection.Id, out var presence) || presence.Age > TimeSpan.FromSeconds(2.0) || !IsWithinPlacementReach(presence.Position, ((Component)val2).transform.position)) { Reject(messageContext.Connection.Id, message.PredictionId, "sender is not within reach of the placement"); return; } _applyingIntent++; try { if (!PlacementMoveState.Apply(message.Move, settle: true)) { Reject(messageContext.Connection.Id, message.PredictionId, "placement pose could not be applied"); return; } } finally { _applyingIntent--; } NotifyObjectChanged(val2, message.PredictionId, force: true); } internal void NotifyStructureChanged() { Dictionary dictionary = BuildCurrentEntities(); List list = new List(); foreach (KeyValuePair item in _known) { if (!dictionary.ContainsKey(item.Key)) { list.Add(item.Value); } } for (int i = 0; i < list.Count; i++) { PublishDelta(3, RemovalEntry(list[i]), Guid.Empty); _known.Remove(list[i].Key); } foreach (KeyValuePair item2 in dictionary) { if (!_known.TryGetValue(item2.Key, out var value)) { PublishDelta(1, item2.Value.ToEntry(), Guid.Empty); } else if (!value.SameValue(item2.Value)) { PublishDelta(2, item2.Value.ToEntry(), Guid.Empty); } _known[item2.Key] = item2.Value; } RebuildKeyIndex(); PlacementInterop.NotifyStructureChanged(-1); } internal void NotifyObjectChanged(InteractableObject obj) { NotifyObjectChanged(obj, Guid.Empty, force: false); } internal void SetNextMutationPrediction(Guid predictionId) { _nextMutationPrediction = predictionId; } private void NotifyObjectChanged(InteractableObject obj, Guid predictionId, bool force) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) if (_applyingIntent != 0 || (Object)(object)obj == (Object)null) { return; } int num = PlacementInterop.FindKind(obj); if (num < 0) { return; } if (!PlacementApi.TryMakeObjectKey(num, obj, out var key)) { CoopPlugin.Log.LogInfo((object)("[placement] changed without a key (" + ((Object)obj).name + ").")); NotifyStructureChanged(); return; } PlacementEntitySnapshot placementEntitySnapshot = PlacementEntityState.Capture(obj, key); if (!_known.TryGetValue(key, out var value)) { PublishDelta(1, placementEntitySnapshot.ToEntry(), predictionId); } else if (value.Type != placementEntitySnapshot.Type || value.IsBoxed != placementEntitySnapshot.IsBoxed || value.BoxedPos != placementEntitySnapshot.BoxedPos || value.BoxedRot != placementEntitySnapshot.BoxedRot) { PublishDelta(2, placementEntitySnapshot.ToEntry(), predictionId); } else if (force || value.Pos != placementEntitySnapshot.Pos || value.Rot != placementEntitySnapshot.Rot) { PublishDelta(4, placementEntitySnapshot.ToEntry(), predictionId); } else { CoopPlugin.Log.LogInfo((object)("[placement] no change key=" + key + " (" + ((Object)obj).name + ").")); } _known[key] = placementEntitySnapshot; _knownKeyById[PlacementApi.ObjectIdFromObjectKey(key)] = key; } private void NotifyObjectRemoved(InteractableObject obj) { if ((Object)(object)obj == (Object)null || !PlacementIdentity.TryGet(obj, out var id)) { return; } int num = PlacementInterop.FindKind(obj); int value = ((num >= 0) ? ((num << 24) | id) : 0); if (num < 0) { _knownKeyById.TryGetValue(id, out value); } if (value == 0) { PlacementIdentity.Forget(obj); return; } _knownKeyById.Remove(id); if (_known.TryGetValue(value, out var value2)) { PublishDelta(3, RemovalEntry(value2), Guid.Empty); _known.Remove(value); } else { PublishDelta(3, new PlacementMoveEntry { Key = value, Type = PlacementInterop.TypeIdOf((Component)(object)obj) }, Guid.Empty); } } private PlacementBaselineMessage BuildBaseline(WorldTransferManifest manifest) { ShelfManager val = PlacementInterop.FindShelfManager(); PlacementPopulationMessage placementPopulationMessage = _placementPopulation.BuildMessage(manifest); if ((Object)(object)val == (Object)null || placementPopulationMessage == null) { return null; } RefreshKnown(); return new PlacementBaselineMessage { Population = placementPopulationMessage, Moves = PlacementMoveState.Build(val) }; } private void SendBaseline(int connectionId) { if (connectionId > 0 && _context.InGame()) { PlacementBaselineMessage placementBaselineMessage = BuildBaseline(TransferManifestFor(connectionId)); if (placementBaselineMessage != null) { _context.Send(connectionId, (INetMessage)(object)placementBaselineMessage); } } } private void SendBaselinesToJoined() { foreach (int item in new List(_fullyJoined)) { SendBaseline(item); } } private void PublishDelta(byte operation, PlacementMoveEntry entity, Guid predictionId) { if (_shutdown || !_context.InGame() || entity == null) { CoopPlugin.Log.LogInfo((object)("[placement] skip delta op=" + operation + " key=" + (entity?.Key ?? (-1)) + " shutdown=" + _shutdown + " inGame=" + _context.InGame() + ".")); } else { CoopPlugin.Log.LogInfo((object)("[placement] publish delta op=" + operation + " key=" + entity.Key + ".")); _context.Broadcast((INetMessage)(object)new PlacementDeltaMessage { PredictionId = predictionId, Operation = operation, Entity = entity }); } } private static PlacementMoveEntry RemovalEntry(PlacementEntitySnapshot entity) { return new PlacementMoveEntry { Key = entity.Key, Type = entity.Type }; } private void Reject(int connectionId, Guid predictionId, string reason) { CoopPlugin.Log.LogWarning((object)("placement intent rejected for connection=" + connectionId + ": " + reason)); if (predictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, connectionId, predictionId); } } private bool IsPeerMessage(MessageContext messageContext) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && _context.InGame() && messageContext?.Connection != null) { return IsJoinPhase(messageContext.Connection.State); } return false; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private Dictionary BuildCurrentEntities() { Dictionary dictionary = new Dictionary(); ShelfManager shelfManager = PlacementInterop.FindShelfManager(); for (int i = 0; i < 16; i++) { IList list = PlacementInterop.GetList(shelfManager, i); int num = 0; while (list != null && num < list.Count) { object? obj = list[num]; InteractableObject val = (InteractableObject)((obj is InteractableObject) ? obj : null); if (val != null && PlacementApi.TryMakeObjectKey(i, val, out var key)) { dictionary[key] = PlacementEntityState.Capture(val, key); } num++; } } return dictionary; } private void RefreshKnown() { _known.Clear(); foreach (KeyValuePair item in BuildCurrentEntities()) { _known.Add(item.Key, item.Value); } RebuildKeyIndex(); } private void RebuildKeyIndex() { _knownKeyById.Clear(); foreach (int key in _known.Keys) { ushort num = PlacementApi.ObjectIdFromObjectKey(key); if (num != 0) { _knownKeyById[num] = key; } } } private void OnPlacementSceneLoaded(Scene _, LoadSceneMode __) { _placementHold?.Reset(); _placementPopulation.Reset(); _transferManifests.Clear(); _known.Clear(); PlacementIdentity.Reset(); RefreshKnown(); SendBaselinesToJoined(); } private void InstallPlacementPatches() { Patch(typeof(InteractableObject), "OnDestroyed", null, "InteractableObjectDestroyedPostfix"); Patch(typeof(InteractableObject), "BoxUpObject", null, "ObjectMutationPostfix"); Patch(typeof(InteractableObject), "PlaceMovedObject", null, "ObjectMutationPostfix"); PatchInit(typeof(ShelfManager), "InitInteractableObject", typeof(InteractableObject)); PatchInit(typeof(ShelfManager), "InitDecoObject", typeof(InteractableObject)); PatchInit(typeof(ShelfManager), "InitShelf", typeof(Shelf)); PatchInit(typeof(ShelfManager), "InitWarehouseShelf", typeof(WarehouseShelf)); PatchInit(typeof(ShelfManager), "InitCardShelf", typeof(CardShelf)); PatchInit(typeof(ShelfManager), "InitCardItemCombiShelf", typeof(CardItemCombiShelf)); PatchInit(typeof(ShelfManager), "InitTournamentPrizeShelf", typeof(TournamentPrizeShelf)); PatchInit(typeof(ShelfManager), "InitPlayTable", typeof(InteractablePlayTable)); PatchInit(typeof(ShelfManager), "InitAutoCleanser", typeof(InteractableAutoCleanser)); PatchInit(typeof(ShelfManager), "InitAutoPackOpener", typeof(InteractableAutoPackOpener)); PatchInit(typeof(ShelfManager), "InitWorkbench", typeof(InteractableWorkbench)); PatchInit(typeof(ShelfManager), "InitBulkDonationBox", typeof(InteractableBulkDonationBox)); PatchInit(typeof(ShelfManager), "InitCardStorageShelf", typeof(InteractableCardStorageShelf)); PatchInit(typeof(ShelfManager), "InitTrashBin", typeof(InteractableTrashBin)); PatchInit(typeof(ShelfManager), "InitEmptyBoxStorage", typeof(InteractableEmptyBoxStorage)); PatchInit(typeof(ShelfManager), "InitCashierCounter", typeof(InteractableCashierCounter)); } private void PatchInit(Type target, string method, Type argument) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(target, method, new Type[1] { argument }, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("placement host init patch target missing: " + target.Name + "." + method)); } else { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(WorldHostBehaviour), "ShelfInitPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private void Patch(Type target, string method, string prefix, string postfix) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = AccessTools.Method(target, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("placement host patch target missing: " + target.Name + "." + method)); } else { _harmony.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(WorldHostBehaviour), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(WorldHostBehaviour), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } public static void ObjectMutationPostfix(InteractableObject __instance) { WorldHostBehaviour instance = _instance; if (!((Object)(object)instance == (Object)null) && (!((Object)(object)__instance != (Object)null) || !__instance.GetIsMovingObject())) { instance._placementHold?.LocalEnded(__instance); Guid nextMutationPrediction = instance._nextMutationPrediction; instance._nextMutationPrediction = Guid.Empty; instance.NotifyObjectChanged(__instance, nextMutationPrediction, force: false); } } public static void ShelfInitPostfix() { _instance?.NotifyStructureChanged(); } public static void InteractableObjectDestroyedPostfix(InteractableObject __instance) { WorldHostBehaviour instance = _instance; if (!((Object)(object)__instance == (Object)null) && !((Object)(object)instance == (Object)null)) { instance._placementHold?.LocalEnded(__instance); instance.NotifyObjectRemoved(__instance); PlacementIdentity.Forget(__instance); } } internal void InstallPlacementHold() { _placementHold = new PlacementHoldInteraction(host: true, _context.Broadcast, _context.Send); _harmony.CreateClassProcessor(typeof(PlacementHoldStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlacementHoldRotatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlacementHoldFlipPatch)).Patch(); } internal void ShutdownPlacementHold() { _placementHold?.Reset(); _placementHold = null; } internal void TickPlacementHold() { _placementHold?.Tick(); } [MessageHandler(typeof(PlacementHoldBeginRequestMessage))] private void HandlePlacementHoldBegin(MessageContext context, PlacementHoldBeginRequestMessage message) { if (message != null && _placementHold != null && _context.InGame() && IsJoinPhaseSender(context) && _placementHold.HostGrant(message.HoldKey, context.ConnectionId, out var granted)) { _placementHold.ApplyBegin(granted); _context.Broadcast((INetMessage)(object)granted); } } [MessageHandler(typeof(PlacementHoldEndRequestMessage))] private void HandlePlacementHoldEnd(MessageContext context, PlacementHoldEndRequestMessage message) { if (message != null && _placementHold != null && _context.InGame() && IsJoinPhaseSender(context) && _placementHold.HostRelease(message.HoldKey, context.ConnectionId)) { _placementHold.ApplyEnd(message.HoldKey); _context.Broadcast((INetMessage)(object)new PlacementHoldEndMessage { HoldKey = message.HoldKey }); } } [OnClientDisconnected] private void ReleasePlacementHolds(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _placementHold?.ReleaseMover(connection.Id, _releasedHolds); } } [MessageHandler(typeof(PlacementHoldRotationRequestMessage))] private void HandlePlacementHoldRotation(MessageContext context, PlacementHoldRotationRequestMessage message) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (message != null && _placementHold != null && _context.InGame() && IsJoinPhaseSender(context) && _placementHold.IsHeldBy(message.HoldKey, context.ConnectionId)) { PlacementHoldRotationMessage placementHoldRotationMessage = new PlacementHoldRotationMessage { HoldKey = message.HoldKey, Rotation = message.Rotation }; _placementHold.ApplyRotation(placementHoldRotationMessage); _context.Broadcast((INetMessage)(object)placementHoldRotationMessage); } } private void InstallPlayerBoxInteractionPatches() { _harmony.CreateClassProcessor(typeof(PlayerBoxPickupPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlayerBoxThrowPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlayerBoxDropPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlayerBoxPlacementPatch)).Patch(); } private void InstallPlayerBoxPresenceHook() { PresenceApi.RemoteAvatarsChanged += OnRemoteAvatarsChanged; } private void UninstallPlayerBoxPresenceHook() { PresenceApi.RemoteAvatarsChanged -= OnRemoteAvatarsChanged; } internal void AppendPlayerBoxHoldBaseline(Action append) { _playerBoxInteraction?.AppendBaselineHolds(append); } private void OnRemoteAvatarsChanged() { _playerBoxInteraction?.ReattachRemoteHolds(); } internal void ResetPlayerBoxInteractionState() { _playerBoxInteraction?.Reset(); } [OnClientDisconnected] private void ReleasePlayerBoxHolds(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _playerBoxInteraction?.ReleaseRemoteHoldsForConnection(connection.Id); } } [MessageHandler(typeof(PlayerBoxPickupRequestMessage))] private void HandlePlayerBoxPickup(MessageContext context, PlayerBoxPickupRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, delegate { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) message.HolderConnectionId = context.ConnectionId; if (!ApplyPlayerBoxAction(message)) { RefreshRejectedBoxAction(context, message.BoxNetworkId); return false; } BroadcastWorld((INetMessage)(object)new PlayerBoxPickupMessage { PredictionId = message.PredictionId, BoxNetworkId = message.BoxNetworkId, HeldPosition = message.HeldPosition, HeldRotation = message.HeldRotation, HolderConnectionId = message.HolderConnectionId }); return true; }); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(PlayerBoxPlacementRequestMessage))] private void HandlePlayerBoxPlacement(MessageContext context, PlayerBoxPlacementRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, delegate { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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 (!ApplyPlayerBoxAction(message)) { RefreshRejectedBoxAction(context, message.BoxNetworkId); return false; } BroadcastWorld((INetMessage)(object)new PlayerBoxPlacementMessage { PredictionId = message.PredictionId, BoxNetworkId = message.BoxNetworkId, Position = message.Position, Rotation = message.Rotation }); return true; }); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(PlayerBoxThrowRequestMessage))] private void HandlePlayerBoxThrow(MessageContext context, PlayerBoxThrowRequestMessage message) { if (_context.InGame() && IsJoinPhaseSender(context)) { ExecuteWorldCommand(context, message, delegate { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (!ApplyPlayerBoxAction(message)) { RefreshRejectedBoxAction(context, message.BoxNetworkId); return false; } BroadcastWorld((INetMessage)(object)new PlayerBoxThrowMessage { PredictionId = message.PredictionId, BoxNetworkId = message.BoxNetworkId, Position = message.Position, Rotation = message.Rotation, Velocity = message.Velocity, AngularVelocity = message.AngularVelocity }); return true; }); } else { RejectWorldIntent(context, message); } } private PlayerBoxInteraction.LocalAction CapturePlayerBoxAction(InteractablePackagingBox box, bool isPlayer, bool alignBody) { if (_playerBoxInteraction != null) { return _playerBoxInteraction.CaptureLocalAction(box, isPlayer, alignBody); } return default(PlayerBoxInteraction.LocalAction); } private void PublishPlayerBoxPickup(InteractablePackagingBox box, PlayerBoxInteraction.LocalAction action) { _playerBoxInteraction?.PublishPickup(box, action); } private void PublishPlayerBoxPlacement(InteractablePackagingBox box, PlayerBoxInteraction.LocalAction action) { _playerBoxInteraction?.PublishPlacement(box, action); } private void PublishPlayerBoxThrow(InteractablePackagingBox box, PlayerBoxInteraction.LocalAction action) { _playerBoxInteraction?.PublishThrow(box, action); } private bool ApplyPlayerBoxAction(PlayerBoxInteractionMessage message) { if (_playerBoxInteraction != null) { return _playerBoxInteraction.ApplyIncoming(message); } return false; } private void RefreshRejectedBoxAction(MessageContext context, Guid boxNetworkId) { //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (context?.Connection == null || boxNetworkId == Guid.Empty || _boxNetworkInteraction == null || !_boxNetworkInteraction.TryGetBox(boxNetworkId, out var box) || (Object)(object)box == (Object)null || PlayerBoxInteraction.IsBeingPlaced(box)) { return; } if (_boxNetworkInteraction.IsStored(boxNetworkId)) { InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)(object)((box is InteractablePackagingBox_Item) ? box : null); if (val != null) { ShelfCompartment boxStoredCompartment = val.GetBoxStoredCompartment(); if (boxStoredCompartment != null && (Object)(object)boxStoredCompartment.GetWarehouseShelf() != (Object)null) { SendWorldTo(context.Connection.Id, (INetMessage)(object)new WarehouseDeltaMessage { IsStore = true, ShelfIndex = boxStoredCompartment.GetWarehouseIndex(), CompartmentIndex = boxStoredCompartment.GetIndex(), BoxNetworkId = boxNetworkId, ItemType = (EItemType)(((Object)(object)val.m_ItemCompartment == (Object)null) ? (-1) : ((int)val.m_ItemCompartment.GetItemType())), Amount = ((!((Object)(object)val.m_ItemCompartment == (Object)null)) ? val.m_ItemCompartment.GetItemCount() : 0), IsBig = val.m_IsBigBox }); return; } } } int holderConnectionId; if (_playerBoxInteraction != null && _playerBoxInteraction.LocalHeldBox == box) { SendWorldTo(context.Connection.Id, (INetMessage)(object)new PlayerBoxPickupMessage { BoxNetworkId = boxNetworkId, HeldPosition = ((Component)box).transform.position, HeldRotation = ((Component)box).transform.rotation, HolderConnectionId = 0 }); } else if (_playerBoxInteraction != null && _playerBoxInteraction.TryGetRemoteHolder(boxNetworkId, out holderConnectionId)) { SendWorldTo(context.Connection.Id, (INetMessage)(object)new PlayerBoxPickupMessage { BoxNetworkId = boxNetworkId, HeldPosition = ((Component)box).transform.position, HeldRotation = ((Component)box).transform.rotation, HolderConnectionId = holderConnectionId }); } } private void InstallPlayerShelfInteractionPatches() { _harmony.CreateClassProcessor(typeof(AddItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(RemoveItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(TakeItemToHandPatch)).Patch(); _harmony.CreateClassProcessor(typeof(SetCompartmentItemTypePatch)).Patch(); _harmony.CreateClassProcessor(typeof(PutItemOnShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(TakeItemFromShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(BoxAddToShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(BoxRemoveFromShelfScopePatch)).Patch(); _harmony.CreateClassProcessor(typeof(RemoveLabelPatch)).Patch(); } [MessageHandler(typeof(ShelfItemAddRequestMessage))] private void HandleShelfItemAdd(MessageContext context, ShelfItemAddRequestMessage message) { if (_context == null || !_context.InGame() || !IsJoinPhaseSender(context)) { RejectWorldIntent(context, message); return; } ExecuteWorldCommand(context, message, delegate { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (!ApplyShelfAction(message)) { RefreshRejectedShelfAction(context, message); return false; } BroadcastWorld((INetMessage)(object)new ShelfItemAddMessage { PredictionId = message.PredictionId, ShelfKey = message.ShelfKey, Compartment = message.Compartment, ItemType = message.ItemType, ItemCount = message.ItemCount }); return true; }); } [MessageHandler(typeof(ShelfItemRemoveRequestMessage))] private void HandleShelfItemRemove(MessageContext context, ShelfItemRemoveRequestMessage message) { if (_context == null || !_context.InGame() || !IsJoinPhaseSender(context)) { RejectWorldIntent(context, message); return; } ExecuteWorldCommand(context, message, delegate { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (!ApplyShelfAction(message)) { RefreshRejectedShelfAction(context, message); return false; } BroadcastWorld((INetMessage)(object)new ShelfItemRemoveMessage { PredictionId = message.PredictionId, ShelfKey = message.ShelfKey, Compartment = message.Compartment, ItemType = message.ItemType, ItemCount = message.ItemCount }); return true; }); } internal void ResetPlayerShelfInteractionState() { _shelfInteraction?.Reset(); } private ShelfInteraction.LocalMutation CaptureShelfMutation(ShelfCompartment compartment, bool add, EItemType itemType, Item item) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (_shelfInteraction != null) { return _shelfInteraction.CaptureLocalMutation(compartment, add, itemType, item); } return default(ShelfInteraction.LocalMutation); } private void PublishShelfAdd(ShelfCompartment compartment, ShelfInteraction.LocalMutation mutation) { _shelfInteraction?.PublishAdd(compartment, mutation); } private void PublishShelfRemove(ShelfCompartment compartment, ShelfInteraction.LocalMutation mutation) { _shelfInteraction?.PublishRemove(compartment, mutation); } private InteractablePackagingBox_Item FindItemBox(ShelfCompartment compartment) { return _boxNetworkInteraction?.FindItemBox(compartment); } private bool IsBoxHeld(InteractablePackagingBox box) { if (_boxNetworkInteraction != null) { return _boxNetworkInteraction.IsBeingHeld(box); } return false; } private unsafe void PublishLabelChange(ShelfCompartment compartment, EItemType previousType, bool allowWarehouse = false) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 if (_shelfInteraction != null && !((Object)(object)compartment == (Object)null) && _context.InGame() && compartment.GetItemCount() <= 0 && (int)compartment.GetItemType() == -1 && (int)previousType != -1) { CoopPlugin.Log.LogInfo((object)("[shelf] label removed on " + ((Object)compartment).name + " (" + ((object)(*(EItemType*)(&previousType))/*cast due to .constrained prefix*/).ToString() + ").")); _shelfInteraction.PublishRemove(compartment, _shelfInteraction.CaptureLabelChange(compartment, allowWarehouse)); } } private bool ApplyShelfAction(ShelfInteractionMessage message) { if (_shelfInteraction != null) { return _shelfInteraction.ApplyIncoming(message); } return false; } private void RefreshRejectedShelfAction(MessageContext context, ShelfInteractionMessage message) { if (context?.Connection != null && _shelfInteraction != null) { ShelfItemAddMessage shelfItemAddMessage = _shelfInteraction.BuildRefresh(message); if (shelfItemAddMessage != null) { SendWorldTo(context.Connection.Id, (INetMessage)(object)shelfItemAddMessage); } } } private void EnterPlayerShelfMutation() { _shelfInteraction?.EnterPlayerMutation(); } private void ExitPlayerShelfMutation() { _shelfInteraction?.ExitPlayerMutation(); } private void InstallWarehouseShelfInteractionPatches() { if (WarehouseShelfInteraction.Available()) { if (WarehouseShelfInteraction.UsesRecords) { _harmony.CreateClassProcessor(typeof(BoxRecordStoreCompletePatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoredBoxRecordAddedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoredBoxRecordPoppedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoredBoxRecordArrangePatch)).Patch(); } else { _harmony.CreateClassProcessor(typeof(BoxAddedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BoxRemovedPatch)).Patch(); } } } [MessageHandler(typeof(WarehouseStoreMessage))] private void HandleWarehouseStore(MessageContext context, WarehouseStoreMessage message) { if (!_context.InGame() || !IsJoinPhaseSender(context)) { RejectWorldIntent(context, message); return; } ExecuteWorldCommand(context, message, delegate { if (Warehouse == null) { return false; } if (!Warehouse.HostApplyStore(message, context.ConnectionId, delegate(INetMessage response) { SendWorldTo(context.ConnectionId, response); })) { return false; } _playerBoxInteraction?.ReleaseRemoteHoldForBox(message.BoxNetworkId); return true; }); } [MessageHandler(typeof(WarehouseTakeMessage))] private void HandleWarehouseTake(MessageContext context, WarehouseTakeMessage message) { if (!_context.InGame() || !IsJoinPhaseSender(context)) { RejectWorldIntent(context, message); return; } ExecuteWorldCommand(context, message, () => Warehouse != null && Warehouse.HostApplyTake(message, context.ConnectionId, delegate(INetMessage response) { SendWorldTo(context.ConnectionId, response); })); } private void NotifyStoredBoxRecordAdded(ShelfCompartment compartment) { Warehouse?.OnStoredBoxRecordAdded(compartment); } private void NotifyStoredBoxRecordPopped(ShelfCompartment compartment, bool didPop, WarehouseBoxState removed) { Warehouse?.OnStoredBoxRecordPopped(compartment, didPop, removed); } private void NotifyWarehouseBoxAdded(ShelfCompartment compartment) { Warehouse?.OnBoxAdded(compartment); } private void NotifyWarehouseBoxRemoved(ShelfCompartment compartment, InteractablePackagingBox_Item box) { Warehouse?.OnBoxRemoved(compartment, box); } private void InstallWorkbench() { _workbenchInteraction = new WorldWorkbenchInteraction(host: true, BroadcastWorld, SendWorldTo, () => _context.InGame()); _harmony.CreateClassProcessor(typeof(HostWorkbenchAddItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostWorkbenchRemoveItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostWorkbenchTakeItemToHandPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostWorkbenchDispenseFromBoxPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostWorkbenchRemoveFromShelfPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostWorkbenchBundleStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(HostWorkbenchTaskCompletedPatch)).Patch(); } private void ShutdownWorkbench() { _workbenchInteraction?.Reset(); _workbenchInteraction = null; } private void AppendWorkbenchBaseline(int connectionId) { _workbenchInteraction?.AppendBaselineMessages(delegate(INetMessage message) { SendWorldTo(connectionId, message); }); } private void PublishWorkbenchStorage(InteractableWorkbench bench) { if (!WorldWorkbenchInteraction.ApplyingRemote) { _workbenchInteraction?.HostStorageChanged(bench); } } [MessageHandler(typeof(WorkbenchOpMessage))] private void HandleWorkbenchOp(MessageContext context, WorkbenchOpMessage message) { if (_context.InGame() && IsJoinPhaseSender(context) && message != null) { ExecuteWorldCommand(context, message, delegate { if (_workbenchInteraction != null && _workbenchInteraction.HostApplyOp(message)) { return true; } WorkbenchStateMessage workbenchStateMessage = _workbenchInteraction?.BuildRefresh(message.Index); if (workbenchStateMessage != null) { SendWorldTo(context.Connection.Id, (INetMessage)(object)workbenchStateMessage); } return false; }); } else { RejectWorldIntent(context, message); } } [MessageHandler(typeof(WorkbenchBundleMessage))] private void HandleWorkbenchBundle(MessageContext context, WorkbenchBundleMessage message) { if (_context.InGame() && IsJoinPhaseSender(context) && message != null) { ExecuteWorldCommand(context, message, () => _workbenchInteraction != null && _workbenchInteraction.HostCompleteBundle(message.Index)); } else { RejectWorldIntent(context, message); } } } [NetworkMessage] public class PlayerBoxPickupMessage : PlayerBoxInteractionMessage { public Vector3 HeldPosition; public Quaternion HeldRotation; public int HolderConnectionId; } [NetworkMessage] public sealed class PlayerBoxPickupRequestMessage : PlayerBoxPickupMessage { } [NetworkMessage] public class PlayerBoxPlacementMessage : PlayerBoxInteractionMessage { public Vector3 Position; public Quaternion Rotation; } [NetworkMessage] public sealed class PlayerBoxPlacementRequestMessage : PlayerBoxPlacementMessage { } [NetworkMessage] public class PlayerBoxThrowMessage : PlayerBoxInteractionMessage { public Vector3 Position; public Quaternion Rotation; public Vector3 Velocity; public Vector3 AngularVelocity; } [NetworkMessage] public sealed class PlayerBoxThrowRequestMessage : PlayerBoxThrowMessage { } public abstract class PlayerBoxInteractionMessage : WorldMessage { public Guid BoxNetworkId; } internal sealed class PlayerBoxInteraction { internal struct LocalAction { public bool Send; public Guid BoxNetworkId; public Action Undo; } private const float MaxCoordinate = 1000f; private const float MaxVelocity = 1000f; private static readonly Vector3 RemoteHoldLocalOffset = new Vector3(-0.12f, 0f, 0f); private static readonly Quaternion RemoteHoldLocalRotation = Quaternion.Euler(0f, -90f, 0f) * Quaternion.Euler(90f, 0f, 0f); private readonly Dictionary _remoteHoldAnchors = new Dictionary(); private readonly HashSet _remoteHeldBoxIds = new HashSet(); private readonly Dictionary _remoteHolders = new Dictionary(); private readonly PropertyInfo _rigidbodyVelocity = typeof(Rigidbody).GetProperty("velocity"); private readonly PropertyInfo _rigidbodyLinearVelocity = typeof(Rigidbody).GetProperty("linearVelocity"); private readonly BoxNetworkInteraction _boxes; private static readonly FieldInfo CurrentHoldingBoxField = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingBox"); private readonly Action _broadcast; private readonly Action _send; private readonly bool _host; private bool _applyingPrediction; private int _coveredHold; internal InteractablePackagingBox LocalHeldBox { get { InteractionPlayerController val = SceneRef.Get(); if (!((Object)(object)val == (Object)null)) { object? obj = CurrentHoldingBoxField?.GetValue(val); return (InteractablePackagingBox)((obj is InteractablePackagingBox) ? obj : null); } return null; } } internal Guid LocalHeldBoxId { get { if (!_boxes.TryGetId(LocalHeldBox, out var id)) { return Guid.Empty; } return id; } } internal bool IsCoveredHold => _coveredHold > 0; internal PlayerBoxInteraction(BoxNetworkInteraction boxes, Action broadcast) { _boxes = boxes ?? throw new ArgumentNullException("boxes"); _broadcast = broadcast ?? throw new ArgumentNullException("broadcast"); _host = true; } internal PlayerBoxInteraction(BoxNetworkInteraction boxes, Action send) { _boxes = boxes ?? throw new ArgumentNullException("boxes"); _send = send ?? throw new ArgumentNullException("send"); } internal void Reset() { foreach (KeyValuePair remoteHoldAnchor in _remoteHoldAnchors) { if (_boxes.TryGetBox(remoteHoldAnchor.Key, out var box) && (Object)(object)box != (Object)null && ((Component)box).transform.parent == remoteHoldAnchor.Value) { box.DropBox(false); } Transform value = remoteHoldAnchor.Value; if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)((Component)value).gameObject); } } _remoteHoldAnchors.Clear(); _remoteHeldBoxIds.Clear(); _remoteHolders.Clear(); _coveredHold = 0; } internal LocalAction CaptureLocalAction(InteractablePackagingBox box, bool isPlayer, bool alignBody) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) if (_applyingPrediction || PredictionApi.IsReconciling) { return default(LocalAction); } if (!isPlayer || (Object)(object)box == (Object)null || !_boxes.TryGetId(box, out var id)) { if (isPlayer && (Object)(object)box != (Object)null) { CoopPlugin.Log.LogWarning((object)("[box-id] ignored action for unregistered box " + ((Object)box).name + ".")); } return default(LocalAction); } if (alignBody) { AlignBodyToVisual(box); } Transform priorParent = ((Component)box).transform.parent; Vector3 priorPosition = ((Component)box).transform.position; Quaternion priorRotation = ((Component)box).transform.rotation; Vector3 priorVelocity = ReadVelocity(box.m_Rigidbody); Vector3 priorAngularVelocity = (((Object)(object)box.m_Rigidbody == (Object)null) ? Vector3.zero : box.m_Rigidbody.angularVelocity); return new LocalAction { Send = true, BoxNetworkId = id, Undo = delegate { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) RestorePredictedLocal(box, priorParent, priorPosition, priorRotation, priorVelocity, priorAngularVelocity); } }; } private void ApplyPredictedLocal(InteractablePackagingBox box, PlayerBoxInteractionMessage message) { if (IsBeingPlaced(box)) { return; } _applyingPrediction = true; try { if (message is PlayerBoxPickupMessage) { TakeIntoLocalHand(box); return; } if (message is PlayerBoxPlacementMessage) { box.DropBox(true); return; } if (message is PlayerBoxThrowMessage) { box.ThrowBox(true); return; } throw new InvalidOperationException("Unsupported predicted player-box action."); } finally { _applyingPrediction = false; } } internal void TakeIntoLocalHand(InteractablePackagingBox box) { if (!((Object)(object)box == (Object)null)) { if (!PredictionApi.IsReconciling && _boxes.TryGetId(box, out var id) && _boxes.HasPendingLocalMove(id)) { ManualLogSource log = CoopPlugin.Log; Guid guid = id; log.LogInfo((object)("[box-id] hold for id=" + guid.ToString() + " crossed a pending local move; leaving the box with the move.")); } else if (!((Object)(object)SceneRef.Get() == (Object)null)) { ApplyLocalHold(box); } } } internal void TakeCoveredIntoLocalHand(InteractablePackagingBox box) { BeginCoveredHold(); try { TakeIntoLocalHand(box); } finally { EndCoveredHold(); } } internal void BeginCoveredHold() { _coveredHold++; } internal void EndCoveredHold() { if (_coveredHold > 0) { _coveredHold--; } } private void ApplyLocalHold(InteractablePackagingBox box) { if (!((Object)(object)box == (Object)null) && !IsBeingPlaced(box)) { InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { ReleaseConflictingLocalHold(box); box.StartHoldBox(true, val.m_HoldItemPos); } } } private void ReleaseConflictingLocalHold(InteractablePackagingBox keep) { InteractionPlayerController val = SceneRef.Get(); InteractablePackagingBox localHeldBox = LocalHeldBox; if (!((Object)(object)localHeldBox == (Object)null) && localHeldBox != keep) { Guid id; bool num = _boxes.TryGetId(localHeldBox, out id); ((InteractableObject)localHeldBox).StopLerpToTransform(); if (num) { PublishForcedDrop(localHeldBox, id, keep); ReleaseRemoteHold(id); } localHeldBox.DropBox(false); val.OnExitHoldBoxMode(); } } private void PublishForcedDrop(InteractablePackagingBox box, Guid boxNetworkId, InteractablePackagingBox replacement) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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) PlayerBoxPlacementMessage placement = (_host ? new PlayerBoxPlacementMessage() : new PlayerBoxPlacementRequestMessage()); placement.BoxNetworkId = boxNetworkId; placement.Position = ((Component)box).transform.position; placement.Rotation = ((Component)box).transform.rotation; if (_host) { _broadcast((INetMessage)(object)placement); return; } if (_coveredHold > 0 || PredictionApi.IsApplying || PredictionApi.IsReconciling) { WorldClientBehaviour.SendClientIntent(placement); return; } WorldPrediction.Predict("world.boxes", placement, delegate { ApplyPredictedLocal(box, placement); }, delegate { RestoreForcedHold(box); }, delegate { ReleaseRefusedReplacement(replacement); }); } private void RestoreForcedHold(InteractablePackagingBox box) { if (!((Object)(object)box == (Object)null) && (!_boxes.TryGetId(box, out var id) || (!HasMirroredHold(box, id) && !_boxes.IsStored(id)))) { ApplyLocalHold(box); } } private void ReleaseRefusedReplacement(InteractablePackagingBox replacement) { if (!((Object)(object)replacement == (Object)null) && !IsBeingPlaced(replacement)) { ((InteractableObject)replacement).StopLerpToTransform(); replacement.DropBox(false); } } private void RestorePredictedLocal(InteractablePackagingBox box, Transform parent, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 angularVelocity) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)box == (Object)null) && !IsBeingPlaced(box) && (!_boxes.TryGetId(box, out var id) || (!HasMirroredHold(box, id) && !_boxes.IsStored(id)))) { _applyingPrediction = true; try { box.DropBox(false); } finally { _applyingPrediction = false; } ((Component)box).transform.SetParent(parent, true); ((Component)box).transform.SetPositionAndRotation(position, rotation); if ((Object)(object)box.m_Rigidbody != (Object)null) { box.m_Rigidbody.position = position; box.m_Rigidbody.rotation = rotation; WriteVelocity(box.m_Rigidbody, velocity); box.m_Rigidbody.angularVelocity = angularVelocity; } } } internal void PublishPickup(InteractablePackagingBox box, LocalAction action) { if (action.Send && !((Object)(object)box == (Object)null)) { ReleaseRemoteHold(action.BoxNetworkId); PlayerBoxPickupMessage playerBoxPickupMessage = (_host ? new PlayerBoxPickupMessage() : new PlayerBoxPickupRequestMessage()); ReadHoldPose(box, out playerBoxPickupMessage.HeldPosition, out playerBoxPickupMessage.HeldRotation); playerBoxPickupMessage.BoxNetworkId = action.BoxNetworkId; playerBoxPickupMessage.HolderConnectionId = 0; PublishAction(box, action, playerBoxPickupMessage); } } internal void PublishHostHeld(InteractablePackagingBox box, Guid boxNetworkId) { if (_host && !((Object)(object)box == (Object)null) && !(boxNetworkId == Guid.Empty)) { PlayerBoxPickupMessage playerBoxPickupMessage = new PlayerBoxPickupMessage { BoxNetworkId = boxNetworkId }; ReadHoldPose(box, out playerBoxPickupMessage.HeldPosition, out playerBoxPickupMessage.HeldRotation); playerBoxPickupMessage.HolderConnectionId = 0; _broadcast((INetMessage)(object)playerBoxPickupMessage); } } internal void AnnounceGrantedHold(Guid boxNetworkId, int holderConnectionId) { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) if (_host) { if (boxNetworkId == Guid.Empty || !_boxes.TryGetBox(boxNetworkId, out var box) || (Object)(object)box == (Object)null) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] granted hold id=", null, null, null, null }; Guid guid = boxNetworkId; obj[1] = guid.ToString(); obj[2] = " holder="; obj[3] = holderConnectionId.ToString(); obj[4] = " is not bound; not announced."; log.LogWarning((object)string.Concat(obj)); } else if (_boxes.IsStored(boxNetworkId) || IsBeingPlaced(box)) { ManualLogSource log2 = CoopPlugin.Log; string[] obj2 = new string[9] { "[box-id] granted hold for box id=", null, null, null, null, null, null, null, null }; Guid guid = boxNetworkId; obj2[1] = guid.ToString(); obj2[2] = " name="; obj2[3] = ((Object)box).name; obj2[4] = " could not be announced (stored="; obj2[5] = _boxes.IsStored(boxNetworkId).ToString(); obj2[6] = ", moving="; obj2[7] = IsBeingPlaced(box).ToString(); obj2[8] = ")."; log2.LogWarning((object)string.Concat(obj2)); } else { PlayerBoxPickupMessage playerBoxPickupMessage = new PlayerBoxPickupMessage { BoxNetworkId = boxNetworkId, HeldPosition = ((Component)box).transform.position, HeldRotation = ((Component)box).transform.rotation, HolderConnectionId = holderConnectionId }; bool flag = ApplyIncoming(playerBoxPickupMessage); ManualLogSource log3 = CoopPlugin.Log; string[] obj3 = new string[11] { "[box-id] granted hold id=", null, null, null, null, null, null, null, null, null, null }; Guid guid = boxNetworkId; obj3[1] = guid.ToString(); obj3[2] = " name="; obj3[3] = ((Object)box).name; obj3[4] = " holder="; obj3[5] = holderConnectionId.ToString(); obj3[6] = " applied="; obj3[7] = flag.ToString(); obj3[8] = " held="; obj3[9] = _boxes.IsBeingHeld(box).ToString(); obj3[10] = "."; log3.LogInfo((object)string.Concat(obj3)); _broadcast((INetMessage)(object)playerBoxPickupMessage); } } } internal void PublishPlacement(InteractablePackagingBox box, LocalAction action) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (action.Send && !((Object)(object)box == (Object)null)) { PlayerBoxPlacementMessage playerBoxPlacementMessage = (_host ? new PlayerBoxPlacementMessage() : new PlayerBoxPlacementRequestMessage()); playerBoxPlacementMessage.Position = ((Component)box).transform.position; playerBoxPlacementMessage.Rotation = ((Component)box).transform.rotation; playerBoxPlacementMessage.BoxNetworkId = action.BoxNetworkId; PublishAction(box, action, playerBoxPlacementMessage); } } internal void PublishThrow(InteractablePackagingBox box, LocalAction action) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) if (action.Send && !((Object)(object)box == (Object)null)) { Rigidbody rigidbody = box.m_Rigidbody; PlayerBoxThrowMessage playerBoxThrowMessage = (_host ? new PlayerBoxThrowMessage() : new PlayerBoxThrowRequestMessage()); playerBoxThrowMessage.Position = (((Object)(object)rigidbody == (Object)null) ? ((Component)box).transform.position : rigidbody.position); playerBoxThrowMessage.Rotation = (((Object)(object)rigidbody == (Object)null) ? ((Component)box).transform.rotation : rigidbody.rotation); playerBoxThrowMessage.Velocity = ReadVelocity(rigidbody); playerBoxThrowMessage.AngularVelocity = (((Object)(object)rigidbody == (Object)null) ? Vector3.zero : rigidbody.angularVelocity); playerBoxThrowMessage.BoxNetworkId = action.BoxNetworkId; PublishAction(box, action, playerBoxThrowMessage); } } private void PublishAction(InteractablePackagingBox box, LocalAction action, PlayerBoxInteractionMessage message) { if (_host) { _broadcast((INetMessage)(object)message); } else { if (_coveredHold > 0 && !PredictionApi.IsApplying && !PredictionApi.IsReconciling && message is PlayerBoxPickupMessage) { return; } if (_coveredHold > 0 || PredictionApi.IsApplying || PredictionApi.IsReconciling) { WorldClientBehaviour.SendClientIntent(message); return; } Guid predictionId = WorldPrediction.Predict("world.boxes", message, delegate { ApplyPredictedLocal(box, message); }, action.Undo); if (message is PlayerBoxPlacementRequestMessage || message is PlayerBoxThrowRequestMessage) { _boxes.NotePendingLocalMove(message.BoxNetworkId, predictionId); } } } private static void ReadHoldPose(InteractablePackagingBox box, out Vector3 position, out Quaternion rotation) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) InteractionPlayerController val = SceneRef.Get(); Transform val2 = (((Object)(object)val == (Object)null) ? null : val.m_HoldItemPos); if ((Object)(object)val2 != (Object)null) { position = val2.position; rotation = val2.rotation; } else { position = ((Component)box).transform.position; rotation = ((Component)box).transform.rotation; } } internal bool ApplyIncoming(PlayerBoxInteractionMessage message) { //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (_host && !Validate(message)) { return false; } if (!_boxes.TryGetBox(message.BoxNetworkId, out var box)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[box-id] ignoring player-box action for unknown box id=", null, null, null, null }; Guid boxNetworkId = message.BoxNetworkId; obj[1] = boxNetworkId.ToString(); obj[2] = " ("; obj[3] = message.GetType().Name; obj[4] = ")."; log.LogWarning((object)string.Concat(obj)); return false; } if (IsBeingPlaced(box)) { if (message is PlayerBoxPlacementMessage || message is PlayerBoxThrowMessage) { ReleaseRemoteHold(message.BoxNetworkId); } return false; } if (message is PlayerBoxPickupMessage playerBoxPickupMessage) { if (_boxes.IsStored(playerBoxPickupMessage.BoxNetworkId)) { ReleaseLocalHold(playerBoxPickupMessage.BoxNetworkId); return false; } if (!_host && playerBoxPickupMessage.HolderConnectionId == CoopCore.LocalConnectionId) { ApplyPredictedLocal(box, playerBoxPickupMessage); } else { if (LocalHeldBoxId == playerBoxPickupMessage.BoxNetworkId) { ((InteractableObject)box).StopLerpToTransform(); box.DropBox(false); InteractionPlayerController obj2 = SceneRef.Get(); if (obj2 != null) { obj2.OnExitHoldBoxMode(); } } ApplyPickup(box, playerBoxPickupMessage.BoxNetworkId, playerBoxPickupMessage.HeldPosition, playerBoxPickupMessage.HeldRotation, playerBoxPickupMessage.HolderConnectionId); } } else if (message is PlayerBoxPlacementMessage message2) { ApplyPlacement(box, message2); } else { if (!(message is PlayerBoxThrowMessage message3)) { throw new InvalidOperationException("Unsupported player-box action " + message.GetType().FullName + "."); } ApplyThrow(box, message3); } return true; } private void ApplyPickup(InteractablePackagingBox box, Guid boxNetworkId, Vector3 heldPosition, Quaternion heldRotation, int holderConnectionId) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) Transform remoteHoldAnchor = GetRemoteHoldAnchor(boxNetworkId, heldPosition, heldRotation); Transform anchor; bool flag = PresenceApi.TryGetRemoteCarryAnchor(holderConnectionId, out anchor) && (Object)(object)anchor != (Object)null; if (flag) { remoteHoldAnchor.SetParent(anchor, false); remoteHoldAnchor.localPosition = Vector3.zero; remoteHoldAnchor.localRotation = RemoteHoldLocalRotation; } _remoteHeldBoxIds.Add(boxNetworkId); _remoteHolders[boxNetworkId] = holderConnectionId; box.StartHoldBox(false, remoteHoldAnchor); ((InteractableObject)box).StopLerpToTransform(); ((Component)box).transform.SetParent(remoteHoldAnchor, false); ((Component)box).transform.localPosition = (flag ? RemoteHoldLocalOffset : Vector3.zero); ((Component)box).transform.localRotation = Quaternion.identity; } private void ApplyPlacement(InteractablePackagingBox box, PlayerBoxPlacementMessage message) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) box.DropBox(false); ReleaseRemoteHold(message.BoxNetworkId); ApplyPose(box, message.Position, message.Rotation); } private void ApplyThrow(InteractablePackagingBox box, PlayerBoxThrowMessage message) { //IL_0002: 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_0027: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) ApplyPose(box, message.Position, message.Rotation); box.ThrowBox(false); ReleaseRemoteHold(message.BoxNetworkId); ApplyPose(box, message.Position, message.Rotation); if ((Object)(object)box.m_Rigidbody != (Object)null) { WriteVelocity(box.m_Rigidbody, message.Velocity); box.m_Rigidbody.angularVelocity = message.AngularVelocity; box.m_Rigidbody.WakeUp(); } } private Transform GetRemoteHoldAnchor(Guid boxNetworkId, Vector3 position, Quaternion rotation) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!_remoteHoldAnchors.TryGetValue(boxNetworkId, out var value) || (Object)(object)value == (Object)null) { Guid guid = boxNetworkId; value = new GameObject("CardShopCoop.RemoteBoxHold." + guid).transform; _remoteHoldAnchors[boxNetworkId] = value; } value.SetPositionAndRotation(position, rotation); return value; } internal void ReleaseRemoteHoldForBox(Guid boxNetworkId) { ReleaseRemoteHold(boxNetworkId); } internal bool HasMirroredHold(InteractablePackagingBox box, Guid boxNetworkId) { if ((Object)(object)box == (Object)null || boxNetworkId == Guid.Empty || !_remoteHoldAnchors.TryGetValue(boxNetworkId, out var value) || (Object)(object)value == (Object)null) { return false; } return ((Component)box).transform.parent == value; } internal bool DetachMirroredHold(InteractablePackagingBox box, Guid boxNetworkId) { if (!HasMirroredHold(box, boxNetworkId)) { return false; } ((InteractableObject)box).StopLerpToTransform(); box.DropBox(false); return true; } internal void ReleaseHeldObject(InteractablePackagingBox box) { if (!((Object)(object)box == (Object)null) && LocalHeldBox == box) { CoopPlugin.Log.LogInfo((object)("[box-id] releasing held box " + ((Object)box).name + " because the game is destroying it.")); ((InteractableObject)box).StopLerpToTransform(); box.DropBox(false); InteractionPlayerController obj = SceneRef.Get(); if (obj != null) { obj.OnExitHoldBoxMode(); } } } internal void ForgetLocalHold(Guid boxNetworkId) { ReleaseLocalHold(boxNetworkId); } internal void ReleaseLocalHold(Guid boxNetworkId = default(Guid)) { InteractionPlayerController val = SceneRef.Get(); InteractablePackagingBox localHeldBox = LocalHeldBox; Guid id; Guid guid = (_boxes.TryGetId(localHeldBox, out id) ? id : Guid.Empty); bool flag = boxNetworkId == Guid.Empty || guid == boxNetworkId; if ((Object)(object)localHeldBox != (Object)null && flag) { ((InteractableObject)localHeldBox).StopLerpToTransform(); localHeldBox.DropBox(true); } else if ((Object)(object)localHeldBox == (Object)null && flag && (Object)(object)val != (Object)null) { val.OnExitHoldBoxMode(); } } internal void ForgetDestroyed(Guid boxNetworkId) { if (boxNetworkId == Guid.Empty) { return; } ReleaseRemoteHold(boxNetworkId); if (!(LocalHeldBoxId != boxNetworkId)) { InteractablePackagingBox localHeldBox = LocalHeldBox; ManualLogSource log = CoopPlugin.Log; Guid guid = boxNetworkId; log.LogInfo((object)("[box-id] releasing held box id=" + guid.ToString() + " (authoritative destroy).")); ((InteractableObject)localHeldBox).StopLerpToTransform(); localHeldBox.DropBox(false); InteractionPlayerController obj = SceneRef.Get(); if (obj != null) { obj.OnExitHoldBoxMode(); } } } internal void ReleaseRemoteHoldsForConnection(int connectionId) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) if (!_host || connectionId == 0 || _remoteHolders.Count == 0) { return; } List list = null; foreach (KeyValuePair remoteHolder in _remoteHolders) { if (remoteHolder.Value == connectionId) { (list ?? (list = new List())).Add(remoteHolder.Key); } } if (list == null) { return; } for (int i = 0; i < list.Count; i++) { Guid guid = list[i]; if (!_boxes.TryGetBox(guid, out var box) || (Object)(object)box == (Object)null) { ReleaseRemoteHold(guid); continue; } ((InteractableObject)box).StopLerpToTransform(); box.DropBox(false); ReleaseRemoteHold(guid); _broadcast((INetMessage)(object)new PlayerBoxPlacementMessage { BoxNetworkId = guid, Position = ((Component)box).transform.position, Rotation = ((Component)box).transform.rotation }); } } private void ReleaseRemoteHold(Guid boxNetworkId) { _remoteHeldBoxIds.Remove(boxNetworkId); _remoteHolders.Remove(boxNetworkId); if (_remoteHoldAnchors.TryGetValue(boxNetworkId, out var value)) { _remoteHoldAnchors.Remove(boxNetworkId); if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)((Component)value).gameObject); } } } internal void AppendBaselineHolds(Action append) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) if (!_host || append == null) { return; } InteractablePackagingBox localHeldBox = LocalHeldBox; if ((Object)(object)localHeldBox != (Object)null && _boxes.TryGetId(localHeldBox, out var id) && !_boxes.IsStored(id)) { append((INetMessage)(object)BuildBaselineHold(id, ((Component)localHeldBox).transform.position, ((Component)localHeldBox).transform.rotation, 0)); } foreach (KeyValuePair remoteHolder in _remoteHolders) { if (!_boxes.IsStored(remoteHolder.Key) && _boxes.TryGetBox(remoteHolder.Key, out var box) && (Object)(object)box != (Object)null) { append((INetMessage)(object)BuildBaselineHold(remoteHolder.Key, ((Component)box).transform.position, ((Component)box).transform.rotation, remoteHolder.Value)); } } } private static PlayerBoxPickupMessage BuildBaselineHold(Guid boxNetworkId, Vector3 position, Quaternion rotation, int holderConnectionId) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: 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) return new PlayerBoxPickupMessage { BoxNetworkId = boxNetworkId, HeldPosition = position, HeldRotation = rotation, HolderConnectionId = holderConnectionId }; } internal bool TryGetRemoteHolder(Guid boxNetworkId, out int holderConnectionId) { return _remoteHolders.TryGetValue(boxNetworkId, out holderConnectionId); } internal void ReattachRemoteHolds() { //IL_0067: 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) if (_remoteHolders.Count == 0) { return; } List> list = new List>(_remoteHolders); for (int i = 0; i < list.Count; i++) { Guid key = list[i].Key; if (_boxes.TryGetBox(key, out var box) && !((Object)(object)box == (Object)null) && !_boxes.IsStored(key) && !IsBeingPlaced(box)) { ApplyPickup(box, key, ((Component)box).transform.position, ((Component)box).transform.rotation, list[i].Value); } } } internal static bool IsBeingPlaced(InteractablePackagingBox box) { if ((Object)(object)box != (Object)null) { return ((InteractableObject)box).GetIsMovingObject(); } return false; } private static void AlignBodyToVisual(InteractablePackagingBox box) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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) if (!((Object)(object)box.m_Rigidbody == (Object)null)) { box.m_Rigidbody.position = ((Component)box).transform.position; box.m_Rigidbody.rotation = ((Component)box).transform.rotation; box.m_Rigidbody.angularVelocity = Vector3.zero; } } private static void ApplyPose(InteractablePackagingBox box, Vector3 position, Quaternion rotation) { //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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)box.m_Rigidbody != (Object)null) { box.m_Rigidbody.position = position; box.m_Rigidbody.rotation = rotation; } ((Component)box).transform.SetPositionAndRotation(position, rotation); } private Vector3 ReadVelocity(Rigidbody body) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) PropertyInfo propertyInfo = _rigidbodyLinearVelocity ?? _rigidbodyVelocity; if ((Object)(object)body != (Object)null) { object obj = propertyInfo?.GetValue(body); if (obj is Vector3) { return (Vector3)obj; } } return Vector3.zero; } private void WriteVelocity(Rigidbody body, Vector3 velocity) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) (_rigidbodyLinearVelocity ?? _rigidbodyVelocity)?.SetValue(body, velocity); } private static bool Validate(PlayerBoxInteractionMessage message) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (message == null || message.BoxNetworkId == Guid.Empty) { return false; } if (!(message is PlayerBoxPickupMessage playerBoxPickupMessage)) { if (!(message is PlayerBoxPlacementMessage playerBoxPlacementMessage)) { if (message is PlayerBoxThrowMessage playerBoxThrowMessage) { return IsSanePose(playerBoxThrowMessage.Position, playerBoxThrowMessage.Rotation) && IsSaneVector(playerBoxThrowMessage.Velocity, 1000f) && IsSaneVector(playerBoxThrowMessage.AngularVelocity, 1000f); } return false; } return IsSanePose(playerBoxPlacementMessage.Position, playerBoxPlacementMessage.Rotation); } return IsSanePose(playerBoxPickupMessage.HeldPosition, playerBoxPickupMessage.HeldRotation); } private static bool IsSanePose(Vector3 position, Quaternion rotation) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) if (IsSaneVector(position, 1000f) && IsFinite(rotation.x) && IsFinite(rotation.y) && IsFinite(rotation.z)) { return IsFinite(rotation.w); } return false; } private static bool IsSaneVector(Vector3 vector, float maximum) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0043: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(vector.x) && IsFinite(vector.y) && IsFinite(vector.z) && Mathf.Abs(vector.x) <= maximum && Mathf.Abs(vector.y) <= maximum) { return Mathf.Abs(vector.z) <= maximum; } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } [NetworkMessage] public class ShelfItemAddMessage : ShelfInteractionMessage { } [NetworkMessage] public sealed class ShelfItemAddRequestMessage : ShelfItemAddMessage { } [NetworkMessage] public class ShelfItemRemoveMessage : ShelfInteractionMessage { } [NetworkMessage] public sealed class ShelfItemRemoveRequestMessage : ShelfItemRemoveMessage { } public abstract class ShelfInteractionMessage : WorldMessage { public int ShelfKey; public int Compartment; public EItemType ItemType; public int ItemCount; } internal sealed class ShelfInteraction { internal struct LocalMutation { public bool Send; public ShelfInteractionMessage Command; public int ShelfKey; public int Compartment; public Item Removed; } private const int MaxItemCount = 4096; private const int MaxCompartments = 256; private readonly Dictionary _pendingClientStates = new Dictionary(); private readonly Dictionary> _pendingClientAddIds = new Dictionary>(); private readonly Dictionary> _pendingClientRemoveIds = new Dictionary>(); private readonly Dictionary _pendingClientAddItems = new Dictionary(); private static readonly FieldInfo StoredItemsField = AccessTools.Field(typeof(ShelfCompartment), "m_StoredItemList"); private static readonly FieldInfo CompartmentListField = AccessTools.Field(typeof(InteractableObject), "m_ItemCompartmentList"); private static readonly FieldInfo PosListField = AccessTools.Field(typeof(ShelfCompartment), "m_PosList"); private static readonly FieldInfo HoldItemListField = AccessTools.Field(typeof(InteractionPlayerController), "m_HoldItemList"); private static readonly MethodInfo RemoveHoldItemMethod = AccessTools.Method(typeof(InteractionPlayerController), "RemoveHoldItem", (Type[])null, (Type[])null); private readonly Action _broadcast; private readonly Action _send; private readonly Func _inGame; private readonly bool _host; private bool _applying; private int _playerMutationDepth; internal ShelfInteraction(Action broadcast, Func inGame) { if (broadcast == null) { throw new ArgumentNullException("broadcast"); } _host = true; _broadcast = broadcast; _inGame = inGame; } internal ShelfInteraction(Action send) { if (send == null) { throw new ArgumentNullException("send"); } _send = send; } internal void Reset() { _pendingClientStates.Clear(); _pendingClientAddIds.Clear(); _pendingClientRemoveIds.Clear(); _pendingClientAddItems.Clear(); _applying = false; _playerMutationDepth = 0; } internal void EnterPlayerMutation() { _playerMutationDepth++; } internal void ExitPlayerMutation() { if (_playerMutationDepth <= 0) { throw new InvalidOperationException("Player shelf-mutation scope ended without a matching start."); } _playerMutationDepth--; } internal LocalMutation CaptureLocalMutation(ShelfCompartment compartment, bool isAdd, EItemType requestedType, Item item) { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: 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_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Invalid comparison between Unknown and I4 //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) bool num; if (!_applying && IsPlayerShelf(compartment)) { if (!_host) { if (_send != null) { num = _playerMutationDepth == 0; goto IL_0070; } } else if (_broadcast != null && _inGame != null) { num = !_inGame(); goto IL_0070; } } goto IL_0072; IL_0072: return default(LocalMutation); IL_0070: if (!num) { if (!TryMakeKey(compartment, out var shelfKey, out var compartmentIndex)) { CoopPlugin.Log.LogWarning((object)("[shelf] no furniture id for " + ((Object)compartment).name + "; " + (isAdd ? "placement" : "removal") + " not synced.")); return default(LocalMutation); } EItemType itemType = compartment.GetItemType(); int itemCount = compartment.GetItemCount(); EItemType itemType2 = ((isAdd && (int)requestedType != -1) ? requestedType : itemType); int itemCount2 = (isAdd ? (itemCount + 1) : Math.Max(0, itemCount - 1)); ShelfInteractionMessage shelfInteractionMessage = (isAdd ? ((ShelfInteractionMessage)new ShelfItemAddRequestMessage()) : ((ShelfInteractionMessage)new ShelfItemRemoveRequestMessage())); shelfInteractionMessage.ShelfKey = shelfKey; shelfInteractionMessage.Compartment = compartmentIndex; shelfInteractionMessage.ItemType = itemType2; shelfInteractionMessage.ItemCount = itemCount2; if (!_host && isAdd) { Guid guid = WorldPrediction.Predict("world.shelves", shelfInteractionMessage, delegate { AddCapturedItem(compartment, item); }, delegate { ReturnCapturedItem(compartment, item); }); RecordPendingAdd(ShelfKey(shelfInteractionMessage), guid); if ((Object)(object)item != (Object)null) { _pendingClientAddItems[guid] = item; } } return new LocalMutation { Send = true, Command = ((_host || !isAdd) ? null : shelfInteractionMessage), ShelfKey = shelfKey, Compartment = compartmentIndex }; } goto IL_0072; } private void AddCapturedItem(ShelfCompartment compartment, Item item) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: 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) if ((Object)(object)compartment == (Object)null || (Object)(object)item == (Object)null) { return; } try { _applying = true; InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null && HoldItemListField?.GetValue(val) is List list && list.Contains(item)) { RemoveHoldItemMethod?.Invoke(val, new object[1] { item }); } if (compartment.GetItemCount() <= 0 && compartment.GetItemType() != item.GetItemType()) { compartment.SetCompartmentItemType(item.GetItemType()); compartment.CalculatePositionList(); } compartment.AddItem(item, false); PositionStored(compartment); } finally { _applying = false; } } private void ReturnCapturedItem(ShelfCompartment compartment, Item item) { if ((Object)(object)compartment == (Object)null || (Object)(object)item == (Object)null) { return; } try { _applying = true; if (StoredItemsField?.GetValue(compartment) is List list && list.Contains(item)) { compartment.RemoveItem(item); InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { val.AddHoldItemToFront(item); } PositionStored(compartment); CoopPlugin.Log.LogInfo((object)("[shelf] rolled a rejected placement back to hand on " + ((Object)compartment).name + ".")); } } finally { _applying = false; } } private void ReturnPendingItems(ShelfCompartment compartment) { if (_host || (Object)(object)compartment == (Object)null || _pendingClientAddItems.Count == 0 || !(StoredItemsField?.GetValue(compartment) is List { Count: not 0 } list)) { return; } InteractionPlayerController val = SceneRef.Get(); for (int num = list.Count - 1; num >= 0; num--) { Item val2 = list[num]; if (!((Object)(object)val2 == (Object)null)) { Guid guid = Guid.Empty; foreach (KeyValuePair pendingClientAddItem in _pendingClientAddItems) { if (pendingClientAddItem.Value == val2 && PredictionApi.IsPending(pendingClientAddItem.Key)) { guid = pendingClientAddItem.Key; break; } } if (!(guid == Guid.Empty)) { _pendingClientAddItems.Remove(guid); compartment.RemoveItem(val2); if ((Object)(object)val != (Object)null) { val.AddHoldItemToFront(val2); } CoopPlugin.Log.LogInfo((object)("[shelf] returned a pending placement item on " + ((Object)compartment).name + " before an authoritative clear.")); } } } PrunePendingAddItems(); PositionStored(compartment); } private void PrunePendingAddItems() { if (_pendingClientAddItems.Count == 0) { return; } List list = new List(); foreach (KeyValuePair pendingClientAddItem in _pendingClientAddItems) { if (!PredictionApi.IsPending(pendingClientAddItem.Key)) { list.Add(pendingClientAddItem.Key); } } for (int i = 0; i < list.Count; i++) { _pendingClientAddItems.Remove(list[i]); } } internal void PublishAdd(ShelfCompartment compartment, LocalMutation mutation) { Publish(compartment, mutation, isAdd: true); } internal void PublishRemove(ShelfCompartment compartment, LocalMutation mutation) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (!mutation.Send || (Object)(object)compartment == (Object)null) { return; } if (_host || (Object)(object)mutation.Removed == (Object)null) { Publish(compartment, mutation, isAdd: false); return; } Item removed = mutation.Removed; ShelfItemRemoveRequestMessage shelfItemRemoveRequestMessage = new ShelfItemRemoveRequestMessage { ShelfKey = mutation.ShelfKey, Compartment = mutation.Compartment, ItemType = compartment.GetItemType(), ItemCount = compartment.GetItemCount() }; string key = ShelfKey(shelfItemRemoveRequestMessage); Guid predictionId = WorldPrediction.Predict("world.shelves", shelfItemRemoveRequestMessage, delegate { RemoveCapturedItem(compartment, removed); }, delegate { RestoreCapturedItem(compartment, removed); }); RecordPendingRemove(key, predictionId); } private void RemoveCapturedItem(ShelfCompartment compartment, Item item) { if ((Object)(object)compartment == (Object)null || (Object)(object)item == (Object)null) { return; } try { _applying = true; if (StoredItemsField?.GetValue(compartment) is List list && list.Contains(item)) { compartment.RemoveItem(item); InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null && HoldItemListField?.GetValue(val) is List list2 && !list2.Contains(item)) { val.AddHoldItemToFront(item); } PositionStored(compartment); } } finally { _applying = false; } } private void RestoreCapturedItem(ShelfCompartment compartment, Item item) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)compartment == (Object)null || (Object)(object)item == (Object)null || (compartment.GetItemCount() <= 0 && (int)compartment.GetItemType() == -1)) { return; } try { _applying = true; InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null && HoldItemListField?.GetValue(val) is List list && list.Contains(item)) { RemoveHoldItemMethod?.Invoke(val, new object[1] { item }); } if (compartment.GetItemCount() <= 0 && compartment.GetItemType() != item.GetItemType()) { compartment.SetCompartmentItemType(item.GetItemType()); compartment.CalculatePositionList(); } compartment.AddItem(item, false); PositionStored(compartment); CoopPlugin.Log.LogInfo((object)("[shelf] rolled a rejected removal back onto " + ((Object)compartment).name + ".")); } finally { _applying = false; } } internal LocalMutation CaptureLabelChange(ShelfCompartment compartment, bool allowWarehouse = false) { if (_applying || !(allowWarehouse ? IsSyncableShelf(compartment) : IsPlayerShelf(compartment)) || (_host ? (_broadcast == null) : (_send == null))) { return default(LocalMutation); } if (!TryMakeKey(compartment, out var shelfKey, out var compartmentIndex)) { CoopPlugin.Log.LogWarning((object)("[shelf] no furniture id for " + ((Object)compartment).name + "; label removal not synced.")); return default(LocalMutation); } return new LocalMutation { Send = true, Command = null, ShelfKey = shelfKey, Compartment = compartmentIndex }; } internal bool ApplyIncoming(ShelfInteractionMessage message) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Invalid comparison between Unknown and I4 //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Invalid comparison between Unknown and I4 if (_host && !Validate(message)) { return false; } ShelfCompartment val = ResolveCompartment(message.ShelfKey, message.Compartment); if ((Object)(object)val == (Object)null) { if (!_host) { _pendingClientStates[ShelfKey(message)] = message; return true; } return false; } if (_host && !ResolveHostResult(val, message)) { return false; } try { _applying = true; int num = message.ItemCount; if (!_host) { EItemType itemType = val.GetItemType(); if (itemType == message.ItemType || (int)itemType == -1) { string key = ShelfKey(message); if ((int)message.ItemType != -1) { num += PendingAddCount(key); } num -= PendingRemoveCount(key); } if (num < 0) { num = 0; } } ApplyState(val, message.ItemType, num); } finally { _applying = false; } return true; } internal ShelfItemAddMessage BuildRefresh(ShelfInteractionMessage failed) { //IL_0041: 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) if (failed == null) { return null; } ShelfCompartment val = ResolveCompartment(failed.ShelfKey, failed.Compartment); if ((Object)(object)val == (Object)null) { return null; } return new ShelfItemAddMessage { ShelfKey = failed.ShelfKey, Compartment = failed.Compartment, ItemType = val.GetItemType(), ItemCount = val.GetItemCount() }; } private void RecordPendingAdd(string key, Guid predictionId) { if (!_host) { RecordPending(_pendingClientAddIds, key, predictionId); } } private void RecordPendingRemove(string key, Guid predictionId) { if (!_host) { RecordPending(_pendingClientRemoveIds, key, predictionId); } } private static void RecordPending(Dictionary> pending, string key, Guid predictionId) { if (!string.IsNullOrEmpty(key) && !(predictionId == Guid.Empty)) { if (!pending.TryGetValue(key, out var value)) { value = (pending[key] = new List()); } value.Add(predictionId); } } private int PendingAddCount(string key) { return PendingCount(_pendingClientAddIds, key); } private int PendingRemoveCount(string key) { return PendingCount(_pendingClientRemoveIds, key); } private int PendingCount(Dictionary> pending, string key) { if (_host || !pending.TryGetValue(key, out var value)) { return 0; } int num = 0; for (int num2 = value.Count - 1; num2 >= 0; num2--) { if (PredictionApi.IsPending(value[num2])) { num++; } else { value.RemoveAt(num2); } } if (value.Count == 0) { pending.Remove(key); } return num; } private static bool ResolveHostResult(ShelfCompartment compartment, ShelfInteractionMessage message) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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) //IL_00bc: Invalid comparison between Unknown and I4 //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) int itemCount = compartment.GetItemCount(); EItemType itemType = compartment.GetItemType(); if (message is ShelfItemAddRequestMessage) { EItemType itemType2 = message.ItemType; if (itemCount > 0 && itemType != itemType2) { return false; } int num = Mathf.Max(compartment.GetMaxItemCount(), compartment.GetItemPosListCount()); if (itemCount > 0 && num > 0 && itemCount >= num) { CoopPlugin.Log.LogInfo((object)("[shelf] rejected placement on " + ((Object)compartment).name + ": full (" + itemCount + "/" + num + ").")); return false; } message.ItemType = itemType2; message.ItemCount = itemCount + 1; return true; } if (message is ShelfItemRemoveRequestMessage) { if (itemCount <= 0) { if ((int)itemType == -1) { return false; } message.ItemType = (EItemType)(-1); message.ItemCount = 0; return true; } message.ItemType = itemType; message.ItemCount = itemCount - 1; return true; } return false; } internal void FlushClientState() { if (!_host && _pendingClientStates.Count != 0) { List list = new List(_pendingClientStates.Values); _pendingClientStates.Clear(); for (int i = 0; i < list.Count; i++) { ApplyIncoming(list[i]); } } } private void Publish(ShelfCompartment compartment, LocalMutation mutation, bool isAdd) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if (mutation.Send && !((Object)(object)compartment == (Object)null) && (_host || mutation.Command == null)) { ShelfInteractionMessage shelfInteractionMessage = ((!isAdd) ? ((ShelfInteractionMessage)(_host ? new ShelfItemRemoveMessage() : new ShelfItemRemoveRequestMessage())) : ((ShelfInteractionMessage)(_host ? new ShelfItemAddMessage() : new ShelfItemAddRequestMessage()))); shelfInteractionMessage.ShelfKey = mutation.ShelfKey; shelfInteractionMessage.Compartment = mutation.Compartment; shelfInteractionMessage.ItemType = compartment.GetItemType(); shelfInteractionMessage.ItemCount = compartment.GetItemCount(); if (_host) { _broadcast((INetMessage)(object)shelfInteractionMessage); } else { _send(1, (INetMessage)(object)shelfInteractionMessage); } } } private static bool IsSyncableShelf(ShelfCompartment compartment) { if ((Object)(object)compartment != (Object)null) { return (Object)(object)((Component)compartment).GetComponentInParent(true) == (Object)null; } return false; } private static bool IsPlayerShelf(ShelfCompartment compartment) { if (IsSyncableShelf(compartment)) { return (Object)(object)compartment.GetWarehouseShelf() == (Object)null; } return false; } private static string ShelfKey(ShelfInteractionMessage message) { return WorldMessageMetadata.StableEntityId(message); } private static bool TryMakeKey(ShelfCompartment compartment, out int shelfKey, out int compartmentIndex) { shelfKey = 0; compartmentIndex = -1; if ((Object)(object)compartment == (Object)null) { return false; } Transform val = ((Component)compartment).transform; while ((Object)(object)val != (Object)null) { InteractableObject componentInParent = ((Component)val).GetComponentInParent(true); if ((Object)(object)componentInParent == (Object)null) { return false; } if (CompartmentListField?.GetValue(componentInParent) is List list) { int num = list.IndexOf(compartment); if (num >= 0 && num < 256) { int num2 = PlacementInterop.FindKind(componentInParent); if (num2 >= 0 && PlacementApi.TryMakeObjectKey(num2, componentInParent, out shelfKey)) { compartmentIndex = num; return true; } } } val = ((Component)componentInParent).transform.parent; } return false; } private static ShelfCompartment ResolveCompartment(int shelfKey, int compartmentIndex) { if (compartmentIndex >= 0 && compartmentIndex < 256) { Component obj = PlacementApi.ResolveObjectByKey(shelfKey); InteractableObject val = (InteractableObject)(object)((obj is InteractableObject) ? obj : null); if (val != null && CompartmentListField?.GetValue(val) is List list && compartmentIndex < list.Count) { return list[compartmentIndex]; } } return null; } private unsafe void ApplyState(ShelfCompartment compartment, EItemType type, int itemCount) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (itemCount <= 0 || (int)type == -1) { Clear(compartment); compartment.SetCompartmentItemType(type); return; } if (compartment.GetItemType() != type) { ReturnPendingItems(compartment); Clear(compartment); compartment.SetCompartmentItemType(type); compartment.CalculatePositionList(); } else if (compartment.GetItemPosListCount() == 0) { compartment.CalculatePositionList(); } int itemCount2 = compartment.GetItemCount(); if (itemCount2 == itemCount) { return; } if (itemCount2 < itemCount) { for (int i = itemCount2; i < itemCount; i++) { AddOne(compartment, type); } } else { for (int num = itemCount2; num > itemCount; num--) { RemoveOne(compartment); } } CoopPlugin.Log.LogInfo((object)("[shelf] merged " + ((Object)compartment).name + " type=" + ((object)(*(EItemType*)(&type))/*cast due to .constrained prefix*/).ToString() + " count " + itemCount2 + "->" + itemCount + ".")); } private static void AddOne(ShelfCompartment compartment, EItemType itemType) { //IL_0000: 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) ItemMeshData itemMeshData = InventoryBase.GetItemMeshData(itemType); if (itemMeshData != null) { Item item = ItemSpawnManager.GetItem(compartment.m_StoredItemListGrp); item.SetMesh(itemMeshData.mesh, itemMeshData.material, itemType, itemMeshData.meshSecondary, itemMeshData.materialSecondary, itemMeshData.materialList); ((Component)item).gameObject.SetActive(true); compartment.AddItem(item, false); PositionStored(compartment); } } private static void RemoveOne(ShelfCompartment compartment) { if (StoredItemsField?.GetValue(compartment) is List { Count: not 0 } list) { Item val = list[list.Count - 1]; compartment.RemoveItem(val); if ((Object)(object)val != (Object)null) { val.DisableItem(); } } } private static void PositionStored(ShelfCompartment compartment) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if (!(StoredItemsField?.GetValue(compartment) is List list) || !(PosListField?.GetValue(compartment) is List list2)) { compartment.RefreshItemPosition(true); return; } for (int i = 0; i < list.Count && i < list2.Count; i++) { Item val = list[i]; Transform val2 = list2[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null)) { ((Component)val).transform.position = ((Component)val2).transform.position; ((Component)val).transform.rotation = compartment.m_StartLoc.rotation; ((Component)val).transform.localScale = ((Component)val2).transform.localScale; } } } private static void Clear(ShelfCompartment compartment) { if (StoredItemsField?.GetValue(compartment) is List list) { for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null) { list[i].DisableItem(); } } list.Clear(); } else { while (compartment.GetItemCount() > 0) { Item lastItem = compartment.GetLastItem(); if ((Object)(object)lastItem == (Object)null) { break; } compartment.RemoveItem(lastItem); lastItem.DisableItem(); } } compartment.PreSpawnItemUpdate(0); } private bool Validate(ShelfInteractionMessage message) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (message == null || message.Compartment < 0 || message.Compartment >= 256 || message.ItemCount < 0 || message.ItemCount > 4096) { return false; } int num = message.ShelfKey >> 24; if (num >= 0 && num < 16 && PlacementApi.ObjectIdFromObjectKey(message.ShelfKey) != 0) { return CanResolveItemType(message.ItemType, message.ItemCount); } return false; } private bool CanResolveItemType(EItemType itemType, int itemCount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if ((int)itemType == -1) { return itemCount == 0; } try { Vector3 val = InventoryBase.GetItemData(itemType)?.itemDimension ?? Vector3.zero; return val.x > 0f && val.y > 0f && val.z > 0f; } catch (Exception) { return false; } } } internal sealed class WorldMarketInteraction { private struct PendingModCard { public ECardExpansionType Expansion; public int Index; public bool IsDestiny; public bool HasPercent; public float Percent; public float Base; public bool HasBase; } private readonly CoopRuntimeContext _context; private readonly bool _host; public Action BroadcastState; private MarketStateMessage _pendingState; private static bool s_dirty; private static List s_eplModdedCache; private static readonly Dictionary s_modCardPending = new Dictionary(); private static bool s_eplProbed; private static object s_eplSaveMgr; private static MethodInfo s_eplTryGet; private static PropertyInfo s_eplAssetsProp; private static PropertyInfo s_eplItemLibProp; private static PropertyInfo s_eplItemDataProp; private static PropertyInfo s_eplGenMarket; private static PropertyInfo s_eplGenCost; private static PropertyInfo s_eplPctChange; private static PropertyInfo s_eplAvgCost; private static bool s_rawWriteMissLogged; private static WorldMarketInteraction Current { get { object obj = WorldHostBehaviour.ActiveCards?.Market; if (obj == null) { WorldCardInteraction activeCards = WorldClientBehaviour.ActiveCards; if (activeCards == null) { return null; } obj = activeCards.Market; } return (WorldMarketInteraction)obj; } } private static bool IsHost => Current?._host ?? false; private static bool InGame { get { if (Current != null) { return Current._context.InGame(); } return false; } } private static int EplWeaveBoundary => 130; internal WorldMarketInteraction(CoopRuntimeContext context, bool host) { _context = context ?? throw new ArgumentNullException("context"); _host = host; } internal void Reset() { _pendingState = null; s_dirty = false; InvalidateContentCache(); s_modCardPending.Clear(); s_rawWriteMissLogged = false; } internal static void InvalidateContentCache() { s_eplModdedCache = null; } internal void Tick() { if (InGame && _host) { FlushHostMutation(); } } internal void FlushClientState() { if (!_host && _pendingState != null) { MarketStateMessage pendingState = _pendingState; _pendingState = null; ClientApplyState(pendingState); } } internal void AppendBaselineMessages(Action append) { if (_host && append != null && InGame && CPlayerData.m_ItemPricePercentChangeList != null && CPlayerData.m_ItemPricePercentChangeList.Count != 0) { append((INetMessage)(object)BuildState()); s_dirty = false; } } internal static void ApplyPatches(Harmony h) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown Try(h, typeof(PriceChangeManager), "OnDayStarted", null, new HarmonyMethod(typeof(WorldMarketInteraction), "HostRolledPostfix", (Type[])null)); Try(h, typeof(PriceChangeManager), "SetGameEventPrice", null, new HarmonyMethod(typeof(WorldMarketInteraction), "MarkDirtyHostPostfix", (Type[])null)); Try(h, typeof(PriceChangeManager), "Init", null, new HarmonyMethod(typeof(WorldMarketInteraction), "MarkDirtyHostPostfix", (Type[])null)); Try(h, typeof(RestockManager), "Init", null, new HarmonyMethod(typeof(WorldMarketInteraction), "MarkDirtyHostPostfix", (Type[])null)); Try(h, typeof(CPlayerData), "UpdateAverageItemCost", null, new HarmonyMethod(typeof(WorldMarketInteraction), "MarkDirtyHostPostfix", (Type[])null)); Try(h, typeof(CPlayerData), "SetAverageItemCost", null, new HarmonyMethod(typeof(WorldMarketInteraction), "MarkDirtyHostPostfix", (Type[])null)); Try(h, typeof(CPlayerData), "AddCardPricePercentChange", null, new HarmonyMethod(typeof(WorldMarketInteraction), "CardPercentChangedPostfix", (Type[])null)); Try(h, typeof(CPlayerData), "SetCardGeneratedMarketPrice", null, new HarmonyMethod(typeof(WorldMarketInteraction), "CardBaseChangedPostfix", (Type[])null)); } private static bool IsVanillaCardExpansion(ECardExpansionType expansion) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return WorldCardInteraction.IsVanillaCardExpansion(expansion); } private static long ModCardKey(ECardExpansionType expansion, int index, bool isDestiny) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return (long)((ulong)((long)expansion << 33) | ((ulong)(uint)index << 1)) | (long)(isDestiny ? 1 : 0); } public static void CardPercentChangedPostfix(int cardIndex, ECardExpansionType expansionType, bool isDestiny, float percentChange) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (IsHost && !IsVanillaCardExpansion(expansionType)) { float cardPricePercentChange; try { cardPricePercentChange = CPlayerData.GetCardPricePercentChange(cardIndex, expansionType, isDestiny); } catch (Exception e) { Swallow.Log(e, "CardPercentChangedPostfix", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/World/WorldMarketInteraction.cs", 201); return; } long key = ModCardKey(expansionType, cardIndex, isDestiny); s_modCardPending.TryGetValue(key, out var value); value.Expansion = expansionType; value.Index = cardIndex; value.IsDestiny = isDestiny; value.Percent = cardPricePercentChange; value.HasPercent = true; s_modCardPending[key] = value; s_dirty = true; } } public static void CardBaseChangedPostfix(int cardIndex, ECardExpansionType expansionType, bool isDestiny, float price) { //IL_0007: 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_0029: 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 (IsHost && !IsVanillaCardExpansion(expansionType)) { long key = ModCardKey(expansionType, cardIndex, isDestiny); s_modCardPending.TryGetValue(key, out var value); value.Expansion = expansionType; value.Index = cardIndex; value.IsDestiny = isDestiny; value.Base = price; value.HasBase = true; s_modCardPending[key] = value; s_dirty = true; } } public static void MarkDirtyHostPostfix() { if (IsHost) { s_dirty = true; Current?.FlushHostMutation(); } } private static void Try(Harmony h, Type type, string method, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("Patch target missing: " + type.Name + "." + method)); } else { h.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Patch failed: " + type.Name + "." + method + ": " + ex.Message)); } } public static void HostRolledPostfix() { if (IsHost) { s_dirty = true; Current?.FlushHostMutation(); } } private void FlushHostMutation() { if (BroadcastState == null || !s_dirty) { return; } try { if (CPlayerData.m_ItemPricePercentChangeList != null && CPlayerData.m_ItemPricePercentChangeList.Count != 0) { MarketStateMessage obj = BuildState(); BroadcastState((INetMessage)(object)obj); s_modCardPending.Clear(); s_dirty = false; } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("WorldMarketInteraction host: " + ex.Message)); } } private static MarketStateMessage BuildState() { List modded = EplModdedItemTypes(); MarketStateMessage marketStateMessage = new MarketStateMessage(); FillPercents(marketStateMessage.ItemPricePercentChangeList, CPlayerData.m_ItemPricePercentChangeList, modded); FillMarket(marketStateMessage.GenCardMarketPriceList, CPlayerData.m_GenCardMarketPriceList); FillMarket(marketStateMessage.GenCardMarketPriceListDestiny, CPlayerData.m_GenCardMarketPriceListDestiny); FillMarket(marketStateMessage.GenCardMarketPriceListGhost, CPlayerData.m_GenCardMarketPriceListGhost); FillMarket(marketStateMessage.GenCardMarketPriceListGhostBlack, CPlayerData.m_GenCardMarketPriceListGhostBlack); FillMarket(marketStateMessage.GenCardMarketPriceListMegabot, CPlayerData.m_GenCardMarketPriceListMegabot); FillMarket(marketStateMessage.GenCardMarketPriceListFantasyRPG, CPlayerData.m_GenCardMarketPriceListFantasyRPG); FillMarket(marketStateMessage.GenCardMarketPriceListCatJob, CPlayerData.m_GenCardMarketPriceListCatJob); FillMarket(marketStateMessage.GenCardMarketPriceListAscension, CPlayerData.m_GenCardMarketPriceListAscension); FillFloats(marketStateMessage.SetGameEventPriceList, CPlayerData.m_SetGameEventPriceList); FillFloats(marketStateMessage.GeneratedGameEventPriceList, CPlayerData.m_GeneratedGameEventPriceList); FillFloats(marketStateMessage.GameEventPricePercentChangeList, CPlayerData.m_GameEventPricePercentChangeList); FillFloats(marketStateMessage.GenGradedCardPriceMultiplierList, CPlayerData.m_GenGradedCardPriceMultiplierList); FillModdedCards(marketStateMessage.ModdedCards); FillSparse(marketStateMessage.GeneratedMarketPriceList, CPlayerData.m_GeneratedMarketPriceList, modded, s_eplGenMarket); FillSparse(marketStateMessage.GeneratedCostPriceList, CPlayerData.m_GeneratedCostPriceList, modded, s_eplGenCost); FillSparse(marketStateMessage.AverageItemCostList, CPlayerData.m_AverageItemCostList, modded, s_eplAvgCost); return marketStateMessage; } public void ClientApplyState(MarketStateMessage message) { ClientApplyInner(message); } public void ClientApplyOrBuffer(MarketStateMessage message, bool inGame) { if (message == null) { throw new InvalidOperationException("Authoritative market state is missing."); } if (!inGame) { _pendingState = message; return; } if (_pendingState != null) { _pendingState = null; } ClientApplyState(message); } private void ClientApplyInner(MarketStateMessage message) { ReadPercentsInto(message.ItemPricePercentChangeList, CPlayerData.m_ItemPricePercentChangeList); ReadMarketInto(message.GenCardMarketPriceList, CPlayerData.m_GenCardMarketPriceList); ReadMarketInto(message.GenCardMarketPriceListDestiny, CPlayerData.m_GenCardMarketPriceListDestiny); ReadMarketInto(message.GenCardMarketPriceListGhost, CPlayerData.m_GenCardMarketPriceListGhost); ReadMarketInto(message.GenCardMarketPriceListGhostBlack, CPlayerData.m_GenCardMarketPriceListGhostBlack); ReadMarketInto(message.GenCardMarketPriceListMegabot, CPlayerData.m_GenCardMarketPriceListMegabot); ReadMarketInto(message.GenCardMarketPriceListFantasyRPG, CPlayerData.m_GenCardMarketPriceListFantasyRPG); ReadMarketInto(message.GenCardMarketPriceListCatJob, CPlayerData.m_GenCardMarketPriceListCatJob); ReadMarketInto(message.GenCardMarketPriceListAscension, CPlayerData.m_GenCardMarketPriceListAscension); ReadFloatsInto(message.SetGameEventPriceList, CPlayerData.m_SetGameEventPriceList); ReadFloatsInto(message.GeneratedGameEventPriceList, CPlayerData.m_GeneratedGameEventPriceList); ReadFloatsInto(message.GameEventPricePercentChangeList, CPlayerData.m_GameEventPricePercentChangeList); ReadFloatsInto(message.GenGradedCardPriceMultiplierList, CPlayerData.m_GenGradedCardPriceMultiplierList); ReadSparseFloatsInto(message.GeneratedMarketPriceList, CPlayerData.m_GeneratedMarketPriceList, ModdedGenMarket, "generated market price"); ReadSparseFloatsInto(message.GeneratedCostPriceList, CPlayerData.m_GeneratedCostPriceList, ModdedGenCost, "generated cost price"); ReadSparseFloatsInto(message.AverageItemCostList, CPlayerData.m_AverageItemCostList, ModdedAvgCost, "average item cost"); ApplyModdedCards(message.ModdedCards); } private static void FillPercents(List out_list, List list, List modded) { //IL_002d: 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) int num = VanillaWalkCount(list); List> list2 = CollectModded(modded, s_eplPctChange); for (int i = 0; i < num; i++) { if (list[i] != 0f) { out_list.Add(new MarketPercentEntry { ItemType = (EItemType)i, Percent = (short)Mathf.Clamp(Mathf.RoundToInt(list[i] * 100f), -32768, 32767) }); } } for (int j = 0; j < list2.Count; j++) { out_list.Add(new MarketPercentEntry { ItemType = (EItemType)list2[j].Key, Percent = (short)Mathf.Clamp(Mathf.RoundToInt(list2[j].Value * 100f), -32768, 32767) }); } } private static void FillSparse(List out_list, List list, List modded, PropertyInfo eplField) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) int num = VanillaWalkCount(list); List> list2 = CollectModded(modded, eplField); for (int i = 0; i < num; i++) { if (list[i] != 0f) { out_list.Add(new MarketSparseEntry { ItemType = (EItemType)i, Value = list[i] }); } } for (int j = 0; j < list2.Count; j++) { out_list.Add(new MarketSparseEntry { ItemType = (EItemType)list2[j].Key, Value = list2[j].Value }); } } private static void ReadSparseFloatsInto(List entries, List list, Action moddedWrite, string what) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected I4, but got Unknown for (int i = 0; i < entries.Count; i++) { int num = (int)entries[i].ItemType; float value = entries[i].Value; if (RoutesToEpl(num)) { moddedWrite(num, value); } else { WriteRawItem(list, num, value, what); } } } private static void ReadPercentsInto(List entries, List list) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected I4, but got Unknown for (int i = 0; i < list.Count; i++) { list[i] = 0f; } if (EplMarketBridge()) { List list2 = EplModdedItemTypes(); for (int j = 0; j < list2.Count; j++) { EplSetFloat(list2[j], s_eplPctChange, 0f); } } for (int k = 0; k < entries.Count; k++) { int itemType = (int)entries[k].ItemType; float value = (float)entries[k].Percent / 100f; if (RoutesToEpl(itemType)) { EplSetFloat(itemType, s_eplPctChange, value); } else { WriteRawItem(list, itemType, value, "item percent change"); } } } private static void FillMarket(List out_list, List list) { int num = Mathf.Min(list?.Count ?? 0, 65535); for (int i = 0; i < num; i++) { out_list.Add(new MarketCardEntry { Percent = (short)Mathf.Clamp(Mathf.RoundToInt(((list[i] != null) ? list[i].pricePercentChangeList : 0f) * 100f), -32768, 32767), GeneratedMarketPrice = ((list[i] != null) ? list[i].generatedMarketPrice : 0f) }); } } private static void ReadMarketInto(List entries, List list) { for (int i = 0; i < entries.Count; i++) { float pricePercentChangeList = (float)entries[i].Percent / 100f; float generatedMarketPrice = entries[i].GeneratedMarketPrice; MarketPrice obj = list[i]; obj.pricePercentChangeList = pricePercentChangeList; obj.generatedMarketPrice = generatedMarketPrice; } } private static void FillModdedCards(List out_list) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (s_modCardPending.Count == 0) { return; } foreach (KeyValuePair item in s_modCardPending) { PendingModCard value = item.Value; out_list.Add(new MarketModdedCardEntry { Expansion = value.Expansion, Index = value.Index, IsDestiny = value.IsDestiny, HasPercent = value.HasPercent, Percent = value.Percent, Base = value.Base, HasBase = value.HasBase }); } } private static void ApplyModdedCards(List entries) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < entries.Count; i++) { MarketModdedCardEntry marketModdedCardEntry = entries[i]; if (marketModdedCardEntry.HasPercent) { float cardPricePercentChange = CPlayerData.GetCardPricePercentChange(marketModdedCardEntry.Index, marketModdedCardEntry.Expansion, marketModdedCardEntry.IsDestiny); float num = marketModdedCardEntry.Percent - cardPricePercentChange; if (num != 0f) { CPlayerData.AddCardPricePercentChange(marketModdedCardEntry.Index, marketModdedCardEntry.Expansion, marketModdedCardEntry.IsDestiny, num); } } if (marketModdedCardEntry.HasBase) { CPlayerData.SetCardGeneratedMarketPrice(marketModdedCardEntry.Index, marketModdedCardEntry.Expansion, marketModdedCardEntry.IsDestiny, marketModdedCardEntry.Base); } } } private static void FillFloats(List out_list, List list) { int num = Mathf.Min(list?.Count ?? 0, 65535); for (int i = 0; i < num; i++) { out_list.Add(list[i]); } } private static void ReadFloatsInto(List entries, List list) { for (int i = 0; i < entries.Count; i++) { float value = entries[i]; list[i] = value; } } private static bool EplMarketBridge() { if (!s_eplProbed) { s_eplProbed = true; try { Type type = ModParity.ResolveType("EnhancedPrefabLoader.Core.EplRuntimeData", "EnhancedPrefabLoader"); Type type2 = ModParity.ResolveType("EnhancedPrefabLoader.Core.Models.SaveData.ItemSaveData", "EnhancedPrefabLoader"); s_eplAssetsProp = type?.GetProperty("Assets", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj = s_eplAssetsProp?.GetValue(null); s_eplItemLibProp = obj?.GetType().GetProperty("ItemLibrary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_eplItemDataProp = ((obj == null) ? null : s_eplItemLibProp?.GetValue(obj))?.GetType().GetProperty("ItemData", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj2 = type?.GetProperty("Services", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); s_eplSaveMgr = obj2?.GetType().GetProperty("SaveDataManager", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); if (s_eplSaveMgr != null && type2 != null) { MethodInfo[] methods = s_eplSaveMgr.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "TryGetSaveData" && methodInfo.GetGenericArguments().Length == 2) { s_eplTryGet = methodInfo.MakeGenericMethod(typeof(EItemType), type2); break; } } } s_eplGenMarket = type2?.GetProperty("GeneratedMarketPrice", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_eplGenCost = type2?.GetProperty("GeneratedCostPrice", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_eplPctChange = type2?.GetProperty("ItemPriceChangePercent", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); s_eplAvgCost = type2?.GetProperty("AverageItemCost", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch (Exception e) { Swallow.Log(e, "EplMarketBridge", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/World/WorldMarketInteraction.cs", 635); } if (s_eplTryGet == null || s_eplItemDataProp == null || s_eplGenMarket == null || s_eplGenCost == null || s_eplPctChange == null || s_eplAvgCost == null) { s_eplTryGet = null; CoopPlugin.Log.LogInfo((object)"EPL market bridge inactive (EPL absent or its internals changed) - vanilla market only"); } else { CoopPlugin.Log.LogInfo((object)"EPL market bridge active (modded item market data syncs)"); } } return s_eplTryGet != null; } private static int VanillaWalkCount(List list) { int num = list?.Count ?? 0; if (!EplMarketBridge()) { return num; } return Mathf.Min(num, EplWeaveBoundary); } private static bool RoutesToEpl(int itemType) { if (EplMarketBridge()) { return itemType >= EplWeaveBoundary; } return false; } private static void WriteRawItem(List list, int itemType, float value, string what) { if (itemType >= 0 && itemType < list.Count) { list[itemType] = value; } else if (!s_rawWriteMissLogged) { s_rawWriteMissLogged = true; CoopPlugin.Log.LogWarning((object)("[market] dropped " + what + " for item " + itemType + ": the local item price list holds " + list.Count + " rows, so the id cannot be stored (further drops this session are not logged).")); } } private static List EplModdedItemTypes() { if (s_eplModdedCache != null) { return s_eplModdedCache; } List list = new List(); if (EplMarketBridge()) { try { object value = s_eplAssetsProp.GetValue(null); object obj = ((value == null) ? null : s_eplItemLibProp.GetValue(value)); IDictionary dictionary = ((obj == null) ? null : (s_eplItemDataProp.GetValue(obj) as IDictionary)); if (dictionary != null) { foreach (object key in dictionary.Keys) { list.Add(Convert.ToInt32(key)); } } } catch (Exception e) { Swallow.Log(e, "EplModdedItemTypes", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/World/WorldMarketInteraction.cs", 732); } } s_eplModdedCache = list; return list; } private static object EplSaveData(int itemType) { object[] array = new object[2] { (object)(EItemType)itemType, null }; if (!(bool)s_eplTryGet.Invoke(s_eplSaveMgr, array)) { throw new InvalidOperationException("EPL has no market row for item " + itemType + "."); } return array[1]; } private static float EplGetFloat(int itemType, PropertyInfo field) { object obj = EplSaveData(itemType); if (obj != null) { return (float)field.GetValue(obj, null); } return 0f; } private static void EplSetFloat(int itemType, PropertyInfo field, float value) { object obj = EplSaveData(itemType); field.SetValue(obj, value, null); } private static List> CollectModded(List modded, PropertyInfo field) { List> list = new List>(modded.Count); if (field != null) { for (int i = 0; i < modded.Count; i++) { float num = EplGetFloat(modded[i], field); if (num != 0f) { list.Add(new KeyValuePair(modded[i], num)); } } } return list; } private static void ModdedGenMarket(int itemType, float v) { EplSetFloat(itemType, s_eplGenMarket, v); } private static void ModdedGenCost(int itemType, float v) { EplSetFloat(itemType, s_eplGenCost, v); } private static void ModdedAvgCost(int itemType, float v) { CPlayerData.SetAverageItemCost((EItemType)itemType, v); } } [NetworkMessage] public sealed class MarketStateMessage : WorldMessage { public List ItemPricePercentChangeList = new List(); public List GenCardMarketPriceList = new List(); public List GenCardMarketPriceListDestiny = new List(); public List GenCardMarketPriceListGhost = new List(); public List GenCardMarketPriceListGhostBlack = new List(); public List GenCardMarketPriceListMegabot = new List(); public List GenCardMarketPriceListFantasyRPG = new List(); public List GenCardMarketPriceListCatJob = new List(); public List GenCardMarketPriceListAscension = new List(); public List GenGradedCardPriceMultiplierList = new List(); public List ModdedCards = new List(); public List SetGameEventPriceList = new List(); public List GeneratedGameEventPriceList = new List(); public List GameEventPricePercentChangeList = new List(); public List GeneratedMarketPriceList = new List(); public List GeneratedCostPriceList = new List(); public List AverageItemCostList = new List(); } public sealed class MarketPercentEntry { public EItemType ItemType; public short Percent; } public sealed class MarketCardEntry { public short Percent; public float GeneratedMarketPrice; } public sealed class MarketModdedCardEntry { public ECardExpansionType Expansion; public int Index; public bool IsDestiny; public bool HasPercent; public float Percent; public float Base; public bool HasBase; } public sealed class MarketSparseEntry { public EItemType ItemType; public float Value; } public enum WorldOperationKind : ushort { Unknown = 0, BoxDestroy = 2, FurnitureBoxUp = 3, PlayerBoxPickup = 4, PlayerBoxPlacement = 5, PlayerBoxThrow = 6, ShelfAdd = 7, ShelfRemove = 8, WarehouseStore = 9, WarehouseTake = 10, Container = 11, CardDelta = 12, CardDeltaBatch = 13, GradedRemove = 14, BoxState = 15, CardDisplay = 16, Workbench = 17 } public abstract class WorldMessage : IPredictedMessage, INetMessage { public WorldOperationKind OperationKind; public string StableEntityId; public Guid PredictionId { get; set; } } public abstract class WorldControlMessage : WorldMessage { } public enum BoxNetworkKind : byte { Item = 1, Card, Furniture } public sealed class BoxCardState { public ECardExpansionType ExpansionType; public EMonsterType MonsterType; public string BorderType; public bool IsFoil; public bool IsDestiny; public bool IsChampionCard; public bool IsNew; public int CardGrade; public int GradedCardIndex; } public sealed class BoxNetworkState { public Guid BoxNetworkId; public BoxNetworkKind Kind; public Vector3 Position; public Quaternion Rotation; public EItemType ItemType; public int ItemCount; public bool IsBig; public bool IsBoxOpened; public List Cards = new List(); public EObjectType FurnitureObjectType; } [NetworkMessage] public sealed class BoxCreatedMessage : WorldMessage { public BoxNetworkState Box; public int SnapshotSlot = -1; } [NetworkMessage] public sealed class BoxDestroyRequestMessage : WorldMessage { public Guid BoxNetworkId; } [NetworkMessage] public sealed class BoxDestroyedMessage : WorldMessage { public Guid BoxNetworkId; } [NetworkMessage] public sealed class BoxBaselineCompleteMessage : WorldControlMessage { } [NetworkMessage] public sealed class BoxStateRequestMessage : WorldMessage { public Guid BoxNetworkId; public bool IsBoxOpened; public bool ContentsChanged; public EItemType ItemType; public int ItemCount; } [NetworkMessage] public sealed class BoxStateMessage : WorldMessage { public Guid BoxNetworkId; public bool IsBoxOpened; public bool ContentsChanged; public EItemType ItemType; public int ItemCount; } [NetworkMessage] public sealed class WorldBaselineStartMessage : WorldControlMessage { public long BaselineId; } [NetworkMessage] public sealed class WorldBaselineCompleteMessage : WorldControlMessage { public long BaselineId; } internal static class WorldMessageMetadata { internal const long FurnitureIdentityScope = 1L; internal static string StableEntityId(WorldMessage message) { if (message == null) { return string.Empty; } if (!string.IsNullOrEmpty(message.StableEntityId)) { return message.StableEntityId; } if (message is BoxCreatedMessage { Box: not null } boxCreatedMessage) { Guid boxNetworkId = boxCreatedMessage.Box.BoxNetworkId; return "box:" + boxNetworkId; } if (message is BoxDestroyRequestMessage boxDestroyRequestMessage) { Guid boxNetworkId = boxDestroyRequestMessage.BoxNetworkId; return "box:" + boxNetworkId; } if (message is BoxDestroyedMessage boxDestroyedMessage) { Guid boxNetworkId = boxDestroyedMessage.BoxNetworkId; return "box:" + boxNetworkId; } if (message is BoxStateRequestMessage boxStateRequestMessage) { Guid boxNetworkId = boxStateRequestMessage.BoxNetworkId; return "box:" + boxNetworkId; } if (message is BoxStateMessage boxStateMessage) { Guid boxNetworkId = boxStateMessage.BoxNetworkId; return "box:" + boxNetworkId; } if (message is PlayerBoxInteractionMessage playerBoxInteractionMessage) { Guid boxNetworkId = playerBoxInteractionMessage.BoxNetworkId; return "box:" + boxNetworkId; } if (message is CardDisplayRequestMessage cardDisplayRequestMessage) { return CardDisplayEntityId(cardDisplayRequestMessage.ShelfKey, cardDisplayRequestMessage.Compartment); } if (message is CardDisplayMessage cardDisplayMessage) { return CardDisplayEntityId(cardDisplayMessage.ShelfKey, cardDisplayMessage.Compartment); } if (message is ContainerOpMessage containerOpMessage) { return ContainerEntityId(containerOpMessage.Kind, containerOpMessage.Index); } if (message is WarehouseStoreMessage warehouseStoreMessage) { return WarehouseEntityId(warehouseStoreMessage.ShelfIndex, warehouseStoreMessage.CompartmentIndex); } if (message is WarehouseTakeMessage warehouseTakeMessage) { return WarehouseEntityId(warehouseTakeMessage.ShelfIndex, warehouseTakeMessage.CompartmentIndex); } if (message is ShelfInteractionMessage shelfInteractionMessage) { return "shelf:" + shelfInteractionMessage.ShelfKey + ":" + shelfInteractionMessage.Compartment; } if (message is FurnitureBoxUpRequestMessage furnitureBoxUpRequestMessage) { return "furniture:" + ((object)Unsafe.As(ref furnitureBoxUpRequestMessage.ObjectType)/*cast due to .constrained prefix*/).ToString() + ":" + furnitureBoxUpRequestMessage.Position.x.ToString("R", CultureInfo.InvariantCulture) + ":" + furnitureBoxUpRequestMessage.Position.y.ToString("R", CultureInfo.InvariantCulture) + ":" + furnitureBoxUpRequestMessage.Position.z.ToString("R", CultureInfo.InvariantCulture); } if (message is ContainerStateMessage) { return "container-state"; } if (message is WorkbenchStateMessage workbenchStateMessage) { return WorkbenchEntityId(workbenchStateMessage.Index); } if (message is WorkbenchOpMessage workbenchOpMessage) { return WorkbenchEntityId(workbenchOpMessage.Index); } if (message is WorkbenchBundleMessage workbenchBundleMessage) { return WorkbenchEntityId(workbenchBundleMessage.Index); } if (message is WarehouseStateMessage) { return "warehouse-state"; } if (message is MarketStateMessage) { return "market"; } return message.GetType().FullName ?? message.GetType().Name; } internal static string CardDisplayEntityId(int shelfKey, int compartment) { return "card-display:" + shelfKey + ":" + compartment; } internal static string ContainerEntityId(int kind, int index) { return "container:" + kind + ":" + index; } internal static string WorkbenchEntityId(int index) { return "workbench:" + index; } internal static string WarehouseEntityId(int shelfIndex, int compartmentIndex) { return "warehouse:" + shelfIndex + ":" + compartmentIndex; } internal static void PrepareIntent(WorldMessage message) { if (message == null) { throw new ArgumentNullException("message"); } if (message.StableEntityId == null) { message.StableEntityId = StableEntityId(message); } message.OperationKind = OperationFor(message); } internal static bool IsValidIntent(WorldMessage message) { if (message != null && message.PredictionId != Guid.Empty && message.OperationKind == OperationFor(message) && message.OperationKind != WorldOperationKind.Unknown) { return !string.IsNullOrEmpty(message.StableEntityId); } return false; } private static WorldOperationKind OperationFor(WorldMessage message) { if (message is BoxDestroyRequestMessage) { return WorldOperationKind.BoxDestroy; } if (message is BoxStateRequestMessage) { return WorldOperationKind.BoxState; } if (message is FurnitureBoxUpRequestMessage) { return WorldOperationKind.FurnitureBoxUp; } if (message is PlayerBoxPickupRequestMessage) { return WorldOperationKind.PlayerBoxPickup; } if (message is PlayerBoxPlacementRequestMessage) { return WorldOperationKind.PlayerBoxPlacement; } if (message is PlayerBoxThrowRequestMessage) { return WorldOperationKind.PlayerBoxThrow; } if (message is ShelfItemAddRequestMessage) { return WorldOperationKind.ShelfAdd; } if (message is ShelfItemRemoveRequestMessage) { return WorldOperationKind.ShelfRemove; } if (message is WarehouseStoreMessage) { return WorldOperationKind.WarehouseStore; } if (message is WarehouseTakeMessage) { return WorldOperationKind.WarehouseTake; } if (message is CardDisplayRequestMessage) { return WorldOperationKind.CardDisplay; } if (message is ContainerOpMessage) { return WorldOperationKind.Container; } if (message is CardDeltaBatchRequestMessage) { return WorldOperationKind.CardDeltaBatch; } if (message is CardDeltaRequestMessage) { return WorldOperationKind.CardDelta; } if (message is GradedRemoveRequestMessage) { return WorldOperationKind.GradedRemove; } if (message is WorkbenchOpMessage || message is WorkbenchBundleMessage) { return WorldOperationKind.Workbench; } return WorldOperationKind.Unknown; } } [NetworkMessage] public sealed class FurnitureBoxUpRequestMessage : WorldMessage { public EObjectType ObjectType; public Vector3 Position; public Guid BoxNetworkId; } [NetworkMessage] public sealed class WarehouseStateMessage : WorldMessage { public List Compartments = new List(); } public sealed class WarehouseCompartmentState { public string StableEntityId; public int ShelfIndex; public int CompartmentIndex; public List Boxes = new List(); } public sealed class WarehouseBoxState { public Guid BoxNetworkId; public EItemType ItemType; public int Amount; public bool IsBig; } [NetworkMessage] public sealed class WarehouseStoreMessage : WorldMessage { public Guid BoxNetworkId; public int ShelfIndex; public int CompartmentIndex; public EItemType ItemType; public int Amount; public bool IsBig; } [NetworkMessage] public sealed class WarehouseTakeMessage : WorldMessage { public Guid BoxNetworkId; public int ShelfIndex; public int CompartmentIndex; } [NetworkMessage] public sealed class WarehouseDeltaMessage : WorldMessage { public bool IsStore; public int ShelfIndex; public int CompartmentIndex; public Guid BoxNetworkId; public EItemType ItemType; public int Amount; public bool IsBig; } internal static class WorldPrediction { internal const string CardsScope = "world.cards"; internal const string BoxesScope = "world.boxes"; internal const string ShelvesScope = "world.shelves"; internal const string WarehouseScope = "world.warehouse"; internal const string ContainersScope = "world.containers"; internal const string WorkbenchScope = "world.workbench"; internal const string BoxStateScope = "world.boxstate"; internal const string CardDisplayScope = "world.carddisplay"; internal static Guid Predict(string key, WorldMessage intent, Action apply, Action undo) { return PredictionApi.Predict(key, delegate(Guid predictionId) { WorldClientBehaviour.SendClientIntent(intent, predictionId); }, apply, undo); } internal static Guid Predict(string key, WorldMessage intent, Action apply, Action undo, Action rejected) { return PredictionApi.Predict(key, delegate(Guid predictionId) { WorldClientBehaviour.SendClientIntent(intent, predictionId); }, apply, undo, rejected); } internal static void Ack(WorldMessage message) { PredictionApi.Ack(message?.PredictionId ?? Guid.Empty); } internal static void AckOrApply(WorldMessage message, Action apply) { PredictionApi.AckOrApply(message?.PredictionId ?? Guid.Empty, apply); } internal static void Confirm(WorldMessage message, Action apply) { PredictionApi.Confirm(message?.PredictionId ?? Guid.Empty, apply); } internal static void ApplyAuthoritative(WorldMessage message, Action apply) { PredictionApi.ApplyAuthoritative(message?.PredictionId ?? Guid.Empty, apply); } } internal sealed class WorldWorkbenchInteraction { internal const int KindWorkbench = 7; private static readonly FieldInfo FiSpawnType = ReflectionSurface.RequiredField(typeof(InteractableWorkbench), "m_CurrentItemTypeSpawn"); private static readonly FieldInfo FiIsEditingDeck = ReflectionSurface.RequiredField(typeof(InteractableWorkbench), "m_IsEditingDeck"); internal static bool ApplyingRemote; private readonly Dictionary _authorityEpochs = new Dictionary(); private readonly bool _host; private readonly Action _broadcast; private readonly Action _send; private readonly Func _inGame; private int AuthorityEpoch(InteractableWorkbench bench) { if (!((Object)(object)bench != (Object)null) || !_authorityEpochs.TryGetValue(bench, out var value)) { return 0; } return value; } private void AdvanceAuthorityEpoch(InteractableWorkbench bench) { if ((Object)(object)bench != (Object)null) { _authorityEpochs[bench] = AuthorityEpoch(bench) + 1; } } internal WorldWorkbenchInteraction(bool host, Action broadcast, Action send, Func inGame = null) { _host = host; _broadcast = broadcast; _send = send; _inGame = inGame; } internal void Reset() { ApplyingRemote = false; _authorityEpochs.Clear(); } internal void MarkAuthorityAdvanced(int index) { AdvanceAuthorityEpoch(GetBench(index)); } private bool InGame() { if (_inGame != null) { return _inGame(); } return true; } private static ShelfManager Sm() { return SceneRef.Get(); } private static IList Benches() { ShelfManager val = Sm(); if (!((Object)(object)val == (Object)null)) { return PlacementApi.GetList(val, 7); } return null; } internal static InteractableWorkbench GetBench(int index) { IList list = Benches(); if (list == null || index < 0 || index >= list.Count) { return null; } object? obj = list[index]; return (InteractableWorkbench)((obj is InteractableWorkbench) ? obj : null); } private static int IndexOf(InteractableWorkbench bench) { int num = Benches()?.IndexOf(bench) ?? (-1); if (num < 0 || num >= 250) { return -1; } return num; } private WorkbenchStateMessage BuildState(int index, InteractableWorkbench bench) { //IL_0027: 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_0076: Unknown result type (might be due to invalid IL or missing references) WorkbenchStateMessage workbenchStateMessage = new WorkbenchStateMessage { Index = (byte)index, StableEntityId = WorldMessageMetadata.WorkbenchEntityId(index), Bundling = IsBundling(bench), SpawnItemType = GetSpawnType(bench) }; List list = bench?.m_StoredItemList; int num = Mathf.Min(list?.Count ?? 0, 64); for (int i = 0; i < num; i++) { workbenchStateMessage.StoredTypes.Add((EItemType)(((Object)(object)list[i] != (Object)null) ? ((int)list[i].GetItemType()) : (-1))); } return workbenchStateMessage; } private static EItemType GetSpawnType(InteractableWorkbench bench) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bench != (Object)null) { object obj = FiSpawnType?.GetValue(bench); if (obj is EItemType) { return (EItemType)obj; } } return (EItemType)(-1); } private static bool IsBundling(InteractableWorkbench bench) { if ((Object)(object)bench != (Object)null && (Object)(object)bench.m_JankBoxAnim != (Object)null) { return ((Component)bench.m_JankBoxAnim).gameObject.activeSelf; } return false; } internal void AppendBaselineMessages(Action append) { if (!_host || append == null || !InGame()) { return; } IList list = Benches(); int num = 0; while (list != null && num < list.Count && num < 250) { object? obj = list[num]; InteractableWorkbench val = (InteractableWorkbench)((obj is InteractableWorkbench) ? obj : null); if (val != null) { append((INetMessage)(object)BuildState(num, val)); } num++; } } internal void HostStorageChanged(InteractableWorkbench bench) { if (!ApplyingRemote) { HostPublish(bench); } } internal void HostBundleStarted(InteractableWorkbench bench) { if (!((Object)(object)bench == (Object)null)) { HostPublish(bench); } } internal void HostBundleCompleted(InteractableWorkbench bench) { if (!((Object)(object)bench == (Object)null)) { HostPublish(bench); } } private void HostPublish(InteractableWorkbench bench) { if (_host && !((Object)(object)bench == (Object)null) && _broadcast != null && InGame()) { int num = IndexOf(bench); if (num >= 0) { _broadcast((INetMessage)(object)BuildState(num, bench)); } } } internal bool HostApplyOp(WorkbenchOpMessage message) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0370: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) if (!_host || message == null) { return false; } InteractableWorkbench bench = GetBench(message.Index); if ((Object)(object)bench == (Object)null) { return false; } int num = bench.m_PosList?.Count ?? 0; if (num <= 0) { return false; } if ((int)message.SpawnItemType != -1 && !IsRealItemType(message.SpawnItemType)) { CoopPlugin.Log.LogWarning((object)("[workbench] rejected op with an invalid spawn type " + ((object)Unsafe.As(ref message.SpawnItemType)/*cast due to .constrained prefix*/).ToString() + " on bench " + message.Index + ".")); return false; } List list = TypesOf(bench); List removedTypes = message.RemovedTypes; if (removedTypes != null && removedTypes.Count > num) { CoopPlugin.Log.LogWarning((object)("[workbench] rejected op removing " + removedTypes.Count + " types from a " + num + "-slot bench " + message.Index + ".")); return false; } int num2 = 0; while (removedTypes != null && num2 < removedTypes.Count) { if (!IsRealItemType(removedTypes[num2])) { CoopPlugin.Log.LogWarning((object)("[workbench] rejected op with an invalid removed type " + ((object)removedTypes[num2]/*cast due to .constrained prefix*/).ToString() + " on bench " + message.Index + ".")); return false; } int num3 = list.IndexOf(removedTypes[num2]); if (num3 >= 0) { list.RemoveAt(num3); } num2++; } List addedTypes = message.AddedTypes; if (addedTypes != null && addedTypes.Count > num) { CoopPlugin.Log.LogWarning((object)("[workbench] rejected op adding " + addedTypes.Count + " types to a " + num + "-slot bench " + message.Index + ".")); return false; } if (list.Count + (addedTypes?.Count ?? 0) > num) { CoopPlugin.Log.LogWarning((object)("[workbench] rejected op that would overfill bench " + message.Index + " (" + list.Count + "+" + (addedTypes?.Count ?? 0) + " > " + num + ").")); return false; } int num4 = 0; while (addedTypes != null && num4 < addedTypes.Count) { if (!IsRealItemType(addedTypes[num4])) { CoopPlugin.Log.LogWarning((object)("[workbench] rejected op with an invalid added type " + ((object)addedTypes[num4]/*cast due to .constrained prefix*/).ToString() + " on bench " + message.Index + ".")); return false; } list.Insert(0, addedTypes[num4]); num4++; } SyncBench(bench, list, message.SpawnItemType, message.Bundling); WorkbenchStateMessage workbenchStateMessage = BuildState(message.Index, bench); workbenchStateMessage.PredictionId = message.PredictionId; _broadcast?.Invoke((INetMessage)(object)workbenchStateMessage); return true; } private static bool IsRealItemType(EItemType type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) if ((int)type != -1) { return Enum.IsDefined(typeof(EItemType), type); } return false; } internal bool HostCompleteBundle(int index) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 if (!_host) { return false; } InteractableWorkbench bench = GetBench(index); if ((Object)(object)bench == (Object)null) { return false; } if ((int)GetSpawnType(bench) == -1) { return false; } HideBundlingAnimation(bench); _broadcast?.Invoke((INetMessage)(object)BuildState(index, bench)); return true; } internal void ClientStorageChanged(InteractableWorkbench bench, List before) { if (!ApplyingRemote) { ClientPublish(bench, before, force: false); } } internal void ClientBundleStarted(InteractableWorkbench bench) { if (!((Object)(object)bench == (Object)null)) { ClientPublish(bench, TypesOf(bench), force: true); } } internal void ClientCompleteBundle(InteractableWorkbench bench) { if (!_host && !((Object)(object)bench == (Object)null)) { HideBundlingAnimation(bench); int num = IndexOf(bench); if (num >= 0) { _send?.Invoke(1, (INetMessage)(object)new WorkbenchBundleMessage { StableEntityId = WorldMessageMetadata.WorkbenchEntityId(num), Index = (byte)num }); } } } private void ClientPublish(InteractableWorkbench bench, List before, bool force) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) if (_host || (Object)(object)bench == (Object)null || _send == null) { return; } int num = IndexOf(bench); if (num < 0) { return; } List after = TypesOf(bench); List list = new List(); List list2 = new List(); DiffTypes(before, after, list, list2); if (!force && list.Count == 0 && list2.Count == 0) { return; } WorldWorkbenchInteraction self = this; int epoch = AuthorityEpoch(bench); WorldPrediction.Predict("world.workbench", new WorkbenchOpMessage { Index = (byte)num, AddedTypes = list, RemovedTypes = list2, SpawnItemType = GetSpawnType(bench), Bundling = IsBundling(bench) }, delegate { self.ReplayItems(bench, after); }, delegate { if (self.AuthorityEpoch(bench) == epoch) { self.ReplayItems(bench, before); } }); } private void ReplayItems(InteractableWorkbench bench, List types) { ApplyingRemote = true; try { SyncBenchItems(bench, types); } finally { ApplyingRemote = false; } } internal void ClientApplyState(WorkbenchStateMessage message) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (message != null) { InteractableWorkbench bench = GetBench(message.Index); if (!((Object)(object)bench == (Object)null)) { SyncBench(bench, message.StoredTypes, message.SpawnItemType, message.Bundling); } } } internal WorkbenchStateMessage BuildRefresh(int index) { if (!_host) { return null; } InteractableWorkbench bench = GetBench(index); if (!((Object)(object)bench == (Object)null)) { return BuildState(index, bench); } return null; } private void SyncBench(InteractableWorkbench bench, List types, EItemType spawnType, bool bundling) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bench == (Object)null) { return; } ApplyingRemote = true; try { SyncBenchItems(bench, types); if ((int)spawnType != -1) { FiSpawnType.SetValue(bench, spawnType); } if (bundling) { ShowBundlingAnimation(bench, spawnType); } else { HideBundlingAnimation(bench); } } finally { ApplyingRemote = false; } } private static void SyncBenchItems(InteractableWorkbench bench, List types) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) List storedItemList = bench.m_StoredItemList; if (storedItemList == null || types == null) { return; } bool flag = storedItemList.Count == types.Count; int num = 0; while (flag && num < types.Count) { flag = (Object)(object)storedItemList[num] != (Object)null && storedItemList[num].GetItemType() == types[num]; num++; } if (flag) { return; } int num2; if (FiIsEditingDeck != null) { object value = FiIsEditingDeck.GetValue(bench); num2 = ((value is bool && (bool)value) ? 1 : 0); } else { num2 = 0; } bool flag2 = (byte)num2 != 0; if (flag2) { bench.SetIsEditingDeck(false); } try { Dictionary dictionary = Tally(types); List list = new List(); for (int num3 = storedItemList.Count - 1; num3 >= 0; num3--) { Item val = storedItemList[num3]; EItemType key = (EItemType)((!((Object)(object)val != (Object)null)) ? (-1) : ((int)val.GetItemType())); if (dictionary.TryGetValue(key, out var value2) && value2 > 0) { dictionary[key] = value2 - 1; } else { list.Add(val); } } for (int i = 0; i < list.Count; i++) { Item val2 = list[i]; if (!((Object)(object)val2 == (Object)null)) { bench.RemoveItem(val2); val2.DisableItem(); } } Dictionary dictionary2 = TallyItems(storedItemList); for (int num4 = types.Count - 1; num4 >= 0; num4--) { if (dictionary2.TryGetValue(types[num4], out var value3) && value3 > 0) { dictionary2[types[num4]] = value3 - 1; } else { AddBenchItem(bench, types[num4]); } } } finally { if (flag2) { bench.SetIsEditingDeck(true); } } } private static void AddBenchItem(InteractableWorkbench bench, EItemType type) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) int itemCount = bench.GetItemCount(); Transform parent = ((bench.m_PosList != null && itemCount >= 0 && itemCount < bench.m_PosList.Count) ? bench.m_PosList[itemCount] : ((Component)bench).transform); bench.AddItem(SpawnItem(type, parent), true); } internal static List TypesOf(InteractableWorkbench bench) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = bench?.m_StoredItemList; if (list2 == null) { return list; } for (int i = 0; i < list2.Count; i++) { list.Add((EItemType)(((Object)(object)list2[i] != (Object)null) ? ((int)list2[i].GetItemType()) : (-1))); } return list; } private static void DiffTypes(List before, List after, List added, List removed) { //IL_000e: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = Tally(before); int num = 0; while (after != null && num < after.Count) { if (dictionary.TryGetValue(after[num], out var value) && value > 0) { dictionary[after[num]] = value - 1; } else { added.Add(after[num]); } num++; } dictionary = Tally(after); int num2 = 0; while (before != null && num2 < before.Count) { if (dictionary.TryGetValue(before[num2], out var value2) && value2 > 0) { dictionary[before[num2]] = value2 - 1; } else { removed.Add(before[num2]); } num2++; } } private static Dictionary Tally(List types) { //IL_000d: 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) Dictionary dictionary = new Dictionary(); int num = 0; while (types != null && num < types.Count) { dictionary.TryGetValue(types[num], out var value); dictionary[types[num]] = value + 1; num++; } return dictionary; } private static Dictionary TallyItems(List items) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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) Dictionary dictionary = new Dictionary(); int num = 0; while (items != null && num < items.Count) { EItemType key = (EItemType)(((Object)(object)items[num] != (Object)null) ? ((int)items[num].GetItemType()) : (-1)); dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; num++; } return dictionary; } private static void ShowBundlingAnimation(InteractableWorkbench bench, EItemType spawnType) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)bench == (Object)null) && (int)spawnType != -1) { ItemMeshData itemMeshData = InventoryBase.GetItemMeshData(spawnType); if ((Object)(object)bench.m_JankBoxSkinMesh != (Object)null) { ((Renderer)bench.m_JankBoxSkinMesh).material = itemMeshData.material; } if ((Object)(object)bench.m_JankBoxAnim != (Object)null) { ((Component)bench.m_JankBoxAnim).gameObject.SetActive(true); bench.m_JankBoxAnim.SetBool("IsClosing", true); } } } private static void HideBundlingAnimation(InteractableWorkbench bench) { if ((Object)(object)bench == (Object)null) { return; } if ((Object)(object)bench.m_JankBoxAnim != (Object)null) { ((Component)bench.m_JankBoxAnim).gameObject.SetActive(false); bench.m_JankBoxAnim.SetBool("IsClosing", false); } List cardEnterBoxAnimList = bench.m_CardEnterBoxAnimList; List interactableCard3dList = bench.m_InteractableCard3dList; int num = Mathf.Min(cardEnterBoxAnimList?.Count ?? 0, interactableCard3dList?.Count ?? 0); for (int i = 0; i < num; i++) { InteractableCard3d val = interactableCard3dList[i]; if ((Object)(object)val?.m_Card3dUI != (Object)null) { val.m_Card3dUI.SetVisibility(false); } if ((Object)(object)cardEnterBoxAnimList[i] != (Object)null) { ((Component)cardEnterBoxAnimList[i]).gameObject.SetActive(false); } } } internal static Item SpawnItem(EItemType itemType, Transform parent) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) ItemMeshData itemMeshData = InventoryBase.GetItemMeshData(itemType); Item item = ItemSpawnManager.GetItem(parent); item.SetMesh(itemMeshData.mesh, itemMeshData.material, itemType, itemMeshData.meshSecondary, itemMeshData.materialSecondary, (List)null); ((Component)item).transform.localPosition = Vector3.zero; ((Component)item).transform.localRotation = Quaternion.identity; ((Component)item).gameObject.SetActive(true); return item; } } [NetworkMessage] public class WorkbenchStateMessage : WorldMessage { public byte Index; public List StoredTypes = new List(); public EItemType SpawnItemType; public bool Bundling; } [NetworkMessage] public sealed class WorkbenchOpMessage : WorldMessage { public byte Index; public List AddedTypes = new List(); public List RemovedTypes = new List(); public EItemType SpawnItemType; public bool Bundling; } [NetworkMessage] public sealed class WorkbenchBundleMessage : WorldMessage { public byte Index; } } namespace CardShopCoop.Modules.Tutorial { [ClientBehaviour] public sealed class TutorialClientBehaviour : CoopBehaviour { private readonly struct PendingTutorialDelta { internal int Condition { get; } internal float Value { get; } internal int TutorialIndex { get; } internal PendingTutorialDelta(int condition, float value, int tutorialIndex) { Condition = condition; Value = value; TutorialIndex = tutorialIndex; } } [HarmonyPatch(typeof(TutorialManager), "AddTaskValue")] private static class CreditPatch { [HarmonyPrefix] private static void Prefix(ETutorialTaskCondition tutorialTaskCondition, float valueAdd, out TutorialStateMessage __state) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!_applyingRemote && !((Object)(object)_active == (Object)null) && _active._joined && _active._context != null && _active._context.InGame() && !IsHostDerived(tutorialTaskCondition)) { __state = CaptureState(); } } [HarmonyPostfix] private static void Postfix(ETutorialTaskCondition tutorialTaskCondition, float valueAdd, TutorialStateMessage __state) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (__state != null) { ForwardAction(tutorialTaskCondition, valueAdd, __state); } } } [HarmonyPatch(typeof(TutorialManager), "Awake")] private static class TutorialReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.TryApplyPending(); } } private static TutorialClientBehaviour _active; private static bool _applyingRemote; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private TutorialStateMessage _pendingState; private readonly SortedDictionary _pendingDeltas = new SortedDictionary(); private void OnEnable() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; bool flag2 = false; try { ResetSessionState(); TutorialInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; flag2 = true; _harmony = new Harmony("dev.meepen.cardshopcoop.tutorial.client"); _harmony.CreateClassProcessor(typeof(CreditPatch)).Patch(); _harmony.CreateClassProcessor(typeof(TutorialReadyPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Tutorial client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag2) { CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; } if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } ResetSessionState(); TutorialInterop.Reset(); _context = null; throw; } } [MessageHandler(typeof(TutorialStateMessage))] private void HandleState(MessageContext context, TutorialStateMessage message) { if (!_shutdown) { _pendingState = message; _pendingDeltas.Clear(); TryApplyPending(); } } [MessageHandler(typeof(TutorialDeltaMessage))] private void HandleDelta(MessageContext context, TutorialDeltaMessage message) { if (_shutdown) { return; } if (message.Values != null) { PredictionApi.Confirm(message.PredictionId, delegate { ApplyDelta(message); }); } else { PredictionApi.Confirm(message.PredictionId, delegate { ApplyDelta(message); }); } } private void TryApplyPending() { if (_shutdown || _context == null || !_context.InGame()) { return; } if (_pendingState != null) { TutorialStateMessage pendingState = _pendingState; if (!TryApply(pendingState)) { return; } CoopPlugin.Log.LogDebug((object)("tutorial baseline applied: index=" + pendingState.TutorialIndex + " values=" + ((pendingState.Values != null) ? pendingState.Values.Count : 0))); _pendingState = null; } TryApplyPendingDelta(); } private void TryApplyPendingDelta() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (_pendingDeltas.Count != 0 && TutorialInterop.IsSceneReady(TutorialInterop.FindManager()) && CPlayerData.m_TutorialDataList != null) { List list = new List(_pendingDeltas.Values); _pendingDeltas.Clear(); for (int i = 0; i < list.Count; i++) { PendingTutorialDelta pendingTutorialDelta = list[i]; ETutorialTaskCondition condition = (ETutorialTaskCondition)pendingTutorialDelta.Condition; ApplyLocalAction(condition, pendingTutorialDelta.Value - TutorialInterop.ValueFor(condition)); CPlayerData.m_TutorialIndex = pendingTutorialDelta.TutorialIndex; } } } private static void ForwardAction(ETutorialTaskCondition condition, float increment, TutorialStateMessage previous) { //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) TutorialClientBehaviour client = _active; if (!((Object)(object)client == (Object)null) && !client._shutdown && client._joined && client._context != null && client._context.InGame()) { PredictionApi.Predict("tutorial", delegate(Guid predictionId) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected I4, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected I4, but got Unknown client._context.Send(1, (INetMessage)(object)new TutorialActionDeltaMessage { PredictionId = predictionId, ExpectedTutorialIndex = previous.TutorialIndex, ExpectedCondition = (int)condition, Action = (int)condition, Increment = increment }); }, delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ApplyLocalAction(condition, increment); }, delegate { TryApply(previous); }); } } private static TutorialStateMessage CaptureState() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected I4, but got Unknown TutorialStateMessage tutorialStateMessage = new TutorialStateMessage { TutorialIndex = CPlayerData.m_TutorialIndex }; List tutorialDataList = CPlayerData.m_TutorialDataList; int num = 0; while (tutorialDataList != null && num < tutorialDataList.Count) { tutorialStateMessage.Values.Add(new TutorialValue { Condition = (int)tutorialDataList[num].tutorialTaskCondition, Value = tutorialDataList[num].value }); num++; } return tutorialStateMessage; } private static void ApplyLocalAction(ETutorialTaskCondition condition, float increment) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) _applyingRemote = true; try { TutorialManager.AddTaskValue(condition, increment); } finally { _applyingRemote = false; } } private static bool TryApply(TutorialStateMessage message) { TutorialManager manager = TutorialInterop.FindManager(); if (!TutorialInterop.IsSceneReady(manager) || CPlayerData.m_TutorialDataList == null) { return false; } _applyingRemote = true; try { ApplyInner(manager, message); } finally { _applyingRemote = false; } return true; } private void ApplyDelta(TutorialDeltaMessage message) { if (message.Values != null) { _pendingState = new TutorialStateMessage { TutorialIndex = message.TutorialIndex, Values = message.Values }; _pendingDeltas.Clear(); TryApplyPending(); } else { _pendingDeltas[message.Condition] = new PendingTutorialDelta(message.Condition, message.Value, message.TutorialIndex); TryApplyPending(); } } private static void ApplyInner(TutorialManager manager, TutorialStateMessage message) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) int count = message.Values.Count; List tutorialDataList = CPlayerData.m_TutorialDataList; bool num = message.TutorialIndex != 0 && CPlayerData.m_TutorialIndex == 0; TutorialInterop.SyncMarker(manager, message.TutorialIndex); if (num) { TutorialInterop.ClearIntroPresentation(); } List list = new List(count); for (int i = 0; i < count; i++) { TutorialValue tutorialValue = message.Values[i]; list.Add(new TutorialData { tutorialTaskCondition = (ETutorialTaskCondition)tutorialValue.Condition, value = tutorialValue.Value }); } tutorialDataList.Clear(); tutorialDataList.AddRange(list); CPlayerData.m_TutorialIndex = message.TutorialIndex; TutorialInterop.SetFinished(manager, finished: false); for (int j = 0; j < manager.m_TutorialSubGroupList.Count; j++) { TutorialSubGroup val = manager.m_TutorialSubGroupList[j]; if (TutorialInterop.FiSubgroupCurrent != null) { TutorialInterop.FiSubgroupCurrent.SetValue(val, 0f); } if (TutorialInterop.FiSubgroupFinished != null) { TutorialInterop.FiSubgroupFinished.SetValue(val, false); } val.m_TutorialData.value = 0f; for (int k = 0; k < list.Count; k++) { val.AddTaskValue(list[k].value, list[k].tutorialTaskCondition); } if (TutorialInterop.FiSubgroupFinished != null && j < message.TutorialIndex - 1) { TutorialInterop.FiSubgroupFinished.SetValue(val, true); } } if (message.TutorialIndex == 0) { for (int l = 0; l < manager.m_TutorialSubGroupList.Count; l++) { manager.m_TutorialSubGroupList[l].CloseScreen(); } TutorialInterop.SyncMarker(manager, 0); } else { manager.EvaluateTaskVisibility(); } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { TutorialInterop.Reset(); TryApplyPending(); } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; TryApplyPending(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ResetSessionState(); } } private void ResetSessionState() { _joined = false; _pendingState = null; _pendingDeltas.Clear(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } ResetSessionState(); TutorialInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } private static bool IsHostDerived(ETutorialTaskCondition condition) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected I4, but got Unknown if ((int)condition != 1) { switch (condition - 6) { case 0: case 1: case 4: case 7: case 8: break; default: return false; } } return true; } } [ServerBehaviour] public sealed class TutorialHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(TutorialManager), "AddTaskValue")] private static class TutorialVisibilityPatch { [HarmonyPrefix] private static void Prefix(out int __state) { __state = CPlayerData.m_TutorialIndex; } [HarmonyPostfix] private static void Postfix(ETutorialTaskCondition tutorialTaskCondition, int __state) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(BuildDelta(Guid.Empty, tutorialTaskCondition, __state)); } } } private const float MaxActionIncrement = 100f; private const int MaxStateValues = 4096; private static TutorialHostBehaviour _active; private readonly HashSet _fullyJoined = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _applyingIntent; private void OnEnable() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { TutorialInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.tutorial.host"); _harmony.CreateClassProcessor(typeof(TutorialVisibilityPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Tutorial host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } TutorialInterop.Reset(); _context = null; throw; } } [OnFullyJoined] private void SendInitialState(PeerConnection connection) { if (connection != null) { _fullyJoined.Add(connection.Id); SendState(connection.Id); } } [OnClientDisconnected] private void ForgetPeer(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _fullyJoined.Remove(connection.Id); } } [MessageHandler(typeof(TutorialActionDeltaMessage))] private void HandleAction(MessageContext context, TutorialActionDeltaMessage message) { if (!IsPeerMessage(context) || message == null || !ValidateAction(message)) { if (message != null) { CoopPlugin.Log.LogDebug((object)("tutorial intent rejected from peer " + (context?.ConnectionId ?? 0) + ": expected index=" + message.ExpectedTutorialIndex + " condition=" + message.ExpectedCondition + " action=" + message.Action + " increment=" + message.Increment)); } if (message != null && message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); } return; } try { _applyingIntent = true; try { TutorialManager.AddTaskValue((ETutorialTaskCondition)message.Action, message.Increment); } finally { _applyingIntent = false; } BroadcastDelta(BuildDelta(message.PredictionId, (ETutorialTaskCondition)message.Action, message.ExpectedTutorialIndex)); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Tutorial action failed for peer " + context.Connection.Id + ": " + ex)); if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); } } } private bool ValidateAction(TutorialActionDeltaMessage message) { if (!Enum.IsDefined(typeof(ETutorialTaskCondition), message.Action) || message.Action == 0) { return false; } if (message.ExpectedCondition != message.Action) { return false; } if (message.Increment <= 0f || float.IsNaN(message.Increment) || float.IsInfinity(message.Increment) || message.Increment > 100f) { return false; } return true; } private void BroadcastDelta(TutorialDeltaMessage message) { if (!_shutdown && _context != null && _context.InGame() && message != null) { _context.Broadcast((INetMessage)(object)message); } } private void SendState(int peer) { if (!_shutdown && _context != null && _context.InGame() && TryBuildState(out var message)) { _context.Send(peer, (INetMessage)(object)message); } } private bool TryBuildState(out TutorialStateMessage message) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected I4, but got Unknown message = null; if (_context == null || !_context.InGame() || !TutorialInterop.IsSceneReady(TutorialInterop.FindManager()) || CPlayerData.m_TutorialDataList == null || CPlayerData.m_TutorialDataList.Count > 4096) { return false; } List tutorialDataList = CPlayerData.m_TutorialDataList; message = new TutorialStateMessage { Full = true, TutorialIndex = CPlayerData.m_TutorialIndex }; for (int i = 0; i < tutorialDataList.Count; i++) { TutorialData val = tutorialDataList[i]; if (val == null || !TutorialInterop.IsFiniteNonNegative(val.value)) { return false; } message.Values.Add(new TutorialValue { Condition = (int)val.tutorialTaskCondition, Value = val.value }); } return true; } private static TutorialDeltaMessage BuildDelta(Guid predictionId, ETutorialTaskCondition condition, int previousIndex) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected I4, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected I4, but got Unknown TutorialDeltaMessage tutorialDeltaMessage = new TutorialDeltaMessage { PredictionId = predictionId, TutorialIndex = CPlayerData.m_TutorialIndex, Condition = (int)condition, Value = TutorialInterop.ValueFor(condition) }; if (previousIndex != tutorialDeltaMessage.TutorialIndex) { tutorialDeltaMessage.Values = new List(); List tutorialDataList = CPlayerData.m_TutorialDataList; int num = 0; while (tutorialDataList != null && num < tutorialDataList.Count) { tutorialDeltaMessage.Values.Add(new TutorialValue { Condition = (int)tutorialDataList[num].tutorialTaskCondition, Value = tutorialDataList[num].value }); num++; } } return tutorialDeltaMessage; } private bool IsPeerMessage(MessageContext context) { if (!_shutdown && context?.Connection != null) { return _fullyJoined.Contains(context.Connection.Id); } return false; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { TutorialInterop.Reset(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _fullyJoined.Clear(); TutorialInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class TutorialInterop { internal static readonly FieldInfo FiSubgroupCurrent = AccessTools.Field(typeof(TutorialSubGroup), "m_CurrentValue"); internal static readonly FieldInfo FiSubgroupFinished = AccessTools.Field(typeof(TutorialSubGroup), "m_IsTaskFinish"); internal static readonly FieldInfo FiTutorialIndicator = AccessTools.Field(typeof(TutorialManager), "m_TutorialTargetIndicator"); internal static readonly FieldInfo FiTutorialFinished = AccessTools.Field(typeof(TutorialManager), "m_FinishedTutorial"); private static InteractionPlayerController _controller; private static GameUIScreen _gameUi; internal static void Reset() { _controller = null; _gameUi = null; } internal static T FindSceneObject() where T : Component { //IL_002f: 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) T[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null) { Scene scene = ((Component)array[i]).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return array[i]; } } } return default(T); } internal static TutorialManager FindManager() { return SceneRef.Get(); } internal static bool IsSceneReady(TutorialManager manager) { if ((Object)(object)manager == (Object)null || manager.m_TutorialSubGroupList == null || (Object)(object)manager.m_TutorialTargetIndicator == (Object)null) { return false; } for (int i = 0; i < manager.m_TutorialSubGroupList.Count; i++) { TutorialSubGroup val = manager.m_TutorialSubGroupList[i]; if ((Object)(object)val == (Object)null || val.m_TutorialData == null || (Object)(object)val.m_ScreenGrp == (Object)null) { return false; } } return true; } internal static float ValueFor(ETutorialTaskCondition condition) { //IL_001a: 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) List tutorialDataList = CPlayerData.m_TutorialDataList; int num = 0; while (tutorialDataList != null && num < tutorialDataList.Count) { if (tutorialDataList[num] != null && tutorialDataList[num].tutorialTaskCondition == condition) { return tutorialDataList[num].value; } num++; } return 0f; } internal static void SyncMarker(TutorialManager manager, int tutorialIndex) { if (!((Object)(object)manager == (Object)null) && !(FiTutorialIndicator == null)) { object? value = FiTutorialIndicator.GetValue(manager); GameObject val = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val != (Object)null) { val.SetActive(tutorialIndex == 0); } } } internal static void SetFinished(TutorialManager manager, bool finished) { if (!((Object)(object)manager == (Object)null) && !(FiTutorialFinished == null)) { FiTutorialFinished.SetValue(manager, finished); } } internal static void ClearIntroPresentation() { if ((Object)(object)_controller == (Object)null) { _controller = TutorialInterop.FindSceneObject(); } if ((Object)(object)_controller != (Object)null) { EGameAction[] array = new EGameAction[7]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); EGameAction[] array2 = (EGameAction[])(object)array; for (int i = 0; i < array2.Length; i++) { InteractionPlayerController.RemoveToolTip(array2[i]); } } if ((Object)(object)_gameUi == (Object)null) { _gameUi = TutorialInterop.FindSceneObject(); } if ((Object)(object)_gameUi != (Object)null) { GameUIScreen.SetGameUIVisible(true); } } internal static bool IsFiniteNonNegative(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return value >= 0f; } return false; } } [NetworkMessage] public sealed class TutorialStateMessage : INetMessage { public bool Full = true; public int TutorialIndex; public List Values = new List(); } public sealed class TutorialValue { public int Condition; public float Value; } [NetworkMessage] public sealed class TutorialActionDeltaMessage : IPredictedMessage, INetMessage { public int ExpectedTutorialIndex; public int ExpectedCondition; public int Action; public float Increment; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class TutorialDeltaMessage : IPredictedMessage, INetMessage { public int TutorialIndex; public int Condition; public float Value; public List Values; public Guid PredictionId { get; set; } } } namespace CardShopCoop.Modules.Trade { [ClientBehaviour] public sealed class TradeClientBehaviour : CoopBehaviour { private sealed class LocalOffer { public TradeOfferState State; public Customer Carrier; public Renderer[] MaskedRenderers; public bool[] RendererStates; public bool RemovedByHost; } private sealed class PredictionFrame { public TradeIntentOperation Operation; public TradeOfferState State; public bool ClaimAccepted; public bool TerminalSent; public bool ThinkingSent; public bool AcceptedShown; public bool ScreenOpen; public AcceptEffect Effect; } private sealed class IntentCapture { public TradeIntentOperation Operation; public float Price; public PredictionFrame Frame; public TradeOfferState State; public CustomerTradeData AcceptBefore; public IDisposable CardForwarding; public bool EconomyScoped; } private sealed class AcceptEffect { public CardData Received; public CardData Given; public float CoinSpent; } private sealed class AcceptResult { public TradeOutcome Outcome; public TradeOfferState State; } [HarmonyPatch(typeof(Customer), "OnMousePress")] private static class CustomerPressPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { TradeClientBehaviour active = _active; if ((Object)(object)active == (Object)null) { return; } if (!active.IsCarrier(__instance, out var offer)) { if (active._carrierKeysByCustomer.Count > 0) { CoopPlugin.Log.LogInfo((object)("[trade] click on customer " + (((Object)(object)__instance == (Object)null) ? "" : ((Object)__instance).name) + " that is not a trade carrier (carriers=" + active._carrierKeysByCustomer.Count + ").")); } } else if (offer.State != null && active._pendingCounter < 0) { CoopPlugin.Log.LogInfo((object)("[trade] carrier clicked id=" + offer.State.Counter + "; starting session.")); active.BeginSession(offer); } } } [HarmonyPatch(typeof(Customer), "OnPressStopInteract")] private static class CustomerStopPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { TradeClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active.IsCarrier(__instance, out var _) && active._pendingCounter >= 0) { active._stopHandled = true; TradeInterop.RestorePlayerUi(); active.EndLocalSession(!active._terminalSent && !active._thinkingSent && !active._awaitingPrediction); } } } [HarmonyPatch(typeof(CustomerTradeCardScreen), "OnPressAccept")] private static class AcceptPatch { [HarmonyPrefix] private static void Prefix(CustomerTradeCardScreen __instance, out IntentCapture __state) { __state = null; TradeClientBehaviour active = _active; LocalOffer localOffer = active?.PendingOffer(); if (!((Object)(object)active == (Object)null) && localOffer != null && TradeInterop.CurrentCustomer(__instance) == localOffer.Carrier && !TradeInterop.HasAccepted(__instance) && !active._awaitingPrediction && active._claimAccepted) { if (!TradeInterop.IsTrading(__instance) && (Object)(object)__instance.m_SetPriceInput != (Object)null && !string.IsNullOrWhiteSpace(__instance.m_SetPriceInput.text)) { __instance.OnInputTextUpdated(__instance.m_SetPriceInput.text); } __state = active.BeginIntent(TradeIntentOperation.Accept, TradeInterop.IsTrading(__instance) ? 0f : TradeInterop.PriceSet(__instance), TradeInterop.Capture(__instance)); if (__state != null) { __state.EconomyScoped = true; EconomyActionScope.Enter(); __state.CardForwarding = WorldCardInteraction.SuppressCardForwarding(); } } } [HarmonyPostfix] private static void Postfix(IntentCapture __state) { _active?.EndIntent(__state); } [HarmonyFinalizer] private static void Finalizer(IntentCapture __state) { if (__state != null) { __state.CardForwarding?.Dispose(); __state.CardForwarding = null; if (__state.EconomyScoped) { __state.EconomyScoped = false; EconomyActionScope.Exit(); } } } } [HarmonyPatch(typeof(CustomerTradeCardScreen), "OnPressDecline")] private static class DeclinePatch { [HarmonyPrefix] private static void Prefix(CustomerTradeCardScreen __instance, out IntentCapture __state) { __state = null; TradeClientBehaviour active = _active; LocalOffer localOffer = active?.PendingOffer(); if (!((Object)(object)active == (Object)null) && localOffer != null && TradeInterop.CurrentCustomer(__instance) == localOffer.Carrier && active._claimAccepted && !active._awaitingPrediction && !active._terminalSent) { __state = active.BeginIntent(TradeIntentOperation.Decline, 0f); } } [HarmonyPostfix] private static void Postfix(IntentCapture __state) { _active?.EndIntent(__state); } } [HarmonyPatch(typeof(CustomerTradeCardScreen), "OnPressLetMeThink")] private static class ThinkPatch { [HarmonyPrefix] private static void Prefix(CustomerTradeCardScreen __instance, out IntentCapture __state) { __state = null; TradeClientBehaviour active = _active; LocalOffer localOffer = active?.PendingOffer(); if (!((Object)(object)active == (Object)null) && localOffer != null && TradeInterop.CurrentCustomer(__instance) == localOffer.Carrier && !active._awaitingPrediction && active._claimAccepted && !active._thinkingSent) { float price = (TradeInterop.IsTrading(__instance) ? 0f : TradeInterop.PriceSet(__instance)); __state = active.BeginIntent(TradeIntentOperation.Think, price); } } [HarmonyPostfix] private static void Postfix(IntentCapture __state) { _active?.EndIntent(__state); } } [HarmonyPatch(typeof(CustomerTradeCardScreen), "OnCloseScreen")] private static class ScreenClosePatch { [HarmonyPostfix] private static void Postfix(CustomerTradeCardScreen __instance) { TradeClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._ignoreScreenClose && active._pendingCounter >= 0 && !TradeInterop.IsScreenOpen(__instance) && !active._stopHandled) { active.EndLocalSession(!active._terminalSent && !active._thinkingSent && !active._awaitingPrediction); } } } [HarmonyPatch(typeof(CustomerManager), "Start")] private static class CustomerManagerLifecyclePatch { [HarmonyPostfix] private static void Postfix() { _active?.OnReadinessSignal(); } } [HarmonyPatch(typeof(UIScreenBase), "Start")] private static class TradeScreenStartPatch { [HarmonyPostfix] private static void Postfix(UIScreenBase __instance) { if (__instance is CustomerTradeCardScreen) { _active?.OnReadinessSignal(); } } } [HarmonyPatch(typeof(Customer), "ActivateCustomer")] [HarmonyPatch(typeof(Customer), "DeactivateCustomer")] [HarmonyPatch(typeof(Customer), "OnPressStopInteract")] private static class CustomerLifecyclePatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null)) { int index = TradeInterop.CustomerListIndex(__instance); _active.PrepareOfferForCustomer(index); _active.ApplyPendingMessages(); } } } private const string PredictionScope = "trade"; private static TradeClientBehaviour _active; private readonly Dictionary _offers = new Dictionary(); private readonly Dictionary _carrierKeysByCustomer = new Dictionary(); private CoopRuntimeContext _context; private Harmony _harmony; private TradeOfferBaselineMessage _pendingBaseline; private readonly Dictionary _pendingDeltas = new Dictionary(); private bool _applyingPending; private int _pendingCounter = -1; private bool _claimAccepted; private bool _awaitingPrediction; private bool _terminalSent; private bool _thinkingSent; private bool _acceptedShown; private bool _stopHandled; private bool _ignoreScreenClose; private bool _shutdown; private void OnEnable() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (_harmony == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); _harmony = new Harmony("dev.meepen.cardshopcoop.trade.client"); _harmony.CreateClassProcessor(typeof(CustomerPressPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerStopPatch)).Patch(); _harmony.CreateClassProcessor(typeof(AcceptPatch)).Patch(); _harmony.CreateClassProcessor(typeof(DeclinePatch)).Patch(); _harmony.CreateClassProcessor(typeof(ThinkPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ScreenClosePatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerLifecyclePatch)).Patch(); _harmony.CreateClassProcessor(typeof(TradeScreenStartPatch)).Patch(); NpcClientBehaviour.ExistingCustomerPuppetReady += OnExistingCustomerPuppetReady; NpcClientBehaviour.CustomerManagerReady += OnReadinessSignal; NpcClientBehaviour.CustomerPoolChanged += OnReadinessSignal; NpcClientBehaviour.CustomerCapacityChanged += OnCustomerCapacityChanged; NpcClientBehaviour.ExistingCustomerChanged += OnExistingCustomerChanged; SceneManager.sceneLoaded += OnSceneLoaded; OnReadinessSignal(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; NpcClientBehaviour.ExistingCustomerPuppetReady -= OnExistingCustomerPuppetReady; NpcClientBehaviour.CustomerManagerReady -= OnReadinessSignal; NpcClientBehaviour.CustomerPoolChanged -= OnReadinessSignal; NpcClientBehaviour.CustomerCapacityChanged -= OnCustomerCapacityChanged; NpcClientBehaviour.ExistingCustomerChanged -= OnExistingCustomerChanged; SceneManager.sceneLoaded -= OnSceneLoaded; ClearForSceneChange(); _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } } } private void OnDestroy() { Shutdown(); } private void ClearPendingDeltas() { foreach (TradeOfferDeltaMessage value in _pendingDeltas.Values) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas.Clear(); } private void ClearForSceneChange() { CustomerTradeCardScreen screen = TradeInterop.Screen; if (_pendingCounter >= 0 && (Object)(object)screen != (Object)null && TradeInterop.IsScreenOpen(screen)) { CloseRemoteScreen(); } foreach (LocalOffer value in _offers.Values) { ReleaseCarrier(value); } _offers.Clear(); _carrierKeysByCustomer.Clear(); _pendingBaseline = null; ClearPendingDeltas(); ResetSession(); _stopHandled = true; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (!_shutdown && _active == this) { ClearForSceneChange(); } } [MessageHandler(typeof(TradeOfferBaselineMessage))] private void HandleBaseline(MessageContext context, TradeOfferBaselineMessage message) { _pendingBaseline = message; ApplyPendingMessages(); } [MessageHandler(typeof(TradeOfferDeltaMessage))] private void HandleDelta(MessageContext context, TradeOfferDeltaMessage message) { CoopPlugin.Log.LogInfo((object)("[trade] client offer delta counter=" + message.Counter + " removed=" + message.Removed + " ready=" + IsTradeSceneReady() + ".")); if (!_context.InGame() || !IsTradeSceneReady()) { DeferDelta(message); } else { ApplyOfferDelta(message); } } private void ApplyPendingMessages() { if (_applyingPending) { CoopPlugin.Log.LogDebug((object)"[trade] re-entrant offer apply suppressed"); return; } if (!_context.InGame() || !IsTradeSceneReady()) { if (_pendingBaseline != null || _pendingDeltas.Count > 0) { CoopPlugin.Log.LogInfo((object)("[trade] offers waiting: inGame=" + _context.InGame() + " manager=" + ((Object)(object)TradeInterop.Manager != (Object)null) + " customers=" + (TradeInterop.Customers != null) + " screen=" + ((Object)(object)TradeInterop.Screen != (Object)null) + " pending=" + _pendingDeltas.Count + " baseline=" + (_pendingBaseline != null) + ".")); } return; } _applyingPending = true; try { if (_pendingBaseline != null) { TradeOfferBaselineMessage pendingBaseline = _pendingBaseline; _pendingBaseline = null; ApplyBaseline(pendingBaseline); } if (_pendingDeltas.Count != 0) { List list = new List(_pendingDeltas.Values); _pendingDeltas.Clear(); for (int i = 0; i < list.Count; i++) { ApplyOfferDelta(list[i]); } } } finally { _applyingPending = false; } } private void DeferDelta(TradeOfferDeltaMessage message) { if (_pendingDeltas.TryGetValue(message.Counter, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas[message.Counter] = message; } private void ApplyBaseline(TradeOfferBaselineMessage message) { HashSet hashSet = new HashSet(); for (int i = 0; i < message.Offers.Count; i++) { TradeOfferState tradeOfferState = message.Offers[i]; hashSet.Add(tradeOfferState.Counter); ApplyOfferState(tradeOfferState); } List list = new List(); foreach (KeyValuePair offer in _offers) { if (hashSet.Contains(offer.Key)) { continue; } if (_pendingCounter == offer.Key && (_awaitingPrediction || _acceptedShown)) { offer.Value.RemovedByHost = true; RemoveCarrierKey(offer.Value.State, offer.Key); continue; } if (_pendingCounter == offer.Key) { CloseRemoteScreen(); } RemoveCarrierKey(offer.Value.State, offer.Key); ReleaseCarrier(offer.Value); list.Add(offer.Key); } for (int j = 0; j < list.Count; j++) { _offers.Remove(list[j]); } } private void ApplyOfferState(TradeOfferState snapshot) { CoopPlugin.Log.LogInfo((object)("[trade] client apply offer counter=" + snapshot.Counter + " index=" + snapshot.CustomerIndex + " gen=" + snapshot.CustomerGeneration + " nonce=" + snapshot.OfferNonce + " ask=" + snapshot.Price.ToString("F2") + " priceSet=" + snapshot.PriceSet.ToString("F2") + " trading=" + snapshot.Trading + ".")); if (!_offers.TryGetValue(snapshot.Counter, out var value)) { value = new LocalOffer(); _offers.Add(snapshot.Counter, value); } if (value.State != null && (value.State.OfferNonce != snapshot.OfferNonce || value.State.CustomerGeneration != snapshot.CustomerGeneration || value.State.CustomerIndex != snapshot.CustomerIndex)) { RemoveCarrierKey(value.State, snapshot.Counter); if (_pendingCounter == snapshot.Counter) { CloseRemoteScreen(); } ReleaseCarrier(value); } value.State = snapshot; value.RemovedByHost = false; _carrierKeysByCustomer[snapshot.CustomerIndex] = snapshot.Counter; RefreshCarrierData(value); PrepareCarrier(value); } private void ApplyOfferDelta(TradeOfferDeltaMessage message) { PredictionApi.Confirm(message.PredictionId, delegate { ApplyOfferDeltaAuthoritative(message); }); } private void ApplyOfferDeltaAuthoritative(TradeOfferDeltaMessage message) { if (message.Removed) { if (!_offers.TryGetValue(message.Counter, out var value)) { return; } value.RemovedByHost = true; if (_pendingCounter == message.Counter) { _awaitingPrediction = false; _claimAccepted = false; _terminalSent = true; if (message.Outcome == TradeOutcome.Accepted) { _acceptedShown = true; TradeInterop.ShowAccepted(TradeInterop.Screen); } else { CloseRemoteScreen(); } } else { RemoveCarrierKey(value.State, message.Counter); ReleaseCarrier(value); _offers.Remove(message.Counter); } } else { if (message.Offer == null) { return; } ApplyOfferState(message.Offer); if (_offers.TryGetValue(message.Counter, out var value2) && _pendingCounter == message.Counter) { switch (message.Outcome) { case TradeOutcome.Thinking: _claimAccepted = false; _thinkingSent = true; CloseRemoteScreen(); break; case TradeOutcome.Haggle: case TradeOutcome.Refused: _awaitingPrediction = false; _terminalSent = false; _acceptedShown = false; TradeInterop.OpenData(value2.Carrier, TradeInterop.DataFromState(value2.State)); break; default: throw new ArgumentOutOfRangeException("Outcome", message.Outcome, "Unknown authoritative trade outcome."); case TradeOutcome.None: break; } } } } private void PrepareCarrier(LocalOffer offer) { //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) if (offer?.State == null || (Object)(object)offer.Carrier != (Object)null) { return; } IList customers = TradeInterop.Customers; if (customers == null || offer.State.CustomerIndex >= customers.Count) { CoopPlugin.Log.LogWarning((object)("[trade] carrier slot missing for offer id=" + offer.State.Counter + " index=" + offer.State.CustomerIndex + " count=" + (customers?.Count ?? (-1)) + ".")); return; } Customer val = customers[offer.State.CustomerIndex]; if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)("[trade] carrier is null for offer id=" + offer.State.Counter + " index=" + offer.State.CustomerIndex + ".")); return; } if (NpcClientBehaviour.IsExistingCustomer(val)) { CoopPlugin.Log.LogWarning((object)("[trade] carrier index=" + offer.State.CustomerIndex + " offer id=" + offer.State.Counter + " is already an existing customer; not preparing a trade carrier for it.")); return; } CustomerTradeData data = TradeInterop.DataFromState(offer.State); Renderer[] componentsInChildren = ((Component)val).GetComponentsInChildren(true); bool[] array = new bool[componentsInChildren.Length]; for (int i = 0; i < componentsInChildren.Length; i++) { array[i] = (Object)(object)componentsInChildren[i] != (Object)null && componentsInChildren[i].enabled; } ((Component)val).transform.position = offer.State.Position; ((Component)val).transform.rotation = Quaternion.Euler(0f, offer.State.Yaw, 0f); TradeInterop.SetStoredData(val, data); NpcClientBehaviour.SuppressedCustomer.Add(offer.State.CustomerIndex); NpcClientBehaviour.AttachExistingCustomer(offer.State.CustomerIndex, offer.State.CustomerGeneration, val, keepPuppetVisible: true); ((Component)val).gameObject.SetActive(true); GameObject exclaimationMesh = val.m_ExclaimationMesh; if (exclaimationMesh != null) { exclaimationMesh.SetActive(true); } GameObject interactCollider = val.m_InteractCollider; if (interactCollider != null) { interactCollider.SetActive(true); } offer.Carrier = val; offer.MaskedRenderers = componentsInChildren; offer.RendererStates = array; UpdateCarrierVisibility(offer); CoopPlugin.Log.LogInfo((object)("[trade] prepared carrier id=" + offer.State.Counter + " index=" + offer.State.CustomerIndex + " name=" + ((Object)val).name + " active=" + ((Component)val).gameObject.activeSelf + " collider=" + ((Object)(object)val.m_InteractCollider != (Object)null && val.m_InteractCollider.activeSelf) + ".")); } private static void RestoreRenderers(Renderer[] renderers, bool[] states) { int num = 0; while (renderers != null && num < renderers.Length && num < states.Length) { if ((Object)(object)renderers[num] != (Object)null) { renderers[num].enabled = states[num]; } num++; } } private void RefreshCarrierData(LocalOffer offer) { if ((Object)(object)offer?.Carrier != (Object)null && offer.State != null) { TradeInterop.SetStoredData(offer.Carrier, TradeInterop.DataFromState(offer.State)); } } private void UpdateCarrierVisibility(LocalOffer offer) { if ((Object)(object)offer?.Carrier == (Object)null || offer.State == null || offer.MaskedRenderers == null) { return; } bool flag = NpcClientBehaviour.IsExistingCustomerPuppetReady(offer.State.CustomerIndex, offer.State.CustomerGeneration); for (int i = 0; i < offer.MaskedRenderers.Length; i++) { if ((Object)(object)offer.MaskedRenderers[i] != (Object)null) { offer.MaskedRenderers[i].enabled = !flag && offer.RendererStates[i]; } } } private void ReleaseCarrier(LocalOffer offer) { if (offer == null) { return; } Customer carrier = offer.Carrier; Renderer[] maskedRenderers = offer.MaskedRenderers; bool[] rendererStates = offer.RendererStates; offer.Carrier = null; offer.MaskedRenderers = null; offer.RendererStates = null; RestoreRenderers(maskedRenderers, rendererStates); if (!((Object)(object)carrier == (Object)null)) { if (offer.State != null) { NpcClientBehaviour.DetachExistingCustomer(offer.State.CustomerIndex, carrier); } TradeInterop.SetStoredData(carrier, null); if ((Object)(object)((Component)carrier).gameObject != (Object)null) { ((Component)carrier).gameObject.SetActive(false); } } } private void OnExistingCustomerPuppetReady(int index, int generation, Customer customer) { if (_carrierKeysByCustomer.TryGetValue((ushort)index, out var value) && _offers.TryGetValue(value, out var value2)) { UpdateCarrierVisibility(value2); } } private void OnCustomerCapacityChanged(int index) { PrepareOfferForCustomer(index); ApplyPendingMessages(); } private void OnExistingCustomerChanged(int index, int generation, Customer customer) { PrepareOfferForCustomer(index); ApplyPendingMessages(); } private void PrepareOfferForCustomer(int index) { if (_carrierKeysByCustomer.TryGetValue((ushort)index, out var value) && _offers.TryGetValue(value, out var value2)) { PrepareCarrier(value2); } } private void RemoveCarrierKey(TradeOfferState snapshot, byte counter) { if (snapshot != null && _carrierKeysByCustomer.TryGetValue(snapshot.CustomerIndex, out var value) && value == counter) { _carrierKeysByCustomer.Remove(snapshot.CustomerIndex); } } private bool IsTradeSceneReady() { if ((Object)(object)TradeInterop.Manager != (Object)null && TradeInterop.Customers != null) { return (Object)(object)TradeInterop.Screen != (Object)null; } return false; } private void OnReadinessSignal() { if (!_shutdown && _context.InGame() && IsTradeSceneReady()) { ApplyPendingMessages(); } } [MessageHandler(typeof(TradeSessionMessage))] private void HandleSession(MessageContext context, TradeSessionMessage message) { CoopPlugin.Log.LogInfo((object)("[trade] client session counter=" + message.Counter + " index=" + message.CustomerIndex + " gen=" + message.CustomerGeneration + " nonce=" + message.OfferNonce + " accepted=" + message.Accepted + " id=" + message.PredictionId.ToString() + ".")); PredictionApi.AckOrApply(message.PredictionId, delegate { if (message.Accepted) { _claimAccepted = true; } }); } private LocalOffer PendingOffer() { if (_pendingCounter < 0 || !_offers.TryGetValue((byte)_pendingCounter, out var value) || !((Object)(object)value.Carrier != (Object)null)) { return null; } return value; } private bool IsCarrier(Customer customer, out LocalOffer offer) { foreach (KeyValuePair offer2 in _offers) { if (offer2.Value.Carrier == customer) { offer = offer2.Value; return true; } } offer = null; return false; } private void BeginSession(LocalOffer offer) { _pendingCounter = offer.State.Counter; _claimAccepted = false; _awaitingPrediction = false; _terminalSent = false; _thinkingSent = false; _acceptedShown = false; _stopHandled = false; if (PredictIntentObserved(TradeIntentOperation.Open, 0f)) { _claimAccepted = true; } } private bool PredictIntentObserved(TradeIntentOperation operation, float price) { return RegisterIntent(operation, price, PredictionApi.Predict); } private bool RegisterIntent(TradeIntentOperation operation, float price, Func, Action, Action, Guid> register) { LocalOffer offer = PendingOffer(); if (!_context.InGame() || offer?.State == null || _pendingCounter < 0) { return false; } if (offer.State.Trading && (operation == TradeIntentOperation.Accept || operation == TradeIntentOperation.Think)) { price = 0f; } PredictionFrame frame = CapturePredictionFrame(offer, operation); register("trade", delegate(Guid predictionId) { SendIntent(predictionId, operation, price, offer.State); }, delegate { ApplyPrediction(operation); }, delegate { UndoPrediction(frame); }); return true; } private IntentCapture BeginIntent(TradeIntentOperation operation, float price, CustomerTradeData acceptBefore = null) { LocalOffer localOffer = PendingOffer(); if (!_context.InGame() || localOffer?.State == null || _pendingCounter < 0) { return null; } if (localOffer.State.Trading && (operation == TradeIntentOperation.Accept || operation == TradeIntentOperation.Think)) { price = 0f; } PredictionFrame frame = CapturePredictionFrame(localOffer, operation); MarkPredicted(operation); return new IntentCapture { Operation = operation, Price = price, Frame = frame, State = localOffer.State, AcceptBefore = acceptBefore }; } private void EndIntent(IntentCapture capture) { if (capture == null) { return; } if (capture.Operation == TradeIntentOperation.Accept) { AcceptResult result = BuildAcceptResult(capture); if (result == null) { RestoreIntent(capture.Frame); return; } capture.Frame.Effect = ((result.Outcome == TradeOutcome.Accepted) ? BuildAcceptEffect(capture) : null); PredictionApi.Predict("trade", delegate(Guid predictionId) { SendIntent(predictionId, capture.Operation, capture.Price, capture.State, result.Outcome, result.State, capture.Frame.Effect); }, delegate { ApplyPrediction(capture.Operation, capture.Frame.Effect); }, delegate { UndoPrediction(capture.Frame); }); } else { PredictionApi.Predict("trade", delegate(Guid predictionId) { SendIntent(predictionId, capture.Operation, capture.Price, capture.State); }, delegate { ApplyPrediction(capture.Operation); }, delegate { UndoPrediction(capture.Frame); }); } } private static AcceptEffect BuildAcceptEffect(IntentCapture capture) { return new AcceptEffect { Received = CopyCard(capture.State.CardL), Given = (capture.State.Trading ? CopyCard(capture.State.CardR) : null), CoinSpent = (capture.State.Trading ? 0f : capture.Price) }; } private AcceptResult BuildAcceptResult(IntentCapture capture) { CustomerTradeCardScreen screen = TradeInterop.Screen; if (TradeInterop.HasAccepted(screen)) { return new AcceptResult { Outcome = TradeOutcome.Accepted }; } if (!TradeInterop.IsScreenOpen(screen)) { return new AcceptResult { Outcome = TradeOutcome.WalkedAway }; } CustomerTradeData val = TradeInterop.Capture(screen); CustomerTradeData acceptBefore = capture.AcceptBefore; bool flag = acceptBefore != null && !Mathf.Approximately(val.m_SellCardAskPrice, acceptBefore.m_SellCardAskPrice); bool flag2 = acceptBefore != null && val.m_MaxDeclineCount != acceptBefore.m_MaxDeclineCount; if (!flag && !flag2) { return null; } return new AcceptResult { Outcome = (flag ? TradeOutcome.Haggle : TradeOutcome.Refused), State = ApplyDataToState(capture.State, val) }; } private void RestoreIntent(PredictionFrame frame) { _claimAccepted = frame.ClaimAccepted; _terminalSent = frame.TerminalSent; _thinkingSent = frame.ThinkingSent; _acceptedShown = frame.AcceptedShown; _awaitingPrediction = false; } private static TradeOfferState ApplyDataToState(TradeOfferState baseState, CustomerTradeData data) { TradeOfferState tradeOfferState = CloneState(baseState); tradeOfferState.Trading = data.m_IsTrading; tradeOfferState.CardL = CopyCard(data.m_CardData_L); tradeOfferState.CardR = CopyCard(data.m_CardData_R); tradeOfferState.Price = data.m_SellCardAskPrice; tradeOfferState.MarketPrice = data.m_SellCardMarketPrice; tradeOfferState.PriceSet = data.m_PriceSet; tradeOfferState.LastPriceSet = data.m_LastPriceSet; tradeOfferState.MaxDeclineCount = data.m_MaxDeclineCount; tradeOfferState.DeclineCount = data.m_DeclineCount; return tradeOfferState; } private void MarkPredicted(TradeIntentOperation operation) { switch (operation) { case TradeIntentOperation.Accept: _awaitingPrediction = true; _terminalSent = true; _acceptedShown = true; break; case TradeIntentOperation.Decline: _awaitingPrediction = true; _terminalSent = true; break; case TradeIntentOperation.Think: _awaitingPrediction = true; _claimAccepted = false; _thinkingSent = true; break; } } private void SendIntent(Guid predictionId, TradeIntentOperation operation, float price, TradeOfferState state, TradeOutcome result = TradeOutcome.None, TradeOfferState resultState = null, AcceptEffect effect = null) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[17] { "[trade] client intent ", operation.ToString(), " counter=", state.Counter.ToString(), " index=", state.CustomerIndex.ToString(), " gen=", state.CustomerGeneration.ToString(), " nonce=", state.OfferNonce.ToString(), " price=", price.ToString("F2"), " result=", result.ToString(), " id=", null, null }; Guid guid = predictionId; obj[15] = guid.ToString(); obj[16] = "."; log.LogInfo((object)string.Concat(obj)); _context.Send(1, (INetMessage)(object)new TradeIntentMessage { PredictionId = predictionId, Operation = operation, Counter = state.Counter, CustomerIndex = state.CustomerIndex, CustomerGeneration = state.CustomerGeneration, OfferNonce = state.OfferNonce, Price = price, Result = result, ResultState = resultState, CardReceived = effect?.Received, CardRemoved = effect?.Given, CoinSpent = (effect?.CoinSpent ?? 0f) }); } private PredictionFrame CapturePredictionFrame(LocalOffer offer, TradeIntentOperation operation) { return new PredictionFrame { Operation = operation, State = CloneState(offer.State), ClaimAccepted = _claimAccepted, TerminalSent = _terminalSent, ThinkingSent = _thinkingSent, AcceptedShown = _acceptedShown, ScreenOpen = TradeInterop.IsScreenOpen(TradeInterop.Screen) }; } private void ApplyPrediction(TradeIntentOperation operation, AcceptEffect effect = null) { switch (operation) { case TradeIntentOperation.Open: _claimAccepted = true; break; case TradeIntentOperation.Accept: _awaitingPrediction = true; _terminalSent = true; _acceptedShown = true; TradeInterop.ShowAccepted(TradeInterop.Screen); break; case TradeIntentOperation.Decline: _awaitingPrediction = true; _terminalSent = true; ClosePredictedScreen(); break; case TradeIntentOperation.Think: _awaitingPrediction = true; _claimAccepted = false; _thinkingSent = true; ClosePredictedScreen(); break; } if (effect != null) { ApplyAcceptEffect(effect); } } private static void ApplyAcceptEffect(AcceptEffect effect) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (effect.Received != null) { WorldCardInteraction.ApplyPredictedCardDelta(effect.Received, 1, isAdd: true); } if (effect.Given != null) { WorldCardInteraction.ApplyPredictedCardDelta(effect.Given, 1, isAdd: false); } if (effect.CoinSpent > 0f) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin(effect.CoinSpent, false)); } if (effect.Received != null || effect.Given != null) { WorldClientBehaviour.ActiveCards?.NotifyCardsChanged(); } } private static void UndoAcceptEffect(AcceptEffect effect) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (effect.Given != null) { WorldCardInteraction.ApplyPredictedCardDelta(effect.Given, 1, isAdd: true); } if (effect.Received != null) { WorldCardInteraction.ApplyPredictedCardDelta(effect.Received, 1, isAdd: false); } if (effect.CoinSpent > 0f) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin(effect.CoinSpent, true)); } if (effect.Received != null || effect.Given != null) { WorldClientBehaviour.ActiveCards?.NotifyCardsChanged(); } } private void UndoPrediction(PredictionFrame frame) { CoopPlugin.Log.LogInfo((object)("[trade] client undo " + frame.Operation.ToString() + " state=" + ((frame.State == null) ? "" : frame.State.Counter.ToString()) + " effect=" + (frame.Effect != null) + ".")); if (frame.Effect != null) { UndoAcceptEffect(frame.Effect); } LocalOffer localOffer = PendingOffer(); if (localOffer != null && frame.State != null) { localOffer.State = CloneState(frame.State); RefreshCarrierData(localOffer); } _claimAccepted = frame.ClaimAccepted; _terminalSent = frame.TerminalSent; _thinkingSent = frame.ThinkingSent; _acceptedShown = frame.AcceptedShown; _awaitingPrediction = false; if (frame.Operation == TradeIntentOperation.Open) { ClosePredictedScreen(); EndLocalSession(sendClose: false); return; } CustomerTradeCardScreen screen = TradeInterop.Screen; bool flag = TradeInterop.IsScreenOpen(screen); if (frame.ScreenOpen && (Object)(object)localOffer?.Carrier != (Object)null && localOffer.State != null) { TradeInterop.OpenData(localOffer.Carrier, TradeInterop.DataFromState(localOffer.State)); } else if (!frame.ScreenOpen && flag) { ClosePredictedScreen(); } if (frame.AcceptedShown && (Object)(object)screen != (Object)null && !TradeInterop.HasAccepted(screen)) { TradeInterop.ShowAccepted(screen); } } private void ClosePredictedScreen() { CustomerTradeCardScreen screen = TradeInterop.Screen; if ((Object)(object)screen == (Object)null || !TradeInterop.IsScreenOpen(screen)) { return; } _ignoreScreenClose = true; try { TradeInterop.CloseScreen(screen); } finally { _ignoreScreenClose = false; } } private static TradeOfferState CloneState(TradeOfferState state) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if (state == null) { return null; } return new TradeOfferState { Counter = state.Counter, Trading = state.Trading, CardL = CopyCard(state.CardL), CardR = CopyCard(state.CardR), Price = state.Price, MarketPrice = state.MarketPrice, PriceSet = state.PriceSet, LastPriceSet = state.LastPriceSet, MaxDeclineCount = state.MaxDeclineCount, DeclineCount = state.DeclineCount, Remaining = state.Remaining, CustomerIndex = state.CustomerIndex, CustomerGeneration = state.CustomerGeneration, OfferNonce = state.OfferNonce, CustomerFemale = state.CustomerFemale, Position = state.Position, Yaw = state.Yaw }; } private static CardData CopyCard(CardData card) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown if (card == null) { return null; } CardData val = new CardData(); val.CopyData(card); return val; } private void ResetSession() { _pendingCounter = -1; _claimAccepted = false; _awaitingPrediction = false; _terminalSent = false; _thinkingSent = false; _acceptedShown = false; } private void EndLocalSession(bool sendClose) { if (_pendingCounter < 0) { return; } int pendingCounter = _pendingCounter; LocalOffer localOffer = PendingOffer(); if (sendClose && !_terminalSent && !_thinkingSent && !_awaitingPrediction && localOffer != null) { SendIntent(Guid.NewGuid(), TradeIntentOperation.Close, 0f, localOffer.State); } _stopHandled = true; ResetSession(); if (localOffer != null && !localOffer.RemovedByHost && localOffer.State != null) { TradeInterop.SetStoredData(localOffer.Carrier, TradeInterop.DataFromState(localOffer.State)); TradeInterop.ResetCarrierInteraction(localOffer.Carrier); return; } if (localOffer != null) { ReleaseCarrier(localOffer); } _offers.Remove((byte)pendingCounter); } private void CloseRemoteScreen() { CustomerTradeCardScreen screen = TradeInterop.Screen; if ((Object)(object)screen != (Object)null && TradeInterop.IsScreenOpen(screen)) { _stopHandled = true; try { TradeInterop.CloseScreen(screen); return; } finally { EndLocalSession(sendClose: false); } } EndLocalSession(sendClose: false); } } [ServerBehaviour] public sealed class TradeHostBehaviour : CoopBehaviour { private sealed class Offer { public byte Counter; public ushort CustomerIndex; public int CustomerGeneration; public Customer Customer; public CustomerTradeData Data; public uint Nonce; public int Owner; } [HarmonyPatch(typeof(CustomerManager), "Start")] private static class CustomerManagerStartPatch { [HarmonyPostfix] private static void Postfix(CustomerManager __instance) { _active?.RegisterAllCustomers((__instance != null) ? __instance.GetCustomerList() : null); _active?.SignalCustomerManagerReady(); } } [HarmonyPatch(typeof(UIScreenBase), "Start")] private static class TradeScreenStartPatch { [HarmonyPostfix] private static void Postfix(UIScreenBase __instance) { if (__instance is CustomerTradeCardScreen) { _active?.SignalTradeScreenReady(); } } } [HarmonyPatch(typeof(Customer), "ActivateCustomer")] [HarmonyPatch(typeof(Customer), "DeactivateCustomer")] private static class CustomerPopulationPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { _active?.ReconcileCustomer(__instance); } } [HarmonyPatch(typeof(Customer), "OnMousePress")] private static class CustomerPressPatch { [HarmonyPrefix] private static bool Prefix(Customer __instance) { if ((Object)(object)_active == (Object)null || (Object)(object)__instance == (Object)null) { return true; } foreach (Offer value in _active._offers.Values) { if (value.Customer == __instance && value.Owner != 0) { return false; } } return true; } } [HarmonyPatch(typeof(Customer), "SetState")] private static class CustomerStatePatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { _active?.ReconcileCustomer(__instance); } } [HarmonyPatch(typeof(Customer), "DetermineShopAction")] private static class CustomerActionPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { _active?.ReconcileCustomer(__instance); } } [HarmonyPatch(typeof(Customer), "OnReachedPathEnd")] private static class CustomerPathEndPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { _active?.ReconcileCustomer(__instance); } } [HarmonyPatch(typeof(Customer), "OnPressStopInteract")] private static class CustomerStopPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { _active?.ReconcileCustomer(__instance); } } [HarmonyPatch(typeof(CustomerTradeCardScreen), "OnPressLetMeThink")] private static class ThinkPatch { [HarmonyPostfix] private static void Postfix() { CustomerTradeCardScreen screen = TradeInterop.Screen; _active?.ReconcileCustomer(TradeInterop.CurrentCustomer(screen)); } } private const float Reach = 4f; private static TradeHostBehaviour _active; private readonly Dictionary _offers = new Dictionary(); private readonly Dictionary _offersByCustomer = new Dictionary(); private readonly Dictionary _customerIndices = new Dictionary(); private readonly Dictionary _joinedConnections = new Dictionary(); private CoopRuntimeContext _context; private Harmony _harmony; private uint _nextNonce; private bool _shutdown; private bool _customerManagerReady; private bool _tradeScreenReady; private int _observedDay; private bool _hasObservedDay; private void OnEnable() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (_harmony == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); _harmony = new Harmony("dev.meepen.cardshopcoop.trade.host"); _harmony.CreateClassProcessor(typeof(CustomerPressPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerStatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerActionPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerPathEndPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerStopPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ThinkPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerManagerStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerPopulationPatch)).Patch(); _harmony.CreateClassProcessor(typeof(TradeScreenStartPatch)).Patch(); SceneManager.sceneLoaded += OnSceneLoaded; CEventManager.AddListener((EventDelegate)OnGameDataFinishLoaded); RegisterAllCustomers(TradeInterop.Customers); SignalCustomerManagerReady(); SignalTradeScreenReady(); CoopPlugin.Log.LogInfo((object)"[trade] host module enabled."); } } private void Update() { if (!_context.InGame()) { if (_offers.Count != 0) { CleanupOffers(); } _hasObservedDay = false; } else if (!_hasObservedDay) { _observedDay = CPlayerData.m_CurrentDay; _hasObservedDay = true; } else if (_observedDay != CPlayerData.m_CurrentDay) { _observedDay = CPlayerData.m_CurrentDay; CleanupOffers(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded -= OnSceneLoaded; CleanupOffers(); _joinedConnections.Clear(); _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } } } private void OnDestroy() { Shutdown(); } private void RegisterAllCustomers(IList customers) { int num = 0; while (customers != null && num < customers.Count) { Customer val = customers[num]; if ((Object)(object)val != (Object)null) { _customerIndices[val] = num; ReconcileCustomer(val); } num++; } } [OnFullyJoined] private void SendJoinState(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State)) { _joinedConnections[connection.Id] = connection; SendBaselineWhenReady(connection.Id); } } [OnClientDisconnected] private void ReleaseConnection(PeerConnection connection, DisconnectInfo info) { if (connection == null) { return; } _joinedConnections.Remove(connection.Id); foreach (Offer value in _offers.Values) { if (value.Owner == connection.Id) { value.Owner = 0; SendOfferDelta(value, removed: false, Guid.Empty, TradeOutcome.None, 0); } } } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private void SendBaselineWhenReady(int connectionId) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (_context.InGame() && _customerManagerReady && _tradeScreenReady && _joinedConnections.TryGetValue(connectionId, out var value) && value != null && IsJoinPhase(value.State)) { _context.Send(connectionId, (INetMessage)(object)BuildBaseline()); } } private void SendBaselinesToJoined() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (!_context.InGame() || !_customerManagerReady || !_tradeScreenReady) { return; } foreach (KeyValuePair joinedConnection in _joinedConnections) { if (joinedConnection.Value != null && IsJoinPhase(joinedConnection.Value.State)) { _context.Send(joinedConnection.Key, (INetMessage)(object)BuildBaseline()); } } } [MessageHandler(typeof(TradeIntentMessage))] private void HandleIntent(MessageContext messageContext, TradeIntentMessage message) { if (!IsValidSender(messageContext, message)) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "[trade] host ignored intent from invalid sender conn=", null, null, null, null }; int? obj2; if (messageContext == null) { obj2 = null; } else { PeerConnection connection = messageContext.Connection; obj2 = ((connection != null) ? new int?(connection.Id) : ((int?)null)); } int? num = obj2; obj[1] = num.GetValueOrDefault().ToString(); obj[2] = " op="; obj[3] = ((message == null) ? "" : message.Operation.ToString()); obj[4] = "."; log.LogWarning((object)string.Concat(obj)); return; } if (!_offers.TryGetValue(message.Counter, out var value) || !Matches(value, message)) { Reject(messageContext.Connection.Id, message, (value == null) ? ("no live offer for counter " + message.Counter) : ("offer identity mismatch (offer index=" + value.CustomerIndex + " gen=" + value.CustomerGeneration + " nonce=" + value.Nonce + ", intent index=" + message.CustomerIndex + " gen=" + message.CustomerGeneration + " nonce=" + message.OfferNonce + ")")); return; } int id = messageContext.Connection.Id; string reason; if (message.Operation == TradeIntentOperation.Open) { Open(id, message, value); } else if (message.Operation == TradeIntentOperation.Close) { if (value.Owner == id) { value.Owner = 0; } } else if (value.Owner != id) { Reject(id, message, "offer is owned by connection " + value.Owner + ", not " + id); } else if (!ValidateLiveAction(id, value, out reason)) { Reject(id, message, reason); } else if (message.Operation == TradeIntentOperation.Accept) { ResolveAccept(id, message, value); } else if (message.Operation == TradeIntentOperation.Decline) { ResolveDecline(id, message, value); } else if (message.Operation == TradeIntentOperation.Think) { ResolveThink(id, message, value); } else { Reject(id, message, "unknown operation " + message.Operation); } } private void Open(int connectionId, TradeIntentMessage message, Offer offer) { if (offer.Owner != 0) { Reject(connectionId, message, "offer already owned by connection " + offer.Owner); return; } if (!ValidateLiveAction(connectionId, offer, out var reason)) { Reject(connectionId, message, reason); return; } offer.Owner = connectionId; CoopPlugin.Log.LogInfo((object)("[trade] host accepted open counter=" + offer.Counter + " index=" + offer.CustomerIndex + " gen=" + offer.CustomerGeneration + " nonce=" + offer.Nonce + " owner=" + connectionId + ".")); _context.Send(connectionId, (INetMessage)(object)new TradeSessionMessage { PredictionId = message.PredictionId, Counter = offer.Counter, CustomerIndex = offer.CustomerIndex, CustomerGeneration = offer.CustomerGeneration, OfferNonce = offer.Nonce, Accepted = true }); } private void ResolveAccept(int connectionId, TradeIntentMessage message, Offer offer) { CustomerTradeData data = offer.Data; if (!TryValidateBid(data, message.Price, out var bid)) { Reject(connectionId, message, "invalid bid price=" + message.Price + " (trading=" + ((data == null) ? "null" : data.m_IsTrading.ToString()) + ", ask=" + ((data == null) ? "n/a" : data.m_SellCardAskPrice.ToString("F2")) + ")"); return; } switch (message.Result) { case TradeOutcome.Accepted: if (!ApplyAcceptedEffect(connectionId, message, offer, bid)) { Reject(connectionId, message, "accept effect could not be applied"); break; } RemoveOffer(offer); SendOfferDelta(offer, removed: true, message.PredictionId, message.Result, connectionId); FinishCustomerReservation(offer); break; case TradeOutcome.WalkedAway: RemoveOffer(offer); SendOfferDelta(offer, removed: true, message.PredictionId, message.Result, connectionId); FinishCustomerReservation(offer); break; case TradeOutcome.Haggle: case TradeOutcome.Refused: { if (message.ResultState == null) { Reject(connectionId, message, "kept offer has no resulting state"); break; } CustomerTradeData data2 = TradeInterop.DataFromState(message.ResultState); TradeInterop.SetStoredData(offer.Customer, data2); offer.Data = data2; SendOfferDelta(offer, removed: false, message.PredictionId, message.Result, connectionId); break; } default: Reject(connectionId, message, "unexpected accept result " + message.Result); break; } } private bool ApplyAcceptedEffect(int connectionId, TradeIntentMessage message, Offer offer, float bid) { CustomerTradeData data = offer.Data; bool isTrading = data.m_IsTrading; if (isTrading && message.CoinSpent != 0f) { CoopPlugin.Log.LogWarning((object)("[trade] accept for card-for-card offer " + offer.Counter + " carried a coin spend; refusing.")); return false; } if (!isTrading && Mathf.Abs(message.CoinSpent - bid) > 0.001f) { CoopPlugin.Log.LogWarning((object)("[trade] accept for purchase offer " + offer.Counter + " spent " + message.CoinSpent + " but the validated bid is " + bid + "; refusing.")); return false; } CardData cardData_L = data.m_CardData_L; CardData val = (isTrading ? data.m_CardData_R : null); if (!SameCard(cardData_L, message.CardReceived) || !SameCard(val, message.CardRemoved)) { CoopPlugin.Log.LogWarning((object)("[trade] accept for offer " + offer.Counter + " carried cards that do not match the offer (received=" + DescribeCard(message.CardReceived) + " expected=" + DescribeCard(cardData_L) + ", given=" + DescribeCard(message.CardRemoved) + " expected=" + DescribeCard(val) + "); refusing.")); return false; } EconomyAuthority.HostSpendReservation reservation = null; if (!isTrading && message.CoinSpent > 0f && !EconomyAuthority.TryReserveHostSpend(message.CoinSpent, out reservation)) { CoopPlugin.Log.LogInfo((object)("[trade] host cannot fund guest purchase of " + message.CoinSpent + " on offer " + offer.Counter + "; refusing the accept.")); return false; } if (cardData_L != null || val != null) { WorldCardInteraction activeCards = WorldHostBehaviour.ActiveCards; CardDeltaBatchMessage cardDeltaBatchMessage = new CardDeltaBatchMessage { PredictionId = message.PredictionId }; if (val != null) { cardDeltaBatchMessage.Deltas.Add(new CardDeltaEntry { IsAdd = false, Amount = 1, Card = val }); } if (cardData_L != null) { cardDeltaBatchMessage.Deltas.Add(new CardDeltaEntry { IsAdd = true, Amount = 1, Card = cardData_L }); } if (activeCards == null || !activeCards.HandleCardDeltaBatch(connectionId, cardDeltaBatchMessage, requireAllApplied: true)) { CoopPlugin.Log.LogWarning((object)("[trade] host could not apply the card movement for offer " + offer.Counter + "; refusing the accept.")); return false; } } if (reservation != null) { EconomyAuthority.QueueHostSpend(reservation); } return true; } private void ResolveDecline(int connectionId, TradeIntentMessage message, Offer offer) { RemoveOffer(offer); SendOfferDelta(offer, removed: true, message.PredictionId, TradeOutcome.Declined, connectionId); FinishCustomerReservation(offer); } private void ResolveThink(int connectionId, TradeIntentMessage message, Offer offer) { if (!TryValidateBid(offer.Data, message.Price, out var bid)) { Reject(connectionId, message, "invalid think price=" + message.Price + " (trading=" + ((offer.Data == null) ? "null" : offer.Data.m_IsTrading.ToString()) + ")"); return; } CustomerTradeCardScreen screen = TradeInterop.Screen; if ((Object)(object)screen == (Object)null) { throw new InvalidOperationException("Trade think has no CustomerTradeCardScreen."); } TradeInterop.OpenData(offer.Customer, offer.Data); if (!offer.Data.m_IsTrading) { TradeInterop.SetPrice(screen, bid); } try { screen.OnPressLetMeThink(); } finally { TradeInterop.SetManagerTrading(value: false); } CustomerTradeData val = TradeInterop.StoredData(offer.Customer); if (val == null) { throw new InvalidOperationException("Trade think did not persist customer data."); } offer.Data = TradeInterop.CopyData(val); SendOfferDelta(offer, removed: false, message.PredictionId, TradeOutcome.Thinking, connectionId); offer.Owner = 0; } private bool ValidateLiveAction(int connectionId, Offer offer, out string reason) { //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) if (HostIsBusy()) { CustomerManager manager = TradeInterop.Manager; reason = "host is busy (playerTrading=" + ((Object)(object)manager != (Object)null && manager.m_IsPlayerTrading) + " screenOpen=" + TradeInterop.IsScreenOpen(TradeInterop.Screen) + ")"; return false; } if (!TradeInterop.IsWaitingForTrade(offer.Customer)) { reason = "customer is not waiting to trade (state=" + (((Object)(object)offer.Customer == (Object)null) ? "null" : ((object)Unsafe.As(ref offer.Customer.m_CurrentState)/*cast due to .constrained prefix*/).ToString()) + ")"; return false; } if (!TradeInterop.SameCustomer(offer.Customer, offer.CustomerIndex, offer.CustomerGeneration)) { reason = "customer identity changed (offer index=" + offer.CustomerIndex + " gen=" + offer.CustomerGeneration + ", live index=" + TradeInterop.CustomerIdentity(offer.Customer) + " gen=" + TradeInterop.CustomerGeneration(offer.Customer) + ")"; return false; } int num = TradeInterop.CounterIndex(offer.Customer); if (num != offer.Counter) { reason = "counter changed (offer=" + offer.Counter + ", live=" + num + ")"; return false; } if (!_context.PeerPresence.TryGet(connectionId, out var presence)) { reason = "no presence recorded for connection " + connectionId; return false; } if (presence.Age > TimeSpan.FromSeconds(2.0)) { reason = "presence is stale (" + presence.Age.TotalSeconds.ToString("F2") + "s old)"; return false; } if (!TradeInterop.IsReachable(presence.Position, ((Component)offer.Customer).transform.position, 4f)) { reason = "player is out of reach (distance=" + Vector3.Distance(presence.Position, ((Component)offer.Customer).transform.position).ToString("F2") + " > " + 4f + ")"; return false; } reason = null; return true; } private bool HostIsBusy() { CustomerManager manager = TradeInterop.Manager; CustomerTradeCardScreen screen = TradeInterop.Screen; if ((Object)(object)manager != (Object)null) { if (!manager.m_IsPlayerTrading) { return TradeInterop.IsScreenOpen(screen); } return true; } return false; } private static bool IsValidSender(MessageContext context, TradeIntentMessage message) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (context?.Connection != null && (int)context.Connection.State == 3 && message != null && message.Counter < 250 && message.CustomerIndex != ushort.MaxValue) { return message.PredictionId != Guid.Empty; } return false; } private static bool Matches(Offer offer, TradeIntentMessage message) { if (offer.CustomerIndex == message.CustomerIndex && offer.CustomerGeneration == message.CustomerGeneration) { return offer.Nonce == message.OfferNonce; } return false; } private void ReconcileCustomer(Customer customer) { if ((Object)(object)customer == (Object)null) { return; } int value; int num = (_customerIndices.TryGetValue(customer, out value) ? value : TradeInterop.CustomerListIndex(customer)); if (num >= 0) { _customerIndices[customer] = num; } _offersByCustomer.TryGetValue(customer, out var value2); if (!TradeInterop.IsWaitingForTrade(customer)) { if (value2 != null) { ExpireOffer(value2); } return; } int num2 = TradeInterop.CounterIndex(customer); int num3 = TradeInterop.CustomerGeneration(customer); if (num < 0 || num > 65535 || num2 < 0 || num2 >= 250 || num3 <= 0) { CoopPlugin.Log.LogDebug((object)"Trade host: waiting customer has no stable trade identity"); return; } if (_offers.TryGetValue((byte)num2, out var value3) && value3.Customer != customer) { ExpireOffer(value3); } if (value2 != null && (value2.Counter != num2 || value2.CustomerIndex != num || value2.CustomerGeneration != num3)) { ExpireOffer(value2); value2 = null; } if (value2 == null) { TryCreateOffer(customer, (ushort)num, (byte)num2); } else { RefreshStoredData(value2); } } private void TryCreateOffer(Customer customer, ushort customerIndex, byte counter) { if (HostIsBusy() || (Object)(object)TradeInterop.Screen == (Object)null) { CoopPlugin.Log.LogDebug((object)("[trade] host offer index=" + customerIndex + " deferred: busy=" + HostIsBusy() + " screen=" + ((Object)(object)TradeInterop.Screen != (Object)null) + ".")); return; } CustomerTradeData val; try { TradeInterop.Screen.SetCustomer(customer, (CustomerTradeData)null); val = TradeInterop.Capture(TradeInterop.Screen); } finally { TradeInterop.SetManagerTrading(value: false); } if (val.m_CardData_L == null || (val.m_IsTrading && val.m_CardData_R == null)) { CoopPlugin.Log.LogDebug((object)("[trade] host offer index=" + customerIndex + " has no tradeable card: L=" + (val.m_CardData_L != null) + " R=" + (val.m_CardData_R != null) + " trading=" + val.m_IsTrading + ".")); return; } int num = TradeInterop.CustomerGeneration(customer); if (num > 0) { TradeInterop.SetStoredData(customer, val); _nextNonce++; if (_nextNonce == 0) { _nextNonce = 1u; } _offers[counter] = new Offer { Counter = counter, CustomerIndex = customerIndex, CustomerGeneration = num, Customer = customer, Data = val, Nonce = _nextNonce }; _offersByCustomer[customer] = _offers[counter]; CoopPlugin.Log.LogInfo((object)("[trade] host created offer counter=" + counter + " index=" + customerIndex + " gen=" + num + " nonce=" + _nextNonce + " ask=" + val.m_SellCardAskPrice.ToString("F2") + " priceSet=" + val.m_PriceSet.ToString("F2") + " trading=" + val.m_IsTrading + ".")); SendOfferDelta(_offers[counter], removed: false, Guid.Empty, TradeOutcome.None, 0); } } private void RefreshStoredData(Offer offer) { CustomerTradeData val = TradeInterop.StoredData(offer.Customer); if (val != null && !SameData(offer.Data, val)) { CoopPlugin.Log.LogInfo((object)("[trade] host refreshed offer counter=" + offer.Counter + " nonce=" + offer.Nonce + " ask=" + val.m_SellCardAskPrice.ToString("F2") + " priceSet=" + val.m_PriceSet.ToString("F2") + " trading=" + val.m_IsTrading + " (was ask=" + ((offer.Data == null) ? "n/a" : offer.Data.m_SellCardAskPrice.ToString("F2")) + " priceSet=" + ((offer.Data == null) ? "n/a" : offer.Data.m_PriceSet.ToString("F2")) + " trading=" + ((offer.Data == null) ? "n/a" : offer.Data.m_IsTrading.ToString()) + ").")); offer.Data = TradeInterop.CopyData(val); SendOfferDelta(offer, removed: false, Guid.Empty, TradeOutcome.None, 0); } } private void CleanupOffers() { List list = new List(_offers.Values); _offers.Clear(); _offersByCustomer.Clear(); for (int i = 0; i < list.Count; i++) { Offer offer = list[i]; SendOfferDelta(offer, removed: true, Guid.Empty, TradeOutcome.Expired, 0); offer.Owner = 0; FinishCustomerReservation(offer); } } private void ExpireOffer(Offer offer) { if (offer != null) { RemoveOffer(offer); SendOfferDelta(offer, removed: true, Guid.Empty, TradeOutcome.Expired, 0); FinishCustomerReservation(offer); } } private void RemoveOffer(Offer offer) { if (offer != null && _offers.TryGetValue(offer.Counter, out var value) && value == offer) { _offers.Remove(offer.Counter); _offersByCustomer.Remove(offer.Customer); } } private void FinishCustomerReservation(Offer offer) { if (!((Object)(object)offer?.Customer == (Object)null)) { CustomerTradeCardScreen screen = TradeInterop.Screen; if ((Object)(object)screen != (Object)null && TradeInterop.IsScreenOpen(screen) && TradeInterop.CurrentCustomer(screen) == offer.Customer) { TradeInterop.CloseScreen(screen); } TradeInterop.FinishCustomer(offer.Customer); } } private TradeOfferBaselineMessage BuildBaseline() { TradeOfferBaselineMessage tradeOfferBaselineMessage = new TradeOfferBaselineMessage(); List list = new List(_offers.Keys); list.Sort(); for (int i = 0; i < list.Count; i++) { tradeOfferBaselineMessage.Offers.Add(ToState(_offers[list[i]])); } return tradeOfferBaselineMessage; } private static TradeOfferState ToState(Offer offer) { //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) return new TradeOfferState { Counter = offer.Counter, Trading = offer.Data.m_IsTrading, CardL = CopyCard(offer.Data.m_CardData_L), CardR = CopyCard(offer.Data.m_CardData_R), Price = offer.Data.m_SellCardAskPrice, MarketPrice = offer.Data.m_SellCardMarketPrice, PriceSet = offer.Data.m_PriceSet, LastPriceSet = offer.Data.m_LastPriceSet, MaxDeclineCount = offer.Data.m_MaxDeclineCount, DeclineCount = offer.Data.m_DeclineCount, Remaining = TradeInterop.WaitRemaining(offer.Customer), CustomerIndex = offer.CustomerIndex, CustomerGeneration = offer.CustomerGeneration, OfferNonce = offer.Nonce, CustomerFemale = offer.Customer.m_IsFemale, Position = ((Component)offer.Customer).transform.position, Yaw = ((Component)offer.Customer).transform.eulerAngles.y }; } private void Reject(int connectionId, TradeIntentMessage message, string reason) { CoopPlugin.Log.LogInfo((object)("[trade] host rejected " + message.Operation.ToString() + " counter=" + message.Counter + " id=" + message.PredictionId.ToString() + " nonce=" + message.OfferNonce + ": " + reason + ".")); PredictionApi.Rollback((ICoopContext)(object)_context, connectionId, message.PredictionId); } private void SendOfferDelta(Offer offer, bool removed, Guid predictionId, TradeOutcome outcome, int predictionPeer) { //IL_017a: Unknown result type (might be due to invalid IL or missing references) if (_shutdown || !_context.InGame() || offer == null) { return; } ManualLogSource log = CoopPlugin.Log; string[] obj = new string[19] { "[trade] host sending offer counter=", offer.Counter.ToString(), " index=", offer.CustomerIndex.ToString(), " gen=", offer.CustomerGeneration.ToString(), " removed=", removed.ToString(), " nonce=", offer.Nonce.ToString(), " ask=", (offer.Data == null) ? "n/a" : offer.Data.m_SellCardAskPrice.ToString("F2"), " priceSet=", (offer.Data == null) ? "n/a" : offer.Data.m_PriceSet.ToString("F2"), " pred=", null, null, null, null }; Guid guid = predictionId; obj[15] = guid.ToString(); obj[16] = " peers="; obj[17] = _joinedConnections.Count.ToString(); obj[18] = "."; log.LogInfo((object)string.Concat(obj)); TradeOfferState offer2 = (removed ? null : ToState(offer)); foreach (KeyValuePair joinedConnection in _joinedConnections) { if (joinedConnection.Value != null && IsJoinPhase(joinedConnection.Value.State)) { _context.Send(joinedConnection.Key, (INetMessage)(object)new TradeOfferDeltaMessage { PredictionId = ((joinedConnection.Key == predictionPeer) ? predictionId : Guid.Empty), Counter = offer.Counter, Outcome = outcome, Removed = removed, Offer = offer2 }); } } } private static bool TryValidateBid(CustomerTradeData data, float value, out float bid) { bid = 0f; if (data == null || float.IsNaN(value) || float.IsInfinity(value) || value < 0f) { return false; } if (data.m_IsTrading) { return value == 0f; } bid = value; return true; } private static bool SameData(CustomerTradeData left, CustomerTradeData right) { if (left != null && right != null && left.m_IsTrading == right.m_IsTrading && Mathf.Approximately(left.m_PriceSet, right.m_PriceSet) && Mathf.Approximately(left.m_LastPriceSet, right.m_LastPriceSet) && Mathf.Approximately(left.m_SellCardAskPrice, right.m_SellCardAskPrice) && Mathf.Approximately(left.m_SellCardMarketPrice, right.m_SellCardMarketPrice) && left.m_MaxDeclineCount == right.m_MaxDeclineCount && left.m_DeclineCount == right.m_DeclineCount && SameCard(left.m_CardData_L, right.m_CardData_L)) { return SameCard(left.m_CardData_R, right.m_CardData_R); } return false; } private static bool SameCard(CardData left, CardData right) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (left == null || right == null) { return left == right; } if (left.monsterType == right.monsterType && left.expansionType == right.expansionType && left.borderType == right.borderType && left.isFoil == right.isFoil && left.isDestiny == right.isDestiny && left.cardGrade == right.cardGrade) { return left.gradedCardIndex == right.gradedCardIndex; } return false; } private static CardData CopyCard(CardData card) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown if (card == null) { return null; } CardData val = new CardData(); val.CopyData(card); return val; } private static string DescribeCard(CardData card) { if (card != null) { return ((object)Unsafe.As(ref card.expansionType)/*cast due to .constrained prefix*/).ToString() + "/" + ((object)Unsafe.As(ref card.monsterType)/*cast due to .constrained prefix*/).ToString() + "/" + ((object)Unsafe.As(ref card.borderType)/*cast due to .constrained prefix*/).ToString() + (card.isFoil ? "/foil" : "") + (card.isDestiny ? "/destiny" : "") + ((card.cardGrade > 0) ? ("/grade" + card.cardGrade) : ""); } return ""; } private void SignalCustomerManagerReady() { _customerManagerReady = (Object)(object)TradeInterop.Manager != (Object)null && TradeInterop.Customers != null; SendBaselinesToJoined(); } private void SignalTradeScreenReady() { _tradeScreenReady = (Object)(object)TradeInterop.Screen != (Object)null; if (_tradeScreenReady) { RegisterAllCustomers(TradeInterop.Customers); } SendBaselinesToJoined(); } private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _) { SignalCustomerManagerReady(); SignalTradeScreenReady(); RegisterAllCustomers(TradeInterop.Customers); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (!_shutdown) { _customerManagerReady = false; _tradeScreenReady = false; _offers.Clear(); _offersByCustomer.Clear(); _customerIndices.Clear(); _hasObservedDay = false; } } } internal static class TradeInterop { private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const float VanillaWaitSeconds = 60f; internal static CustomerManager Manager => SceneRef.Get(); internal static CustomerTradeCardScreen Screen => Manager?.m_CustomerTradeCardScreen; internal static IList Customers { get { CustomerManager manager = Manager; if (manager == null) { return null; } return manager.GetCustomerList(); } } internal static bool IsReachable(Vector3 player, Vector3 target, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: 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) Vector3 val = player - target; return ((Vector3)(ref val)).sqrMagnitude <= radius * radius; } internal static int CustomerListIndex(Customer customer) { IList customers = Customers; if (customers != null && !((Object)(object)customer == (Object)null)) { return customers.IndexOf(customer); } return -1; } internal static ushort CustomerIdentity(Customer customer) { int num = CustomerListIndex(customer); if (num >= 0 && num <= 65535) { return (ushort)num; } return ushort.MaxValue; } internal static int CustomerGeneration(Customer customer) { if (!((Object)(object)customer == (Object)null)) { return NpcHostBehaviour.GetCustomerGeneration(customer); } return 0; } internal static bool SameCustomer(Customer customer, ushort index, int generation) { if ((Object)(object)customer != (Object)null && CustomerIdentity(customer) == index) { return CustomerGeneration(customer) == generation; } return false; } internal static bool IsWaitingForTrade(Customer customer) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if ((Object)(object)customer != (Object)null) { return (int)customer.m_CurrentState == 18; } return false; } internal static int CounterIndex(Customer customer) { object obj = Read(customer, "m_CurrentTradeCardCashierCounter"); InteractableCashierCounter val = (InteractableCashierCounter)((obj is InteractableCashierCounter) ? obj : null); List list = SceneRef.Get()?.m_CashierCounterList; if (!((Object)(object)val == (Object)null) && list != null) { return list.IndexOf(val); } return -1; } internal static Customer CurrentCustomer(CustomerTradeCardScreen screen) { object obj = Read(screen, "m_CurrentCustomer"); return (Customer)((obj is Customer) ? obj : null); } internal static CustomerTradeData StoredData(Customer customer) { object obj = Read(customer, "m_CustomerTradeData"); return (CustomerTradeData)((obj is CustomerTradeData) ? obj : null); } internal static void SetStoredData(Customer customer, CustomerTradeData data) { Field(customer, "m_CustomerTradeData").SetValue(customer, data); } internal static CustomerTradeData Capture(CustomerTradeCardScreen screen) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown if ((Object)(object)screen == (Object)null) { throw new InvalidOperationException("Customer trade screen is unavailable."); } return new CustomerTradeData { m_IsTrading = Get(screen, "m_IsTrading"), m_PriceSet = Get(screen, "m_PriceSet"), m_LastPriceSet = Get(screen, "m_LastPriceSet"), m_SellCardAskPrice = Get(screen, "m_SellCardAskPrice"), m_SellCardMarketPrice = Get(screen, "m_SellCardMarketPrice"), m_MaxDeclineCount = Get(screen, "m_MaxDeclineCount"), m_DeclineCount = Get(screen, "m_DeclineCount"), m_CardData_L = CopyCard(Get(screen, "m_CardData_L")), m_CardData_R = CopyCard(Get(screen, "m_CardData_R")) }; } internal static CustomerTradeData CopyData(CustomerTradeData data) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown if (data == null) { return null; } return new CustomerTradeData { m_IsTrading = data.m_IsTrading, m_PriceSet = data.m_PriceSet, m_LastPriceSet = data.m_LastPriceSet, m_SellCardAskPrice = data.m_SellCardAskPrice, m_SellCardMarketPrice = data.m_SellCardMarketPrice, m_MaxDeclineCount = data.m_MaxDeclineCount, m_DeclineCount = data.m_DeclineCount, m_CardData_L = CopyCard(data.m_CardData_L), m_CardData_R = CopyCard(data.m_CardData_R) }; } internal static CustomerTradeData DataFromState(TradeOfferState state) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown if (state == null) { return null; } return new CustomerTradeData { m_IsTrading = state.Trading, m_PriceSet = state.PriceSet, m_LastPriceSet = state.LastPriceSet, m_SellCardAskPrice = state.Price, m_SellCardMarketPrice = state.MarketPrice, m_MaxDeclineCount = state.MaxDeclineCount, m_DeclineCount = state.DeclineCount, m_CardData_L = CopyCard(state.CardL), m_CardData_R = CopyCard(state.CardR) }; } internal static bool IsScreenOpen(CustomerTradeCardScreen screen) { if ((Object)(object)screen != (Object)null) { return ((UIScreenBase)screen).IsScreenOpened(); } return false; } internal static bool HasAccepted(CustomerTradeCardScreen screen) { return Get(screen, "m_HasAccepted"); } internal static float PriceSet(CustomerTradeCardScreen screen) { return Get(screen, "m_PriceSet"); } internal static bool IsTrading(CustomerTradeCardScreen screen) { return Get(screen, "m_IsTrading"); } internal static int MaxDeclineCount(CustomerTradeCardScreen screen) { return Get(screen, "m_MaxDeclineCount"); } internal static float WaitRemaining(Customer customer) { float num = Convert.ToSingle(Read(customer, "m_Timer")); return Mathf.Max(0f, 60f - num); } internal static void SetPrice(CustomerTradeCardScreen screen, float price) { Field(screen, "m_PriceSet").SetValue(screen, price); } internal static void ResetCarrierInteraction(Customer customer) { Set(customer, "m_IsPausingAction", false); object obj = Read(customer, "m_ExclaimationMesh"); object obj2 = ((obj is GameObject) ? obj : null); if (obj2 != null) { ((GameObject)obj2).SetActive(true); } object obj3 = Read(customer, "m_InteractCollider"); object obj4 = ((obj3 is GameObject) ? obj3 : null); if (obj4 != null) { ((GameObject)obj4).SetActive(true); } } internal static void SetManagerTrading(bool value) { CustomerManager manager = Manager; if ((Object)(object)manager != (Object)null) { manager.m_IsPlayerTrading = value; } } internal static void OpenData(Customer customer, CustomerTradeData data) { (Screen ?? throw new InvalidOperationException("Customer trade screen is unavailable.")).SetCustomer(customer, CopyData(data)); } internal static bool HasCard(CardData card) { if (card == null) { return false; } if (card.cardGrade <= 0) { return CPlayerData.GetCardAmount(card) > 0; } return CPlayerData.HasGradedCardInAlbum(card); } internal static void FinishCustomer(Customer customer) { if ((Object)(object)customer == (Object)null) { throw new ArgumentNullException("customer"); } object obj = Read(customer, "m_CurrentTradeCardCashierCounter"); InteractableCashierCounter val = (InteractableCashierCounter)((obj is InteractableCashierCounter) ? obj : null); bool flag = IsWaitingForTrade(customer); if (!((Object)(object)val == (Object)null) || flag || StoredData(customer) != null) { SetStoredData(customer, null); Set(customer, "m_Timer", 0f); Set(customer, "m_TimerMax", 0f); Set(customer, "m_IsPausingAction", false); object obj2 = Read(customer, "m_ExclaimationMesh"); GameObject val2 = (GameObject)((obj2 is GameObject) ? obj2 : null); object obj3 = Read(customer, "m_InteractCollider"); object obj4 = ((obj3 is GameObject) ? obj3 : null); if (val2 != null) { val2.SetActive(false); } if (obj4 != null) { ((GameObject)obj4).SetActive(false); } if ((Object)(object)val != (Object)null) { Set(customer, "m_HasTradedCard", true); val.CustomerFinishTradingCard(); Set(customer, "m_CurrentTradeCardCashierCounter", null); } if ((Object)(object)val != (Object)null || flag) { Invoke(customer, "DetermineShopAction"); } } } internal static void RestorePlayerUi() { InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { val.ExitWorkerInteractMode(); val.StopAimLookAt(); AdvancedWalkerController walkerCtrl = val.m_WalkerCtrl; if (walkerCtrl != null) { walkerCtrl.SetStopMovement(false); } val.ExitUIMode(); } GameUIScreen.ResetToolTipVisibility(); GameUIScreen.ResetEnterGoNextDayIndicatorVisible(); TutorialManager.SetGameUIVisible(true); } internal static void ShowAccepted(CustomerTradeCardScreen screen) { Set(screen, "m_HasAccepted", true); GameObject acceptBtn = screen.m_AcceptBtn; if (acceptBtn != null) { acceptBtn.SetActive(false); } GameObject cancelBtn = screen.m_CancelBtn; if (cancelBtn != null) { cancelBtn.SetActive(false); } GameObject letMeThinkBtn = screen.m_LetMeThinkBtn; if (letMeThinkBtn != null) { letMeThinkBtn.SetActive(false); } GameObject doneBtn = screen.m_DoneBtn; if (doneBtn != null) { doneBtn.SetActive(true); } } internal static void CloseScreen(CustomerTradeCardScreen screen) { if (!((Object)(object)screen == (Object)null) && ((UIScreenBase)screen).IsScreenOpened()) { ((UIScreenBase)screen).CloseScreen(); } } internal static object Read(object instance, string name) { if (instance != null) { return Field(instance, name).GetValue(instance); } return null; } private static T Get(object instance, string name) { return (T)Field(instance, name).GetValue(instance); } private static void Set(object instance, string name, object value) { FieldInfo fieldInfo = Field(instance, name); if (value != null && !fieldInfo.FieldType.IsInstanceOfType(value)) { throw new InvalidCastException("Trade field " + fieldInfo.DeclaringType?.Name + "." + name + " expects " + fieldInfo.FieldType.FullName + ", received " + value.GetType().FullName + "."); } fieldInfo.SetValue(instance, value); } private static FieldInfo Field(object instance, string name) { if (instance == null) { throw new ArgumentNullException("instance"); } return Field(instance.GetType(), name); } private static FieldInfo Field(Type type, string name) { return AccessTools.Field(type, name) ?? throw new MissingFieldException(type.FullName, name); } private static object Invoke(object instance, string name, params object[] args) { MethodInfo methodInfo = AccessTools.Method(instance.GetType(), name, (Type[])null, (Type[])null); if (methodInfo == null) { throw new MissingMethodException(instance.GetType().FullName, name); } return methodInfo.Invoke(instance, args); } private static CardData CopyCard(CardData card) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown if (card == null) { return null; } CardData val = new CardData(); val.CopyData(card); return val; } } public enum TradeIntentOperation : byte { Open = 1, Accept, Decline, Think, Close } public enum TradeOutcome : byte { None, Accepted, Declined, Haggle, Refused, WalkedAway, Expired, Thinking } [NetworkMessage] public sealed class TradeIntentMessage : IPredictedMessage, INetMessage { public TradeIntentOperation Operation; public byte Counter; public ushort CustomerIndex; public int CustomerGeneration; public uint OfferNonce; public float Price; public TradeOutcome Result; public TradeOfferState ResultState; public CardData CardReceived; public CardData CardRemoved; public float CoinSpent; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class TradeOfferBaselineMessage : INetMessage { public List Offers = new List(); } [NetworkMessage] public sealed class TradeSessionMessage : IPredictedMessage, INetMessage { public byte Counter; public ushort CustomerIndex; public int CustomerGeneration; public uint OfferNonce; public bool Accepted; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class TradeOfferDeltaMessage : IPredictedMessage, INetMessage { public byte Counter; public TradeOutcome Outcome; public bool Removed; public TradeOfferState Offer; public Guid PredictionId { get; set; } } public sealed class TradeOfferState { public byte Counter; public bool Trading; public CardData CardL; public CardData CardR; public float Price; public float MarketPrice; public float PriceSet; public float LastPriceSet; public int MaxDeclineCount; public int DeclineCount; public float Remaining; public ushort CustomerIndex; public int CustomerGeneration; public uint OfferNonce; public bool CustomerFemale; public Vector3 Position; public float Yaw; } } namespace CardShopCoop.Modules.Tournament { [ClientBehaviour] public sealed class TournamentClientBehaviour : CoopBehaviour { private struct PairingEntry { public int SortedIndex; public int ModelIndex; public bool IsFemale; public bool IsWin; public bool HasResult; public int WinCount; public int WinPoints; public int OMW; public int OOMW; } private static TournamentClientBehaviour _active; private static readonly FieldInfo ScreenMesh = AccessTools.Field(typeof(TournamentPrizeShelf), "m_ScreenMesh"); private CoopRuntimeContext _context; private Harmony _harmony; private TournamentStateMessage _pendingState; private TournamentDeltaMessage _pendingHeader; private readonly Dictionary _pendingPrizes = new Dictionary(); private readonly Dictionary _pendingBracket = new Dictionary(); private int _pendingBracketCount = -1; private readonly List _clientBracket = new List(); private bool _shutdown; private void OnEnable() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { ResetSessionState(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.tournament.client.runtime"); ApplyPatches(_harmony); CEventManager.AddListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded += OnSceneLoaded; } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Tournament client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } ResetSessionState(); _context = null; throw; } } [MessageHandler(typeof(TournamentStateMessage))] private void HandleState(MessageContext context, TournamentStateMessage message) { if (!_shutdown) { _pendingState = message; TryApplyPending(); } } [MessageHandler(typeof(TournamentDeltaMessage))] private void HandleDelta(MessageContext context, TournamentDeltaMessage message) { if (_shutdown) { return; } if (message.Kind == 0) { _pendingHeader = message; } else if (message.Kind == 1) { _pendingPrizes[message.PrizeIndex] = message; } else { _pendingBracketCount = message.BracketCount; for (int i = 0; i < message.Bracket.Count; i++) { TournamentBracketEntry tournamentBracketEntry = message.Bracket[i]; _pendingBracket[tournamentBracketEntry.SortedIndex] = tournamentBracketEntry; } } TryApplyPending(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _pendingState = null; ClearPendingDeltas(); _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded -= OnSceneLoaded; if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; ResetSessionState(); _context = null; } } private void OnDestroy() { Shutdown(); } private void ResetSessionState() { _clientBracket.Clear(); ClearPendingDeltas(); } private void ClearPendingDeltas() { _pendingHeader = null; _pendingPrizes.Clear(); _pendingBracket.Clear(); _pendingBracketCount = -1; } private static CustomerManager CustomerManagerInScene() { return SceneRef.Get(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ResetSessionState(); } } private bool IsTournamentUiReady() { CustomerManager val = CustomerManagerInScene(); if ((Object)(object)val != (Object)null && (Object)(object)val.m_TournamentPairingScreen != (Object)null) { return val.m_TournamentPairingScreen.m_TournamentPairingUIGrpList != null; } return false; } private void TryApplyPending() { if (_shutdown || !_context.InGame() || !IsTournamentUiReady()) { return; } if (_pendingState != null) { TournamentStateMessage pendingState = _pendingState; _pendingState = null; ApplyMessage(pendingState); } if (_pendingHeader != null) { TournamentDeltaMessage pendingHeader = _pendingHeader; _pendingHeader = null; ApplyDelta(pendingHeader); } if (_pendingPrizes.Count > 0) { List list = new List(_pendingPrizes.Values); _pendingPrizes.Clear(); for (int i = 0; i < list.Count; i++) { ApplyDelta(list[i]); } } if (_pendingBracketCount >= 0) { int pendingBracketCount = _pendingBracketCount; List entries = new List(_pendingBracket.Values); _pendingBracket.Clear(); _pendingBracketCount = -1; ApplyBracketEntries(pendingBracketCount, entries); } } private void ApplyMessage(TournamentStateMessage message) { ApplyFull(message); } private void ApplyDelta(TournamentDeltaMessage message) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0015: Expected O, but got Unknown TournamentData val = CPlayerData.m_TournamentData; if (val == null) { TournamentData val2 = new TournamentData(); val = val2; CPlayerData.m_TournamentData = val2; } if (message.Kind == 0) { ApplyHeader(message.Header, val); } else if (message.Kind == 1) { EnsurePrizeLists(val, message.PrizeIndex + 1); ReplacePrizeSlot(val.m_PrizeDataList[message.PrizeIndex], message.Prize); } else { ApplyBracketEntries(message.BracketCount, message.Bracket); } RefreshBoards(val, _clientBracket); } private void ApplyBracketEntries(int bracketCount, IList entries) { if (bracketCount <= 0) { _clientBracket.Clear(); return; } for (int num = _clientBracket.Count - 1; num >= 0; num--) { if (_clientBracket[num].SortedIndex >= bracketCount) { _clientBracket.RemoveAt(num); } } int num2 = 0; while (entries != null && num2 < entries.Count) { PairingEntry incoming = ToPairingEntry(entries[num2]); int num3 = _clientBracket.FindIndex((PairingEntry value) => value.SortedIndex == incoming.SortedIndex); if (num3 >= 0) { _clientBracket[num3] = incoming; } else { _clientBracket.Add(incoming); } num2++; } _clientBracket.Sort((PairingEntry left, PairingEntry right) => left.SortedIndex.CompareTo(right.SortedIndex)); } private void ApplyFull(TournamentStateMessage message) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0020: Expected O, but got Unknown _clientBracket.Clear(); TournamentData val = CPlayerData.m_TournamentData; if (val == null) { TournamentData val2 = new TournamentData(); val = val2; CPlayerData.m_TournamentData = val2; } ApplyHeader(message, val); EnsurePrizeLists(val, message.PrizeSlots.Count); for (int i = 0; i < message.PrizeSlots.Count; i++) { ReplacePrizeSlot(val.m_PrizeDataList[i], message.PrizeSlots[i]); } _clientBracket.Clear(); for (int j = 0; j < message.Bracket.Count; j++) { _clientBracket.Add(ToPairingEntry(message.Bracket[j])); } RefreshBoards(val, _clientBracket); } private static void ApplyHeader(TournamentStateMessage message, TournamentData tournament) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown tournament.m_IsHostingTournament = (message.Flags & 1) != 0; tournament.m_IsTournamentDay = (message.Flags & 2) != 0; tournament.m_IsTournamentDayOver = (message.Flags & 4) != 0; tournament.m_TournamentMaxPlayerCount = message.MaxPlayerCount; tournament.m_TournamentSignedUpCustomerCount = message.SignedUpCustomerCount; tournament.m_TournamentFinishedCurrentRoundCustomerCount = message.FinishedCurrentRoundCustomerCount; tournament.m_TournamentCurrentRound = message.CurrentRound; tournament.m_TournamentMaxRound = message.MaxRound; tournament.m_TournamentFee = message.Fee; tournament.m_TournamentTotalValue = message.TotalValue; CPlayerData.m_IsPlayerRegisteredForTournament = message.IsPlayerRegistered; object obj = CPlayerData.m_PlayerTournamentData; if (obj == null) { CustomerTournamentData val = new CustomerTournamentData(); CPlayerData.m_PlayerTournamentData = val; obj = (object)val; } CustomerTournamentData target = (CustomerTournamentData)obj; if (message.PlayerState != null) { TournamentInterop.ApplyPlayerState(message.PlayerState, target); return; } TournamentInterop.ApplyPlayerState(new TournamentPlayerState { IsTournamentCustomer = message.PlayerIsTournamentCustomer, IsTournamentWin = message.PlayerIsTournamentWin, HasRegisteredTournamentResult = message.PlayerHasRegisteredResult, TournamentCustomerPlayTableIndex = message.PlayerTournamentCustomerPlayTableIndex, TournamentWinCount = message.PlayerTournamentWinCount, TournamentWinPoints = message.PlayerTournamentWinPoints, TournamentPlacementIndex = message.PlayerTournamentPlacementIndex }, target); } private static void EnsurePrizeLists(TournamentData tournament, int count) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown if (tournament.m_PrizeDataList == null) { tournament.m_PrizeDataList = new List(); } while (tournament.m_PrizeDataList.Count < count) { tournament.m_PrizeDataList.Add(new TournamentPrizeDataList { m_PrizeDataList = new List() }); } while (tournament.m_PrizeDataList.Count > count) { tournament.m_PrizeDataList.RemoveAt(tournament.m_PrizeDataList.Count - 1); } } private static void ReplacePrizeSlot(TournamentPrizeDataList target, TournamentPrizeSlot source) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown if (target.m_PrizeDataList == null) { target.m_PrizeDataList = new List(); } target.m_PrizeDataList.Clear(); if (source != null) { for (int i = 0; i < source.Prizes.Count; i++) { TournamentPrizeEntry tournamentPrizeEntry = source.Prizes[i]; target.m_PrizeDataList.Add(new TournamentPrizeData { m_CardData = (tournamentPrizeEntry.HasCard ? tournamentPrizeEntry.Card : null), m_ItemType = tournamentPrizeEntry.ItemType, m_Count = tournamentPrizeEntry.Count }); } } } private void RefreshBoards(TournamentData tournament, List digest) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown CustomerManager val = CustomerManagerInScene(); bool flag = tournament.m_IsTournamentDay || tournament.m_IsTournamentDayOver; ((Component)val.m_TournamentPairingScreen).gameObject.SetActive(flag); List tournamentPrizeShelfList = ShelfManager.GetTournamentPrizeShelfList(); for (int i = 0; i < tournamentPrizeShelfList.Count; i++) { object? value = ScreenMesh.GetValue(tournamentPrizeShelfList[i]); ((GameObject)((value is GameObject) ? value : null)).SetActive(flag); } TournamentPairingScreen tournamentPairingScreen = val.m_TournamentPairingScreen; if (!flag) { tournamentPairingScreen.ShowPairingScreen(false, 0); return; } tournamentPairingScreen.ShowPairingScreen(true, tournament.m_TournamentMaxPlayerCount); tournamentPairingScreen.UpdateCurrentRound(tournament.m_TournamentCurrentRound, tournament.m_TournamentMaxRound); for (int j = 0; j < digest.Count; j++) { PairingEntry pairingEntry = digest[j]; tournamentPairingScreen.OnCustomerRegisterStart(pairingEntry.SortedIndex, pairingEntry.ModelIndex, pairingEntry.IsFemale); CustomerTournamentData val2 = new CustomerTournamentData { m_TournamentCustomerSortedIndex = pairingEntry.SortedIndex, m_IsTournamentWin = pairingEntry.IsWin, m_HasRegisteredTournamentResult = pairingEntry.HasResult, m_TournamentWinCount = pairingEntry.WinCount, m_TournamentWinPoints = pairingEntry.WinPoints, m_TournamentOMW = pairingEntry.OMW, m_TournamentOOMW = pairingEntry.OOMW }; tournamentPairingScreen.m_TournamentPairingUIGrpList[pairingEntry.SortedIndex / 2].UpdateCustomerData(val2); } } private static PairingEntry ToPairingEntry(TournamentBracketEntry entry) { return new PairingEntry { SortedIndex = entry.SortedIndex, ModelIndex = entry.ModelIndex, IsFemale = ((entry.Flags & 1) != 0), IsWin = ((entry.Flags & 2) != 0), HasResult = ((entry.Flags & 4) != 0), WinCount = entry.WinCount, WinPoints = entry.WinPoints, OMW = entry.OMW, OOMW = entry.OOMW }; } private static void ApplyPatches(Harmony harmony) { TryPatch(harmony, typeof(HostTournamentScreen), "OnPressConfirm"); TryPatch(harmony, typeof(HostTournamentScreen), "OnPressCancel"); TryPatch(harmony, typeof(HostTournamentScreen), "ConfirmCancelTournament"); TryPatch(harmony, typeof(HostTournamentScreen), "OnPressPrizeSetup"); TryPatch(harmony, typeof(HostTournamentScreen), "OnPressPlayerSignUpTournament"); TryPatch(harmony, typeof(HostTournamentScreen), "OnPressPlayerSignOutTournament"); TryPatch(harmony, typeof(CustomerManager), "Start", "ReadinessPostfix"); TryPatch(harmony, typeof(CustomerManager), "Init", "ReadinessPostfix"); TryPatch(harmony, typeof(TournamentPairingScreen), "Awake", "ReadinessPostfix"); TryBlockDayStarted(harmony, typeof(TournamentPrizeShelf)); } private static void TryPatch(Harmony harmony, Type type, string method, string postfix = "ScheduleBlockPrefix") { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("Tournament client patch target missing: " + type.Name + "." + method)); } else { harmony.Patch((MethodBase)methodInfo, (!(postfix == "ScheduleBlockPrefix")) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(TournamentClientBehaviour), postfix, (Type[])null), (postfix == "ScheduleBlockPrefix") ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(TournamentClientBehaviour), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Tournament client patch failed " + type.Name + "." + method + ": " + ex.Message)); } } private static void TryBlockDayStarted(Harmony harmony, Type type) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(type, "OnDayStarted", (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("Tournament client patch target missing: " + type.Name + ".OnDayStarted")); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TournamentClientBehaviour), "BlockDayStartedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Tournament client patch failed " + type.Name + ".OnDayStarted: " + ex.Message)); } } private static bool BlockDayStartedPrefix() { return false; } private static bool ScheduleBlockPrefix() { if ((Object)(object)_active == (Object)null || _active._shutdown) { return true; } _active._context.SetStatusLine?.Invoke("the host schedules tournaments", 3f); return false; } private static void ReadinessPostfix() { _active?.TryApplyPending(); } private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { TryApplyPending(); } } [ServerBehaviour] public sealed class TournamentHostBehaviour : CoopBehaviour { private const int PrizeSlotCount = 8; private const int BracketBatchSize = 8; private const int MaxBracketEntries = 64; private static TournamentHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private void OnEnable() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; try { _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.tournament.host.runtime"); ApplyPatches(_harmony); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Tournament host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _context = null; throw; } } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State) && _context.InGame() && TryBuildState(out var state)) { state.Full = true; _context.Send(connection.Id, (INetMessage)(object)state); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } private static CustomerManager CustomerManagerInScene() { return SceneRef.Get(); } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private void NotifyHeader() { if (!_shutdown && _context.InGame() && TryBuildState(out var _)) { Publish(new TournamentDeltaMessage { Kind = 0, Header = BuildHeaderState(CPlayerData.m_TournamentData) }); } } private void NotifyPrizes() { if (!_shutdown && _context.InGame() && TryBuildState(out var state)) { for (int i = 0; i < state.PrizeSlots.Count; i++) { Publish(new TournamentDeltaMessage { Kind = 1, PrizeIndex = i, Prize = state.PrizeSlots[i] }); } } } private void NotifyPrize(int index) { if (!_shutdown && _context.InGame() && TryBuildState(out var state) && index >= 0 && index < state.PrizeSlots.Count) { Publish(new TournamentDeltaMessage { Kind = 1, PrizeIndex = index, Prize = state.PrizeSlots[index] }); } } private void NotifyBracket() { if (_shutdown || !_context.InGame() || !TryBuildState(out var state)) { return; } for (int i = 0; i < Math.Max(1, state.Bracket.Count); i += 8) { TournamentDeltaMessage tournamentDeltaMessage = new TournamentDeltaMessage { Kind = 2, BracketStart = i, BracketCount = state.Bracket.Count }; for (int j = i; j < i + 8 && j < state.Bracket.Count; j++) { tournamentDeltaMessage.Bracket.Add(state.Bracket[j]); } Publish(tournamentDeltaMessage); } } private bool TryBuildState(out TournamentStateMessage state) { state = null; if (CPlayerData.m_TournamentData == null) { return false; } state = BuildState(CPlayerData.m_TournamentData); return true; } private void Publish(TournamentDeltaMessage message) { if (!_shutdown && message != null && _context.InGame()) { _context.Broadcast((INetMessage)(object)message); } } private TournamentStateMessage BuildState(TournamentData tournament) { TournamentStateMessage tournamentStateMessage = BuildHeaderState(tournament); AddPrizeSlots(tournamentStateMessage, tournament); AddBracketEntries(tournamentStateMessage); return tournamentStateMessage; } private TournamentStateMessage BuildHeaderState(TournamentData tournament) { TournamentStateMessage tournamentStateMessage = new TournamentStateMessage { Flags = (byte)((tournament.m_IsHostingTournament ? 1u : 0u) | (uint)(tournament.m_IsTournamentDay ? 2 : 0) | (uint)(tournament.m_IsTournamentDayOver ? 4 : 0)), MaxPlayerCount = tournament.m_TournamentMaxPlayerCount, SignedUpCustomerCount = tournament.m_TournamentSignedUpCustomerCount, FinishedCurrentRoundCustomerCount = tournament.m_TournamentFinishedCurrentRoundCustomerCount, CurrentRound = tournament.m_TournamentCurrentRound, MaxRound = tournament.m_TournamentMaxRound, Fee = tournament.m_TournamentFee, TotalValue = tournament.m_TournamentTotalValue, IsPlayerRegistered = CPlayerData.m_IsPlayerRegisteredForTournament }; CustomerTournamentData playerTournamentData = CPlayerData.m_PlayerTournamentData; tournamentStateMessage.PlayerState = TournamentInterop.BuildPlayerState(playerTournamentData); if (playerTournamentData != null) { tournamentStateMessage.PlayerIsTournamentCustomer = tournamentStateMessage.PlayerState.IsTournamentCustomer; tournamentStateMessage.PlayerIsTournamentWin = tournamentStateMessage.PlayerState.IsTournamentWin; tournamentStateMessage.PlayerHasRegisteredResult = tournamentStateMessage.PlayerState.HasRegisteredTournamentResult; tournamentStateMessage.PlayerTournamentCustomerPlayTableIndex = tournamentStateMessage.PlayerState.TournamentCustomerPlayTableIndex; tournamentStateMessage.PlayerTournamentWinCount = tournamentStateMessage.PlayerState.TournamentWinCount; tournamentStateMessage.PlayerTournamentWinPoints = tournamentStateMessage.PlayerState.TournamentWinPoints; tournamentStateMessage.PlayerTournamentPlacementIndex = tournamentStateMessage.PlayerState.TournamentPlacementIndex; } return tournamentStateMessage; } private static void AddPrizeSlots(TournamentStateMessage state, TournamentData tournament) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) List list = tournament?.m_PrizeDataList; int num = ((list != null) ? Math.Min(8, list.Count) : 0); for (int i = 0; i < num; i++) { TournamentPrizeSlot tournamentPrizeSlot = new TournamentPrizeSlot(); List list2 = list[i]?.m_PrizeDataList; int num2 = ((list2 != null) ? Math.Min(list2.Count, 64) : 0); for (int j = 0; j < num2; j++) { TournamentPrizeData val = list2[j]; bool flag = val != null && val.m_CardData != null; tournamentPrizeSlot.Prizes.Add(new TournamentPrizeEntry { HasCard = flag, Card = (flag ? val.m_CardData : null), ItemType = (EItemType)((val != null) ? ((int)val.m_ItemType) : 0), Count = (val?.m_Count ?? 0) }); } state.PrizeSlots.Add(tournamentPrizeSlot); } } private static void AddBracketEntries(TournamentStateMessage state) { List list = CustomerManagerInScene()?.m_TournamentSortedCustomerList; int num = ((list != null) ? Math.Min(64, list.Count) : 0); CustomerTournamentData playerTournamentData = CPlayerData.m_PlayerTournamentData; for (int i = 0; i < num; i++) { Customer val = list[i]; CustomerTournamentData val2 = ((val != null) ? val.GetCustomerTournamentData() : null); if (val2 == null) { if (playerTournamentData != null && playerTournamentData.m_IsTournamentCustomer && playerTournamentData.m_TournamentCustomerSortedIndex == i) { state.Bracket.Add(new TournamentBracketEntry { SortedIndex = (byte)Mathf.Clamp(playerTournamentData.m_TournamentCustomerSortedIndex, 0, 255), ModelIndex = playerTournamentData.m_CharacterModelIndex, Flags = (byte)((playerTournamentData.m_IsFemale ? 1u : 0u) | (uint)(playerTournamentData.m_IsTournamentWin ? 2 : 0) | (uint)(playerTournamentData.m_HasRegisteredTournamentResult ? 4 : 0)), WinCount = playerTournamentData.m_TournamentWinCount, WinPoints = playerTournamentData.m_TournamentWinPoints, OMW = playerTournamentData.m_TournamentOMW, OOMW = playerTournamentData.m_TournamentOOMW }); } } else { state.Bracket.Add(new TournamentBracketEntry { SortedIndex = (byte)Mathf.Clamp(val2.m_TournamentCustomerSortedIndex, 0, 255), ModelIndex = val.GetCustomerModelIndex(), Flags = (byte)((val.m_IsFemale ? 1u : 0u) | (uint)(val2.m_IsTournamentWin ? 2 : 0) | (uint)(val2.m_HasRegisteredTournamentResult ? 4 : 0)), WinCount = val2.m_TournamentWinCount, WinPoints = val2.m_TournamentWinPoints, OMW = val2.m_TournamentOMW, OOMW = val2.m_TournamentOOMW }); } } } private static void ApplyPatches(Harmony harmony) { TryPatch(harmony, typeof(HostTournamentScreen), "OnPressConfirm", null, "TournamentChangedPostfix"); TryPatch(harmony, typeof(HostTournamentScreen), "ConfirmCancelTournament", null, "TournamentChangedPostfix"); TryPatch(harmony, typeof(HostTournamentScreen), "OnPressPlayerSignUpTournament", null, "TournamentChangedPostfix"); TryPatch(harmony, typeof(HostTournamentScreen), "OnPressPlayerSignOutTournament", null, "TournamentChangedPostfix"); TryPatch(harmony, typeof(HostTournamentSelectPrizeScreen), "OnPressSelectPrizeItem", null, "PrizeChangedPostfix"); TryPatch(harmony, typeof(HostTournamentSelectPrizeScreen), "OnPressSelectPrizeCard", null, "PrizeChangedPostfix"); TryPatch(harmony, typeof(CustomerManager), "OnDayStarted", "DayStartedPrefix", "DayStartedPostfix"); TryPatch(harmony, typeof(CustomerManager), "OnCustomerFinishTournamentRound", null, "BracketChangedPostfix"); } private static void TryPatch(Harmony harmony, Type type, string method, string prefix, string postfix) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("Tournament patch target missing: " + type.Name + "." + method)); } else { harmony.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(TournamentHostBehaviour), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(TournamentHostBehaviour), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Tournament patch failed " + type.Name + "." + method + ": " + ex.Message)); } } private static void TournamentChangedPostfix() { if (!((Object)(object)_active == (Object)null)) { _active.NotifyHeader(); _active.NotifyBracket(); } } private static void PrizeChangedPostfix(HostTournamentSelectPrizeScreen __instance) { if (!((Object)(object)_active == (Object)null)) { int num = TournamentInterop.PrizeIndex(__instance); if (num >= 0) { _active.NotifyPrize(num); } else { _active.NotifyPrizes(); } } } private static void BracketChangedPostfix() { _active?.NotifyHeader(); _active?.NotifyBracket(); } private static void DayStartedPrefix() { _ = (Object)(object)_active != (Object)null; } private static void DayStartedPostfix() { _active?.NotifyHeader(); _active?.NotifyBracket(); } } internal static class TournamentInterop { private static readonly FieldInfo IsTournamentCustomer = Field("m_IsTournamentCustomer"); private static readonly FieldInfo IsTournamentWin = Field("m_IsTournamentWin"); private static readonly FieldInfo HasFinishCurrentTournamentRound = Field("m_HasFinishCurrentTournamentRound"); private static readonly FieldInfo HasRegisteredTournamentStart = Field("m_HasRegisteredTournamentStart"); private static readonly FieldInfo HasRegisteredTournamentResult = Field("m_HasRegisteredTournamentResult"); private static readonly FieldInfo TournamentCustomerIndex = Field("m_TournamentCustomerIndex"); private static readonly FieldInfo TournamentCustomerSortedIndex = Field("m_TournamentCustomerSortedIndex"); private static readonly FieldInfo TournamentCustomerPlayTableIndex = Field("m_TournamentCustomerPlayTableIndex"); private static readonly FieldInfo TournamentWinCount = Field("m_TournamentWinCount"); private static readonly FieldInfo TournamentWinPoints = Field("m_TournamentWinPoints"); private static readonly FieldInfo TournamentOMW = Field("m_TournamentOMW"); private static readonly FieldInfo TournamentOOMW = Field("m_TournamentOOMW"); private static readonly FieldInfo CurrentPrizeDataIndex = Field("m_CurrentPrizeDataIndex"); private static readonly FieldInfo TournamentPlacementIndex = Field("m_TournamentPlacementIndex"); private static readonly FieldInfo CharacterModelIndex = Field("m_CharacterModelIndex"); private static readonly FieldInfo IsFemale = Field("m_IsFemale"); private static readonly FieldInfo TournamentOpponentIndexList = Field("m_TournamentOpponentIndexList"); private static readonly FieldInfo PrizeDataList = Field("m_PrizeDataList"); private static readonly FieldInfo TargetPrizeData = Field("m_TargetPrizeData"); private static readonly FieldInfo CurrentPrizeIndex = AccessTools.Field(typeof(HostTournamentSelectPrizeScreen), "m_CurrentPrizeIndex"); private static FieldInfo Field(string name) { return ReflectionSurface.RequiredField(typeof(CustomerTournamentData), name); } internal static TournamentPlayerState BuildPlayerState(CustomerTournamentData source) { TournamentPlayerState tournamentPlayerState = new TournamentPlayerState(); if (source == null) { return tournamentPlayerState; } tournamentPlayerState.IsTournamentCustomer = (bool)IsTournamentCustomer.GetValue(source); tournamentPlayerState.IsTournamentWin = (bool)IsTournamentWin.GetValue(source); tournamentPlayerState.HasFinishCurrentTournamentRound = (bool)HasFinishCurrentTournamentRound.GetValue(source); tournamentPlayerState.HasRegisteredTournamentStart = (bool)HasRegisteredTournamentStart.GetValue(source); tournamentPlayerState.HasRegisteredTournamentResult = (bool)HasRegisteredTournamentResult.GetValue(source); tournamentPlayerState.TournamentCustomerIndex = (int)TournamentCustomerIndex.GetValue(source); tournamentPlayerState.TournamentCustomerSortedIndex = (int)TournamentCustomerSortedIndex.GetValue(source); tournamentPlayerState.TournamentCustomerPlayTableIndex = (int)TournamentCustomerPlayTableIndex.GetValue(source); tournamentPlayerState.TournamentWinCount = (int)TournamentWinCount.GetValue(source); tournamentPlayerState.TournamentWinPoints = (int)TournamentWinPoints.GetValue(source); tournamentPlayerState.TournamentOMW = (int)TournamentOMW.GetValue(source); tournamentPlayerState.TournamentOOMW = (int)TournamentOOMW.GetValue(source); tournamentPlayerState.CurrentPrizeDataIndex = (int)CurrentPrizeDataIndex.GetValue(source); tournamentPlayerState.TournamentPlacementIndex = (int)TournamentPlacementIndex.GetValue(source); tournamentPlayerState.CharacterModelIndex = (int)CharacterModelIndex.GetValue(source); tournamentPlayerState.IsFemale = (bool)IsFemale.GetValue(source); if (TournamentOpponentIndexList.GetValue(source) is List collection) { tournamentPlayerState.TournamentOpponentIndexList.AddRange(collection); } if (PrizeDataList.GetValue(source) is List list) { for (int i = 0; i < list.Count; i++) { tournamentPlayerState.PrizeDataList.Add(BuildPrize(list[i])); } } object? value = TargetPrizeData.GetValue(source); TournamentPrizeData val = (TournamentPrizeData)((value is TournamentPrizeData) ? value : null); tournamentPlayerState.HasTargetPrizeData = val != null; if (val != null) { tournamentPlayerState.TargetPrizeData = BuildPrize(val); } return tournamentPlayerState; } internal static int PrizeIndex(HostTournamentSelectPrizeScreen screen) { if (!((Object)(object)screen == (Object)null) && !(CurrentPrizeIndex == null)) { return (int)CurrentPrizeIndex.GetValue(screen); } return -1; } internal static void ApplyPlayerState(TournamentPlayerState state, CustomerTournamentData target) { if (state == null || target == null) { return; } IsTournamentCustomer.SetValue(target, state.IsTournamentCustomer); IsTournamentWin.SetValue(target, state.IsTournamentWin); HasFinishCurrentTournamentRound.SetValue(target, state.HasFinishCurrentTournamentRound); HasRegisteredTournamentStart.SetValue(target, state.HasRegisteredTournamentStart); HasRegisteredTournamentResult.SetValue(target, state.HasRegisteredTournamentResult); TournamentCustomerIndex.SetValue(target, state.TournamentCustomerIndex); TournamentCustomerSortedIndex.SetValue(target, state.TournamentCustomerSortedIndex); TournamentCustomerPlayTableIndex.SetValue(target, state.TournamentCustomerPlayTableIndex); TournamentWinCount.SetValue(target, state.TournamentWinCount); TournamentWinPoints.SetValue(target, state.TournamentWinPoints); TournamentOMW.SetValue(target, state.TournamentOMW); TournamentOOMW.SetValue(target, state.TournamentOOMW); CurrentPrizeDataIndex.SetValue(target, state.CurrentPrizeDataIndex); TournamentPlacementIndex.SetValue(target, state.TournamentPlacementIndex); CharacterModelIndex.SetValue(target, state.CharacterModelIndex); IsFemale.SetValue(target, state.IsFemale); List list = new List(); if (state.TournamentOpponentIndexList != null) { list.AddRange(state.TournamentOpponentIndexList); } TournamentOpponentIndexList.SetValue(target, list); List list2 = new List(); if (state.PrizeDataList != null) { for (int i = 0; i < state.PrizeDataList.Count; i++) { list2.Add(ToPrize(state.PrizeDataList[i])); } } PrizeDataList.SetValue(target, list2); TargetPrizeData.SetValue(target, state.HasTargetPrizeData ? ToPrize(state.TargetPrizeData) : null); } private static TournamentPrizeEntry BuildPrize(TournamentPrizeData source) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) bool flag = source != null && source.m_CardData != null; return new TournamentPrizeEntry { HasCard = flag, Card = (flag ? source.m_CardData : null), ItemType = (EItemType)((source != null) ? ((int)source.m_ItemType) : 0), Count = (source?.m_Count ?? 0) }; } private static TournamentPrizeData ToPrize(TournamentPrizeEntry source) { //IL_0003: 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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown if (source != null) { return new TournamentPrizeData { m_CardData = (source.HasCard ? source.Card : null), m_ItemType = source.ItemType, m_Count = source.Count }; } return null; } } [NetworkMessage] public sealed class TournamentStateMessage : INetMessage { public bool Full = true; public byte Flags; public int MaxPlayerCount; public int SignedUpCustomerCount; public int FinishedCurrentRoundCustomerCount; public int CurrentRound; public int MaxRound; public float Fee; public float TotalValue; public List PrizeSlots = new List(); public List Bracket = new List(); public bool IsPlayerRegistered; public bool PlayerIsTournamentCustomer; public bool PlayerIsTournamentWin; public bool PlayerHasRegisteredResult; public int PlayerTournamentCustomerPlayTableIndex; public int PlayerTournamentWinCount; public int PlayerTournamentWinPoints; public int PlayerTournamentPlacementIndex; public TournamentPlayerState PlayerState; } [NetworkMessage] public sealed class TournamentDeltaMessage : INetMessage { public byte Kind; public TournamentStateMessage Header; public int PrizeIndex; public TournamentPrizeSlot Prize; public int BracketStart; public int BracketCount; public List Bracket = new List(); } public sealed class TournamentPlayerState { public bool IsTournamentCustomer; public bool IsTournamentWin; public bool HasFinishCurrentTournamentRound; public bool HasRegisteredTournamentStart; public bool HasRegisteredTournamentResult; public int TournamentCustomerIndex; public int TournamentCustomerSortedIndex; public int TournamentCustomerPlayTableIndex; public int TournamentWinCount; public int TournamentWinPoints; public int TournamentOMW; public int TournamentOOMW; public int CurrentPrizeDataIndex; public int TournamentPlacementIndex; public int CharacterModelIndex; public bool IsFemale; public List TournamentOpponentIndexList = new List(); public List PrizeDataList = new List(); public bool HasTargetPrizeData; public TournamentPrizeEntry TargetPrizeData; } public sealed class TournamentPrizeSlot { public List Prizes = new List(); } public sealed class TournamentPrizeEntry { public bool HasCard; public CardData Card; public EItemType ItemType; public int Count; } public sealed class TournamentBracketEntry { public byte SortedIndex; public int ModelIndex; public byte Flags; public int WinCount; public int WinPoints; public int OMW; public int OOMW; } } namespace CardShopCoop.Modules.StoreAccess { [ClientBehaviour] public sealed class StoreAccessClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(InteractableOpenCloseSign), "OnMouseButtonUp")] private static class OpenSignPatch { [HarmonyPrefix] private static void Prefix(out bool __state) { __state = CPlayerData.m_IsShopOpen; } [HarmonyPostfix] private static void Postfix(bool __state) { _active?.ForwardToggle(0, __state, CPlayerData.m_IsShopOpen); } } [HarmonyPatch(typeof(InteractableOpenCloseSign), "OnDayStarted")] private static class GuestDayStartedBlockPatch { [HarmonyPrefix] private static bool Prefix() { return false; } } [HarmonyPatch(typeof(InteractableWarehouseAllowEnterSign), "OnMouseButtonUp")] private static class WarehouseSignPatch { [HarmonyPrefix] private static void Prefix(out bool __state) { __state = CPlayerData.m_IsWarehouseDoorClosed; } [HarmonyPostfix] private static void Postfix(bool __state) { _active?.ForwardToggle(1, __state, CPlayerData.m_IsWarehouseDoorClosed); } } [HarmonyPatch(typeof(InteractableOpenCloseSign), "OnEnable")] private static class OpenSignReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.TryApplyPending(); } } [HarmonyPatch(typeof(InteractableWarehouseAllowEnterSign), "OnEnable")] private static class WarehouseSignReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.TryApplyPending(); } } private static StoreAccessClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private StoreAccessStateMessage _pendingState; private StoreAccessDeltaMessage _pendingDelta; private void OnEnable() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; bool flag2 = false; try { ResetSessionState(); StoreAccessInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; flag2 = true; _harmony = new Harmony("dev.meepen.cardshopcoop.store-access.client"); _harmony.CreateClassProcessor(typeof(OpenSignPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WarehouseSignPatch)).Patch(); _harmony.CreateClassProcessor(typeof(OpenSignReadyPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WarehouseSignReadyPatch)).Patch(); _harmony.CreateClassProcessor(typeof(GuestDayStartedBlockPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Store access client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag2) { CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; } if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } ResetSessionState(); StoreAccessInterop.Reset(); _context = null; throw; } } [MessageHandler(typeof(StoreAccessStateMessage))] private void HandleState(MessageContext context, StoreAccessStateMessage message) { if (!_shutdown) { _pendingState = message; _pendingDelta = null; TryApplyPending(); } } [MessageHandler(typeof(StoreAccessDeltaMessage))] private void HandleDelta(MessageContext context, StoreAccessDeltaMessage message) { if (!_shutdown) { PredictionApi.ApplyAuthoritative(message.PredictionId, delegate { ApplyDeltaAuthoritative(message); }); } } private void ApplyDeltaAuthoritative(StoreAccessDeltaMessage message) { bool shopChanged = message.HasShopOpen && CPlayerData.m_IsShopOpen != message.IsShopOpen; bool warehouseChanged = message.HasWarehouseDoorClosed && CPlayerData.m_IsWarehouseDoorClosed != message.IsWarehouseDoorClosed; if (message.HasShopOpen) { CPlayerData.m_IsShopOpen = message.IsShopOpen; } if (message.HasWarehouseDoorClosed) { CPlayerData.m_IsWarehouseDoorClosed = message.IsWarehouseDoorClosed; } if (_pendingState != null) { if (message.HasShopOpen) { _pendingState.IsShopOpen = message.IsShopOpen; } if (message.HasWarehouseDoorClosed) { _pendingState.IsWarehouseDoorClosed = message.IsWarehouseDoorClosed; } _pendingState.Animate = false; } else if (_context != null && _context.InGame() && DeltaSignsReady(message)) { ApplyDeltaVisuals(message, shopChanged, warehouseChanged); _pendingDelta = null; LogDelta("applied", message); } else { _pendingDelta = message; LogDelta("held until the sign is ready", message); } } private static void LogDelta(string action, StoreAccessDeltaMessage message) { CoopPlugin.Log.LogInfo((object)("[store-access] delta " + action + " shop=" + ((!message.HasShopOpen) ? "-" : (message.IsShopOpen ? "open" : "closed")) + " warehouse=" + ((!message.HasWarehouseDoorClosed) ? "-" : (message.IsWarehouseDoorClosed ? "closed" : "open")) + ".")); } private static bool DeltaSignsReady(StoreAccessDeltaMessage message) { if (!message.HasShopOpen || (Object)(object)StoreAccessInterop.FindOpenSign() != (Object)null) { if (message.HasWarehouseDoorClosed && !((Object)(object)StoreAccessInterop.FindWarehouseSign() != (Object)null)) { return (Object)(object)StoreAccessInterop.FindUnlockRoomManager() != (Object)null; } return true; } return false; } private void TryApplyPending() { if (_shutdown || _context == null || !_context.InGame()) { return; } if (_pendingState != null && StoreAccessInterop.IsSceneReady()) { Apply(_pendingState); _pendingState = null; _pendingDelta = null; } if (_pendingDelta == null || !DeltaSignsReady(_pendingDelta)) { return; } if (_pendingDelta.HasShopOpen) { StoreAccessInterop.RefreshOpenMesh(StoreAccessInterop.FindOpenSign()); } if (_pendingDelta.HasWarehouseDoorClosed) { InteractableWarehouseAllowEnterSign val = StoreAccessInterop.FindWarehouseSign(); if ((Object)(object)val != (Object)null) { StoreAccessInterop.RefreshWarehouseMesh(val); } else { StoreAccessInterop.RefreshWarehouseAccess(SceneRef.Get()); } } LogDelta("painted onto the ready sign", _pendingDelta); _pendingDelta = null; } private static void Apply(StoreAccessStateMessage message) { CPlayerData.m_IsShopOpen = message.IsShopOpen; CPlayerData.m_IsWarehouseDoorClosed = message.IsWarehouseDoorClosed; InteractableOpenCloseSign sign = StoreAccessInterop.FindOpenSign(); if (!message.Animate || !StoreAccessInterop.PlayOpenAnimation(sign)) { StoreAccessInterop.RefreshOpenMesh(sign); } InteractableWarehouseAllowEnterSign val = StoreAccessInterop.FindWarehouseSign(); if ((Object)(object)val != (Object)null && (!message.Animate || !StoreAccessInterop.PlayWarehouseAnimation(val))) { StoreAccessInterop.RefreshWarehouseMesh(val); } else if ((Object)(object)val == (Object)null) { StoreAccessInterop.RefreshWarehouseAccess(SceneRef.Get()); } } private static void ApplyDeltaVisuals(StoreAccessDeltaMessage message, bool shopChanged, bool warehouseChanged) { if (message.HasShopOpen) { InteractableOpenCloseSign sign = StoreAccessInterop.FindOpenSign(); if ((!shopChanged || !message.Animate || !StoreAccessInterop.PlayOpenAnimation(sign)) && (shopChanged || !StoreAccessInterop.IsOpenSwapping(sign))) { StoreAccessInterop.RefreshOpenMesh(sign); } } if (message.HasWarehouseDoorClosed) { InteractableWarehouseAllowEnterSign val = StoreAccessInterop.FindWarehouseSign(); if ((Object)(object)val == (Object)null) { StoreAccessInterop.RefreshWarehouseAccess(SceneRef.Get()); } else if ((!warehouseChanged || !message.Animate || !StoreAccessInterop.PlayWarehouseAnimation(val)) && (warehouseChanged || !StoreAccessInterop.IsWarehouseSwapping(val))) { StoreAccessInterop.RefreshWarehouseMesh(val); } } } private void ForwardToggle(byte which, bool before, bool after) { if (!_shutdown && _joined && _context != null && _context.InGame() && after != before) { PredictionApi.Predict((which == 0) ? "store-access:shop" : "store-access:warehouse", delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new StoreAccessToggleMessage { PredictionId = predictionId, Which = which }); }, delegate { ToggleLocal(which); }, delegate { ToggleLocal(which); }); } } private static void ToggleLocal(byte which) { if (which == 0) { ApplyLocal(0, !CPlayerData.m_IsShopOpen); } else { ApplyLocal(1, !CPlayerData.m_IsWarehouseDoorClosed); } } private static void ApplyLocal(byte which, bool value) { if (which == 0) { CPlayerData.m_IsShopOpen = value; StoreAccessInterop.RefreshOpenMesh(StoreAccessInterop.FindOpenSign()); } else { CPlayerData.m_IsWarehouseDoorClosed = value; StoreAccessInterop.RefreshWarehouseMesh(StoreAccessInterop.FindWarehouseSign()); } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { StoreAccessInterop.Reset(); TryApplyPending(); } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; TryApplyPending(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ResetSessionState(); } } private void ResetSessionState() { _joined = false; _pendingState = null; _pendingDelta = null; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } ResetSessionState(); StoreAccessInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class StoreAccessHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(InteractableOpenCloseSign), "OnMouseButtonUp")] private static class SignChangedPatch { [HarmonyPostfix] private static void Postfix() { if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(BuildDelta(Guid.Empty, shopOpen: true, warehouseClosed: false)); } } } [HarmonyPatch(typeof(InteractableWarehouseAllowEnterSign), "OnMouseButtonUp")] private static class WarehouseSignChangedPatch { [HarmonyPostfix] private static void Postfix() { if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(BuildDelta(Guid.Empty, shopOpen: false, warehouseClosed: true)); } } } [HarmonyPatch(typeof(InteractableOpenCloseSign), "OnDayStarted")] private static class StoreOpenedPatch { [HarmonyPostfix] private static void Postfix() { if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(BuildDelta(Guid.Empty, shopOpen: true, warehouseClosed: true)); } } } private static StoreAccessHostBehaviour _active; private readonly HashSet _fullyJoined = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _applyingIntent; private void OnEnable() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { StoreAccessInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.store-access.host"); _harmony.CreateClassProcessor(typeof(SignChangedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WarehouseSignChangedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(StoreOpenedPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Store access host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } StoreAccessInterop.Reset(); _context = null; throw; } } [OnFullyJoined] private void SendInitialState(PeerConnection connection) { if (connection != null) { _fullyJoined.Add(connection.Id); SendState(connection.Id); } } [OnClientDisconnected] private void ForgetPeer(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _fullyJoined.Remove(connection.Id); } } [MessageHandler(typeof(StoreAccessToggleMessage))] private void HandleToggle(MessageContext context, StoreAccessToggleMessage message) { if (!IsPeerMessage(context) || message == null) { return; } int id = context.Connection.Id; bool flag = false; try { if (message.Which > 1) { Rollback(context, message); } else if (!StoreAccessInterop.IsSceneReady()) { Rollback(context, message); } else if (message.Which == 0) { InteractableOpenCloseSign val = StoreAccessInterop.FindOpenSign(); if ((Object)(object)val == (Object)null) { Rollback(context, message); } else { bool isShopOpen = CPlayerData.m_IsShopOpen; _applyingIntent = true; try { ((InteractableObject)val).OnMouseButtonUp(); } finally { _applyingIntent = false; } flag = CPlayerData.m_IsShopOpen != isShopOpen; if (!flag) { Rollback(context, message); } } } else { InteractableWarehouseAllowEnterSign val2 = StoreAccessInterop.FindWarehouseSign(); if ((Object)(object)val2 == (Object)null) { Rollback(context, message); } else { bool isWarehouseDoorClosed = CPlayerData.m_IsWarehouseDoorClosed; _applyingIntent = true; try { ((InteractableObject)val2).OnMouseButtonUp(); } finally { _applyingIntent = false; } flag = CPlayerData.m_IsWarehouseDoorClosed != isWarehouseDoorClosed; if (!flag) { Rollback(context, message); } } } } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Store access toggle failed for peer " + id + ": " + ex)); Rollback(context, message); } if (flag) { BroadcastDelta(BuildDelta(message.PredictionId, message.Which == 0, message.Which == 1)); } } private void BroadcastDelta(StoreAccessDeltaMessage message) { if (!_shutdown && _context != null && _context.InGame() && message != null) { CoopPlugin.Log.LogInfo((object)("[store-access] host broadcast shop=" + ((!message.HasShopOpen) ? "-" : (message.IsShopOpen ? "open" : "closed")) + " warehouse=" + ((!message.HasWarehouseDoorClosed) ? "-" : (message.IsWarehouseDoorClosed ? "closed" : "open")) + " pred=" + message.PredictionId.ToString() + ".")); _context.Broadcast((INetMessage)(object)message); } } private void SendState(int peer) { if (!_shutdown && _context != null && _context.InGame()) { _context.Send(peer, (INetMessage)(object)BuildState(animate: false)); } } private static StoreAccessDeltaMessage BuildDelta(Guid predictionId, bool shopOpen, bool warehouseClosed) { return new StoreAccessDeltaMessage { PredictionId = predictionId, HasShopOpen = shopOpen, IsShopOpen = CPlayerData.m_IsShopOpen, HasWarehouseDoorClosed = warehouseClosed, IsWarehouseDoorClosed = CPlayerData.m_IsWarehouseDoorClosed, Animate = true }; } private static void Rollback(MessageContext context, StoreAccessToggleMessage message) { StoreAccessHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && context?.Connection != null && message != null) { active.SendRejectionState(context.Connection.Id, message); if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)active._context, context.Connection.Id, message.PredictionId); } } } private void SendRejectionState(int peer, StoreAccessToggleMessage message) { if (_shutdown || _context == null || !_context.InGame() || message.Which > 1) { return; } StoreAccessDeltaMessage storeAccessDeltaMessage; if (message.Which == 0) { if ((Object)(object)StoreAccessInterop.FindOpenSign() == (Object)null) { return; } storeAccessDeltaMessage = new StoreAccessDeltaMessage { PredictionId = message.PredictionId, HasShopOpen = true, IsShopOpen = CPlayerData.m_IsShopOpen, Animate = false }; } else { if ((Object)(object)StoreAccessInterop.FindWarehouseSign() == (Object)null && (Object)(object)StoreAccessInterop.FindUnlockRoomManager() == (Object)null) { return; } storeAccessDeltaMessage = new StoreAccessDeltaMessage { PredictionId = message.PredictionId, HasWarehouseDoorClosed = true, IsWarehouseDoorClosed = CPlayerData.m_IsWarehouseDoorClosed, Animate = false }; } CoopPlugin.Log.LogInfo((object)("[store-access] rejection refresh to conn " + peer + " shop=" + ((!storeAccessDeltaMessage.HasShopOpen) ? "-" : (storeAccessDeltaMessage.IsShopOpen ? "open" : "closed")) + " warehouse=" + ((!storeAccessDeltaMessage.HasWarehouseDoorClosed) ? "-" : (storeAccessDeltaMessage.IsWarehouseDoorClosed ? "closed" : "open")) + " pred=" + message.PredictionId.ToString() + ".")); _context.Send(peer, (INetMessage)(object)storeAccessDeltaMessage); } private static StoreAccessStateMessage BuildState(bool animate) { return new StoreAccessStateMessage { Animate = animate, IsShopOpen = CPlayerData.m_IsShopOpen, IsWarehouseDoorClosed = CPlayerData.m_IsWarehouseDoorClosed }; } private bool IsPeerMessage(MessageContext context) { if (!_shutdown && context?.Connection != null) { return _fullyJoined.Contains(context.Connection.Id); } return false; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { StoreAccessInterop.Reset(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _fullyJoined.Clear(); StoreAccessInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class StoreAccessInterop { private static InteractableOpenCloseSign _openSign; private static InteractableWarehouseAllowEnterSign _warehouseSign; private static UnlockRoomManager _unlockRoomManager; private static readonly FieldInfo FiOpenAnimation = AccessTools.Field(typeof(InteractableOpenCloseSign), "m_Anim"); private static readonly FieldInfo FiOpenSwapping = AccessTools.Field(typeof(InteractableOpenCloseSign), "m_IsSwapping"); private static readonly MethodInfo MiOpenMesh = AccessTools.Method(typeof(InteractableOpenCloseSign), "EvaluateSignOpenCloseMesh", (Type[])null, (Type[])null); private static readonly MethodInfo MiOpenDelay = AccessTools.Method(typeof(InteractableOpenCloseSign), "DelaySwapMesh", (Type[])null, (Type[])null); private static readonly FieldInfo FiWarehouseAnimation = AccessTools.Field(typeof(InteractableWarehouseAllowEnterSign), "m_Anim"); private static readonly FieldInfo FiWarehouseSwapping = AccessTools.Field(typeof(InteractableWarehouseAllowEnterSign), "m_IsSwapping"); private static readonly MethodInfo MiWarehouseMesh = AccessTools.Method(typeof(InteractableWarehouseAllowEnterSign), "EvaluateSignOpenCloseMesh", (Type[])null, (Type[])null); private static readonly MethodInfo MiWarehouseDelay = AccessTools.Method(typeof(InteractableWarehouseAllowEnterSign), "DelaySwapMesh", (Type[])null, (Type[])null); private static readonly MethodInfo MiWarehouseEvaluate = AccessTools.Method(typeof(UnlockRoomManager), "EvaluateWarehouseRoomOpenClose", (Type[])null, (Type[])null); internal static void Reset() { _openSign = null; _warehouseSign = null; _unlockRoomManager = null; } internal static T FindSceneObject() where T : Component { //IL_002f: 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) T[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null) { Scene scene = ((Component)array[i]).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return array[i]; } } } return default(T); } internal static InteractableOpenCloseSign FindOpenSign() { if ((Object)(object)_openSign == (Object)null) { _openSign = StoreAccessInterop.FindSceneObject(); } return _openSign; } internal static InteractableWarehouseAllowEnterSign FindWarehouseSign() { if ((Object)(object)_warehouseSign == (Object)null) { _warehouseSign = StoreAccessInterop.FindSceneObject(); } return _warehouseSign; } internal static UnlockRoomManager FindUnlockRoomManager() { if ((Object)(object)_unlockRoomManager == (Object)null) { _unlockRoomManager = StoreAccessInterop.FindSceneObject(); } return _unlockRoomManager; } internal static void RefreshOpenMesh(InteractableOpenCloseSign sign) { if ((Object)(object)sign != (Object)null) { MiOpenMesh?.Invoke(sign, null); } } internal static bool IsOpenSwapping(InteractableOpenCloseSign sign) { if ((Object)(object)sign != (Object)null) { return FiOpenSwapping?.GetValue(sign) as bool? == true; } return false; } internal static bool IsWarehouseSwapping(InteractableWarehouseAllowEnterSign sign) { if ((Object)(object)sign != (Object)null) { return FiWarehouseSwapping?.GetValue(sign) as bool? == true; } return false; } internal static bool PlayOpenAnimation(InteractableOpenCloseSign sign) { return PlayAnimation((MonoBehaviour)(object)sign, FiOpenAnimation, FiOpenSwapping, MiOpenDelay); } internal static bool PlayWarehouseAnimation(InteractableWarehouseAllowEnterSign sign) { return PlayAnimation((MonoBehaviour)(object)sign, FiWarehouseAnimation, FiWarehouseSwapping, MiWarehouseDelay); } internal static void RefreshWarehouseMesh(InteractableWarehouseAllowEnterSign sign) { if ((Object)(object)sign != (Object)null) { MiWarehouseMesh?.Invoke(sign, null); } } internal static void RefreshWarehouseAccess(UnlockRoomManager manager) { if (manager == null) { manager = FindUnlockRoomManager(); } if ((Object)(object)manager != (Object)null) { MiWarehouseEvaluate?.Invoke(manager, null); } } internal static bool IsSceneReady() { if ((Object)(object)FindOpenSign() != (Object)null) { if (!((Object)(object)FindWarehouseSign() != (Object)null)) { return (Object)(object)FindUnlockRoomManager() != (Object)null; } return true; } return false; } private static bool PlayAnimation(MonoBehaviour sign, FieldInfo animationField, FieldInfo swappingField, MethodInfo delayMethod) { if ((Object)(object)sign == (Object)null || animationField == null || swappingField == null || delayMethod == null) { return false; } object? value = animationField.GetValue(sign); Animation val = (Animation)((value is Animation) ? value : null); IEnumerator enumerator = delayMethod.Invoke(sign, null) as IEnumerator; if ((Object)(object)val == (Object)null || enumerator == null) { return false; } object value2 = swappingField.GetValue(sign); bool flag = default(bool); int num; if (value2 is bool) { flag = (bool)value2; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return false; } swappingField.SetValue(sign, true); val.Play(); sign.StartCoroutine(enumerator); return true; } } [NetworkMessage] public sealed class StoreAccessStateMessage : INetMessage { public bool Full = true; public bool Animate; public bool IsShopOpen; public bool IsWarehouseDoorClosed; } [NetworkMessage] public sealed class StoreAccessDeltaMessage : IPredictedMessage, INetMessage { public bool HasShopOpen; public bool IsShopOpen; public bool HasWarehouseDoorClosed; public bool IsWarehouseDoorClosed; public bool Animate; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class StoreAccessToggleMessage : IPredictedMessage, INetMessage { public byte Which; public Guid PredictionId { get; set; } } } namespace CardShopCoop.Modules.Staff { [ClientBehaviour] public sealed class StaffClientBehaviour : CoopBehaviour { private sealed class StaffAction { public Worker Worker; public int Index; public StaffModuleEntry Before; public StaffModuleIntentMessage Message; public double Spend; } private sealed class InteractScreenCache : Cached { protected override WorkerInteractUIScreen GetRawValue() { return Object.FindObjectOfType(true); } } private sealed class HireScreenCache : Cached { protected override HireWorkerScreen GetRawValue() { return Object.FindObjectOfType(true); } } [HarmonyPatch(typeof(HireWorkerPanelUI), "OnPressHireButton")] private static class HirePatch { [HarmonyPrefix] private static void Prefix(HireWorkerPanelUI __instance, out StaffAction __state) { __state = _active?.BeginHireAction(__instance); if (__state != null) { EconomyActionScope.Enter(); } } [HarmonyPostfix] private static void Postfix(HireWorkerPanelUI __instance, StaffAction __state) { _active?.EndHireAction(__instance, __state); } [HarmonyFinalizer] private static void Finalizer(StaffAction __state) { if (__state != null) { EconomyActionScope.Exit(); } } } [HarmonyPatch(typeof(WorkerInteractUIScreen), "SetTaskAsPrimaryOrSecondary")] private static class TaskPatch { [HarmonyPrefix] private static void Prefix(WorkerInteractUIScreen __instance, bool isPrimary, out StaffAction __state) { __state = _active?.BeginTaskAction(__instance, isPrimary); } [HarmonyPostfix] private static void Postfix(WorkerInteractUIScreen __instance, bool isPrimary, StaffAction __state) { _active?.EndTaskAction(__instance, isPrimary, __state); } } [HarmonyPatch(typeof(WorkerOptionUIScreen), "OnPressRestockShelfWithNoLabel")] private static class OptionPatch { [HarmonyPrefix] private static void Prefix(WorkerOptionUIScreen __instance, bool isFillShelfWithoutLabel, out StaffAction __state) { __state = _active?.BeginOptionAction(__instance, isFillShelfWithoutLabel); } [HarmonyPostfix] private static void Postfix(WorkerOptionUIScreen __instance, bool isFillShelfWithoutLabel, StaffAction __state) { _active?.EndOptionAction(__instance, isFillShelfWithoutLabel, __state); } } [HarmonyPatch(typeof(WorkerOptionSetPriceUIScreen), "OnPressConfirm")] private static class PriceOptionPatch { [HarmonyPrefix] private static void Prefix(WorkerOptionSetPriceUIScreen __instance, out StaffAction __state) { __state = _active?.BeginPriceAction(__instance); } [HarmonyPostfix] private static void Postfix(WorkerOptionSetPriceUIScreen __instance, StaffAction __state) { _active?.EndPriceAction(__instance, __state); } } [HarmonyPatch(typeof(WorkerSetPackOpenerTypeOptionScreen), "OnPressConfirm")] private static class PackOptionPatch { [HarmonyPrefix] private static void Prefix(WorkerSetPackOpenerTypeOptionScreen __instance, out StaffAction __state) { __state = _active?.BeginPackAction(__instance); } [HarmonyPostfix] private static void Postfix(WorkerSetPackOpenerTypeOptionScreen __instance, StaffAction __state) { _active?.EndPackAction(__instance, __state); } } [HarmonyPatch(typeof(WorkerInteractUIScreen), "OnPressGiveBonus")] private static class BonusPatch { [HarmonyPrefix] private static void Prefix(WorkerInteractUIScreen __instance, out StaffAction __state) { __state = _active?.BeginBonusAction(__instance); if (__state != null) { EconomyActionScope.Enter(); } } [HarmonyPostfix] private static void Postfix(WorkerInteractUIScreen __instance, StaffAction __state) { _active?.EndBonusAction(__instance, __state); } [HarmonyFinalizer] private static void Finalizer(StaffAction __state) { if (__state != null) { EconomyActionScope.Exit(); } } } [HarmonyPatch(typeof(WorkerInteractUIScreen), "OnPressFire")] private static class FirePatch { [HarmonyPrefix] private static void Prefix(WorkerInteractUIScreen __instance, out StaffAction __state) { __state = _active?.BeginFireAction(__instance); } [HarmonyPostfix] private static void Postfix(WorkerInteractUIScreen __instance, StaffAction __state) { _active?.EndFireAction(__instance, __state); } } [HarmonyPatch(typeof(Worker), "OnMousePress")] private static class WorkerMousePatch { [HarmonyPrefix] private static void Prefix(Worker __instance, out StaffAction __state) { __state = _active?.BeginWorkerMouse(__instance); } [HarmonyPostfix] private static void Postfix(StaffAction __state) { _active?.EndWorkerMouse(__state); } } [HarmonyPatch(typeof(Worker), "OnPressStopInteract")] private static class WorkerStopPatch { [HarmonyPrefix] private static void Prefix(Worker __instance, out StaffAction __state) { __state = _active?.BeginWorkerStop(__instance); } [HarmonyPostfix] private static void Postfix(StaffAction __state) { _active?.EndWorkerStop(__state); } } private const string PredictionScope = "staff"; private static StaffClientBehaviour _active; private static bool _applyingRemote; private static bool _allowWorkerOpen; private readonly Dictionary _workerBusy = new Dictionary(); private readonly HashSet _workerLease = new HashSet(); private readonly Dictionary _workerGenerations = new Dictionary(); private readonly Dictionary _deferredDeltas = new Dictionary(); private StaffModuleBaselineMessage _pendingBaseline; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private int _applyingPrediction; private int _insideStaffAction; private readonly InteractScreenCache _interactScreenCache = new InteractScreenCache(); private readonly HireScreenCache _hireScreenCache = new HireScreenCache(); private void OnEnable() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown if (!_shutdown && _context == null) { _context = base.RuntimeContext; _context.Messages.RegisterAttributedHandlers(this); _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.staff.module.client"); Patch(typeof(HirePatch)); Patch(typeof(TaskPatch)); Patch(typeof(OptionPatch)); Patch(typeof(PriceOptionPatch)); Patch(typeof(PackOptionPatch)); Patch(typeof(BonusPatch)); Patch(typeof(FirePatch)); Patch(typeof(WorkerMousePatch)); Patch(typeof(WorkerStopPatch)); SceneManager.sceneLoaded += OnSceneLoaded; NpcClientBehaviour.WorkerManagerReady += OnReadinessSignal; OnReadinessSignal(); } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } [OnFullyJoined] private void Joined(PeerConnection _) { _joined = true; TryApplyPending(); } [MessageHandler(typeof(StaffModuleBaselineMessage))] private void HandleBaseline(MessageContext _, StaffModuleBaselineMessage message) { _pendingBaseline = message; TryApplyPending(); } [MessageHandler(typeof(StaffModuleDeltaMessage))] private void HandleDelta(MessageContext _, StaffModuleDeltaMessage message) { if (!ApplyDelta(message)) { string key = DeltaKey(message); if (_deferredDeltas.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _deferredDeltas[key] = message; CoopPlugin.Log.LogInfo((object)("[staff] deferred " + message.Kind.ToString() + " index=" + message.Index + " generation=" + message.Generation + " known=" + (_workerGenerations.TryGetValue(message.Index, out var value2) ? value2.ToString() : "none"))); } } private bool IsSceneReady() { WorkerManager val = StaffModuleInterop.FindWorkerManager(); if ((Object)(object)val != (Object)null) { return val.m_WorkerDataList != null; } return false; } private void OnReadinessSignal() { if (_shutdown || !_context.InGame() || !IsSceneReady()) { return; } TryApplyPending(); foreach (KeyValuePair item in new List>(_deferredDeltas)) { if (ApplyDelta(item.Value)) { _deferredDeltas.Remove(item.Key); } } } private void TryApplyPending() { if (_pendingBaseline == null || !_context.InGame() || !IsSceneReady()) { return; } _applyingRemote = true; try { _workerGenerations.Clear(); for (int i = 0; i < _pendingBaseline.Entries.Count; i++) { ApplyEntry(i, _pendingBaseline.Entries[i]); } } finally { _applyingRemote = false; } _pendingBaseline = null; } private bool ApplyDelta(StaffModuleDeltaMessage message) { if (_shutdown || !_context.InGame() || !IsSceneReady()) { return false; } if (message.Kind != StaffDeltaKind.Interaction && !IsWorkerGenerationReady(message)) { return false; } if (message.Kind != StaffDeltaKind.Hired && message.Kind != StaffDeltaKind.Fired && message.Kind != StaffDeltaKind.Interaction && !StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var _)) { return false; } if (message.Kind == StaffDeltaKind.Interaction && message.Occupied && message.Granted && !StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var _)) { return false; } Action apply = delegate { _applyingRemote = true; try { ApplyDeltaCore(message); } finally { _applyingRemote = false; } }; if (message.Kind == StaffDeltaKind.Fired || message.Kind == StaffDeltaKind.Interaction) { PredictionApi.AckOrApply(message.PredictionId, apply); } else { PredictionApi.Confirm(message.PredictionId, apply); } return true; } private bool IsWorkerGenerationReady(StaffModuleDeltaMessage message) { if (message.Kind == StaffDeltaKind.Hired || message.Kind == StaffDeltaKind.Fired) { return true; } if (_workerGenerations.TryGetValue(message.Index, out var value)) { return value == message.Generation; } return false; } private void ApplyDeltaCore(StaffModuleDeltaMessage message) { switch (message.Kind) { case StaffDeltaKind.Hired: _workerGenerations[message.Index] = message.Generation; ApplyEntry(message.Index, message.Bootstrap); break; case StaffDeltaKind.Fired: { _workerGenerations[message.Index] = message.Generation; EnsureHiredSlot(message.Index); CPlayerData.SetIsWorkerHired(message.Index, false); if (StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { worker.FireWorker(); RefreshWorker(worker); } RefreshHirePanels(); break; } case StaffDeltaKind.Task: ApplyTask(message); break; case StaffDeltaKind.Options: ApplyOptions(message); break; case StaffDeltaKind.Pack: ApplyPack(message); break; case StaffDeltaKind.Bonus: ApplyBonus(message); break; case StaffDeltaKind.Experience: ApplyExperience(message); break; case StaffDeltaKind.Interaction: ApplyInteraction(message); break; default: throw new ArgumentOutOfRangeException("Kind", message.Kind, "Unknown staff delta."); } } private void ApplyEntry(int index, StaffModuleEntry entry) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (entry.Generation != 0) { _workerGenerations[index] = entry.Generation; } EnsureHiredSlot(index); CPlayerData.SetIsWorkerHired(index, entry.Hired); if (entry.HasData) { List workerSaveDataList = CPlayerData.m_WorkerSaveDataList; while (workerSaveDataList.Count <= index) { workerSaveDataList.Add(new WorkerSaveData()); } WorkerSaveData data = (workerSaveDataList[index] = (WorkerSaveData)(((object)workerSaveDataList[index]) ?? ((object)new WorkerSaveData()))); StaffModuleInterop.ApplyEntryToSave(entry, data); StaffModuleInterop.RefreshWorkerUi(index, data); if (StaffModuleInterop.TryGetWorkerFromPuppet(index, out var worker)) { StaffModuleInterop.ApplyEntryToWorker(worker, entry); } RefreshInteractScreen(_interactScreenCache.Get(), index); } RefreshHirePanels(); } private void ApplyTask(StaffModuleDeltaMessage message) { if (StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { worker.SetTask((EWorkerTask)message.PrimaryTask); worker.SetLastTask((EWorkerTask)message.WorkerTask); worker.SetSecondaryTask((EWorkerTask)message.SecondaryTask); RefreshWorker(worker); } } private void ApplyOptions(StaffModuleDeltaMessage message) { if (StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { worker.SetRestockShelfWithNoLabel(message.FillNoLabel); worker.UpdateSetPriceOption(message.RoundUpPrice, message.AvoidSetCardPrice, message.PriceMult); worker.UpdateSetCardPriceOption(message.RoundUpCardPrice, message.AvoidSetCardPriceRestock, message.CardPriceMult); worker.SetTask((EWorkerTask)message.PrimaryTask); worker.SetLastTask((EWorkerTask)message.WorkerTask); worker.SetSecondaryTask((EWorkerTask)message.SecondaryTask); RefreshWorker(worker); } } private void ApplyPack(StaffModuleDeltaMessage message) { if (!StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { return; } if (message.PackChanges != null && message.PackChanges.Count > 0) { for (int i = 0; i < message.PackChanges.Count; i++) { StaffPackChange staffPackChange = message.PackChanges[i]; worker.SetCardPackItemTypeEnabled(staffPackChange.Index, staffPackChange.Enabled); } } else { worker.SetCardPackItemTypeEnabled(message.PackIndex, message.PackEnabled); } worker.SetTask((EWorkerTask)message.PrimaryTask); worker.SetLastTask((EWorkerTask)message.WorkerTask); worker.SetSecondaryTask((EWorkerTask)message.SecondaryTask); RefreshWorker(worker); } private void ApplyBonus(StaffModuleDeltaMessage message) { if (StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { worker.m_BonusBoostedCount = message.BonusCount; worker.m_IsBonusBoosted = message.BonusBoosted; RefreshWorker(worker); RefreshInteractScreen(_interactScreenCache.Get(), message.Index); } } private void ApplyExperience(StaffModuleDeltaMessage message) { if (StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { StaffModuleInterop.SetExperience(worker, (EWorkerTask)message.ExperienceTask, message.ExperienceAmount); RefreshWorker(worker); } } private void ApplyInteraction(StaffModuleDeltaMessage message) { _workerBusy[message.Index] = message.Occupied; Worker worker2; if (!message.Occupied) { _workerLease.Remove(message.Index); if (StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out var worker)) { WithRemote((Action)worker.OnPressStopInteract); } } else if (message.Granted && StaffModuleInterop.TryGetWorkerFromPuppet(message.Index, out worker2)) { _workerLease.Add(message.Index); _allowWorkerOpen = true; try { worker2.OnMousePress(); } finally { _allowWorkerOpen = false; } } } private void RefreshWorker(Worker worker) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown StaffModuleEntry entry = StaffModuleInterop.CaptureWorkerEntry(worker, worker.m_WorkerIndex); WorkerSaveData data = new WorkerSaveData(); StaffModuleInterop.ApplyEntryToSave(entry, data); StaffModuleInterop.RefreshWorkerUi(worker.m_WorkerIndex, data); } private static void WithRemote(Action action) { _applyingRemote = true; try { action(); } finally { _applyingRemote = false; } } private void ReleaseLocalInteraction(int index) { _workerLease.Remove(index); _workerBusy[index] = false; } private void WithPrediction(Action action) { _applyingPrediction++; try { action(); } finally { _applyingPrediction--; } } private static void EnsureHiredSlot(int index) { while (CPlayerData.m_IsWorkerHired.Count <= index) { CPlayerData.m_IsWorkerHired.Add(item: false); } } private void RefreshHirePanels() { HireWorkerScreen val = _hireScreenCache.Get(); if ((Object)(object)val == (Object)null || val.m_HireWorkerPanelUIList == null || StaffModuleInterop.PanelEvaluateHired == null || StaffModuleInterop.PanelScreen == null) { return; } for (int i = 0; i < val.m_HireWorkerPanelUIList.Count; i++) { HireWorkerPanelUI val2 = val.m_HireWorkerPanelUIList[i]; if (!((Object)(object)val2 == (Object)null) && StaffModuleInterop.PanelScreen.GetValue(val2) != null) { StaffModuleInterop.PanelEvaluateHired.Invoke(val2, null); } } } private static void RefreshInteractScreen(WorkerInteractUIScreen screen, int index) { if ((Object)(object)screen == (Object)null || (Object)(object)screen.m_ScreenGrp == (Object)null || !screen.m_ScreenGrp.activeSelf) { return; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.InteractWorker, screen); if (!((Object)(object)val == (Object)null) && val.m_WorkerIndex == index) { int bonusBoostedCount = val.GetBonusBoostedCount(); ((Selectable)screen.m_GiveBonusBtn).interactable = bonusBoostedCount < 3; if (bonusBoostedCount > 0) { ((TMP_Text)screen.m_BonusAddAmountText).text = "+" + bonusBoostedCount; screen.m_BonusAddAmountGrp.SetActive(true); } else { screen.m_BonusAddAmountGrp.SetActive(false); } } } private StaffModuleEntry CaptureBefore(Worker worker) { return StaffModuleInterop.CaptureWorkerEntry(worker, worker.m_WorkerIndex); } private StaffModuleEntry CaptureManagerEntry(int index) { return StaffModuleInterop.CaptureEntry(StaffModuleInterop.FindWorkerManager(), index); } private void Restore(StaffModuleEntry entry, Worker worker) { _applyingRemote = true; try { ApplyEntry(worker.m_WorkerIndex, entry); } finally { _applyingRemote = false; } } private void RegisterPostHoc(string key, StaffModuleIntentMessage message, Action apply, Action undo) { PredictionApi.Predict(key, delegate(Guid id) { message.PredictionId = id; Send(message); }, apply, undo); } private static void Charge(double amount) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (amount > 0.0001) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin((float)amount, false)); } } private static void Refund(double amount) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (amount > 0.0001) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin((float)amount, true)); } } private bool CanObserve() { if (!_shutdown && _joined && _context != null && _context.InGame() && !_applyingRemote && _applyingPrediction == 0 && _insideStaffAction == 0) { return !PredictionApi.IsReconciling; } return false; } private StaffAction BeginHireAction(HireWorkerPanelUI panel) { if (!CanObserve() || (Object)(object)panel == (Object)null || StaffModuleInterop.PanelIsHired == null || StaffModuleInterop.PanelIndex == null) { return null; } if ((bool)StaffModuleInterop.PanelIsHired.GetValue(panel)) { return null; } int num = (int)StaffModuleInterop.PanelIndex.GetValue(panel); WorkerManager val = StaffModuleInterop.FindWorkerManager(); float num2 = (((Object)(object)val != (Object)null && val.m_WorkerDataList != null && num >= 0 && num < val.m_WorkerDataList.Count && val.m_WorkerDataList[num] != null) ? val.m_WorkerDataList[num].hiringCost : 0f); return new StaffAction { Index = num, Before = CaptureManagerEntry(num), Spend = num2, Message = new StaffModuleIntentMessage { Kind = StaffIntentKind.Hire, Index = num } }; } private void EndHireAction(HireWorkerPanelUI panel, StaffAction state) { if (state != null && CPlayerData.GetIsWorkerHired(state.Index)) { int index = state.Index; StaffModuleEntry before = state.Before; double spend = state.Spend; StaffModuleEntry predicted = before; predicted.Hired = true; predicted.HasData = true; predicted.PrimaryTask = 0; predicted.SecondaryTask = 0; predicted.WorkerTask = 0; predicted.CurrentState = 0; RegisterPostHoc("staff:" + index, state.Message, delegate { ApplyEntry(index, predicted); Charge(spend); }, delegate { ApplyEntry(index, before); Refund(spend); }); _context.SetStatusLine?.Invoke("hired - starting work at the host's shop", 4f); } } private StaffAction BeginBonusAction(WorkerInteractUIScreen screen) { if (!CanObserve() || (Object)(object)screen == (Object)null) { return null; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.InteractWorker, screen); if ((Object)(object)val == (Object)null) { return null; } return new StaffAction { Worker = val, Index = val.m_WorkerIndex, Before = CaptureBefore(val), Spend = (val.GetWorkerData()?.costPerDay ?? 0f), Message = new StaffModuleIntentMessage { Kind = StaffIntentKind.Bonus, Index = val.m_WorkerIndex } }; } private void EndBonusAction(WorkerInteractUIScreen screen, StaffAction state) { if (state == null) { return; } Worker worker = state.Worker; int index = worker.m_WorkerIndex; double spend = state.Spend; if (worker.GetBonusBoostedCount() <= state.Before.BonusCount) { return; } RegisterPostHoc("staff:" + index, state.Message, delegate { WithPrediction(delegate { worker.GiveSalaryBonus(); RefreshWorker(worker); RefreshInteractScreen(screen, index); Charge(spend); }); }, delegate { Restore(state.Before, worker); Refund(spend); }); } private StaffAction BeginFireAction(WorkerInteractUIScreen screen) { if (!CanObserve() || (Object)(object)screen == (Object)null) { return null; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.InteractWorker, screen); if ((Object)(object)val == (Object)null) { return null; } _insideStaffAction++; return new StaffAction { Worker = val, Before = CaptureBefore(val), Message = new StaffModuleIntentMessage { Kind = StaffIntentKind.Fire, Index = val.m_WorkerIndex } }; } private void EndFireAction(WorkerInteractUIScreen screen, StaffAction state) { if (state == null) { return; } _insideStaffAction--; Worker worker = state.Worker; int index = worker.m_WorkerIndex; RegisterPostHoc("staff:" + index, state.Message, delegate { WithPrediction(delegate { worker.FireWorker(); worker.OnPressStopInteract(); screen.CloseScreen(); }); }, delegate { Restore(state.Before, worker); _workerLease.Add(index); _workerBusy[index] = true; WithRemote((Action)worker.OnMousePress); }); ReleaseLocalInteraction(index); } private StaffAction BeginTaskAction(WorkerInteractUIScreen screen, bool isPrimary) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 //IL_008b: Unknown result type (might be due to invalid IL or missing references) if (!CanObserve() || (Object)(object)screen == (Object)null) { return null; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.InteractWorker, screen); object obj = StaffModuleInterop.TaskToSet?.GetValue(screen); if ((Object)(object)val == (Object)null || !(obj is EWorkerTask val2)) { return null; } if ((int)val2 == 2 || (int)val2 == 3 || (int)val2 == 6 || (int)val2 == 5) { return null; } _insideStaffAction++; return new StaffAction { Worker = val, Before = CaptureBefore(val), Message = BuildTaskIntent(val, isPrimary, val2) }; } private void EndTaskAction(WorkerInteractUIScreen screen, bool isPrimary, StaffAction state) { if (state == null) { return; } _insideStaffAction--; Worker worker = state.Worker; RegisterPostHoc("staff:" + worker.m_WorkerIndex, state.Message, delegate { WithPrediction(delegate { screen.SetTaskAsPrimaryOrSecondary(isPrimary); }); }, delegate { Restore(state.Before, worker); }); ReleaseLocalInteraction(worker.m_WorkerIndex); } private StaffModuleIntentMessage BuildTaskIntent(Worker worker, bool isPrimary, EWorkerTask task) { //IL_002b: 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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) WorkerSaveData workerSaveData = worker.GetWorkerSaveData(); return new StaffModuleIntentMessage { Kind = StaffIntentKind.Task, Index = worker.m_WorkerIndex, PrimaryTask = (byte)(isPrimary ? task : workerSaveData.primaryTask), SecondaryTask = (byte)(isPrimary ? workerSaveData.secondaryTask : task), WorkerTask = (byte)(isPrimary ? task : workerSaveData.workerTask) }; } private StaffModuleIntentMessage BuildOptionsIntent(WorkerOptionUIScreen screen, Worker worker, bool noLabel) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) int num = (int)StaffModuleInterop.OptionTaskIndex.GetValue(screen); WorkerSaveData workerSaveData = worker.GetWorkerSaveData(); return new StaffModuleIntentMessage { Kind = StaffIntentKind.Options, Index = worker.m_WorkerIndex, FillNoLabel = noLabel, PrimaryTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? num : ((int)workerSaveData.primaryTask)), SecondaryTask = (byte)((!worker.GetIsSetTaskSettingPrimarySecondary()) ? num : ((int)workerSaveData.secondaryTask)), WorkerTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? num : ((int)workerSaveData.workerTask)), RoundUpPrice = workerSaveData.isRoundUpPrice, AvoidSetCardPrice = workerSaveData.isAvoidSetCardPrice, RoundUpCardPrice = workerSaveData.isRoundUpCardPrice, AvoidSetCardPriceRestock = workerSaveData.isAvoidSetCardPriceWhileRestock, PriceMult = workerSaveData.setPriceMultiplier, CardPriceMult = workerSaveData.setCardPriceMultiplier }; } private StaffAction BeginOptionAction(WorkerOptionUIScreen screen, bool noLabel) { if (!CanObserve() || (Object)(object)screen == (Object)null) { return null; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.OptionWorker, screen); if ((Object)(object)val == (Object)null) { return null; } _insideStaffAction++; return new StaffAction { Worker = val, Before = CaptureBefore(val), Message = BuildOptionsIntent(screen, val, noLabel) }; } private void EndOptionAction(WorkerOptionUIScreen screen, bool noLabel, StaffAction state) { if (state == null) { return; } _insideStaffAction--; Worker worker = state.Worker; RegisterPostHoc("staff:" + worker.m_WorkerIndex, state.Message, delegate { WithPrediction(delegate { screen.OnPressRestockShelfWithNoLabel(noLabel); }); }, delegate { Restore(state.Before, worker); }); ReleaseLocalInteraction(worker.m_WorkerIndex); } private StaffModuleIntentMessage BuildPriceIntent(WorkerOptionSetPriceUIScreen screen, Worker worker) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) EWorkerTask val = (EWorkerTask)StaffModuleInterop.PriceTask.GetValue(screen); bool flag = (bool)StaffModuleInterop.PriceRoundUp.GetValue(screen); bool flag2 = (bool)StaffModuleInterop.PriceCanSetCard.GetValue(screen); float num = (float)StaffModuleInterop.PriceMultiplier.GetValue(screen); WorkerSaveData workerSaveData = worker.GetWorkerSaveData(); bool flag3 = (int)val == 3; return new StaffModuleIntentMessage { Kind = StaffIntentKind.Options, Index = worker.m_WorkerIndex, PrimaryTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? val : workerSaveData.primaryTask), SecondaryTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? workerSaveData.secondaryTask : val), WorkerTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? val : workerSaveData.workerTask), FillNoLabel = workerSaveData.isFillShelfWithoutLabel, RoundUpPrice = (flag3 ? flag : workerSaveData.isRoundUpPrice), AvoidSetCardPrice = (flag3 ? (!flag2) : workerSaveData.isAvoidSetCardPrice), RoundUpCardPrice = (flag3 ? workerSaveData.isRoundUpCardPrice : flag), AvoidSetCardPriceRestock = (flag3 ? workerSaveData.isAvoidSetCardPriceWhileRestock : (!flag2)), PriceMult = (flag3 ? num : workerSaveData.setPriceMultiplier), CardPriceMult = (flag3 ? workerSaveData.setCardPriceMultiplier : num) }; } private StaffAction BeginPriceAction(WorkerOptionSetPriceUIScreen screen) { if (!CanObserve() || (Object)(object)screen == (Object)null) { return null; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.PriceWorker, screen); if ((Object)(object)val == (Object)null) { return null; } _insideStaffAction++; return new StaffAction { Worker = val, Before = CaptureBefore(val), Message = BuildPriceIntent(screen, val) }; } private void EndPriceAction(WorkerOptionSetPriceUIScreen screen, StaffAction state) { if (state != null) { _insideStaffAction--; Worker worker = state.Worker; RegisterPostHoc("staff:" + worker.m_WorkerIndex, state.Message, delegate { WithPrediction((Action)screen.OnPressConfirm); }, delegate { Restore(state.Before, worker); }); ReleaseLocalInteraction(worker.m_WorkerIndex); } } private StaffModuleIntentMessage BuildPackIntent(WorkerSetPackOpenerTypeOptionScreen screen, Worker worker) { //IL_0099: 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_00c9: Unknown result type (might be due to invalid IL or missing references) List list = (List)StaffModuleInterop.PackEnabled.GetValue(screen); List cardPackItemTypeEnabledList = worker.GetCardPackItemTypeEnabledList(); List list2 = new List(); for (int i = 0; i < list.Count; i++) { if (i >= cardPackItemTypeEnabledList.Count || cardPackItemTypeEnabledList[i] != list[i]) { list2.Add(new StaffPackChange { Index = i, Enabled = list[i] }); } } WorkerSaveData workerSaveData = worker.GetWorkerSaveData(); return new StaffModuleIntentMessage { Kind = StaffIntentKind.Pack, Index = worker.m_WorkerIndex, PrimaryTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? 5u : ((uint)(int)workerSaveData.primaryTask)), SecondaryTask = (byte)((!worker.GetIsSetTaskSettingPrimarySecondary()) ? 5u : ((uint)(int)workerSaveData.secondaryTask)), WorkerTask = (byte)(worker.GetIsSetTaskSettingPrimarySecondary() ? 5u : ((uint)(int)workerSaveData.workerTask)), PackChanges = list2 }; } private StaffAction BeginPackAction(WorkerSetPackOpenerTypeOptionScreen screen) { if (!CanObserve() || (Object)(object)screen == (Object)null) { return null; } Worker val = StaffModuleInterop.WorkerFrom(StaffModuleInterop.PackWorker, screen); if ((Object)(object)val == (Object)null) { return null; } _insideStaffAction++; return new StaffAction { Worker = val, Before = CaptureBefore(val), Message = BuildPackIntent(screen, val) }; } private void EndPackAction(WorkerSetPackOpenerTypeOptionScreen screen, StaffAction state) { if (state != null) { _insideStaffAction--; Worker worker = state.Worker; RegisterPostHoc("staff:" + worker.m_WorkerIndex, state.Message, delegate { WithPrediction((Action)screen.OnPressConfirm); }, delegate { Restore(state.Before, worker); }); ReleaseLocalInteraction(worker.m_WorkerIndex); } } private StaffAction BeginWorkerMouse(Worker worker) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (!CanObserve() || _allowWorkerOpen || (Object)(object)worker == (Object)null) { return null; } int workerIndex = worker.m_WorkerIndex; if (_workerLease.Contains(workerIndex)) { return null; } if (!CoopCore.TryGetLocalPlayerPosition(out var position)) { return null; } return new StaffAction { Worker = worker, Index = workerIndex, Message = new StaffModuleIntentMessage { Kind = StaffIntentKind.BeginInteraction, Index = workerIndex, Position = position } }; } private void EndWorkerMouse(StaffAction state) { if (state == null) { return; } Worker worker = state.Worker; int index = state.Index; _workerBusy[index] = true; _workerLease.Add(index); RegisterPostHoc("staff:" + index, state.Message, delegate { WithPrediction(delegate { _workerBusy[index] = true; _workerLease.Add(index); worker.OnMousePress(); }); }, delegate { _workerLease.Remove(index); _workerBusy[index] = false; WithRemote((Action)worker.OnPressStopInteract); }); } private StaffAction BeginWorkerStop(Worker worker) { if (!CanObserve() || (Object)(object)worker == (Object)null || !_workerLease.Contains(worker.m_WorkerIndex)) { return null; } return new StaffAction { Worker = worker, Index = worker.m_WorkerIndex, Message = new StaffModuleIntentMessage { Kind = StaffIntentKind.EndInteraction, Index = worker.m_WorkerIndex } }; } private void EndWorkerStop(StaffAction state) { if (state == null) { return; } Worker worker = state.Worker; int index = state.Index; _workerLease.Remove(index); _workerBusy[index] = false; RegisterPostHoc("staff:" + index, state.Message, delegate { WithPrediction(delegate { _workerLease.Remove(index); _workerBusy[index] = false; worker.OnPressStopInteract(); }); }, delegate { _workerLease.Add(index); _workerBusy[index] = true; WithRemote((Action)worker.OnMousePress); }); } private void Send(StaffModuleIntentMessage message) { if (_shutdown) { throw new InvalidOperationException("Cannot send a Staff intent after shutdown."); } if (!_joined) { throw new InvalidOperationException("Cannot send a Staff intent before FullyJoined."); } if (_context == null || !_context.InGame()) { throw new InvalidOperationException("Cannot send a Staff intent outside the game."); } _context.Send(1, (INetMessage)(object)message); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (!_shutdown) { ResetWorldState(); OnReadinessSignal(); } } [OnClientDisconnected] private void Disconnected(PeerConnection connection, DisconnectInfo _) { if (connection != null && connection.Id == 1) { _joined = false; ResetWorldState(); } } private void ResetWorldState() { _workerBusy.Clear(); _workerLease.Clear(); _workerGenerations.Clear(); ClearDeferredDeltas(); _pendingBaseline = null; _interactScreenCache.Clear(); _hireScreenCache.Clear(); _applyingRemote = false; _allowWorkerOpen = false; _insideStaffAction = 0; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; ResetWorldState(); NpcClientBehaviour.WorkerManagerReady -= OnReadinessSignal; SceneManager.sceneLoaded -= OnSceneLoaded; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnDestroy() { Shutdown(); } private static string DeltaKey(StaffModuleDeltaMessage message) { string[] obj = new string[9] { message.Index.ToString(), ":", null, null, null, null, null, null, null }; byte kind = (byte)message.Kind; obj[2] = kind.ToString(); obj[3] = ":"; obj[4] = message.Generation.ToString(); obj[5] = ":"; obj[6] = message.PackIndex.ToString(); obj[7] = ":"; obj[8] = message.ExperienceTask.ToString(); return string.Concat(obj); } private void ClearDeferredDeltas() { foreach (StaffModuleDeltaMessage value in _deferredDeltas.Values) { PredictionApi.Ack(value.PredictionId); } _deferredDeltas.Clear(); } } [ServerBehaviour] public sealed class StaffHostBehaviour : CoopBehaviour { private sealed class WorkerIdentity { public Worker Worker; public uint Generation; } [HarmonyPatch(typeof(Worker), "OnMousePress")] private static class WorkerMousePatch { [HarmonyPrefix] private static bool Prefix(Worker __instance) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._shutdown && !((Object)(object)__instance == (Object)null)) { return _active.HostBeginInteraction(__instance.m_WorkerIndex, 0, default(Vector3)); } return true; } } [HarmonyPatch(typeof(Worker), "OnPressStopInteract")] private static class WorkerStopPatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { if ((Object)(object)_active != (Object)null && !_active._shutdown && (Object)(object)__instance != (Object)null) { _active.HostEndInteraction(__instance.m_WorkerIndex, 0); } } } [HarmonyPatch(typeof(Worker), "ActivateWorker")] private static class WorkerLifecyclePatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null)) { _active.PublishWorkerBootstrap(__instance.m_WorkerIndex); } } } [HarmonyPatch(typeof(Worker), "SetTask")] [HarmonyPatch(typeof(Worker), "SetLastTask")] [HarmonyPatch(typeof(Worker), "SetSecondaryTask")] private static class WorkerTaskPatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { _active?.PublishTask(__instance); } } [HarmonyPatch(typeof(Worker), "SetRestockShelfWithNoLabel")] [HarmonyPatch(typeof(Worker), "UpdateSetPriceOption")] [HarmonyPatch(typeof(Worker), "UpdateSetCardPriceOption")] private static class WorkerOptionsPatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { _active?.PublishOptions(__instance); } } [HarmonyPatch(typeof(Worker), "SetCardPackItemTypeEnabled")] private static class WorkerPackPatch { [HarmonyPostfix] private static void Postfix(Worker __instance, int __0, bool __1) { _active?.PublishPack(__instance, __0, __1); } } [HarmonyPatch(typeof(Worker), "GiveSalaryBonus")] private static class WorkerBonusPatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { _active?.PublishBonus(__instance); } } [HarmonyPatch(typeof(Worker), "AddExp")] private static class WorkerExperiencePatch { [HarmonyPostfix] private static void Postfix(Worker __instance, EWorkerTask __0) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _active?.PublishExperience(__instance, __0); } } [HarmonyPatch(typeof(Worker), "FireWorker")] private static class WorkerFirePatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { _active?.PublishFired(__instance); } } [HarmonyPatch(typeof(WorkerManager), "Start")] private static class WorkerManagerStartPatch { [HarmonyPostfix] private static void Postfix() { _active?.PublishBootstrapAll(); } } private static StaffHostBehaviour _active; private readonly Dictionary _leaseOwner = new Dictionary(); private readonly Dictionary _identities = new Dictionary(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private Guid _intentPredictionId; private const float StaffInteractionReach = 5f; private void OnEnable() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown if (!_shutdown && _context == null) { _context = base.RuntimeContext; _context.Messages.RegisterAttributedHandlers(this); _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.staff.module.host"); Patch(typeof(WorkerMousePatch)); Patch(typeof(WorkerStopPatch)); Patch(typeof(WorkerLifecyclePatch)); Patch(typeof(WorkerTaskPatch)); Patch(typeof(WorkerOptionsPatch)); Patch(typeof(WorkerPackPatch)); Patch(typeof(WorkerBonusPatch)); Patch(typeof(WorkerExperiencePatch)); Patch(typeof(WorkerFirePatch)); Patch(typeof(WorkerManagerStartPatch)); SceneManager.sceneLoaded += OnSceneLoaded; } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (connection != null && IsJoinPhase(connection.State) && _context.InGame() && GetWorkerManager()?.m_WorkerDataList != null) { _context.Send(connection.Id, (INetMessage)(object)BuildBaseline()); } } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo _) { if (connection != null) { HostReleaseConnection(connection.Id); } } [MessageHandler(typeof(StaffModuleIntentMessage))] private void HandleIntent(MessageContext context, StaffModuleIntentMessage message) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) if (!IsValidSender(context, message)) { return; } int id = context.Connection.Id; _intentPredictionId = message.PredictionId; bool flag = false; try { switch (message.Kind) { case StaffIntentKind.Hire: flag = HostHire(message.Index); break; case StaffIntentKind.Task: case StaffIntentKind.Options: case StaffIntentKind.Pack: flag = HostUpdate(message, id); break; case StaffIntentKind.Bonus: flag = HostBonus(message.Index, id); break; case StaffIntentKind.Fire: flag = HostFire(message.Index, id); break; case StaffIntentKind.BeginInteraction: flag = HostBeginInteraction(message.Index, id, message.Position); break; case StaffIntentKind.EndInteraction: flag = HostEndInteraction(message.Index, id); break; } } finally { _intentPredictionId = Guid.Empty; } if (!flag) { Reject(id, message.PredictionId); } } private bool IsValidSender(MessageContext context, StaffModuleIntentMessage message) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && _context.InGame() && context?.Connection != null && IsJoinPhase(context.Connection.State)) { return message != null; } return false; } private StaffModuleBaselineMessage BuildBaseline() { WorkerManager workerManager = GetWorkerManager(); StaffModuleBaselineMessage staffModuleBaselineMessage = new StaffModuleBaselineMessage(); List list = StaffModuleInterop.CaptureAll(workerManager); for (int i = 0; i < list.Count; i++) { list[i] = WithGeneration(i, list[i]); } staffModuleBaselineMessage.Entries = list; return staffModuleBaselineMessage; } private StaffModuleEntry WithGeneration(int index, StaffModuleEntry entry) { entry.Generation = EnsureGeneration(index); return entry; } private uint EnsureGeneration(int index) { if (!StaffModuleInterop.TryGetWorker(GetWorkerManager(), index, out var worker)) { if (!_identities.TryGetValue(index, out var value)) { return 1u; } return value.Generation; } if (!_identities.TryGetValue(index, out var value2) || value2.Worker != worker) { uint num = ((value2 == null) ? 1u : (value2.Generation + 1)); if (num == 0) { num = 1u; } value2 = new WorkerIdentity { Worker = worker, Generation = num }; _identities[index] = value2; } return value2.Generation; } private WorkerManager GetWorkerManager() { return SceneRef.Get(); } private bool HostHire(int index) { WorkerManager workerManager = GetWorkerManager(); if (!StaffModuleInterop.IsAddressable(workerManager, index) || CPlayerData.m_IsWorkerHired == null || index >= CPlayerData.m_IsWorkerHired.Count || CPlayerData.GetIsWorkerHired(index)) { return false; } WorkerData val = workerManager.m_WorkerDataList[index]; if (val == null || CPlayerData.m_ShopLevel + 1 < val.shopLevelRequired) { return false; } CGameManager val2 = SceneRef.Get(); if ((Object)(object)val2 != (Object)null && val2.m_IsPrologue && !val.prologueShow) { return false; } if (!TrySpend(val.hiringCost, (ETransactionType)8, index)) { return false; } CPlayerData.SetIsWorkerHired(index, true); workerManager.ActivateWorker(index, true); CPlayerData.m_GameReportDataCollect.employeeCost -= val.hiringCost; CPlayerData.m_GameReportDataCollectPermanent.employeeCost -= val.hiringCost; int num = 0; for (int i = 0; i < CPlayerData.m_IsWorkerHired.Count; i++) { if (CPlayerData.m_IsWorkerHired[i]) { num++; } } AchievementManager.OnStaffHired(num); SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); return true; } private bool HostUpdate(StaffModuleIntentMessage message, int connectionId) { if (!_leaseOwner.TryGetValue(message.Index, out var value) || value != connectionId) { return false; } WorkerManager workerManager = GetWorkerManager(); if (!StaffModuleInterop.IsValidWorker(workerManager, message.Index)) { return false; } if ((message.Kind == StaffIntentKind.Task || message.Kind == StaffIntentKind.Options || message.Kind == StaffIntentKind.Pack) && !AreTasksValid(message)) { return false; } if (message.Kind == StaffIntentKind.Task) { bool num = ApplyTask(message); if (num) { PublishTask(StaffModuleInterop.GetWorkers(workerManager)[message.Index], force: true); HostEndInteraction(message.Index, connectionId); } return num; } if (message.Kind == StaffIntentKind.Options) { if (!TryApplyOptions(message)) { return false; } bool num2 = ApplyTask(message); if (num2) { PublishOptions(StaffModuleInterop.GetWorkers(workerManager)[message.Index], force: true); HostEndInteraction(message.Index, connectionId); } return num2; } if (message.Kind == StaffIntentKind.Pack) { Worker val = StaffModuleInterop.GetWorkers(workerManager)[message.Index]; List cardPackItemTypeEnabledList = val.GetCardPackItemTypeEnabledList(); if (message.PackChanges == null || message.PackChanges.Count > 64 || cardPackItemTypeEnabledList == null) { return false; } for (int i = 0; i < message.PackChanges.Count; i++) { StaffPackChange staffPackChange = message.PackChanges[i]; if (staffPackChange == null || staffPackChange.Index < 0 || staffPackChange.Index >= cardPackItemTypeEnabledList.Count) { return false; } } for (int j = 0; j < message.PackChanges.Count; j++) { StaffPackChange staffPackChange2 = message.PackChanges[j]; val.SetCardPackItemTypeEnabled(staffPackChange2.Index, staffPackChange2.Enabled); } bool num3 = ApplyTask(message); if (num3) { PublishPackBatch(StaffModuleInterop.GetWorkers(workerManager)[message.Index], message.PackChanges, force: true); HostEndInteraction(message.Index, connectionId); } return num3; } return false; } private bool ApplyTask(StaffModuleIntentMessage message) { if (!AreTasksValid(message)) { return false; } Worker obj = StaffModuleInterop.GetWorkers(GetWorkerManager())[message.Index]; obj.SetTask((EWorkerTask)message.PrimaryTask); obj.SetLastTask((EWorkerTask)message.WorkerTask); obj.SetSecondaryTask((EWorkerTask)message.SecondaryTask); return true; } private static bool AreTasksValid(StaffModuleIntentMessage message) { if (StaffModuleInterop.IsValidIntentTask(message.PrimaryTask) && StaffModuleInterop.IsValidIntentTask(message.SecondaryTask)) { return StaffModuleInterop.IsValidIntentTask(message.WorkerTask); } return false; } private bool TryApplyOptions(StaffModuleIntentMessage message) { if (!TryGetFinite(message.PriceMult, out var result) || !TryGetFinite(message.CardPriceMult, out var result2) || result < 0f || result > 10f || result2 < 0f || result2 > 10f) { return false; } Worker obj = StaffModuleInterop.GetWorkers(GetWorkerManager())[message.Index]; obj.SetRestockShelfWithNoLabel(message.FillNoLabel); obj.UpdateSetPriceOption(message.RoundUpPrice, message.AvoidSetCardPrice, Mathf.Clamp(result, 0f, 10f)); obj.UpdateSetCardPriceOption(message.RoundUpCardPrice, message.AvoidSetCardPriceRestock, Mathf.Clamp(result2, 0f, 10f)); return true; } private bool HostBonus(int index, int connectionId) { WorkerManager workerManager = GetWorkerManager(); if (!StaffModuleInterop.IsValidWorker(workerManager, index) || !_leaseOwner.TryGetValue(index, out var value) || value != connectionId) { return false; } Worker val = StaffModuleInterop.GetWorkers(workerManager)[index]; if (val.GetBonusBoostedCount() >= 3 || val.GetWorkerData() == null) { return false; } float costPerDay = val.GetWorkerData().costPerDay; if (!TrySpend(costPerDay, (ETransactionType)9, 0)) { return false; } val.GiveSalaryBonus(); CPlayerData.m_GameReportDataCollect.employeeCost -= costPerDay; CPlayerData.m_GameReportDataCollectPermanent.employeeCost -= costPerDay; return true; } private bool HostFire(int index, int connectionId) { WorkerManager workerManager = GetWorkerManager(); if (!StaffModuleInterop.IsValidWorker(workerManager, index) || !_leaseOwner.TryGetValue(index, out var value) || value != connectionId) { return false; } StaffModuleInterop.GetWorkers(workerManager)[index].FireWorker(); HostEndInteraction(index, connectionId); return true; } private bool TrySpend(float amount, ETransactionType transactionType, int transactionIndex) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) if (float.IsNaN(amount) || float.IsInfinity(amount) || amount < 0f || CPlayerData.m_CoinAmountDouble < (double)amount) { return false; } if (!EconomyAuthority.TryReserveHostSpend(amount, out var reservation)) { return false; } try { if (!EconomyAuthority.QueueHostSpend(reservation)) { reservation.Dispose(); return false; } PriceChangeManager.AddTransaction(0f - amount, transactionType, transactionIndex, 0, (CardData)null); return true; } catch { reservation.Dispose(); throw; } } private bool HostBeginInteraction(int index, int connectionId, Vector3 playerPosition) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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) WorkerManager workerManager = GetWorkerManager(); if (!StaffModuleInterop.IsValidWorker(workerManager, index) || !StaffModuleInterop.IsFinite(playerPosition)) { return false; } Worker val = StaffModuleInterop.GetWorkers(workerManager)[index]; if (connectionId != 0) { if (_context.PeerPresence.TryGet(connectionId, out var presence) && !(presence.Age > TimeSpan.FromSeconds(2.0))) { Vector3 val2 = presence.Position - ((Component)val).transform.position; if (!(((Vector3)(ref val2)).sqrMagnitude > 25f)) { goto IL_0084; } } return false; } goto IL_0084; IL_0084: if (_leaseOwner.TryGetValue(index, out var value)) { return value == connectionId; } if (connectionId != 0) { StaffModuleInterop.AimWorkerAtPlayer(val, playerPosition); } val.m_IsPausingAction = true; _leaseOwner[index] = connectionId; PublishInteraction(index, occupied: true, connectionId); return true; } private bool HostEndInteraction(int index, int connectionId) { if (!_leaseOwner.TryGetValue(index, out var value) || value != connectionId) { return false; } if (StaffModuleInterop.TryGetWorker(GetWorkerManager(), index, out var worker)) { worker.m_IsPausingAction = false; } _leaseOwner.Remove(index); PublishInteraction(index, occupied: false, 0); return true; } private void HostReleaseConnection(int connectionId) { List list = new List(); foreach (KeyValuePair item in _leaseOwner) { if (item.Value == connectionId) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { HostEndInteraction(list[i], connectionId); } } private void PublishInteraction(int index, bool occupied, int grantedPeer) { if (!_shutdown && _context.InGame()) { StaffModuleDeltaMessage staffModuleDeltaMessage = new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Interaction, Index = index, Generation = EnsureGeneration(index), Granted = (grantedPeer > 0), Occupied = occupied }; if (grantedPeer > 0 && occupied) { _context.Send(grantedPeer, (INetMessage)(object)staffModuleDeltaMessage); _context.Relay(grantedPeer, (INetMessage)(object)new StaffModuleDeltaMessage { PredictionId = staffModuleDeltaMessage.PredictionId, Kind = staffModuleDeltaMessage.Kind, Index = staffModuleDeltaMessage.Index, Generation = staffModuleDeltaMessage.Generation, Granted = false, Occupied = true }); } else { _context.Broadcast((INetMessage)(object)staffModuleDeltaMessage); } } } private void PublishWorkerBootstrap(int index) { if (!_shutdown && _context.InGame() && StaffModuleInterop.IsAddressable(GetWorkerManager(), index)) { StaffModuleEntry staffModuleEntry = WithGeneration(index, StaffModuleInterop.CaptureEntry(GetWorkerManager(), index)); Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = (staffModuleEntry.Hired ? StaffDeltaKind.Hired : StaffDeltaKind.Fired), Index = index, Generation = staffModuleEntry.Generation, Hired = staffModuleEntry.Hired, Bootstrap = (staffModuleEntry.Hired ? staffModuleEntry : default(StaffModuleEntry)) }); } } private void PublishTask(Worker worker, bool force = false) { int num = worker?.m_WorkerIndex ?? (-1); if (num >= 0 && (force || !(_intentPredictionId != Guid.Empty))) { StaffModuleEntry staffModuleEntry = StaffModuleInterop.CaptureWorkerEntry(worker, num); Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Task, Index = num, Generation = EnsureGeneration(num), PrimaryTask = staffModuleEntry.PrimaryTask, SecondaryTask = staffModuleEntry.SecondaryTask, WorkerTask = staffModuleEntry.WorkerTask, CurrentState = staffModuleEntry.CurrentState, GoingHome = staffModuleEntry.GoingHome }); } } private void PublishOptions(Worker worker, bool force = false) { int num = worker?.m_WorkerIndex ?? (-1); if (num >= 0 && (force || !(_intentPredictionId != Guid.Empty))) { StaffModuleEntry staffModuleEntry = StaffModuleInterop.CaptureWorkerEntry(worker, num); Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Options, Index = num, Generation = EnsureGeneration(num), PrimaryTask = staffModuleEntry.PrimaryTask, SecondaryTask = staffModuleEntry.SecondaryTask, WorkerTask = staffModuleEntry.WorkerTask, CurrentState = staffModuleEntry.CurrentState, GoingHome = staffModuleEntry.GoingHome, FillNoLabel = staffModuleEntry.FillNoLabel, RoundUpPrice = staffModuleEntry.RoundUpPrice, RoundUpCardPrice = staffModuleEntry.RoundUpCardPrice, AvoidSetCardPrice = staffModuleEntry.AvoidSetCardPrice, AvoidSetCardPriceRestock = staffModuleEntry.AvoidSetCardPriceRestock, PriceMult = staffModuleEntry.PriceMult, CardPriceMult = staffModuleEntry.CardPriceMult }); } } private void PublishPack(Worker worker, int packIndex, bool enabled, bool force = false) { int num = worker?.m_WorkerIndex ?? (-1); if (num >= 0 && (force || _intentPredictionId == Guid.Empty)) { StaffModuleEntry staffModuleEntry = StaffModuleInterop.CaptureWorkerEntry(worker, num); Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Pack, Index = num, Generation = EnsureGeneration(num), PackIndex = packIndex, PackEnabled = enabled, PrimaryTask = staffModuleEntry.PrimaryTask, SecondaryTask = staffModuleEntry.SecondaryTask, WorkerTask = staffModuleEntry.WorkerTask, CurrentState = staffModuleEntry.CurrentState, GoingHome = staffModuleEntry.GoingHome }); } } private void PublishPackBatch(Worker worker, List changes, bool force) { if ((worker?.m_WorkerIndex ?? (-1)) < 0 || (!force && _intentPredictionId != Guid.Empty)) { return; } List cardPackItemTypeEnabledList = worker.GetCardPackItemTypeEnabledList(); int num = 0; while (changes != null && num < changes.Count) { StaffPackChange staffPackChange = changes[num]; if (staffPackChange != null && cardPackItemTypeEnabledList != null && staffPackChange.Index >= 0 && staffPackChange.Index < cardPackItemTypeEnabledList.Count) { PublishPack(worker, staffPackChange.Index, cardPackItemTypeEnabledList[staffPackChange.Index], force); } num++; } } private void PublishBonus(Worker worker) { int num = worker?.m_WorkerIndex ?? (-1); if (num >= 0) { Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Bonus, Index = num, Generation = EnsureGeneration(num), BonusCount = (byte)Mathf.Clamp(worker.GetBonusBoostedCount(), 0, 255), BonusBoosted = worker.m_IsBonusBoosted }); } } private void PublishExperience(Worker worker, EWorkerTask task) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected I4, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) int num = worker?.m_WorkerIndex ?? (-1); if (num >= 0) { StaffModuleEntry staffModuleEntry = StaffModuleInterop.CaptureWorkerEntry(worker, num); int num2 = (int)task; if (staffModuleEntry.ExpList != null && num2 >= 0 && num2 < staffModuleEntry.ExpList.Count) { Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Experience, Index = num, Generation = EnsureGeneration(num), ExperienceTask = (byte)task, ExperienceAmount = staffModuleEntry.ExpList[num2] }); } } } private void PublishFired(Worker worker) { int num = worker?.m_WorkerIndex ?? (-1); if (num >= 0) { Broadcast(new StaffModuleDeltaMessage { PredictionId = _intentPredictionId, Kind = StaffDeltaKind.Fired, Index = num, Generation = EnsureGeneration(num), Hired = false }); } } private void Broadcast(StaffModuleDeltaMessage message) { if (!_shutdown && _context.InGame()) { _context.Broadcast((INetMessage)(object)message); } } private static bool TryGetFinite(float value, out float result) { result = value; if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private void PublishBootstrapAll() { int valueOrDefault = (GetWorkerManager()?.m_WorkerDataList?.Count).GetValueOrDefault(); for (int i = 0; i < Mathf.Min(valueOrDefault, 32); i++) { if (StaffModuleInterop.IsHired(i)) { PublishWorkerBootstrap(i); } } } private void OnSceneLoaded(Scene _, LoadSceneMode __) { ResetWorldState(); } private void ResetWorldState() { foreach (KeyValuePair item in _leaseOwner) { if (StaffModuleInterop.TryGetWorker(GetWorkerManager(), item.Key, out var worker)) { worker.m_IsPausingAction = false; } } _leaseOwner.Clear(); _identities.Clear(); } private void Reject(int connectionId, Guid predictionId) { if (predictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, connectionId, predictionId); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; ResetWorldState(); SceneManager.sceneLoaded -= OnSceneLoaded; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _context = null; } } private void OnDestroy() { Shutdown(); } } public enum StaffIntentKind : byte { Hire = 1, Task, Options, Pack, Bonus, Fire, BeginInteraction, EndInteraction } public enum StaffDeltaKind : byte { Hired = 1, Fired, Task, Options, Pack, Bonus, Experience, Interaction } [NetworkMessage] public sealed class StaffModuleIntentMessage : IPredictedMessage, INetMessage { public StaffIntentKind Kind; public int Index; public Vector3 Position; public byte PrimaryTask; public byte SecondaryTask; public byte WorkerTask; public bool FillNoLabel; public bool RoundUpPrice; public bool AvoidSetCardPrice; public bool RoundUpCardPrice; public bool AvoidSetCardPriceRestock; public float PriceMult; public float CardPriceMult; public List PackChanges = new List(); public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class StaffModuleBaselineMessage : INetMessage { public List Entries = new List(); } [NetworkMessage] public sealed class StaffModuleDeltaMessage : IPredictedMessage, INetMessage { public StaffDeltaKind Kind; public int Index; public uint Generation; public bool Hired; public StaffModuleEntry Bootstrap; public byte PrimaryTask; public byte SecondaryTask; public byte WorkerTask; public byte CurrentState; public bool GoingHome; public bool FillNoLabel; public bool RoundUpPrice; public bool RoundUpCardPrice; public bool AvoidSetCardPrice; public bool AvoidSetCardPriceRestock; public float PriceMult; public float CardPriceMult; public int PackIndex = -1; public bool PackEnabled; public List PackChanges = new List(); public byte BonusCount; public bool BonusBoosted; public byte ExperienceTask; public int ExperienceAmount; public bool Granted; public bool Occupied; public Guid PredictionId { get; set; } } public sealed class StaffPackChange { public int Index; public bool Enabled; } public struct StaffModuleEntry { public uint Generation; public bool Hired; public bool HasData; public byte PrimaryTask; public byte SecondaryTask; public byte WorkerTask; public byte CurrentState; public bool GoingHome; public byte BonusCount; public bool BonusBoosted; public bool FillNoLabel; public bool RoundUpPrice; public bool RoundUpCardPrice; public bool AvoidSetCardPrice; public bool AvoidSetCardPriceRestock; public float PriceMult; public float CardPriceMult; public List PackTypes; public List ExpList; } internal static class StaffModuleInterop { internal const int MaxWorkers = 32; internal static readonly FieldInfo PanelIsHired = AccessTools.Field(typeof(HireWorkerPanelUI), "m_IsHired"); internal static readonly FieldInfo PanelIndex = AccessTools.Field(typeof(HireWorkerPanelUI), "m_Index"); internal static readonly FieldInfo PanelLevelRequired = AccessTools.Field(typeof(HireWorkerPanelUI), "m_LevelRequired"); internal static readonly FieldInfo PanelHireFee = AccessTools.Field(typeof(HireWorkerPanelUI), "m_TotalHireFee"); internal static readonly FieldInfo PanelScreen = AccessTools.Field(typeof(HireWorkerPanelUI), "m_HireWorkerScreen"); internal static readonly MethodInfo PanelEvaluateHired = AccessTools.Method(typeof(HireWorkerPanelUI), "EvaluateHired", (Type[])null, (Type[])null); internal static readonly FieldInfo InteractWorker = AccessTools.Field(typeof(WorkerInteractUIScreen), "m_Worker"); internal static readonly FieldInfo OptionWorker = AccessTools.Field(typeof(WorkerOptionUIScreen), "m_Worker"); internal static readonly FieldInfo PriceWorker = AccessTools.Field(typeof(WorkerOptionSetPriceUIScreen), "m_Worker"); internal static readonly FieldInfo PackWorker = AccessTools.Field(typeof(WorkerSetPackOpenerTypeOptionScreen), "m_Worker"); internal static readonly FieldInfo TaskToSet = AccessTools.Field(typeof(WorkerInteractUIScreen), "m_TaskToSet"); internal static readonly FieldInfo OptionTaskIndex = AccessTools.Field(typeof(WorkerOptionUIScreen), "m_TaskIndex"); internal static readonly FieldInfo PriceTask = AccessTools.Field(typeof(WorkerOptionSetPriceUIScreen), "m_TaskToSet"); internal static readonly FieldInfo PriceRoundUp = AccessTools.Field(typeof(WorkerOptionSetPriceUIScreen), "m_IsRoundUpPrice"); internal static readonly FieldInfo PriceCanSetCard = AccessTools.Field(typeof(WorkerOptionSetPriceUIScreen), "m_CanSetCardPrice"); internal static readonly FieldInfo PriceMultiplier = AccessTools.Field(typeof(WorkerOptionSetPriceUIScreen), "m_SetPricePercentMultiplier"); internal static readonly FieldInfo PackEnabled = AccessTools.Field(typeof(WorkerSetPackOpenerTypeOptionScreen), "m_IsPackTypeEnabled"); internal static readonly FieldInfo WorkerTargetRotation = AccessTools.Field(typeof(Worker), "m_TargetLerpRotation"); private static readonly FieldInfo WorkerList = AccessTools.Field(typeof(WorkerManager), "m_WorkerList"); private static readonly MethodInfo EvaluateSkillLevel = AccessTools.Method(typeof(Worker), "EvaluateSkillLevel", (Type[])null, (Type[])null); internal static WorkerManager FindWorkerManager() { return SceneRef.Get(); } internal static Worker WorkerFrom(FieldInfo field, object instance) { object? obj = field?.GetValue(instance); return (Worker)((obj is Worker) ? obj : null); } internal static List GetWorkers(WorkerManager manager) { if (!((Object)(object)manager == (Object)null) && !(WorkerList == null)) { return WorkerList.GetValue(manager) as List; } return null; } internal static bool TryGetWorker(WorkerManager manager, int index, out Worker worker) { worker = null; List workers = GetWorkers(manager); if (workers == null || index < 0 || index >= workers.Count) { return false; } worker = workers[index]; return (Object)(object)worker != (Object)null; } internal static bool TryGetWorkerFromPuppet(int index, out Worker worker) { worker = NpcClientBehaviour.GetWorkerPuppet(index); return (Object)(object)worker != (Object)null; } internal static bool IsHired(int index) { if (CPlayerData.m_IsWorkerHired != null && index >= 0 && index < CPlayerData.m_IsWorkerHired.Count) { return CPlayerData.GetIsWorkerHired(index); } return false; } internal static bool IsAddressable(WorkerManager manager, int index) { if ((Object)(object)manager != (Object)null && manager.m_WorkerDataList != null && index >= 0) { return index < Mathf.Min(manager.m_WorkerDataList.Count, 32); } return false; } internal static bool IsValidWorker(WorkerManager manager, int index) { if (IsAddressable(manager, index) && TryGetWorker(manager, index, out var _)) { return IsHired(index); } return false; } internal static bool IsValidTask(byte value) { return Enum.IsDefined(typeof(EWorkerTask), (object)(EWorkerTask)value); } internal static bool IsValidIntentTask(byte value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 EWorkerTask val = (EWorkerTask)value; if (value > 6) { return (int)val == 99; } return true; } internal static bool IsValidState(byte value) { return Enum.IsDefined(typeof(EWorkerState), (object)(EWorkerState)value); } internal static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(value.x) && IsFinite(value.y)) { return IsFinite(value.z); } return false; } internal static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } internal static StaffModuleEntry CaptureEntry(WorkerManager manager, int index) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) StaffModuleEntry result = new StaffModuleEntry { Hired = IsHired(index) }; WorkerSaveData val = null; List workers = GetWorkers(manager); Worker val2 = ((workers != null && index >= 0 && index < workers.Count) ? workers[index] : null); if ((Object)(object)val2 != (Object)null && val2.m_IsActive) { try { val = val2.GetWorkerSaveData(); } catch (Exception e) { Swallow.Log(e, "CaptureEntry", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Staff/StaffModuleInterop.cs", 138); } } List workerSaveDataList = CPlayerData.m_WorkerSaveDataList; if (val == null && workerSaveDataList != null && index >= 0 && index < workerSaveDataList.Count) { val = workerSaveDataList[index]; } if (val == null) { return result; } result.HasData = true; result.PrimaryTask = (byte)val.primaryTask; result.SecondaryTask = (byte)val.secondaryTask; result.WorkerTask = (byte)val.workerTask; result.CurrentState = (byte)val.currentState; result.GoingHome = val.isGoingHome; result.BonusCount = (byte)Mathf.Clamp(val.bonusBoostedCount, 0, 255); result.BonusBoosted = val.isBonusBoosted; result.FillNoLabel = val.isFillShelfWithoutLabel; result.RoundUpPrice = val.isRoundUpPrice; result.RoundUpCardPrice = val.isRoundUpCardPrice; result.AvoidSetCardPrice = val.isAvoidSetCardPrice; result.AvoidSetCardPriceRestock = val.isAvoidSetCardPriceWhileRestock; result.PriceMult = val.setPriceMultiplier; result.CardPriceMult = val.setCardPriceMultiplier; result.PackTypes = ((val.cardPackItemTypeEnabledList == null) ? null : new List(val.cardPackItemTypeEnabledList)); result.ExpList = ((val.expList == null) ? null : new List(val.expList)); return result; } internal static StaffModuleEntry CaptureWorkerEntry(Worker worker, int index) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) StaffModuleEntry result = new StaffModuleEntry { Hired = IsHired(index) }; if ((Object)(object)worker == (Object)null) { return result; } result.HasData = true; result.PrimaryTask = (byte)worker.m_PrimaryTask; result.SecondaryTask = (byte)worker.m_SecondaryTask; result.WorkerTask = (byte)worker.m_WorkerTask; result.CurrentState = (byte)worker.m_CurrentState; result.BonusCount = (byte)Mathf.Clamp(worker.GetBonusBoostedCount(), 0, 255); result.BonusBoosted = worker.m_IsBonusBoosted; result.FillNoLabel = worker.GetWorkerSaveData().isFillShelfWithoutLabel; result.RoundUpPrice = worker.GetIsRoundUpPrice(); result.RoundUpCardPrice = worker.GetIsRoundUpCardPrice(); result.AvoidSetCardPrice = worker.GetIsAvoidSetCardPrice(); result.AvoidSetCardPriceRestock = worker.GetIsAvoidSetCardPriceWhileRestock(); result.PriceMult = worker.GetPriceMultiplier(); result.CardPriceMult = worker.GetCardPriceMultiplier(); List cardPackItemTypeEnabledList = worker.GetCardPackItemTypeEnabledList(); result.PackTypes = ((cardPackItemTypeEnabledList == null) ? null : new List(cardPackItemTypeEnabledList)); result.ExpList = ((worker.m_ExpList == null) ? null : new List(worker.m_ExpList)); return result; } internal static List CaptureAll(WorkerManager manager) { List list = new List(); if ((Object)(object)manager == (Object)null || manager.m_WorkerDataList == null) { return list; } int num = Mathf.Min(manager.m_WorkerDataList.Count, 32); for (int i = 0; i < num; i++) { list.Add(CaptureEntry(manager, i)); } return list; } internal static void ApplyEntryToSave(StaffModuleEntry entry, WorkerSaveData data) { //IL_0007: 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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) data.primaryTask = (EWorkerTask)entry.PrimaryTask; data.secondaryTask = (EWorkerTask)entry.SecondaryTask; data.workerTask = (EWorkerTask)entry.WorkerTask; data.currentState = (EWorkerState)entry.CurrentState; data.isGoingHome = entry.GoingHome; data.bonusBoostedCount = entry.BonusCount; data.isBonusBoosted = entry.BonusBoosted; data.isFillShelfWithoutLabel = entry.FillNoLabel; data.isRoundUpPrice = entry.RoundUpPrice; data.isRoundUpCardPrice = entry.RoundUpCardPrice; data.isAvoidSetCardPrice = entry.AvoidSetCardPrice; data.isAvoidSetCardPriceWhileRestock = entry.AvoidSetCardPriceRestock; data.setPriceMultiplier = entry.PriceMult; data.setCardPriceMultiplier = entry.CardPriceMult; if (entry.PackTypes != null) { data.cardPackItemTypeEnabledList = new List(entry.PackTypes); } if (entry.ExpList != null) { data.expList = new List(entry.ExpList); } } internal static void ApplyEntryToWorker(Worker worker, StaffModuleEntry entry) { //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) if ((Object)(object)worker == (Object)null) { return; } worker.m_IsActive = entry.Hired; if ((Object)(object)worker.m_WorkerCollider != (Object)null) { ((Component)worker.m_WorkerCollider).gameObject.SetActive(entry.Hired); } worker.m_PrimaryTask = (EWorkerTask)entry.PrimaryTask; worker.m_SecondaryTask = (EWorkerTask)entry.SecondaryTask; worker.m_WorkerTask = (EWorkerTask)entry.WorkerTask; worker.m_CurrentState = (EWorkerState)entry.CurrentState; worker.m_IsBonusBoosted = entry.BonusBoosted; worker.m_BonusBoostedCount = entry.BonusCount; worker.SetRestockShelfWithNoLabel(entry.FillNoLabel); worker.UpdateSetPriceOption(entry.RoundUpPrice, entry.AvoidSetCardPrice, entry.PriceMult); worker.UpdateSetCardPriceOption(entry.RoundUpCardPrice, entry.AvoidSetCardPriceRestock, entry.CardPriceMult); List cardPackItemTypeEnabledList = worker.GetCardPackItemTypeEnabledList(); if (entry.PackTypes != null && cardPackItemTypeEnabledList != null) { while (cardPackItemTypeEnabledList.Count < entry.PackTypes.Count) { cardPackItemTypeEnabledList.Add(item: true); } for (int i = 0; i < entry.PackTypes.Count; i++) { worker.SetCardPackItemTypeEnabled(i, entry.PackTypes[i]); } } if (entry.ExpList != null && worker.m_ExpList != null) { worker.m_ExpList.Clear(); worker.m_ExpList.AddRange(entry.ExpList); } } internal static void SetExperience(Worker worker, EWorkerTask task, int amount) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected I4, but got Unknown if (worker?.m_ExpList != null) { int num = (int)task; if (num >= 0 && num < worker.m_ExpList.Count) { worker.m_ExpList[num] = amount; EvaluateSkillLevel?.Invoke(worker, null); } } } internal static void AimWorkerAtPlayer(Worker worker, Vector3 playerPosition) { //IL_0017: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_004f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)worker == (Object)null) && !(WorkerTargetRotation == null)) { Vector3 val = playerPosition - ((Component)worker).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.0001f) { WorkerTargetRotation.SetValue(worker, Quaternion.LookRotation(val, Vector3.up)); } } } internal static void RefreshWorkerUi(int index, WorkerSaveData data) { NpcClientBehaviour.RefreshWorkerUi(index, data); } } } namespace CardShopCoop.Modules.Shop { [ClientBehaviour] public sealed class ShopClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(ShopRenamer), "OnPressConfirmShopName")] private static class ShopConfirmPatch { [HarmonyPostfix] private static void Postfix() { if (!((Object)(object)_active == (Object)null) && _active._context.InGame()) { _active.PredictRenameObserved(); } } } [HarmonyPatch(typeof(ShopRenamer), "OnEnable")] private static class ShopRenamerReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.ApplyPendingNameWhenReady(); } } private static ShopClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private string _pendingName; private bool _pendingInitial; private bool _remoteInitialNamingApplied; private const string PredictionScope = "shop.rename"; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.shop.client"); _harmony.CreateClassProcessor(typeof(ShopConfirmPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ShopRenamerReadyPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Shop client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _pendingName = null; _pendingInitial = false; _remoteInitialNamingApplied = false; ShopState.Reset(); _context = null; throw; } } [MessageHandler(typeof(ShopRenameDeltaMessage))] private void Handle(MessageContext _, ShopRenameDeltaMessage message) { if (message != null) { PredictionApi.Confirm(message.PredictionId, delegate { _pendingName = message.Name; _pendingInitial = message.Initial; ApplyPendingNameWhenReady(); }); } } private void ApplyPendingNameWhenReady() { if (_pendingName == null || !_context.InGame() || !ShopState.Apply(_pendingName)) { return; } if (!_pendingInitial && !_remoteInitialNamingApplied) { if (!ShopInterop.ApplyRemoteInitialNamingSideEffects()) { return; } _remoteInitialNamingApplied = true; } _pendingName = null; _pendingInitial = false; } private void PredictRenameObserved() { var (name, previous) = CaptureRename(); PredictionApi.Predict("shop.rename", RenameSend(name), delegate { ShopState.Apply(name); }, delegate { ShopState.Apply(previous); }); ShopState.Apply(name); } private static (string Name, string Previous) CaptureRename() { string playerName = CPlayerData.GetPlayerName(); if (!ShopState.TryGet(out var name)) { name = playerName; } CoopPlugin.Log.LogDebug((object)("Shop rename proposal sent to host: " + playerName)); return (Name: playerName, Previous: name); } private Action RenameSend(string name) { return delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new ShopRenameRequestMessage { PredictionId = predictionId, Name = name }); }; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _pendingName = null; _pendingInitial = false; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _context = null; ShopState.Reset(); _remoteInitialNamingApplied = false; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class ShopHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(ShopRenamer), "OnPressConfirmShopName")] private static class ShopConfirmPatch { [HarmonyPostfix] private static void Postfix() { if (!((Object)(object)_active == (Object)null) && _active._context.InGame() && ShopState.TryNormalize(CPlayerData.GetPlayerName(), out var canonical)) { ShopState.Apply(canonical); _active.BroadcastRename(canonical, Guid.Empty); } } } private static ShopHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private void OnEnable() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { if (!ShopState.TryGet(out var _)) { CoopPlugin.Log.LogWarning((object)"Shop host started before a valid canonical shop name was loaded."); } _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.shop.host"); _harmony.CreateClassProcessor(typeof(ShopConfirmPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Shop host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } ClearSessionState(); _context = null; throw; } } [OnFullyJoined] private void SendBaseline(PeerConnection connection) { if (!_shutdown && connection != null && ShopState.TryGet(out var name)) { _context.Send(connection.Id, (INetMessage)(object)new ShopRenameDeltaMessage { PredictionId = Guid.Empty, Name = name, Initial = true }); } } [MessageHandler(typeof(ShopRenameRequestMessage))] private void Handle(MessageContext context, ShopRenameRequestMessage message) { if (!IsAuthenticatedSender(context) || message == null) { return; } if (!ShopState.TryNormalize(message.Name, out var canonical)) { Reject(context, message.PredictionId); return; } if (!ShopState.Apply(canonical)) { CoopPlugin.Log.LogDebug((object)"Shop rename accepted while the rename UI was unavailable."); } if (!ShopInterop.ApplyRemoteInitialNamingSideEffects()) { CoopPlugin.Log.LogDebug((object)"Shop rename accepted without completing the local rename UI/tutorial side effects."); } BroadcastRename(canonical, message.PredictionId); } private bool IsAuthenticatedSender(MessageContext context) { if (!_shutdown && _context.InGame() && context?.Connection != null) { return context.IsAuthenticated; } return false; } private void Reject(MessageContext context, Guid predictionId) { if (predictionId != Guid.Empty && context?.Connection != null) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, predictionId); } } private void BroadcastRename(string name, Guid predictionId) { if (!_shutdown && _context.InGame()) { _context.Broadcast((INetMessage)(object)new ShopRenameDeltaMessage { PredictionId = predictionId, Name = name }); } } private void ClearSessionState() { ShopState.Reset(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; ClearSessionState(); _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class ShopInterop { private static ShopRenamer _signRenamer; private static readonly FieldInfo FiIsTutorial; private static readonly FieldInfo FiTutorialIndex; private static readonly FieldInfo FiTutorialIndicator; private static readonly MethodInfo MiEvaluateTaskVisibility; private static Type _grantBonusType; private static ConstructorInfo _grantBonusConstructor; private static bool _grantBonusResolved; static ShopInterop() { FiIsTutorial = ReflectionSurface.RequiredField(typeof(ShopRenamer), "m_IsTutorial"); FiTutorialIndex = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_TutorialIndex"); FiTutorialIndicator = ReflectionSurface.RequiredField(typeof(TutorialManager), "m_TutorialTargetIndicator"); MiEvaluateTaskVisibility = ReflectionSurface.RequiredMethod(typeof(TutorialManager), "EvaluateTaskVisibility"); SceneRef.Register(delegate { _signRenamer = null; }, delegate(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) InvalidateForScene(scene); }, delegate(Scene scene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) InvalidateForScene(scene); }); } internal static void Reset() { _signRenamer = null; } private static void InvalidateForScene(Scene scene) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_signRenamer == (Object)null)) { Scene scene2 = ((Component)_signRenamer).gameObject.scene; if (((Scene)(ref scene2)).IsValid()) { scene2 = ((Component)_signRenamer).gameObject.scene; if (((Scene)(ref scene2)).handle != ((Scene)(ref scene)).handle) { return; } } } _signRenamer = null; } internal static ShopRenamer[] FindRenamers() { ShopRenamer val = SceneRef.Get(); if (!((Object)(object)val == (Object)null)) { return (ShopRenamer[])(object)new ShopRenamer[1] { val }; } return (ShopRenamer[])(object)new ShopRenamer[0]; } internal static ShopRenamer[] FindSignRenamers() { if ((Object)(object)_signRenamer == (Object)null) { _signRenamer = FindSceneObject(); if ((Object)(object)_signRenamer == (Object)null) { CoopPlugin.Log.LogDebug((object)"Shop renamer is not present in the scene."); } } if (!((Object)(object)_signRenamer == (Object)null)) { return (ShopRenamer[])(object)new ShopRenamer[1] { _signRenamer }; } return (ShopRenamer[])(object)new ShopRenamer[0]; } private static T FindSceneObject() where T : Component { //IL_002f: 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) T[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null) { Scene scene = ((Component)array[i]).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return array[i]; } } } return default(T); } internal static ShopRenamer FindRenamer() { ShopRenamer[] array = FindRenamers(); if (array.Length != 0) { return array[0]; } return null; } internal static bool ApplyRenamerText(ShopRenamer renamer, string name) { bool result = false; if ((Object)(object)renamer.m_ShopName != (Object)null) { ((TMP_Text)renamer.m_ShopName).text = name; result = true; } if ((Object)(object)renamer.m_SetName != (Object)null) { ((TMP_Text)renamer.m_SetName).text = name; result = true; } if ((Object)(object)renamer.m_SetNameInputDisplay != (Object)null) { ((TMP_Text)renamer.m_SetNameInputDisplay).text = name; result = true; } if ((Object)(object)renamer.m_SetNameInput != (Object)null) { renamer.m_SetNameInput.text = name; result = true; } return result; } internal static void CloseRenameUi(ShopRenamer renamer) { if (!((Object)(object)renamer == (Object)null)) { GameObject introScreen = renamer.m_IntroScreen; if (introScreen != null) { introScreen.SetActive(false); } GameObject nameScreen = renamer.m_NameScreen; if (nameScreen != null) { nameScreen.SetActive(false); } GameObject confirmNameScreen = renamer.m_ConfirmNameScreen; if (confirmNameScreen != null) { confirmNameScreen.SetActive(false); } ((Component)renamer).gameObject.SetActive(false); InteractionPlayerController obj = SceneRef.Get(); if (obj != null) { obj.ExitUIMode(); } InteractionPlayerController obj2 = SceneRef.Get(); if (obj2 != null) { obj2.ExitLockMoveMode(); } GameUIScreen.SetGameUIVisible(true); ControllerScreenUIExtManager.OnCloseScreen(renamer.m_ControllerScreenUIExtension_ConfirmNameScreen); } } internal static bool ApplyRemoteInitialNamingSideEffects() { ShopRenamer[] array = FindRenamers(); TutorialManager val = SceneRef.Get(); if (array.Length == 0 || (Object)(object)val == (Object)null) { return false; } bool flag = Convert.ToInt32(FiTutorialIndex.GetValue(null)) == 0; foreach (ShopRenamer val2 in array) { if ((Object)(object)val2 != (Object)null && Convert.ToBoolean(FiIsTutorial.GetValue(val2))) { flag = true; } } for (int j = 0; j < array.Length; j++) { CloseRenameUi(array[j]); } if (!flag) { return true; } for (int k = 0; k < array.Length; k++) { if ((Object)(object)array[k] != (Object)null) { FiIsTutorial.SetValue(array[k], false); } } FiTutorialIndex.SetValue(null, 1); MiEvaluateTaskVisibility.Invoke(val, null); object? value = FiTutorialIndicator.GetValue(val); object? obj = ((value is GameObject) ? value : null); if (obj != null) { ((GameObject)obj).SetActive(false); } QueueGrantEntitlementsBonus(); return true; } private static void QueueGrantEntitlementsBonus() { if (!_grantBonusResolved) { _grantBonusType = ReflectionSurface.OptionalType("CEventPlayer_GrantEntitlementsBonus", "Assembly-CSharp"); _grantBonusConstructor = _grantBonusType?.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null); _grantBonusResolved = true; } if (!(_grantBonusConstructor == null)) { object obj = _grantBonusConstructor.Invoke(new object[1] { true }); CEvent val = (CEvent)((obj is CEvent) ? obj : null); if (val != null) { CEventManager.QueueEvent(val); } } } } [NetworkMessage] public sealed class ShopRenameRequestMessage : IPredictedMessage, INetMessage { public string Name; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class ShopRenameDeltaMessage : IPredictedMessage, INetMessage { public string Name; public bool Initial; public Guid PredictionId { get; set; } } internal static class ShopState { internal const int MaxNameLength = 64; private static string _canonicalName; internal static void Reset() { _canonicalName = null; ShopInterop.Reset(); } internal static bool TryNormalize(string proposed, out string canonical) { canonical = proposed?.Trim(); if (string.IsNullOrWhiteSpace(canonical) || canonical.Length > 64) { canonical = null; return false; } for (int i = 0; i < canonical.Length; i++) { if (char.IsControl(canonical[i])) { canonical = null; return false; } } return true; } internal static bool TryGet(out string name) { if (TryNormalize(_canonicalName, out name)) { return true; } if (!TryNormalize(CPlayerData.GetPlayerName(), out name)) { name = null; return false; } _canonicalName = name; return true; } internal static bool Apply(string name) { _canonicalName = name; CPlayerData.PlayerName = name; ShopRenamer[] array = ShopInterop.FindSignRenamers(); bool flag = false; foreach (ShopRenamer val in array) { if (!((Object)(object)val == (Object)null)) { flag |= ShopInterop.ApplyRenamerText(val, name); } } return flag; } } } namespace CardShopCoop.Modules.Settings { [ClientBehaviour] public sealed class SettingsClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(SetGameEventFormatScreen), "OnPressConfirmBtn")] private static class GameEventFormatPatch { [HarmonyPrefix] private static void Prefix(out FormatBefore __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) __state = new FormatBefore { Format = CPlayerData.m_PendingGameEventFormat, Expansion = CPlayerData.m_PendingGameEventExpansionType }; } [HarmonyPostfix] private static void Postfix(FormatBefore __state) { //IL_0001: 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) SendGameEventIntent(__state.Format, __state.Expansion); } } [HarmonyPatch(typeof(SetGameEventScreen), "OnPressReset")] private static class GameEventResetPatch { [HarmonyPrefix] private static void Prefix(out FormatBefore __state) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) __state = new FormatBefore { Format = CPlayerData.m_PendingGameEventFormat, Expansion = CPlayerData.m_PendingGameEventExpansionType }; } [HarmonyPostfix] private static void Postfix(FormatBefore __state) { //IL_0001: 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) SendGameEventIntent(__state.Format, __state.Expansion); } } [HarmonyPatch(typeof(PriceChangeManager), "SetGameEventPrice")] private static class GameEventFeePatch { [HarmonyPrefix] private static void Prefix(EGameEventFormat gameEventFormat, out float __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) __state = PriceChangeManager.GetGameEventPrice(gameEventFormat, true); } [HarmonyPostfix] private static void Postfix(EGameEventFormat gameEventFormat, float price, float __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) SendGameEventFeeIntent(gameEventFormat, price, __state); } } [HarmonyPatch(typeof(InteractableCashierCounter), "SetCanCheckout")] private static class CashierCheckoutPatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __instance, out byte __state) { __state = (byte)((__instance.CanCheckout() ? 1u : 0u) | (uint)(__instance.CanTradeCard() ? 2 : 0)); } [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance, byte __state) { SendCashierIntent(__instance, __state); } } [HarmonyPatch(typeof(InteractableCashierCounter), "SetCanTradeCard")] private static class CashierTradePatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __instance, out byte __state) { __state = (byte)((__instance.CanCheckout() ? 1u : 0u) | (uint)(__instance.CanTradeCard() ? 2 : 0)); } [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance, byte __state) { SendCashierIntent(__instance, __state); } } [HarmonyPatch(typeof(InteractablePlayTable), "Awake")] private static class TableInitPatch { [HarmonyPostfix] private static void Postfix() { _active?.TryApplyPendingState(); } } [HarmonyPatch(typeof(InteractableCashierCounter), "Awake")] private static class CashierInitPatch { [HarmonyPostfix] private static void Postfix() { _active?.TryApplyPendingState(); } } [HarmonyPatch(typeof(InteractablePlayTable), "SetTournamentPlayTableNumber")] private static class TableNumberPatch { [HarmonyPrefix] private static void Prefix(InteractablePlayTable __instance, out int __state) { __state = __instance.GetTournamentPlayTableNumber(); } [HarmonyPostfix] private static void Postfix(InteractablePlayTable __instance, int tableNumber, int __state) { SendTableIntent(__instance, tableNumber, __state); } } private struct FormatBefore { internal EGameEventFormat Format; internal ECardExpansionType Expansion; } private const byte OpGameEvent = 3; private const byte OpGameEventFee = 4; private const byte OpCashier = 5; private const byte OpTableNumber = 6; private static SettingsClientBehaviour _active; private static bool _applyingRemote; private CoopRuntimeContext _context; private Harmony _harmony; private SettingsStateMessage _pendingFullState; private readonly Dictionary _pendingStates = new Dictionary(); private bool _shutdown; private bool _joined; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.settings.client"); Patch(typeof(GameEventFormatPatch)); Patch(typeof(GameEventResetPatch)); Patch(typeof(GameEventFeePatch)); Patch(typeof(CashierCheckoutPatch)); Patch(typeof(CashierTradePatch)); Patch(typeof(TableNumberPatch)); Patch(typeof(CashierInitPatch)); Patch(typeof(TableInitPatch)); CEventManager.AddListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded += OnSceneLoaded; PlacementApi.StructureChanged += OnPlacementStructureChanged; } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Settings client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded -= OnSceneLoaded; PlacementApi.StructureChanged -= OnPlacementStructureChanged; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { TryApplyPendingState(); } private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _) { TryApplyPendingState(); } private void OnPlacementStructureChanged(int _) { TryApplyPendingState(); } private void TryApplyPendingState() { if (_shutdown || !_context.InGame() || !SettingsInterop.IsSceneReady) { return; } if (_pendingFullState != null) { SettingsStateMessage full = _pendingFullState; _pendingFullState = null; PredictionApi.Confirm(full.PredictionId, delegate { ApplyState(full); }); } if (_pendingStates.Count <= 0) { return; } List> list = new List>(_pendingStates); for (int num = 0; num < list.Count; num++) { SettingsStateMessage state = list[num].Value; if (CanApply(state)) { _pendingStates.Remove(list[num].Key); PredictionApi.Confirm(state.PredictionId, delegate { ApplyState(state); }); } } } private static bool CanApply(SettingsStateMessage message) { if (message == null || message.Full || message.Tombstone) { return true; } return message.Index switch { 6 => (Object)(object)SettingsInterop.ResolveCashier(message.CashierKey) != (Object)null, 7 => (Object)(object)SettingsInterop.ResolveTable(message.TableKey) != (Object)null, _ => true, }; } [MessageHandler(typeof(SettingsStateMessage))] private void HandleState(MessageContext context, SettingsStateMessage message) { if (_shutdown) { return; } if (message.Full) { ClearPendingStates(); _pendingFullState = message; } else { string key = StateKey(message); if (_pendingStates.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingStates[key] = message; } TryApplyPendingState(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ClearPendingStates(); } } private bool ApplyState(SettingsStateMessage message) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) _applyingRemote = true; try { if (!message.Full) { return ApplyPartial(message); } CPlayerData.m_GameEventFormat = (EGameEventFormat)message.GameEventFormat; CPlayerData.m_PendingGameEventFormat = (EGameEventFormat)message.PendingGameEventFormat; CPlayerData.m_GameEventExpansionType = message.GameEventExpansion; CPlayerData.m_PendingGameEventExpansionType = message.PendingGameEventExpansion; ApplyFees(message.GameEventPrices, message.GameEventPrices.Count); ApplyCashiers(message.CashierFlags, message.CashierKeys, message.CashierFlags.Count); ApplyTables(message.TableNumbers, message.TableKeys, message.TableNumbers.Count); return true; } finally { _applyingRemote = false; } } private static bool ApplyPartial(SettingsStateMessage message) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (message.Index == 4) { CPlayerData.m_GameEventFormat = (EGameEventFormat)message.GameEventFormat; CPlayerData.m_PendingGameEventFormat = (EGameEventFormat)message.PendingGameEventFormat; CPlayerData.m_GameEventExpansionType = message.GameEventExpansion; CPlayerData.m_PendingGameEventExpansionType = message.PendingGameEventExpansion; } else if (message.Index == 5) { int gameEventPriceCount = message.GameEventPriceCount; ResizeFees(gameEventPriceCount); if (message.ItemIndex >= 0) { if (!message.Tombstone && message.ItemIndex < CPlayerData.m_SetGameEventPriceList.Count && message.GameEventPrices.Count > 0) { CPlayerData.m_SetGameEventPriceList[message.ItemIndex] = message.GameEventPrices[0]; } } else { ApplyFees(message.GameEventPrices, gameEventPriceCount); } } else if (message.Index == 6) { ApplyCashierTail(message.CashierCount); if (!message.Tombstone && message.CashierFlags.Count > 0 && !ApplyCashierByKey(message.CashierKey, message.CashierFlags[0])) { return false; } } else { if (message.Index != 7) { throw new ArgumentOutOfRangeException("Index", message.Index, "Unknown authoritative settings discriminator."); } ApplyTableTail(message.TableCount); if (!message.Tombstone && message.TableNumbers.Count > 0 && !ApplyTableByKey(message.TableKey, message.TableNumbers[0])) { return false; } } return true; } private static string StateKey(SettingsStateMessage message) { if (message.Index == 6) { return "6:" + message.CashierKey; } if (message.Index == 7) { return "7:" + message.TableKey; } return message.Index + ":" + message.ItemIndex; } private void ClearPendingStates() { foreach (SettingsStateMessage value in _pendingStates.Values) { PredictionApi.Ack(value.PredictionId); } _pendingStates.Clear(); if (_pendingFullState != null) { PredictionApi.Ack(_pendingFullState.PredictionId); } _pendingFullState = null; } private static bool ApplyCashierByKey(int key, byte flags) { InteractableCashierCounter val = SettingsInterop.ResolveCashier(key); if ((Object)(object)val == (Object)null) { return false; } bool canCheckout = (flags & 1) != 0; bool canTradeCard = (flags & 2) != 0; val.SetCanCheckout(canCheckout); val.SetCanTradeCard(canTradeCard); return true; } private static bool ApplyTableByKey(int key, byte number) { InteractablePlayTable val = SettingsInterop.ResolveTable(key); if ((Object)(object)val == (Object)null) { return false; } val.SetTournamentPlayTableNumber((int)number); return true; } private static void ResizeFees(int count) { List setGameEventPriceList = CPlayerData.m_SetGameEventPriceList; while (setGameEventPriceList.Count > count) { setGameEventPriceList.RemoveAt(setGameEventPriceList.Count - 1); } while (setGameEventPriceList.Count < count) { setGameEventPriceList.Add(0f); } } private static void ApplyFees(List values, int count) { List setGameEventPriceList = CPlayerData.m_SetGameEventPriceList; ResizeFees(count); for (int i = 0; i < values.Count && i < setGameEventPriceList.Count; i++) { setGameEventPriceList[i] = values[i]; } } private static void ApplyCashiers(List flags, List keys, int count) { ApplyCashierTail(count); for (int i = 0; i < flags.Count && i < keys.Count; i++) { if (keys[i] != 0) { ApplyCashierByKey(keys[i], flags[i]); } } } private static void ApplyCashierTail(int count) { List list = SettingsInterop.FindShelfManager()?.m_CashierCounterList; if (list == null) { return; } for (int i = ((count >= 0) ? count : 0); i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null) { list[i].SetCanCheckout(true); list[i].SetCanTradeCard(true); } } } private static void ApplyTables(List numbers, List keys, int count) { ApplyTableTail(count); for (int i = 0; i < numbers.Count && i < keys.Count; i++) { if (keys[i] != 0) { ApplyTableByKey(keys[i], numbers[i]); } } } private static void ApplyTableTail(int count) { List list = SettingsInterop.FindShelfManager()?.m_PlayTableList; if (list == null) { return; } for (int i = ((count >= 0) ? count : 0); i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null) { list[i].SetTournamentPlayTableNumber(0); } } } private static void SendGameEventIntent(EGameEventFormat previousFormat, ECardExpansionType previousExpansion) { //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_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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected I4, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (_applyingRemote || (Object)(object)_active == (Object)null || _active._shutdown || !_active._context.InGame()) { return; } _active.SendIntent(new SettingsOpMessage { Op = 3, Index = (int)CPlayerData.m_PendingGameEventFormat, Expansion = CPlayerData.m_PendingGameEventExpansionType }, delegate { ApplyUndo(delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_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) CPlayerData.m_PendingGameEventFormat = previousFormat; CPlayerData.m_PendingGameEventExpansionType = previousExpansion; }); }); } private static void SendGameEventFeeIntent(EGameEventFormat format, float fee, float previousFee) { //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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected I4, but got Unknown if (_applyingRemote || (Object)(object)_active == (Object)null || _active._shutdown || !_active._context.InGame()) { return; } _active.SendIntent(new SettingsOpMessage { Op = 4, Index = (int)format, Fee = fee }, delegate { ApplyUndo(delegate { //IL_0001: Unknown result type (might be due to invalid IL or missing references) PriceChangeManager.SetGameEventPrice(format, previousFee); }); }); } private static void SendCashierIntent(InteractableCashierCounter counter, byte previousFlags) { if (_applyingRemote || (Object)(object)_active == (Object)null || _active._shutdown || !_active._context.InGame() || (Object)(object)counter == (Object)null) { return; } if (!SettingsInterop.TryGetCashierKey(counter, out var key)) { CoopPlugin.Log.LogWarning((object)"Settings client: cashier mutation has no stable identity"); return; } _active.SendIntent(new SettingsOpMessage { Op = 5, CashierKey = key, CashierFlags = (byte)((counter.CanCheckout() ? 1u : 0u) | (uint)(counter.CanTradeCard() ? 2 : 0)) }, delegate { ApplyUndo(delegate { counter.SetCanCheckout((previousFlags & 1) != 0); counter.SetCanTradeCard((previousFlags & 2) != 0); }); }); } private static void SendTableIntent(InteractablePlayTable table, int number, int previousNumber) { if (_applyingRemote || (Object)(object)_active == (Object)null || _active._shutdown || !_active._context.InGame() || (Object)(object)table == (Object)null) { return; } if (!SettingsInterop.TryGetTableKey(table, out var key)) { CoopPlugin.Log.LogWarning((object)"Settings client: table mutation has no stable identity"); return; } _active.SendIntent(new SettingsOpMessage { Op = 6, TableKey = key, TableNumber = number }, delegate { ApplyUndo(delegate { table.SetTournamentPlayTableNumber(previousNumber); }); }); } private void SendIntent(SettingsOpMessage message, Action undo) { if (!_shutdown && message != null && _joined && _context.InGame() && !_applyingRemote) { PredictionApi.Predict("settings", delegate(Guid predictionId) { message.PredictionId = predictionId; _context.Send(1, (INetMessage)(object)message); }, delegate { }, undo ?? ((Action)delegate { })); } } private static void ApplyUndo(Action undo) { _applyingRemote = true; try { undo(); } finally { _applyingRemote = false; } } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; TryApplyPendingState(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded -= OnSceneLoaded; PlacementApi.StructureChanged -= OnPlacementStructureChanged; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } ClearPendingStates(); _joined = false; _applyingRemote = false; _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class SettingsHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(SetGameEventFormatScreen), "OnPressConfirmBtn")] private static class GameEventFormatPatch { [HarmonyPostfix] private static void Postfix() { BroadcastGameEvent(); } } [HarmonyPatch(typeof(SetGameEventScreen), "OnPressReset")] private static class GameEventResetPatch { [HarmonyPostfix] private static void Postfix() { BroadcastGameEvent(); } } [HarmonyPatch(typeof(PriceChangeManager), "SetGameEventPrice")] private static class GameEventFeePatch { [HarmonyPostfix] private static void Postfix(EGameEventFormat gameEventFormat) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) BroadcastGameEventFee(gameEventFormat); } } [HarmonyPatch(typeof(InteractableCashierCounter), "SetCanCheckout")] private static class CashierCheckoutPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { BroadcastCashier(__instance); } } [HarmonyPatch(typeof(InteractableCashierCounter), "SetCanTradeCard")] private static class CashierTradePatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { BroadcastCashier(__instance); } } [HarmonyPatch(typeof(InteractablePlayTable), "SetTournamentPlayTableNumber")] private static class TableNumberPatch { [HarmonyPostfix] private static void Postfix(InteractablePlayTable __instance) { BroadcastTable(__instance); } } [HarmonyPatch(typeof(EndOfDayReportScreen), "DelayGoNextDay")] private static class DelayGoNextDayPatch { [HarmonyPostfix] private static void Postfix(ref IEnumerator __result) { if (__result != null) { __result = Relay(__result); } } private static IEnumerator Relay(IEnumerator inner) { while (true) { EGameEventFormat gameEventFormat = CPlayerData.m_GameEventFormat; ECardExpansionType gameEventExpansionType = CPlayerData.m_GameEventExpansionType; if (!inner.MoveNext()) { break; } if (gameEventFormat != CPlayerData.m_GameEventFormat || gameEventExpansionType != CPlayerData.m_GameEventExpansionType) { BroadcastGameEvent(); } yield return inner.Current; } } } private const byte OpGameEvent = 3; private const byte OpGameEventFee = 4; private const byte OpCashier = 5; private const byte OpTableNumber = 6; private const int MaxWireListCount = 255; private const float GameEventFeeMax = 1000000f; private static SettingsHostBehaviour _active; private static bool _applyingRemote; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.settings.host"); Patch(typeof(GameEventFormatPatch)); Patch(typeof(GameEventResetPatch)); Patch(typeof(GameEventFeePatch)); Patch(typeof(CashierCheckoutPatch)); Patch(typeof(CashierTradePatch)); Patch(typeof(TableNumberPatch)); Patch(typeof(DelayGoNextDayPatch)); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Settings host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State) && _context.InGame() && SettingsInterop.IsSceneReady) { _context.Send(connection.Id, (INetMessage)(object)BuildFullState()); } } [MessageHandler(typeof(SettingsOpMessage))] private void HandleOperation(MessageContext context, SettingsOpMessage message) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) if (!IsFullyJoinedSender(context)) { return; } if (message == null) { CoopPlugin.Log.LogWarning((object)("Settings: rejected null authenticated operation from " + context.Connection.Id + ".")); return; } if (!TryValidateOperation(message, out var reason)) { CoopPlugin.Log.LogWarning((object)("Settings: rejected malformed authenticated operation from " + context.Connection.Id + ": " + reason)); if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); } return; } switch (message.Op) { case 3: CPlayerData.m_PendingGameEventFormat = (EGameEventFormat)message.Index; CPlayerData.m_PendingGameEventExpansionType = message.Expansion; BroadcastMutation(4, -1, message.PredictionId); break; case 4: _applyingRemote = true; try { PriceChangeManager.SetGameEventPrice((EGameEventFormat)message.Index, Mathf.Clamp(message.Fee, 0f, 1000000f)); } finally { _applyingRemote = false; } BroadcastMutation(5, message.Index, message.PredictionId); break; case 5: { InteractableCashierCounter val2 = SettingsInterop.ResolveCashier(message.CashierKey); if ((Object)(object)val2 == (Object)null) { CoopPlugin.Log.LogWarning((object)("Settings: stale cashier key " + message.CashierKey + " from connection " + context.Connection.Id + ".")); if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); } } else { ApplyCashier(val2, message.CashierFlags); PublishCashier(val2, "cashier intent", message.PredictionId); } break; } case 6: { InteractablePlayTable val = SettingsInterop.ResolveTable(message.TableKey); if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)("Settings: stale table key " + message.TableKey + " from connection " + context.Connection.Id + ".")); if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); } } else { ApplyTableNumber(val, message.TableNumber); PublishTable(val, "table number intent", message.PredictionId); } break; } default: CoopPlugin.Log.LogWarning((object)("Settings: unknown sub-op " + message.Op)); break; } } private static bool IsValidEventIndex(int index, bool allowNone) { if (index >= (allowNone ? (-1) : 0)) { return index < 12; } return false; } private static bool IsValidExpansion(ECardExpansionType expansion) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Expected I4, but got Unknown int num = (int)expansion; if (num >= -1 && num != 8) { return CatalogApi.IsDefinedEnumValue(EnumKind.CardExpansion, num); } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool TryValidateOperation(SettingsOpMessage message, out string reason) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) reason = null; switch (message.Op) { case 3: if (!IsValidEventIndex(message.Index, allowNone: true)) { reason = "game event format is outside the enum range"; } else if (message.Index >= 0 && (CPlayerData.m_SetGameEventPriceList == null || message.Index >= CPlayerData.m_SetGameEventPriceList.Count || message.Index >= 255)) { reason = "game event format list index is out of bounds"; } else if (!IsValidExpansion(message.Expansion)) { reason = "game event expansion is outside the enum range"; } return reason == null; case 4: if (!IsValidEventIndex(message.Index, allowNone: false)) { reason = "game event fee format is outside the enum range"; } else if (!IsFinite(message.Fee) || message.Fee < 0f) { reason = "game event fee is not a finite non-negative value"; } else if (CPlayerData.m_SetGameEventPriceList == null || message.Index >= CPlayerData.m_SetGameEventPriceList.Count || message.Index >= 255) { reason = "game event fee list index is out of bounds"; } return reason == null; case 5: if ((message.CashierFlags & -4) != 0) { reason = "cashier flags contain unknown bits"; } return reason == null; case 6: if (message.TableNumber < 0 || message.TableNumber > 255) { reason = "table number is outside the wire range"; } return reason == null; default: reason = "unknown settings operation"; return false; } } private void ApplyCashier(InteractableCashierCounter counter, byte flags) { if ((Object)(object)counter == (Object)null) { return; } bool flag = (flags & 1) != 0; bool flag2 = (flags & 2) != 0; _applyingRemote = true; try { if (counter.CanCheckout() != flag) { counter.SetCanCheckout(flag); } if (counter.CanTradeCard() != flag2) { counter.SetCanTradeCard(flag2); } } finally { _applyingRemote = false; } } private void ApplyTableNumber(InteractablePlayTable table, int number) { if ((Object)(object)table == (Object)null) { return; } _applyingRemote = true; try { if (table.GetTournamentPlayTableNumber() != number) { table.SetTournamentPlayTableNumber(Mathf.Max(0, number)); } } finally { _applyingRemote = false; } } private bool IsFullyJoinedSender(MessageContext context) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 if (!_shutdown && _context.InGame() && context?.Connection != null) { return (int)context.Connection.State == 3; } return false; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private void BroadcastMutation(byte kind, int index, Guid predictionId = default(Guid)) { if (!_shutdown && _context.InGame()) { _context.Broadcast((INetMessage)(object)BuildMutation(kind, index, predictionId)); } } private static SettingsStateMessage BuildFullState() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected I4, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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) SettingsStateMessage settingsStateMessage = new SettingsStateMessage { GameEventFormat = (int)CPlayerData.m_GameEventFormat, PendingGameEventFormat = (int)CPlayerData.m_PendingGameEventFormat, GameEventExpansion = CPlayerData.m_GameEventExpansionType, PendingGameEventExpansion = CPlayerData.m_PendingGameEventExpansionType, GameEventPriceCount = Math.Min(CPlayerData.m_SetGameEventPriceList?.Count ?? 0, 255), CashierCount = Math.Min((SettingsInterop.FindShelfManager()?.m_CashierCounterList?.Count).GetValueOrDefault(), 255), TableCount = Math.Min((SettingsInterop.FindShelfManager()?.m_PlayTableList?.Count).GetValueOrDefault(), 255) }; List setGameEventPriceList = CPlayerData.m_SetGameEventPriceList; int num = 0; while (setGameEventPriceList != null && num < Mathf.Min(setGameEventPriceList.Count, 255)) { settingsStateMessage.GameEventPrices.Add(setGameEventPriceList[num]); num++; } CopyCashiers(settingsStateMessage.CashierFlags, settingsStateMessage.CashierKeys); CopyTables(settingsStateMessage.TableNumbers, settingsStateMessage.TableKeys); return settingsStateMessage; } private static SettingsStateMessage BuildMutation(int index, int target, Guid predictionId = default(Guid)) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected I4, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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) SettingsStateMessage settingsStateMessage = new SettingsStateMessage { Full = false, PredictionId = predictionId, Index = index }; switch (index) { case 4: settingsStateMessage.GameEventFormat = (int)CPlayerData.m_GameEventFormat; settingsStateMessage.PendingGameEventFormat = (int)CPlayerData.m_PendingGameEventFormat; settingsStateMessage.GameEventExpansion = CPlayerData.m_GameEventExpansionType; settingsStateMessage.PendingGameEventExpansion = CPlayerData.m_PendingGameEventExpansionType; break; case 5: { settingsStateMessage.ItemIndex = target; List setGameEventPriceList = CPlayerData.m_SetGameEventPriceList; settingsStateMessage.GameEventPriceCount = Math.Min(setGameEventPriceList?.Count ?? 0, 255); if (setGameEventPriceList != null && target >= 0 && target < setGameEventPriceList.Count) { settingsStateMessage.GameEventPrices.Add(setGameEventPriceList[target]); } break; } case 6: { settingsStateMessage.CashierKey = target; settingsStateMessage.CashierCount = Math.Min((SettingsInterop.FindShelfManager()?.m_CashierCounterList?.Count).GetValueOrDefault(), 255); InteractableCashierCounter val2 = SettingsInterop.ResolveCashier(target); if ((Object)(object)val2 != (Object)null) { settingsStateMessage.CashierFlags.Add((byte)((val2.CanCheckout() ? 1u : 0u) | (uint)(val2.CanTradeCard() ? 2 : 0))); } break; } case 7: { settingsStateMessage.TableKey = target; settingsStateMessage.TableCount = Math.Min((SettingsInterop.FindShelfManager()?.m_PlayTableList?.Count).GetValueOrDefault(), 255); InteractablePlayTable val = SettingsInterop.ResolveTable(target); if ((Object)(object)val != (Object)null) { settingsStateMessage.TableNumbers.Add((byte)Mathf.Clamp(val.GetTournamentPlayTableNumber(), 0, 255)); } break; } } if (index == 5 && target >= 0 && target >= settingsStateMessage.GameEventPriceCount) { settingsStateMessage.Tombstone = true; } return settingsStateMessage; } private static void CopyCashiers(List destination, List keys) { List list = SettingsInterop.FindShelfManager()?.m_CashierCounterList; int num = 0; while (list != null && num < Mathf.Min(list.Count, 255)) { byte item = 3; if ((Object)(object)list[num] != (Object)null) { item = (byte)((list[num].CanCheckout() ? 1u : 0u) | (uint)(list[num].CanTradeCard() ? 2 : 0)); } destination.Add(item); keys.Add(SettingsInterop.TryGetCashierKey(list[num], out var key) ? key : 0); num++; } } private static void CopyTables(List destination, List keys) { List list = SettingsInterop.FindShelfManager()?.m_PlayTableList; int num = 0; while (list != null && num < Mathf.Min(list.Count, 255)) { int num2 = ((!((Object)(object)list[num] == (Object)null)) ? list[num].GetTournamentPlayTableNumber() : 0); destination.Add((byte)Mathf.Clamp(num2, 0, 255)); keys.Add(SettingsInterop.TryGetTableKey(list[num], out var key) ? key : 0); num++; } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _applyingRemote = false; _context = null; } } private void OnDestroy() { Shutdown(); } private static void BroadcastGameEvent() { if (!_applyingRemote && !((Object)(object)_active == (Object)null) && !_active._shutdown) { _active.BroadcastMutation(4, -1, Guid.Empty); } } private static void BroadcastGameEventFee(EGameEventFormat format) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected I4, but got Unknown if (!_applyingRemote && !((Object)(object)_active == (Object)null) && !_active._shutdown) { _active.BroadcastMutation(5, (int)format, Guid.Empty); } } private static void BroadcastCashier(InteractableCashierCounter counter) { if (!_applyingRemote && !((Object)(object)_active == (Object)null) && !_active._shutdown) { _active.PublishCashier(counter, "cashier setting mutation"); } } private static void BroadcastTable(InteractablePlayTable table) { if (!_applyingRemote && !((Object)(object)_active == (Object)null) && !_active._shutdown) { _active.PublishTable(table, "table number mutation"); } } private void PublishCashier(InteractableCashierCounter counter, string source, Guid predictionId = default(Guid)) { if ((Object)(object)counter == (Object)null || !SettingsInterop.TryGetCashierKey(counter, out var key)) { CoopPlugin.Log.LogWarning((object)("Settings host: " + source + " could not resolve a stable cashier identity")); } else { BroadcastMutation(6, key, predictionId); } } private void PublishTable(InteractablePlayTable table, string source, Guid predictionId = default(Guid)) { if ((Object)(object)table == (Object)null || !SettingsInterop.TryGetTableKey(table, out var key)) { CoopPlugin.Log.LogWarning((object)("Settings host: " + source + " could not resolve a stable table identity")); } else { BroadcastMutation(7, key, predictionId); } } } internal static class SettingsInterop { internal const int CashierKind = 4; internal const int PlayTableKind = 6; internal static bool IsSceneReady { get { ShelfManager val = FindShelfManager(); if ((Object)(object)val != (Object)null && val.m_FinishLoadingObjectData && val.m_CashierCounterList != null && val.m_PlayTableList != null) { return CPlayerData.m_SetGameEventPriceList != null; } return false; } } internal static ShelfManager FindShelfManager() { return SceneRef.Get(); } internal static bool TryGetCashierKey(InteractableCashierCounter counter, out int key) { return PlacementApi.TryMakeObjectKey(4, (InteractableObject)(object)counter, out key); } internal static bool TryGetTableKey(InteractablePlayTable table, out int key) { return PlacementApi.TryMakeObjectKey(6, (InteractableObject)(object)table, out key); } internal static InteractableCashierCounter ResolveCashier(int key) { Component obj = PlacementApi.ResolveObjectByKey(key); InteractableCashierCounter val = (InteractableCashierCounter)(object)((obj is InteractableCashierCounter) ? obj : null); if (val == null || !TryGetCashierKey(val, out var key2) || key2 != key) { return null; } return val; } internal static InteractablePlayTable ResolveTable(int key) { Component obj = PlacementApi.ResolveObjectByKey(key); InteractablePlayTable val = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if (val == null || !TryGetTableKey(val, out var key2) || key2 != key) { return null; } return val; } } [NetworkMessage] public sealed class SettingsOpMessage : IPredictedMessage, INetMessage { public byte Op; public int Index; public ECardExpansionType Expansion; public float Fee; public int CashierKey; public byte CashierFlags; public int TableKey; public int TableNumber; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class SettingsStateMessage : IPredictedMessage, INetMessage { public bool Full = true; public int Index = -1; public int ItemIndex = -1; public int CashierKey; public int TableKey; public int GameEventFormat; public int PendingGameEventFormat; public ECardExpansionType GameEventExpansion; public ECardExpansionType PendingGameEventExpansion; public int GameEventPriceCount = -1; public int CashierCount = -1; public int TableCount = -1; public bool Tombstone; public List GameEventPrices = new List(); public List CashierFlags = new List(); public List TableNumbers = new List(); public List CashierKeys = new List(); public List TableKeys = new List(); public Guid PredictionId { get; set; } } } namespace CardShopCoop.Modules.SessionInput { [PersistentBehaviour] public sealed class SessionInputProcessBehaviour : SessionInputBehaviour { private void OnEnable() { StartSessionInput("process"); } [OnSessionStopped] private void OnSessionStopped() { DebugToolsRuntime.CloseForSessionStop(); SessionInputRuntime.SessionStopped(); } internal void Shutdown() { StopSessionInput(); } private void OnDestroy() { StopSessionInput(); } } public abstract class SessionInputBehaviour : CoopBehaviour { private Harmony _harmony; private bool _shutdown; protected void StartSessionInput(string role) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!_shutdown && _harmony == null) { SessionInputRuntime.Attach(this); _harmony = new Harmony("dev.meepen.cardshopcoop.session-input." + role); SessionInputPatches.Apply(_harmony); } } protected void StopSessionInput() { if (!_shutdown) { _shutdown = true; SessionInputRuntime.Detach(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; } } } internal static class SessionInputPatches { internal static void Apply(Harmony harmony) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00c0: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown TryPatch(harmony, typeof(CameraMouseInput), "GetHorizontalCameraInput", new HarmonyMethod(typeof(SessionInputPatches), "CameraInputPrefix", (Type[])null)); TryPatch(harmony, typeof(CameraMouseInput), "GetVerticalCameraInput", new HarmonyMethod(typeof(SessionInputPatches), "CameraInputPrefix", (Type[])null)); TryPatch(harmony, typeof(MouseCursorLock), "Update", new HarmonyMethod(typeof(SessionInputPatches), "MouseCursorLockPrefix", (Type[])null)); TryPatch(harmony, typeof(PauseScreen), "OpenScreen", new HarmonyMethod(typeof(SessionInputPatches), "PauseOpenPrefix", (Type[])null), new HarmonyMethod(typeof(SessionInputPatches), "PauseOpenPostfix", (Type[])null)); TryPatch(harmony, typeof(PauseScreen), "CloseScreen", null, new HarmonyMethod(typeof(SessionInputPatches), "PauseClosePostfix", (Type[])null)); TryPatch(harmony, typeof(InteractionPlayerController), "Update", new HarmonyMethod(typeof(SessionInputPatches), "PauseProbePrefix", (Type[])null)); } internal static void PauseProbePrefix(InteractionPlayerController __instance) { if (Input.GetKeyDown((KeyCode)27)) { SessionInputRuntime.LogPauseGate(__instance); } } internal static bool CameraInputPrefix(ref float __result) { if (!SessionInputRuntime.WindowBlocksInput && !SessionInputRuntime.PauseMenuOpen() && !SessionInputRuntime.CheatMenuFreezeActive()) { return true; } __result = 0f; return false; } internal static bool MouseCursorLockPrefix() { if (!SessionInputRuntime.WindowBlocksInput && !SessionInputRuntime.PauseMenuOpen()) { return !SessionInputRuntime.CheatMenuFreezeActive(); } return false; } internal static void PauseOpenPostfix() { SessionInputRuntime.PauseChanged(); } internal static void PauseOpenPrefix() { SessionInputRuntime.PauseOpening(); } internal static void PauseClosePostfix() { SessionInputRuntime.PauseClosed(); } private static void TryPatch(Harmony harmony, Type type, string method, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("SessionInput patch target missing: " + type.Name + "." + method)); } else { harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("SessionInput patch failed for " + type.Name + "." + method + ": " + ex.Message)); } } } internal static class SessionInputRuntime { [Flags] private enum ModalOwner { None = 0, Window = 1, Pause = 2, Cheat = 4 } private const ModalOwner VanillaModalOwners = ModalOwner.Pause | ModalOwner.Cheat; private static SessionInputBehaviour _active; private static bool _pauseMenuOpen; private static PauseScreen _pauseScreen; private static bool _cheatMenuOpen; private static ModalOwner _modalOwners; private static bool _windowModalRequested; private static bool _ownsWindowModal; private static InteractionPlayerController _windowModalController; private static readonly FieldInfo WalkerStopField = AccessTools.Field(typeof(AdvancedWalkerController), "isStopMovement"); private static readonly FieldInfo UiModeField = AccessTools.Field(typeof(InteractionPlayerController), "m_IsInUIMode"); private static readonly FieldInfo CameraLerpField = AccessTools.Field(typeof(InteractionPlayerController), "m_IsLerpingCameraRot"); private static readonly FieldInfo PhoneModeField = AccessTools.Field(typeof(InteractionPlayerController), "m_IsPhoneScreenMode"); private static readonly FieldInfo DecoModeField = AccessTools.Field(typeof(InteractionPlayerController), "m_IsDecoScreenMode"); private static bool _ownsWalkerStop; private static AdvancedWalkerController _latchedWalker; private static bool _walkerFieldWarningLogged; internal static bool WindowBlocksInput { get { if ((Object)(object)_active != (Object)null) { return _windowModalRequested; } return false; } } internal static void Attach(SessionInputBehaviour behaviour) { if ((Object)(object)_active != (Object)null && _active != behaviour) { throw new InvalidOperationException("SessionInput is already active for another session."); } ReleaseWindowModal(); ReleaseWalkerOnShutdown(); _active = behaviour; _pauseMenuOpen = false; _pauseScreen = null; _cheatMenuOpen = false; _modalOwners = ModalOwner.None; _windowModalRequested = false; _ownsWindowModal = false; _windowModalController = null; _ownsWalkerStop = false; _latchedWalker = null; _walkerFieldWarningLogged = false; } internal static void Detach(SessionInputBehaviour behaviour) { if (_active == behaviour) { bool num = PauseMenuOpen() || _cheatMenuOpen; ReleaseWindowModal(); ReleaseWalkerOnShutdown(); Time.timeScale = (num ? 0f : 1f); _active = null; _pauseMenuOpen = false; _pauseScreen = null; _cheatMenuOpen = false; _modalOwners = ModalOwner.None; _windowModalRequested = false; _ownsWindowModal = false; _windowModalController = null; _ownsWalkerStop = false; _latchedWalker = null; _walkerFieldWarningLogged = false; } } internal static void LogPauseGate(InteractionPlayerController controller) { if ((Object)(object)controller == (Object)null) { CoopPlugin.Log.LogInfo((object)"[pause] pause key pressed with no InteractionPlayerController."); return; } bool flag = default(bool); int num; if (UiModeField != null) { object value = UiModeField.GetValue(controller); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; bool flag3 = default(bool); int num2; if (PhoneModeField != null) { object value = PhoneModeField.GetValue(controller); if (value is bool) { flag3 = (bool)value; num2 = 1; } else { num2 = 0; } } else { num2 = 0; } bool flag4 = (byte)((uint)num2 & (flag3 ? 1u : 0u)) != 0; bool flag5 = default(bool); int num3; if (DecoModeField != null) { object value = DecoModeField.GetValue(controller); if (value is bool) { flag5 = (bool)value; num3 = 1; } else { num3 = 0; } } else { num3 = 0; } bool flag6 = (byte)((uint)num3 & (flag5 ? 1u : 0u)) != 0; PauseScreen val = SceneRef.Get(); bool flag7 = (Object)(object)val != (Object)null && (Object)(object)val.m_ScreenGrp != (Object)null && val.m_ScreenGrp.activeSelf; CoopPlugin.Log.LogInfo((object)("[pause] pause key pressed: inUI=" + flag2 + " phone=" + flag4 + " deco=" + flag6 + " pauseOpen=" + flag7 + " windowBlocks=" + WindowBlocksInput + " cheatFreeze=" + CheatMenuFreezeActive() + " role=" + CoopCore.Role.ToString() + ".")); } internal static bool PauseMenuOpen() { if (!_pauseMenuOpen) { return false; } if ((Object)(object)_pauseScreen == (Object)null || (Object)(object)_pauseScreen.m_ScreenGrp == (Object)null || !_pauseScreen.m_ScreenGrp.activeSelf) { _pauseMenuOpen = false; SetOwner(ModalOwner.Pause, owned: false); return false; } return true; } internal static void SetWindowModal(bool open) { if (!((Object)(object)_active == (Object)null)) { _windowModalRequested = open; ReconcileModalOwnership(); } } internal static bool CheatMenuFreezeActive() { if ((Object)(object)_active != (Object)null && HasOwner(ModalOwner.Cheat)) { return CoopCore.Role != CoopRole.None; } return false; } internal static void PrepareVanillaModalOpen() { if (!((Object)(object)_active == (Object)null)) { ReleaseWindowModal(); } } internal static void PauseOpening() { if (!((Object)(object)_active == (Object)null)) { RefreshPauseMenuOpen(); if (!_pauseMenuOpen) { PrepareVanillaModalOpen(); } } } internal static void PauseChanged() { RefreshPauseMenuOpen(); if (CoopCore.Role == CoopRole.None) { RestoreLocalWalkerIfIdle(); return; } Time.timeScale = 1f; ReconcileModalOwnership(); } internal static void PauseClosed() { RefreshPauseMenuOpen(); if (CoopCore.Role != CoopRole.None) { ReconcileModalOwnership(); } else { RestoreLocalWalkerIfIdle(); } } internal static void CheatMenuChanged(bool open) { if (!((Object)(object)_active == (Object)null)) { _cheatMenuOpen = open; SetOwner(ModalOwner.Cheat, open); if (CoopCore.Role == CoopRole.None) { RestoreLocalWalkerIfIdle(); return; } Time.timeScale = 1f; ReconcileModalOwnership(); } } internal static void SessionStopped() { if (!((Object)(object)_active == (Object)null)) { bool num = PauseMenuOpen() || _cheatMenuOpen; _cheatMenuOpen = false; SetOwner(ModalOwner.Cheat, owned: false); _windowModalRequested = false; ReleaseWindowModal(); ReleaseWalkerOnShutdown(); Time.timeScale = (num ? 0f : 1f); } } private static void RefreshPauseMenuOpen() { try { _pauseScreen = SceneRef.Get(); _pauseMenuOpen = (Object)(object)_pauseScreen != (Object)null && (Object)(object)_pauseScreen.m_ScreenGrp != (Object)null && _pauseScreen.m_ScreenGrp.activeSelf; SetOwner(ModalOwner.Pause, _pauseMenuOpen); } catch (Exception e) { Swallow.Log(e, "RefreshPauseMenuOpen", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SessionInput/SessionInputRuntime.cs", 272); _pauseMenuOpen = false; SetOwner(ModalOwner.Pause, owned: false); } } private static void ReconcileModalOwnership() { RefreshPauseMenuOpen(); if (_windowModalRequested && !HasAnyOwner(ModalOwner.Pause | ModalOwner.Cheat)) { AcquireWindowModal(); } else { ReleaseWindowModal(); } if (WindowBlocksInput || HasAnyOwner(ModalOwner.Pause | ModalOwner.Cheat)) { StopLocalWalkerForModal(); } else { RestoreLocalWalkerIfIdle(); } } private static bool HasOwner(ModalOwner owner) { return (_modalOwners & owner) != 0; } private static bool HasAnyOwner(ModalOwner owners) { return (_modalOwners & owners) != 0; } private static void SetOwner(ModalOwner owner, bool owned) { if (owned) { _modalOwners |= owner; } else { _modalOwners &= ~owner; } } private static void AcquireWindowModal() { try { InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { ReleaseOwnedWindowModal(); return; } if (_ownsWindowModal && _windowModalController != val) { ReleaseOwnedWindowModal(); } if (_ownsWindowModal) { if (!val.IsInUIMode()) { val.EnterUIMode(); } } else { if (val.IsInUIMode()) { _windowModalController = null; val.ShowCursor(); return; } val.EnterUIMode(); _windowModalController = val; _ownsWindowModal = true; SetOwner(ModalOwner.Window, owned: true); CoopPlugin.Log.LogInfo((object)"Co-op window opened: entering game UI mode to block gameplay input"); } val.ShowCursor(); } catch (Exception e) { Swallow.Log(e, "AcquireWindowModal", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SessionInput/SessionInputRuntime.cs", 376); } } private static void ReleaseOwnedWindowModal() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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) if (!_ownsWindowModal) { SetOwner(ModalOwner.Window, owned: false); _windowModalController = null; return; } InteractionPlayerController windowModalController = _windowModalController; try { if (!((Object)(object)windowModalController != (Object)null)) { return; } if (UiModeField != null) { bool flag = default(bool); int num; if (CameraLerpField != null) { object value = CameraLerpField.GetValue(windowModalController); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { Quaternion localRotation = ((Component)windowModalController.m_CameraController).transform.localRotation; Vector3 eulerAngles = ((Quaternion)(ref localRotation)).eulerAngles; float num2 = ((eulerAngles.x > 180f) ? (eulerAngles.x - 360f) : eulerAngles.x); float num3 = ((eulerAngles.y > 180f) ? (eulerAngles.y - 360f) : eulerAngles.y); windowModalController.m_CameraController.SetRotationAngles(num2, num3); } windowModalController.HideCursor(); CameraLerpField?.SetValue(windowModalController, false); UiModeField.SetValue(windowModalController, false); windowModalController.ResetMousePress(); } else { CoopPlugin.Log.LogWarning((object)"SessionInput: InteractionPlayerController.m_IsInUIMode not found; using vanilla ExitUIMode"); windowModalController.ExitUIMode(); } CoopPlugin.Log.LogInfo((object)"Co-op window closed: restoring gameplay input"); } catch (Exception e) { Swallow.Log(e, "ReleaseOwnedWindowModal", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SessionInput/SessionInputRuntime.cs", 429); } finally { _ownsWindowModal = false; _windowModalController = null; SetOwner(ModalOwner.Window, owned: false); } } private static void StopLocalWalkerForModal() { try { InteractionPlayerController val = SceneRef.Get(); AdvancedWalkerController val2 = (((Object)(object)val != (Object)null) ? val.m_WalkerCtrl : null); if ((Object)(object)val2 == (Object)null) { ReleaseOwnedWalker(); return; } if ((Object)(object)_latchedWalker != (Object)(object)val2) { ReleaseOwnedWalker(); } if (WalkerStopField == null) { if (!_walkerFieldWarningLogged) { _walkerFieldWarningLogged = true; CoopPlugin.Log.LogWarning((object)"SessionInput: AdvancedWalkerController.isStopMovement not found; movement lock left alone"); } return; } object value = WalkerStopField.GetValue(val2); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0) { val2.SetStopMovement(true); _ownsWalkerStop = true; _latchedWalker = val2; } } catch (Exception e) { Swallow.Log(e, "StopLocalWalkerForModal", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SessionInput/SessionInputRuntime.cs", 481); } } private static void RestoreLocalWalkerIfIdle() { if (!_ownsWalkerStop) { return; } try { InteractionPlayerController val = SceneRef.Get(); AdvancedWalkerController val2 = (((Object)(object)val != (Object)null) ? val.m_WalkerCtrl : null); if ((Object)(object)val2 == (Object)null || (Object)(object)_latchedWalker != (Object)(object)val2) { ReleaseOwnedWalker(); } else if (!WindowBlocksInput && !PauseMenuOpen() && !CheatMenuFreezeActive()) { val2.SetStopMovement(false); _ownsWalkerStop = false; _latchedWalker = null; } } catch (Exception e) { Swallow.Log(e, "RestoreLocalWalkerIfIdle", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SessionInput/SessionInputRuntime.cs", 513); } } private static void ReleaseWalkerOnShutdown() { if (_ownsWalkerStop) { ReleaseOwnedWalker(); } } private static void ReleaseOwnedWalker() { if (!_ownsWalkerStop) { _latchedWalker = null; return; } AdvancedWalkerController latchedWalker = _latchedWalker; try { if ((Object)(object)latchedWalker != (Object)null) { latchedWalker.SetStopMovement(false); } } catch (Exception e) { Swallow.Log(e, "ReleaseOwnedWalker", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SessionInput/SessionInputRuntime.cs", 545); } finally { _ownsWalkerStop = false; _latchedWalker = null; } } private static void ReleaseWindowModal() { ReleaseOwnedWindowModal(); } } } namespace CardShopCoop.Modules.SaveTransfer { public static class SaveTransferApi { public static int CoopSlot => SaveTransferStorage.CoopSlot; public static int HostSnapshotSlot => 6; public static bool ClientReloading => SaveTransferRuntime.ClientReloading; public static bool PreloadHold => SaveTransferRuntime.ClientReloading; public static bool GuestBorrowedWorld => SaveTransferRuntime.GuestBorrowedWorld; public static void SetGuestBorrowedWorld(bool borrowed) { SaveTransferRuntime.SetGuestBorrowedWorld(borrowed); } public static void ClearBorrowedWorldIfSafe(bool inGame) { SaveTransferRuntime.ClearBorrowedWorldIfSafe(inGame); } public static void ConfigureCore(Func role = null, Func generation = null, Func isGenerationCurrent = null, Func enqueueMainThread = null, object transferLock = null, Action fullyJoinedEmitter = null, Action failureReporter = null, Action transferFailure = null) { SaveTransferRuntime.ConfigureCore(role, generation, isGenerationCurrent, enqueueMainThread, transferLock, fullyJoinedEmitter, failureReporter, transferFailure); } public static bool TryAuthorizeAndSend(PeerConnection target, Func sendHandshakeOffer, Action afterOfferQueued = null) { return SaveTransferHostBehaviour.TryAuthorizeAndSend(target, sendHandshakeOffer, afterOfferQueued); } public static bool TryBeginClientTransfer(int saveLength, int bundleLength, bool sidecarsComplete, string sidecarWarning) { return SaveTransferClientBehaviour.TryBeginClientTransfer(saveLength, bundleLength, sidecarsComplete, sidecarWarning); } internal static bool TryConsumeHostTransferAcknowledgement(PeerConnection connection) { return SaveTransferHostBehaviour.TryConsumeTransferAcknowledgement(connection); } internal static bool TryConsumeClientTransferAcknowledgement(PeerConnection connection) { return SaveTransferClientBehaviour.TryConsumeFullyJoinedAcknowledgement(connection); } public static byte[] BuildHostPayload() { return SaveTransferStorage.BuildHostPayload(); } public static void ForceLoadSlot(int slot) { SaveTransferStorage.ForceLoadSlot(slot); } } [PersistentBehaviour] public sealed class SaveTransferProcessBehaviour : SaveTransferBehaviour { private void OnEnable() { StartSaveTransfer("process"); } private void Update() { SaveTransferRuntime.ClearBorrowedWorldIfSafe(base.RuntimeContext != null && base.RuntimeContext.InGame()); } internal void Shutdown() { StopSaveTransfer(); } private void OnDestroy() { StopSaveTransfer(); } } public abstract class SaveTransferBehaviour : CoopBehaviour { private static SaveTransferBehaviour _active; private Harmony _harmony; private bool _shutdown; protected void StartSaveTransfer(string role) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (!_shutdown && _harmony == null) { if ((Object)(object)_active != (Object)null && _active != this) { throw new InvalidOperationException("SaveTransfer is already active for another session."); } _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.save-transfer." + role); SaveTransferPatches.Apply(_harmony); } } protected void StopSaveTransfer() { if (!_shutdown) { _shutdown = true; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } } } } [ClientBehaviour] public sealed class SaveTransferClientBehaviour : CoopBehaviour { internal const int ChunkSize = 131072; private static SaveTransferClientBehaviour _active; private CoopRuntimeContext _context; private MemoryStream _saveBuffer; private MemoryStream _bundleBuffer; private byte[] _pendingSave; private int _saveExpected; private int _bundleExpected; private int _sessionGeneration; private bool _sidecarsComplete; private string _sidecarWarning; private bool _saveDone; private bool _worldRequested; private bool _transferFailed; private bool _worldArrived; private float _reloadStartedAt; private bool _awaitingFullyJoinedAck; private float _disconnectAfterFailureAt; private string _failureReason; private bool _shutdown; public static bool IsReloading => SaveTransferRuntime.ClientReloading; public static bool PreloadHold => SaveTransferRuntime.ClientReloading; private void OnEnable() { if (!_shutdown && _context == null) { if ((Object)(object)_active != (Object)null && _active != this) { throw new InvalidOperationException("A save-transfer client owner is already active."); } _active = this; _context = base.RuntimeContext; _context.Messages.RegisterAttributedHandlers(this); SceneManager.sceneLoaded += OnSceneLoaded; CEventManager.AddListener((EventDelegate)OnWorldLoaded); } } public static bool TryBeginClientTransfer(int saveLength, int bundleLength, bool sidecarsComplete, string sidecarWarning) { SaveTransferClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._shutdown || saveLength <= 0 || saveLength > 33554432 || bundleLength < 0 || bundleLength > 33554432) { return false; } if (active._saveBuffer != null || active._bundleBuffer != null || active._worldRequested) { active.RejectTransfer("duplicate world transfer authorization"); return false; } active._saveExpected = saveLength; active._bundleExpected = bundleLength; active._sidecarsComplete = sidecarsComplete; active._sidecarWarning = BoundWarning(sidecarWarning); active._sessionGeneration = SaveTransferRuntime.SessionGeneration; if (active._sessionGeneration < 0 || !SaveTransferRuntime.IsSessionGeneration(active._sessionGeneration)) { active.RejectTransfer("the session generation is no longer active"); return false; } active._saveBuffer = new MemoryStream(Math.Min(saveLength, 33554432)); active._bundleBuffer = new MemoryStream(Math.Min(bundleLength, 33554432)); active._pendingSave = null; active._saveDone = false; active._worldRequested = false; active._transferFailed = false; active._awaitingFullyJoinedAck = false; active._disconnectAfterFailureAt = 0f; active._failureReason = null; SaveTransferRuntime.SetGuestBorrowedWorld(borrowed: true); string text = "Downloading host shop (" + (saveLength + bundleLength) / 1024 + " KB)..."; if (!sidecarsComplete) { text += " Some mod data was omitted."; CoopPlugin.Log.LogWarning((object)("coop: host mod-data transfer is partial" + (string.IsNullOrEmpty(active._sidecarWarning) ? "" : (": " + active._sidecarWarning)))); } active.SetStatus(text); return true; } private void Update() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (_disconnectAfterFailureAt > 0f && Time.realtimeSinceStartup >= _disconnectAfterFailureAt) { _disconnectAfterFailureAt = 0f; _context?.Disconnect?.Invoke(1, new DisconnectInfo("world transfer failed: " + _failureReason, false, "transfer_failed", true, (ConnectionState)2)); } } private void OnWorldLoaded(CEventPlayer_GameDataFinishLoaded _) { if (!_shutdown && _worldArrived && SaveTransferRuntime.ClientReloading) { SaveTransferRuntime.SetReloading(reloading: false); _worldArrived = false; float num = Time.realtimeSinceStartup - _reloadStartedAt; SetStatus("World loaded - co-op sync resumed."); CoopPlugin.Log.LogInfo((object)("Join world load completed in " + num.ToString("F2") + "s; requesting authoritative baseline")); _awaitingFullyJoinedAck = true; SaveTransferRuntime.EmitFullyJoined(); } } internal static bool TryConsumeFullyJoinedAcknowledgement(PeerConnection connection) { SaveTransferClientBehaviour active = _active; if ((Object)(object)active == (Object)null || connection == null || connection.Id != 1 || !active._awaitingFullyJoinedAck) { return false; } active._awaitingFullyJoinedAck = false; return true; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (SaveTransferRuntime.ClientReloading && !string.Equals(((Scene)(ref scene)).name, "Title", StringComparison.Ordinal)) { _worldArrived = true; _reloadStartedAt = Time.realtimeSinceStartup; } } [MessageHandler(typeof(SaveChunkMessage))] private void HandleSaveChunk(MessageContext _, SaveChunkMessage message) { if (CanReceive() && _saveBuffer != null && !_saveDone && message != null) { byte[] data = message.Data; if (data == null || data.Length == 0 || data.Length > 131072 || message.Offset != _saveBuffer.Length || _saveBuffer.Length + data.Length > _saveExpected) { RejectTransfer("invalid save chunk offset or length"); return; } _saveBuffer.Write(data, 0, data.Length); SetStatus("Downloading host shop... " + _saveBuffer.Length * 100 / _saveExpected + "%"); } } [MessageHandler(typeof(SaveDoneMessage))] private void HandleSaveDone(MessageContext _, SaveDoneMessage message) { if (!CanReceive() || _saveBuffer == null || _saveDone || message == null) { return; } if (message.TotalLength != _saveExpected || _saveBuffer.Length != _saveExpected) { RejectTransfer("save transfer length did not match its authorization"); return; } try { _pendingSave = SaveTransferStorage.GunzipCapped(_saveBuffer.ToArray(), 67108864); SaveTransferStorage.ValidateSavePayload(_pendingSave); _saveDone = true; _saveBuffer.Dispose(); _saveBuffer = null; SetStatus("Shop received - downloading mod data..."); } catch (Exception ex) { RejectTransfer("world download could not be unpacked: " + ex.Message); } } [MessageHandler(typeof(BundleChunkMessage))] private void HandleBundleChunk(MessageContext _, BundleChunkMessage message) { if (!CanReceive() || _bundleBuffer == null || !_saveDone || _worldRequested || message == null) { return; } byte[] data = message.Data; if (data == null || data.Length == 0 || data.Length > 131072 || message.Offset != _bundleBuffer.Length || _bundleBuffer.Length + data.Length > _bundleExpected) { RejectTransfer("invalid mod-data chunk offset or length"); return; } _bundleBuffer.Write(data, 0, data.Length); if (_bundleExpected > 0) { SetStatus("Downloading mod data... " + _bundleBuffer.Length * 100 / _bundleExpected + "%"); } } [MessageHandler(typeof(BundleDoneMessage))] private void HandleBundleDone(MessageContext _, BundleDoneMessage message) { if (!CanReceive() || !_saveDone || _pendingSave == null || _bundleBuffer == null || _worldRequested || message == null) { return; } if (message.TotalLength != _bundleExpected || _bundleBuffer.Length != _bundleExpected) { RejectTransfer("mod-data transfer length did not match its authorization"); return; } byte[] array; try { array = ((_bundleExpected == 0) ? new byte[0] : SaveTransferStorage.GunzipCapped(_bundleBuffer.ToArray(), 67108864)); } catch (Exception ex) { RejectTransfer("mod data could not be unpacked: " + ex.Message); return; } if (!_sidecarsComplete) { CoopPlugin.Log.LogWarning((object)("coop: applying a partial mod-data bundle" + (string.IsNullOrEmpty(_sidecarWarning) ? "" : (": " + _sidecarWarning)))); } _worldRequested = true; _saveDone = false; _saveBuffer?.Dispose(); _bundleBuffer.Dispose(); _saveBuffer = null; _bundleBuffer = null; byte[] save = _pendingSave; _pendingSave = null; int generation = _sessionGeneration; SaveTransferRuntime.SetReloading(reloading: true); _worldArrived = false; _reloadStartedAt = Time.realtimeSinceStartup; SetStatus("World received - loading..."); if (array.Length == 0) { ApplySave(save, generation); return; } try { SaveTransferSidecars.ApplyBundleAsync(array, 6, SaveTransferStorage.CoopSlot, generation, delegate { ApplySave(save, generation); }, delegate(Exception error) { RejectTransfer("could not apply mod data: " + error.Message); }); } catch (Exception ex2) { RejectTransfer("could not start mod-data apply: " + ex2.Message); } } private void ApplySave(byte[] save, int generation) { if (_shutdown || !SaveTransferRuntime.IsSessionGeneration(generation)) { return; } try { SaveTransferStorage.ApplyAndLoadAsync(save, generation, delegate { }, delegate(Exception error) { RejectTransfer("could not apply the received world: " + error.Message); }); } catch (Exception ex) { RejectTransfer("could not start world apply: " + ex.Message); } } private bool CanReceive() { if (!_shutdown && !_transferFailed) { return SaveTransferRuntime.IsSessionGeneration(_sessionGeneration); } return false; } private void RejectTransfer(string reason) { if (!_transferFailed) { _transferFailed = true; _saveBuffer?.Dispose(); _bundleBuffer?.Dispose(); _saveBuffer = null; _bundleBuffer = null; _pendingSave = null; SaveTransferRuntime.SetReloading(reloading: false); SetStatus("World transfer failed: " + reason); SaveTransferRuntime.ReportFailure(reason); _failureReason = reason; _disconnectAfterFailureAt = Time.realtimeSinceStartup + 0.25f; _context.Send(1, (INetMessage)(object)new TransferFailureMessage { Reason = reason }); } } private static string BoundWarning(string warning) { if (string.IsNullOrWhiteSpace(warning)) { return null; } warning = warning.Trim(); if (warning.Length > 512) { return warning.Substring(0, 509) + "..."; } return warning; } private void SetStatus(string status) { _context?.SetStatusLine?.Invoke(status, 8f); } internal static void EmitFullyJoinedFallback() { SaveTransferClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._shutdown) { active._context.Send(1, (INetMessage)(object)new FullyJoinedMessage()); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; SceneManager.sceneLoaded -= OnSceneLoaded; CEventManager.RemoveListener((EventDelegate)OnWorldLoaded); _context?.Messages.UnregisterAttributedHandlers(this); _saveBuffer?.Dispose(); _bundleBuffer?.Dispose(); _saveBuffer = null; _bundleBuffer = null; _pendingSave = null; _sidecarWarning = null; _disconnectAfterFailureAt = 0f; _failureReason = null; _worldArrived = false; _worldRequested = false; _awaitingFullyJoinedAck = false; SaveTransferRuntime.SetReloading(reloading: false); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } public sealed class SaveTransferOffer { public int SaveLength { get; internal set; } public int BundleLength { get; internal set; } public bool SidecarsComplete { get; internal set; } public string SidecarWarning { get; internal set; } internal byte[] SavePayload { get; set; } internal byte[] BundlePayload { get; set; } internal string SidecarRoot { get; set; } internal DateTime SaveStartedUtc { get; set; } } [ServerBehaviour] public sealed class SaveTransferHostBehaviour : CoopBehaviour { private static SaveTransferHostBehaviour _active; private readonly HashSet _sending = new HashSet(); private readonly HashSet _readyForAcknowledgement = new HashSet(); private readonly HashSet _aborted = new HashSet(); private readonly Dictionary _completionDeadlines = new Dictionary(); private readonly object _sendingLock = new object(); private const float CompletionDeadlineSeconds = 120f; private CoopRuntimeContext _context; private bool _shutdown; private void OnEnable() { if (!_shutdown && _context == null) { if ((Object)(object)_active != (Object)null && _active != this) { throw new InvalidOperationException("A save-transfer host owner is already active."); } _active = this; _context = base.RuntimeContext; _context.Messages.RegisterAttributedHandlers(this); } } private void Update() { //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown if (_shutdown) { return; } List list = null; float realtimeSinceStartup = Time.realtimeSinceStartup; lock (_sendingLock) { foreach (KeyValuePair completionDeadline in _completionDeadlines) { if (!(realtimeSinceStartup < completionDeadline.Value)) { if (list == null) { list = new List(); } list.Add(completionDeadline.Key); _aborted.Add(completionDeadline.Key); _sending.Remove(completionDeadline.Key); _readyForAcknowledgement.Remove(completionDeadline.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { _completionDeadlines.Remove(list[i]); } } } if (list != null) { for (int j = 0; j < list.Count; j++) { int arg = list[j]; string text = "world transfer completion timed out for connection " + arg; CoopPlugin.Log.LogWarning((object)("coop: " + text)); _context.Disconnect?.Invoke(arg, new DisconnectInfo(text, false, "transfer_timeout", true, (ConnectionState)2)); } } } public static bool TryAuthorizeAndSend(PeerConnection target, Func sendHandshakeOffer, Action afterOfferQueued = null) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 SaveTransferHostBehaviour owner = _active; if ((Object)(object)owner == (Object)null || owner._shutdown || target == null || (int)target.State != 2 || sendHandshakeOffer == null) { return false; } lock (owner._sendingLock) { if (!owner._sending.Add(target.Id)) { CoopPlugin.Log.LogWarning((object)("coop: duplicate save transfer authorization for connection " + target.Id + " ignored")); return false; } owner._aborted.Remove(target.Id); owner._completionDeadlines[target.Id] = Time.realtimeSinceStartup + 120f; } SaveTransferOffer offer; try { DateTime saveStartedUtc; byte[] savePayload = SaveTransferStorage.BuildHostPayload(target.Id, out saveStartedUtc); offer = new SaveTransferOffer { SavePayload = savePayload, SidecarRoot = Application.persistentDataPath, SaveStartedUtc = saveStartedUtc }; } catch (Exception ex) { lock (owner._sendingLock) { owner._sending.Remove(target.Id); owner._completionDeadlines.Remove(target.Id); } CoopPlugin.Log.LogError((object)("coop: host snapshot failed: " + ex)); SaveTransferRuntime.FailTransfer(target, ex.Message); return false; } int generation = SaveTransferRuntime.SessionGeneration; Thread thread = new Thread((ThreadStart)delegate { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 bool flag = false; try { if (SaveTransferRuntime.IsSessionGeneration(generation) && !owner._shutdown && (int)target.State == 2 && !owner._shutdown && !owner.IsAborted(target.Id) && SaveTransferRuntime.IsSessionGeneration(generation)) { SaveTransferFileSet saveTransferFileSet; try { saveTransferFileSet = SaveTransferSidecars.CollectWrittenFiles(offer.SidecarRoot, offer.SaveStartedUtc); } catch (Exception ex2) { saveTransferFileSet = null; CoopPlugin.Log.LogWarning((object)("Sidecar gather failed (sending base save only): " + ex2)); } byte[] savePayload2 = Msg.Gzip(offer.SavePayload); if (savePayload2.Length == 0 || savePayload2.Length > 33554432) { throw new InvalidDataException("Compressed world transfer exceeds its safety limit."); } byte[] bundlePayload = new byte[0]; if (saveTransferFileSet != null) { byte[] array = SaveTransferSidecars.BuildBundle(saveTransferFileSet); offer.SidecarsComplete = saveTransferFileSet.Complete; offer.SidecarWarning = saveTransferFileSet.Warning; if (array.Length != 0) { bundlePayload = Msg.Gzip(array); if (bundlePayload.Length > 33554432) { bundlePayload = new byte[0]; offer.SidecarsComplete = false; offer.SidecarWarning = BoundWarning("mod-data bundle exceeded the compressed transfer limit and was omitted"); CoopPlugin.Log.LogWarning((object)"Sidecar bundle exceeded the compressed transfer limit; sending the base save with mod data marked incomplete"); } } } else { offer.SidecarsComplete = false; offer.SidecarWarning = BoundWarning("mod-data gather failed"); } offer.SaveLength = savePayload2.Length; offer.BundleLength = bundlePayload.Length; if (!SaveTransferRuntime.TryEnqueueMainThread(delegate { SendTransferOnMainThread(owner, target, generation, offer, savePayload2, bundlePayload, sendHandshakeOffer, afterOfferQueued); })) { throw new InvalidOperationException("the main-thread queue rejected the world transfer"); } flag = true; } } catch (Exception ex3) { CoopPlugin.Log.LogError((object)("coop: world send failed: " + ex3)); if (!owner._shutdown && !owner.IsAborted(target.Id)) { SaveTransferRuntime.FailTransfer(target, ex3.Message); } } finally { lock (owner._sendingLock) { if (!flag) { owner._sending.Remove(target.Id); owner._completionDeadlines.Remove(target.Id); } } } }); thread.IsBackground = true; thread.Name = "CoopWorldSend"; thread.Start(); return true; } private static void SendTransferOnMainThread(SaveTransferHostBehaviour owner, PeerConnection target, int generation, SaveTransferOffer offer, byte[] savePayload, byte[] bundlePayload, Func sendHandshakeOffer, Action afterOfferQueued) { try { SendHandshakeOffer(owner, target, generation, offer, sendHandshakeOffer); afterOfferQueued?.Invoke(); SendChunks(owner, target, generation, savePayload, save: true); SendDirect(owner, target, generation, (INetMessage)(object)new SaveDoneMessage { TotalLength = savePayload.Length }); SendChunks(owner, target, generation, bundlePayload, save: false); SendDirect(owner, target, generation, (INetMessage)(object)new BundleDoneMessage { TotalLength = bundlePayload.Length }); lock (owner._sendingLock) { owner._sending.Remove(target.Id); if (!owner._aborted.Contains(target.Id)) { owner._readyForAcknowledgement.Add(target.Id); } } CoopPlugin.Log.LogInfo((object)("coop: transfer sent to connection " + target.Id + " (save " + savePayload.Length / 1024 + " KB, mod data " + bundlePayload.Length / 1024 + " KB)")); } catch (Exception ex) { bool flag = false; lock (owner._sendingLock) { flag = !owner._aborted.Contains(target.Id); owner._aborted.Add(target.Id); owner._sending.Remove(target.Id); owner._readyForAcknowledgement.Remove(target.Id); owner._completionDeadlines.Remove(target.Id); } if (!owner._shutdown && flag) { SaveTransferRuntime.FailTransfer(target, ex.Message); } throw; } } internal static bool TryConsumeTransferAcknowledgement(PeerConnection connection) { SaveTransferHostBehaviour active = _active; if ((Object)(object)active == (Object)null || connection == null) { return false; } lock (active._sendingLock) { int num; if (!active._sending.Contains(connection.Id) && !active._aborted.Contains(connection.Id)) { num = (active._readyForAcknowledgement.Remove(connection.Id) ? 1 : 0); if (num != 0) { active._completionDeadlines.Remove(connection.Id); } } else { num = 0; } return (byte)num != 0; } } [MessageHandler(typeof(TransferFailureMessage))] private void HandleTransferFailure(MessageContext context, TransferFailureMessage message) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown if (!_shutdown && context?.Connection != null && (int)context.Connection.State == 2) { string text = (string.IsNullOrWhiteSpace(message?.Reason) ? "the client rejected the world transfer" : message.Reason); int id = context.Connection.Id; lock (_sendingLock) { _aborted.Add(id); _sending.Remove(id); _readyForAcknowledgement.Remove(id); _completionDeadlines.Remove(id); } CoopPlugin.Log.LogWarning((object)("coop: client rejected world transfer for connection " + id + ": " + text)); _context.Disconnect?.Invoke(id, new DisconnectInfo("client rejected world transfer: " + text, true, "transfer_failed", false, (ConnectionState)2)); } } private static void SendChunks(SaveTransferHostBehaviour owner, PeerConnection target, int generation, byte[] payload, bool save) { for (int i = 0; i < payload.Length; i += 131072) { int num = Math.Min(131072, payload.Length - i); byte[] array = new byte[num]; Buffer.BlockCopy(payload, i, array, 0, num); if (save) { SendDirect(owner, target, generation, (INetMessage)(object)new SaveChunkMessage { Offset = i, Data = array }); } else { SendDirect(owner, target, generation, (INetMessage)(object)new BundleChunkMessage { Offset = i, Data = array }); } } } private static void SendDirect(SaveTransferHostBehaviour owner, PeerConnection target, int generation, INetMessage message) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (owner._shutdown || owner.IsAborted(target.Id) || (int)target.State != 2 || !SaveTransferRuntime.IsSessionGeneration(generation)) { throw new InvalidOperationException("world transfer session ended before send"); } owner._context.Send(target.Id, message); } private static void SendHandshakeOffer(SaveTransferHostBehaviour owner, PeerConnection target, int generation, SaveTransferOffer offer, Func sendHandshakeOffer) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (owner._shutdown || owner.IsAborted(target.Id) || (int)target.State != 2 || !SaveTransferRuntime.IsSessionGeneration(generation)) { throw new InvalidOperationException("world transfer session ended before Welcome"); } if (!sendHandshakeOffer(offer)) { throw new InvalidOperationException("Welcome admission was rejected"); } } private bool IsAborted(int connectionId) { lock (_sendingLock) { return _aborted.Contains(connectionId); } } private static string BoundWarning(string warning) { if (string.IsNullOrWhiteSpace(warning)) { return null; } warning = warning.Trim(); if (warning.Length > 512) { return warning.Substring(0, 509) + "..."; } return warning; } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo info) { if (connection != null) { lock (_sendingLock) { _sending.Remove(connection.Id); _readyForAcknowledgement.Remove(connection.Id); _aborted.Add(connection.Id); _completionDeadlines.Remove(connection.Id); } } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; lock (_sendingLock) { _sending.Clear(); _readyForAcknowledgement.Clear(); _aborted.Clear(); _completionDeadlines.Clear(); } _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class SaveTransferInterop { private const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Type SaveScreenType = typeof(CGameManager).Assembly.GetType("SaveLoadGameSlotSelectScreen", throwOnError: false); private static readonly MethodInfo _updateSlotMethod = ((SaveScreenType == null) ? null : AccessTools.Method(SaveScreenType, "UpdateSlot", (Type[])null, (Type[])null)); private static readonly FieldInfo _saveSlotPanels = SaveScreenType?.GetField("m_SaveLoadSlotPanelUIList", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static readonly Type _singletonType = ((SaveScreenType == null) ? null : typeof(CSingleton<>).MakeGenericType(SaveScreenType)); private static readonly PropertyInfo _screenIsSet = _singletonType?.GetProperty("IsSet", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static readonly PropertyInfo _screenInstance = _singletonType?.GetProperty("Instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); internal static MethodInfo UpdateSlotMethod => _updateSlotMethod; internal static bool IsVisibleSaveSlot(int slot) { try { if (ScreenIsSet()) { object obj = _screenInstance?.GetValue(null, null); IList list = ((obj == null) ? null : (_saveSlotPanels?.GetValue(obj) as IList)); if (list != null) { int num = Math.Min(list.Count, 4); return slot >= 0 && slot < num; } } } catch (Exception e) { Swallow.Log(e, "IsVisibleSaveSlot", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferInterop.cs", 54); } if (slot >= 0) { return slot < 4; } return false; } private static bool ScreenIsSet() { if (_screenIsSet == null) { return false; } try { object value = _screenIsSet.GetValue(null, null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception e) { Swallow.Log(e, "ScreenIsSet", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferInterop.cs", 77); return false; } } } [NetworkMessage] public sealed class SaveChunkMessage : INetMessage { public int Offset; public byte[] Data = new byte[0]; } [NetworkMessage] public sealed class SaveDoneMessage : INetMessage { public int TotalLength; } [NetworkMessage] public sealed class BundleChunkMessage : INetMessage { public int Offset; public byte[] Data = new byte[0]; } [NetworkMessage] public sealed class BundleDoneMessage : INetMessage { public int TotalLength; } [NetworkMessage] public sealed class TransferFailureMessage : INetMessage { private const int MaxReasonLength = 256; private string _reason; public string Reason { get { return _reason; } set { string text = (string.IsNullOrWhiteSpace(value) ? "world transfer failed" : value.Trim()); _reason = text.Substring(0, Math.Min(256, text.Length)); } } } internal static class SaveTransferPatches { private static int _customerLoggedGeneration = int.MinValue; private static int _workerLoggedGeneration = int.MinValue; internal static void Apply(Harmony harmony) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown TryPatch(harmony, typeof(CGameManager), "SaveGameData", new HarmonyMethod(typeof(SaveTransferPatches), "SaveGuardPrefix", (Type[])null)); TryPatch(harmony, typeof(CustomerManager), "SaveCustomerData", new HarmonyMethod(typeof(SaveTransferPatches), "CustomerSaveGuardPrefix", (Type[])null)); TryPatch(harmony, typeof(WorkerManager), "SaveWorkerData", new HarmonyMethod(typeof(SaveTransferPatches), "WorkerSaveGuardPrefix", (Type[])null)); MethodInfo updateSlotMethod = SaveTransferInterop.UpdateSlotMethod; if (updateSlotMethod == null) { CoopPlugin.Log.LogInfo((object)"SaveTransfer: reflected SaveLoadGameSlotSelectScreen.UpdateSlot is absent; legacy save UI needs no guard"); return; } try { harmony.Patch((MethodBase)updateSlotMethod, new HarmonyMethod(typeof(SaveTransferPatches), "UpdateSlotGuardPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("SaveTransfer reflected UpdateSlot patch failed: " + ex.Message)); } } internal static bool SaveGuardPrefix() { return SaveTransferRuntime.CanWriteSave; } internal static bool CustomerSaveGuardPrefix() { if (SaveTransferRuntime.CanWriteSave) { return true; } int sessionGeneration = SaveTransferRuntime.SessionGeneration; if (Interlocked.Exchange(ref _customerLoggedGeneration, sessionGeneration) != sessionGeneration) { CoopPlugin.Log.LogInfo((object)("[save-guard] skipped CustomerManager.SaveCustomerData on guest (host-derived NPC state kept out of the local save); session=" + sessionGeneration)); } return false; } internal static bool WorkerSaveGuardPrefix() { if (SaveTransferRuntime.CanWriteSave) { return true; } int sessionGeneration = SaveTransferRuntime.SessionGeneration; if (Interlocked.Exchange(ref _workerLoggedGeneration, sessionGeneration) != sessionGeneration) { CoopPlugin.Log.LogInfo((object)("[save-guard] skipped WorkerManager.SaveWorkerData on guest (host-derived NPC state kept out of the local save); session=" + sessionGeneration)); } return false; } internal static bool UpdateSlotGuardPrefix(int slot) { return SaveTransferInterop.IsVisibleSaveSlot(slot); } private static void TryPatch(Harmony harmony, Type type, string method, HarmonyMethod prefix = null) { try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("SaveTransfer patch target missing: " + type.Name + "." + method)); } else { harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("SaveTransfer patch failed for " + type.Name + "." + method + ": " + ex.Message)); } } } public static class SaveTransferRuntime { private static readonly object DefaultTransferLock = new object(); private static readonly object StateLock = new object(); private static Func _role; private static Func _generation; private static Func _generationIsCurrent; private static Func _enqueue; private static object _transferLock; private static Action _fullyJoinedEmitter; private static Action _failureReporter; private static Action _transferFailure; private static bool _borrowedWorld; private static bool _reloading; public static bool ClientReloading { get { lock (StateLock) { return _reloading; } } } public static bool GuestBorrowedWorld { get { lock (StateLock) { return _borrowedWorld; } } } internal static CoopRole CurrentRole { get { if (_role != null) { return _role(); } return CoopRole.None; } } internal static bool CanWriteSave { get { bool guestBorrowedWorld = GuestBorrowedWorld; if (CurrentRole != CoopRole.Client) { return !guestBorrowedWorld; } return false; } } internal static int SessionGeneration { get { if (_generation != null) { return _generation(); } return -1; } } internal static object TransferLock => _transferLock ?? DefaultTransferLock; public static void ConfigureCore(Func role = null, Func generation = null, Func isGenerationCurrent = null, Func enqueueMainThread = null, object transferLock = null, Action fullyJoinedEmitter = null, Action failureReporter = null, Action transferFailure = null) { _role = role; _generation = generation; _generationIsCurrent = isGenerationCurrent; _enqueue = enqueueMainThread; _transferLock = transferLock; _fullyJoinedEmitter = fullyJoinedEmitter; _failureReporter = failureReporter; _transferFailure = transferFailure; } public static void SetGuestBorrowedWorld(bool borrowed) { lock (StateLock) { _borrowedWorld = borrowed; } } public static void ClearBorrowedWorldIfSafe(bool inGame) { if (!inGame) { SetGuestBorrowedWorld(borrowed: false); } } internal static void SetReloading(bool reloading) { lock (StateLock) { _reloading = reloading; } } internal static bool IsSessionGeneration(int generation) { if (_generationIsCurrent != null) { return _generationIsCurrent(generation); } return false; } internal static bool TryEnqueueMainThread(Action action) { if (action == null) { return false; } if (_enqueue != null) { return _enqueue(action); } return false; } internal static void ReportFailure(string reason) { CoopPlugin.Log.LogError((object)("coop: save transfer failed: " + reason)); _failureReporter?.Invoke(reason); } internal static void FailTransfer(PeerConnection connection, string reason) { ReportFailure(reason); _transferFailure?.Invoke(connection, reason); } internal static void EmitFullyJoined() { if (_fullyJoinedEmitter != null) { _fullyJoinedEmitter(); } else { SaveTransferClientBehaviour.EmitFullyJoinedFallback(); } } internal static void ResetForShutdown() { SetReloading(reloading: false); if (CurrentRole != CoopRole.Client) { SetGuestBorrowedWorld(borrowed: false); } } } internal sealed class SaveTransferFile { internal readonly string RelativePath; internal readonly byte[] Data; internal SaveTransferFile(string relativePath, byte[] data) { RelativePath = relativePath; Data = data; } } internal sealed class SaveTransferFileSet { internal List Files = new List(); internal bool Complete = true; internal string Warning; internal long RawBytes; } internal static class SaveTransferSidecars { internal const int MaxFiles = 4096; internal const int MaxFileBytes = 16777216; internal const int MaxAggregateRawBytes = 33554432; private const int MaxWarningLength = 512; private const int MaxWarningDetails = 8; private const int FreshnessSlackSeconds = 2; internal static SaveTransferFileSet CollectWrittenFiles(string root, DateTime sinceUtc) { if (string.IsNullOrEmpty(root)) { throw new ArgumentException("A sidecar root is required.", "root"); } SaveTransferFileSet saveTransferFileSet = new SaveTransferFileSet(); List warningDetails = new List(); string text = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); DateTime thresholdUtc = sinceUtc.AddSeconds(-2.0); string[] directories; try { directories = Directory.GetDirectories(text); } catch (Exception ex) { throw new InvalidDataException("mod-data directories could not be enumerated: " + ex.Message); } foreach (string path in directories) { string fileName = Path.GetFileName(path); if (string.Equals(fileName, "Screenshots", StringComparison.OrdinalIgnoreCase) || string.Equals(fileName, "Unity", StringComparison.OrdinalIgnoreCase)) { continue; } string[] files; try { files = Directory.GetFiles(path, "*", SearchOption.AllDirectories); } catch (Exception ex2) { AddWarning("directory '" + GetRelativePath(text, path) + "' could not be enumerated: " + ex2.Message, warningDetails); saveTransferFileSet.Complete = false; continue; } foreach (string text2 in files) { if (string.Equals(Path.GetFileName(text2), "enum_values.json", StringComparison.OrdinalIgnoreCase) || !IsFresh(text2, thresholdUtc)) { continue; } if (saveTransferFileSet.Files.Count >= 4096) { AddWarning("more than " + 4096 + " files were written; the rest were not considered", warningDetails); saveTransferFileSet.Complete = false; break; } string relativePath = GetRelativePath(text, text2); try { long length = new FileInfo(text2).Length; if (length > 16777216) { SkipFile(relativePath, "is " + length + " bytes; the per-file limit is " + 16777216 + " bytes", warningDetails); saveTransferFileSet.Complete = false; } else if (length < 0 || saveTransferFileSet.RawBytes + length > 33554432) { SkipFile(relativePath, "would exceed the aggregate raw limit of " + 33554432 + " bytes", warningDetails); saveTransferFileSet.Complete = false; } else { byte[] array = ReadStableFile(text2, (int)length); if (array == null) { SkipFile(relativePath, "changed while it was being read", warningDetails); saveTransferFileSet.Complete = false; } else { saveTransferFileSet.Files.Add(new SaveTransferFile(relativePath, array)); saveTransferFileSet.RawBytes += array.Length; } } } catch (Exception ex3) { SkipFile(relativePath, "could not be read: " + ex3.Message, warningDetails); saveTransferFileSet.Complete = false; } } } saveTransferFileSet.Files.Sort((SaveTransferFile a, SaveTransferFile b) => string.CompareOrdinal(a.RelativePath, b.RelativePath)); saveTransferFileSet.Warning = BuildWarning(warningDetails); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogInfo((object)("Sidecar gather: " + saveTransferFileSet.Files.Count + " files written by the save, " + saveTransferFileSet.RawBytes / 1024 + " KB raw" + (saveTransferFileSet.Complete ? "" : " (partial)"))); } return saveTransferFileSet; } internal static byte[] BuildBundle(SaveTransferFileSet set) { if (set == null) { throw new ArgumentNullException("set"); } while (true) { byte[] array = SerializeEntries(set.Files); if (array.Length <= 33554432) { return array; } if (set.Files.Count == 0) { break; } SaveTransferFile saveTransferFile = set.Files[set.Files.Count - 1]; set.Files.RemoveAt(set.Files.Count - 1); set.RawBytes -= saveTransferFile.Data.Length; set.Complete = false; set.Warning = BoundWarning("file '" + saveTransferFile.RelativePath + "' would exceed the encoded sidecar bundle limit" + ((set.Warning == null) ? "" : ("; " + set.Warning))); ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("Sidecar skipped file '" + saveTransferFile.RelativePath + "' (encoded bundle limit)")); } } throw new InvalidDataException("Sidecar bundle metadata exceeds its transfer limit."); } private static bool IsFresh(string path, DateTime thresholdUtc) { try { return File.GetLastWriteTimeUtc(path) >= thresholdUtc; } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("Sidecar timestamp read failed for " + path + ": " + ex.Message)); } return false; } } private static byte[] ReadStableFile(string path, int expectedLength) { byte[] array = new byte[expectedLength]; using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536, FileOptions.SequentialScan); int num; for (int i = 0; i < array.Length; i += num) { num = fileStream.Read(array, i, array.Length - i); if (num <= 0) { return null; } } if (fileStream.Length != expectedLength || fileStream.ReadByte() != -1) { return null; } return array; } private static byte[] SerializeEntries(List entries) { using MemoryStream memoryStream = new MemoryStream(); using NetWriter netWriter = new NetWriter(memoryStream); netWriter.Write(entries.Count); for (int i = 0; i < entries.Count; i++) { SaveTransferFile saveTransferFile = entries[i]; netWriter.Write(saveTransferFile.RelativePath); netWriter.Write(saveTransferFile.Data.Length); netWriter.Write(saveTransferFile.Data); } netWriter.Flush(); return memoryStream.ToArray(); } private static string GetRelativePath(string root, string path) { string text = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string fullPath = Path.GetFullPath(path); if (fullPath.Length == text.Length) { return ""; } return fullPath.Substring(text.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Replace(Path.DirectorySeparatorChar, '/') .Replace(Path.AltDirectorySeparatorChar, '/'); } private static void SkipFile(string relative, string reason, List warningDetails) { string text = "file '" + relative + "' " + reason; ManualLogSource log = CoopPlugin.Log; if (log != null) { log.LogWarning((object)("Sidecar skipped " + text)); } AddWarning(text, warningDetails); } private static void AddWarning(string detail, List warningDetails) { if (warningDetails.Count < 8) { warningDetails.Add(detail); } } private static string BuildWarning(List warningDetails) { if (warningDetails == null || warningDetails.Count == 0) { return null; } return BoundWarning(string.Join("; ", warningDetails)); } private static string BoundWarning(string warning) { if (string.IsNullOrWhiteSpace(warning)) { return null; } warning = warning.Trim(); if (warning.Length > 512) { return warning.Substring(0, 509) + "..."; } return warning; } public static void ApplyBundleAsync(byte[] bundle, int hostSlot, int clientSlot, int sessionGeneration, Action completed, Action failed) { ApplyBundleAsync(bundle, hostSlot, clientSlot, sessionGeneration, Application.persistentDataPath, completed, failed); } internal static void ApplyBundleAsync(byte[] bundle, int hostSlot, int clientSlot, int sessionGeneration, string root, Action completed, Action failed) { if (bundle == null || bundle.Length < 4 || bundle.Length > 33554432) { throw new InvalidDataException("Sidecar bundle is empty or exceeds its limit."); } byte[] copy = new byte[bundle.Length]; Buffer.BlockCopy(bundle, 0, copy, 0, bundle.Length); Thread thread = new Thread((ThreadStart)delegate { try { lock (SaveTransferRuntime.TransferLock) { if (!SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { CoopPlugin.Log.LogInfo((object)"coop: stale sidecar apply discarded before disk write"); return; } ApplyBundle(copy, hostSlot, clientSlot, root); } if (!SaveTransferRuntime.IsSessionGeneration(sessionGeneration) || SaveTransferRuntime.TryEnqueueMainThread(delegate { if (SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { completed?.Invoke(); } })) { return; } throw new InvalidOperationException("The co-op main-thread queue is unavailable."); } catch (Exception ex) { Exception ex2 = ex; Exception error = ex2; CoopPlugin.Log.LogError((object)("coop: sidecar apply worker failed: " + error)); if (SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { SaveTransferRuntime.TryEnqueueMainThread(delegate { failed?.Invoke(error); }); } } }); thread.IsBackground = true; thread.Name = "CoopSidecarApply"; thread.Start(); } private static void ApplyBundle(byte[] bundle, int hostSlot, int clientSlot, string root) { ValidateSlot(hostSlot); ValidateSlot(clientSlot); if (bundle == null || bundle.Length < 4 || bundle.Length > 33554432) { throw new InvalidDataException("Sidecar bundle is empty or exceeds its limit."); } Regex regex = new Regex("(?<=_|Release)" + Regex.Escape(hostSlot.ToString()) + "(?=_|\\.|$)", RegexOptions.CultureInvariant); int num = 0; int num2 = 0; long num3 = 0L; using (NetReader netReader = new NetReader(new MemoryStream(bundle, writable: false))) { int num4 = netReader.ReadInt32(); if (num4 < 0 || num4 > 4096) { throw new InvalidDataException("Sidecar file count is invalid."); } for (int i = 0; i < num4; i++) { string text = netReader.ReadString(); if (text.Length > 1024) { throw new InvalidDataException("Sidecar path is too long."); } int num5 = netReader.ReadInt32(); if (num5 < 0 || num5 > 16777216) { throw new InvalidDataException("Sidecar file length is invalid."); } if (num3 + num5 > 33554432) { throw new InvalidDataException("Sidecar aggregate raw length exceeds its limit."); } byte[] array = netReader.ReadBytes(num5); if (array.Length != num5) { throw new EndOfStreamException("Sidecar file was truncated."); } num3 += num5; if (!TryGetSafePath(root, text, out var _)) { RejectUnsafe(text); num2++; continue; } string text2 = Path.GetDirectoryName(text) ?? ""; string fileName = Path.GetFileName(text); string text3 = regex.Replace(fileName, clientSlot.ToString()); string relative = (string.IsNullOrEmpty(text2) ? text3 : Path.Combine(text2, text3)); if (!TryGetSafePath(root, relative, out var full2)) { RejectUnsafe(text); num2++; continue; } Directory.CreateDirectory(Path.GetDirectoryName(full2)); if (File.Exists(full2) && !File.Exists(full2 + ".coopbak")) { File.Copy(full2, full2 + ".coopbak"); } AtomicWrite(full2, array); num++; } } CoopPlugin.Log.LogInfo((object)("Sidecar bundle applied: " + num + " files (slot " + hostSlot + " -> " + clientSlot + "), " + num2 + " skipped")); } private static void ValidateSlot(int slot) { if (slot == -1) { throw new ArgumentOutOfRangeException("slot", "Save slot -1 is the game's \"no save\" sentinel and cannot be used."); } } private static bool TryGetSafePath(string root, string relative, out string full) { full = null; if (string.IsNullOrEmpty(relative) || Path.IsPathRooted(relative) || relative.IndexOf(':') >= 0) { return false; } string[] array = relative.Split(new char[2] { '/', '\\' }, StringSplitOptions.None); foreach (string text in array) { if (text == ".." || text == "." || text.Length == 0) { return false; } } string text2 = Path.GetFullPath(root); string text3 = text2; char directorySeparatorChar = Path.DirectorySeparatorChar; if (!text3.EndsWith(directorySeparatorChar.ToString(), StringComparison.Ordinal)) { string text4 = text2; directorySeparatorChar = Path.DirectorySeparatorChar; text2 = text4 + directorySeparatorChar; } full = Path.GetFullPath(Path.Combine(text2, relative.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar))); if (full.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { return full.Length > text2.Length; } return false; } private static void RejectUnsafe(string relative) { CoopPlugin.Log.LogWarning((object)("sidecar: rejecting unsafe path '" + relative + "'")); } private static void AtomicWrite(string path, byte[] data) { string text = path + ".cooptmp." + Guid.NewGuid().ToString("N"); try { File.WriteAllBytes(text, data); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } finally { if (File.Exists(text)) { File.Delete(text); } } } } public static class SaveTransferStorage { public const int HostSnapshotSlot = 6; public const int ReservedCoopSlot = -100; public const int MaxTransferBytes = 33554432; public const int MaxSidecarRawBytes = 33554432; public const int MaxExpandedBytes = 67108864; private const int FreshnessSlackSeconds = 1; private static string _startSceneName; public static int CoopSlot { get { int num = CoopPlugin.ClientWorldSlot?.Value ?? (-100); if (num != -1) { return num; } return -100; } } public static string SlotPath(int slot) { ValidateSlot(slot); return Path.Combine(Application.persistentDataPath, "savedGames_Release" + slot + ".json"); } private static void ValidateSlot(int slot) { if (slot == -1) { throw new ArgumentOutOfRangeException("slot", "Save slot -1 is the game's \"no save\" sentinel and cannot be used."); } } public static byte[] BuildHostPayload() { DateTime saveStartedUtc; return BuildHostPayload(out saveStartedUtc); } public static byte[] BuildHostPayload(out DateTime saveStartedUtc) { return BuildHostPayload(0, out saveStartedUtc); } public static byte[] BuildHostPayload(int connectionId, out DateTime saveStartedUtc) { ValidateSlot(6); CGameManager val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("The game manager is not available for a host snapshot."); } string text = SlotPath(6); TryDelete(text); TryDelete(Path.Combine(Application.persistentDataPath, "savedGames_Release" + 6 + ".gd")); DateTime before = (saveStartedUtc = DateTime.UtcNow); int index; int cycle; bool flag = PlatformProbe.TrySampleSaveCounter(out index, out cycle); int currentSaveLoadSlotSelectedIndex = val.m_CurrentSaveLoadSlotSelectedIndex; try { val.SaveGameData(6); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("coop: SaveGameData(" + 6 + ") threw: " + ex.Message)); } finally { val.m_CurrentSaveLoadSlotSelectedIndex = currentSaveLoadSlotSelectedIndex; } WorldHostBehaviour.CaptureTransferManifest(connectionId); if (IsFresh(text, before)) { byte[] array = File.ReadAllBytes(text); ValidateSavePayload(array); CoopPlugin.Log.LogInfo((object)("coop: shipping world from slot file" + DescribeWorldJson(array) + " (" + array.Length / 1024 + " KB)")); return array; } int index2; int cycle2; bool flag2 = PlatformProbe.TrySampleSaveCounter(out index2, out cycle2); bool flag3 = flag && flag2; bool flag4 = flag3 && (index != index2 || cycle != cycle2); if (flag4 || (!flag3 && PlatformProbe.GamePassBuild)) { string identity; byte[] array2 = SerializeInMemorySave(flag4 ? index2 : (-1), out identity); if (array2 != null) { CoopPlugin.Log.LogInfo((object)("coop: shipping in-memory world" + ((identity == null) ? "" : (" - " + identity)) + " (" + array2.Length / 1024 + " KB, " + (flag4 ? "save-completion counter advanced" : "Gamecore backend") + ")")); return array2; } } throw new FileNotFoundException((flag3 && !flag4) ? "Host snapshot failed: SaveGameData did not complete and no fresh slot file exists. Load into the shop fully, then retry." : "Host snapshot failed: neither a fresh slot file nor a readable in-memory world was produced. Load into the shop fully, then retry.", text); } private static bool IsFresh(string path, DateTime before) { try { return File.Exists(path) && File.GetLastWriteTimeUtc(path) >= before.AddSeconds(-1.0); } catch (Exception e) { Swallow.Log(e, "IsFresh", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferStorage.cs", 166); return false; } } private static byte[] SerializeInMemorySave(int expectedSaveIndex, out string identity) { identity = null; try { FieldInfo field = typeof(CSaveLoad).GetField("m_SavedGame", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return null; } object obj = field.GetValue(null); if (obj == null || !MatchesSaveIndex(obj, expectedSaveIndex)) { object obj2 = field.FieldType.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (obj2 != null) { obj = obj2; } } if (obj == null) { return null; } identity = DescribeWorldObject(obj); string text = JsonUtility.ToJson(obj); if (string.IsNullOrEmpty(text) || text.Length < 32 || text == "{}") { return null; } return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(text); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("coop: in-memory save serialization failed: " + ex.Message)); return null; } } private static bool MatchesSaveIndex(object saved, int expected) { if (expected < 0) { return true; } try { FieldInfo field = saved.GetType().GetField("m_SaveIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return field == null || (int)field.GetValue(saved) == expected; } catch (Exception e) { Swallow.Log(e, "MatchesSaveIndex", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferStorage.cs", 231); return true; } } private static string DescribeWorldObject(object world) { try { BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; Type type = world.GetType(); object obj = type.GetField("m_CurrentDay", bindingAttr)?.GetValue(world); string player = type.GetField("m_PlayerName", bindingAttr)?.GetValue(world) as string; return Describe((obj is int value) ? new int?(value) : ((int?)null), player); } catch (Exception e) { Swallow.Log(e, "DescribeWorldObject", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferStorage.cs", 248); return null; } } private static string DescribeWorldJson(byte[] bytes) { try { string text = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetString(bytes); int? day = null; int num = text.IndexOf("\"m_CurrentDay\":", StringComparison.Ordinal); if (num >= 0) { int num2 = num + "\"m_CurrentDay\":".Length; int i; for (i = num2; i < text.Length && (text[i] == '-' || char.IsDigit(text[i])); i++) { } if (int.TryParse(text.Substring(num2, i - num2), out var result)) { day = result; } } string player = null; int num3 = text.IndexOf("\"m_PlayerName\":\"", StringComparison.Ordinal); if (num3 >= 0) { int num4 = num3 + "\"m_PlayerName\":\"".Length; int j; for (j = num4; j < text.Length && (text[j] != '"' || text[j - 1] == '\\'); j++) { } if (j < text.Length) { player = text.Substring(num4, j - num4); } } string text2 = Describe(day, player); return (text2 == null) ? "" : (" - " + text2); } catch (Exception e) { Swallow.Log(e, "DescribeWorldJson", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferStorage.cs", 297); return ""; } } private static string Describe(int? day, string player) { if (!day.HasValue && string.IsNullOrEmpty(player)) { return null; } if (!string.IsNullOrEmpty(player) && player.Length > 64) { player = player.Substring(0, 64) + "..."; } if (!day.HasValue) { return "shop \"" + player + "\""; } if (!string.IsNullOrEmpty(player)) { return "day " + day.Value + ", shop \"" + player + "\""; } return "day " + day.Value; } internal static void ValidateSavePayload(byte[] bytes) { if (bytes == null || bytes.Length < 32) { throw new InvalidDataException("Save payload is empty or too small."); } if (!new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetString(bytes).TrimStart('\ufeff', ' ', '\r', '\n', '\t').StartsWith("{", StringComparison.Ordinal)) { throw new InvalidDataException("Save payload is not a JSON world."); } } public static void ApplyAndLoadAsync(byte[] saveBytes, int sessionGeneration, Action completed, Action failed) { ValidateSavePayload(saveBytes); byte[] copy = new byte[saveBytes.Length]; Buffer.BlockCopy(saveBytes, 0, copy, 0, saveBytes.Length); Thread thread = new Thread((ThreadStart)delegate { try { lock (SaveTransferRuntime.TransferLock) { if (!SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { CoopPlugin.Log.LogInfo((object)"coop: stale save apply discarded before disk write"); return; } string path = SlotPath(CoopSlot); string path2 = Path.ChangeExtension(path, ".gd"); string path3 = Path.Combine(Application.persistentDataPath, "savedGames_ReleaseBackupFile" + CoopSlot + ".json"); TryDelete(path2); TryDelete(path); TryDelete(path3); if (File.Exists(path) || File.Exists(path2) || File.Exists(path3)) { throw new IOException("A previous co-op save could not be cleared."); } File.WriteAllBytes(path, copy); } if (!SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { CoopPlugin.Log.LogInfo((object)"coop: stale save apply discarded before load"); } else if (!SaveTransferRuntime.TryEnqueueMainThread(delegate { if (!SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { return; } try { ApplyAndLoad(copy); completed?.Invoke(); } catch (Exception ex3) { CoopPlugin.Log.LogError((object)("coop: received world main-thread apply failed: " + ex3)); failed?.Invoke(ex3); } })) { throw new InvalidOperationException("The co-op main-thread queue is unavailable."); } } catch (Exception ex) { Exception ex2 = ex; Exception error = ex2; CoopPlugin.Log.LogError((object)("coop: save apply worker failed: " + error)); if (SaveTransferRuntime.IsSessionGeneration(sessionGeneration)) { SaveTransferRuntime.TryEnqueueMainThread(delegate { failed?.Invoke(error); }); } } }); thread.IsBackground = true; thread.Name = "CoopSaveApply"; thread.Start(); } public static void ApplyAndLoad(byte[] saveBytes) { ValidateSavePayload(saveBytes); CGameManager obj = SceneRef.Get(); if ((Object)(object)obj == (Object)null) { throw new InvalidOperationException("The game manager is not available for world load."); } obj.m_ForceNoCloudSaveLoad = true; CoopPlugin.Log.LogInfo((object)("Coop save received (" + saveBytes.Length / 1024 + " KB), loading world...")); SaveEnumRemap.Arm(); ForceLoadSlot(CoopSlot); } internal static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("coop: could not clear " + Path.GetFileName(path) + ": " + ex.Message)); } } public static void ForceLoadSlot(int slot) { ValidateSlot(slot); CGameManager obj = SceneRef.Get(); if ((Object)(object)obj == (Object)null) { throw new InvalidOperationException("The game manager is not available for world load."); } obj.m_CurrentSaveLoadSlotSelectedIndex = slot; typeof(CGameManager).GetField("m_InitLoaded", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(null, false); obj.LoadMainLevelAsync(StartSceneName(), slot); } private static string StartSceneName() { if (_startSceneName != null) { return _startSceneName; } try { _startSceneName = typeof(CGameManager).GetField("k_StartSceneName", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null) as string; } catch (Exception e) { Swallow.Log(e, "StartSceneName", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/SaveTransfer/SaveTransferStorage.cs", 475); } if (string.IsNullOrEmpty(_startSceneName)) { _startSceneName = "Start"; } CoopPlugin.Log.LogInfo((object)("coop: loading co-op world via scene '" + _startSceneName + "'")); return _startSceneName; } internal static byte[] GunzipCapped(byte[] compressed, int cap) { if (compressed == null || compressed.Length == 0) { return new byte[0]; } if (compressed.Length > 33554432 || cap <= 0 || cap > 67108864) { throw new InvalidDataException("Compressed transfer exceeds its safety limit."); } using MemoryStream stream = new MemoryStream(compressed, writable: false); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[65536]; int num; while ((num = gZipStream.Read(array, 0, array.Length)) > 0) { if (memoryStream.Length + num > cap) { throw new InvalidDataException("Compressed transfer exceeds decompression limit."); } memoryStream.Write(array, 0, num); } return memoryStream.ToArray(); } } } namespace CardShopCoop.Modules.Report { internal static class ReportApi { internal static void NotifyCommittedPurchaseMutation() { ReportHostBehaviour.NotifyCommittedPurchaseMutation(); } } [ClientBehaviour] public sealed class ReportClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(EndOfDayReportScreen), "OnPressGoNextButton")] private static class NextButtonPatch { [HarmonyPrefix] private static bool Prefix(EndOfDayReportScreen __instance) { return NextButton(__instance); } } [HarmonyPatch(typeof(EndOfDayReportScreen), "OnPressGoNextDay")] private static class NextDayPatch { [HarmonyPrefix] private static bool Prefix() { return NextDay(); } } [HarmonyPatch(typeof(InteractionPlayerController), "ShowGoNextDayScreen")] private static class ShowGoNextDayPatch { [HarmonyPrefix] private static bool Prefix() { if (!((Object)(object)_active == (Object)null) && !_active._shutdown) { return !EndOfDayReportScreen.IsActive(); } return true; } } [HarmonyPatch(typeof(EndOfDayReportScreen), "CloseScreen")] private static class ReportClosePatch { [HarmonyPostfix] private static void Postfix() { ReportClosed(); } } private static readonly FieldInfo FiIsLerping = ReflectionSurface.RequiredField(typeof(EndOfDayReportScreen), "m_IsLerpingNumber"); private static readonly FieldInfo FiHoldingMouseDown = ReflectionSurface.RequiredField(typeof(EndOfDayReportScreen), "m_IsHoldingMouseDown"); private static readonly FieldInfo FiMouseDownTime = ReflectionSurface.RequiredField(typeof(EndOfDayReportScreen), "m_MouseDownTime"); private static readonly FieldInfo FiPhoneMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsPhoneScreenMode"); private static readonly FieldInfo FiCashMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsCashCounterMode"); private static readonly FieldInfo FiInUiMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsInUIMode"); private static readonly FieldInfo FiViewAlbumMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsViewCardAlbumMode"); private static readonly FieldInfo FiExitingAlbumMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsExitingViewCardAlbumMode"); private static readonly FieldInfo FiScanRestockMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsScanRestockMode"); private static readonly FieldInfo FiCameraPhotoMode = ReflectionSurface.RequiredField(typeof(InteractionPlayerController), "m_IsCameraTakePhotoMode"); private static readonly FieldInfo FiLoadingGrp = ReflectionSurface.RequiredField(typeof(EndOfDayReportScreen), "m_LoadingScreenGrp"); private static readonly FieldInfo FiLoadingCurrentDay = ReflectionSurface.RequiredField(typeof(EndOfDayReportScreen), "m_LoadingCurrentDayText"); private static readonly FieldInfo FiLoadingNextDay = ReflectionSurface.RequiredField(typeof(EndOfDayReportScreen), "m_LoadingNextDayText"); private const int CounterSlice = 0; private const int MoneySlice = 1; private const int ReviewSlice = 2; private const string EndOfDayPendingNotice = "End of Day pending, back out of what you are doing to proceed!"; private static ReportClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private ReportStateMessage _pendingState; private EndOfDayReportScreen _screen; private InteractionPlayerController _ipc; private GameReportDataCollect _clientOpenReport; private int _reviewSeq = -1; private bool _haveOpenReport; private bool _pendingOpen; private bool _shutdown; private bool _nextDayLocalReady; private ReportNextDayWaitMessage _nextDayWait; private Coroutine _rolloverCoroutine; private bool PendingOpen { get { return _pendingOpen; } set { _pendingOpen = value; RefreshPendingNotice(); } } private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.report.client"); Patch(typeof(NextButtonPatch)); Patch(typeof(NextDayPatch)); Patch(typeof(ShowGoNextDayPatch)); Patch(typeof(ReportClosePatch)); PatchScreenReadiness(); CEventManager.AddListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded += OnSceneLoaded; } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Report client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } private void PatchScreenReadiness() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(EndOfDayReportScreen), "Awake", (Type[])null, (Type[])null); if (methodInfo != null) { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ReportClientBehaviour), "ScreenReadyPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void ScreenReadyPostfix(EndOfDayReportScreen __instance) { if (!((Object)(object)_active == (Object)null) && !_active._shutdown) { _active._screen = __instance; _active.TryApplyPendingState(); } } private void Update() { if (!_shutdown && _pendingOpen) { if (_context == null || !_context.InGame()) { RefreshPendingNotice(); } else { PendingOpen = !TryOpenReportScreen(); } } } private void RefreshPendingNotice() { int num; object text; if (!_shutdown && _pendingOpen && _context != null) { num = (_context.InGame() ? 1 : 0); if (num != 0) { text = "End of Day pending, back out of what you are doing to proceed!"; goto IL_003a; } } else { num = 0; } text = ""; goto IL_003a; IL_003a: HudApi.SetEndOfDayPending((byte)num != 0, (string)text); } [MessageHandler(typeof(ReportStateMessage))] private void HandleState(MessageContext context, ReportStateMessage message) { _pendingState = message; TryApplyPendingState(); } [MessageHandler(typeof(ReportDeltaMessage))] private void HandleDelta(MessageContext context, ReportDeltaMessage message) { if (!_shutdown) { PredictionApi.AckOrApply(message.PredictionId, delegate { ApplyState(ToState(message)); }); } } private static ReportStateMessage ToState(ReportDeltaMessage message) { return new ReportStateMessage { Full = false, Index = message.Kind, OpenScreen = message.OpenScreen, CloseScreen = message.CloseScreen, CustomerVisited = message.CustomerVisited, CheckoutCount = message.CheckoutCount, CustomerDisatisfied = message.CustomerDisatisfied, CustomerBoughtItem = message.CustomerBoughtItem, CustomerBoughtCard = message.CustomerBoughtCard, CustomerPlayed = message.CustomerPlayed, StoreExpGained = message.StoreExpGained, StoreLevelGained = message.StoreLevelGained, ItemAmountSold = message.ItemAmountSold, CardAmountSold = message.CardAmountSold, TotalPlayTableTime = message.TotalPlayTableTime, TotalItemEarning = message.TotalItemEarning, TotalCardEarning = message.TotalCardEarning, TotalPlayTableEarning = message.TotalPlayTableEarning, SupplyCost = message.SupplyCost, UpgradeCost = message.UpgradeCost, EmployeeCost = message.EmployeeCost, RentCost = message.RentCost, BillCost = message.BillCost, CardPackOpened = message.CardPackOpened, SmellyCustomerCleaned = message.SmellyCustomerCleaned, ManualCheckoutCount = message.ManualCheckoutCount, GemMintCardObtained = message.GemMintCardObtained, ReviewCount = message.ReviewCount, ReviewScoreAverage = message.ReviewScoreAverage, Reviews = message.Reviews }; } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (connection != null && connection.Id == 1) { _pendingState = null; _clientOpenReport = default(GameReportDataCollect); _reviewSeq = -1; _haveOpenReport = false; PendingOpen = false; ClearNextDayWait(); } } private void ApplyState(ReportStateMessage message) { ApplyStateInner(message); } private void ApplyStateInner(ReportStateMessage message) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) bool full = message.Full; int index = message.Index; if (!full && (index < 0 || index > 2)) { throw new ArgumentOutOfRangeException("Index", message.Index, "Unknown authoritative report discriminator."); } bool flag = full || index == 0; bool flag2 = full || index == 1; bool flag3 = full || index == 2; bool flag4 = flag && message.OpenScreen; GameReportDataCollect gameReportDataCollect = CPlayerData.m_GameReportDataCollect; if (message.CloseScreen) { CloseClientReport(); PlayNextDayRollover(); return; } if (flag) { gameReportDataCollect.customerVisited = message.CustomerVisited; gameReportDataCollect.checkoutCount = message.CheckoutCount; gameReportDataCollect.customerDisatisfied = message.CustomerDisatisfied; gameReportDataCollect.customerBoughtItem = message.CustomerBoughtItem; gameReportDataCollect.customerBoughtCard = message.CustomerBoughtCard; gameReportDataCollect.customerPlayed = message.CustomerPlayed; gameReportDataCollect.storeExpGained = message.StoreExpGained; gameReportDataCollect.storeLevelGained = message.StoreLevelGained; gameReportDataCollect.itemAmountSold = message.ItemAmountSold; gameReportDataCollect.cardAmountSold = message.CardAmountSold; gameReportDataCollect.cardPackOpened = message.CardPackOpened; gameReportDataCollect.smellyCustomerCleaned = message.SmellyCustomerCleaned; gameReportDataCollect.manualCheckoutCount = message.ManualCheckoutCount; gameReportDataCollect.gemMintCardObtained = message.GemMintCardObtained; } if (flag2) { gameReportDataCollect.totalPlayTableTime = message.TotalPlayTableTime; gameReportDataCollect.totalItemEarning = message.TotalItemEarning; gameReportDataCollect.totalCardEarning = message.TotalCardEarning; gameReportDataCollect.totalPlayTableEarning = message.TotalPlayTableEarning; gameReportDataCollect.supplyCost = message.SupplyCost; gameReportDataCollect.upgradeCost = message.UpgradeCost; gameReportDataCollect.employeeCost = message.EmployeeCost; gameReportDataCollect.rentCost = message.RentCost; gameReportDataCollect.billCost = message.BillCost; } CPlayerData.m_GameReportDataCollect = gameReportDataCollect; if (_haveOpenReport || flag4) { _clientOpenReport = gameReportDataCollect; } if (flag3) { ApplyReviews(message, full); } if (flag4) { _clientOpenReport = CPlayerData.m_GameReportDataCollect; PendingOpen = !TryOpenReportScreen(); } } private void ApplyReviews(ReportStateMessage message, bool full) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown int reviewCount = message.ReviewCount; List reviews = message.Reviews; List customerReviewDataList = CPlayerData.m_CustomerReviewDataList; if (full) { customerReviewDataList.Clear(); _reviewSeq = reviewCount - reviews.Count; } else if (_reviewSeq < 0) { _reviewSeq = CPlayerData.m_CustomerReviewCount; } for (int i = 0; i < reviews.Count; i++) { if (reviewCount - reviews.Count + i + 1 > _reviewSeq) { ReportReviewEntry reportReviewEntry = reviews[i]; customerReviewDataList.Add(new CustomerReviewData { customerReviewType = (ECustomerReviewType)reportReviewEntry.CustomerReviewType, starLevel = reportReviewEntry.StarLevel, textSOGoodBadLevel = reportReviewEntry.TextSOGoodBadLevel, textSOIndex = reportReviewEntry.TextSOIndex, day = reportReviewEntry.Day, hour = reportReviewEntry.Hour, minute = reportReviewEntry.Minute, itemType = reportReviewEntry.ItemType, customerName = reportReviewEntry.CustomerName }); } } _reviewSeq = Math.Max(_reviewSeq, reviewCount); while (customerReviewDataList.Count > 50) { customerReviewDataList.RemoveAt(0); } CPlayerData.m_CustomerReviewCount = reviewCount; CPlayerData.m_CustomerReviewScoreAverage = message.ReviewScoreAverage; } private bool TryOpenReportScreen() { if ((Object)(object)_screen == (Object)null) { _screen = SceneRef.Get(); } if ((Object)(object)_screen == (Object)null) { return false; } if (EndOfDayReportScreen.IsActive()) { _haveOpenReport = true; PendingOpen = false; return true; } if ((Object)(object)_ipc == (Object)null) { _ipc = SceneRef.Get(); } InteractionPlayerController ipc = _ipc; if ((Object)(object)ipc != (Object)null) { if ((bool)FiCashMode.GetValue(ipc)) { ipc.OnExitCashCounterMode(); if ((bool)FiInUiMode.GetValue(ipc)) { ipc.ExitUIMode(); } } RegisterClientBehaviour.ForceExitManned(); if (IsInteractionBlocking(ipc)) { return false; } } if (IsGlobalScreenBlocking()) { return false; } EndOfDayReportScreen.OpenScreen(); _haveOpenReport = true; PendingOpen = false; return true; } private static bool IsInteractionBlocking(InteractionPlayerController playerController) { if (!(bool)FiPhoneMode.GetValue(playerController) && !(bool)FiInUiMode.GetValue(playerController) && !(bool)FiViewAlbumMode.GetValue(playerController) && !(bool)FiExitingAlbumMode.GetValue(playerController) && !(bool)FiScanRestockMode.GetValue(playerController)) { return (bool)FiCameraPhotoMode.GetValue(playerController); } return true; } private static bool IsGlobalScreenBlocking() { PauseScreen val = SceneRef.Get(); if ((Object)(object)val != (Object)null && (Object)(object)val.m_ScreenGrp != (Object)null && val.m_ScreenGrp.activeSelf) { return true; } SettingScreen val2 = SceneRef.Get(); if ((Object)(object)val2 != (Object)null && (Object)(object)val2.m_ScreenGrp != (Object)null && val2.m_ScreenGrp.activeSelf) { return true; } LoadingScreen val3 = SceneRef.Get(); if ((Object)(object)val3 != (Object)null && (Object)(object)val3.m_ScreenGrp != (Object)null) { return val3.m_ScreenGrp.activeSelf; } return false; } private void TryApplyPendingState() { if (!_shutdown && _context.InGame()) { if (_pendingState != null) { ReportStateMessage pendingState = _pendingState; _pendingState = null; ApplyState(pendingState); } if (_pendingOpen) { PendingOpen = !TryOpenReportScreen(); } } } private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _) { TryApplyPendingState(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { StopNextDayRollover(); _screen = null; _ipc = null; TryApplyPendingState(); } public static void CloseClientReport() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) ReportClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._shutdown) { return; } active.PendingOpen = false; if ((Object)(object)active._screen == (Object)null) { active._screen = SceneRef.Get(); } if (!((Object)(object)active._screen == (Object)null) && EndOfDayReportScreen.IsActive()) { SoundManager.SetEnableSound_CoinIncrease(false); if (active._haveOpenReport) { CPlayerData.m_GameReportDataCollect = active._clientOpenReport; } active._haveOpenReport = false; EndOfDayReportScreen.CloseScreen(); FiHoldingMouseDown.SetValue(active._screen, false); FiMouseDownTime.SetValue(active._screen, 0f); } } private static void ReportClosed() { if (!((Object)(object)_active == (Object)null) && !_active._shutdown) { SoundManager.SetEnableSound_CoinIncrease(false); } } private static bool NextButton(EndOfDayReportScreen screen) { if ((Object)(object)_active == (Object)null || _active._shutdown) { return true; } if ((bool)FiIsLerping.GetValue(screen)) { return true; } _active.ReadyForNextDay(); return false; } private static bool NextDay() { if ((Object)(object)_active == (Object)null || _active._shutdown) { return true; } _active.ReadyForNextDay(); return false; } private void ReadyForNextDay() { if (_context != null && _context.InGame()) { if (!_nextDayLocalReady) { _nextDayLocalReady = true; _context.Send(1, (INetMessage)(object)new ReportNextDayReadyMessage()); } ApplyNextDayWaitUi(); } } [MessageHandler(typeof(ReportNextDayWaitMessage))] private void HandleNextDayWait(MessageContext context, ReportNextDayWaitMessage message) { if (!_shutdown) { if (message == null || !message.Active) { ClearNextDayWait(); return; } _nextDayWait = message; ApplyNextDayWaitUi(); } } private void ApplyNextDayWaitUi() { ReportNextDayWaitMessage nextDayWait = _nextDayWait; if (nextDayWait == null || !nextDayWait.Active || !_nextDayLocalReady || nextDayWait.Pending == null || nextDayWait.Pending.Count == 0) { HudApi.SetNextDayWait(active: false, ""); } else { HudApi.SetNextDayWait(active: true, "Waiting for: " + string.Join(", ", nextDayWait.Pending)); } } private void ClearNextDayWait() { _nextDayLocalReady = false; _nextDayWait = null; HudApi.SetNextDayWait(active: false, ""); } private void PlayNextDayRollover() { if (!_shutdown && _rolloverCoroutine == null) { EndOfDayReportScreen val = (((Object)(object)_screen != (Object)null) ? _screen : SceneRef.Get()); if (!((Object)(object)val == (Object)null)) { _screen = val; _rolloverCoroutine = ((MonoBehaviour)this).StartCoroutine(NextDayRollover(val)); } } } private IEnumerator NextDayRollover(EndOfDayReportScreen screen) { object? value = FiLoadingGrp.GetValue(screen); GameObject loading = (GameObject)((value is GameObject) ? value : null); object? value2 = FiLoadingCurrentDay.GetValue(screen); TextMeshProUGUI val = (TextMeshProUGUI)((value2 is TextMeshProUGUI) ? value2 : null); if (val != null) { ((TMP_Text)val).text = LocalizationManager.GetTranslation("Day XXX", true, 0, true, false, (GameObject)null, (string)null, true).Replace("XXX", (CPlayerData.m_CurrentDay + 1).ToString()); } object? value3 = FiLoadingNextDay.GetValue(screen); TextMeshProUGUI val2 = (TextMeshProUGUI)((value3 is TextMeshProUGUI) ? value3 : null); if (val2 != null) { ((TMP_Text)val2).text = LocalizationManager.GetTranslation("Day XXX", true, 0, true, false, (GameObject)null, (string)null, true).Replace("XXX", (CPlayerData.m_CurrentDay + 2).ToString()); } if ((Object)(object)loading != (Object)null) { loading.SetActive(true); } yield return (object)new WaitForSeconds(0.5f); yield return (object)new WaitForSeconds(2.5f); if ((Object)(object)loading != (Object)null) { loading.SetActive(false); } _rolloverCoroutine = null; } private void StopNextDayRollover() { if (_rolloverCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_rolloverCoroutine); _rolloverCoroutine = null; } } internal void Shutdown() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown) { _shutdown = true; StopNextDayRollover(); _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _pendingState = null; _screen = null; _ipc = null; _clientOpenReport = default(GameReportDataCollect); _reviewSeq = -1; _haveOpenReport = false; PendingOpen = false; ClearNextDayWait(); _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class ReportHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(EndOfDayReportScreen), "OpenScreen")] private static class ReportOpenPatch { [HarmonyPostfix] private static void Postfix() { ReportOpened(); } } [HarmonyPatch(typeof(CustomerReviewManager), "AddCustomerReview")] private static class ReviewAddPatch { [HarmonyPrefix] private static void Prefix(out int __state) { __state = CPlayerData.m_CustomerReviewCount; } [HarmonyPostfix] private static void Postfix(int __state) { ReviewAdded(__state); } } [HarmonyPatch] private static class ReportMutationPatch { private static readonly string[][] Methods = new string[29][] { new string[2] { "Customer", "OnCustomerReachInsideShop" }, new string[2] { "Customer", "ExitShop" }, new string[2] { "Customer", "PlayTableGameEnded" }, new string[2] { "CPlayerData", "CreateDefaultData" }, new string[2] { "CPlayerData", "CPlayer_OnAddShopExp" }, new string[2] { "CustomerTradeCardScreen", "OnPressAccept" }, new string[2] { "EndOfDayReportScreen", "CloseScreen" }, new string[2] { "AchievementManager", "OnCardPackOpened" }, new string[2] { "AchievementManager", "OnCustomerFinishPlay" }, new string[2] { "AchievementManager", "OnDuelWin" }, new string[2] { "RentBillScreen", "OnPressPayRentBill" }, new string[2] { "RentBillScreen", "OnPressPayElectricBill" }, new string[2] { "RentBillScreen", "OnPressPaySalaryBill" }, new string[2] { "RentBillScreen", "OnPressPayAllBill" }, new string[2] { "ExpansionShopUIScreen", "EvaluateCartCheckout" }, new string[2] { "ExpansionShopUIScreen", "OnPressUnlockShopB" }, new string[2] { "FurnitureShopUIScreen", "EvaluateCartCheckout" }, new string[2] { "GradedCardSubmitSelectScreen", "OnPressSubmitButton" }, new string[2] { "HireWorkerPanelUI", "OnPressHireButton" }, new string[2] { "InteractableAutoPackOpener", "OnMouseButtonUp" }, new string[2] { "InteractableCashierCounter", "OnPressSpaceBar" }, new string[2] { "PlayTableGame", "EvaluateEndGameGift" }, new string[2] { "RestockItemPanelUI", "OnPressPurchaseButton" }, new string[2] { "RestockItemScreen", "EvaluateCartCheckout" }, new string[2] { "ScannerRestockScreen", "OnPressUnlockButton" }, new string[2] { "ScannerRestockScreen", "EvaluateCartCheckout" }, new string[2] { "ShopBuyDecoUIScreen", "OnPressBuyShopDeco" }, new string[2] { "ShopBuyDecoUIScreen", "OnPressBuyShopDecoItem" }, new string[2] { "WorkerInteractUIScreen", "OnPressGiveBonus" } }; private static IEnumerable TargetMethods() { for (int i = 0; i < Methods.Length; i++) { Type type = AccessTools.TypeByName(Methods[i][0]); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, Methods[i][1], (Type[])null, (Type[])null)); if (methodInfo != null) { yield return methodInfo; } } } [HarmonyPrefix] private static void Prefix(out GameReportDataCollect __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) __state = CPlayerData.m_GameReportDataCollect; } [HarmonyPostfix] private static void Postfix(MethodBase __originalMethod, GameReportDataCollect __state) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_active == (Object)null) { return; } if (string.Equals(__originalMethod?.Name, "CloseScreen", StringComparison.Ordinal)) { _active.PublishClose(); return; } GameReportDataCollect after = CPlayerData.m_GameReportDataCollect; if (CountersChanged(in __state, in after)) { _active.PublishDelta(BuildState(after, 0, openScreen: false), 0); } if (MoneyChanged(in __state, in after)) { _active.PublishDelta(BuildState(after, 1, openScreen: false), 1); } } private static bool CountersChanged(in GameReportDataCollect before, in GameReportDataCollect after) { if (before.customerVisited == after.customerVisited && before.checkoutCount == after.checkoutCount && before.customerDisatisfied == after.customerDisatisfied && before.customerBoughtItem == after.customerBoughtItem && before.customerBoughtCard == after.customerBoughtCard && before.customerPlayed == after.customerPlayed && before.storeExpGained == after.storeExpGained && before.storeLevelGained == after.storeLevelGained && before.itemAmountSold == after.itemAmountSold && before.cardAmountSold == after.cardAmountSold && before.cardPackOpened == after.cardPackOpened && before.smellyCustomerCleaned == after.smellyCustomerCleaned && before.manualCheckoutCount == after.manualCheckoutCount) { return before.gemMintCardObtained != after.gemMintCardObtained; } return true; } private static bool MoneyChanged(in GameReportDataCollect before, in GameReportDataCollect after) { if (before.totalPlayTableTime == after.totalPlayTableTime && before.totalItemEarning == after.totalItemEarning && before.totalCardEarning == after.totalCardEarning && before.totalPlayTableEarning == after.totalPlayTableEarning && before.supplyCost == after.supplyCost && before.upgradeCost == after.upgradeCost && before.employeeCost == after.employeeCost && before.rentCost == after.rentCost) { return before.billCost != after.billCost; } return true; } } [HarmonyPatch(typeof(EndOfDayReportScreen), "OnPressGoNextDay")] private static class NextDayGatePatch { [HarmonyPrefix] private static bool Prefix() { ReportHostBehaviour active = _active; if ((Object)(object)active == (Object)null || active._shutdown || active._advancingNextDay) { return true; } return active.HostPressNextDay(); } } [HarmonyPatch(typeof(InteractionPlayerController), "ShowGoNextDayScreen")] private static class ShowGoNextDayPatch { [HarmonyPrefix] private static bool Prefix() { if (!((Object)(object)_active == (Object)null) && !_active._shutdown) { return !EndOfDayReportScreen.IsActive(); } return true; } } [HarmonyPatch(typeof(EndOfDayReportScreen), "DelayGoNextDay")] private static class NextDayCoroutinePatch { [HarmonyPostfix] private static void Postfix(ref IEnumerator __result) { if (__result != null) { __result = Relay(__result); } } private static IEnumerator Relay(IEnumerator inner) { while (true) { float supplyCost = CPlayerData.m_GameReportDataCollect.supplyCost; if (!inner.MoveNext()) { break; } if (CPlayerData.m_GameReportDataCollect.supplyCost != supplyCost) { _active?.PublishDelta(BuildState(CPlayerData.m_GameReportDataCollect, 1, openScreen: false), 1); } yield return inner.Current; } _active?.OnNextDayCoroutineCompleted(); } } [HarmonyPatch(typeof(Customer), "Update")] private static class TournamentEntryMutationPatch { [HarmonyTranspiler] private static IEnumerable Transpiler(IEnumerable instructions) { FieldInfo field = AccessTools.Field(typeof(GameReportDataCollect), "totalPlayTableEarning"); MethodInfo callback = AccessTools.Method(typeof(ReportHostBehaviour), "TournamentEntryReportMutation", (Type[])null, (Type[])null); bool injected = false; foreach (CodeInstruction instruction in instructions) { yield return instruction; if (!injected && instruction.opcode == OpCodes.Stfld && IsTargetField(instruction.operand, field)) { yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)callback); injected = true; } } if (!injected) { CoopPlugin.Log.LogError((object)"Report host could not find Customer.Update tournament fee mutation boundary on this game build."); } } } private const int ReviewTail = 15; private const int CounterSlice = 0; private const int MoneySlice = 1; private const int ReviewSlice = 2; private static ReportHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private readonly HashSet _nextDayParticipants = new HashSet(); private readonly HashSet _nextDayReadyPeers = new HashSet(); private bool _nextDayHostReady; private bool _nextDayGateActive; private bool _advancingNextDay; private bool _nextDayAdvancing; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.report.host"); Patch(typeof(ReportOpenPatch)); Patch(typeof(ReviewAddPatch)); Patch(typeof(ReportMutationPatch)); Patch(typeof(NextDayCoroutinePatch)); Patch(typeof(NextDayGatePatch)); Patch(typeof(ShowGoNextDayPatch)); Patch(typeof(TournamentEntryMutationPatch)); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Report host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State)) { _nextDayParticipants.Add(connection.Id); if (_nextDayGateActive) { BroadcastNextDayWait(); } if (_context.InGame()) { _context.Send(connection.Id, (INetMessage)(object)BuildFullState()); } } } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo info) { if (_shutdown || connection == null) { return; } bool num = _nextDayParticipants.Remove(connection.Id); _nextDayReadyPeers.Remove(connection.Id); if (num && _nextDayGateActive) { if (AllNextDayReady()) { AdvanceNextDay(); } else { BroadcastNextDayWait(); } } } [MessageHandler(typeof(ReportNextDayReadyMessage))] private void HandleNextDayReady(MessageContext context, ReportNextDayReadyMessage message) { if (_shutdown || _nextDayAdvancing || !_context.InGame() || context?.Connection == null) { return; } int id = context.Connection.Id; if (_nextDayParticipants.Contains(id) && _nextDayReadyPeers.Add(id)) { _nextDayGateActive = true; if (AllNextDayReady()) { AdvanceNextDay(); } else { BroadcastNextDayWait(); } } } private bool HostPressNextDay() { if (_shutdown || _context == null || !_context.InGame()) { return true; } if (_nextDayAdvancing) { return false; } _nextDayHostReady = true; _nextDayGateActive = true; if (AllNextDayReady()) { AdvanceNextDay(); return false; } BroadcastNextDayWait(); return false; } private bool AllNextDayReady() { if (!_nextDayHostReady) { return false; } foreach (int nextDayParticipant in _nextDayParticipants) { if (!_nextDayReadyPeers.Contains(nextDayParticipant)) { return false; } } return true; } private void AdvanceNextDay() { ResetNextDayGate(); EndOfDayReportScreen val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { BroadcastNextDayWait(); return; } _nextDayAdvancing = true; _advancingNextDay = true; try { val.OnPressGoNextDay(); } finally { _advancingNextDay = false; } BroadcastNextDayWait(); } internal void OnNextDayCoroutineCompleted() { _nextDayAdvancing = false; } private void ResetNextDayGate() { _nextDayHostReady = false; _nextDayGateActive = false; _nextDayReadyPeers.Clear(); } private void BroadcastNextDayWait() { if (_shutdown || _context == null || !_context.InGame()) { return; } List list = BuildPendingNextDayNames(); int num; object text; if (_nextDayHostReady) { num = ((list.Count > 0) ? 1 : 0); if (num != 0) { text = "Waiting for: " + string.Join(", ", list); goto IL_005d; } } else { num = 0; } text = ""; goto IL_005d; IL_005d: HudApi.SetNextDayWait((byte)num != 0, (string)text); _context.Broadcast((INetMessage)(object)new ReportNextDayWaitMessage { Active = _nextDayGateActive, Pending = list }); } private List BuildPendingNextDayNames() { List list = new List(); if (!_nextDayHostReady) { list.Add(PresenceApi.LocalPlayerName); } foreach (int nextDayParticipant in _nextDayParticipants) { if (!_nextDayReadyPeers.Contains(nextDayParticipant)) { string text = _context.PeerName?.Invoke(nextDayParticipant); list.Add(string.IsNullOrWhiteSpace(text) ? ("Player " + nextDayParticipant) : text); } } return list; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private static ReportStateMessage BuildFullState() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) ReportStateMessage reportStateMessage = BuildState(CPlayerData.m_GameReportDataCollect, -1, openScreen: false, closeScreen: false, full: true); reportStateMessage.Full = true; reportStateMessage.Index = -1; return reportStateMessage; } private void Publish(ReportStateMessage message) { if (!_shutdown && message != null && _context.InGame()) { _context.Broadcast((INetMessage)(object)message); } } private static ReportStateMessage BuildState(GameReportDataCollect report, int index, bool openScreen, bool closeScreen = false, bool full = false) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected I4, but got Unknown //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) ReportStateMessage reportStateMessage = new ReportStateMessage { Full = full, Index = (full ? (-1) : index), OpenScreen = openScreen, CloseScreen = closeScreen }; bool num = full || index == 0; bool flag = full || index == 1; bool flag2 = full || index == 2; if (num) { reportStateMessage.CustomerVisited = report.customerVisited; reportStateMessage.CheckoutCount = report.checkoutCount; reportStateMessage.CustomerDisatisfied = report.customerDisatisfied; reportStateMessage.CustomerBoughtItem = report.customerBoughtItem; reportStateMessage.CustomerBoughtCard = report.customerBoughtCard; reportStateMessage.CustomerPlayed = report.customerPlayed; reportStateMessage.StoreExpGained = report.storeExpGained; reportStateMessage.StoreLevelGained = report.storeLevelGained; reportStateMessage.ItemAmountSold = report.itemAmountSold; reportStateMessage.CardAmountSold = report.cardAmountSold; reportStateMessage.CardPackOpened = report.cardPackOpened; reportStateMessage.SmellyCustomerCleaned = report.smellyCustomerCleaned; reportStateMessage.ManualCheckoutCount = report.manualCheckoutCount; reportStateMessage.GemMintCardObtained = report.gemMintCardObtained; } if (flag) { reportStateMessage.TotalPlayTableTime = report.totalPlayTableTime; reportStateMessage.TotalItemEarning = report.totalItemEarning; reportStateMessage.TotalCardEarning = report.totalCardEarning; reportStateMessage.TotalPlayTableEarning = report.totalPlayTableEarning; reportStateMessage.SupplyCost = report.supplyCost; reportStateMessage.UpgradeCost = report.upgradeCost; reportStateMessage.EmployeeCost = report.employeeCost; reportStateMessage.RentCost = report.rentCost; reportStateMessage.BillCost = report.billCost; } if (flag2) { reportStateMessage.ReviewCount = CPlayerData.m_CustomerReviewCount; reportStateMessage.ReviewScoreAverage = CPlayerData.m_CustomerReviewScoreAverage; } List customerReviewDataList = CPlayerData.m_CustomerReviewDataList; if (flag2) { int num2 = (full ? 50 : 15); int num3 = Mathf.Min(customerReviewDataList?.Count ?? 0, num2); for (int i = 0; i < num3; i++) { CustomerReviewData val = customerReviewDataList[customerReviewDataList.Count - num3 + i]; reportStateMessage.Reviews.Add(new ReportReviewEntry { CustomerReviewType = (int)val.customerReviewType, StarLevel = (byte)Mathf.Clamp(val.starLevel, 0, 255), TextSOGoodBadLevel = (byte)Mathf.Clamp(val.textSOGoodBadLevel, 0, 255), TextSOIndex = val.textSOIndex, Day = val.day, Hour = (byte)Mathf.Clamp(val.hour, 0, 255), Minute = (byte)Mathf.Clamp(val.minute, 0, 255), ItemType = val.itemType, CustomerName = (val.customerName ?? "") }); } } return reportStateMessage; } private static bool IsHostActive() { if ((Object)(object)_active != (Object)null && !_active._shutdown) { return _active._context.InGame(); } return false; } private static void ReportOpened() { if (!IsHostActive()) { return; } try { if (!EndOfDayReportScreen.IsActive()) { return; } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Report open detection failed: " + ex.Message)); return; } ModuleGuard.Run("report.host:open", delegate { //IL_0005: 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) _active.PublishDelta(BuildState(CPlayerData.m_GameReportDataCollect, 0, openScreen: true), 0); _active.PublishDelta(BuildState(CPlayerData.m_GameReportDataCollect, 1, openScreen: false), 1); _active.PublishDelta(BuildLatestReview(), 2); }); } private static void ReviewAdded(int before) { if (IsHostActive() && CPlayerData.m_CustomerReviewCount != before) { _active.PublishDelta(BuildLatestReview(), 2); } } private void PublishCounterAndMoney() { //IL_0001: 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) PublishDelta(BuildState(CPlayerData.m_GameReportDataCollect, 0, openScreen: false), 0); PublishDelta(BuildState(CPlayerData.m_GameReportDataCollect, 1, openScreen: false), 1); } internal static void NotifyCommittedPurchaseMutation() { if (IsHostActive()) { _active.PublishCounterAndMoney(); } } private static ReportStateMessage BuildLatestReview() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) ReportStateMessage reportStateMessage = BuildState(CPlayerData.m_GameReportDataCollect, 2, openScreen: false); if (reportStateMessage.Reviews.Count > 1) { reportStateMessage.Reviews = new List { reportStateMessage.Reviews[reportStateMessage.Reviews.Count - 1] }; } return reportStateMessage; } private void PublishClose() { PublishDelta(new ReportStateMessage { CloseScreen = true }, 0); } private void PublishDelta(ReportStateMessage source, int kind) { if (!_shutdown && source != null && _context.InGame()) { _context.Broadcast((INetMessage)(object)ToDelta(source, kind)); } } private static ReportDeltaMessage ToDelta(ReportStateMessage source, int kind) { return new ReportDeltaMessage { Kind = (byte)kind, OpenScreen = source.OpenScreen, CloseScreen = source.CloseScreen, CustomerVisited = source.CustomerVisited, CheckoutCount = source.CheckoutCount, CustomerDisatisfied = source.CustomerDisatisfied, CustomerBoughtItem = source.CustomerBoughtItem, CustomerBoughtCard = source.CustomerBoughtCard, CustomerPlayed = source.CustomerPlayed, StoreExpGained = source.StoreExpGained, StoreLevelGained = source.StoreLevelGained, ItemAmountSold = source.ItemAmountSold, CardAmountSold = source.CardAmountSold, TotalPlayTableTime = source.TotalPlayTableTime, TotalItemEarning = source.TotalItemEarning, TotalCardEarning = source.TotalCardEarning, TotalPlayTableEarning = source.TotalPlayTableEarning, SupplyCost = source.SupplyCost, UpgradeCost = source.UpgradeCost, EmployeeCost = source.EmployeeCost, RentCost = source.RentCost, BillCost = source.BillCost, CardPackOpened = source.CardPackOpened, SmellyCustomerCleaned = source.SmellyCustomerCleaned, ManualCheckoutCount = source.ManualCheckoutCount, GemMintCardObtained = source.GemMintCardObtained, ReviewCount = source.ReviewCount, ReviewScoreAverage = source.ReviewScoreAverage, Reviews = source.Reviews }; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _nextDayParticipants.Clear(); _nextDayReadyPeers.Clear(); _nextDayHostReady = false; _nextDayGateActive = false; _advancingNextDay = false; _nextDayAdvancing = false; HudApi.SetNextDayWait(active: false, ""); _context = null; } } private void OnDestroy() { Shutdown(); } private static bool IsTargetField(object operand, FieldInfo target) { FieldInfo fieldInfo = operand as FieldInfo; if (fieldInfo != null && target != null && fieldInfo.DeclaringType == target.DeclaringType) { return string.Equals(fieldInfo.Name, target.Name, StringComparison.Ordinal); } return false; } private static void TournamentEntryReportMutation(Customer customer) { if (IsHostActive() && !((Object)(object)customer == (Object)null) && customer.GetCustomerTournamentData() != null && customer.GetCustomerTournamentData().m_HasRegisteredTournamentStart && CPlayerData.m_TournamentData != null && !(CPlayerData.m_TournamentData.m_TournamentFee <= 0f)) { _active.PublishCounterAndMoney(); } } } [NetworkMessage] public sealed class ReportStateMessage : INetMessage { public bool Full = true; public int Index = -1; public bool OpenScreen; public bool CloseScreen; public int CustomerVisited; public int CheckoutCount; public int CustomerDisatisfied; public int CustomerBoughtItem; public int CustomerBoughtCard; public int CustomerPlayed; public int StoreExpGained; public int StoreLevelGained; public int ItemAmountSold; public int CardAmountSold; public float TotalPlayTableTime; public float TotalItemEarning; public float TotalCardEarning; public float TotalPlayTableEarning; public float SupplyCost; public float UpgradeCost; public float EmployeeCost; public float RentCost; public float BillCost; public int CardPackOpened; public int SmellyCustomerCleaned; public int ManualCheckoutCount; public int GemMintCardObtained; public int ReviewCount; public float ReviewScoreAverage; public List Reviews = new List(); } [NetworkMessage] public sealed class ReportDeltaMessage : IPredictedMessage, INetMessage { public byte Kind; public bool OpenScreen; public bool CloseScreen; public int CustomerVisited; public int CheckoutCount; public int CustomerDisatisfied; public int CustomerBoughtItem; public int CustomerBoughtCard; public int CustomerPlayed; public int StoreExpGained; public int StoreLevelGained; public int ItemAmountSold; public int CardAmountSold; public float TotalPlayTableTime; public float TotalItemEarning; public float TotalCardEarning; public float TotalPlayTableEarning; public float SupplyCost; public float UpgradeCost; public float EmployeeCost; public float RentCost; public float BillCost; public int CardPackOpened; public int SmellyCustomerCleaned; public int ManualCheckoutCount; public int GemMintCardObtained; public int ReviewCount; public float ReviewScoreAverage; public List Reviews = new List(); public Guid PredictionId { get; set; } } public sealed class ReportReviewEntry { public int CustomerReviewType; public byte StarLevel; public byte TextSOGoodBadLevel; public int TextSOIndex; public int Day; public byte Hour; public byte Minute; public EItemType ItemType; public string CustomerName; } [NetworkMessage] public sealed class ReportNextDayReadyMessage : INetMessage { } [NetworkMessage] public sealed class ReportNextDayWaitMessage : INetMessage { public bool Active; public List Pending = new List(); } } namespace CardShopCoop.Modules.Register { [ClientBehaviour] public sealed class RegisterClientBehaviour : CoopBehaviour { private sealed class LocalStation { public Customer Carrier; public InteractableCashierCounter Counter; public int CustomerIndex = -1; public int CustomerGeneration; public uint CounterGeneration; } private sealed class CheckoutUndo { public InteractableCashierCounter Counter; public Customer Customer; public int ItemScannedCount; public float CustomerTotal; public double Total; public double Paid; public double Change; public bool UsingCard; public bool ChangeReady; public bool ChangeStarted; public bool TooMuchChange; public ECashierCounterState State; public bool CashActive; public bool HandingOverCash; public double WalletDelta; public bool WalletApplied; public bool UiMode; public bool CreditCardMode; public readonly List ChangeCounts = new List(); } private sealed class ActionCapture { public InteractableCashierCounter Counter; public Customer Customer; public int Index; public CheckoutUndo Undo; public int Slot; public bool IsCard; public InteractableCustomerCash Cash; public double Value; public InteractableCounterMoneyChange Change; public bool TakeBack; public int GivenBefore; } [HarmonyPatch(typeof(InteractableCashierCounter), "OnDayStarted")] private static class GuestDayStartedBlockPatch { [HarmonyPrefix] private static bool Prefix() { return false; } } [HarmonyPatch(typeof(InteractableCashierCounter), "OnMouseButtonUp")] private static class ManningPatch { [HarmonyPrefix] private static bool Prefix(InteractableCashierCounter __instance, out bool __state) { __state = false; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applyingRemote != 0 || active._applyingPrediction != 0) { return true; } int num = RegisterInterop.Key(__instance); if (num == 0 || !active._joined || !active._context.InGame()) { return true; } if (active.IsRemoteOwned(num)) { CoopPlugin.Log.LogInfo((object)("[register] not manning counter " + num + "; another player owns it.")); return false; } __state = !__instance.IsMannedByPlayer(); return true; } [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance, bool __state) { RegisterClientBehaviour active = _active; if (__state && !((Object)(object)active == (Object)null) && !((Object)(object)__instance == (Object)null) && __instance.IsMannedByPlayer()) { int num = RegisterInterop.Key(__instance); if (num != 0 && active._joined && active._context.InGame()) { active.ObserveClaim(__instance, num); } } } } [HarmonyPatch(typeof(InteractableObject), "OnRaycasted")] private static class OutlinePatch { [HarmonyPrefix] private static bool Prefix(InteractableObject __instance) { RegisterClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active._joined && active._context.InGame()) { InteractableCashierCounter val = (InteractableCashierCounter)(object)((__instance is InteractableCashierCounter) ? __instance : null); if (val != null) { if (active.IsRemoteOwned(RegisterInterop.Key(val))) { RegisterHighlight.Clear(val); return false; } return true; } } return true; } } [HarmonyPatch(typeof(InteractableCashierCounter), "OnPressEsc")] private static class ExitPatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __instance, out bool __state) { __state = false; RegisterClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active._applyingRemote == 0 && active._applyingPrediction == 0) { int num = RegisterInterop.Key(__instance); if (num != 0 && active._claims.Contains(num)) { __state = true; } } } [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance, bool __state) { RegisterClientBehaviour active = _active; if (__state && !((Object)(object)active == (Object)null) && !((Object)(object)__instance == (Object)null) && !__instance.IsMannedByPlayer()) { int num = RegisterInterop.Key(__instance); if (num != 0) { active.ObserveRelease(__instance, num); } } } } [HarmonyPatch(typeof(InteractableScanItem), "OnMouseButtonUp")] private static class ScanItemPatch { [HarmonyPrefix] private static void Prefix(InteractableScanItem __instance, out ActionCapture __state) { __state = null; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applyingRemote != 0 || active._applyingPrediction != 0) { return; } InteractableCashierCounter val = RegisterInterop.FindCounterForScanItem(__instance); int index; LocalStation localStation = active.Station(val, out index); if (localStation != null && localStation.Carrier == val?.m_CurrentCustomer) { int num = active.ItemSlot(__instance, index); if (num >= 0) { __state = new ActionCapture { Counter = val, Customer = val?.m_CurrentCustomer, Index = index, Slot = num, IsCard = false, Undo = active.CaptureUndo(val, val?.m_CurrentCustomer) }; } } } [HarmonyPostfix] private static void Postfix(ActionCapture __state) { if (__state != null) { _active?.ObserveScan(__state); } } } [HarmonyPatch(typeof(InteractableCard3d), "OnMouseButtonUp")] private static class ScanCardPatch { [HarmonyPrefix] private static void Prefix(InteractableCard3d __instance, out ActionCapture __state) { __state = null; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applyingRemote != 0 || active._applyingPrediction != 0) { return; } InteractableCashierCounter val = RegisterInterop.FindCounterForCard(__instance); int index; LocalStation localStation = active.Station(val, out index); if (localStation != null && localStation.Carrier == val?.m_CurrentCustomer) { int num = active.CardSlot(__instance, index); if (num >= 0) { __state = new ActionCapture { Counter = val, Customer = val?.m_CurrentCustomer, Index = index, Slot = num, IsCard = true, Undo = active.CaptureUndo(val, val?.m_CurrentCustomer) }; } } } [HarmonyPostfix] private static void Postfix(ActionCapture __state) { if (__state != null) { _active?.ObserveScan(__state); } } } [HarmonyPatch(typeof(InteractableCustomerCash), "OnMouseButtonUp")] private static class PaymentPatch { [HarmonyPrefix] private static bool Prefix(InteractableCustomerCash __instance, out ActionCapture __state) { __state = null; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || !active._context.InGame() || active._applyingRemote != 0 || active._applyingPrediction != 0) { return true; } object obj = RegisterInterop.Read(__instance, "m_CurrentCustomer"); Customer val = (Customer)((obj is Customer) ? obj : null); InteractableCashierCounter val2 = RegisterInterop.FindCounterForCash(__instance); int index; LocalStation localStation = active.Station(val2, out index); if ((Object)(object)val2 == (Object)null || (Object)(object)val == (Object)null || val2.m_CurrentCustomer != val || localStation == null || localStation.Carrier != val) { LogBlockedCashClick(__instance, val2, val); return false; } __state = new ActionCapture { Counter = val2, Customer = val, Index = index, Cash = __instance, Undo = active.CaptureUndo(val2, val) }; return true; } [HarmonyPostfix] private static void Postfix(ActionCapture __state) { if (__state != null) { _active?.ObservePayment(__state); } } } [HarmonyPatch(typeof(InteractableCashierCounter), "EvaluateCreditCard")] private static class CardPaymentPatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __instance, double value, out ActionCapture __state) { __state = null; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applyingRemote != 0 || active._applyingPrediction != 0) { return; } int index; LocalStation localStation = active.Station(__instance, out index); if (localStation != null) { if (localStation.Carrier != __instance.m_CurrentCustomer) { LogCheckoutMismatch("card payment", __instance); return; } active._evaluatingCreditCard = true; __state = new ActionCapture { Counter = __instance, Customer = __instance.m_CurrentCustomer, Index = index, Value = value, Undo = active.CaptureUndo(__instance, __instance.m_CurrentCustomer) }; EconomyActionScope.Enter(); } } [HarmonyPostfix] private static void Postfix(ActionCapture __state) { RegisterClientBehaviour active = _active; if ((Object)(object)active != (Object)null) { active._evaluatingCreditCard = false; } if (__state != null) { active?.ObserveCardPayment(__state); } } [HarmonyFinalizer] private static void Finalizer(ActionCapture __state) { if (__state != null) { EconomyActionScope.Exit(); } } } [HarmonyPatch(typeof(InteractableCounterMoneyChange), "OnMouseButtonUp")] private static class AddChangePatch { [HarmonyPrefix] private static void Prefix(InteractableCounterMoneyChange __instance, out ActionCapture __state) { CaptureChange(__instance, takeBack: false, out __state); } [HarmonyPostfix] private static void Postfix(InteractableCounterMoneyChange __instance, ActionCapture __state) { if (__state != null && RegisterInterop.GivenAmount(__instance) != __state.GivenBefore) { _active?.ObserveChange(__state); } } } [HarmonyPatch(typeof(InteractableCounterMoneyChange), "OnRightMouseButtonUp")] private static class RemoveChangePatch { [HarmonyPrefix] private static void Prefix(InteractableCounterMoneyChange __instance, out ActionCapture __state) { CaptureChange(__instance, takeBack: true, out __state); } [HarmonyPostfix] private static void Postfix(InteractableCounterMoneyChange __instance, ActionCapture __state) { if (__state != null && RegisterInterop.GivenAmount(__instance) != __state.GivenBefore) { _active?.ObserveChange(__state); } } } [HarmonyPatch(typeof(InteractableCashierCounter), "OnPressSpaceBar")] private static class FinishPatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __instance, out ActionCapture __state) { __state = null; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applyingRemote != 0 || active._applyingPrediction != 0 || active._evaluatingCreditCard) { return; } int index; LocalStation localStation = active.Station(__instance, out index); if (localStation != null) { if (localStation.Carrier != __instance.m_CurrentCustomer) { LogCheckoutMismatch("complete press", __instance); } else if (RegisterInterop.IsChangeReady(__instance)) { __state = new ActionCapture { Counter = __instance, Customer = __instance.m_CurrentCustomer, Index = index, Undo = active.CaptureUndo(__instance, __instance.m_CurrentCustomer) }; EconomyActionScope.Enter(); } } } [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance, ActionCapture __state) { if (__state != null && !RegisterInterop.IsChangeReady(__instance)) { _active?.ObserveComplete(__state); } } [HarmonyFinalizer] private static void Finalizer(ActionCapture __state) { if (__state != null) { EconomyActionScope.Exit(); } } } [HarmonyPatch(typeof(Customer), "OnCashTaken")] private static class CashTakenGuardPatch { [HarmonyPrefix] private static bool Prefix(Customer __instance) { RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || !active._context.InGame()) { return true; } InteractableCashierCounter val = RegisterInterop.CounterFor(__instance); if ((Object)(object)val != (Object)null && val.m_CurrentCustomer == __instance) { return true; } CoopPlugin.Log.LogWarning((object)("[register] blocked OnCashTaken for a customer with no matching counter; customer=" + (((Object)(object)__instance == (Object)null) ? "null" : ((Object)__instance).name) + " counter=" + (((Object)(object)val == (Object)null) ? "null" : RegisterInterop.Key(val).ToString()))); return false; } } [HarmonyPatch(typeof(Customer), "EvaluateFinishScanItem")] private static class FinishScanPatch { [HarmonyPrefix] private static bool Prefix() { RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || !active._context.InGame()) { return true; } return false; } } private const string PredictionScope = "register"; private static RegisterClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private int _applyingRemote; private int _applyingPrediction; private bool _applyingBaseline; private bool _evaluatingCreditCard; private RegisterBaselineMessage _pendingBaseline; private readonly Dictionary _owners = new Dictionary(); private readonly Dictionary _counterGenerations = new Dictionary(); private readonly Dictionary _stations = new Dictionary(); private readonly Dictionary _deferredDeltas = new Dictionary(); private readonly HashSet _claims = new HashSet(); internal static bool IsCarrier(Customer customer) { if ((Object)(object)customer == (Object)null || (Object)(object)_active == (Object)null) { return false; } foreach (LocalStation value in _active._stations.Values) { if (value.Carrier == customer) { return true; } } return false; } internal static void ForceExitManned() { RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null) { return; } foreach (int item in new List(active._claims)) { InteractableCashierCounter val = RegisterInterop.CounterForKey(item); if ((Object)(object)val != (Object)null && val.IsMannedByPlayer()) { ((InteractableObject)val).OnPressEsc(); } } } private void OnEnable() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (_harmony == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); _harmony = new Harmony("dev.meepen.cardshopcoop.register.client"); Patch(typeof(ManningPatch)); Patch(typeof(OutlinePatch)); Patch(typeof(ExitPatch)); Patch(typeof(ScanItemPatch)); Patch(typeof(ScanCardPatch)); Patch(typeof(PaymentPatch)); Patch(typeof(CardPaymentPatch)); Patch(typeof(AddChangePatch)); Patch(typeof(RemoveChangePatch)); Patch(typeof(FinishPatch)); Patch(typeof(FinishScanPatch)); Patch(typeof(CashTakenGuardPatch)); Patch(typeof(GuestDayStartedBlockPatch)); SceneManager.sceneLoaded += OnSceneLoaded; NpcClientBehaviour.CustomerManagerReady += OnReadinessSignal; NpcClientBehaviour.CustomerPoolChanged += OnReadinessSignal; PlacementApi.StructureChanged += OnPlacementStructureChanged; OnReadinessSignal(); } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } [OnFullyJoined] private void Joined(PeerConnection _) { _joined = true; TryApplyPending(); } private bool IsSceneReady() { if (RegisterInterop.Counters != null) { CustomerManager obj = SceneRef.Get(); if (((obj != null) ? obj.GetCustomerList() : null) != null) { return PlacementApi.IsPlacementIdentityReady; } } return false; } private void OnPlacementStructureChanged(int _) { OnReadinessSignal(); } private void OnReadinessSignal() { if (_shutdown || !_context.InGame() || !IsSceneReady()) { return; } TryApplyPending(); foreach (KeyValuePair item in new List>(_deferredDeltas)) { if (ApplyDelta(item.Value)) { CoopPlugin.Log.LogInfo((object)("[register] applied deferred delta kind=" + item.Value.Kind.ToString() + " counter=" + item.Value.CounterKey + " pred=" + item.Value.PredictionId)); _deferredDeltas.Remove(item.Key); } } } private void TryApplyPending() { if (_applyingBaseline) { CoopPlugin.Log.LogDebug((object)"[register] re-entrant baseline apply suppressed"); } else { if (_pendingBaseline == null || !_context.InGame() || !IsSceneReady()) { return; } RegisterBaselineMessage pendingBaseline = _pendingBaseline; _pendingBaseline = null; _applyingBaseline = true; _applyingRemote++; try { _owners.Clear(); _counterGenerations.Clear(); for (int i = 0; i < pendingBaseline.Counters.Count; i++) { ApplyBaseline(pendingBaseline.Counters[i]); } } finally { _applyingRemote--; _applyingBaseline = false; } } } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (_shutdown) { return; } foreach (int item in new List(_stations.Keys)) { ReleaseStation(item); } _stations.Clear(); _owners.Clear(); _counterGenerations.Clear(); _claims.Clear(); ClearDeferredDeltas(); _pendingBaseline = null; OnReadinessSignal(); } [MessageHandler(typeof(RegisterBaselineMessage))] private void HandleBaseline(MessageContext _, RegisterBaselineMessage message) { _pendingBaseline = message; TryApplyPending(); } [MessageHandler(typeof(RegisterDeltaMessage))] private void HandleDelta(MessageContext _, RegisterDeltaMessage message) { if (!ApplyDelta(message)) { CoopPlugin.Log.LogInfo((object)("[register] deferring delta kind=" + message.Kind.ToString() + " counter=" + message.CounterKey + " counterGen=" + message.CounterGeneration + " customer=" + message.CustomerIndex + " customerGen=" + message.CustomerGeneration + " pred=" + message.PredictionId)); string key = DeltaKey(message); if (_deferredDeltas.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _deferredDeltas[key] = message; } } private bool ApplyDelta(RegisterDeltaMessage message) { if (_shutdown || !_context.InGame()) { return false; } if (message.Kind == RegisterDeltaKind.CounterLifecycle) { PredictionApi.AckOrApply(message.PredictionId, delegate { ApplyCounterLifecycle(message); }); return true; } if (!IsSceneReady()) { return false; } if (message.Kind != RegisterDeltaKind.Ownership && !HasCounterGeneration(message.CounterKey, message.CounterGeneration)) { return false; } if (message.Kind == RegisterDeltaKind.CustomerLifecycle && message.HasCustomer && !HasCustomerCapacity(message.CustomerIndex)) { return false; } if (RequiresCustomer(message.Kind) && !HasCustomer(message)) { return false; } bool confirmedOwnPrediction = PredictionApi.IsPending(message.PredictionId); PredictionApi.Confirm(message.PredictionId, delegate { _applyingRemote++; try { ApplyDeltaCore(message, confirmedOwnPrediction); } finally { _applyingRemote--; } }); return true; } private static bool RequiresCustomer(RegisterDeltaKind kind) { if (kind != RegisterDeltaKind.Scan && kind != RegisterDeltaKind.Change) { return kind == RegisterDeltaKind.PaidAmount; } return true; } private bool HasCounterGeneration(int index, uint generation) { if (_counterGenerations.TryGetValue(index, out var value) && value == generation) { return (Object)(object)RegisterInterop.CounterForKey(index) != (Object)null; } return false; } private bool HasCustomer(RegisterDeltaMessage message) { if (_stations.TryGetValue(message.CounterKey, out var value) && (Object)(object)value.Carrier != (Object)null && value.CustomerIndex == message.CustomerIndex) { return value.CustomerGeneration == message.CustomerGeneration; } return false; } private bool HasCustomerCapacity(int index) { CustomerManager obj = SceneRef.Get(); List list = ((obj != null) ? obj.GetCustomerList() : null); if (list != null && index >= 0) { return index < list.Count; } return false; } private void ApplyBaseline(RegisterBaselineCounter baseline) { _owners[baseline.CounterKey] = baseline.Owner; _counterGenerations[baseline.CounterKey] = baseline.CounterGeneration; int counterKey = baseline.CounterKey; if (IsRemoteOwner(baseline.Owner)) { RegisterHighlight.Clear(RegisterInterop.CounterForKey(counterKey)); } if (!baseline.Exists || (Object)(object)RegisterInterop.CounterForKey(counterKey) == (Object)null) { ReleaseStation(counterKey); return; } InteractableCashierCounter counter = RegisterInterop.CounterForKey(counterKey); if (!baseline.HasCustomer) { ReleaseStation(counterKey); ApplyOwner(counterKey, counter, baseline.Owner); return; } LocalStation orBuildStation = GetOrBuildStation(counterKey, baseline.CounterGeneration, baseline.CustomerIndex, baseline.CustomerGeneration, baseline.CharacterName, baseline.Lines, counter); if (orBuildStation != null) { ApplyOwner(counterKey, counter, baseline.Owner); ApplyBaselineScanned(orBuildStation.Carrier, baseline.Lines); ApplyPhase(counter, orBuildStation.Carrier, baseline.State, baseline.UsingCard, baseline.Paid, baseline.Total, baseline.CustomerTotal, baseline.Change, baseline.ChangeReady, baseline.ChangeStarted, baseline.TooMuchChange); ApplyChangeStack(counter, baseline.State, baseline.UsingCard, baseline.ChangeItems); } } private void ApplyDeltaCore(RegisterDeltaMessage message, bool confirmedOwnPrediction) { int counterKey = message.CounterKey; switch (message.Kind) { case RegisterDeltaKind.CounterLifecycle: ApplyCounterLifecycle(message); break; case RegisterDeltaKind.Ownership: ApplyOwnership(counterKey, message.Owner); break; case RegisterDeltaKind.CustomerLifecycle: ApplyCustomerLifecycle(message); break; case RegisterDeltaKind.Scan: ApplyScan(message, confirmedOwnPrediction); break; case RegisterDeltaKind.PhasePayment: ApplyPhaseDelta(message); break; case RegisterDeltaKind.PaidAmount: ApplyPhaseDelta(message); break; case RegisterDeltaKind.Change: ApplyChange(message, confirmedOwnPrediction); break; default: throw new ArgumentOutOfRangeException("Kind", message.Kind, "Unknown register delta."); } } private void ApplyCounterLifecycle(RegisterDeltaMessage message) { if (message.Exists) { _counterGenerations[message.CounterKey] = message.CounterGeneration; return; } _owners[message.CounterKey] = 0; _claims.Remove(message.CounterKey); ReleaseStation(message.CounterKey); _counterGenerations.Remove(message.CounterKey); } private void ApplyCustomerLifecycle(RegisterDeltaMessage message) { int counterKey = message.CounterKey; InteractableCashierCounter counter = RegisterInterop.CounterForKey(counterKey); if (!message.HasCustomer) { ApplyPhase(counter, (_stations.TryGetValue(message.CounterKey, out var value) ? value : null)?.Carrier, message.State, message.UsingCard, message.Paid, message.Total, message.CustomerTotal, message.Change, message.ChangeReady, message.ChangeStarted, message.TooMuchChange); ReleaseStation(counterKey); return; } LocalStation orBuildStation = GetOrBuildStation(counterKey, message.CounterGeneration, message.CustomerIndex, message.CustomerGeneration, message.CharacterName, message.Lines, counter); if (orBuildStation != null) { ApplyPhase(counter, orBuildStation.Carrier, message.State, message.UsingCard, message.Paid, message.Total, message.CustomerTotal, message.Change, message.ChangeReady, message.ChangeStarted, message.TooMuchChange); } } private void ApplyScan(RegisterDeltaMessage message, bool confirmedOwnPrediction) { if (!_stations.TryGetValue(message.CounterKey, out var value) || (Object)(object)value.Carrier == (Object)null) { return; } Customer carrier = value.Carrier; if (message.IsCard) { List cardInBagList = carrier.GetCardInBagList(); if (message.Slot >= 0 && message.Slot < cardInBagList.Count) { InteractableCard3d val = cardInBagList[message.Slot]; if ((Object)(object)val != (Object)null && val.IsNotScanned()) { ((InteractableObject)val).OnMouseButtonUp(); } } } else { List itemInBagList = carrier.GetItemInBagList(); if (message.Slot >= 0 && message.Slot < itemInBagList.Count) { InteractableScanItem val2 = itemInBagList[message.Slot]?.m_InteractableScanItem; if ((Object)(object)val2 != (Object)null && val2.IsNotScanned()) { ((InteractableObject)val2).OnMouseButtonUp(); } } } if (!confirmedOwnPrediction) { ApplyPhase(RegisterInterop.CounterForKey(message.CounterKey), carrier, message.State, message.UsingCard, message.Paid, message.Total, message.CustomerTotal, message.Change, message.ChangeReady, message.ChangeStarted, message.TooMuchChange); } } private void ApplyPhaseDelta(RegisterDeltaMessage message) { ApplyPhase(RegisterInterop.CounterForKey(message.CounterKey), (_stations.TryGetValue(message.CounterKey, out var value) ? value : null)?.Carrier, message.State, message.UsingCard, message.Paid, message.Total, message.CustomerTotal, message.Change, message.ChangeReady, message.ChangeStarted, message.TooMuchChange); } private void ApplyChange(RegisterDeltaMessage message, bool confirmedOwnPrediction) { if (!confirmedOwnPrediction) { RegisterInterop.SetGivenAmount(RegisterInterop.FindChange(RegisterInterop.CounterForKey(message.CounterKey), message.Slot, message.IsCoin), message.ChangeCount); ApplyPhase(RegisterInterop.CounterForKey(message.CounterKey), (_stations.TryGetValue(message.CounterKey, out var value) ? value : null)?.Carrier, message.State, message.UsingCard, message.Paid, message.Total, message.CustomerTotal, message.Change, message.ChangeReady, message.ChangeStarted, message.TooMuchChange); } } private LocalStation GetOrBuildStation(int index, uint counterGeneration, int customerIndex, int customerGeneration, string characterName, List lines, InteractableCashierCounter counter) { //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)counter == (Object)null) { return null; } if (_stations.TryGetValue(index, out var value) && value.CounterGeneration == counterGeneration && value.CustomerIndex == customerIndex && value.CustomerGeneration == customerGeneration && (Object)(object)value.Carrier != (Object)null) { EnsureNpcAttachment(customerIndex, customerGeneration, value.Carrier); return value; } ReleaseStation(index); Customer val = FindCarrier(customerIndex); if ((Object)(object)val == (Object)null) { return null; } RegisterInterop.PrepareCarrier(val, counter); int num = val.GetItemInBagList()?.Count ?? 0; int num2 = val.GetCardInBagList()?.Count ?? 0; if (num > 0 || num2 > 0) { CoopPlugin.Log.LogDebug((object)("[register] cleared stale carrier bag counter=" + index + " items=" + num + " cards=" + num2 + ".")); } RegisterInterop.ReleaseContents(val); if ((Object)(object)val.m_CharacterCustom != (Object)null) { val.m_CharacterCustom.CharacterName = characterName; val.m_CharacterCustom.Initialize(); } if ((Object)(object)counter.m_QueueStartPos != (Object)null) { ((Component)val).transform.position = counter.m_QueueStartPos.position; Vector3 val2 = ((Component)counter).transform.position - ((Component)val).transform.position; val2.y = 0f; ((Component)val).transform.rotation = ((((Vector3)(ref val2)).sqrMagnitude > 0.0001f) ? Quaternion.LookRotation(val2, Vector3.up) : counter.m_QueueStartPos.rotation); } EnsureNpcAttachment(customerIndex, customerGeneration, val); ((Component)val).gameObject.SetActive(true); counter.UpdateCurrentCustomer(val); counter.SetPlsaticBagVisibility(true); counter.UpdateCashierCounterState((ECashierCounterState)1); int num3 = 0; int num4 = 0; int num5 = 0; while (lines != null && num5 < lines.Count) { RegisterLine registerLine = lines[num5]; if (registerLine.IsCard) { RegisterInterop.SpawnCard(counter, val, registerLine.Card, registerLine.Price, num4++); } else { RegisterInterop.SpawnItem(counter, val, registerLine.ItemType, registerLine.Price, num3++); } num5++; } LocalStation localStation = new LocalStation { Carrier = val, Counter = counter, CustomerIndex = customerIndex, CustomerGeneration = customerGeneration, CounterGeneration = counterGeneration }; _stations[index] = localStation; return localStation; } private static Customer FindCarrier(int customerIndex) { CustomerManager obj = SceneRef.Get(); List list = ((obj != null) ? obj.GetCustomerList() : null); if (list == null || customerIndex < 0 || customerIndex >= list.Count) { return null; } return list[customerIndex]; } private static void EnsureNpcAttachment(int index, int generation, Customer carrier) { NpcClientBehaviour.SuppressedCustomer.Add(index); if (!NpcClientBehaviour.IsExistingCustomer(carrier) || !NpcClientBehaviour.TryGetCustomerGeneration(index, out var generation2) || generation2 != generation) { NpcClientBehaviour.AttachExistingCustomer(index, generation, carrier); } } private static void ApplyBaselineScanned(Customer customer, List lines) { List itemInBagList = customer.GetItemInBagList(); List cardInBagList = customer.GetCardInBagList(); int num = 0; int num2 = 0; int num3 = 0; while (lines != null && num3 < lines.Count) { RegisterLine registerLine = lines[num3]; if (registerLine.IsCard) { if (registerLine.Scanned && num2 < cardInBagList.Count && cardInBagList[num2].IsNotScanned()) { ((InteractableObject)cardInBagList[num2]).OnMouseButtonUp(); } num2++; } else { if (registerLine.Scanned && num < itemInBagList.Count && itemInBagList[num].m_InteractableScanItem.IsNotScanned()) { ((InteractableObject)itemInBagList[num].m_InteractableScanItem).OnMouseButtonUp(); } num++; } num3++; } } private unsafe static void ApplyPhase(InteractableCashierCounter counter, Customer customer, byte state, bool usingCard, double paid, double total, double customerTotal, double change, bool changeReady, bool changeStarted, bool tooMuchChange) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Invalid comparison between Unknown and I4 if ((Object)(object)counter == (Object)null) { return; } double num = RegisterInterop.Total(counter); double num2 = (((Object)(object)customer == (Object)null) ? 0.0 : Convert.ToDouble(RegisterInterop.Read(customer, "m_TotalScannedItemCost") ?? ((object)0f))); ECashierCounterState val = (ECashierCounterState)state; if ((Object)(object)customer != (Object)null && (int)val == 3 && (int)counter.m_CashierCounterState != 3) { customer.OnCashTaken(usingCard); } RegisterInterop.Write(counter, "m_IsUsingCard", usingCard); RegisterInterop.Write(counter, "m_CustomerPaidAmount", paid); RegisterInterop.Write(counter, "m_TotalScannedItemCost", total); RegisterInterop.Write(counter, "m_CurrentMoneyChangeValue", change); RegisterInterop.Write(counter, "m_IsChangeReady", changeReady); RegisterInterop.Write(counter, "m_IsStartGivingChange", changeStarted); RegisterInterop.Write(counter, "m_TooMuchChangeGiven", tooMuchChange); if ((Object)(object)customer != (Object)null) { RegisterInterop.Write(customer, "m_TotalScannedItemCost", (float)customerTotal); if ((Object)(object)customer.m_CustomerCash != (Object)null) { bool isCard = customer.m_CustomerCash.m_IsCard; customer.m_CustomerCash.SetIsCard(usingCard); ((Component)customer.m_CustomerCash).gameObject.SetActive(state == 2); if (state == 2 && isCard != usingCard) { CoopPlugin.Log.LogInfo((object)("[register] offer counter=" + RegisterInterop.Key(counter) + " " + (usingCard ? "card" : "cash") + " (was " + (isCard ? "card" : "cash") + "), customer=" + (((Object)customer).name ?? "?"))); } } customer.m_Anim.SetBool("HandingOverCash", state == 2); } counter.UpdateCashierCounterState(val); UI_CashCounterScreen obj = RegisterInterop.CashScreen(counter); if (obj != null) { obj.UpdateMoneyChangeAmount(changeReady, paid, total, change); } if (Math.Abs(total - num) > 0.005 || Math.Abs(customerTotal - num2) > 0.005) { CoopPlugin.Log.LogInfo((object)("[register] phase counter=" + RegisterInterop.Key(counter) + " state=" + ((object)(*(ECashierCounterState*)(&val))/*cast due to .constrained prefix*/).ToString() + " card=" + usingCard + " total=" + num.ToString("F3") + "->" + total.ToString("F3") + " customer=" + num2.ToString("F3") + "->" + customerTotal.ToString("F3") + " paid=" + paid.ToString("F3") + " change=" + change.ToString("F3") + " ready=" + changeReady + " started=" + changeStarted)); } } private static void ApplyChangeStack(InteractableCashierCounter counter, byte state, bool usingCard, List changes) { if ((Object)(object)counter == (Object)null || state != 3 || usingCard) { return; } List interactableCounterMoneyChangeList = counter.m_InteractableCounterMoneyChangeList; int[] array = new int[interactableCounterMoneyChangeList?.Count ?? 0]; int num = 0; while (changes != null && num < changes.Count) { for (int i = 0; i < array.Length; i++) { if ((Object)(object)interactableCounterMoneyChangeList[i] != (Object)null && interactableCounterMoneyChangeList[i].m_Index == changes[num].Slot) { array[i] = changes[num].Count; break; } } num++; } SetChangeStack(counter, array); } private static void SetChangeStack(InteractableCashierCounter counter, IList counts) { List interactableCounterMoneyChangeList = counter.m_InteractableCounterMoneyChangeList; if (interactableCounterMoneyChangeList == null) { return; } RegisterInterop.Write(counter, "m_CurrentMoneyChangeValue", 0.0); RegisterInterop.Write(counter, "m_ChangeMoneyAddedCount", 0); RegisterInterop.Write(counter, "m_ChangeCoinAddedCount", 0); for (int i = 0; i < interactableCounterMoneyChangeList.Count; i++) { InteractableCounterMoneyChange obj = interactableCounterMoneyChangeList[i]; if (obj != null) { obj.ResetAmountGiven(); } } for (int j = 0; j < interactableCounterMoneyChangeList.Count; j++) { int num = ((counts != null && j < counts.Count) ? counts[j] : 0); for (int k = 0; k < num; k++) { InteractableCounterMoneyChange obj2 = interactableCounterMoneyChangeList[j]; if (obj2 != null) { ((InteractableObject)obj2).OnMouseButtonUp(); } } } } private void ApplyOwner(int index, InteractableCashierCounter counter, int owner) { if (owner > 0 && PresenceApi.IsLocalConnection(owner)) { _claims.Add(index); if ((Object)(object)counter != (Object)null && !counter.IsMannedByPlayer()) { ((InteractableObject)counter).OnMouseButtonUp(); } } else { _claims.Remove(index); if ((Object)(object)counter != (Object)null && counter.IsMannedByPlayer()) { ((InteractableObject)counter).OnPressEsc(); } } } private bool IsRemoteOwned(int index) { if (_owners.TryGetValue(index, out var value)) { return IsRemoteOwner(value); } return false; } private static bool IsRemoteOwner(int owner) { if (owner != 0) { return !PresenceApi.IsLocalConnection(owner); } return false; } private void RefreshOwnershipHighlight(int index, int owner) { if (IsRemoteOwner(owner)) { RegisterHighlight.Clear(RegisterInterop.CounterForKey(index)); } } private void ApplyOwnership(int index, int owner) { _owners[index] = owner; ApplyOwner(index, RegisterInterop.CounterForKey(index), owner); RefreshOwnershipHighlight(index, owner); } private LocalStation Station(InteractableCashierCounter counter, out int index) { index = RegisterInterop.Key(counter); if ((Object)(object)counter != (Object)null) { foreach (KeyValuePair station in _stations) { if (station.Value.Counter == counter) { return station.Value; } } } if (index == 0 || !_stations.TryGetValue(index, out var value)) { return null; } return value; } private CheckoutUndo CaptureUndo(InteractableCashierCounter counter, Customer customer) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) CheckoutUndo obj = new CheckoutUndo { Counter = counter, Customer = customer, ItemScannedCount = Convert.ToInt32(RegisterInterop.Read(customer, "m_ItemScannedCount") ?? ((object)0)), CustomerTotal = Convert.ToSingle(RegisterInterop.Read(customer, "m_TotalScannedItemCost") ?? ((object)0f)), Total = RegisterInterop.Total(counter), Paid = RegisterInterop.Paid(counter), Change = RegisterInterop.Change(counter), UsingCard = RegisterInterop.IsUsingCard(counter), ChangeReady = RegisterInterop.IsChangeReady(counter), ChangeStarted = RegisterInterop.ChangeStarted(counter), TooMuchChange = RegisterInterop.TooMuchChange(counter), State = counter.m_CashierCounterState, CashActive = ((Object)(object)customer?.m_CustomerCash != (Object)null && ((Component)customer.m_CustomerCash).gameObject.activeSelf), HandingOverCash = ((Object)(object)customer?.m_Anim != (Object)null && customer.m_Anim.GetBool("HandingOverCash")) }; object obj2 = RegisterInterop.Read(SceneRef.Get(), "m_IsInUIMode"); bool flag = default(bool); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } obj.UiMode = (byte)((uint)num & (flag ? 1u : 0u)) != 0; obj.CreditCardMode = RegisterInterop.CreditCardMode(counter); CheckoutUndo checkoutUndo = obj; List list = counter?.m_InteractableCounterMoneyChangeList; int num2 = 0; while (list != null && num2 < list.Count) { checkoutUndo.ChangeCounts.Add(RegisterInterop.GivenAmount(list[num2])); num2++; } return checkoutUndo; } private void UndoCheckout(CheckoutUndo undo) { //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Invalid comparison between Unknown and I4 //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Invalid comparison between Unknown and I4 InteractableCashierCounter counter = undo.Counter; Customer customer = undo.Customer; if ((Object)(object)counter == (Object)null || (Object)(object)customer == (Object)null) { return; } bool flag = RegisterInterop.ChangeStarted(counter) && !RegisterInterop.IsUsingCard(counter); _applyingRemote++; try { if (counter.m_CurrentCustomer != customer) { RestoreContents(counter, customer); counter.UpdateCurrentCustomer(customer); counter.SetPlsaticBagVisibility(true); } RegisterInterop.Write(customer, "m_IsActive", true); RegisterInterop.Write(customer, "m_HasCheckedOut", false); RegisterInterop.Write(customer, "m_ItemScannedCount", undo.ItemScannedCount); RegisterInterop.Write(customer, "m_TotalScannedItemCost", undo.CustomerTotal); RegisterInterop.Write(counter, "m_CustomerPaidAmount", undo.Paid); RegisterInterop.Write(counter, "m_TotalScannedItemCost", undo.Total); RegisterInterop.Write(counter, "m_CurrentMoneyChangeValue", undo.Change); RegisterInterop.Write(counter, "m_IsUsingCard", undo.UsingCard); RegisterInterop.Write(counter, "m_IsChangeReady", undo.ChangeReady); RegisterInterop.Write(counter, "m_IsStartGivingChange", undo.ChangeStarted); RegisterInterop.Write(counter, "m_TooMuchChangeGiven", undo.TooMuchChange); InteractableCustomerCash customerCash = customer.m_CustomerCash; if (customerCash != null) { customerCash.SetIsCard(undo.UsingCard); } if ((Object)(object)customer.m_CustomerCash != (Object)null) { ((Component)customer.m_CustomerCash).gameObject.SetActive(undo.CashActive); } customer.m_Anim.SetBool("HandingOverCash", undo.HandingOverCash); counter.UpdateCashierCounterState(undo.State); if (undo.UsingCard) { if ((int)undo.State == 3) { RegisterInterop.ShowCreditCardMachine(counter); } else { RegisterInterop.RestoreCreditCardMachine(counter); } } bool flag2 = undo.ChangeStarted && !undo.UsingCard; if (flag != flag2) { if (flag2) { RegisterInterop.OpenDrawer(counter); } else { RegisterInterop.CloseDrawer(counter); } } RestoreChangeCounts(counter, undo.ChangeCounts); if (undo.WalletApplied) { ReverseWallet(undo.WalletDelta); undo.WalletApplied = false; } RebuildCashScreen(counter, customer); if ((int)undo.State == 3 && !undo.UsingCard) { UI_CashCounterScreen obj = RegisterInterop.CashScreen(counter); if (obj != null) { obj.OnStartGivingChange(); } } UI_CashCounterScreen obj2 = RegisterInterop.CashScreen(counter); if (obj2 != null) { obj2.UpdateMoneyChangeAmount(undo.ChangeReady, undo.Paid, undo.Total, undo.Change); } InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val != (Object)null) { object obj3 = RegisterInterop.Read(val, "m_IsInUIMode"); bool flag3 = default(bool); int num; if (obj3 is bool) { flag3 = (bool)obj3; num = 1; } else { num = 0; } bool flag4 = (byte)((uint)num & (flag3 ? 1u : 0u)) != 0; if (undo.UiMode && !flag4) { val.EnterUIMode(); } else if (!undo.UiMode && flag4) { val.ExitUIMode(); CoopPlugin.Log.LogInfo((object)("[register] rollback released the counter UI mode for counter=" + RegisterInterop.Key(counter) + ".")); } } UI_CreditCardScreen val2 = RegisterInterop.CreditScreen(counter); if ((Object)(object)val2 != (Object)null) { bool flag5 = RegisterInterop.CreditCardMode(counter); if (undo.CreditCardMode && !flag5) { val2.EnableCreditCardMode(counter.IsMannedByPlayer()); } else if (!undo.CreditCardMode && flag5) { val2.ResetCounter(); CoopPlugin.Log.LogInfo((object)("[register] rollback reset the credit card screen for counter=" + RegisterInterop.Key(counter) + ".")); } } } finally { _applyingRemote--; } } private static double CheckoutWalletDelta(double paid, double change) { double value = paid - change; if (!(GameInstance.GetCurrencyConversionRate() > 1f)) { return (float)Math.Round(value, 2, MidpointRounding.AwayFromZero); } return (float)Math.Round(value, 3, MidpointRounding.AwayFromZero); } private static void ReverseWallet(double delta) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown if (delta > 0.0001) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin((float)delta, false)); } else if (delta < -0.0001) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin((float)(0.0 - delta), true)); } } private static void RestoreContents(InteractableCashierCounter counter, Customer customer) { //IL_006d: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) int num = 0; List itemInBagList = customer.GetItemInBagList(); int num2 = 0; while (itemInBagList != null && num2 < itemInBagList.Count) { Item val = itemInBagList[num2]; if (!((Object)(object)val == (Object)null)) { if ((Object)(object)val.m_InteractableScanItem != (Object)null && !val.m_InteractableScanItem.IsNotScanned()) { num++; } else { ((Component)val).transform.SetParent(((Component)counter).transform); ((Component)val).transform.position = counter.m_CustomerPlaceItemPos.position + counter.m_CustomerPlaceItemPos.forward * (-0.025f * (float)(num++ % 8)); ((Component)val).gameObject.SetActive(true); ((Collider)val.m_Collider).enabled = true; Rigidbody val2 = RegisterInterop.EnsureItemRigidbody(val); if ((Object)(object)val2 != (Object)null) { val2.isKinematic = false; } ((Behaviour)val.m_InteractableScanItem).enabled = true; val.m_InteractableScanItem.RegisterScanItem(customer, counter.m_ScannedItemLerpPos); } } num2++; } int num3 = 0; List cardInBagList = customer.GetCardInBagList(); int num4 = 0; while (cardInBagList != null && num4 < cardInBagList.Count) { InteractableCard3d val3 = cardInBagList[num4]; if (!((Object)(object)val3 == (Object)null)) { if (!val3.IsNotScanned()) { num3++; } else { ((Component)val3).transform.SetParent(((Component)counter).transform); ((Component)val3).transform.position = counter.m_CustomerPlaceItemPos.position + counter.m_CustomerPlaceItemPos.forward * (-0.035f * (float)(num3++ % 8)); ((Component)val3).gameObject.SetActive(true); ((Collider)val3.m_Collider).enabled = true; Rigidbody val4 = RegisterInterop.EnsureCardRigidbody(val3); if ((Object)(object)val4 != (Object)null) { val4.isKinematic = false; } val3.RegisterScanCard(customer, counter.m_ScannedItemLerpPos); } } num4++; } } private static void RestoreChangeCounts(InteractableCashierCounter counter, List counts) { SetChangeStack(counter, counts); } private static void RebuildCashScreen(InteractableCashierCounter counter, Customer customer) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) UI_CashCounterScreen val = RegisterInterop.CashScreen(counter); if ((Object)(object)val == (Object)null) { return; } val.ResetCounter(); double num = 0.0; List itemInBagList = customer.GetItemInBagList(); int num2 = 0; while (itemInBagList != null && num2 < itemInBagList.Count) { Item val2 = itemInBagList[num2]; if ((Object)(object)val2 != (Object)null && (Object)(object)val2.m_InteractableScanItem != (Object)null && !val2.m_InteractableScanItem.IsNotScanned()) { num += (double)val2.GetCurrentPrice(); val.OnItemScanned((double)val2.GetCurrentPrice(), val2.GetItemType(), num); } num2++; } List cardInBagList = customer.GetCardInBagList(); int num3 = 0; while (cardInBagList != null && num3 < cardInBagList.Count) { InteractableCard3d val3 = cardInBagList[num3]; if ((Object)(object)val3 != (Object)null && !val3.IsNotScanned()) { num += (double)val3.GetCurrentPrice(); val.OnCardScanned((double)val3.GetCurrentPrice(), val3.m_Card3dUI.m_CardUI.GetCardData(), num); } num3++; } } private void ObserveClaim(InteractableCashierCounter counter, int index) { if (_claims.Contains(index)) { return; } _claims.Add(index); _owners[index] = 0; PredictionApi.Predict("register:" + index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = index, Kind = RegisterIntentKind.Claim }); }, delegate { WithPrediction((Action)((InteractableObject)counter).OnMouseButtonUp); }, delegate { _claims.Remove(index); _owners[index] = 0; if (counter.IsMannedByPlayer()) { WithPrediction((Action)((InteractableObject)counter).OnPressEsc); } }); } private void ObserveRelease(InteractableCashierCounter counter, int index) { _claims.Remove(index); _owners[index] = 0; PredictionApi.Predict("register:" + index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = index, Kind = RegisterIntentKind.Release }); }, delegate { _claims.Remove(index); _owners[index] = 0; if (counter.IsMannedByPlayer()) { WithPrediction((Action)((InteractableObject)counter).OnPressEsc); } }, delegate { _claims.Add(index); _owners[index] = 0; if (!counter.IsMannedByPlayer()) { WithPrediction((Action)((InteractableObject)counter).OnMouseButtonUp); } }); } private void ObserveScan(ActionCapture capture) { Customer customer = capture?.Customer; if ((Object)(object)customer == (Object)null) { return; } CoopPlugin.Log.LogInfo((object)("[register] observed " + (capture.IsCard ? "card" : "item") + " scan counter=" + capture.Index + " slot=" + capture.Slot + ".")); int slot = capture.Slot; bool isCard = capture.IsCard; PredictionApi.Predict("register:" + capture.Index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = capture.Index, Kind = (isCard ? RegisterIntentKind.ScanCard : RegisterIntentKind.ScanItem), Slot = slot }); }, delegate { WithPrediction(delegate { if (isCard) { ((InteractableObject)customer.GetCardInBagList()[slot]).OnMouseButtonUp(); } else { ((InteractableObject)customer.GetItemInBagList()[slot].m_InteractableScanItem).OnMouseButtonUp(); } }); }, delegate { WithPrediction(delegate { if (isCard) { List cardInBagList = customer.GetCardInBagList(); if (slot >= 0 && slot < cardInBagList.Count) { RegisterInterop.UnscanCard(capture.Counter, customer, cardInBagList[slot]); } } else { List itemInBagList = customer.GetItemInBagList(); if (slot >= 0 && slot < itemInBagList.Count) { RegisterInterop.UnscanItem(capture.Counter, customer, itemInBagList[slot]); } } UndoCheckout(capture.Undo); }); }); } private void ObservePayment(ActionCapture capture) { if (!((Object)(object)capture?.Cash == (Object)null) && !((Object)(object)capture.Counter == (Object)null)) { CoopPlugin.Log.LogInfo((object)("[register] observed take payment card=" + capture.Cash.m_IsCard + " counterTotal=" + RegisterInterop.Total(capture.Counter).ToString("F3") + " state=" + ((object)Unsafe.As(ref capture.Counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString())); PredictionApi.Predict("register:" + capture.Index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = capture.Index, Kind = RegisterIntentKind.TakePayment, IsCard = capture.Cash.m_IsCard }); }, delegate { WithPrediction((Action)((InteractableObject)capture.Cash).OnMouseButtonUp); }, delegate { UndoCheckout(capture.Undo); }); } } private void ObserveCardPayment(ActionCapture capture) { if ((Object)(object)capture?.Counter == (Object)null) { return; } CoopPlugin.Log.LogInfo((object)("[register] observed card payment value=" + capture.Value.ToString("F3") + " counterTotal=" + RegisterInterop.Total(capture.Counter).ToString("F3") + " customerTotal=" + Convert.ToDouble(RegisterInterop.Read(capture.Counter.m_CurrentCustomer, "m_TotalScannedItemCost") ?? ((object)0f)).ToString("F3") + " state=" + ((object)Unsafe.As(ref capture.Counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString())); if (RegisterInterop.Total(capture.Counter) <= 0.0001) { capture.Undo.WalletDelta = CheckoutWalletDelta(capture.Value, 0.0); capture.Undo.WalletApplied = true; } PredictionApi.Predict("register:" + capture.Index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = capture.Index, Kind = RegisterIntentKind.CardPayment, Value = capture.Value }); }, delegate { WithPrediction(delegate { capture.Counter.EvaluateCreditCard(capture.Value); capture.Undo.WalletApplied = RegisterInterop.Total(capture.Counter) <= 0.0001; }); }, delegate { UndoCheckout(capture.Undo); }); } private void ObserveChange(ActionCapture capture) { InteractableCounterMoneyChange change = capture?.Change; if ((Object)(object)change == (Object)null) { return; } CoopPlugin.Log.LogInfo((object)("[register] observed change slot=" + change.m_Index + " coin=" + change.m_IsCoin + " takeBack=" + capture.TakeBack + " value=" + change.m_ValueDouble.ToString("F3") + " given=" + RegisterInterop.GivenAmount(change) + " counterTotal=" + RegisterInterop.Total(change.m_CashierCounter).ToString("F3"))); bool takeBack = capture.TakeBack; PredictionApi.Predict("register:" + capture.Index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = capture.Index, Kind = RegisterIntentKind.Change, Slot = change.m_Index, IsCoin = change.m_IsCoin, TakeBack = takeBack, Value = change.m_ValueDouble }); }, delegate { WithPrediction(delegate { if (takeBack) { ((InteractableObject)change).OnRightMouseButtonUp(); } else { ((InteractableObject)change).OnMouseButtonUp(); } }); }, delegate { WithPrediction(delegate { if (takeBack) { ((InteractableObject)change).OnMouseButtonUp(); } else { ((InteractableObject)change).OnRightMouseButtonUp(); } }); }); } private void ObserveComplete(ActionCapture capture) { if ((Object)(object)capture?.Counter == (Object)null) { return; } capture.Undo.WalletDelta = CheckoutWalletDelta(capture.Undo.Paid, capture.Undo.Change); capture.Undo.WalletApplied = true; PredictionApi.Predict("register:" + capture.Index, delegate(Guid id) { Send(new RegisterIntentMessage { PredictionId = id, CounterKey = capture.Index, Kind = RegisterIntentKind.Complete }); }, delegate { WithPrediction(delegate { ((InteractableObject)capture.Counter).OnPressSpaceBar(); capture.Undo.WalletApplied = RegisterInterop.Total(capture.Counter) <= 0.0001; }); }, delegate { UndoCheckout(capture.Undo); }); } private static void LogBlockedCashClick(InteractableCustomerCash cash, InteractableCashierCounter counter, Customer cashCustomer) { bool flag = false; if ((Object)(object)counter != (Object)null && (Object)(object)_active != (Object)null) { flag = _active.Station(counter, out var _) != null; } CoopPlugin.Log.LogWarning((object)("[register] blocked cash click: counter=" + (((Object)(object)counter == (Object)null) ? "none" : RegisterInterop.Key(counter).ToString()) + " cashCustomer=" + (((Object)(object)cashCustomer == (Object)null) ? "null" : ((Object)cashCustomer).name) + " counterCustomer=" + (((Object)(object)counter == (Object)null || (Object)(object)counter.m_CurrentCustomer == (Object)null) ? "null" : ((Object)counter.m_CurrentCustomer).name) + " station=" + (flag ? "present" : "none") + " isCard=" + ((Object)(object)cash != (Object)null && cash.m_IsCard) + " cashModel=" + ((Object)(object)cash?.m_CashModel != (Object)null && cash.m_CashModel.activeSelf) + " cardModel=" + ((Object)(object)cash?.m_CardModel != (Object)null && cash.m_CardModel.activeSelf) + " state=" + (((Object)(object)counter == (Object)null) ? "none" : ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString()) + " usingCard=" + ((Object)(object)counter != (Object)null && RegisterInterop.IsUsingCard(counter)))); } private static void LogCheckoutMismatch(string action, InteractableCashierCounter counter) { CoopPlugin.Log.LogWarning((object)("[register] ignored " + action + " for a checkout whose counter customer is not the station carrier; counter=" + (((Object)(object)counter == (Object)null) ? "null" : RegisterInterop.Key(counter).ToString()) + " counterCustomer=" + (((Object)(object)counter == (Object)null || (Object)(object)counter.m_CurrentCustomer == (Object)null) ? "null" : ((Object)counter.m_CurrentCustomer).name))); } private void WithPrediction(Action action) { _applyingPrediction++; try { action(); } finally { _applyingPrediction--; } } private void Send(RegisterIntentMessage message) { if (!_shutdown && _joined && _context.InGame()) { _context.Send(1, (INetMessage)(object)message); } } private void ReleaseStation(int index) { if (!_stations.TryGetValue(index, out var value)) { return; } InteractableCashierCounter val = (((Object)(object)value.Counter != (Object)null) ? value.Counter : RegisterInterop.CounterForKey(index)); if ((Object)(object)value.Carrier != (Object)null) { InteractableCustomerCash customerCash = value.Carrier.m_CustomerCash; if (customerCash != null) { ((Component)customerCash).gameObject.SetActive(false); } ((Component)value.Carrier).gameObject.SetActive(false); RegisterInterop.ReleaseContents(value.Carrier); NpcClientBehaviour.DetachExistingCustomer(value.CustomerIndex, value.Carrier); } if ((Object)(object)val != (Object)null) { RegisterInterop.ResetCheckoutVisuals(val); } _stations.Remove(index); } private static string DeltaKey(RegisterDeltaMessage message) { if (message.Kind == RegisterDeltaKind.Change) { return message.CounterKey + ":" + message.CounterGeneration + ":change:" + message.Slot + ":" + message.IsCoin; } if (message.Kind == RegisterDeltaKind.Scan) { return message.CounterKey + ":" + message.CounterGeneration + ":scan:" + message.CustomerGeneration + ":" + message.Slot + ":" + message.IsCard; } string[] obj = new string[7] { message.CounterKey.ToString(), ":", null, null, null, null, null }; byte kind = (byte)message.Kind; obj[2] = kind.ToString(); obj[3] = ":"; obj[4] = message.CounterGeneration.ToString(); obj[5] = ":"; obj[6] = message.CustomerGeneration.ToString(); return string.Concat(obj); } private void ClearDeferredDeltas() { foreach (RegisterDeltaMessage value in _deferredDeltas.Values) { PredictionApi.Ack(value.PredictionId); } _deferredDeltas.Clear(); } private void ShutdownContents() { foreach (int item in new List(_claims)) { InteractableCashierCounter val = RegisterInterop.CounterForKey(item); if ((Object)(object)val != (Object)null && val.IsMannedByPlayer()) { WithPrediction((Action)((InteractableObject)val).OnPressEsc); } } foreach (int item2 in new List(_stations.Keys)) { ReleaseStation(item2); } _stations.Clear(); _claims.Clear(); _owners.Clear(); _counterGenerations.Clear(); ClearDeferredDeltas(); _pendingBaseline = null; _joined = false; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; ShutdownContents(); NpcClientBehaviour.CustomerManagerReady -= OnReadinessSignal; NpcClientBehaviour.CustomerPoolChanged -= OnReadinessSignal; PlacementApi.StructureChanged -= OnPlacementStructureChanged; SceneManager.sceneLoaded -= OnSceneLoaded; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnDestroy() { Shutdown(); } private static void CaptureChange(InteractableCounterMoneyChange change, bool takeBack, out ActionCapture capture) { capture = null; RegisterClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applyingRemote != 0 || active._applyingPrediction != 0) { return; } InteractableCashierCounter val = change?.m_CashierCounter; int index; LocalStation localStation = active.Station(val, out index); if (localStation != null) { if (localStation.Carrier != val?.m_CurrentCustomer) { LogCheckoutMismatch("change click", val); return; } capture = new ActionCapture { Counter = val, Customer = val.m_CurrentCustomer, Index = index, Change = change, TakeBack = takeBack, GivenBefore = RegisterInterop.GivenAmount(change) }; } } private int ItemSlot(InteractableScanItem item, int counterIndex) { InteractableCashierCounter obj = RegisterInterop.CounterForKey(counterIndex); object obj2; if (obj == null) { obj2 = null; } else { Customer currentCustomer = obj.m_CurrentCustomer; obj2 = ((currentCustomer != null) ? currentCustomer.GetItemInBagList() : null); } List list = (List)obj2; int num = 0; while (list != null && num < list.Count) { if ((Object)(object)list[num]?.m_InteractableScanItem == (Object)(object)item) { return num; } num++; } return -1; } private int CardSlot(InteractableCard3d card, int counterIndex) { InteractableCashierCounter obj = RegisterInterop.CounterForKey(counterIndex); object obj2; if (obj == null) { obj2 = null; } else { Customer currentCustomer = obj.m_CurrentCustomer; obj2 = ((currentCustomer != null) ? currentCustomer.GetCardInBagList() : null); } List list = (List)obj2; int num = 0; while (list != null && num < list.Count) { if ((Object)(object)list[num] == (Object)(object)card) { return num; } num++; } return -1; } } internal static class RegisterHighlight { private static FieldInfo _raycastedField; private static bool _raycastedFieldResolved; private static FieldInfo _controllerTargetField; private static bool _controllerTargetFieldResolved; private static bool IsRaycast(InteractableCashierCounter counter) { if ((Object)(object)counter == (Object)null) { return false; } if (!_raycastedFieldResolved) { _raycastedField = ReflectionSurface.OptionalField(typeof(InteractableObject), "m_IsRaycasted"); _raycastedFieldResolved = true; } object obj = _raycastedField?.GetValue(counter); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static FieldInfo ControllerTargetField() { if (!_controllerTargetFieldResolved) { _controllerTargetField = ReflectionSurface.OptionalField(typeof(InteractionPlayerController), "m_CurrentRaycastObject"); _controllerTargetFieldResolved = true; } return _controllerTargetField; } private static void DetachIfAimed(InteractableCashierCounter counter) { if (!((Object)(object)counter == (Object)null)) { InteractionPlayerController val = SceneRef.Get(); FieldInfo fieldInfo = (((Object)(object)val == (Object)null) ? null : ControllerTargetField()); if (!(fieldInfo == null) && fieldInfo.GetValue(val) == counter) { fieldInfo.SetValue(val, null); } } } internal static void Clear(InteractableCashierCounter counter) { if (!((Object)(object)counter == (Object)null)) { if (IsRaycast(counter)) { ((InteractableObject)counter).OnRaycastEnded(); } DetachIfAimed(counter); } } } [ServerBehaviour] public sealed class RegisterHostBehaviour : CoopBehaviour { private sealed class Station { public InteractableCashierCounter Counter; public uint CounterGeneration; public int Owner; public Customer Customer; public uint CustomerGeneration; } [HarmonyPatch(typeof(InteractableCashierCounter), "OnMouseButtonUp")] private static class ManningPatch { [HarmonyPrefix] private static bool Prefix(InteractableCashierCounter __instance) { RegisterHostBehaviour active = _active; if ((Object)(object)active == (Object)null || (Object)(object)__instance == (Object)null || !active._context.InGame()) { return true; } int num = Key(__instance); if (num == 0) { return true; } if (active.ObservePublishing(num, __instance).Owner > 0) { CoopPlugin.Log.LogInfo((object)("[register] host left counter " + num + " to the player already manning it.")); return false; } return true; } [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null) && _active._context.InGame() && __instance.IsMannedByPlayer()) { int num = Key(__instance); if (num != 0) { _active.ObservePublishing(num, __instance).Owner = -1; _active.PublishOwnership(num); } } } } [HarmonyPatch(typeof(InteractableObject), "OnRaycasted")] private static class OutlinePatch { [HarmonyPrefix] private static bool Prefix(InteractableObject __instance) { RegisterHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active._context.InGame()) { InteractableCashierCounter val = (InteractableCashierCounter)(object)((__instance is InteractableCashierCounter) ? __instance : null); if (val != null) { int num = Key(val); if (num != 0 && active._stations.TryGetValue(num, out var value) && value.Owner > 0) { RegisterHighlight.Clear(val); return false; } return true; } } return true; } } [HarmonyPatch(typeof(InteractableCashierCounter), "OnPressEsc")] private static class ExitPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { int num = Key(__instance); if ((Object)(object)_active != (Object)null && num != 0 && _active.GetStation(num).Owner == -1 && !__instance.IsMannedByPlayer()) { _active.GetStation(num).Owner = 0; _active.PublishOwnership(num); } } } [HarmonyPatch(typeof(InteractableCashierCounter), "UpdateCurrentCustomer")] private static class CustomerPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { _active?.PublishCustomerLifecycle(Key(__instance)); } } [HarmonyPatch(typeof(InteractableCashierCounter), "UpdateCashierCounterState")] private static class StatePatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 _active?.PublishPhase(Key(__instance)); if ((Object)(object)__instance != (Object)null && (int)__instance.m_CashierCounterState == 2) { _active?.PublishPaidAmount(Key(__instance)); } } } [HarmonyPatch(typeof(InteractableCashierCounter), "SetCustomerPaidAmount")] private static class PaymentPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { _active?.PublishPhase(Key(__instance)); } } [HarmonyPatch(typeof(InteractableCashierCounter), "StartGivingChange")] private static class ChangePhasePatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { _active?.PublishPhase(Key(__instance)); } } [HarmonyPatch(typeof(InteractableScanItem), "OnMouseButtonUp")] private static class ScanItemPatch { [HarmonyPrefix] private static void Prefix(InteractableScanItem __instance, out InteractableCashierCounter __state) { __state = RegisterInterop.FindCounterForScanItem(__instance); } [HarmonyPostfix] private static void Postfix(InteractableScanItem __instance, InteractableCashierCounter __state) { int num = Key(__state); if (!((Object)(object)_active != (Object)null) || num == 0 || !((Object)(object)__state?.m_CurrentCustomer != (Object)null)) { return; } List itemInBagList = __state.m_CurrentCustomer.GetItemInBagList(); int num2 = 0; while (itemInBagList != null && num2 < itemInBagList.Count) { if ((Object)(object)itemInBagList[num2]?.m_InteractableScanItem == (Object)(object)__instance) { _active.PublishScan(num, isCard: false, num2); break; } num2++; } } } [HarmonyPatch(typeof(InteractableCard3d), "OnMouseButtonUp")] private static class ScanCardPatch { [HarmonyPrefix] private static void Prefix(InteractableCard3d __instance, out InteractableCashierCounter __state) { __state = RegisterInterop.FindCounterForCard(__instance); } [HarmonyPostfix] private static void Postfix(InteractableCard3d __instance, InteractableCashierCounter __state) { int num = Key(__state); if (!((Object)(object)_active != (Object)null) || num == 0 || !((Object)(object)__state?.m_CurrentCustomer != (Object)null)) { return; } List cardInBagList = __state.m_CurrentCustomer.GetCardInBagList(); int num2 = 0; while (cardInBagList != null && num2 < cardInBagList.Count) { if ((Object)(object)cardInBagList[num2] == (Object)(object)__instance) { _active.PublishScan(num, isCard: true, num2); break; } num2++; } } } [HarmonyPatch(typeof(InteractableCounterMoneyChange), "OnMouseButtonUp")] private static class AddChangePatch { [HarmonyPostfix] private static void Postfix(InteractableCounterMoneyChange __instance) { _active?.PublishChange(__instance, takeBack: false); } } [HarmonyPatch(typeof(InteractableCounterMoneyChange), "OnRightMouseButtonUp")] private static class RemoveChangePatch { [HarmonyPostfix] private static void Postfix(InteractableCounterMoneyChange __instance) { _active?.PublishChange(__instance, takeBack: true); } } [HarmonyPatch(typeof(InteractableCashierCounter), "OnPressSpaceBar")] private static class FinishPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { _active?.PublishPhase(Key(__instance)); } } [HarmonyPatch(typeof(ShelfManager), "InitCashierCounter")] private static class CounterAddedPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __0) { _active?.PublishCounterLifecycle(__0); } } [HarmonyPatch(typeof(ShelfManager), "RemoveCashierCounter")] private static class CounterRemovedPatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __0) { _active?.Tombstone(__0); } } [HarmonyPatch(typeof(InteractableCashierCounter), "OnDestroyed")] private static class CounterDestroyedPatch { [HarmonyPrefix] private static void Prefix(InteractableCashierCounter __instance) { _active?.Tombstone(__instance); } } [HarmonyPatch(typeof(InteractableCashierCounter), "ForceResetCounter")] private static class ResetCounterPatch { [HarmonyPostfix] private static void Postfix(InteractableCashierCounter __instance) { RegisterHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !((Object)(object)__instance == (Object)null) && active._context.InGame()) { int num = Key(__instance); if (num != 0 && active.GetStation(num).Owner != 0) { active.GetStation(num).Owner = 0; active.ObservePublishing(num, __instance); active.PublishOwnership(num); RegisterHighlight.Clear(__instance); } } } } private static RegisterHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private Guid _intentPredictionId; private readonly Dictionary _stations = new Dictionary(); private void OnEnable() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (_harmony == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); _harmony = new Harmony("dev.meepen.cardshopcoop.register.host"); Patch(typeof(ManningPatch)); Patch(typeof(ExitPatch)); Patch(typeof(CustomerPatch)); Patch(typeof(StatePatch)); Patch(typeof(PaymentPatch)); Patch(typeof(ChangePhasePatch)); Patch(typeof(ScanItemPatch)); Patch(typeof(ScanCardPatch)); Patch(typeof(AddChangePatch)); Patch(typeof(RemoveChangePatch)); Patch(typeof(FinishPatch)); Patch(typeof(OutlinePatch)); SceneManager.sceneLoaded += OnSceneLoaded; Patch(typeof(CounterAddedPatch)); Patch(typeof(CounterRemovedPatch)); Patch(typeof(CounterDestroyedPatch)); Patch(typeof(ResetCounterPatch)); } } private void Patch(Type patchType) { _harmony.CreateClassProcessor(patchType).Patch(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (connection != null && IsJoinPhase(connection.State) && _context.InGame() && RegisterInterop.Counters != null) { _context.Send(connection.Id, (INetMessage)(object)BuildBaseline()); } } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (!_shutdown) { _stations.Clear(); } } [MessageHandler(typeof(RegisterIntentMessage))] private void HandleIntent(MessageContext context, RegisterIntentMessage message) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) PeerConnection val = context?.Connection; if (_shutdown || val == null || message == null || !IsJoinPhase(val.State) || !_context.InGame() || message.CounterKey == 0 || !Enum.IsDefined(typeof(RegisterIntentKind), message.Kind)) { return; } _intentPredictionId = message.PredictionId; try { int counterKey = message.CounterKey; InteractableCashierCounter val2 = RegisterInterop.CounterForKey(counterKey); Station station = (((Object)(object)val2 == (Object)null) ? null : ObservePublishing(counterKey, val2)); if ((Object)(object)val2 == (Object)null) { Reject(val.Id, message.PredictionId); } else if (message.Kind == RegisterIntentKind.Claim) { if (station.Owner != 0 && station.Owner != val.Id) { Reject(val.Id, message.PredictionId); return; } if (val2.IsMannedByPlayer() && station.Owner != val.Id) { Reject(val.Id, message.PredictionId); return; } val2.StopCurrentWorker(); station.Owner = val.Id; PublishOwnership(counterKey); RegisterHighlight.Clear(val2); } else if (message.Kind == RegisterIntentKind.Release) { if (station.Owner != val.Id) { Reject(val.Id, message.PredictionId); return; } station.Owner = 0; PublishOwnership(counterKey); } else if (station.Owner != val.Id || !Apply(val2, message)) { if (station.Owner != val.Id) { LogIntentReject("station owner " + station.Owner + " != sender " + val.Id, message); } Reject(val.Id, message.PredictionId); } else if (message.Kind == RegisterIntentKind.TakePayment) { PublishPhase(counterKey, force: true); } else if (message.Kind == RegisterIntentKind.CardPayment || message.Kind == RegisterIntentKind.Complete) { CreditGuestCheckout(); PublishCustomerLifecycle(counterKey, force: true); } } finally { _intentPredictionId = Guid.Empty; } } private bool Apply(InteractableCashierCounter counter, RegisterIntentMessage message) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Invalid comparison between Unknown and I4 //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Invalid comparison between Unknown and I4 //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Invalid comparison between Unknown and I4 //IL_044a: Unknown result type (might be due to invalid IL or missing references) //IL_0450: Invalid comparison between Unknown and I4 //IL_0562: Unknown result type (might be due to invalid IL or missing references) //IL_0568: Invalid comparison between Unknown and I4 Customer currentCustomer = counter.m_CurrentCustomer; if ((Object)(object)currentCustomer == (Object)null || !currentCustomer.m_IsActive) { return false; } switch (message.Kind) { case RegisterIntentKind.ScanItem: { if ((int)counter.m_CashierCounterState != 1) { LogIntentReject("counter state " + ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString(), message); return false; } List itemInBagList = currentCustomer.GetItemInBagList(); if (message.Slot < 0 || message.Slot >= itemInBagList.Count || (Object)(object)itemInBagList[message.Slot] == (Object)null || (Object)(object)itemInBagList[message.Slot].m_InteractableScanItem == (Object)null || !itemInBagList[message.Slot].m_InteractableScanItem.IsNotScanned()) { LogIntentReject("slot=" + message.Slot + " bagItems=" + (itemInBagList?.Count ?? (-1)) + " alreadyScanned=" + (itemInBagList != null && message.Slot >= 0 && message.Slot < itemInBagList.Count && (Object)(object)itemInBagList[message.Slot]?.m_InteractableScanItem != (Object)null && !itemInBagList[message.Slot].m_InteractableScanItem.IsNotScanned()), message); return false; } ((InteractableObject)itemInBagList[message.Slot].m_InteractableScanItem).OnMouseButtonUp(); return true; } case RegisterIntentKind.ScanCard: { if ((int)counter.m_CashierCounterState != 1) { LogIntentReject("counter state " + ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString(), message); return false; } List cardInBagList = currentCustomer.GetCardInBagList(); if (message.Slot < 0 || message.Slot >= cardInBagList.Count || (Object)(object)cardInBagList[message.Slot] == (Object)null || !cardInBagList[message.Slot].IsNotScanned()) { LogIntentReject("slot=" + message.Slot + " bagCards=" + (cardInBagList?.Count ?? (-1)) + " alreadyScanned=" + (cardInBagList != null && message.Slot >= 0 && message.Slot < cardInBagList.Count && (Object)(object)cardInBagList[message.Slot] != (Object)null && !cardInBagList[message.Slot].IsNotScanned()), message); return false; } ((InteractableObject)cardInBagList[message.Slot]).OnMouseButtonUp(); return true; } case RegisterIntentKind.TakePayment: if ((int)counter.m_CashierCounterState != 2 || (Object)(object)currentCustomer.m_CustomerCash == (Object)null || currentCustomer.m_CustomerCash.m_IsCard != message.IsCard) { LogIntentReject("take payment state=" + ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString() + " intentCard=" + message.IsCard + " cash=" + (((Object)(object)currentCustomer.m_CustomerCash == (Object)null) ? "null" : currentCustomer.m_CustomerCash.m_IsCard.ToString()), message); return false; } ((InteractableObject)currentCustomer.m_CustomerCash).OnMouseButtonUp(); return true; case RegisterIntentKind.CardPayment: if ((int)counter.m_CashierCounterState != 3 || !RegisterInterop.IsUsingCard(counter) || !RegisterInterop.IsFinite(message.Value) || Math.Abs(message.Value - RegisterInterop.Total(counter)) > 0.011) { LogIntentReject("card payment state=" + ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString() + " usingCard=" + RegisterInterop.IsUsingCard(counter) + " value=" + message.Value.ToString("F3") + " hostTotal=" + RegisterInterop.Total(counter).ToString("F3"), message); return false; } counter.EvaluateCreditCard(message.Value); return true; case RegisterIntentKind.Change: { if ((int)counter.m_CashierCounterState != 3 || RegisterInterop.IsUsingCard(counter) || !RegisterInterop.IsFinite(message.Value)) { LogIntentReject("change state=" + ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString() + " usingCard=" + RegisterInterop.IsUsingCard(counter) + " value=" + message.Value.ToString("F3"), message); return false; } InteractableCounterMoneyChange val = RegisterInterop.FindChange(counter, message.Slot, message.IsCoin); if ((Object)(object)val == (Object)null) { LogIntentReject("change slot=" + message.Slot + " coin=" + message.IsCoin + " value=" + message.Value.ToString("F3") + " not found", message); return false; } if (message.TakeBack) { ((InteractableObject)val).OnRightMouseButtonUp(); } else { ((InteractableObject)val).OnMouseButtonUp(); } return true; } case RegisterIntentKind.Complete: if ((int)counter.m_CashierCounterState != 3 || !RegisterInterop.IsChangeReady(counter)) { LogIntentReject("complete state=" + ((object)Unsafe.As(ref counter.m_CashierCounterState)/*cast due to .constrained prefix*/).ToString() + " changeReady=" + RegisterInterop.IsChangeReady(counter), message); return false; } ((InteractableObject)counter).OnPressSpaceBar(); return true; default: return false; } } private Station Observe(int index, InteractableCashierCounter counter) { if (!_stations.TryGetValue(index, out var value)) { value = new Station(); _stations.Add(index, value); } if (value.Counter != counter) { value.Counter = counter; value.CounterGeneration++; if (value.CounterGeneration == 0) { value.CounterGeneration = 1u; } value.Owner = 0; value.Customer = null; } Customer currentCustomer = counter.m_CurrentCustomer; if (value.Customer != currentCustomer) { value.Customer = currentCustomer; if ((Object)(object)currentCustomer != (Object)null) { int customerGeneration = NpcHostBehaviour.GetCustomerGeneration(currentCustomer); value.CustomerGeneration = ((customerGeneration > 0) ? ((uint)customerGeneration) : NextCustomerGeneration(value.CustomerGeneration)); } } else if ((Object)(object)currentCustomer != (Object)null) { int customerGeneration2 = NpcHostBehaviour.GetCustomerGeneration(currentCustomer); if (customerGeneration2 > 0 && customerGeneration2 != (int)value.CustomerGeneration) { CoopPlugin.Log.LogDebug((object)("[register] counter " + index + " customer generation " + value.CustomerGeneration + " -> " + customerGeneration2 + " (pooled customer reused).")); value.CustomerGeneration = (uint)customerGeneration2; } } if (value.Owner == -1 && !counter.IsMannedByPlayer()) { value.Owner = 0; } else if (value.Owner == 0 && counter.IsMannedByPlayer()) { value.Owner = -1; } return value; } private static uint NextCustomerGeneration(uint generation) { generation++; if (generation != 0) { return generation; } return 1u; } private Station ObservePublishing(int index, InteractableCashierCounter counter) { bool num = GetStation(index).Counter != counter; Station station = Observe(index, counter); if (num) { Broadcast(new RegisterDeltaMessage { Kind = RegisterDeltaKind.CounterLifecycle, CounterKey = index, CounterGeneration = station.CounterGeneration, Exists = true, PredictionId = Guid.Empty }); } return station; } private RegisterBaselineMessage BuildBaseline() { RegisterBaselineMessage registerBaselineMessage = new RegisterBaselineMessage(); IList counters = RegisterInterop.Counters; int num = 0; while (counters != null && num < counters.Count) { InteractableCashierCounter val = counters[num]; int num2 = ((!((Object)(object)val == (Object)null)) ? RegisterInterop.Key(val) : 0); if (num2 != 0) { registerBaselineMessage.Counters.Add(BuildBaselineCounter(num2, val)); } num++; } return registerBaselineMessage; } private RegisterBaselineCounter BuildBaselineCounter(int key, InteractableCashierCounter counter) { Station station = Observe(key, counter); RegisterBaselineCounter result = new RegisterBaselineCounter { CounterKey = key, CounterGeneration = station.CounterGeneration, Exists = true, Owner = station.Owner, CustomerIndex = -1 }; FillCommon(result, counter, station.Customer); return result; } private void FillCommon(RegisterBaselineCounter result, InteractableCashierCounter counter, Customer customer) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) result.HasCustomer = (Object)(object)customer != (Object)null; result.State = (byte)counter.m_CashierCounterState; result.UsingCard = RegisterInterop.IsUsingCard(counter); result.Paid = RegisterInterop.Paid(counter); result.Total = RegisterInterop.Total(counter); result.Change = RegisterInterop.Change(counter); result.ChangeReady = RegisterInterop.IsChangeReady(counter); result.ChangeStarted = RegisterInterop.ChangeStarted(counter); result.TooMuchChange = RegisterInterop.TooMuchChange(counter); if (!((Object)(object)customer == (Object)null)) { Station station = Observe(RegisterInterop.Key(counter), counter); result.CustomerIndex = RegisterInterop.CustomerIndex(customer); result.CustomerGeneration = (int)station.CustomerGeneration; result.CustomerFemale = customer.m_IsFemale; result.CharacterName = customer.m_CharacterCustom?.CharacterName; result.CustomerTotal = Convert.ToDouble(RegisterInterop.Read(customer, "m_TotalScannedItemCost") ?? ((object)0f)); AddLines(result.Lines, customer); AddChanges(result.ChangeItems, counter); } } private static void AddLines(List lines, Customer customer) { //IL_0024: 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) List itemInBagList = customer.GetItemInBagList(); int num = 0; while (itemInBagList != null && num < itemInBagList.Count) { Item val = itemInBagList[num]; lines.Add(new RegisterLine { ItemType = (EItemType)(((Object)(object)val == (Object)null) ? (-1) : ((int)val.GetItemType())), Price = (((Object)(object)val == (Object)null) ? 0f : val.GetCurrentPrice()), Scanned = ((Object)(object)val != (Object)null && (Object)(object)val.m_InteractableScanItem != (Object)null && !val.m_InteractableScanItem.IsNotScanned()) }); num++; } List cardInBagList = customer.GetCardInBagList(); int num2 = 0; while (cardInBagList != null && num2 < cardInBagList.Count) { InteractableCard3d val2 = cardInBagList[num2]; RegisterLine obj = new RegisterLine { IsCard = true }; object card; if (val2 == null) { card = null; } else { Card3dUIGroup card3dUI = val2.m_Card3dUI; if (card3dUI == null) { card = null; } else { CardUI cardUI = card3dUI.m_CardUI; card = ((cardUI != null) ? cardUI.GetCardData() : null); } } obj.Card = (CardData)card; obj.Price = (((Object)(object)val2 == (Object)null) ? 0f : val2.GetCurrentPrice()); obj.Scanned = (Object)(object)val2 != (Object)null && !val2.IsNotScanned(); lines.Add(obj); num2++; } } private static void AddChanges(List changes, InteractableCashierCounter counter) { List interactableCounterMoneyChangeList = counter.m_InteractableCounterMoneyChangeList; int num = 0; while (interactableCounterMoneyChangeList != null && num < interactableCounterMoneyChangeList.Count) { InteractableCounterMoneyChange val = interactableCounterMoneyChangeList[num]; int num2 = ((!((Object)(object)val == (Object)null)) ? RegisterInterop.GivenAmount(val) : 0); if ((Object)(object)val != (Object)null && num2 > 0) { changes.Add(new RegisterChange { Slot = val.m_Index, IsCoin = val.m_IsCoin, Value = val.m_ValueDouble, Count = num2 }); } num++; } } private RegisterDeltaMessage NewDelta(int index, RegisterDeltaKind kind) { InteractableCashierCounter val = RegisterInterop.CounterForKey(index); Station station = (((Object)(object)val == (Object)null) ? GetStation(index) : ObservePublishing(index, val)); return new RegisterDeltaMessage { Kind = kind, CounterKey = index, CounterGeneration = station.CounterGeneration, PredictionId = _intentPredictionId }; } private void PublishOwnership(int index) { InteractableCashierCounter val = RegisterInterop.CounterForKey(index); RegisterDeltaMessage registerDeltaMessage = NewDelta(index, RegisterDeltaKind.Ownership); registerDeltaMessage.Owner = ((!((Object)(object)val == (Object)null)) ? Observe(index, val).Owner : 0); Broadcast(registerDeltaMessage); } private void PublishCustomerLifecycle(int index, bool force = false) { if (index != 0 && (force || !(_intentPredictionId != Guid.Empty))) { InteractableCashierCounter val = RegisterInterop.CounterForKey(index); RegisterDeltaMessage registerDeltaMessage = NewDelta(index, RegisterDeltaKind.CustomerLifecycle); Station station = (((Object)(object)val == (Object)null) ? GetStation(index) : Observe(index, val)); Customer val2 = val?.m_CurrentCustomer; registerDeltaMessage.HasCustomer = (Object)(object)val2 != (Object)null; if ((Object)(object)val2 != (Object)null) { registerDeltaMessage.CustomerIndex = RegisterInterop.CustomerIndex(val2); registerDeltaMessage.CustomerGeneration = (int)station.CustomerGeneration; registerDeltaMessage.CustomerFemale = val2.m_IsFemale; registerDeltaMessage.CharacterName = val2.m_CharacterCustom?.CharacterName; registerDeltaMessage.CustomerTotal = Convert.ToDouble(RegisterInterop.Read(val2, "m_TotalScannedItemCost") ?? ((object)0f)); AddLines(registerDeltaMessage.Lines, val2); } FillPhase(registerDeltaMessage, val); Broadcast(registerDeltaMessage); } } private void PublishScan(int index, bool isCard, int slot) { InteractableCashierCounter val = RegisterInterop.CounterForKey(index); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.m_CurrentCustomer == (Object)null) && slot >= 0) { RegisterDeltaMessage registerDeltaMessage = NewDelta(index, RegisterDeltaKind.Scan); registerDeltaMessage.HasCustomer = true; registerDeltaMessage.CustomerIndex = RegisterInterop.CustomerIndex(val.m_CurrentCustomer); registerDeltaMessage.CustomerGeneration = (int)Observe(index, val).CustomerGeneration; registerDeltaMessage.IsCard = isCard; registerDeltaMessage.Slot = slot; FillPhase(registerDeltaMessage, val); Broadcast(registerDeltaMessage); } } private void PublishPhase(int index, bool force = false) { if (index != 0 && (force || !(_intentPredictionId != Guid.Empty))) { InteractableCashierCounter val = RegisterInterop.CounterForKey(index); if (!((Object)(object)val == (Object)null)) { RegisterDeltaMessage delta = NewDelta(index, RegisterDeltaKind.PhasePayment); FillPhase(delta, val); Broadcast(delta); } } } private void PublishPaidAmount(int index) { if (index != 0) { InteractableCashierCounter val = RegisterInterop.CounterForKey(index); Customer val2 = val?.m_CurrentCustomer; if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null)) { Station station = ObservePublishing(index, val); RegisterDeltaMessage delta = new RegisterDeltaMessage { Kind = RegisterDeltaKind.PaidAmount, CounterKey = index, CounterGeneration = station.CounterGeneration, HasCustomer = true, CustomerIndex = RegisterInterop.CustomerIndex(val2), CustomerGeneration = (int)station.CustomerGeneration, PredictionId = Guid.Empty }; FillPhase(delta, val); Broadcast(delta); } } } private static void FillPhase(RegisterDeltaMessage delta, InteractableCashierCounter counter) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)counter == (Object)null)) { delta.State = (byte)counter.m_CashierCounterState; delta.UsingCard = RegisterInterop.IsUsingCard(counter); delta.Paid = RegisterInterop.Paid(counter); delta.Total = RegisterInterop.Total(counter); delta.Change = RegisterInterop.Change(counter); delta.ChangeReady = RegisterInterop.IsChangeReady(counter); delta.ChangeStarted = RegisterInterop.ChangeStarted(counter); delta.TooMuchChange = RegisterInterop.TooMuchChange(counter); if ((Object)(object)counter.m_CurrentCustomer != (Object)null) { delta.CustomerTotal = Convert.ToDouble(RegisterInterop.Read(counter.m_CurrentCustomer, "m_TotalScannedItemCost") ?? ((object)0f)); } } } private void PublishChange(InteractableCounterMoneyChange change, bool takeBack) { InteractableCashierCounter val = change?.m_CashierCounter; int num = RegisterInterop.Key(val); if (num != 0 && !((Object)(object)change == (Object)null)) { RegisterDeltaMessage registerDeltaMessage = NewDelta(num, RegisterDeltaKind.Change); registerDeltaMessage.Slot = change.m_Index; registerDeltaMessage.IsCoin = change.m_IsCoin; registerDeltaMessage.Value = change.m_ValueDouble; Customer currentCustomer = val.m_CurrentCustomer; registerDeltaMessage.HasCustomer = (Object)(object)currentCustomer != (Object)null; if ((Object)(object)currentCustomer != (Object)null) { registerDeltaMessage.CustomerIndex = RegisterInterop.CustomerIndex(currentCustomer); registerDeltaMessage.CustomerGeneration = (int)Observe(num, val).CustomerGeneration; } registerDeltaMessage.ChangeCount = RegisterInterop.GivenAmount(change); FillPhase(registerDeltaMessage, val); Broadcast(registerDeltaMessage); } } private void PublishCounterLifecycle(InteractableCashierCounter counter) { int num = ((!((Object)(object)counter == (Object)null)) ? RegisterInterop.Key(counter) : 0); if (num != 0) { RegisterDeltaMessage registerDeltaMessage = NewDelta(num, RegisterDeltaKind.CounterLifecycle); registerDeltaMessage.Exists = true; Broadcast(registerDeltaMessage); } } private void Tombstone(InteractableCashierCounter counter) { if ((Object)(object)counter == (Object)null) { return; } int num = 0; foreach (KeyValuePair station in _stations) { if (station.Value.Counter == counter) { num = station.Key; break; } } if (num != 0) { uint counterGeneration = _stations[num].CounterGeneration; _stations.Remove(num); RegisterDeltaMessage delta = new RegisterDeltaMessage { Kind = RegisterDeltaKind.CounterLifecycle, CounterKey = num, CounterGeneration = counterGeneration, Exists = false, PredictionId = _intentPredictionId }; Broadcast(delta); } } private Station GetStation(int index) { if (!_stations.TryGetValue(index, out var value)) { value = new Station { CounterGeneration = 1u }; _stations.Add(index, value); } return value; } private void Broadcast(RegisterDeltaMessage delta) { if (!_shutdown && _context.InGame()) { _context.Broadcast((INetMessage)(object)delta); } } private static void LogIntentReject(string reason, RegisterIntentMessage message) { CoopPlugin.Log.LogWarning((object)("[register] rejected " + (message?.Kind).ToString() + " counter=" + (message?.CounterKey ?? 0) + " slot=" + (message?.Slot ?? (-1)) + ": " + reason)); } private void Reject(int connectionId, Guid predictionId) { if (predictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, connectionId, predictionId); } } private static void CreditGuestCheckout() { CPlayerData.m_GameReportDataCollect.manualCheckoutCount++; CPlayerData.m_GameReportDataCollectPermanent.manualCheckoutCount++; AchievementManager.OnCustomerFinishCheckout(CPlayerData.m_GameReportDataCollectPermanent.manualCheckoutCount); TutorialManager.AddTaskValue((ETutorialTaskCondition)6, 1f); } [OnClientDisconnected] private void Disconnected(PeerConnection connection, DisconnectInfo _) { if (connection == null) { return; } foreach (KeyValuePair station in _stations) { if (station.Value.Owner == connection.Id) { station.Value.Owner = 0; PublishOwnership(station.Key); } } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _stations.Clear(); } } private void OnDestroy() { Shutdown(); } private static int Key(InteractableCashierCounter counter) { return RegisterInterop.Key(counter); } } internal static class RegisterInterop { private static MethodInfo _getOrCreateCardRigidbody; private static bool _cardRigidbodyMethodResolved; private static MethodInfo _getOrCreateItemRigidbody; private static bool _itemRigidbodyMethodResolved; internal const int CashierKind = 4; internal static IList Counters => SceneRef.Get()?.m_CashierCounterList; internal static FieldInfo Field(Type type, string name) { return ReflectionSurface.OptionalField(type, name); } internal static object Read(object target, string name) { if (target == null) { return null; } return Field(target.GetType(), name)?.GetValue(target); } internal static void Write(object target, string name, object value) { if (target != null) { Field(target.GetType(), name)?.SetValue(target, value); } } internal static int Key(InteractableCashierCounter counter) { if (!((Object)(object)counter != (Object)null) || !PlacementApi.TryMakeObjectKey(4, (InteractableObject)(object)counter, out var key)) { return 0; } return key; } internal static InteractableCashierCounter CounterForKey(int key) { if (key != 0) { Component obj = PlacementApi.ResolveObjectByKey(key); InteractableCashierCounter val = (InteractableCashierCounter)(object)((obj is InteractableCashierCounter) ? obj : null); if (val != null) { return val; } } return null; } internal static UI_CashCounterScreen CashScreen(InteractableCashierCounter counter) { object obj = Read(counter, "m_UICashCounterScreen"); return (UI_CashCounterScreen)((obj is UI_CashCounterScreen) ? obj : null); } internal static UI_CreditCardScreen CreditScreen(InteractableCashierCounter counter) { object obj = Read(counter, "m_UICreditCardScreen"); return (UI_CreditCardScreen)((obj is UI_CreditCardScreen) ? obj : null); } internal static bool IsUsingCard(InteractableCashierCounter counter) { object obj = Read(counter, "m_IsUsingCard"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static bool IsChangeReady(InteractableCashierCounter counter) { object obj = Read(counter, "m_IsChangeReady"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static double Total(InteractableCashierCounter counter) { return Convert.ToDouble(Read(counter, "m_TotalScannedItemCost") ?? ((object)0.0)); } internal static double Paid(InteractableCashierCounter counter) { return Convert.ToDouble(Read(counter, "m_CustomerPaidAmount") ?? ((object)0.0)); } internal static double Change(InteractableCashierCounter counter) { return Convert.ToDouble(Read(counter, "m_CurrentMoneyChangeValue") ?? ((object)0.0)); } internal static bool ChangeStarted(InteractableCashierCounter counter) { object obj = Read(counter, "m_IsStartGivingChange"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static bool TooMuchChange(InteractableCashierCounter counter) { object obj = Read(counter, "m_TooMuchChangeGiven"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static int GivenAmount(InteractableCounterMoneyChange change) { return Convert.ToInt32(Read(change, "m_GivenAmount") ?? ((object)0)); } internal static void SetGivenAmount(InteractableCounterMoneyChange change, int amount) { if ((Object)(object)change == (Object)null) { return; } amount = Math.Max(0, amount); int num = GivenAmount(change); if (num == amount) { return; } if (num < amount) { for (int i = num; i < amount; i++) { ((InteractableObject)change).OnMouseButtonUp(); } } else { for (int j = amount; j < num; j++) { ((InteractableObject)change).OnRightMouseButtonUp(); } } Write(change, "m_GivenAmount", amount); } internal static int CustomerIndex(Customer customer) { CustomerManager obj = SceneRef.Get(); List list = ((obj != null) ? obj.GetCustomerList() : null); if (list != null && !((Object)(object)customer == (Object)null)) { return list.IndexOf(customer); } return -1; } internal static bool IsManned(InteractableCashierCounter counter) { if ((Object)(object)counter != (Object)null) { return counter.IsMannedByPlayer(); } return false; } internal static bool IsFinite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } internal static InteractableCounterMoneyChange FindChange(InteractableCashierCounter counter, int slot, bool isCoin) { List list = counter?.m_InteractableCounterMoneyChangeList; if (list == null || slot < 0) { return null; } for (int i = 0; i < list.Count; i++) { InteractableCounterMoneyChange val = list[i]; if ((Object)(object)val != (Object)null && val.m_Index == slot && val.m_IsCoin == isCoin) { return val; } } return null; } internal static InteractableCashierCounter CounterFor(Customer customer) { object obj = Read(customer, "m_CurrentQueueCashierCounter"); return (InteractableCashierCounter)((obj is InteractableCashierCounter) ? obj : null); } internal static InteractableCashierCounter FindCounterForScanItem(InteractableScanItem item) { if (!((Object)(object)item == (Object)null)) { object obj = Read(item, "m_CurrentCustomer"); return CounterFor((Customer)((obj is Customer) ? obj : null)); } return null; } internal static InteractableCashierCounter FindCounterForCard(InteractableCard3d card) { if (!((Object)(object)card == (Object)null)) { object obj = Read(card, "m_CurrentCustomer"); return CounterFor((Customer)((obj is Customer) ? obj : null)); } return null; } internal static InteractableCashierCounter FindCounterForCash(InteractableCustomerCash cash) { if (!((Object)(object)cash == (Object)null)) { object obj = Read(cash, "m_CurrentCustomer"); return CounterFor((Customer)((obj is Customer) ? obj : null)); } return null; } internal static void PrepareCarrier(Customer customer, InteractableCashierCounter counter) { Write(customer, "m_IsActive", true); Write(customer, "m_ItemScannedCount", 0); Write(customer, "m_TotalScannedItemCost", 0f); Write(customer, "m_HasCheckedOut", false); Write(customer, "m_CurrentQueueCashierCounter", counter); customer.m_CustomerCash.Init(customer); ((Component)customer.m_CustomerCash).gameObject.SetActive(false); } internal static void RestoreCreditCardMachine(InteractableCashierCounter counter) { //IL_002f: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)counter == (Object)null) { return; } Transform creditCardMachineModel = counter.m_CreditCardMachineModel; if ((Object)(object)creditCardMachineModel != (Object)null) { if (Read(counter, "m_CreditCardMachineOriginalPos") is Vector3 position) { creditCardMachineModel.position = position; } if (Read(counter, "m_CreditCardMachineOriginalRot") is Quaternion rotation) { creditCardMachineModel.rotation = rotation; } } GameObject creditCardModel = counter.m_CreditCardModel; if (creditCardModel != null) { creditCardModel.SetActive(false); } } internal static void ShowCreditCardMachine(InteractableCashierCounter counter) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)counter == (Object)null)) { Transform creditCardMachineModel = counter.m_CreditCardMachineModel; if ((Object)(object)creditCardMachineModel != (Object)null && (Object)(object)counter.m_CreditCardMachineTargetPos != (Object)null) { creditCardMachineModel.position = counter.m_CreditCardMachineTargetPos.position; creditCardMachineModel.rotation = counter.m_CreditCardMachineTargetPos.rotation; } GameObject creditCardModel = counter.m_CreditCardModel; if (creditCardModel != null) { creditCardModel.SetActive(true); } } } internal static void OpenDrawer(InteractableCashierCounter counter) { if (counter != null) { Animation openCloseDrawerAnim = counter.m_OpenCloseDrawerAnim; if (openCloseDrawerAnim != null) { openCloseDrawerAnim.Play("CashRegisterOpenDrawer"); } } } internal static void CloseDrawer(InteractableCashierCounter counter) { if (counter != null) { Animation openCloseDrawerAnim = counter.m_OpenCloseDrawerAnim; if (openCloseDrawerAnim != null) { openCloseDrawerAnim.Play("CashRegisterCloseDrawer"); } } } internal static bool CreditCardMode(InteractableCashierCounter counter) { UI_CreditCardScreen val = CreditScreen(counter); bool flag = default(bool); int num; if ((Object)(object)val != (Object)null) { object obj = Read(val, "m_IsCreditCardMode"); if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } internal static void ResetCheckoutVisuals(InteractableCashierCounter counter) { if ((Object)(object)counter == (Object)null) { return; } List interactableCounterMoneyChangeList = counter.m_InteractableCounterMoneyChangeList; int num = 0; while (interactableCounterMoneyChangeList != null && num < interactableCounterMoneyChangeList.Count) { InteractableCounterMoneyChange obj = interactableCounterMoneyChangeList[num]; if (obj != null) { obj.ResetAmountGiven(); } num++; } UI_CashCounterScreen obj2 = CashScreen(counter); if (obj2 != null) { obj2.ResetCounter(); } UI_CreditCardScreen obj3 = CreditScreen(counter); if (obj3 != null) { obj3.ResetCounter(); } if ((Object)(object)counter.m_CreditCardModel != (Object)null && counter.m_CreditCardModel.activeSelf) { RestoreCreditCardMachine(counter); } if (ChangeStarted(counter)) { CloseDrawer(counter); } Write(counter, "m_IsUsingCard", false); Write(counter, "m_IsStartGivingChange", false); Write(counter, "m_IsChangeReady", false); Write(counter, "m_CurrentMoneyChangeValue", 0.0); Write(counter, "m_TooMuchChangeGiven", false); counter.UpdateCashierCounterState((ECashierCounterState)0); counter.UpdateCurrentCustomer((Customer)null); counter.SetPlsaticBagVisibility(false); } internal static void ReleaseContents(Customer customer) { if ((Object)(object)customer == (Object)null) { return; } List itemInBagList = customer.m_ItemInBagList; for (int num = ((itemInBagList == null) ? (-1) : (itemInBagList.Count - 1)); num >= 0; num--) { Item obj = itemInBagList[num]; if (obj != null) { obj.DisableItem(); } } itemInBagList?.Clear(); List cardInBagList = customer.m_CardInBagList; for (int num2 = ((cardInBagList == null) ? (-1) : (cardInBagList.Count - 1)); num2 >= 0; num2--) { InteractableCard3d obj2 = cardInBagList[num2]; if (obj2 != null) { ((InteractableObject)obj2).OnDestroyed(); } } cardInBagList?.Clear(); } internal static void UnscanItem(InteractableCashierCounter counter, Customer customer, Item item) { if (!((Object)(object)item == (Object)null) && !((Object)(object)customer == (Object)null) && !((Object)(object)item.m_InteractableScanItem == (Object)null)) { ((InteractableObject)item.m_InteractableScanItem).StopLerpToTransform(); ((Component)item).gameObject.SetActive(true); ((Collider)item.m_Collider).enabled = true; Rigidbody val = EnsureItemRigidbody(item); if ((Object)(object)val != (Object)null) { val.isKinematic = false; } ((Behaviour)item.m_InteractableScanItem).enabled = true; item.m_InteractableScanItem.RegisterScanItem(customer, ((Object)(object)counter != (Object)null) ? counter.m_ScannedItemLerpPos : null); } } internal static void UnscanCard(InteractableCashierCounter counter, Customer customer, InteractableCard3d card) { if (!((Object)(object)card == (Object)null) && !((Object)(object)customer == (Object)null)) { ((InteractableObject)card).StopLerpToTransform(); ((Component)card).gameObject.SetActive(true); ((Collider)card.m_Collider).enabled = true; Rigidbody val = EnsureCardRigidbody(card); if ((Object)(object)val != (Object)null) { val.isKinematic = false; } card.RegisterScanCard(customer, ((Object)(object)counter != (Object)null) ? counter.m_ScannedItemLerpPos : null); } } internal static Item SpawnItem(InteractableCashierCounter counter, Customer customer, EItemType type, float price, int slot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) ItemMeshData itemMeshData = InventoryBase.GetItemMeshData(type); if (itemMeshData == null || (Object)(object)counter?.m_CustomerPlaceItemPos == (Object)null) { return null; } Item item = ItemSpawnManager.GetItem(((Component)counter).transform); item.SetMesh(itemMeshData.mesh, itemMeshData.material, type, itemMeshData.meshSecondary, itemMeshData.materialSecondary, itemMeshData.materialList); item.SetCurrentPrice(price); ((Component)item).transform.parent = ((Component)counter).transform; ((Component)item).transform.position = counter.m_CustomerPlaceItemPos.position; Transform transform = ((Component)item).transform; transform.position += counter.m_CustomerPlaceItemPos.forward * (-0.025f * (float)(slot % 8)); Transform transform2 = ((Component)item).transform; transform2.position += counter.m_CustomerPlaceItemPos.right * (0.1f * (float)Mathf.Clamp(slot / 8, 0, 1)); Transform transform3 = ((Component)item).transform; transform3.position += Vector3.up * (0.2f * (float)Mathf.Clamp(slot / 16, 0, 2)); ((Component)item).transform.rotation = counter.m_CustomerPlaceItemPos.rotation; ((Component)item).transform.Rotate(new Vector3((float)Random.Range(-30, -5), (float)Random.Range(-5, 5), (float)Random.Range(-5, 5))); ((Renderer)item.m_Mesh).enabled = true; ((Component)item).gameObject.SetActive(true); ((Collider)item.m_Collider).enabled = true; Rigidbody val = EnsureItemRigidbody(item); if ((Object)(object)val != (Object)null) { val.isKinematic = false; } ((Behaviour)item.m_InteractableScanItem).enabled = true; item.m_InteractableScanItem.RegisterScanItem(customer, counter.m_ScannedItemLerpPos); customer.m_ItemInBagList.Add(item); return item; } internal static InteractableCard3d SpawnCard(InteractableCashierCounter counter, Customer customer, CardData data, float price, int slot) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) if (data == null || (Object)(object)counter?.m_CustomerPlaceItemPos == (Object)null) { return null; } Card3dUISpawner obj = SceneRef.Get(); Card3dUIGroup val = ((obj != null) ? obj.GetCardUI() : null); InteractableObject val2 = ShelfManager.SpawnInteractableObject((EObjectType)9); InteractableCard3d val3 = ((val2 != null) ? ((Component)val2).GetComponent() : null); if ((Object)(object)val == (Object)null || (Object)(object)val3 == (Object)null) { if (val != null) { val.DisableCard(); } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } return null; } val.m_CardUI.SetCardUI(data); val3.SetCardUIFollow(val); val3.SetEnableCollision(false); val3.SetCurrentPrice(price); Rigidbody val4 = EnsureCardRigidbody(val3); if ((Object)(object)val4 == (Object)null) { val.DisableCard(); Object.Destroy((Object)(object)val2); return null; } ((Component)val3).transform.parent = ((Component)counter).transform; ((Component)val3).transform.position = counter.m_CustomerPlaceItemPos.position; ((Component)val3).transform.localScale = Vector3.one; Transform transform = ((Component)val3).transform; transform.position += counter.m_CustomerPlaceItemPos.forward * (-0.035f * (float)(slot % 8)); Transform transform2 = ((Component)val3).transform; transform2.position += counter.m_CustomerPlaceItemPos.right * (0.1f * (float)Mathf.Clamp(slot / 8, 0, 1)); Transform transform3 = ((Component)val3).transform; transform3.position += Vector3.up * (0.3f * (float)Mathf.Clamp(slot / 16, 0, 2)); ((Component)val3).transform.rotation = counter.m_CustomerPlaceItemPos.rotation; ((Component)val3).transform.Rotate(new Vector3((float)(Random.Range(-30, -5) + 180), (float)Random.Range(-5, 5), (float)Random.Range(-5, 5))); ((Component)val3.m_Card3dUI).gameObject.SetActive(true); ((Component)val3).gameObject.SetActive(true); ((Collider)val3.m_Collider).enabled = true; val4.isKinematic = false; val3.RegisterScanCard(customer, counter.m_ScannedItemLerpPos); customer.m_CardInBagList.Add(val3); return val3; } internal static Rigidbody EnsureCardRigidbody(InteractableCard3d card) { if ((Object)(object)card == (Object)null) { return null; } if ((Object)(object)card.m_Rigidbody != (Object)null) { return card.m_Rigidbody; } if (!_cardRigidbodyMethodResolved) { _getOrCreateCardRigidbody = ReflectionSurface.OptionalMethod(typeof(InteractableCard3d), "GetOrCreateRigidbody"); _cardRigidbodyMethodResolved = true; } ref Rigidbody rigidbody = ref card.m_Rigidbody; object? obj = _getOrCreateCardRigidbody?.Invoke(card, null); return rigidbody = (Rigidbody)(((obj is Rigidbody) ? obj : null) ?? ((Component)card).GetComponent()); } internal static Rigidbody EnsureItemRigidbody(Item item) { if ((Object)(object)item == (Object)null) { return null; } if ((Object)(object)item.m_Rigidbody != (Object)null) { return item.m_Rigidbody; } if (!_itemRigidbodyMethodResolved) { _getOrCreateItemRigidbody = ReflectionSurface.OptionalMethod(typeof(Item), "GetOrCreateRigidbody"); _itemRigidbodyMethodResolved = true; } ref Rigidbody rigidbody = ref item.m_Rigidbody; object? obj = _getOrCreateItemRigidbody?.Invoke(item, null); return rigidbody = (Rigidbody)(((obj is Rigidbody) ? obj : null) ?? ((Component)item).GetComponent()); } } [NetworkMessage] public sealed class RegisterIntentMessage : IPredictedMessage, INetMessage { public int CounterKey; public RegisterIntentKind Kind; public int Slot = -1; public bool IsCard; public bool IsCoin; public bool TakeBack; public double Value; public Guid PredictionId { get; set; } } public enum RegisterIntentKind : byte { Claim = 1, Release, ScanItem, ScanCard, TakePayment, CardPayment, Change, Complete } public enum RegisterDeltaKind : byte { Ownership = 1, CustomerLifecycle, Scan, PhasePayment, Change, CounterLifecycle, PaidAmount } [NetworkMessage] public sealed class RegisterBaselineMessage : INetMessage { public List Counters = new List(); } [NetworkMessage] public sealed class RegisterDeltaMessage : IPredictedMessage, INetMessage { public RegisterDeltaKind Kind; public int CounterKey; public uint CounterGeneration; public bool Exists; public int Owner; public bool HasCustomer; public int CustomerIndex = -1; public int CustomerGeneration; public bool CustomerFemale; public string CharacterName; public List Lines = new List(); public byte State; public bool UsingCard; public double Paid; public double Total; public double CustomerTotal; public double Change; public bool ChangeReady; public bool ChangeStarted; public bool TooMuchChange; public int Slot = -1; public bool IsCard; public bool IsCoin; public bool TakeBack; public double Value; public int ChangeCount; public Guid PredictionId { get; set; } } public sealed class RegisterBaselineCounter { public int CounterKey; public uint CounterGeneration; public bool Exists; public int Owner; public bool HasCustomer; public int CustomerIndex = -1; public int CustomerGeneration; public bool CustomerFemale; public string CharacterName; public byte State; public bool UsingCard; public double Paid; public double Total; public double CustomerTotal; public double Change; public bool ChangeReady; public bool ChangeStarted; public bool TooMuchChange; public List Lines = new List(); public List ChangeItems = new List(); } public sealed class RegisterLine { public bool IsCard; public EItemType ItemType; public CardData Card; public float Price; public bool Scanned; } public sealed class RegisterChange { public int Slot; public bool IsCoin; public double Value; public int Count; } } namespace CardShopCoop.Modules.Purchasing { [ClientBehaviour] public sealed class PurchasingClientBehaviour : CoopBehaviour { private enum PendingSurface : byte { Restock, Scanner, Furniture, ProductLicense, ScannerLicense } private sealed class PendingRequest { internal PurchaseIntentMessage Message; internal PendingSurface Surface; internal RestockItemScreen RestockScreen; internal ScannerRestockScreen ScannerScreen; internal FurnitureShopConfirmPurchaseScreen FurnitureScreen; internal FurnitureShopUIScreen FurnitureOwner; internal int LocalIndex = -1; internal Dictionary RestockQuantities; internal List> ScannerQuantities; internal Dictionary RestockBefore; internal List> ScannerBefore; internal List BoxIds = new List(); internal int StartDeliveries; internal int AppendedDeliveries; internal float SupplyCostBefore; internal float UpgradeCostBefore; internal double Spent; } [HarmonyPatch(typeof(RestockItemCheckoutScreen), "OnPressConfirmCheckout")] private static class RestockCheckoutPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(RestockItemCheckoutScreen __instance, out PendingRequest __state) { __state = _active?.CaptureRestockCheckout(__instance); EnterChargeScope(__state); } [HarmonyPostfix] private static void Postfix(PendingRequest __state) { _active?.Observe(__state); } [HarmonyFinalizer] private static void Finalizer(PendingRequest __state) { ReleaseChargeScope(__state); } } [HarmonyPatch(typeof(ScannerRestockScreen), "OnPressCheckoutButton")] private static class ScannerCheckoutPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ScannerRestockScreen __instance, out PendingRequest __state) { __state = _active?.CaptureScanner(__instance); EnterChargeScope(__state); } [HarmonyPostfix] private static void Postfix(PendingRequest __state) { _active?.Observe(__state); } [HarmonyFinalizer] private static void Finalizer(PendingRequest __state) { ReleaseChargeScope(__state); } } [HarmonyPatch(typeof(FurnitureShopUIScreen), "EvaluateCartCheckout")] private static class FurnitureCheckoutPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(FurnitureShopUIScreen __instance, int index, out PendingRequest __state) { __state = _active?.CaptureFurniture(__instance, index); EnterChargeScope(__state); } [HarmonyPostfix] private static void Postfix(PendingRequest __state) { _active?.Observe(__state); } [HarmonyFinalizer] private static void Finalizer(PendingRequest __state) { ReleaseChargeScope(__state); } } [HarmonyPatch(typeof(RestockItemPanelUI), "OnPressPurchaseButton")] private static class ProductLicensePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(RestockItemPanelUI __instance, out PendingRequest __state) { __state = _active?.CaptureProductLicense(__instance); EnterChargeScope(__state); } [HarmonyPostfix] private static void Postfix(PendingRequest __state) { _active?.Observe(__state); } [HarmonyFinalizer] private static void Finalizer(PendingRequest __state) { ReleaseChargeScope(__state); } } [HarmonyPatch(typeof(ScannerRestockScreen), "OnPressUnlockButton")] private static class ScannerLicensePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(ScannerRestockScreen __instance, out PendingRequest __state) { __state = _active?.CaptureScannerLicense(__instance); EnterChargeScope(__state); } [HarmonyPostfix] private static void Postfix(PendingRequest __state) { _active?.Observe(__state); } [HarmonyFinalizer] private static void Finalizer(PendingRequest __state) { ReleaseChargeScope(__state); } } private static PurchasingClientBehaviour _active; private readonly Dictionary _pendingRequests = new Dictionary(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private void OnEnable() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; PredictionApi.PredictionRetired += RetirePendingRequest; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.purchasing.client"); Patch(typeof(RestockCheckoutPatch)); Patch(typeof(ScannerCheckoutPatch)); Patch(typeof(FurnitureCheckoutPatch)); Patch(typeof(ProductLicensePatch)); Patch(typeof(ScannerLicensePatch)); } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } PredictionApi.PredictionRetired -= RetirePendingRequest; if (_active == this) { _active = null; } _context = null; throw; } } [MessageHandler(typeof(PurchaseOutcomeMessage))] private void HandleOutcome(MessageContext context, PurchaseOutcomeMessage outcome) { if (!_shutdown && outcome.Success && _pendingRequests.TryGetValue(outcome.PredictionId, out var pending)) { _pendingRequests.Remove(outcome.PredictionId); PredictionApi.Confirm(outcome.PredictionId, delegate { ApplyAccepted(pending); }); ShowStatus(outcome.Text); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; PredictionApi.PredictionRetired -= RetirePendingRequest; _pendingRequests.Clear(); _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } private void RetirePendingRequest(Guid predictionId) { _pendingRequests.Remove(predictionId); } private void Patch(Type type) { _harmony.CreateClassProcessor(type).Patch(); } private static void EnterChargeScope(PendingRequest state) { if (state != null) { EconomyActionScope.Enter(); } } private static void ReleaseChargeScope(PendingRequest state) { if (state != null) { EconomyActionScope.Exit(); } } private PendingRequest CaptureRestockCheckout(RestockItemCheckoutScreen checkout) { if (!CanCapture("restock checkout")) { return null; } RestockItemScreen val = PurchasingInterop.RestockScreen(checkout); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("Purchasing could not read the restock checkout screen."); } Dictionary dictionary = PurchasingInterop.RestockCart(val); List list = new List(); if (dictionary != null) { foreach (KeyValuePair item in dictionary) { list.Add(PurchasingInterop.RestockLine(item.Key, item.Value)); } } return BuildPending(new PurchaseIntentMessage { Kind = PurchaseKind.Restock, ScannerCheckout = false, Lines = list }, PendingSurface.Restock, val, null, null, null, -1); } private PendingRequest CaptureScanner(ScannerRestockScreen screen) { if (!CanCapture("scanner checkout")) { return null; } List list = PurchasingInterop.ScannerIndexes(screen); List list2 = PurchasingInterop.ScannerCounts(screen); List list3 = new List(); int num = Math.Max(list?.Count ?? 0, list2?.Count ?? 0); for (int i = 0; i < num; i++) { int index = ((list != null && i < list.Count) ? list[i] : (-1)); int count = ((list2 != null && i < list2.Count) ? list2[i] : (-1)); list3.Add(PurchasingInterop.RestockLine(index, count)); } return BuildPending(new PurchaseIntentMessage { Kind = PurchaseKind.Restock, ScannerCheckout = true, Lines = list3 }, PendingSurface.Scanner, null, screen, null, null, -1); } private PendingRequest CaptureFurniture(FurnitureShopUIScreen owner, int index) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected I4, but got Unknown if (!CanCapture("furniture checkout")) { return null; } FurniturePurchaseData val = ((index >= 0) ? InventoryBase.GetFurniturePurchaseData(index) : null); if (val == null) { return null; } FurnitureShopConfirmPurchaseScreen val2 = PurchasingInterop.OpenFurnitureConfirmation(owner, index); if ((Object)(object)val2 == (Object)null) { CoopPlugin.Log.LogInfo((object)("Purchasing captured a furniture checkout that bypassed the confirmation screen (index " + index + ", type " + (int)val.objectType + ").")); } return BuildPending(new PurchaseIntentMessage { Kind = PurchaseKind.Furniture, Lines = new List { new PurchaseLine { ObjectType = val.objectType, Count = 1 } } }, PendingSurface.Furniture, null, null, val2, owner, index); } private PendingRequest CaptureProductLicense(RestockItemPanelUI panel) { if (!CanCapture("product license checkout")) { return null; } int num = PurchasingInterop.PanelIndex(panel); PurchaseLine item = PurchasingInterop.RestockLine(num, 1); return BuildPending(new PurchaseIntentMessage { Kind = PurchaseKind.ProductLicense, Lines = new List { item } }, PendingSurface.ProductLicense, null, null, null, null, num); } private PendingRequest CaptureScannerLicense(ScannerRestockScreen screen) { if (!CanCapture("scanner unlock")) { return null; } return BuildPending(new PurchaseIntentMessage { Kind = PurchaseKind.ScannerLicense, Lines = new List { new PurchaseLine { Count = 1 } } }, PendingSurface.ScannerLicense, null, screen, null, null, -1); } private PendingRequest BuildPending(PurchaseIntentMessage message, PendingSurface surface, RestockItemScreen restock, ScannerRestockScreen scanner, FurnitureShopConfirmPurchaseScreen furniture, FurnitureShopUIScreen owner, int localIndex) { PendingRequest pendingRequest = new PendingRequest { Message = message, Surface = surface, RestockScreen = restock, ScannerScreen = scanner, FurnitureScreen = furniture, FurnitureOwner = owner, LocalIndex = localIndex }; if (surface == PendingSurface.Restock && (Object)(object)restock != (Object)null) { Dictionary dictionary = PurchasingInterop.RestockCart(restock); pendingRequest.RestockBefore = ((dictionary == null) ? null : new Dictionary(dictionary)); pendingRequest.RestockQuantities = ((dictionary == null) ? null : new Dictionary(dictionary)); } else if (surface == PendingSurface.Scanner && (Object)(object)scanner != (Object)null) { List list = PurchasingInterop.ScannerIndexes(scanner); List list2 = PurchasingInterop.ScannerCounts(scanner); pendingRequest.ScannerBefore = new List>(); pendingRequest.ScannerQuantities = new List>(); int num = Math.Max(list?.Count ?? 0, list2?.Count ?? 0); for (int i = 0; i < num; i++) { KeyValuePair item = new KeyValuePair((list != null && i < list.Count) ? list[i] : (-1), (list2 != null && i < list2.Count) ? list2[i] : (-1)); pendingRequest.ScannerBefore.Add(item); pendingRequest.ScannerQuantities.Add(item); } } pendingRequest.SupplyCostBefore = CPlayerData.m_GameReportDataCollectPermanent.supplyCost; pendingRequest.UpgradeCostBefore = CPlayerData.m_GameReportDataCollectPermanent.upgradeCost; if (surface == PendingSurface.Restock || surface == PendingSurface.Scanner || surface == PendingSurface.Furniture) { pendingRequest.StartDeliveries = WorldClientBehaviour.PendingClientDeliveryCount(); for (int j = 0; j < message.Lines.Count; j++) { PurchaseLine purchaseLine = message.Lines[j]; int num2 = ((surface == PendingSurface.Furniture) ? 1 : Math.Max(purchaseLine.Count, 0)); for (int k = 0; k < num2; k++) { Guid item2 = Guid.NewGuid(); purchaseLine.BoxNetworkIds.Add(item2); pendingRequest.BoxIds.Add(item2); } } if (surface == PendingSurface.Furniture) { if (pendingRequest.BoxIds.Count > 0) { WorldClientBehaviour.PushClientCreatedId(pendingRequest.BoxIds[0]); } } else { WorldClientBehaviour.PushClientDeliveryIds(pendingRequest.BoxIds); } } return pendingRequest; } private void Observe(PendingRequest pending) { if (pending == null) { return; } CoopRuntimeContext context = _context; if (context == null || !context.InGame()) { return; } pending.AppendedDeliveries = Math.Max(WorldClientBehaviour.PendingClientDeliveryCount() - pending.StartDeliveries, 0); if (pending.Surface == PendingSurface.Furniture || pending.AppendedDeliveries == 0) { WorldClientBehaviour.ReleaseUnspawnedClientIds(pending.BoxIds); } pending.Spent = pending.SupplyCostBefore - CPlayerData.m_GameReportDataCollectPermanent.supplyCost + (pending.UpgradeCostBefore - CPlayerData.m_GameReportDataCollectPermanent.upgradeCost); if (pending.Spent <= 0.0001) { WorldClientBehaviour.ReleaseUnspawnedClientIds(pending.BoxIds); return; } PurchaseIntentMessage message = pending.Message; PredictionApi.Predict("purchasing", delegate(Guid predictionId) { message.PredictionId = predictionId; _pendingRequests[predictionId] = pending; try { _context.Send(1, (INetMessage)(object)message); } catch { _pendingRequests.Remove(predictionId); WorldClientBehaviour.RollbackClientCreated(pending.BoxIds); WorldClientBehaviour.CancelPendingClientDeliveries(pending.AppendedDeliveries); throw; } }, delegate { ReplayAccepted(pending); }, delegate { Undo(pending); }); } private static void ReplayAccepted(PendingRequest pending) { ApplyAccepted(pending); Charge(pending.Spent); } private bool CanCapture(string action) { CoopRuntimeContext context = _context; if (context != null && context.InGame()) { return true; } CoopPlugin.Log.LogWarning((object)("Purchasing skipped " + action + ": the shop is not ready")); return false; } private static void ApplyAccepted(PendingRequest pending) { switch (pending.Surface) { case PendingSurface.Restock: if ((Object)(object)pending.RestockScreen != (Object)null) { PurchasingInterop.RemoveRestockQuantities(pending.RestockScreen, pending.RestockQuantities); } break; case PendingSurface.Scanner: if ((Object)(object)pending.ScannerScreen != (Object)null) { PurchasingInterop.RemoveScannerQuantities(pending.ScannerScreen, pending.ScannerQuantities); } break; case PendingSurface.Furniture: if (PurchasingInterop.IsSameFurniturePurchase(pending.FurnitureScreen, pending.FurnitureOwner, pending.LocalIndex)) { PurchasingInterop.CloseFurnitureConfirmation(pending.FurnitureScreen); } break; case PendingSurface.ProductLicense: CatalogApi.ApplyClientProductLicense(pending.LocalIndex, unlocked: true); break; case PendingSurface.ScannerLicense: CatalogApi.ApplyClientScannerLicense(unlocked: true); break; } } private static void Undo(PendingRequest pending) { switch (pending.Surface) { case PendingSurface.Restock: if ((Object)(object)pending.RestockScreen != (Object)null) { PurchasingInterop.RestoreRestockCart(pending.RestockScreen, pending.RestockBefore); } break; case PendingSurface.Scanner: if ((Object)(object)pending.ScannerScreen != (Object)null) { PurchasingInterop.RestoreScannerCart(pending.ScannerScreen, pending.ScannerBefore); } break; case PendingSurface.Furniture: if ((Object)(object)pending.FurnitureScreen != (Object)null) { PurchasingInterop.ReopenFurnitureConfirmation(pending.FurnitureOwner, pending.LocalIndex); } break; case PendingSurface.ProductLicense: CatalogApi.ApplyClientProductLicense(pending.LocalIndex, unlocked: false); break; case PendingSurface.ScannerLicense: CatalogApi.ApplyClientScannerLicense(unlocked: false); break; } Refund(pending.Spent); WorldClientBehaviour.RollbackClientCreated(pending.BoxIds); WorldClientBehaviour.CancelPendingClientDeliveries(pending.AppendedDeliveries); } private static void Charge(double amount) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (amount > 0.0001) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin((float)amount, false)); } } private static void Refund(double amount) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown if (amount > 0.0001) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin((float)amount, true)); } } private void ShowStatus(string text) { _context?.SetStatusLine?.Invoke(text, 8f); } } [ServerBehaviour] public sealed class PurchasingHostBehaviour : CoopBehaviour { private sealed class PlannedLine { internal PurchaseLine Request; internal int RestockIndex = -1; internal RestockData Restock; internal FurniturePurchaseData Furniture; internal double Price; } private sealed class PurchasePlan { internal PurchaseKind Kind; internal bool IdempotentNoOp; internal readonly List Lines = new List(); internal double Total; internal int TotalBoxes; } private sealed class DeliveryResult { internal double Charged; internal int RestockBoxes; internal int RestockItems; internal bool Success; internal string Failure; } private sealed class MutationSnapshot { private sealed class TransactionState { internal TransactionData Reference; internal int Day; internal int HourMinute; internal int Index; internal int Amount; internal ETransactionType TransactionType; internal float MoneyChangeAmount; internal CardData CardData; } internal readonly HashSet Objects = new HashSet(); internal readonly List AverageCosts = new List(); internal readonly List CurrentItemCounts = new List(); private readonly List Transactions = new List(); internal readonly List PastReports = new List(); internal readonly List Achievements = new List(); internal readonly List TutorialData = new List(); internal readonly List PendingRestockRows = new List(); internal readonly List PendingRestockIndexes = new List(); internal readonly List PendingRestockCounts = new List(); internal readonly GameReportDataCollect Report; internal readonly GameReportDataCollect PermanentReport; internal readonly int TutorialIndex; internal readonly bool TutorialWasFinished; internal readonly bool HasTutorialManager; internal readonly TutorialManager TutorialManagerInstance; internal readonly int ShopExpPoint; internal readonly int ShopLevel; internal readonly bool GameInstanceLicenseUnlocked; internal readonly CatalogApi.PurchaseLicenseState CatalogLicenses; internal readonly bool ScannerUnlocked; internal readonly int EventCount; internal readonly BoxNetworkInteraction.DeliveryQueueState DeliveryQueue; internal MutationSnapshot(PurchasePlan plan) { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) Capture((IList)RestockManager.GetItemPackagingBoxList()); Capture((IList)RestockManager.GetCardPackagingBoxList()); Capture((IList)RestockManager.GetShelfPackagingBoxList()); if (CPlayerData.m_AverageItemCostList != null) { AverageCosts.AddRange(CPlayerData.m_AverageItemCostList); } if (CPlayerData.m_CurrentTotalItemCountList != null) { CurrentItemCounts.AddRange(CPlayerData.m_CurrentTotalItemCountList); } CaptureTransactions(); Report = CPlayerData.m_GameReportDataCollect; PermanentReport = CPlayerData.m_GameReportDataCollectPermanent; if (CPlayerData.m_GameReportDataCollectPastList != null) { PastReports.AddRange(CPlayerData.m_GameReportDataCollectPastList); } if (CPlayerData.m_IsAchievementUnlocked != null) { Achievements.AddRange(CPlayerData.m_IsAchievementUnlocked); } CaptureTutorialData(); TutorialIndex = CPlayerData.m_TutorialIndex; TutorialManager val = (TutorialManagerInstance = SceneRef.Get()); HasTutorialManager = (Object)(object)val != (Object)null && TutorialFinished != null; TutorialWasFinished = HasTutorialManager && (bool)TutorialFinished.GetValue(val); ShopExpPoint = CPlayerData.m_ShopExpPoint; ShopLevel = CPlayerData.m_ShopLevel; GameInstanceLicenseUnlocked = GameInstance.m_IsItemLicenseUnlocked; CatalogApi.PurchaseLicenseState state = null; if (plan.Kind == PurchaseKind.ProductLicense && !CatalogApi.TryCapturePurchaseState(out state)) { throw new InvalidOperationException("catalog license state could not be snapshotted"); } CatalogLicenses = state; RestockManager val2 = SceneRef.Get(); if ((Object)(object)val2 != (Object)null) { CopyList(PendingRestockBoxes?.GetValue(val2) as IList, PendingRestockRows); } if (CPlayerData.m_SpawnBoxRestockIndexWaitingList != null) { PendingRestockIndexes.AddRange(CPlayerData.m_SpawnBoxRestockIndexWaitingList); } if (CPlayerData.m_SpawnBoxItemCountWaitingList != null) { PendingRestockCounts.AddRange(CPlayerData.m_SpawnBoxItemCountWaitingList); } ScannerUnlocked = CPlayerData.m_IsScannerRestockUnlocked; EventCount = QueueCount(); DeliveryQueue = WorldHostBehaviour.CaptureDeliveryQueueState(); } internal void Rollback() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) DestroyNew((IList)RestockManager.GetItemPackagingBoxList()); DestroyNew((IList)RestockManager.GetCardPackagingBoxList()); DestroyNew((IList)RestockManager.GetShelfPackagingBoxList()); RestoreList(CPlayerData.m_AverageItemCostList, AverageCosts); RestoreList(CPlayerData.m_CurrentTotalItemCountList, CurrentItemCounts); CPlayerData.m_GameReportDataCollect = Report; CPlayerData.m_GameReportDataCollectPermanent = PermanentReport; RestoreList(CPlayerData.m_GameReportDataCollectPastList, PastReports); RestoreList(CPlayerData.m_IsAchievementUnlocked, Achievements); RestoreTutorialData(); CPlayerData.m_TutorialIndex = TutorialIndex; if (HasTutorialManager) { TutorialFinished.SetValue(TutorialManagerInstance, TutorialWasFinished); } CPlayerData.m_ShopExpPoint = ShopExpPoint; CPlayerData.m_ShopLevel = ShopLevel; RestoreTransactions(); RestoreList(PendingRestockBoxes, SceneRef.Get(), PendingRestockRows); RestoreList(CPlayerData.m_SpawnBoxRestockIndexWaitingList, PendingRestockIndexes); RestoreList(CPlayerData.m_SpawnBoxItemCountWaitingList, PendingRestockCounts); WorldHostBehaviour.RestoreDeliveryQueueState(DeliveryQueue); TrimEvents(EventCount); GameInstance.m_IsItemLicenseUnlocked = GameInstanceLicenseUnlocked; if (CatalogLicenses != null && !CatalogApi.HostRestorePurchaseState(CatalogLicenses)) { CoopPlugin.Log.LogError((object)"Purchasing could not restore the catalog license state"); } if (CPlayerData.m_IsScannerRestockUnlocked != ScannerUnlocked && !CatalogApi.HostRestoreScannerLicense(ScannerUnlocked)) { CoopPlugin.Log.LogError((object)"Purchasing could not restore scanner entitlement state"); } } private void Capture(IList objects) where T : Object { if (objects == null) { return; } for (int i = 0; i < objects.Count; i++) { if ((Object)(object)objects[i] != (Object)null) { Objects.Add((Object)(object)objects[i]); } } } private void DestroyNew(IList objects) where T : Object { if (objects == null) { return; } for (int num = objects.Count - 1; num >= 0; num--) { if ((Object)(object)objects[num] != (Object)null && !Objects.Contains((Object)(object)objects[num])) { object obj = objects[num]; InteractablePackagingBox val = (InteractablePackagingBox)((obj is InteractablePackagingBox) ? obj : null); if (val != null) { ((InteractableObject)val).OnDestroyed(); } else { Object.Destroy((Object)(object)objects[num]); } } } } private void CaptureTransactions() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) List transactionDataList = CPlayerData.m_TransactionDataList; if (transactionDataList != null) { for (int i = 0; i < transactionDataList.Count; i++) { TransactionData val = transactionDataList[i]; Transactions.Add((val == null) ? null : new TransactionState { Reference = val, Day = val.day, HourMinute = val.hourMinute, Index = val.index, Amount = val.amount, TransactionType = val.transactionType, MoneyChangeAmount = val.moneyChangeAmount, CardData = val.cardData }); } } } private void CaptureTutorialData() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (CPlayerData.m_TutorialDataList != null) { for (int i = 0; i < CPlayerData.m_TutorialDataList.Count; i++) { TutorialData val = CPlayerData.m_TutorialDataList[i]; TutorialData.Add((val == null) ? ((TutorialData)null) : new TutorialData { tutorialTaskCondition = val.tutorialTaskCondition, value = val.value }); } } } private void RestoreTransactions() { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) List list = CPlayerData.m_TransactionDataList; if (list == null) { list = (CPlayerData.m_TransactionDataList = new List()); } list.Clear(); for (int i = 0; i < Transactions.Count; i++) { TransactionState transactionState = Transactions[i]; if (transactionState == null) { list.Add(null); continue; } transactionState.Reference.day = transactionState.Day; transactionState.Reference.hourMinute = transactionState.HourMinute; transactionState.Reference.index = transactionState.Index; transactionState.Reference.amount = transactionState.Amount; transactionState.Reference.transactionType = transactionState.TransactionType; transactionState.Reference.moneyChangeAmount = transactionState.MoneyChangeAmount; transactionState.Reference.cardData = transactionState.CardData; list.Add(transactionState.Reference); } } private void RestoreTutorialData() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (CPlayerData.m_TutorialDataList == null) { CPlayerData.m_TutorialDataList = new List(); } CPlayerData.m_TutorialDataList.Clear(); for (int i = 0; i < TutorialData.Count; i++) { TutorialData val = TutorialData[i]; CPlayerData.m_TutorialDataList.Add((val == null) ? ((TutorialData)null) : new TutorialData { tutorialTaskCondition = val.tutorialTaskCondition, value = val.value }); } } private static void CopyList(IList source, List target) { if (source != null && target != null) { for (int i = 0; i < source.Count; i++) { target.Add((T)source[i]); } } } private static void RestoreList(List target, List source) { if (target != null && source != null) { target.Clear(); target.AddRange(source); } } private static void RestoreList(FieldInfo field, object instance, List source) { if (field?.GetValue(instance) is IList list && source != null) { list.Clear(); for (int i = 0; i < source.Count; i++) { list.Add(source[i]); } } } private static int QueueCount() { return (EventQueue?.GetValue(SceneRef.Get()) as Queue)?.Count ?? 0; } private static void TrimEvents(int count) { if (!(EventQueue?.GetValue(SceneRef.Get()) is Queue queue) || queue.Count <= count) { return; } List list = new List(count); while (queue.Count > 0) { object item = queue.Dequeue(); if (list.Count < count) { list.Add(item); } } for (int i = 0; i < list.Count; i++) { queue.Enqueue(list[i]); } } } private const int MaxLines = 64; private const int MaxBoxesPerLine = 100; private const int MaxTotalBoxes = 1000; private const float ScannerUnlockPrice = 12000f; private static readonly FieldInfo PendingRestockBoxes = AccessTools.Field(typeof(RestockManager), "m_SpawnBoxItemWaitingList"); private static readonly FieldInfo PendingRestockIndexes = AccessTools.Field(typeof(CPlayerData), "m_SpawnBoxRestockIndexWaitingList"); private static readonly FieldInfo PendingRestockCounts = AccessTools.Field(typeof(CPlayerData), "m_SpawnBoxItemCountWaitingList"); private static readonly FieldInfo EventQueue = AccessTools.Field(typeof(CEventManager), "m_queueEvent"); private static readonly FieldInfo TutorialFinished = AccessTools.Field(typeof(TutorialManager), "m_FinishedTutorial"); private static PurchasingHostBehaviour _active; private bool _shutdown; private CoopRuntimeContext _context; private void OnEnable() { if (_shutdown || _context != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Purchasing host initialization failed: " + ex)); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } [MessageHandler(typeof(PurchaseIntentMessage))] private void HandleIntent(MessageContext context, PurchaseIntentMessage message) { if (!_shutdown && _context.InGame() && context?.Connection != null && message != null) { int id = context.Connection.Id; PurchaseOutcomeMessage purchaseOutcomeMessage = Process(message, id, PeerLabel(id)); if (purchaseOutcomeMessage.Success) { SendOutcome(id, purchaseOutcomeMessage); } else if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, id, message.PredictionId); } } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } private PurchaseOutcomeMessage Process(PurchaseIntentMessage message, int connectionId, string peer) { if (!Enum.IsDefined(typeof(PurchaseKind), message.Kind)) { return Failure(message, peer, "purchase kind is invalid"); } EconomyAuthority.HostSpendReservation reservation = null; MutationSnapshot mutationSnapshot = null; PurchasePlan plan = null; double committedDebit = 0.0; try { if (!TryBuildPlan(message, out plan, out var error)) { return Failure(message, peer, error); } if (plan.IdempotentNoOp) { return Accepted(message, 0.0); } if (!EconomyAuthority.TryReserveHostSpend(plan.Total, out reservation)) { return Failure(message, peer, "not enough money - the purchase was cancelled"); } if (!EconomyAuthority.QueueHostSpend(reservation)) { return Failure(message, peer, "purchase failed while admitting the debit"); } reservation = null; committedDebit = plan.Total; mutationSnapshot = new MutationSnapshot(plan); DeliveryResult deliveryResult; try { deliveryResult = Deliver(plan, peer); } catch (Exception arg) { mutationSnapshot.Rollback(); RefundFailedPurchase(ref committedDebit); CoopPlugin.Log.LogError((object)$"Purchasing delivery crashed for {peer}: {arg}"); return Failure(message, peer, "purchase failed while delivering the order"); } if (!deliveryResult.Success) { mutationSnapshot.Rollback(); RefundFailedPurchase(ref committedDebit); return Failure(message, peer, deliveryResult.Failure ?? "purchase delivery failed"); } if (Math.Abs(deliveryResult.Charged - plan.Total) > 0.0001) { mutationSnapshot.Rollback(); RefundFailedPurchase(ref committedDebit); CoopPlugin.Log.LogError((object)("Purchasing canonical amount changed for " + peer + ": " + $"preflight={plan.Total}, delivered={deliveryResult.Charged}")); return Failure(message, peer, "purchase failed while charging the order"); } ApplyCommittedSideEffects(plan, deliveryResult); if (plan.Kind == PurchaseKind.ProductLicense || plan.Kind == PurchaseKind.ScannerLicense) { ReportApi.NotifyCommittedPurchaseMutation(); } if (plan.Kind == PurchaseKind.ProductLicense) { if (!CatalogApi.HostPublishProductLicense(plan.Lines[0].Restock, Guid.Empty)) { throw new InvalidOperationException("catalog license publication failed"); } CatalogApi.HostApplyProductEntitlementSideEffects(plan.Lines[0].Restock); } else if (plan.Kind == PurchaseKind.ScannerLicense && !CatalogApi.HostPublishScannerLicense(Guid.Empty)) { throw new InvalidOperationException("catalog scanner license publication failed"); } PurchaseOutcomeMessage result = Accepted(message, deliveryResult.Charged); committedDebit = 0.0; if (plan.Kind == PurchaseKind.Restock || plan.Kind == PurchaseKind.Furniture) { ((MonoBehaviour)this).StartCoroutine(SaveShelfDataAfterPurchase()); } return result; } catch (Exception arg2) { try { mutationSnapshot?.Rollback(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Purchasing rollback failed for " + peer + ": " + ex)); } if (plan != null) { RefundFailedPurchase(ref committedDebit); } CoopPlugin.Log.LogError((object)$"Purchasing preflight crashed for {peer}: {arg2}"); return Failure(message, peer, "purchase could not be validated"); } } private static void RefundFailedPurchase(ref double committedDebit) { if (committedDebit > 0.0) { EconomyAuthority.RefundHostSpend(committedDebit); committedDebit = 0.0; } } private bool TryBuildPlan(PurchaseIntentMessage message, out PurchasePlan plan, out string error) { plan = new PurchasePlan { Kind = message.Kind }; error = null; if (message.Lines == null || message.Lines.Count == 0 || message.Lines.Count > 64) { error = "purchase has an invalid line count"; return false; } if (message.Kind != PurchaseKind.Restock && message.ScannerCheckout) { error = "scanner cart flag is invalid for this purchase"; return false; } switch (message.Kind) { case PurchaseKind.Restock: return TryBuildRestockPlan(message, plan, out error); case PurchaseKind.Furniture: return TryBuildFurniturePlan(message, plan, out error); case PurchaseKind.ProductLicense: return TryBuildProductLicensePlan(message, plan, out error); case PurchaseKind.ScannerLicense: return TryBuildScannerLicensePlan(message, plan, out error); default: error = "purchase kind is invalid"; return false; } } private bool TryBuildRestockPlan(PurchaseIntentMessage message, PurchasePlan plan, out string error) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) error = null; if (message.ScannerCheckout && !CPlayerData.m_IsScannerRestockUnlocked) { error = "scanner restock is locked on the host"; return false; } int num = 0; for (int i = 0; i < message.Lines.Count; i++) { PurchaseLine purchaseLine = message.Lines[i]; if (!ValidProductLine(purchaseLine, 100)) { error = $"restock line {i} is invalid"; return false; } if (!CatalogApi.TryResolveProduct(purchaseLine.ItemType, purchaseLine.IsBigBox, purchaseLine.Name ?? "", out var index, out var data) || data == null) { error = $"restock line {i} is not in the host catalog"; return false; } if (!CatalogApi.TryGetLicense(index, out var unlocked) || !unlocked) { error = $"restock line {i} is not licensed on the host"; return false; } int maxItemCountInBox = RestockManager.GetMaxItemCountInBox(data.itemType, data.isBigBox); float itemCost = CPlayerData.GetItemCost(data.itemType); double num2 = (double)itemCost * (double)maxItemCountInBox * (double)purchaseLine.Count; if (maxItemCountInBox <= 0 || !IsFinite(itemCost) || itemCost < 0f || !IsFinite(num2)) { error = $"restock line {i} has an invalid host price"; return false; } num += purchaseLine.Count; if (num > 1000) { error = "restock order is too large"; return false; } plan.Lines.Add(new PlannedLine { Request = purchaseLine, RestockIndex = index, Restock = data, Price = num2 }); plan.Total += num2; } plan.TotalBoxes = num; plan.Total += ScannerDeliveryFee(num); return IsValidTotal(plan.Total, out error); } private static bool TryBuildFurniturePlan(PurchaseIntentMessage message, PurchasePlan plan, out string error) { //IL_003b: 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_0066: Unknown result type (might be due to invalid IL or missing references) error = null; if (message.Lines.Count != 1 || !ValidFurnitureLine(message.Lines[0])) { error = "furniture purchase must contain one valid item"; return false; } PurchaseLine purchaseLine = message.Lines[0]; FurniturePurchaseData furniturePurchaseData = InventoryBase.GetFurniturePurchaseData(purchaseLine.ObjectType); if (furniturePurchaseData == null || (Object)(object)InventoryBase.GetSpawnInteractableObjectPrefab(purchaseLine.ObjectType) == (Object)null) { error = "furniture is not available in the host catalog"; return false; } if (!PurchasingInterop.IsFurnitureAvailable(purchaseLine.ObjectType)) { error = "furniture is not available for this platform entitlement"; return false; } if (CPlayerData.m_ShopLevel + 1 < furniturePurchaseData.levelRequirement) { error = "shop level is too low for this furniture"; return false; } if (!IsFinite(furniturePurchaseData.price) || furniturePurchaseData.price <= 0f) { error = "furniture has an invalid host price"; return false; } plan.Lines.Add(new PlannedLine { Request = purchaseLine, Furniture = furniturePurchaseData, Price = furniturePurchaseData.price }); plan.Total = furniturePurchaseData.price; return IsValidTotal(plan.Total, out error); } private static bool TryBuildProductLicensePlan(PurchaseIntentMessage message, PurchasePlan plan, out string error) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) error = null; if (message.Lines.Count != 1 || !ValidProductLine(message.Lines[0], 1)) { error = "product license purchase must contain one valid item"; return false; } PurchaseLine purchaseLine = message.Lines[0]; if (!CatalogApi.TryResolveProduct(purchaseLine.ItemType, purchaseLine.IsBigBox, purchaseLine.Name ?? "", out var index, out var data) || data == null) { error = "product license is not in the host catalog"; return false; } if (!CatalogApi.TryGetLicense(index, out var unlocked)) { error = "product license state is unavailable on the host"; return false; } if (unlocked) { plan.IdempotentNoOp = true; plan.Lines.Add(new PlannedLine { Request = purchaseLine, RestockIndex = index, Restock = data, Price = 0.0 }); return true; } if (CPlayerData.m_ShopLevel + 1 < data.licenseShopLevelRequired) { error = "shop level is too low for this product license"; return false; } if (!IsFinite(data.licensePrice) || data.licensePrice < 0f) { error = "product license has an invalid host price"; return false; } plan.Lines.Add(new PlannedLine { Request = purchaseLine, RestockIndex = index, Restock = data, Price = data.licensePrice }); plan.Total = data.licensePrice; return IsValidTotal(plan.Total, out error); } private static bool TryBuildScannerLicensePlan(PurchaseIntentMessage message, PurchasePlan plan, out string error) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Invalid comparison between Unknown and I4 //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 error = null; if (message.Lines.Count != 1 || message.Lines[0] == null || message.Lines[0].Count != 1 || (int)message.Lines[0].ItemType != -1 || (int)message.Lines[0].ObjectType != -1 || message.Lines[0].IsBigBox || message.Lines[0].Name == null || message.Lines[0].Name.Length != 0) { error = "scanner license purchase must contain one valid item"; return false; } if (CPlayerData.m_IsScannerRestockUnlocked) { plan.IdempotentNoOp = true; plan.Lines.Add(new PlannedLine { Request = message.Lines[0], Price = 0.0 }); return true; } plan.Lines.Add(new PlannedLine { Request = message.Lines[0], Price = 12000.0 }); plan.Total = 12000.0; return true; } private DeliveryResult Deliver(PurchasePlan plan, string peer) { DeliveryResult result = new DeliveryResult { Success = true }; switch (plan.Kind) { case PurchaseKind.Restock: DeliverRestock(plan, result, peer); break; case PurchaseKind.Furniture: DeliverFurniture(plan, result, peer); break; case PurchaseKind.ProductLicense: DeliverProductLicense(plan, result, peer); break; case PurchaseKind.ScannerLicense: DeliverScannerLicense(plan, result, peer); break; } return result; } private void DeliverRestock(PurchasePlan plan, DeliveryResult result, string peer) { double num = 0.0; for (int i = 0; i < plan.Lines.Count; i++) { PlannedLine plannedLine = plan.Lines[i]; try { if (!WorldHostBehaviour.StageCreatedBoxIds(plannedLine.Request.BoxNetworkIds)) { throw new InvalidOperationException("delivery box ids are not fresh; rejecting the order"); } RestockManager.SpawnPackageBoxItemMultipleFrame(plannedLine.RestockIndex, plannedLine.Request.Count); num += plannedLine.Price; result.RestockBoxes += plannedLine.Request.Count; result.RestockItems += plannedLine.Request.Count; } catch (Exception ex) { WorldHostBehaviour.CancelStagedDeliveryIds(); result.Success = false; result.Failure = "restock delivery failed for '" + plannedLine.Restock.name + "': " + ex.Message; CoopPlugin.Log.LogError((object)("Purchasing restock delivery failed for " + peer + ": " + ex)); break; } } if (result.RestockBoxes > 0) { double num2 = ScannerDeliveryFee(result.RestockBoxes); num += num2; result.Charged = num; } else { result.Charged = 0.0; } } private void DeliverFurniture(PurchasePlan plan, DeliveryResult result, string peer) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) PlannedLine plannedLine = plan.Lines[0]; try { if (plannedLine.Request.BoxNetworkIds != null && plannedLine.Request.BoxNetworkIds.Count > 0 && !WorldHostBehaviour.PushCreatedBoxId(plannedLine.Request.BoxNetworkIds[0])) { throw new InvalidOperationException("furniture package id is not fresh; rejecting the order"); } Transform val = PurchasingInterop.RandomPackageSpawn(); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("no package spawn point is available"); } ShelfManager.SpawnInteractableObjectInPackageBox(plannedLine.Request.ObjectType, val.position, val.rotation); result.Charged = plannedLine.Price; } catch (Exception ex) { if (plannedLine.Request.BoxNetworkIds != null && plannedLine.Request.BoxNetworkIds.Count > 0) { WorldHostBehaviour.CancelCreatedBoxId(plannedLine.Request.BoxNetworkIds[0]); } result.Success = false; result.Failure = "furniture delivery failed: " + ex.Message; CoopPlugin.Log.LogError((object)("Purchasing furniture delivery failed for " + peer + ": " + ex)); } } private void DeliverProductLicense(PurchasePlan plan, DeliveryResult result, string peer) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) PlannedLine plannedLine = plan.Lines[0]; try { if (!CatalogApi.HostApplyProductLicense(plannedLine.Request.ItemType, plannedLine.Request.IsBigBox, plannedLine.Request.Name ?? "", applyVanillaEntitlementSideEffects: false, publish: false, out var changed) || !changed) { throw new InvalidOperationException("catalog license was not changed"); } result.Charged = plannedLine.Price; PurchasingInterop.RefreshProductLicensePanels(plannedLine.RestockIndex); } catch (Exception ex) { result.Success = false; result.Failure = "product license delivery failed: " + ex.Message; CoopPlugin.Log.LogError((object)("Purchasing product license failed for " + peer + ": " + ex)); } } private void DeliverScannerLicense(PurchasePlan plan, DeliveryResult result, string peer) { try { if (!CatalogApi.HostApplyScannerLicense(publish: false, out var changed) || !changed) { throw new InvalidOperationException("catalog scanner license was not changed"); } result.Charged = plan.Total; CatalogApi.RefreshScannerLicenseUi(); } catch (Exception ex) { result.Success = false; result.Failure = "scanner license delivery failed: " + ex.Message; CoopPlugin.Log.LogError((object)("Purchasing scanner license failed for " + peer + ": " + ex)); } } private void ApplyCommittedSideEffects(PurchasePlan plan, DeliveryResult result) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected I4, but got Unknown switch (plan.Kind) { case PurchaseKind.Restock: { for (int i = 0; i < plan.Lines.Count; i++) { PlannedLine plannedLine = plan.Lines[i]; int num = RestockManager.GetMaxItemCountInBox(plannedLine.Restock.itemType, plannedLine.Restock.isBigBox) * plannedLine.Request.Count; float num2 = ((num > 0) ? ((float)(plannedLine.Price / (double)num)) : 0f); CPlayerData.UpdateAverageItemCost(plannedLine.Restock.itemType, num, num2); PriceChangeManager.AddTransaction(0f - (float)plannedLine.Price, (ETransactionType)0, (int)plannedLine.Restock.itemType, num, (CardData)null); } PriceChangeManager.AddTransaction(0f - (float)ScannerDeliveryFee(result.RestockBoxes), (ETransactionType)17, 0, 0, (CardData)null); ApplyRestockSideEffects(result.Charged, result.RestockItems); break; } case PurchaseKind.Furniture: ApplyFurnitureSideEffects(plan.Lines[0].Furniture, result.Charged); break; case PurchaseKind.ProductLicense: ApplyProductLicenseSideEffects(plan.Lines[0], result.Charged); break; case PurchaseKind.ScannerLicense: ApplyScannerLicenseSideEffects(result.Charged); break; } } private static void ApplyRestockSideEffects(double amount, int boxes) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown CPlayerData.m_GameReportDataCollect.supplyCost -= (float)amount; CPlayerData.m_GameReportDataCollectPermanent.supplyCost -= (float)amount; CEventManager.QueueEvent((CEvent)new CEventPlayer_AddShopExp(boxes * 5, false)); TutorialManager.AddTaskValue((ETutorialTaskCondition)1, (float)boxes); SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); } private static void ApplyFurnitureSideEffects(FurniturePurchaseData furniture, double amount) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected I4, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown PriceChangeManager.AddTransaction(0f - (float)amount, (ETransactionType)1, (int)furniture.objectType, 0, (CardData)null); CEventManager.QueueEvent((CEvent)new CEventPlayer_AddShopExp(Mathf.Clamp(Mathf.RoundToInt((float)amount / 100f), 5, 100), false)); CPlayerData.m_GameReportDataCollect.upgradeCost -= (float)amount; CPlayerData.m_GameReportDataCollectPermanent.upgradeCost -= (float)amount; SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); } private static void ApplyProductLicenseSideEffects(PlannedLine line, double amount) { PriceChangeManager.AddTransaction(0f - (float)amount, (ETransactionType)16, line.RestockIndex, 0, (CardData)null); CPlayerData.m_GameReportDataCollect.upgradeCost -= (float)amount; CPlayerData.m_GameReportDataCollectPermanent.upgradeCost -= (float)amount; SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); } private static void ApplyScannerLicenseSideEffects(double amount) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown CPlayerData.m_GameReportDataCollect.upgradeCost -= (float)amount; CPlayerData.m_GameReportDataCollectPermanent.upgradeCost -= (float)amount; PriceChangeManager.AddTransaction(0f - (float)amount, (ETransactionType)18, 0, 0, (CardData)null); SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); CEventManager.QueueEvent((CEvent)new CEventPlayer_ScannerRestockUnlocked()); } private IEnumerator SaveShelfDataAfterPurchase() { yield return (object)new WaitForSeconds(1f); ShelfManager val = PurchasingInterop.FindShelfManager(); if ((Object)(object)val == (Object)null) { CoopPlugin.Log.LogWarning((object)"Purchasing could not schedule shelf save: ShelfManager is missing"); } else { val.SaveInteractableObjectData(false); } } private static PurchaseOutcomeMessage Accepted(PurchaseIntentMessage message, double charged) { return new PurchaseOutcomeMessage { PredictionId = message.PredictionId, Kind = message.Kind, Success = true, Text = $"purchase accepted (${charged:F2})" }; } private PurchaseOutcomeMessage Failure(PurchaseIntentMessage message, string peer, string reason) { CoopPlugin.Log.LogWarning((object)("Purchasing rejected " + peer + ": " + reason)); return new PurchaseOutcomeMessage { Kind = message.Kind, Success = false, Text = "purchase rejected - " + reason }; } private void SendOutcome(int connectionId, PurchaseOutcomeMessage outcome) { if (!_shutdown) { _context.Send(connectionId, (INetMessage)(object)outcome); } } private static bool ValidProductLine(PurchaseLine line, int maxCount) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 if (line != null && (int)line.ItemType != -1 && line.Count > 0 && line.Count <= maxCount && line.Name != null && line.Name.Length <= 256) { return (int)line.ObjectType == -1; } return false; } private static bool ValidFurnitureLine(PurchaseLine line) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (line != null && (int)line.ObjectType != -1 && line.Count == 1 && (int)line.ItemType == -1 && !line.IsBigBox && line.Name != null) { return line.Name.Length == 0; } return false; } private static bool IsValidTotal(double total, out string error) { if (!IsFinite(total) || total < 0.0 || total > 3.4028234663852886E+38) { error = "purchase total is invalid"; return false; } error = null; return true; } private static bool IsFinite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } private static double ScannerDeliveryFee(int boxes) { if (boxes > 0) { return Mathf.Clamp(10 * boxes / 5, 5, 1000); } return 0.0; } private string PeerLabel(int connectionId) { string text = _context?.PeerName?.Invoke(connectionId); if (!string.IsNullOrEmpty(text)) { return $"{text}/conn {connectionId}"; } return $"conn {connectionId}"; } } internal static class PurchasingInterop { private static readonly FieldInfo FiRestockCart = AccessTools.Field(typeof(RestockItemScreen), "m_CartItemList"); private static readonly FieldInfo FiScannerIndexes = AccessTools.Field(typeof(ScannerRestockScreen), "m_RestockIndexList"); private static readonly FieldInfo FiScannerCounts = AccessTools.Field(typeof(ScannerRestockScreen), "m_RestockBoxCountList"); private static readonly FieldInfo FiScannerPage = AccessTools.Field(typeof(ScannerRestockScreen), "m_PageIndex"); private static readonly MethodInfo MiScannerPage = AccessTools.Method(typeof(ScannerRestockScreen), "EvaluatePanelUIPage", (Type[])null, (Type[])null); private static readonly MethodInfo MiScannerTotals = AccessTools.Method(typeof(ScannerRestockScreen), "UpdateTotalCostAndBoxCount", (Type[])null, (Type[])null); private static readonly FieldInfo FiCheckoutRestockScreen = AccessTools.Field(typeof(RestockItemCheckoutScreen), "m_RestockItemScreen"); private static readonly FieldInfo FiPanelIndex = AccessTools.Field(typeof(RestockItemPanelUI), "m_Index"); private static readonly FieldInfo FiPanelScreen = AccessTools.Field(typeof(RestockItemPanelUI), "m_RestockItemScreen"); private static readonly FieldInfo FiLicenseGroup = AccessTools.Field(typeof(RestockItemPanelUI), "m_LicenseUIGrp"); private static readonly FieldInfo FiProductGroup = AccessTools.Field(typeof(RestockItemPanelUI), "m_UIGrp"); private static readonly FieldInfo FiFurnitureIndex = AccessTools.Field(typeof(FurnitureShopConfirmPurchaseScreen), "m_Index"); private static readonly FieldInfo FiFurnitureOwner = AccessTools.Field(typeof(FurnitureShopConfirmPurchaseScreen), "m_FurnitureShopUIScreen"); private static readonly FieldInfo FiFurnitureConfirmScreen = AccessTools.Field(typeof(FurnitureShopUIScreen), "m_ConfirmPurchaseScreen"); private static readonly Type PlatformManagerType = AccessTools.TypeByName("GA.PlatformManager"); private static readonly PropertyInfo PlatformManagerInstance = PlatformManagerType?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); private static readonly MethodInfo IsFurnitureAvailableMethod = PlatformManagerType?.GetMethod("IsFurnitureAvailable", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(EObjectType) }, null); internal static Dictionary RestockCart(RestockItemScreen screen) { return FiRestockCart?.GetValue(screen) as Dictionary; } internal static RestockItemScreen RestockScreen(RestockItemCheckoutScreen screen) { object? obj = FiCheckoutRestockScreen?.GetValue(screen); return (RestockItemScreen)((obj is RestockItemScreen) ? obj : null); } internal static List ScannerIndexes(ScannerRestockScreen screen) { return FiScannerIndexes?.GetValue(screen) as List; } internal static List ScannerCounts(ScannerRestockScreen screen) { return FiScannerCounts?.GetValue(screen) as List; } internal static int PanelIndex(RestockItemPanelUI panel) { object obj = FiPanelIndex?.GetValue(panel); if (obj != null) { return Convert.ToInt32(obj); } return -1; } internal static int FurnitureIndex(FurnitureShopConfirmPurchaseScreen screen) { object obj = FiFurnitureIndex?.GetValue(screen); if (obj != null) { return Convert.ToInt32(obj); } return -1; } internal static FurnitureShopUIScreen FurnitureOwner(FurnitureShopConfirmPurchaseScreen screen) { object? obj = FiFurnitureOwner?.GetValue(screen); return (FurnitureShopUIScreen)((obj is FurnitureShopUIScreen) ? obj : null); } internal static FurnitureShopConfirmPurchaseScreen OpenFurnitureConfirmation(FurnitureShopUIScreen owner, int index) { if ((Object)(object)owner == (Object)null || index < 0 || FiFurnitureConfirmScreen == null) { return null; } object? value = FiFurnitureConfirmScreen.GetValue(owner); FurnitureShopConfirmPurchaseScreen val = (FurnitureShopConfirmPurchaseScreen)((value is FurnitureShopConfirmPurchaseScreen) ? value : null); if ((Object)(object)val == (Object)null || !((UIScreenBase)val).IsScreenOpened()) { return null; } if (FurnitureIndex(val) != index) { return null; } return val; } internal static PurchaseLine RestockLine(int index, int count) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) CatalogApi.TryGetProduct(index, out var data); if (data == null) { return new PurchaseLine { ItemType = (EItemType)(-1), IsBigBox = false, Name = "", Count = count }; } return new PurchaseLine { ItemType = data.itemType, IsBigBox = data.isBigBox, Name = (data.name ?? ""), Count = count }; } internal static void RemoveRestockQuantities(RestockItemScreen screen, Dictionary purchased) { Dictionary dictionary = RestockCart(screen); if (dictionary == null || purchased == null) { throw new InvalidOperationException("Purchasing restock cart is not available."); } foreach (KeyValuePair item in purchased) { if (dictionary.TryGetValue(item.Key, out var value)) { value -= item.Value; if (value > 0) { dictionary[item.Key] = value; } else { dictionary.Remove(item.Key); } } } RestockItemCheckoutScreen restockItemCheckoutScreen = screen.m_RestockItemCheckoutScreen; if (restockItemCheckoutScreen != null) { restockItemCheckoutScreen.UpdateData(screen, dictionary, false); } } internal static void RestoreRestockCart(RestockItemScreen screen, Dictionary before) { Dictionary dictionary = RestockCart(screen); if (dictionary == null || before == null) { return; } dictionary.Clear(); foreach (KeyValuePair item in before) { dictionary[item.Key] = item.Value; } RestockItemCheckoutScreen restockItemCheckoutScreen = screen.m_RestockItemCheckoutScreen; if (restockItemCheckoutScreen != null) { restockItemCheckoutScreen.UpdateData(screen, dictionary, false); } } internal static void RemoveScannerQuantities(ScannerRestockScreen screen, List> purchased) { List list = ScannerIndexes(screen); List list2 = ScannerCounts(screen); if (list == null || list2 == null || purchased == null) { throw new InvalidOperationException("Purchasing scanner cart is not available."); } for (int i = 0; i < purchased.Count; i++) { int key = purchased[i].Key; int num = purchased[i].Value; int num2 = list.Count - 1; while (num2 >= 0 && num > 0) { if (list[num2] == key) { list2[num2] -= num; if (list2[num2] <= 0) { num = -list2[num2]; list.RemoveAt(num2); list2.RemoveAt(num2); } else { num = 0; } } num2--; } } int num3 = ((FiScannerPage?.GetValue(screen) is int num4) ? num4 : 0); MiScannerPage?.Invoke(screen, new object[1] { num3 }); MiScannerTotals?.Invoke(screen, null); } internal static void RestoreScannerCart(ScannerRestockScreen screen, List> before) { List list = ScannerIndexes(screen); List list2 = ScannerCounts(screen); if (list != null && list2 != null && before != null) { list.Clear(); list2.Clear(); for (int i = 0; i < before.Count; i++) { list.Add(before[i].Key); list2.Add(before[i].Value); } int num = ((FiScannerPage?.GetValue(screen) is int num2) ? num2 : 0); MiScannerPage?.Invoke(screen, new object[1] { num }); MiScannerTotals?.Invoke(screen, null); } } internal static bool IsSameFurniturePurchase(FurnitureShopConfirmPurchaseScreen screen, FurnitureShopUIScreen owner, int index) { if ((Object)(object)screen != (Object)null && FurnitureOwner(screen) == owner) { return FurnitureIndex(screen) == index; } return false; } internal static void RefreshProductLicensePanels(int index) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (index < 0) { return; } List list = SceneComponentRegistry.Snapshot(SceneManager.GetActiveScene(), activeOnly: true); for (int i = 0; i < list.Count; i++) { RestockItemPanelUI val = list[i]; if ((Object)(object)val == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).IsValid() || !((Component)val).gameObject.activeInHierarchy || PanelIndex(val) != index) { continue; } object? obj = FiPanelScreen?.GetValue(val); RestockItemScreen val2 = (RestockItemScreen)((obj is RestockItemScreen) ? obj : null); bool unlocked; if ((Object)(object)val2 != (Object)null) { val.Init(val2, index); } else if (CatalogApi.TryGetLicense(index, out unlocked)) { object? obj2 = FiLicenseGroup?.GetValue(val); GameObject val3 = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val3 != null) { val3.SetActive(!unlocked); } object? obj3 = FiProductGroup?.GetValue(val); GameObject val4 = (GameObject)((obj3 is GameObject) ? obj3 : null); if (val4 != null) { val4.SetActive(unlocked); } } } } internal static bool IsFurnitureAvailable(EObjectType objectType) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (PlatformManagerType == null) { return true; } if (PlatformManagerInstance == null || IsFurnitureAvailableMethod == null) { CoopPlugin.Log.LogError((object)"Purchasing could not resolve beta furniture entitlement API"); return false; } try { object value = PlatformManagerInstance.GetValue(null); return value != null && Convert.ToBoolean(IsFurnitureAvailableMethod.Invoke(value, new object[1] { objectType })); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Purchasing could not query furniture entitlement: " + ex)); return false; } } internal static void CloseFurnitureConfirmation(FurnitureShopConfirmPurchaseScreen screen) { if (screen != null) { ((UIScreenBase)screen).CloseScreen(); } } internal static void ReopenFurnitureConfirmation(FurnitureShopUIScreen owner, int index) { if ((Object)(object)owner != (Object)null && index >= 0) { owner.OnPressPanelUIButton(index); } } internal static Transform RandomPackageSpawn() { return RestockManager.GetRandomPackageSpawnPos(); } internal static ShelfManager FindShelfManager() { return SceneRef.Get(); } } public enum PurchaseKind : byte { Restock, Furniture, ProductLicense, ScannerLicense } [NetworkMessage] public sealed class PurchaseIntentMessage : IPredictedMessage, INetMessage { public PurchaseKind Kind; public bool ScannerCheckout; public List Lines = new List(); public Guid PredictionId { get; set; } } public sealed class PurchaseLine { public EItemType ItemType = (EItemType)(-1); public EObjectType ObjectType = (EObjectType)(-1); public bool IsBigBox; public string Name = ""; public int Count; public List BoxNetworkIds = new List(); } [NetworkMessage] public sealed class PurchaseOutcomeMessage : IPredictedMessage, INetMessage { public PurchaseKind Kind; public bool Success; public string Text = ""; public Guid PredictionId { get; set; } } } namespace CardShopCoop.Modules.Pricing { [ClientBehaviour] public sealed class PricingClientBehaviour : CoopBehaviour { private static PricingClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private PricingStateMessage _pendingState; private readonly Dictionary _pendingItemDeltas = new Dictionary(); private readonly Dictionary _pendingCardDeltas = new Dictionary(); private bool _contentReady; private bool _applying; private bool _shutdown; private static readonly FieldInfo FiPriceScreenIsCard = AccessTools.Field(typeof(SetItemPriceScreen), "m_IsSetCardPrice"); private static readonly FieldInfo FiPriceScreenCardCompartment = AccessTools.Field(typeof(SetItemPriceScreen), "m_CurrentCardCompartment"); private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.pricing.client"); PricingClientPatches.Apply(_harmony); CEventManager.AddListener((EventDelegate)OnReady); } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } CEventManager.RemoveListener((EventDelegate)OnReady); if (_active == this) { _active = null; } _context = null; throw; } } private void OnReady(CEventPlayer_GameDataFinishLoaded _) { ContentReady(); } [MessageHandler(typeof(PricingStateMessage))] private void State(MessageContext context, PricingStateMessage message) { if (!_shutdown) { _pendingState = message; ApplyPendingState(); } } internal static void ContentReady() { if (!((Object)(object)_active == (Object)null)) { _active._contentReady = true; _active.ApplyPendingState(); } } private void ApplyPendingState() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) if (_shutdown || !_contentReady || !_context.InGame() || (Object)(object)WorldCardInteraction.Inv() == (Object)null) { return; } if (_pendingState != null) { PricingStateMessage pendingState = _pendingState; ApplyState(pendingState); _pendingState = null; } if (_pendingItemDeltas.Count > 0) { List> list = new List>(_pendingItemDeltas); for (int i = 0; i < list.Count; i++) { if (ApplyItemDelta(list[i].Value)) { _pendingItemDeltas.Remove(list[i].Key); } } } if (_pendingCardDeltas.Count <= 0) { return; } List> list2 = new List>(_pendingCardDeltas); for (int j = 0; j < list2.Count; j++) { if (ApplyCardDelta(list2[j].Value)) { _pendingCardDeltas.Remove(list2[j].Key); } } } private void ApplyState(PricingStateMessage state) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) _applying = true; try { for (int i = 0; i < state.ItemTypes.Count; i++) { CPlayerData.SetItemPrice(state.ItemTypes[i], state.ItemPrices[i]); } for (int j = 0; j < state.Cards.Count; j++) { CardData obj = PricingInterop.CopyCard(state.Cards[j], state.CardGrades[j]); GradingApi.Remember(obj); CPlayerData.SetCardPrice(obj, state.CardPrices[j]); } } finally { _applying = false; } } [MessageHandler(typeof(PricingDeltaBatchMessage))] private void HandleDeltaBatch(MessageContext context, PricingDeltaBatchMessage message) { if (_shutdown || message == null) { return; } bool flag = _contentReady && _context.InGame() && (Object)(object)WorldCardInteraction.Inv() != (Object)null; for (int i = 0; i < message.Items.Count; i++) { if (flag) { ApplyItemDelta(message.Items[i]); } else { DeferItemDelta(message.Items[i]); } } for (int j = 0; j < message.Cards.Count; j++) { if (flag) { ApplyCardDelta(message.Cards[j]); } else { DeferCardDelta(message.Cards[j]); } } } private bool ApplyItemDelta(PricingItemDeltaEntry entry) { PredictionApi.Confirm(entry.PredictionId, delegate { //IL_0012: Unknown result type (might be due to invalid IL or missing references) _applying = true; try { CPlayerData.SetItemPrice(entry.ItemType, entry.Price); } finally { _applying = false; } }); return true; } private bool ApplyCardDelta(PricingCardDeltaEntry entry) { PredictionApi.Confirm(entry.PredictionId, delegate { if (entry.Removed) { return; } CardData val = PricingInterop.CopyCard(entry.Card, entry.EncodedGrade); GradingApi.Remember(val); _applying = true; try { CPlayerData.SetCardPrice(val, entry.Price); } finally { _applying = false; } }); return true; } private void DeferItemDelta(PricingItemDeltaEntry entry) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (_pendingItemDeltas.TryGetValue(entry.ItemType, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingItemDeltas[entry.ItemType] = entry; } private void DeferCardDelta(PricingCardDeltaEntry entry) { string key = PricingInterop.CardKey(entry.Card, entry.EncodedGrade) ?? "null"; if (_pendingCardDeltas.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingCardDeltas[key] = entry; } internal static float CaptureItemBefore(EItemType type) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) PricingClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applying || !PricingInterop.IsItemTypeValid(type)) { return float.NaN; } return PricingInterop.ReadItem(type); } internal static void ObserveItemSetter(EItemType type, float price, float previous) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) PricingClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._applying && !float.IsNaN(previous) && active.CanSend() && PricingInterop.IsItemTypeValid(type) && PricingInterop.ValidPrice(price)) { active.PredictItem(type, price, previous); } } internal static float CaptureCardBefore(CardData card) { PricingClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._applying || !PricingInterop.ValidCard(card)) { return float.NaN; } return PricingInterop.ReadCard(card); } internal static void ObserveCardSetter(CardData card, float price, float previous) { PricingClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._applying && !float.IsNaN(previous) && card != null && active.CanSend() && PricingInterop.ValidCard(card) && PricingInterop.ValidPrice(price)) { InteractableCardCompartment val = CurrentCardPriceCompartment(); if ((Object)(object)val == (Object)null || !WorldCardDisplay.TryMakeKey(val, out var shelfKey, out var compartmentIndex)) { CoopPlugin.Log.LogWarning((object)"[pricing] card price change had no display slot; not forwarded."); return; } int grade = GradingApi.Encoded(card); GradingApi.Remember(card); active.PredictCard(card, price, grade, previous, shelfKey, compartmentIndex); } } private static InteractableCardCompartment CurrentCardPriceCompartment() { SetItemPriceScreen val = SceneRef.Get(); if ((Object)(object)val == (Object)null || FiPriceScreenIsCard == null || !(bool)FiPriceScreenIsCard.GetValue(val)) { return null; } object? obj = FiPriceScreenCardCompartment?.GetValue(val); return (InteractableCardCompartment)((obj is InteractableCardCompartment) ? obj : null); } private void PredictItem(EItemType type, float price, float previous) { //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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected I4, but got Unknown if (CanSend()) { PredictionApi.Predict("pricing-item:" + (int)type, delegate(Guid predictionId) { //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) _context.Send(1, (INetMessage)(object)new PricingItemIntentMessage { PredictionId = predictionId, ItemType = type, Price = price }); }, delegate { //IL_0007: Unknown result type (might be due to invalid IL or missing references) ApplyLocalItem(type, price); }, delegate { //IL_0007: Unknown result type (might be due to invalid IL or missing references) ApplyLocalItem(type, previous); }); } } private void PredictCard(CardData card, float price, int grade, float previous, int shelfKey, int compartment) { if (CanSend()) { CardData copy = PricingInterop.CopyCard(card, grade); string text = PricingInterop.CardKey(copy, grade); PredictionApi.Predict("pricing-card:" + text, delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new PricingCardIntentMessage { PredictionId = predictionId, Card = PricingInterop.CopyCard(copy, grade), Price = price, EncodedGrade = grade, ShelfKey = shelfKey, Compartment = compartment }); }, delegate { ApplyLocalCard(copy, price); }, delegate { ApplyLocalCard(copy, previous); }); } } private void ApplyLocalItem(EItemType type, float price) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) _applying = true; try { CPlayerData.SetItemPrice(type, price); } finally { _applying = false; } } private void ApplyLocalCard(CardData card, float price) { GradingApi.Remember(card); _applying = true; try { CPlayerData.SetCardPrice(card, price); } finally { _applying = false; } } internal static void InventoryReset() { if (!((Object)(object)_active == (Object)null)) { _active._pendingState = null; _active.ClearPendingDeltas(); _active._contentReady = false; } } private void ClearPendingDeltas() { foreach (PricingItemDeltaEntry value in _pendingItemDeltas.Values) { PredictionApi.Ack(value.PredictionId); } foreach (PricingCardDeltaEntry value2 in _pendingCardDeltas.Values) { PredictionApi.Ack(value2.PredictionId); } _pendingItemDeltas.Clear(); _pendingCardDeltas.Clear(); } private bool CanSend() { if (!_shutdown) { return _context?.InGame() ?? false; } return false; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); _pendingState = null; ClearPendingDeltas(); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class PricingHostBehaviour : CoopBehaviour { private sealed class OwnedCardState { internal CardData Card; internal float Price; } private static PricingHostBehaviour _active; private readonly Dictionary _cards = new Dictionary(StringComparer.Ordinal); private readonly HashSet _joined = new HashSet(); private readonly HashSet _baselinePending = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _applying; private readonly List _pendingItemDeltas = new List(); private readonly List _pendingCardDeltas = new List(); internal const int PricingDeltaBatchMax = 200; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.pricing.host"); PricingHostPatches.Apply(_harmony); CEventManager.AddListener((EventDelegate)OnReady); } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } [OnFullyJoined] private void SendJoinState(PeerConnection peer) { if (peer != null) { _joined.Add(peer.Id); if (!SendBaseline(peer.Id)) { _baselinePending.Add(peer.Id); } } } private void OnReady(CEventPlayer_GameDataFinishLoaded _) { SendPendingBaselines(); } [OnClientDisconnected] private void ForgetPeer(PeerConnection peer, DisconnectInfo _) { if (peer != null) { _joined.Remove(peer.Id); _baselinePending.Remove(peer.Id); } } [MessageHandler(typeof(PricingItemIntentMessage))] private void Item(MessageContext context, PricingItemIntentMessage message) { //IL_001a: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (_shutdown || context?.Connection == null || message == null || !PricingInterop.IsItemTypeValid(message.ItemType) || !PricingInterop.ValidPrice(message.Price) || !TrySetItem(message.ItemType, message.Price, out var _)) { Reject(context, message?.PredictionId ?? Guid.Empty); } else { QueueItemDelta(message.PredictionId, message.ItemType, PricingInterop.ReadItem(message.ItemType)); } } [MessageHandler(typeof(PricingCardIntentMessage))] private void Card(MessageContext context, PricingCardIntentMessage message) { if (_shutdown || context?.Connection == null || message?.Card == null || !PricingInterop.ValidCard(message.Card) || !PricingInterop.ValidPrice(message.Price) || message.EncodedGrade < 0) { CoopPlugin.Log.LogWarning((object)("[pricing] rejected card intent: card=" + ((message?.Card == null) ? "null" : Describe(message.Card)) + " valid=" + (message?.Card != null && PricingInterop.ValidCard(message.Card)) + " price=" + (message?.Price ?? (-1f)) + " grade=" + (message?.EncodedGrade ?? (-1)) + ".")); Reject(context, message?.PredictionId ?? Guid.Empty); return; } string text = PricingInterop.CardKey(message.Card, message.EncodedGrade); if (text == null || !WorldCardDisplay.TryResolve(message.ShelfKey, message.Compartment, out var compartment) || !WorldCardDisplay.Matches(compartment, message.Card, message.EncodedGrade)) { CoopPlugin.Log.LogWarning((object)("[pricing] rejected card intent: not the displayed card shelf=" + message.ShelfKey + " compartment=" + message.Compartment + " card=" + Describe(message.Card) + " key=" + (text ?? "null") + " grade=" + message.EncodedGrade + ".")); Reject(context, message.PredictionId); return; } CardData card = WorldCardDisplay.Read(compartment); CardData val = PricingInterop.CopyCard(card, GradingApi.Encoded(card)); if (val == null || GradingApi.Encoded(val) != message.EncodedGrade || !TrySetCard(val, message.Price, out var actual)) { CoopPlugin.Log.LogWarning((object)("[pricing] rejected card intent: set failed card=" + Describe(message.Card) + " requestedGrade=" + message.EncodedGrade + " canonicalGrade=" + ((val == null) ? (-1) : GradingApi.Encoded(val)) + " requestedPrice=" + message.Price + " actualPrice=" + ((val == null) ? (-1f) : PricingInterop.ReadCard(val)) + ".")); Reject(context, message.PredictionId); } else { GradingApi.Remember(val); _cards[text] = new OwnedCardState { Card = val, Price = actual }; QueueCardDelta(message.PredictionId, val, actual, removed: false); } } private static string Describe(CardData card) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected I4, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected I4, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected I4, but got Unknown if (card != null) { return (int)card.expansionType + "/" + (int)card.monsterType + "/" + (int)card.borderType + "/foil=" + (card.isFoil ? 1 : 0) + "/destiny=" + (card.isDestiny ? 1 : 0) + "/champ=" + (card.isChampionCard ? 1 : 0) + "/grade=" + card.cardGrade + "/saveIndex=" + PricingInterop.SafeSaveIndex(card); } return "null"; } internal static void CaptureItemSetter(EItemType type, float requestedPrice) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) _active?.CaptureItemSetterLocal(type, requestedPrice); } internal static void CaptureCardSetter(CardData card, float requestedPrice) { _active?.CaptureCardSetterLocal(card, requestedPrice); } internal static void InventoryChanged(CardData card) { _active?.ObserveInventoryMutation(card); } internal static void InventoryReset() { _active?.ResetInventory(); } internal static void GradedInventoryChanged() { } private void CaptureItemSetterLocal(EItemType type, float requestedPrice) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && !_applying && PricingInterop.IsItemTypeValid(type) && PricingInterop.ValidPrice(requestedPrice)) { QueueItemDelta(Guid.Empty, type, PricingInterop.ReadItem(type)); } } private void CaptureCardSetterLocal(CardData card, float requestedPrice) { if (!_shutdown && !_applying && card != null && PricingInterop.ValidPrice(requestedPrice) && PricingInterop.ValidCard(card)) { int encodedGrade = GradingApi.Encoded(card); CardData card2 = PricingInterop.CopyCard(card, encodedGrade); string text = PricingInterop.CardKey(card2, encodedGrade); if (text != null) { _cards[text] = new OwnedCardState { Card = card2, Price = ResolveCardPrice(card2, requestedPrice) }; QueueCardDelta(Guid.Empty, card2, _cards[text].Price, removed: false); } } } private void ObserveInventoryMutation(CardData card) { if (_shutdown || card == null || !PricingInterop.ValidCard(card)) { return; } int num = GradingApi.Encoded(card); CardData val = PricingInterop.CopyCard(card, num); string text = PricingInterop.CardKey(val, num); if (text != null) { if (!((num > 0) ? CPlayerData.HasGradedCardInAlbum(val) : (CPlayerData.GetCardAmount(val) > 0))) { _cards.Remove(text); QueueCardDelta(Guid.Empty, val, 0f, removed: true); return; } float price = EffectiveCardPrice(text, val); _cards[text] = new OwnedCardState { Card = val, Price = price }; QueueCardDelta(Guid.Empty, val, price, removed: false); } } private void ResetInventory() { _cards.Clear(); _pendingItemDeltas.Clear(); _pendingCardDeltas.Clear(); } private PricingStateMessage BuildState() { //IL_0022: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) PricingStateMessage pricingStateMessage = new PricingStateMessage(); EItemType[] array = (EItemType[])Enum.GetValues(typeof(EItemType)); foreach (EItemType val in array) { if (PricingInterop.IsItemTypeValid(val)) { pricingStateMessage.ItemTypes.Add(val); pricingStateMessage.ItemPrices.Add(PricingInterop.ReadItem(val)); } } List list = new List(_cards.Keys); list.Sort(StringComparer.Ordinal); for (int j = 0; j < list.Count; j++) { OwnedCardState ownedCardState = _cards[list[j]]; CardData val2 = PricingInterop.CopyCard(ownedCardState.Card, GradingApi.Encoded(ownedCardState.Card)); pricingStateMessage.Cards.Add(val2); pricingStateMessage.CardPrices.Add(ownedCardState.Price); pricingStateMessage.CardGrades.Add(GradingApi.Encoded(val2)); } return pricingStateMessage; } private bool SendBaseline(int connectionId) { if (_shutdown || !_context.InGame() || PricingInterop.ItemCount == 0 || (Object)(object)WorldCardInteraction.Inv() == (Object)null) { return false; } HydrateOwnedCards(); _context.Send(connectionId, (INetMessage)(object)BuildState()); _baselinePending.Remove(connectionId); return true; } private void SendPendingBaselines() { foreach (int item in new List(_baselinePending)) { if (!_joined.Contains(item)) { _baselinePending.Remove(item); } else { SendBaseline(item); } } } private void QueueItemDelta(Guid predictionId, EItemType type, float price) { //IL_003d: 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 (!_shutdown) { CoopRuntimeContext context = _context; if (context != null && context.InGame()) { _pendingItemDeltas.Add(new PricingItemDeltaEntry { PredictionId = predictionId, ItemType = type, Price = price }); } } } private void QueueCardDelta(Guid predictionId, CardData card, float price, bool removed) { if (!_shutdown) { CoopRuntimeContext context = _context; if (context != null && context.InGame()) { _pendingCardDeltas.Add(new PricingCardDeltaEntry { PredictionId = predictionId, Card = PricingInterop.CopyCard(card, GradingApi.Encoded(card)), Price = price, EncodedGrade = GradingApi.Encoded(card), Removed = removed }); } } } private void FlushPendingDeltas() { if (!_shutdown) { CoopRuntimeContext context = _context; if (context != null && context.InGame()) { while (_pendingItemDeltas.Count > 0) { int count = Math.Min(200, _pendingItemDeltas.Count); List range = _pendingItemDeltas.GetRange(0, count); _context.Broadcast((INetMessage)(object)new PricingDeltaBatchMessage { Items = range }); _pendingItemDeltas.RemoveRange(0, count); } while (_pendingCardDeltas.Count > 0) { int count2 = Math.Min(200, _pendingCardDeltas.Count); List range2 = _pendingCardDeltas.GetRange(0, count2); _context.Broadcast((INetMessage)(object)new PricingDeltaBatchMessage { Cards = range2 }); _pendingCardDeltas.RemoveRange(0, count2); } return; } } _pendingItemDeltas.Clear(); _pendingCardDeltas.Clear(); } private void Update() { if (_pendingItemDeltas.Count > 0 || _pendingCardDeltas.Count > 0) { FlushPendingDeltas(); } } private void Reject(MessageContext context, Guid predictionId) { if (predictionId != Guid.Empty && context?.Connection != null) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, predictionId); } } private void HydrateOwnedCards() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Invalid comparison between Unknown and I4 //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_0055: Unknown result type (might be due to invalid IL or missing references) for (ECardExpansionType val = (ECardExpansionType)0; (int)val <= 7; val = (ECardExpansionType)(val + 1)) { if ((int)val == 6) { continue; } int num = (((int)val != 2) ? 1 : 2); for (int i = 0; i < num; i++) { bool flag = i != 0; List shownMonsterList = InventoryBase.GetShownMonsterList(val); List cardCollectedList = CPlayerData.GetCardCollectedList(val, flag); if (shownMonsterList == null || cardCollectedList == null) { continue; } int cardAmountPerMonsterType = CPlayerData.GetCardAmountPerMonsterType(val, true); for (int j = 0; j < shownMonsterList.Count * cardAmountPerMonsterType && j < cardCollectedList.Count; j++) { if (cardCollectedList[j] > 0) { RememberOwnedCard(CPlayerData.GetCardData(j, val, flag)); } } } } List gradedCardInventoryList = CPlayerData.m_GradedCardInventoryList; if (gradedCardInventoryList == null) { return; } for (int k = 0; k < gradedCardInventoryList.Count; k++) { if (gradedCardInventoryList[k] != null && gradedCardInventoryList[k].amount > 0) { RememberOwnedCard(CPlayerData.GetGradedCardData(gradedCardInventoryList[k])); } } } private void RememberOwnedCard(CardData card) { if (card != null && PricingInterop.ValidCard(card)) { int encodedGrade = GradingApi.Encoded(card); CardData card2 = PricingInterop.CopyCard(card, encodedGrade); string text = PricingInterop.CardKey(card2, encodedGrade); if (text != null) { GradingApi.Remember(card2); _cards[text] = new OwnedCardState { Card = card2, Price = EffectiveCardPrice(text, card2) }; } } } private float EffectiveCardPrice(string key, CardData card) { float num = PricingInterop.ReadCard(card); if (num <= 0f && _cards.TryGetValue(key, out var value) && value.Price > 0f) { return value.Price; } return num; } private static float ResolveCardPrice(CardData card, float requested) { float num = PricingInterop.ReadCard(card); if (!(Math.Abs(num - requested) <= 0.0075f)) { return requested; } return num; } private bool TrySetItem(EItemType type, float price, out float actual) { //IL_0000: 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) if (!PricingInterop.IsItemTypeValid(type)) { actual = 0f; return false; } _applying = true; try { if (!PricingInterop.SetItem(type, price, out actual)) { return false; } } finally { _applying = false; } return true; } private bool TrySetCard(CardData card, float price, out float actual) { _applying = true; try { return PricingInterop.SetCard(card, price, out actual); } finally { _applying = false; } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnReady); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _cards.Clear(); _joined.Clear(); _baselinePending.Clear(); _pendingItemDeltas.Clear(); _pendingCardDeltas.Clear(); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class PricingInterop { private static bool _readCardOutOfRangeLogged; internal const float PriceEpsilon = 0.0075f; internal static int ItemCount => CPlayerData.m_SetItemPriceList?.Count ?? 0; internal static bool IsItemTypeValid(EItemType type) { //IL_0000: 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_0017: Expected I4, but got Unknown if (!CatalogApi.IsWireableItemType(type, "pricing item")) { return false; } List setItemPriceList = CPlayerData.m_SetItemPriceList; int num = (int)type; if (setItemPriceList != null && num >= 0) { return num < setItemPriceList.Count; } return false; } internal static float ReadItem(EItemType type) { //IL_0000: 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_000d: Unknown result type (might be due to invalid IL or missing references) if (!IsItemTypeValid(type)) { throw new ArgumentOutOfRangeException("type", type, "Unknown item type."); } return CPlayerData.GetItemPrice(type, false); } internal static bool SetItem(EItemType type, float price, out float actual) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) actual = 0f; if (!IsItemTypeValid(type) || !ValidPrice(price)) { return false; } CPlayerData.SetItemPrice(type, price); actual = ReadItem(type); return Math.Abs(actual - price) <= 0.0075f; } internal static bool SetCard(CardData card, float price, out float actual) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected I4, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected I4, but got Unknown actual = 0f; if (card == null || !ValidPrice(price)) { return false; } CPlayerData.SetCardPrice(card, price); actual = ReadCard(card); if (Math.Abs(actual - price) <= 0.0075f) { return true; } CoopPlugin.Log.LogWarning((object)("[pricing] card price store did not hold " + price + " for expansion=" + (int)card.expansionType + " monster=" + (int)card.monsterType + " grade=" + card.cardGrade + " (read back " + actual + "); publishing the requested price so peers stay in step.")); actual = price; return true; } internal static float ReadCard(CardData card) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected I4, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected I4, but got Unknown if (card == null) { throw new ArgumentNullException("card"); } try { return CPlayerData.GetCardPrice(card); } catch (ArgumentOutOfRangeException) { if (!_readCardOutOfRangeLogged) { _readCardOutOfRangeLogged = true; CoopPlugin.Log.LogWarning((object)("[pricing] card price store is out of range for expansion=" + (int)card.expansionType + " monster=" + (int)card.monsterType + " grade=" + card.cardGrade + "; treating its price as unset.")); } return 0f; } } internal static CardData CopyCard(CardData card, int encodedGrade) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0019: Expected O, but got Unknown if (card == null) { return null; } CardData val = new CardData(); val.CopyData(card); val.cardGrade = encodedGrade; return val; } internal static bool ValidCard(CardData card) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (card == null || (int)card.expansionType == -1 || (int)card.monsterType == 0 || card.cardGrade < 0) { return false; } try { return CPlayerData.GetCardSaveIndex(card) >= 0 && GradingApi.Encoded(card) >= 0; } catch { return false; } } internal static bool ValidPrice(float price) { if (!float.IsNaN(price) && !float.IsInfinity(price) && price >= 0f) { return price <= 1E+09f; } return false; } internal static int SafeSaveIndex(CardData card) { if (card == null) { return -1; } try { return CPlayerData.GetCardSaveIndex(card); } catch { return -1; } } internal static string CardKey(CardData card, int encodedGrade = int.MinValue) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected I4, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected I4, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected I4, but got Unknown if (card == null) { return null; } int num = ((encodedGrade == int.MinValue) ? GradingApi.Encoded(card) : encodedGrade); return (int)card.expansionType + ":" + (int)card.monsterType + ":" + (int)card.borderType + ":" + (card.isFoil ? 1 : 0) + ":" + (card.isDestiny ? 1 : 0) + ":" + (card.isChampionCard ? 1 : 0) + ":" + num; } } [NetworkMessage] public sealed class PricingItemIntentMessage : IPredictedMessage, INetMessage { public EItemType ItemType; public float Price; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PricingCardIntentMessage : IPredictedMessage, INetMessage { public CardData Card; public float Price; public int EncodedGrade; public int ShelfKey; public int Compartment; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PricingStateMessage : INetMessage { public List ItemTypes = new List(); public List ItemPrices = new List(); public List Cards = new List(); public List CardPrices = new List(); public List CardGrades = new List(); } public struct PricingItemDeltaEntry { public Guid PredictionId; public EItemType ItemType; public float Price; } public struct PricingCardDeltaEntry { public Guid PredictionId; public CardData Card; public float Price; public int EncodedGrade; public bool Removed; } [NetworkMessage] public sealed class PricingDeltaBatchMessage : INetMessage { public List Items = new List(); public List Cards = new List(); } internal static class PricingHostPatches { [HarmonyPatch(typeof(CPlayerData), "SetItemPrice")] private static class DirectItemSetter { [HarmonyPostfix] private static void Postfix(EItemType itemType, float price) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) PricingHostBehaviour.CaptureItemSetter(itemType, price); } } [HarmonyPatch(typeof(CPlayerData), "SetCardPrice")] private static class DirectCardSetter { [HarmonyPostfix] private static void Postfix(CardData cardData, float priceSet) { PricingHostBehaviour.CaptureCardSetter(cardData, priceSet); } } [HarmonyPatch(typeof(CPlayerData), "AddCard")] private static class CardAdded { [HarmonyPostfix] private static void Postfix(CardData cardData) { PricingHostBehaviour.InventoryChanged(cardData); PricingHostBehaviour.GradedInventoryChanged(); } } [HarmonyPatch(typeof(CPlayerData), "ReduceCard")] private static class CardReduced { [HarmonyPostfix] private static void Postfix(CardData cardData) { PricingHostBehaviour.InventoryChanged(cardData); } } [HarmonyPatch(typeof(CPlayerData), "ReduceCardUsingIndex")] private static class CardReducedUsingIndex { [HarmonyPostfix] private static void Postfix(int index, ECardExpansionType expansionType, bool isDestiny) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) PricingHostBehaviour.InventoryChanged(CPlayerData.GetCardData(index, expansionType, isDestiny)); } } [HarmonyPatch(typeof(CPlayerData), "RemoveGradedCard")] private static class GradedCardRemoved { [HarmonyPostfix] private static void Postfix(CardData cardData) { PricingHostBehaviour.InventoryChanged(cardData); } } [HarmonyPatch(typeof(CPlayerData), "SetCard")] private static class CardSet { [HarmonyPostfix] private static void Postfix(CardData cardData) { PricingHostBehaviour.InventoryChanged(cardData); } } [HarmonyPatch(typeof(CPlayerData), "ResetData")] private static class CardInventoryReset { [HarmonyPostfix] private static void Postfix() { PricingHostBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CPlayerData), "CreateDefaultData")] private static class CardInventoryDefaultReset { [HarmonyPostfix] private static void Postfix() { PricingHostBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CGameData), "PropagateLoadData")] private static class CardInventoryLoad { [HarmonyPostfix] private static void Postfix() { PricingHostBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(RestockManager), "OnDayStarted")] private static class GradedInventoryDayStarted { [HarmonyPostfix] private static void Postfix() { PricingHostBehaviour.GradedInventoryChanged(); } } internal static void Apply(Harmony harmony) { harmony.CreateClassProcessor(typeof(DirectItemSetter)).Patch(); harmony.CreateClassProcessor(typeof(DirectCardSetter)).Patch(); harmony.CreateClassProcessor(typeof(CardAdded)).Patch(); harmony.CreateClassProcessor(typeof(CardReduced)).Patch(); harmony.CreateClassProcessor(typeof(CardReducedUsingIndex)).Patch(); harmony.CreateClassProcessor(typeof(GradedCardRemoved)).Patch(); harmony.CreateClassProcessor(typeof(CardSet)).Patch(); harmony.CreateClassProcessor(typeof(CardInventoryReset)).Patch(); harmony.CreateClassProcessor(typeof(CardInventoryDefaultReset)).Patch(); harmony.CreateClassProcessor(typeof(CardInventoryLoad)).Patch(); harmony.CreateClassProcessor(typeof(GradedInventoryDayStarted)).Patch(); } } internal static class PricingClientPatches { [HarmonyPatch(typeof(CPlayerData), "SetItemPrice")] private static class DirectItemSetter { [HarmonyPrefix] private static void Prefix(EItemType itemType, out float __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) __state = PricingClientBehaviour.CaptureItemBefore(itemType); } [HarmonyPostfix] private static void Postfix(EItemType itemType, float price, float __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) PricingClientBehaviour.ObserveItemSetter(itemType, price, __state); } } [HarmonyPatch(typeof(CPlayerData), "SetCardPrice")] private static class DirectCardSetter { [HarmonyPrefix] private static void Prefix(CardData cardData, out float __state) { __state = PricingClientBehaviour.CaptureCardBefore(cardData); } [HarmonyPostfix] private static void Postfix(CardData cardData, float priceSet, float __state) { PricingClientBehaviour.ObserveCardSetter(cardData, priceSet, __state); } } [HarmonyPatch(typeof(CPlayerData), "ResetData")] private static class CardInventoryReset { [HarmonyPostfix] private static void Postfix() { PricingClientBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CPlayerData), "CreateDefaultData")] private static class CardInventoryDefaultReset { [HarmonyPostfix] private static void Postfix() { PricingClientBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CGameData), "PropagateLoadData")] private static class CardInventoryLoad { [HarmonyPostfix] private static void Postfix() { PricingClientBehaviour.InventoryReset(); } } internal static void Apply(Harmony harmony) { harmony.CreateClassProcessor(typeof(DirectItemSetter)).Patch(); harmony.CreateClassProcessor(typeof(DirectCardSetter)).Patch(); harmony.CreateClassProcessor(typeof(CardInventoryReset)).Patch(); harmony.CreateClassProcessor(typeof(CardInventoryDefaultReset)).Patch(); harmony.CreateClassProcessor(typeof(CardInventoryLoad)).Patch(); } } } namespace CardShopCoop.Modules.Presence { public readonly struct PeerPresence { public Vector3 Position { get; } public TimeSpan Age { get; } public byte Hold { get; } public IReadOnlyList HoldTypes { get; } public PeerPresence(Vector3 position, TimeSpan age, byte hold, IReadOnlyList holdTypes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Position = position; Age = age; Hold = hold; int[] array = new int[holdTypes?.Count ?? 0]; if (holdTypes != null) { for (int i = 0; i < array.Length; i++) { array[i] = holdTypes[i]; } } HoldTypes = array; } } public interface IPeerPresence { bool TryGet(int connectionId, out PeerPresence presence); void RecordAuthenticatedPosition(PeerConnection connection, Vector3 position); void RecordAuthenticatedHold(PeerConnection connection, byte hold, IReadOnlyList holdTypes); void Clear(int connectionId); void ClearAll(); } public sealed class PeerPresenceProvider : IPeerPresence { private sealed class Entry { public Vector3 Position; public byte Hold; public int[] HoldTypes; public long Receipt; } private readonly Dictionary _entries = new Dictionary(); private readonly object _gate = new object(); private readonly long _expiryTicks; public PeerPresenceProvider(TimeSpan expiry) { if (expiry <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException("expiry"); } _expiryTicks = (long)(expiry.TotalSeconds * (double)Stopwatch.Frequency); } public bool TryGet(int connectionId, out PeerPresence presence) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) long timestamp = Stopwatch.GetTimestamp(); lock (_gate) { if (!_entries.TryGetValue(connectionId, out var value) || timestamp - value.Receipt >= _expiryTicks) { _entries.Remove(connectionId); presence = default(PeerPresence); return false; } presence = new PeerPresence(value.Position, TimeSpan.FromSeconds((double)(timestamp - value.Receipt) / (double)Stopwatch.Frequency), value.Hold, value.HoldTypes); return true; } } public void RecordAuthenticatedPosition(PeerConnection connection, Vector3 position) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000c: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (connection == null || (int)connection.State != 3 || !Finite(position)) { return; } lock (_gate) { if (_entries.TryGetValue(connection.Id, out var value)) { value.Position = position; value.Receipt = Stopwatch.GetTimestamp(); } else { _entries[connection.Id] = new Entry { Position = position, Receipt = Stopwatch.GetTimestamp() }; } } } public void RecordAuthenticatedHold(PeerConnection connection, byte hold, IReadOnlyList holdTypes) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (connection == null || (int)connection.State != 3) { return; } int[] holdTypes2 = CopyHoldTypes(holdTypes); lock (_gate) { if (_entries.TryGetValue(connection.Id, out var value)) { value.Hold = hold; value.HoldTypes = holdTypes2; value.Receipt = Stopwatch.GetTimestamp(); } else { _entries[connection.Id] = new Entry { Hold = hold, HoldTypes = holdTypes2, Receipt = Stopwatch.GetTimestamp() }; } } } public void Clear(int connectionId) { lock (_gate) { _entries.Remove(connectionId); } } public void ClearAll() { lock (_gate) { _entries.Clear(); } } private static int[] CopyHoldTypes(IReadOnlyList values) { int num = values?.Count ?? 0; int[] array = new int[num]; for (int i = 0; i < num; i++) { array[i] = values[i]; } return array; } private static bool Finite(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(p.x) && !float.IsInfinity(p.x) && !float.IsNaN(p.y) && !float.IsInfinity(p.y) && !float.IsNaN(p.z)) { return !float.IsInfinity(p.z); } return false; } } public static class PresenceApi { private static readonly Dictionary Names = new Dictionary(); private static Func _localName; public static string LocalPlayerName => _localName?.Invoke() ?? CoopPlugin.PlayerName.Value; public static IReadOnlyCollection PeerNames => Names.Values; public static int PlayerModelGeneration => PresenceService.Active?.ModelGeneration ?? 0; public static bool CanUndoPlayerModel => PresenceService.Active?.CanUndo ?? false; public static bool CanRedoPlayerModel => PresenceService.Active?.CanRedo ?? false; public static event Action RemoteAvatarsChanged; public static string PeerName(int connectionId) { if (!Names.TryGetValue(connectionId, out var value)) { return null; } return value; } internal static void ConfigureIdentity(Func localName) { _localName = localName; } internal static void SetPeerName(int connectionId, string name) { if (connectionId > 0) { Names[connectionId] = (string.IsNullOrWhiteSpace(name) ? ("Player " + connectionId) : name); } } internal static void RemovePeer(int connectionId) { Names.Remove(connectionId); } internal static void ClearPeerNames() { Names.Clear(); } public static bool TryGetPeerPresence(int connectionId, out PeerPresence presence) { PresenceHostBehaviour active = PresenceHostBehaviour.Active; if ((Object)(object)active != (Object)null) { return active.TryGetPeerPresence(connectionId, out presence); } presence = default(PeerPresence); return false; } public static bool TryGetPeerCamera(int avatarId, out Vector3 position, out Quaternion rotation) { //IL_0001: 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_000d: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); rotation = Quaternion.identity; return PresenceService.Active?.TryGetPeerCamera(avatarId, out position, out rotation) ?? false; } public static bool TryGetPeerCameraForHolder(int holderConnectionId, out Vector3 position, out Quaternion rotation) { //IL_0001: 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_000d: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); rotation = Quaternion.identity; return PresenceService.Active?.TryGetPeerCameraForHolder(holderConnectionId, out position, out rotation) ?? false; } public static bool TryGetRemoteCarryAnchor(int holderConnectionId, out Transform anchor) { anchor = null; return PresenceService.Active?.TryGetRemoteCarryAnchor(holderConnectionId, out anchor) ?? false; } internal static void NotifyRemoteAvatarsChanged() { PresenceApi.RemoteAvatarsChanged?.Invoke(); } public static bool TryGetLocalPlayerPosition(out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); return PresenceService.Active?.TryGetLocalPosition(out position) ?? false; } public static PresenceModelEntry GetLocalPlayerModel() { return PresenceService.Active?.GetLocalModel(); } public static bool IsLocalConnection(int connectionId) { if (connectionId == 0 && (Object)(object)PresenceHostBehaviour.Active != (Object)null) { return true; } return PresenceClientBehaviour.Active?.IsLocalConnection(connectionId) ?? false; } public static void SetCharacterPreview(bool active) { PresenceService.Active?.SetPreview(active); } public static List GetLocalModelSliders() { return PresenceService.Active?.GetLocalSliders() ?? new List(); } public static CharacterCustomization GetLocalCustomization() { return PresenceService.Active?.GetLocalCustomization(); } public static void CommitLocalCustomization() { PresenceService.Active?.CommitCustomization(); } public static bool UndoPlayerModel() { return PresenceService.Active?.Undo() ?? false; } public static bool RedoPlayerModel() { return PresenceService.Active?.Redo() ?? false; } public static List GetLocalPresetNames() { return PresenceService.Active?.GetPresetNames() ?? new List(); } public static void ApplyLocalPreset(string presetName) { PresenceService.Active?.ApplyPreset(presetName); } public static void ClearLocalHair(int slot) { PresenceService.Active?.ClearHair(slot); } public static void ClearLocalApparel(int slot) { PresenceService.Active?.ClearApparel(slot); } public static void SetLocalHairColor(int slot, Color color) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) PresenceService.Active?.SetHairColor(slot, color); } public static void SetLocalApparelTint(int slot, Color color) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) PresenceService.Active?.SetApparelTint(slot, color); } public static void SetLocalModelSlider(string propertyName, float value) { PresenceService.Active?.SetSlider(propertyName, value); } public static void SetLocalPlayerModel(bool female, int modelIndex) { PresenceService.Active?.SetModel(female, modelIndex); } public static void SetNsfwAllowed(bool allowed) { PresenceService.Active?.SetNsfwAllowed(allowed); } public static void SendEmote() { PresenceClientBehaviour.Active?.SendEmote(); } public static void SendActivity(EItemType pack) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) PresenceClientBehaviour.Active?.SendActivity(pack); } } internal sealed class PresenceAvatarRenderer { private struct Snapshot { public Vector3 Pos; public float Yaw; public float RecvTime; } private class RemoteAvatar { public GameObject Go; public Animator Anim; public bool HasMoveSpeed; public bool HasHoldingBox; public TMP_Text NameTag; public TMP_Text EmoteTag; public Transform CarryAnchor; public string Name = "Player"; public Vector3 TargetPos; public Vector3 Velocity; public float LastStateTime; public float TargetYaw; public Vector3 CameraPosition; public Quaternion CameraRotation = Quaternion.identity; public Vector3 PreviousCameraPosition; public Quaternion PreviousCameraRotation = Quaternion.identity; public bool HasCamera; public float AnimSpeed; public float AppliedAnimSpeed = float.NaN; public Vector3 PreviousRenderedPosition; public bool HasRenderedPosition; public byte HoldState; public readonly List HoldTypes = new List(); public List HoldCards; public readonly Snapshot[] Snaps = new Snapshot[4]; public int SnapHead = -1; public int SnapCount; public string HeldSig = ""; public string CardSig = ""; public string PendingCardSig = ""; public string PendingItemSig = ""; public readonly List HeldItems = new List(); public readonly List HeldCards3d = new List(); public GameObject BinderProp; public Item PackProp; public float PackTimer; public float EmoteTimer; public bool EverPositioned; public bool HasState; public bool HoldingBoxPose; public bool HoldingBoxPoseSet; public bool HasModel; public bool Female; public int ModelIndex; public string CustomizationJson; } internal Action RemoteAvatarsChanged; private const int SnapBufferSize = 4; private const float InterpDelay = 2f / 15f; private const float MaxExtrapolation = 0.25f; private static readonly int MoveSpeedHash = Animator.StringToHash("MoveSpeed"); private static readonly int IsHoldingBoxHash = Animator.StringToHash("IsHoldingBox"); private readonly Dictionary _avatars = new Dictionary(); private bool _loggedAnimParams; private GameObject _editorHolder; private CharacterCustomization _editorCustomization; private bool _editorFemale; private GameObject _previewBody; private CharacterCustomization _previewCustomization; private string _previewSignature; private const string ApparelTintPrefix = "CardShopCoop.ApparelTint."; public static Transform ViewCamera; private static TMP_FontAsset _tagFont; internal int Count => _avatars.Count; public void SetName(int connId, string name) { if (string.IsNullOrEmpty(name)) { return; } if (_avatars.TryGetValue(connId, out var value)) { if (!(value.Name == name)) { value.Name = name; if ((Object)(object)value.NameTag != (Object)null) { value.NameTag.text = name; } if ((Object)(object)value.Go != (Object)null) { ((Object)value.Go).name = "CoopAvatar_" + name; } } } else { _avatars[connId] = new RemoteAvatar { Name = name }; } } public void SetModel(int connId, bool female, int modelIndex, string customizationJson) { if (!_avatars.TryGetValue(connId, out var value)) { value = new RemoteAvatar(); _avatars[connId] = value; } bool num = !value.HasModel || value.Female != female || value.ModelIndex != modelIndex || value.CustomizationJson != customizationJson; value.HasModel = true; value.Female = female; value.ModelIndex = modelIndex; value.CustomizationJson = customizationJson; if (num && (Object)(object)value.Go != (Object)null) { DespawnBody(value); } } public PresenceModelEntry CaptureLocalModel(Transform root) { PresenceModelEntry presenceModelEntry = new PresenceModelEntry { Female = false, ModelIndex = 0, CustomizationJson = null }; CharacterCustomization val = FindCustomization(root); if ((Object)(object)val == (Object)null) { return presenceModelEntry; } string text = val.CharacterName ?? ""; presenceModelEntry.Female = text.StartsWith("Female", StringComparison.OrdinalIgnoreCase); int num = (presenceModelEntry.Female ? 6 : 4); if (text.Length > num && int.TryParse(text.Substring(num), out var result)) { presenceModelEntry.ModelIndex = result; } if (val.StoredCharacterData != null) { presenceModelEntry.CustomizationJson = JsonConvert.SerializeObject((object)val.StoredCharacterData); } return presenceModelEntry; } public PresenceModelEntry CaptureLocalModel(CharacterCustomization custom, bool female, int modelIndex) { PresenceModelEntry presenceModelEntry = new PresenceModelEntry { Female = female, ModelIndex = modelIndex, CustomizationJson = null }; if ((Object)(object)custom != (Object)null && custom.StoredCharacterData != null) { presenceModelEntry.CustomizationJson = JsonConvert.SerializeObject((object)custom.StoredCharacterData); } return presenceModelEntry; } public void ApplyLocalModel(Transform root, PresenceModelEntry model) { CharacterCustomization custom = FindCustomization(root); ApplyLocalModel(custom, model); } public void ApplyLocalModel(CharacterCustomization custom, PresenceModelEntry model) { custom.CharacterName = (model.Female ? "Female" : "Male") + model.ModelIndex; custom.Initialize(); if (!string.IsNullOrEmpty(model.CustomizationJson)) { CC_CharacterData data = (custom.StoredCharacterData = JsonConvert.DeserializeObject(model.CustomizationJson)); ApplyCharacterData(custom, data); ClearEmptyWardrobeSlots(custom, data); } } private static void ApplyCharacterData(CharacterCustomization custom, CC_CharacterData data) { List list = ((data.ColorProperties != null) ? data.ColorProperties.FindAll((CC_Property p) => p != null && p.propertyName != null && p.propertyName.StartsWith("CardShopCoop.ApparelTint.", StringComparison.Ordinal)) : new List()); if (data.ColorProperties != null) { data.ColorProperties.RemoveAll((CC_Property p) => p != null && p.propertyName != null && p.propertyName.StartsWith("CardShopCoop.ApparelTint.", StringComparison.Ordinal)); } custom.ApplyCharacterVars(data); if (data.ColorProperties != null) { data.ColorProperties.AddRange(list); } ApplyStoredApparelTints(custom, list); } private static void ApplyStoredApparelTints(CharacterCustomization custom, List tints) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)custom == (Object)null || tints == null) { return; } Color val = default(Color); foreach (CC_Property tint in tints) { if (tint == null || !int.TryParse(tint.propertyName.Substring("CardShopCoop.ApparelTint.".Length), out var result) || !ColorUtility.TryParseHtmlString("#" + tint.stringValue, ref val)) { continue; } FieldInfo field = typeof(CharacterCustomization).GetField("ApparelObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(custom) as List) : null); if (list == null || result < 0 || result >= list.Count || (Object)(object)list[result] == (Object)null) { continue; } Renderer[] componentsInChildren = list[result].GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Material[] materials = componentsInChildren[i].materials; foreach (Material obj in materials) { obj.SetColor("_Tint", val); obj.SetColor("_Tint_R", val); obj.SetColor("_Tint_G", val); obj.SetColor("_Tint_B", val); } } } } private static void ClearEmptyWardrobeSlots(CharacterCustomization custom, CC_CharacterData data) { if ((Object)(object)custom == (Object)null || data == null) { return; } for (int i = 0; i < custom.HairTables.Count; i++) { if (i >= data.HairNames.Count || string.IsNullOrEmpty(data.HairNames[i])) { ClearHairOverlay(custom, i); } } for (int j = 0; j < custom.ApparelTables.Count; j++) { if (j >= data.ApparelNames.Count || string.IsNullOrEmpty(data.ApparelNames[j])) { ClearApparelOverlay(custom, j); } } } private static void ClearAllApparel(CharacterCustomization custom) { if (!((Object)(object)custom == (Object)null) && custom.ApparelTables != null) { for (int i = 0; i < custom.ApparelTables.Count; i++) { ClearApparelOverlay(custom, i); } } } private static void ClearHairOverlay(CharacterCustomization custom, int slot) { if (!((Object)(object)custom == (Object)null) && slot >= 0 && slot < custom.HairTables.Count) { FieldInfo field = typeof(CharacterCustomization).GetField("HairObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(custom) as List) : null); if (list != null && slot < list.Count && (Object)(object)list[slot] != (Object)null) { Object.Destroy((Object)(object)list[slot]); list[slot] = null; } if (custom.StoredCharacterData != null && slot < custom.StoredCharacterData.HairNames.Count) { custom.StoredCharacterData.HairNames[slot] = ""; } } } private static void ClearApparelOverlay(CharacterCustomization custom, int slot) { if ((Object)(object)custom == (Object)null || slot < 0 || slot >= custom.ApparelTables.Count) { return; } FieldInfo field = typeof(CharacterCustomization).GetField("ApparelObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(custom) as List) : null); if (list != null && slot < list.Count && (Object)(object)list[slot] != (Object)null) { Object.Destroy((Object)(object)list[slot]); list[slot] = null; } ResetApparelMask(custom, slot); if (custom.StoredCharacterData != null && slot < custom.StoredCharacterData.ApparelNames.Count) { custom.StoredCharacterData.ApparelNames[slot] = ""; if (slot < custom.StoredCharacterData.ApparelMaterials.Count) { custom.StoredCharacterData.ApparelMaterials[slot] = 0; } } } private static void ResetApparelMask(CharacterCustomization custom, int slot) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown if (!((Object)(object)custom == (Object)null) && custom.ApparelTables != null && slot >= 0 && slot < custom.ApparelTables.Count) { scrObj_Apparel val = custom.ApparelTables[slot]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.MaskProperty)) { custom.setTextureProperty(new CC_Property { propertyName = val.MaskProperty }, false, Texture2D.whiteTexture); } } } public static bool IsNude(PresenceModelEntry model) { if (model == null) { return true; } return IsNude(model.CustomizationJson); } public static bool IsNude(string customizationJson) { if (string.IsNullOrEmpty(customizationJson)) { return true; } CC_CharacterData data; try { data = JsonConvert.DeserializeObject(customizationJson); } catch (Exception e) { Swallow.Log(e, "IsNude", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 417); return true; } return IsNude(data); } public static bool IsNude(CC_CharacterData data) { if (data == null || data.ApparelNames == null || data.ApparelNames.Count == 0) { return true; } for (int i = 0; i < data.ApparelNames.Count; i++) { if (!IsNudeApparelName(data.ApparelNames[i])) { return false; } } return true; } private static bool IsNudeApparelName(string name) { if (!string.IsNullOrEmpty(name) && !name.Equals("None", StringComparison.OrdinalIgnoreCase)) { return name.Equals("Nude", StringComparison.OrdinalIgnoreCase); } return true; } private static void ClotheNudeSlots(CharacterCustomization custom, CC_CharacterData data) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)custom == (Object)null || data == null || custom.ApparelTables == null) { return; } if (data.ApparelNames == null) { data.ApparelNames = new List(); } while (data.ApparelNames.Count < custom.ApparelTables.Count) { data.ApparelNames.Add(""); } for (int i = 0; i < custom.ApparelTables.Count && i < data.ApparelNames.Count; i++) { if (IsNudeApparelName(data.ApparelNames[i])) { scrObj_Apparel val = custom.ApparelTables[i]; if (!((Object)(object)val == (Object)null) && val.Items != null && val.Items.Count != 0) { data.ApparelNames[i] = val.Items[0].Name ?? ""; } } } } public static PresenceModelEntry ModelFromPreset(CC_CharacterData preset) { if (preset == null) { return null; } bool female = (preset.CharacterName ?? "").StartsWith("Female", StringComparison.OrdinalIgnoreCase); return new PresenceModelEntry { Female = female, ModelIndex = ParsePresetIndex(preset.CharacterName, female), CustomizationJson = JsonConvert.SerializeObject((object)preset) }; } private static int ParsePresetIndex(string characterName, bool female) { int num = (female ? 6 : 4); if (!string.IsNullOrEmpty(characterName) && characterName.Length > num && int.TryParse(characterName.Substring(num), out var result)) { return result; } return 0; } public PresenceModelEntry TryCreateRandomPresetModel(bool female) { CharacterCustomization editorCustomization = GetEditorCustomization(female); if ((Object)(object)editorCustomization == (Object)null) { return null; } string value = (female ? "Female" : "Male"); List list = (((Object)(object)editorCustomization.Presets != (Object)null) ? editorCustomization.Presets.Presets : null); if (list != null && list.Count > 0) { List list2 = new List(); foreach (CC_CharacterData item in list) { if (item != null && (item.CharacterName ?? "").StartsWith(value, StringComparison.OrdinalIgnoreCase)) { list2.Add(item); } } if (list2.Count > 0) { return ModelFromPreset(list2[Random.Range(0, list2.Count)]); } } return ModelFromPreset(editorCustomization.StoredCharacterData); } public PresenceModelEntry TryCreateRandomModel() { return TryCreateRandomPresetModel(Random.value < 0.5f); } public PresenceModelEntry CreateDefaultModel() { return new PresenceModelEntry { Female = (Random.value < 0.5f), ModelIndex = 0, CustomizationJson = null }; } public List GetLocalBlendshapes(Transform root) { CharacterCustomization val = FindCustomization(root); if ((Object)(object)val == (Object)null || val.StoredCharacterData == null) { return new List(); } return val.StoredCharacterData.Blendshapes ?? new List(); } public List GetBlendshapes(CharacterCustomization custom) { if ((Object)(object)custom == (Object)null || custom.StoredCharacterData == null) { return new List(); } return custom.StoredCharacterData.Blendshapes ?? new List(); } public CharacterCustomization GetLocalCustomization(Transform root) { return FindCustomization(root); } public CharacterCustomization GetEditorCustomization(bool female) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown if ((Object)(object)_editorCustomization != (Object)null && _editorFemale == female) { return _editorCustomization; } if ((Object)(object)_editorHolder != (Object)null) { Object.DestroyImmediate((Object)(object)_editorHolder); } _editorCustomization = null; CustomerManager val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return null; } Customer val2 = (female ? val.m_CustomerFemalePrefab : val.m_CustomerPrefab); if ((Object)(object)val2 == (Object)null) { return null; } _editorHolder = new GameObject("CoopCharacterEditorTemplate"); _editorHolder.SetActive(false); GameObject val3 = Object.Instantiate(((Component)val2).gameObject, _editorHolder.transform); Customer component = val3.GetComponent(); _editorCustomization = (((Object)(object)component != (Object)null) ? component.m_CharacterCustom : val3.GetComponentInChildren(true)); SceneComponentRegistry.Register(_editorCustomization); _editorFemale = female; if ((Object)(object)_editorCustomization != (Object)null) { _editorCustomization.CharacterName = (female ? "Female" : "Male") + "0"; _editorCustomization.Initialize(); CoopPlugin.Log.LogInfo((object)string.Format("character editor template: {0}, hair slots={1}, apparel slots={2}, presets={3}", female ? "female" : "male", _editorCustomization.HairTables.Count, _editorCustomization.ApparelTables.Count, ((Object)(object)_editorCustomization.Presets != (Object)null && _editorCustomization.Presets.Presets != null) ? _editorCustomization.Presets.Presets.Count : 0)); } return _editorCustomization; } public List GetBlendshapes(Transform root, bool female) { return GetBlendshapes(GetEditorCustomization(female)); } public bool SetLocalBlendshape(Transform root, string propertyName, float value) { CharacterCustomization val = FindCustomization(root); if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(propertyName)) { return false; } val.setBlendshapeByName(propertyName, Mathf.Clamp01(value), true); return true; } public bool SetBlendshape(CharacterCustomization custom, string propertyName, float value) { if ((Object)(object)custom == (Object)null || string.IsNullOrEmpty(propertyName)) { return false; } custom.setBlendshapeByName(propertyName, Mathf.Clamp01(value), true); return true; } public bool SetHairColor(CharacterCustomization custom, int slot, Color color) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown if ((Object)(object)custom == (Object)null || slot < 0 || custom.HairTables == null || slot >= custom.HairTables.Count || custom.StoredCharacterData == null) { return false; } custom.setHairColor(new CC_Property { propertyName = "_Hair_Color" }, color, slot, true); return true; } public Color GetHairColor(CharacterCustomization custom, int slot) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)custom == (Object)null || custom.StoredCharacterData == null || slot < 0 || custom.StoredCharacterData.HairColor == null || slot >= custom.StoredCharacterData.HairColor.Count) { return Color.white; } Color result = default(Color); if (!ColorUtility.TryParseHtmlString("#" + custom.StoredCharacterData.HairColor[slot].stringValue, ref result)) { return Color.white; } return result; } public bool SetApparelTint(CharacterCustomization custom, int slot, Color color) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown if ((Object)(object)custom == (Object)null || custom.StoredCharacterData == null || slot < 0 || custom.ApparelTables == null || slot >= custom.ApparelTables.Count) { return false; } FieldInfo field = typeof(CharacterCustomization).GetField("ApparelObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(custom) as List) : null); if (list == null || slot >= list.Count || (Object)(object)list[slot] == (Object)null) { return false; } Renderer[] componentsInChildren = list[slot].GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Material[] materials = componentsInChildren[i].materials; foreach (Material obj in materials) { obj.SetColor("_Tint", color); obj.SetColor("_Tint_R", color); obj.SetColor("_Tint_G", color); obj.SetColor("_Tint_B", color); } } string propertyName = "CardShopCoop.ApparelTint." + slot; string stringValue = ColorUtility.ToHtmlStringRGBA(color); List list2 = custom.StoredCharacterData.ColorProperties; if (list2 == null) { list2 = (custom.StoredCharacterData.ColorProperties = new List()); } CC_Property val = list2.Find((CC_Property p) => p != null && p.propertyName == propertyName); if (val == null) { list2.Add(new CC_Property { propertyName = propertyName, stringValue = stringValue }); } else { val.stringValue = stringValue; } return true; } public bool ClearLocalHair(Transform root, int slot) { CharacterCustomization val = FindCustomization(root); if ((Object)(object)val == (Object)null || slot < 0 || slot >= val.HairTables.Count) { return false; } FieldInfo field = typeof(CharacterCustomization).GetField("HairObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(val) as List) : null); if (list != null && slot < list.Count && (Object)(object)list[slot] != (Object)null) { Object.Destroy((Object)(object)list[slot]); list[slot] = null; } if (val.StoredCharacterData != null && slot < val.StoredCharacterData.HairNames.Count) { val.StoredCharacterData.HairNames[slot] = ""; } return true; } public bool ClearHair(CharacterCustomization custom, int slot) { if ((Object)(object)custom == (Object)null || slot < 0 || slot >= custom.HairTables.Count) { return false; } FieldInfo field = typeof(CharacterCustomization).GetField("HairObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(custom) as List) : null); if (list != null && slot < list.Count && (Object)(object)list[slot] != (Object)null) { Object.Destroy((Object)(object)list[slot]); list[slot] = null; } if (custom.StoredCharacterData != null && slot < custom.StoredCharacterData.HairNames.Count) { custom.StoredCharacterData.HairNames[slot] = ""; } return true; } public bool ClearLocalApparel(Transform root, int slot) { CharacterCustomization val = FindCustomization(root); if ((Object)(object)val == (Object)null || slot < 0 || slot >= val.ApparelTables.Count) { return false; } FieldInfo field = typeof(CharacterCustomization).GetField("ApparelObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(val) as List) : null); if (list != null && slot < list.Count && (Object)(object)list[slot] != (Object)null) { Object.Destroy((Object)(object)list[slot]); list[slot] = null; } ResetApparelMask(val, slot); if (val.StoredCharacterData != null && slot < val.StoredCharacterData.ApparelNames.Count) { val.StoredCharacterData.ApparelNames[slot] = ""; } return true; } public bool ClearApparel(CharacterCustomization custom, int slot) { if ((Object)(object)custom == (Object)null || slot < 0 || slot >= custom.ApparelTables.Count) { return false; } FieldInfo field = typeof(CharacterCustomization).GetField("ApparelObjects", BindingFlags.Instance | BindingFlags.NonPublic); List list = ((field != null) ? (field.GetValue(custom) as List) : null); if (list != null && slot < list.Count && (Object)(object)list[slot] != (Object)null) { Object.Destroy((Object)(object)list[slot]); list[slot] = null; } ResetApparelMask(custom, slot); if (custom.StoredCharacterData != null && slot < custom.StoredCharacterData.ApparelNames.Count) { custom.StoredCharacterData.ApparelNames[slot] = ""; } return true; } private static CharacterCustomization FindCustomization(Transform root) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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) //IL_006c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)root == (Object)null) { return null; } CharacterCustomization componentInChildren = ((Component)root).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren; } componentInChildren = ((Component)root).GetComponentInParent(); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren; } List list = SceneComponentRegistry.SnapshotAll(activeOnly: false); float num = 9f; for (int i = 0; i < list.Count; i++) { if (!((Object)(object)list[i] == (Object)null)) { Vector3 val = ((Component)list[i]).transform.position - root.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; componentInChildren = list[i]; } } } return componentInChildren; } public void UpdateState(int connId, Vector3 pos, float yaw, Vector3 cameraPosition = default(Vector3), Quaternion cameraRotation = default(Quaternion)) { //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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) if (!_avatars.TryGetValue(connId, out var value)) { value = new RemoteAvatar(); _avatars[connId] = value; } float time = Time.time; float num = time - value.LastStateTime; if (value.HasState && num > 0.01f && num < 1f) { Vector3 val = (pos - value.TargetPos) / num; val.y = 0f; value.Velocity = Vector3.ClampMagnitude(val, 6f); } else { value.Velocity = Vector3.zero; } if (value.HasCamera) { value.PreviousCameraPosition = value.CameraPosition; value.PreviousCameraRotation = value.CameraRotation; } else { value.PreviousCameraPosition = cameraPosition; value.PreviousCameraRotation = cameraRotation; } value.LastStateTime = time; value.TargetPos = pos; value.TargetYaw = yaw; value.CameraPosition = cameraPosition; value.CameraRotation = ((cameraRotation == default(Quaternion)) ? Quaternion.identity : cameraRotation); value.HasCamera = cameraRotation != default(Quaternion); value.HasState = true; value.SnapHead = (value.SnapHead + 1) % 4; value.Snaps[value.SnapHead] = new Snapshot { Pos = pos, Yaw = yaw, RecvTime = time }; if (value.SnapCount < 4) { value.SnapCount++; } if (!value.EverPositioned && (Object)(object)value.Go != (Object)null) { value.Go.transform.position = pos; value.EverPositioned = true; } } public void UpdateHold(int connId, byte holdState, List holdTypes, List holdCards) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected I4, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected I4, but got Unknown if (!_avatars.TryGetValue(connId, out var value)) { value = new RemoteAvatar(); _avatars[connId] = value; } value.HoldState = holdState; value.HoldTypes.Clear(); if (holdTypes != null) { for (int i = 0; i < holdTypes.Count; i++) { value.HoldTypes.Add(holdTypes[i]); } } value.HoldCards = ((holdCards == null) ? null : new List(holdCards)); if (holdState == 3 && holdCards != null && holdCards.Count > 0) { StringBuilder stringBuilder = new StringBuilder(); foreach (CardData holdCard in holdCards) { stringBuilder.Append((int)holdCard.monsterType).Append('/').Append((int)holdCard.expansionType) .Append('/') .Append(holdCard.isFoil ? 1 : 0) .Append(';'); } value.PendingCardSig = stringBuilder.ToString(); } else { value.PendingCardSig = ""; } value.PendingItemSig = ((holdState == 2 && value.HoldTypes.Count > 0) ? string.Join(",", value.HoldTypes) : ""); } public bool TryGetPlacementCamera(int connId, out Vector3 position, out Quaternion rotation) { //IL_006b: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (_avatars.TryGetValue(connId, out var value) && value.HasState && value.HasCamera) { float num = Mathf.Clamp01((Time.time - value.LastStateTime) / (1f / 15f)); position = Vector3.Lerp(value.PreviousCameraPosition, value.CameraPosition, num); rotation = Quaternion.Slerp(value.PreviousCameraRotation, value.CameraRotation, num); return true; } position = default(Vector3); rotation = Quaternion.identity; return false; } public void ShowEmote(int connId) { ShowTag(connId, "\\o/ hi!", 2.5f); } public void ShowTag(int connId, string text, float seconds) { if (_avatars.TryGetValue(connId, out var value) && (Object)(object)value.EmoteTag != (Object)null) { value.EmoteTag.text = text; value.EmoteTimer = seconds; } } public void Remove(int connId) { if (_avatars.TryGetValue(connId, out var value)) { ReleaseHeld(value); DestroyBody(value); _avatars.Remove(connId); } } public void Clear() { foreach (RemoteAvatar value in _avatars.Values) { DespawnBody(value); } _avatars.Clear(); if ((Object)(object)_editorHolder != (Object)null) { Object.Destroy((Object)(object)_editorHolder); } _editorHolder = null; _editorCustomization = null; DestroyPreview(); } public void UpdatePreview(PresenceModelEntry model, Transform player, Transform camera, bool visible) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if (!visible || model == null || (Object)(object)player == (Object)null || (Object)(object)camera == (Object)null) { DestroyPreview(); return; } string text = (model.Female ? "F" : "M") + model.ModelIndex + ":" + model.CustomizationJson; if ((Object)(object)_previewBody == (Object)null || _previewSignature != text) { DestroyPreview(); SpawnPreview(model); _previewSignature = text; } if (!((Object)(object)_previewBody == (Object)null)) { Vector3 forward = camera.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.001f) { forward = player.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val = player.position + forward * 1.6f; val.y = player.position.y; _previewBody.transform.position = val; Vector3 val2 = player.position - val; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude > 0.001f) { _previewBody.transform.rotation = Quaternion.LookRotation(((Vector3)(ref val2)).normalized, Vector3.up); } } } public void DestroyPreview() { if ((Object)(object)_previewBody != (Object)null) { Object.Destroy((Object)(object)_previewBody); } _previewBody = null; _previewCustomization = null; _previewSignature = null; } private void SpawnPreview(PresenceModelEntry model) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown CustomerManager val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return; } Customer val2 = (model.Female ? val.m_CustomerFemalePrefab : val.m_CustomerPrefab); if ((Object)(object)val2 == (Object)null) { return; } GameObject val3 = new GameObject("CoopCharacterPreviewHolder_tmp"); val3.SetActive(false); GameObject val4 = Object.Instantiate(((Component)val2).gameObject, val3.transform); val4.transform.SetParent((Transform)null, false); val4.SetActive(true); Object.Destroy((Object)(object)val3); Customer component = val4.GetComponent(); HideCustomerProps(component, "character preview"); _previewCustomization = (((Object)(object)component != (Object)null) ? component.m_CharacterCustom : val4.GetComponentInChildren(true)); if ((Object)(object)_previewCustomization != (Object)null) { _previewCustomization.CharacterName = (model.Female ? "Female" : "Male") + model.ModelIndex; _previewCustomization.Initialize(); if (!string.IsNullOrEmpty(model.CustomizationJson)) { CC_CharacterData val5 = JsonConvert.DeserializeObject(model.CustomizationJson); if (val5 != null && (CoopPlugin.AllowNsfw.Value || !IsNude(val5))) { _previewCustomization.StoredCharacterData = val5; ApplyCharacterData(_previewCustomization, val5); ClearEmptyWardrobeSlots(_previewCustomization, val5); } } } MonoBehaviour[] componentsInChildren = val4.GetComponentsInChildren(true); foreach (MonoBehaviour val6 in componentsInChildren) { if (!((Object)(object)val6 == (Object)null)) { switch (((object)val6).GetType().Name) { case "CopyPose": case "BlendshapeManager": case "ScaleCharacter": case "CharacterCustomization": case "TransformBone": case "MipBiasAdjust": continue; } Object.DestroyImmediate((Object)(object)val6); } } Collider[] componentsInChildren2 = val4.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren2[i]); } Rigidbody[] componentsInChildren3 = val4.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren3.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren3[i]); } ((Object)val4).name = "CoopCharacterPreview"; _previewBody = val4; } private void DespawnBody(RemoteAvatar av) { if (av != null && !((Object)(object)av.Go == (Object)null)) { ReleaseHeld(av); DestroyBody(av); av.Go = null; av.Anim = null; av.HasMoveSpeed = false; av.HasHoldingBox = false; av.HoldingBoxPoseSet = false; av.EverPositioned = false; } } public void RefreshRemoteAvatars() { foreach (RemoteAvatar value in _avatars.Values) { DespawnBody(value); } } private static void DestroyBody(RemoteAvatar av) { if ((Object)(object)av.CarryAnchor != (Object)null) { for (int num = av.CarryAnchor.childCount - 1; num >= 0; num--) { av.CarryAnchor.GetChild(num).SetParent((Transform)null, true); } } if ((Object)(object)av.Go != (Object)null) { Object.Destroy((Object)(object)av.Go); } } internal bool TryGetCarryAnchor(int avatarId, out Transform anchor) { anchor = null; if (!_avatars.TryGetValue(avatarId, out var value) || (Object)(object)value.Go == (Object)null) { return false; } if ((Object)(object)value.CarryAnchor == (Object)null) { value.CarryAnchor = CreateCarryAnchor(value); } anchor = value.CarryAnchor; return (Object)(object)anchor != (Object)null; } private static Transform CreateCarryAnchor(RemoteAvatar av) { //IL_0048: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) Transform val = null; try { val = (((Object)(object)av.Anim != (Object)null) ? av.Anim.GetBoneTransform((HumanBodyBones)8) : null); } catch (Exception e) { Swallow.Log(e, "CreateCarryAnchor", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1242); } if ((Object)(object)val == (Object)null) { return null; } GameObject val2 = new GameObject("CoopCarryAnchor"); val2.transform.SetParent(val, false); Transform transform = av.Go.transform; Vector3 val3 = val.position - transform.up * 0.05f + transform.forward * 0.42f; val2.transform.localPosition = val.InverseTransformPoint(val3); val2.transform.localRotation = Quaternion.identity; return val2.transform; } private static void ReleaseItems(RemoteAvatar av) { foreach (Item heldItem in av.HeldItems) { if ((Object)(object)heldItem != (Object)null) { try { heldItem.DisableItem(); } catch (Exception e) { Swallow.Log(e, "ReleaseItems", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1271); } } } av.HeldItems.Clear(); av.HeldSig = ""; } private static void ReleaseHeld(RemoteAvatar av) { ReleaseItems(av); ReleaseCards(av); if ((Object)(object)av.PackProp != (Object)null) { try { av.PackProp.DisableItem(); } catch (Exception e) { Swallow.Log(e, "ReleaseHeld", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1289); } av.PackProp = null; } if ((Object)(object)av.BinderProp != (Object)null) { Object.Destroy((Object)(object)av.BinderProp); av.BinderProp = null; } } private static void HideCustomerProps(Customer customer, string context) { if ((Object)(object)customer == (Object)null) { return; } try { if ((Object)(object)customer.m_ShoppingBagTransform != (Object)null) { ((Component)customer.m_ShoppingBagTransform).gameObject.SetActive(false); } if ((Object)(object)customer.m_CustomerCash != (Object)null) { ((Component)customer.m_CustomerCash).gameObject.SetActive(false); } if ((Object)(object)customer.m_GameCardFanOut != (Object)null) { customer.m_GameCardFanOut.SetActive(false); } if ((Object)(object)customer.m_GameCardSingle != (Object)null) { customer.m_GameCardSingle.SetActive(false); } if ((Object)(object)customer.m_CleanFX != (Object)null) { customer.m_CleanFX.SetActive(false); } if ((Object)(object)customer.m_ExclaimationMesh != (Object)null) { customer.m_ExclaimationMesh.SetActive(false); } if ((Object)(object)customer.m_InteractCollider != (Object)null) { customer.m_InteractCollider.SetActive(false); } if ((Object)(object)customer.m_SmellyFX != (Object)null) { customer.m_SmellyFX.SetActive(false); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Customer prop hiding partial (" + context + "): " + ex.Message)); } } private static void ReleaseCards(RemoteAvatar av) { foreach (InteractableCard3d item in av.HeldCards3d) { if ((Object)(object)item != (Object)null) { try { ((InteractableObject)item).OnDestroyed(); } catch (Exception e) { Swallow.Log(e, "ReleaseCards", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1365); } } } av.HeldCards3d.Clear(); av.CardSig = ""; } public void ShowPackOpen(int connId, int packIndex) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) if (!_avatars.TryGetValue(connId, out var value) || (Object)(object)value.Go == (Object)null) { return; } value.PackTimer = 4f; try { if ((Object)(object)value.Anim != (Object)null) { value.Anim.SetTrigger("GrabItem"); } } catch (Exception e) { Swallow.Log(e, "ShowPackOpen", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1390); } if (!((Object)(object)value.PackProp == (Object)null) || packIndex < 0) { return; } try { ItemMeshData itemMeshData = InventoryBase.GetItemMeshData((EItemType)packIndex); if (itemMeshData != null) { Item item = ItemSpawnManager.GetItem(value.Go.transform); item.SetMesh(itemMeshData.mesh, itemMeshData.material, (EItemType)packIndex, itemMeshData.meshSecondary, itemMeshData.materialSecondary, itemMeshData.materialList); ((Component)item).transform.localPosition = new Vector3(0f, 1.15f, 0.4f); ((Component)item).transform.localRotation = Quaternion.Euler(35f, 0f, 0f); ((Component)item).gameObject.SetActive(true); if ((Object)(object)item.m_Rigidbody != (Object)null) { item.m_Rigidbody.isKinematic = true; } if ((Object)(object)item.m_Collider != (Object)null) { ((Collider)item.m_Collider).enabled = false; } value.PackProp = item; } else { CoopPlugin.Log.LogInfo((object)$"pack-open visual: no mesh for pack index {packIndex}"); } } catch (Exception e2) { Swallow.Log(e2, "ShowPackOpen", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1426); } } public void Tick(float dt) { if (!CoopPlugin.AvatarsEnabled.Value) { return; } CGameManager val = SceneRef.Get(); if (!((Object)(object)val != (Object)null) || !val.m_IsGameLevel) { return; } using (PerfProbe.Sample("module.presence.avatar-update")) { TickInner(dt); } } private void TickInner(float dt) { //IL_0408: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_0443: Unknown result type (might be due to invalid IL or missing references) //IL_045b: Unknown result type (might be due to invalid IL or missing references) //IL_0769: Unknown result type (might be due to invalid IL or missing references) //IL_076f: Unknown result type (might be due to invalid IL or missing references) //IL_0774: Unknown result type (might be due to invalid IL or missing references) //IL_0779: Unknown result type (might be due to invalid IL or missing references) //IL_0690: Unknown result type (might be due to invalid IL or missing references) //IL_06b0: Unknown result type (might be due to invalid IL or missing references) //IL_07a7: Unknown result type (might be due to invalid IL or missing references) //IL_07ad: Unknown result type (might be due to invalid IL or missing references) //IL_07b2: Unknown result type (might be due to invalid IL or missing references) //IL_07b7: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) object obj; if (!((Object)(object)ViewCamera != (Object)null)) { Camera main = Camera.main; obj = ((main != null) ? ((Component)main).transform : null); } else { obj = ViewCamera; } Transform val = (Transform)obj; foreach (RemoteAvatar value in _avatars.Values) { if ((Object)(object)value.Go == (Object)null) { value.HasRenderedPosition = false; value.AnimSpeed = 0f; value.AppliedAnimSpeed = float.NaN; value.HasMoveSpeed = false; value.HasHoldingBox = false; value.HoldingBoxPoseSet = false; value.PackProp = null; value.BinderProp = null; value.CarryAnchor = null; value.HeldItems.Clear(); value.HeldCards3d.Clear(); value.HeldSig = ""; value.CardSig = ""; if (value.HasState) { long start = PerfProbe.Start(); try { TrySpawn(value); } finally { PerfProbe.End("avatar.spawn", start); } } continue; } Transform transform = value.Go.transform; Vector3 pos; float yaw; if (value.SnapCount > 0) { SampleSnapshots(value, Time.time - 2f / 15f, dt, out pos, out yaw); } else { pos = value.TargetPos; yaw = value.TargetYaw; } Vector3 val2 = transform.position - pos; bool flag = ((Vector3)(ref val2)).sqrMagnitude > 25f; float num = 1f - Mathf.Exp(-14f * dt); transform.position = (flag ? pos : Vector3.Lerp(transform.position, pos, num)); Quaternion val3 = Quaternion.Euler(0f, yaw, 0f); transform.rotation = (flag ? val3 : Quaternion.Slerp(transform.rotation, val3, num)); if ((Object)(object)value.Anim != (Object)null) { if (value.HasMoveSpeed) { if (!value.HasRenderedPosition) { value.PreviousRenderedPosition = transform.position; value.AnimSpeed = 0f; value.AppliedAnimSpeed = float.NaN; value.HasRenderedPosition = true; } else { Vector3 val4 = transform.position - value.PreviousRenderedPosition; val4.y = 0f; float num2 = (flag ? 0f : Mathf.Min(((Vector3)(ref val4)).magnitude / Mathf.Max(dt, 0.0001f), 6f)); value.PreviousRenderedPosition = transform.position; float num3 = ((num2 < value.AnimSpeed) ? 14f : 8f); value.AnimSpeed = Mathf.Lerp(value.AnimSpeed, num2, 1f - Mathf.Exp((0f - num3) * dt)); if (value.AnimSpeed < 0.05f) { value.AnimSpeed = 0f; } } if (float.IsNaN(value.AppliedAnimSpeed) || Mathf.Abs(value.AppliedAnimSpeed - value.AnimSpeed) > 0.01f) { value.Anim.SetFloat(MoveSpeedHash, value.AnimSpeed); value.AppliedAnimSpeed = value.AnimSpeed; } } if (value.HasHoldingBox) { bool flag2 = value.HoldState == 1; if (!value.HoldingBoxPoseSet || value.HoldingBoxPose != flag2) { value.Anim.SetBool(IsHoldingBoxHash, flag2); value.HoldingBoxPose = flag2; value.HoldingBoxPoseSet = true; } } } if (value.PendingCardSig != value.CardSig) { ReleaseCards(value); value.CardSig = value.PendingCardSig; if (value.CardSig.Length > 0 && value.HoldCards != null) { for (int i = 0; i < value.HoldCards.Count; i++) { Card3dUIGroup val5 = null; InteractableCard3d val6 = null; try { val5 = SceneRef.Get().GetCardUI(); val6 = ((Component)ShelfManager.SpawnInteractableObject((EObjectType)9)).GetComponent(); val5.m_CardUI.SetCardUI(value.HoldCards[i]); ((Component)val6).transform.SetParent(value.Go.transform, false); float num4 = (float)i - (float)(value.HoldCards.Count - 1) * 0.5f; ((Component)val6).transform.localPosition = new Vector3(num4 * 0.08f, 1.15f, 0.38f); ((Component)val6).transform.localRotation = Quaternion.Euler(30f, num4 * -9f, 0f); ((Component)val5).transform.position = ((Component)val6).transform.position; ((Component)val5).transform.rotation = ((Component)val6).transform.rotation; val6.SetCardUIFollow(val5); val6.SetEnableCollision(false); value.HeldCards3d.Add(val6); } catch (Exception e) { if ((Object)(object)val6 != (Object)null) { try { ((InteractableObject)val6).OnDestroyed(); } catch (Exception e2) { Swallow.Log(e2, "TickInner", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1600); } } if ((Object)(object)val5 != (Object)null) { try { val5.DisableCard(); } catch (Exception e3) { Swallow.Log(e3, "TickInner", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1611); } } Swallow.Log(e, "TickInner", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1614); } } } } if ((Object)(object)value.PackProp != (Object)null) { value.PackTimer -= dt; if (value.PackTimer <= 0f) { try { value.PackProp.DisableItem(); } catch (Exception e4) { Swallow.Log(e4, "TickInner", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1630); } value.PackProp = null; } } bool flag3 = value.HoldState == 4; if (flag3 && (Object)(object)value.BinderProp == (Object)null) { TrySpawnBinder(value); } if ((Object)(object)value.BinderProp != (Object)null && value.BinderProp.activeSelf != flag3) { value.BinderProp.SetActive(flag3); } if (value.PendingItemSig != value.HeldSig) { ReleaseItems(value); value.HeldSig = value.PendingItemSig; if (value.HeldSig.Length > 0) { for (int j = 0; j < value.HoldTypes.Count; j++) { try { if (value.HoldTypes[j] == -1) { continue; } ItemMeshData itemMeshData = InventoryBase.GetItemMeshData((EItemType)value.HoldTypes[j]); if (itemMeshData != null) { Item item = ItemSpawnManager.GetItem(value.Go.transform); item.SetMesh(itemMeshData.mesh, itemMeshData.material, (EItemType)value.HoldTypes[j], itemMeshData.meshSecondary, itemMeshData.materialSecondary, itemMeshData.materialList); ((Component)item).transform.localPosition = new Vector3(0f, 1.04f + 0.018f * (float)j, 0.36f + 0.055f * (float)j); ((Component)item).transform.localRotation = Quaternion.Euler(14f, 0f, 0f); ((Component)item).gameObject.SetActive(true); if ((Object)(object)item.m_Rigidbody != (Object)null) { item.m_Rigidbody.isKinematic = true; } if ((Object)(object)item.m_Collider != (Object)null) { ((Collider)item.m_Collider).enabled = false; } value.HeldItems.Add(item); } } catch (Exception e5) { Swallow.Log(e5, "TickInner", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceAvatarRenderer.cs", 1693); } } } } if ((Object)(object)val != (Object)null) { if ((Object)(object)value.NameTag != (Object)null) { value.NameTag.transform.rotation = Quaternion.LookRotation(value.NameTag.transform.position - val.position); } if ((Object)(object)value.EmoteTag != (Object)null) { value.EmoteTag.transform.rotation = Quaternion.LookRotation(value.EmoteTag.transform.position - val.position); } } if (value.EmoteTimer > 0f) { value.EmoteTimer -= dt; if (value.EmoteTimer <= 0f && (Object)(object)value.EmoteTag != (Object)null) { value.EmoteTag.text = ""; } } } } private static void SampleSnapshots(RemoteAvatar av, float renderTime, float dt, out Vector3 pos, out float yaw) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) Snapshot snapshot = av.Snaps[av.SnapHead]; if (renderTime >= snapshot.RecvTime) { av.Velocity *= Mathf.Exp(-3f * dt); float num = Mathf.Min(renderTime - snapshot.RecvTime, 0.25f); pos = snapshot.Pos + av.Velocity * num; yaw = snapshot.Yaw; return; } int num2 = (av.SnapHead - av.SnapCount + 1 + 4) % 4; Snapshot snapshot2 = av.Snaps[num2]; for (int i = 1; i < av.SnapCount; i++) { Snapshot snapshot3 = av.Snaps[(num2 + i) % 4]; if (renderTime <= snapshot3.RecvTime) { float num3 = snapshot3.RecvTime - snapshot2.RecvTime; float num4 = ((num3 > 0.0001f) ? ((renderTime - snapshot2.RecvTime) / num3) : 1f); pos = Vector3.Lerp(snapshot2.Pos, snapshot3.Pos, num4); yaw = Mathf.LerpAngle(snapshot2.Yaw, snapshot3.Yaw, num4); return; } snapshot2 = snapshot3; } pos = snapshot2.Pos; yaw = snapshot2.Yaw; } private void TrySpawn(RemoteAvatar av) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_04b2: Unknown result type (might be due to invalid IL or missing references) //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Invalid comparison between Unknown and I4 //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Invalid comparison between Unknown and I4 CustomerManager val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return; } int num = 17; string name = av.Name; foreach (char c in name) { num = num * 31 + c; } bool flag = (num & 1) == 1; Customer val2 = (flag ? val.m_CustomerFemalePrefab : val.m_CustomerPrefab); if (av.HasModel) { flag = av.Female; val2 = (flag ? val.m_CustomerFemalePrefab : val.m_CustomerPrefab); } if ((Object)(object)val2 == (Object)null) { val2 = (((Object)(object)val.m_CustomerPrefab != (Object)null) ? val.m_CustomerPrefab : val.m_CustomerFemalePrefab); } if ((Object)(object)val2 == (Object)null) { return; } GameObject val3 = new GameObject("CoopAvatarHolder_tmp"); val3.SetActive(false); GameObject val4 = Object.Instantiate(((Component)val2).gameObject, val3.transform); val4.transform.SetParent((Transform)null, false); val4.transform.position = av.TargetPos; val4.SetActive(true); Object.Destroy((Object)(object)val3); Customer component = val4.GetComponent(); if ((Object)(object)component != (Object)null) { bool flag2 = !string.IsNullOrEmpty(av.CustomizationJson); if (!av.HasModel) { component.RandomizeCharacterMesh(); } else { component.m_CharacterCustom.CharacterName = (flag ? "Female" : "Male") + av.ModelIndex; component.m_CharacterCustom.Initialize(); if (!flag2) { component.RandomizeCharacterMesh(); } else { CC_CharacterData val5 = JsonConvert.DeserializeObject(av.CustomizationJson); if (!CoopPlugin.AllowNsfw.Value) { ClotheNudeSlots(component.m_CharacterCustom, val5); } component.m_CharacterCustom.StoredCharacterData = val5; ApplyCharacterData(component.m_CharacterCustom, val5); ClearEmptyWardrobeSlots(component.m_CharacterCustom, val5); } } } HideCustomerProps(component, "remote avatar"); MonoBehaviour[] componentsInChildren = val4.GetComponentsInChildren(true); foreach (MonoBehaviour val6 in componentsInChildren) { if (!((Object)(object)val6 == (Object)null)) { switch (((object)val6).GetType().Name) { case "CopyPose": case "BlendshapeManager": case "ScaleCharacter": case "TransformBone": case "MipBiasAdjust": continue; } Object.DestroyImmediate((Object)(object)val6); } } Component[] componentsInChildren2 = val4.GetComponentsInChildren(true); foreach (Component val7 in componentsInChildren2) { if (!((Object)(object)val7 == (Object)null)) { switch (((object)val7).GetType().Name) { case "NavMeshAgent": case "NavMeshObstacle": case "Seeker": case "FunnelModifier": Object.DestroyImmediate((Object)(object)val7); break; } } } Collider[] componentsInChildren3 = val4.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren3.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren3[i]); } Rigidbody[] componentsInChildren4 = val4.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren4.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren4[i]); } ((Object)val4).name = "CoopAvatar_" + av.Name; av.Go = val4; av.Anim = val4.GetComponentInChildren(true); av.EverPositioned = true; av.HasMoveSpeed = false; av.HasHoldingBox = false; av.HasRenderedPosition = false; av.AnimSpeed = 0f; av.AppliedAnimSpeed = float.NaN; av.HoldingBoxPoseSet = false; if ((Object)(object)av.Anim != (Object)null) { AnimatorControllerParameter[] parameters = av.Anim.parameters; foreach (AnimatorControllerParameter val8 in parameters) { if (val8.name == "MoveSpeed" && (int)val8.type == 1) { av.HasMoveSpeed = true; } if (val8.name == "IsHoldingBox" && (int)val8.type == 4) { av.HasHoldingBox = true; } } if (!_loggedAnimParams) { _loggedAnimParams = true; StringBuilder stringBuilder = new StringBuilder("Avatar animator params: "); parameters = av.Anim.parameters; foreach (AnimatorControllerParameter val9 in parameters) { stringBuilder.Append(val9.name).Append(' '); } CoopPlugin.Log.LogInfo((object)stringBuilder.ToString()); } } float characterHeightFactor = GetCharacterHeightFactor(((Object)(object)component != (Object)null) ? component.m_CharacterCustom : null); av.NameTag = MakeTag(val4.transform, av.Name, 2.25f * characterHeightFactor, Color.white); av.EmoteTag = MakeTag(val4.transform, "", 2.55f * characterHeightFactor, new Color(1f, 0.85f, 0.2f)); CoopPlugin.Log.LogInfo((object)("Spawned co-op avatar for '" + av.Name + "' (" + (flag ? "female" : "male") + " model)")); RemoteAvatarsChanged?.Invoke(); } private static float GetCharacterHeightFactor(CharacterCustomization custom) { if ((Object)(object)custom == (Object)null || custom.StoredCharacterData == null || custom.StoredCharacterData.FloatProperties == null) { return 1f; } foreach (CC_Property floatProperty in custom.StoredCharacterData.FloatProperties) { if (floatProperty != null && !(floatProperty.propertyName != "Height")) { return floatProperty.floatValue; } } return 1f; } private static void TrySpawnBinder(RemoteAvatar av) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) try { List list = SceneComponentRegistry.Snapshot(SceneManager.GetActiveScene(), activeOnly: false); CollectionBinderFlipAnimCtrl val = ((list.Count > 0) ? list[0] : null); if ((Object)(object)val == (Object)null) { return; } GameObject val2 = new GameObject("CoopBinderHolder_tmp"); val2.SetActive(false); GameObject val3 = Object.Instantiate(((Component)val).gameObject, val2.transform); MonoBehaviour[] componentsInChildren = val3.GetComponentsInChildren(true); foreach (MonoBehaviour val4 in componentsInChildren) { if ((Object)(object)val4 != (Object)null) { Object.DestroyImmediate((Object)(object)val4); } } Collider[] componentsInChildren2 = val3.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren2[i]); } val3.transform.SetParent(av.Go.transform, false); val3.transform.localPosition = new Vector3(0f, 1.1f, 0.38f); val3.transform.localRotation = Quaternion.Euler(35f, 0f, 0f); val3.transform.localScale = Vector3.one * 0.8f; ((Object)val3).name = "CoopBinder"; val3.SetActive(true); Object.Destroy((Object)(object)val2); av.BinderProp = val3; } catch (Exception ex) { CoopPlugin.Log.LogInfo((object)("binder prop unavailable: " + ex.Message)); } } private static TMP_Text MakeTag(Transform parent, string text, float height, Color color) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("CoopTag"); val.transform.SetParent(parent, false); val.transform.localPosition = new Vector3(0f, height, 0f); TextMeshPro val2 = val.AddComponent(); ((TMP_Text)val2).text = text; ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; ((TMP_Text)val2).fontSize = 1.8f; ((Graphic)val2).color = color; UnityCompat.SetWordWrapping((TMP_Text)(object)val2, enabled: false); ((TMP_Text)val2).overflowMode = (TextOverflowModes)0; ((TMP_Text)val2).rectTransform.sizeDelta = new Vector2(4f, 1f); if ((Object)(object)_tagFont == (Object)null) { _tagFont = TMP_Settings.defaultFontAsset; if ((Object)(object)_tagFont == (Object)null) { List list = SceneComponentRegistry.SnapshotAll(activeOnly: false); for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null && (Object)(object)list[i].font != (Object)null) { _tagFont = list[i].font; break; } } } } if ((Object)(object)_tagFont != (Object)null) { ((TMP_Text)val2).font = _tagFont; } return (TMP_Text)(object)val2; } } [ClientBehaviour] public sealed class PresenceClientBehaviour : CoopBehaviour { private static PresenceClientBehaviour _active; private readonly HashSet _rosterIds = new HashSet(); private readonly HashSet _modelIds = new HashSet(); private CoopRuntimeContext _context; private PresenceService _service; private float _stateTimer; private int _selfId = -1; private bool _joined; private bool _modelPending; private bool _shutdown; private PresenceModelStateMessage _pendingModelState; private PresenceModelEntry _lastSentModel; internal static PresenceClientBehaviour Active => _active; internal bool IsLocalConnection(int connectionId) { if (_selfId > 0) { return connectionId == _selfId; } return false; } private void OnEnable() { if (_shutdown || _context != null) { return; } _context = base.RuntimeContext; _service = new PresenceService(_context); PresenceService service = _service; service.ModelChanged = (Action)Delegate.Combine(service.ModelChanged, new Action(OnLocalModelChanged)); bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Presence client initialization failed: " + ex)); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } _service.Shutdown(); _service = null; _context = null; throw; } } private void Update() { if (_shutdown) { return; } using (PerfProbe.Sample("module.presence.client-update")) { UpdateInner(); } } private void UpdateInner() { _service.Tick(Time.deltaTime); if (!_joined || !_context.InGame()) { return; } Func preloadHold = _context.PreloadHold; if (preloadHold == null || !preloadHold()) { if (_modelPending) { _modelPending = false; SendLocalModel(); } PublishLocalState(Time.deltaTime); } } [OnFullyJoined] private void MarkJoined(PeerConnection _) { if (!_shutdown) { _joined = true; _modelPending = true; _service.InvalidateLocalHold(); SendLocalModel(); } } [MessageHandler(typeof(PresenceRosterMessage))] private void HandleRoster(MessageContext context, PresenceRosterMessage message) { if (message == null) { return; } _selfId = message.SelfId; HashSet hashSet = new HashSet(); foreach (PresenceRosterEntry entry in message.Entries) { if (entry.Id != _selfId) { hashSet.Add(entry.Id); _rosterIds.Add(entry.Id); _service.SetName(1000 + entry.Id, entry.Name); } } List list = new List(); foreach (int rosterId in _rosterIds) { if (!hashSet.Contains(rosterId)) { list.Add(rosterId); } } foreach (int item in list) { _rosterIds.Remove(item); _service.Remove(1000 + item); } string text = _context.PeerName?.Invoke(1); _service.SetName(1, string.IsNullOrEmpty(text) ? "Host" : text); if (_selfId > 0 && _pendingModelState != null) { PresenceModelStateMessage pendingModelState = _pendingModelState; _pendingModelState = null; ApplyModelState(pendingModelState); } } [MessageHandler(typeof(PresenceRosterDeltaMessage))] private void HandleRosterDelta(MessageContext _, PresenceRosterDeltaMessage message) { if (message != null) { int id = 1000 + message.Id; if (message.Kind == PresenceEntityDeltaKind.Remove) { _rosterIds.Remove(message.Id); _service.Remove(id); } else if (message.Entry != null) { _rosterIds.Add(message.Id); _service.SetName(id, message.Entry.Name); } } } [MessageHandler(typeof(PresenceStateMessage))] private void HandleHostState(MessageContext context, PresenceStateMessage message) { if (message != null) { _service.ApplyState(1, message); } } [MessageHandler(typeof(PresenceRelayStateMessage))] private void HandleRelayState(MessageContext context, PresenceRelayStateMessage message) { if (message != null) { _service.ApplyState(1000 + message.SenderId, message.State); } } [MessageHandler(typeof(PresenceHoldMessage))] private void HandleHostHold(MessageContext context, PresenceHoldMessage message) { if (message != null) { _service.ApplyHold(1, message); } } [MessageHandler(typeof(PresenceRelayHoldMessage))] private void HandleRelayHold(MessageContext context, PresenceRelayHoldMessage message) { if (message?.Hold != null) { _service.ApplyHold(1000 + message.SenderId, message.Hold); } } [MessageHandler(typeof(PresenceModelStateMessage))] private void HandleModelState(MessageContext context, PresenceModelStateMessage message) { if (message != null) { if (_selfId <= 0) { _pendingModelState = message; } else { ApplyModelState(message); } } } private void ApplyModelState(PresenceModelStateMessage message) { if (message == null) { return; } HashSet hashSet = new HashSet(); foreach (PresenceModelEntry entry in message.Entries) { hashSet.Add(entry.Id); if (entry.Id == _selfId) { _lastSentModel = Clone(entry); _service.ApplyAuthoritativeLocalModel(entry); } else { int id = ((entry.Id == 0) ? 1 : (1000 + entry.Id)); _modelIds.Add(entry.Id); _service.SetModel(id, entry); } } List list = new List(); foreach (int modelId in _modelIds) { if (!hashSet.Contains(modelId)) { list.Add(modelId); } } foreach (int item in list) { _modelIds.Remove(item); _service.Remove((item == 0) ? 1 : (1000 + item)); } } [MessageHandler(typeof(PresenceModelDeltaMessage))] private void HandleModelDelta(MessageContext _, PresenceModelDeltaMessage message) { if (message == null) { return; } if (message.Kind == PresenceEntityDeltaKind.Remove) { _modelIds.Remove(message.Id); _service.Remove((message.Id == 0) ? 1 : (1000 + message.Id)); } else { if (message.Entry == null) { return; } if (message.Id == _selfId) { PredictionApi.AckOrApply(message.PredictionId, delegate { _service.ApplyPredictionLocalModel(message.Entry); _lastSentModel = Clone(message.Entry); }); } else { _modelIds.Add(message.Id); _service.SetModel((message.Id == 0) ? 1 : (1000 + message.Id), message.Entry); } } } [MessageHandler(typeof(PresenceRelayTagMessage))] private void HandleRelayTag(MessageContext context, PresenceRelayTagMessage message) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected I4, but got Unknown if (message == null) { return; } int id = 1000 + message.SenderId; if (message.Kind == 0) { _service.ShowEmote(id); return; } if (message.Kind == 1) { _service.ShowActivity(id, (int)message.Extra); return; } throw new ArgumentOutOfRangeException("Kind", message.Kind, "Unknown authoritative presence tag discriminator."); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { PresenceApi.ClearPeerNames(); _rosterIds.Clear(); _modelIds.Clear(); _joined = false; _service?.ClearRemoteAvatars(); } } internal void SendEmote() { if (CanSendIntent()) { _context.Send(1, (INetMessage)(object)new PresenceEmoteMessage { Emote = 0 }); } } internal void SendActivity(EItemType pack) { //IL_0021: 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) if (CanSendIntent()) { _context.Send(1, (INetMessage)(object)new PresenceActivityMessage { Activity = 1, Pack = pack }); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } if (_service != null) { PresenceService service = _service; service.ModelChanged = (Action)Delegate.Remove(service.ModelChanged, new Action(OnLocalModelChanged)); _service.Shutdown(); _service = null; } _rosterIds.Clear(); _modelIds.Clear(); _joined = false; _modelPending = false; _pendingModelState = null; _lastSentModel = null; _context = null; } } private void OnDestroy() { Shutdown(); } private void OnLocalModelChanged(PresenceModelEntry model) { if (model == null) { return; } if (CanSendIntent()) { _modelPending = false; PresenceModelEntry next = Clone(model); if (_lastSentModel == null) { _context.Send(1, (INetMessage)(object)new PresenceModelRequestMessage { PredictionId = Guid.Empty, Female = next.Female, ModelIndex = next.ModelIndex, CustomizationJson = next.CustomizationJson }); _lastSentModel = next; return; } PresenceModelEntry previous = Clone(_lastSentModel); PredictionApi.Predict("presence.model", delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new PresenceModelRequestMessage { PredictionId = predictionId, Female = next.Female, ModelIndex = next.ModelIndex, CustomizationJson = next.CustomizationJson }); }, delegate { _service.ApplyPredictionLocalModel(next); _lastSentModel = Clone(next); }, delegate { _service.ApplyPredictionLocalModel(previous); _lastSentModel = Clone(previous); }); _lastSentModel = next; } else if (_joined) { _modelPending = true; } } private void SendLocalModel() { if (_selfId <= 0) { _modelPending = true; } else { OnLocalModelChanged(_service.GetLocalModel()); } } private void PublishLocalState(float deltaTime) { _stateTimer += deltaTime; float num = 1f / Mathf.Clamp(CoopPlugin.SendRateHz.Value, 4f, 30f); if (!(_stateTimer < num) && _service.TryGetLocalState(out var state)) { _stateTimer = 0f; _context.Send(1, (INetMessage)(object)state); PublishLocalHold(); } } private void PublishLocalHold() { if (CanSendIntent() && _service.TryConsumeLocalHoldChange(out var hold)) { _context.Send(1, (INetMessage)(object)hold); } } private bool CanSendIntent() { if (!_shutdown && _joined && _selfId > 0 && _context.InGame()) { Func preloadHold = _context.PreloadHold; if (preloadHold == null) { return true; } return !preloadHold(); } return false; } private static PresenceModelEntry Clone(PresenceModelEntry model) { if (model != null) { return new PresenceModelEntry { Id = model.Id, Female = model.Female, ModelIndex = model.ModelIndex, CustomizationJson = model.CustomizationJson }; } return null; } } [ServerBehaviour] public sealed class PresenceHostBehaviour : CoopBehaviour { private const int MaxCustomizationJson = 524288; private static PresenceHostBehaviour _active; private readonly HashSet _joined = new HashSet(); private readonly Dictionary _models = new Dictionary(); private readonly Dictionary _latestStates = new Dictionary(); private readonly Dictionary _latestHolds = new Dictionary(); private CoopRuntimeContext _context; private PresenceService _service; private float _stateTimer; private bool _shutdown; internal static PresenceHostBehaviour Active => _active; private void OnEnable() { if (_shutdown || _context != null) { return; } _context = base.RuntimeContext; _service = new PresenceService(_context); PresenceService service = _service; service.ModelChanged = (Action)Delegate.Combine(service.ModelChanged, new Action(OnLocalModelChanged)); bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Presence host initialization failed: " + ex)); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } _service.Shutdown(); _service = null; _context = null; throw; } } private void Update() { if (_shutdown) { return; } using (PerfProbe.Sample("module.presence.host-update")) { UpdateInner(); } } private void UpdateInner() { _service.Tick(Time.deltaTime); if (_context.InGame()) { PublishLocalState(Time.deltaTime); } } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { if (!_shutdown && connection != null) { _joined.Add(connection.Id); ApplyPeerName(connection.Id); SendBaseline(connection.Id); BroadcastRosterDelta(new PresenceRosterDeltaMessage { Kind = PresenceEntityDeltaKind.Add, Id = connection.Id, Entry = BuildRosterEntry(connection.Id) }, connection.Id); } } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo _) { if (connection != null) { _joined.Remove(connection.Id); _models.Remove(connection.Id); _latestStates.Remove(connection.Id); _latestHolds.Remove(connection.Id); _context?.PeerPresence.Clear(connection.Id); _service?.Remove(connection.Id); BroadcastRosterDelta(new PresenceRosterDeltaMessage { Kind = PresenceEntityDeltaKind.Remove, Id = connection.Id }); BroadcastModelDelta(new PresenceModelDeltaMessage { Kind = PresenceEntityDeltaKind.Remove, Id = connection.Id, PredictionId = Guid.Empty }); } } [MessageHandler(typeof(PresenceStateMessage))] private void HandleState(MessageContext context, PresenceStateMessage message) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!IsAuthenticatedSender(context) || message == null) { return; } _context.PeerPresence.RecordAuthenticatedPosition(context.Connection, message.Position); _service.ApplyState(context.Connection.Id, message); _latestStates[context.Connection.Id] = CloneState(message); PresenceRelayStateMessage arg = new PresenceRelayStateMessage { SenderId = context.Connection.Id, State = CloneState(message) }; foreach (int item in ConnectionIds()) { if (item != context.Connection.Id && _joined.Contains(item)) { _context.Send(item, (INetMessage)(object)arg); } } } [MessageHandler(typeof(PresenceHoldMessage))] private void HandleHold(MessageContext context, PresenceHoldMessage message) { if (!IsAuthenticatedSender(context) || message == null) { return; } _context.PeerPresence.RecordAuthenticatedHold(context.Connection, message.Hold, message.HoldTypes); _service.ApplyHold(context.Connection.Id, message); _latestHolds[context.Connection.Id] = CloneHold(message); PresenceRelayHoldMessage arg = new PresenceRelayHoldMessage { SenderId = context.Connection.Id, Hold = CloneHold(message) }; foreach (int item in ConnectionIds()) { if (item != context.Connection.Id && _joined.Contains(item)) { _context.Send(item, (INetMessage)(object)arg); } } } [MessageHandler(typeof(PresenceModelRequestMessage))] private void HandleModelRequest(MessageContext context, PresenceModelRequestMessage message) { if (!IsAuthenticatedSender(context) || message == null) { return; } if (message.ModelIndex >= 0) { string customizationJson = message.CustomizationJson; if (customizationJson == null || customizationJson.Length <= 524288) { PresenceEntityDeltaKind kind = ((!_models.ContainsKey(context.Connection.Id)) ? PresenceEntityDeltaKind.Add : PresenceEntityDeltaKind.Update); PresenceModelEntry presenceModelEntry = new PresenceModelEntry { Id = context.Connection.Id, Female = message.Female, ModelIndex = message.ModelIndex, CustomizationJson = message.CustomizationJson }; _models[context.Connection.Id] = Clone(presenceModelEntry); _service.SetModel(context.Connection.Id, presenceModelEntry); CoopPlugin.Log.LogInfo((object)("appearance update from player " + context.Connection.Id + ": " + (presenceModelEntry.Female ? "female" : "male") + " " + presenceModelEntry.ModelIndex)); BroadcastModelDelta(new PresenceModelDeltaMessage { Kind = kind, Id = context.Connection.Id, Entry = Clone(presenceModelEntry), PredictionId = message.PredictionId }); return; } } Reject(context, message.PredictionId); } [MessageHandler(typeof(PresenceEmoteMessage))] private void HandleEmote(MessageContext context, PresenceEmoteMessage message) { if (IsAuthenticatedSender(context) && message != null) { _service.ShowEmote(context.Connection.Id); RelayTag(context.Connection.Id, 0, (EItemType)0); } } [MessageHandler(typeof(PresenceActivityMessage))] private void HandleActivity(MessageContext context, PresenceActivityMessage message) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected I4, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (IsAuthenticatedSender(context) && message != null) { _service.ShowActivity(context.Connection.Id, (int)message.Pack); RelayTag(context.Connection.Id, 1, message.Pack); } } internal bool TryGetPeerPresence(int connectionId, out PeerPresence presence) { presence = default(PeerPresence); if (_context != null) { return _context.PeerPresence.TryGet(connectionId, out presence); } return false; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } if (_service != null) { PresenceService service = _service; service.ModelChanged = (Action)Delegate.Remove(service.ModelChanged, new Action(OnLocalModelChanged)); _service.Shutdown(); _service = null; } _joined.Clear(); _models.Clear(); _latestStates.Clear(); _context = null; } } private void OnDestroy() { Shutdown(); } private void SendBaseline(int connectionId) { _context.Send(connectionId, (INetMessage)(object)BuildRoster(connectionId)); _context.Send(connectionId, (INetMessage)(object)BuildModelState()); if (_latestStates.TryGetValue(0, out var value)) { _context.Send(connectionId, (INetMessage)(object)CloneState(value)); } foreach (KeyValuePair latestState in _latestStates) { if (latestState.Key != 0 && latestState.Key != connectionId && _joined.Contains(latestState.Key)) { _context.Send(connectionId, (INetMessage)(object)new PresenceRelayStateMessage { SenderId = latestState.Key, State = CloneState(latestState.Value) }); } } if (_latestHolds.TryGetValue(0, out var value2)) { _context.Send(connectionId, (INetMessage)(object)CloneHold(value2)); } foreach (KeyValuePair latestHold in _latestHolds) { if (latestHold.Key != 0 && latestHold.Key != connectionId && _joined.Contains(latestHold.Key)) { _context.Send(connectionId, (INetMessage)(object)new PresenceRelayHoldMessage { SenderId = latestHold.Key, Hold = CloneHold(latestHold.Value) }); } } } private void PublishLocalState(float deltaTime) { _stateTimer += deltaTime; float num = 1f / Mathf.Clamp(CoopPlugin.SendRateHz.Value, 4f, 30f); if (!(_stateTimer < num) && _service.TryGetLocalState(out var state)) { _stateTimer = 0f; _latestStates[0] = CloneState(state); _context.Broadcast((INetMessage)(object)state); PublishLocalHold(); } } private void PublishLocalHold() { if (_service.TryConsumeLocalHoldChange(out var hold)) { _latestHolds[0] = CloneHold(hold); _context.Broadcast((INetMessage)(object)hold); } } private void OnLocalModelChanged(PresenceModelEntry model) { if (model != null && !_shutdown) { model.Id = 0; _models[0] = Clone(model); if (_context.InGame()) { BroadcastModelDelta(new PresenceModelDeltaMessage { Kind = PresenceEntityDeltaKind.Update, Id = 0, Entry = Clone(model), PredictionId = Guid.Empty }); } } } private PresenceModelStateMessage BuildModelState() { PresenceModelEntry localModel = _service.GetLocalModel(); localModel.Id = 0; _models[0] = Clone(localModel); PresenceModelStateMessage presenceModelStateMessage = new PresenceModelStateMessage(); foreach (KeyValuePair model in _models) { PresenceModelEntry presenceModelEntry = Clone(model.Value); presenceModelEntry.Id = model.Key; presenceModelStateMessage.Entries.Add(presenceModelEntry); } return presenceModelStateMessage; } private PresenceRosterMessage BuildRoster(int selfId = 0) { PresenceRosterMessage presenceRosterMessage = new PresenceRosterMessage { SelfId = selfId }; IReadOnlyList readOnlyList = ConnectionIds(); for (int i = 0; i < readOnlyList.Count; i++) { int num = readOnlyList[i]; if (_joined.Contains(num)) { string name = _context.PeerName?.Invoke(num); presenceRosterMessage.Entries.Add(BuildRosterEntry(num, name)); } } return presenceRosterMessage; } private PresenceRosterEntry BuildRosterEntry(int id, string name = null) { if (name == null) { name = _context.PeerName?.Invoke(id); } return new PresenceRosterEntry { Id = id, Name = (string.IsNullOrEmpty(name) ? ("Player " + id) : name), SteamId = 0uL }; } private void ApplyPeerName(int connectionId) { string text = _context.PeerName?.Invoke(connectionId); if (!string.IsNullOrEmpty(text)) { _service.SetName(connectionId, text); } } private void BroadcastRosterDelta(PresenceRosterDeltaMessage delta, int excludedConnectionId = -1) { if (_shutdown) { return; } foreach (int item in ConnectionIds()) { if (item != excludedConnectionId && _joined.Contains(item)) { _context.Send(item, (INetMessage)(object)delta); } } } private void BroadcastModelDelta(PresenceModelDeltaMessage delta, int excludedConnectionId = -1) { if (_shutdown) { return; } foreach (int item in ConnectionIds()) { if (item != excludedConnectionId && _joined.Contains(item)) { _context.Send(item, (INetMessage)(object)delta); } } } private void Reject(MessageContext context, Guid predictionId) { if (predictionId != Guid.Empty && context?.Connection != null) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, predictionId); } } private void RelayTag(int senderId, byte kind, EItemType extra) { //IL_0014: 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) PresenceRelayTagMessage arg = new PresenceRelayTagMessage { SenderId = senderId, Kind = kind, Extra = extra }; foreach (int item in ConnectionIds()) { if (item != senderId && _joined.Contains(item)) { _context.Send(item, (INetMessage)(object)arg); } } } private bool IsAuthenticatedSender(MessageContext context) { if (!_shutdown && _context.InGame() && context?.Connection != null && _joined.Contains(context.Connection.Id)) { return context.IsAuthenticated; } return false; } private IReadOnlyList ConnectionIds() { return _context.ConnectionIds?.Invoke() ?? new List(); } private static PresenceStateMessage CloneState(PresenceStateMessage state) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (state != null) { return new PresenceStateMessage { Position = state.Position, Yaw = state.Yaw, CameraPosition = state.CameraPosition, CameraRotation = state.CameraRotation, Speed = state.Speed }; } return null; } private static PresenceHoldMessage CloneHold(PresenceHoldMessage hold) { if (hold != null) { return new PresenceHoldMessage { Hold = hold.Hold, HoldTypes = ((hold.HoldTypes == null) ? null : new List(hold.HoldTypes)), HoldCards = ((hold.HoldCards == null) ? null : new List(hold.HoldCards)) }; } return null; } private static PresenceModelEntry Clone(PresenceModelEntry model) { if (model != null) { return new PresenceModelEntry { Id = model.Id, Female = model.Female, ModelIndex = model.ModelIndex, CustomizationJson = model.CustomizationJson }; } return new PresenceModelEntry(); } } [NetworkMessage] public sealed class PresenceRosterMessage : INetMessage { public int SelfId; public readonly List Entries = new List(); } public sealed class PresenceRosterEntry { public int Id; public string Name; public ulong SteamId; } public enum PresenceEntityDeltaKind : byte { Add = 1, Update, Remove } [NetworkMessage] public sealed class PresenceRosterDeltaMessage : INetMessage { public PresenceEntityDeltaKind Kind; public int Id; public PresenceRosterEntry Entry; } [NetworkMessage] public sealed class PresenceModelRequestMessage : IPredictedMessage, INetMessage { public bool Female; public int ModelIndex; public string CustomizationJson; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PresenceModelStateMessage : INetMessage { public readonly List Entries = new List(); } public sealed class PresenceModelEntry { public int Id; public bool Female; public int ModelIndex; public string CustomizationJson; } [NetworkMessage] public sealed class PresenceModelDeltaMessage : IPredictedMessage, INetMessage { public PresenceEntityDeltaKind Kind; public int Id; public PresenceModelEntry Entry; public Guid PredictionId { get; set; } } [NetworkMessage(/*Could not decode attribute arguments.*/)] [JsonConverter(typeof(PresenceStateJsonConverter))] public sealed class PresenceStateMessage : INetMessage { public Vector3 Position; public float Yaw; public Vector3 CameraPosition; public Quaternion CameraRotation; public float Speed; } [NetworkMessage(/*Could not decode attribute arguments.*/)] public sealed class PresenceRelayStateMessage : INetMessage { public int SenderId; public PresenceStateMessage State = new PresenceStateMessage(); } [NetworkMessage] [JsonConverter(typeof(PresenceHoldJsonConverter))] public sealed class PresenceHoldMessage : INetMessage { public byte Hold; public List HoldTypes; public List HoldCards; } [NetworkMessage] public sealed class PresenceRelayHoldMessage : INetMessage { public int SenderId; public PresenceHoldMessage Hold = new PresenceHoldMessage(); } [NetworkMessage] public sealed class PresenceEmoteMessage : INetMessage { public byte Emote; } [NetworkMessage] public sealed class PresenceActivityMessage : INetMessage { public byte Activity; public EItemType Pack; } [NetworkMessage] public sealed class PresenceRelayTagMessage : INetMessage { public int SenderId; public byte Kind; public EItemType Extra; } internal sealed class PresenceStateJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(PresenceStateMessage); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) PresenceStateMessage presenceStateMessage = (PresenceStateMessage)value; writer.WriteStartObject(); writer.WritePropertyName("Position"); serializer.Serialize(writer, (object)presenceStateMessage.Position); writer.WritePropertyName("Yaw"); writer.WriteValue(presenceStateMessage.Yaw); writer.WritePropertyName("CameraPosition"); serializer.Serialize(writer, (object)presenceStateMessage.CameraPosition); writer.WritePropertyName("CameraRotation"); serializer.Serialize(writer, (object)presenceStateMessage.CameraRotation); writer.WritePropertyName("Speed"); writer.WriteValue(presenceStateMessage.Speed); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if ((int)reader.TokenType != 1) { throw new JsonSerializationException("PresenceState must be an object"); } PresenceStateMessage presenceStateMessage = new PresenceStateMessage(); while (reader.Read() && (int)reader.TokenType != 13) { if ((int)reader.TokenType != 4) { throw new JsonSerializationException("Invalid PresenceState property"); } string text = (string)reader.Value; if (!reader.Read()) { throw new JsonSerializationException("Unexpected end of PresenceState"); } switch (text) { case "Position": presenceStateMessage.Position = serializer.Deserialize(reader); break; case "Yaw": presenceStateMessage.Yaw = serializer.Deserialize(reader); break; case "CameraPosition": presenceStateMessage.CameraPosition = serializer.Deserialize(reader); break; case "CameraRotation": presenceStateMessage.CameraRotation = serializer.Deserialize(reader); break; case "Speed": presenceStateMessage.Speed = serializer.Deserialize(reader); break; default: reader.Skip(); break; } } return presenceStateMessage; } } internal sealed class PresenceHoldJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(PresenceHoldMessage); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { PresenceHoldMessage presenceHoldMessage = (PresenceHoldMessage)value; writer.WriteStartObject(); writer.WritePropertyName("Hold"); writer.WriteValue(presenceHoldMessage.Hold); writer.WritePropertyName("HoldTypes"); writer.WriteStartArray(); if (presenceHoldMessage.HoldTypes != null) { foreach (int holdType in presenceHoldMessage.HoldTypes) { writer.WriteValue(CatalogIdMap.ToHostValue(EnumKind.ItemType, holdType)); } } writer.WriteEndArray(); writer.WritePropertyName("HoldCards"); serializer.Serialize(writer, (object)presenceHoldMessage.HoldCards); writer.WriteEndObject(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if ((int)reader.TokenType != 1) { throw new JsonSerializationException("PresenceHold must be an object"); } PresenceHoldMessage presenceHoldMessage = new PresenceHoldMessage(); while (reader.Read() && (int)reader.TokenType != 13) { if ((int)reader.TokenType != 4) { throw new JsonSerializationException("Invalid PresenceHold property"); } string text = (string)reader.Value; if (!reader.Read()) { throw new JsonSerializationException("Unexpected end of PresenceHold"); } switch (text) { case "Hold": presenceHoldMessage.Hold = serializer.Deserialize(reader); break; case "HoldTypes": ReadHoldTypes(reader, presenceHoldMessage); break; case "HoldCards": ReadHoldCards(reader, serializer, presenceHoldMessage); break; default: reader.Skip(); break; } } return presenceHoldMessage; } private static void ReadHoldTypes(JsonReader reader, PresenceHoldMessage message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Invalid comparison between Unknown and I4 if ((int)reader.TokenType != 11) { if ((int)reader.TokenType != 2) { throw new JsonSerializationException("Presence HoldTypes must be an array"); } message.HoldTypes = new List(); while (reader.Read() && (int)reader.TokenType != 14) { int hostValue = Convert.ToInt32(reader.Value, CultureInfo.InvariantCulture); message.HoldTypes.Add(CatalogIdMap.FromHostValue(EnumKind.ItemType, hostValue)); } } } private static void ReadHoldCards(JsonReader reader, JsonSerializer serializer, PresenceHoldMessage message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 if ((int)reader.TokenType != 11) { if ((int)reader.TokenType != 2) { throw new JsonSerializationException("Presence HoldCards must be an array"); } message.HoldCards = new List(); while (reader.Read() && (int)reader.TokenType != 14) { message.HoldCards.Add(serializer.Deserialize(reader)); } } } } internal static class PresenceModelStore { private static readonly string FilePath = Path.Combine(Paths.ConfigPath, "CardShopCoopPlayerModel.json"); internal static bool TryLoad(out PresenceModelEntry model) { model = null; if (!File.Exists(FilePath)) { return false; } try { model = JsonConvert.DeserializeObject(File.ReadAllText(FilePath)); if (model == null || model.ModelIndex < 0) { throw new InvalidDataException("missing or invalid model fields"); } return true; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("local player model could not be loaded; using the game appearance: " + ex.Message)); model = null; return false; } } internal static void Save(PresenceModelEntry model) { if (model == null) { return; } string text = FilePath + ".tmp"; try { Directory.CreateDirectory(Paths.ConfigPath); File.WriteAllText(text, JsonConvert.SerializeObject((object)model, (Formatting)1)); if (File.Exists(FilePath)) { File.Replace(text, FilePath, null); } else { File.Move(text, FilePath); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("local player model could not be saved: " + ex.Message)); try { if (File.Exists(text)) { File.Delete(text); } } catch (Exception e) { Swallow.Log(e, "Save", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceModelStore.cs", 75); } } } } internal sealed class PresenceService { private const int ModelHistoryLimit = 30; private static readonly FieldInfo HoldItemsField = AccessTools.Field(typeof(InteractionPlayerController), "m_HoldItemList"); private static readonly FieldInfo HoldCardsField = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingCard3dList"); private static readonly FieldInfo ViewAlbumField = AccessTools.Field(typeof(InteractionPlayerController), "m_IsViewCardAlbumMode"); private static readonly FieldInfo ActiveSprayField = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldSprayItem"); private static readonly FieldInfo HoldingBoxField = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingBox"); private static readonly FieldInfo HoldingItemBoxField = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldingItemBox"); private static readonly FieldInfo ItemBoxBigField = AccessTools.Field(typeof(InteractablePackagingBox_Item), "m_IsBigBox"); private static readonly FieldInfo ItemBoxTypeField = AccessTools.Field(typeof(InteractablePackagingBox_Item), "m_ItemType"); private readonly CoopRuntimeContext _context; private readonly List _holdTypes = new List(); private readonly List _holdCards = new List(); private readonly List _lastHoldTypes = new List(); private readonly List _lastHoldCards = new List(); private int _lastHoldKind = -1; private readonly List _undo = new List(); private readonly List _redo = new List(); private readonly PresenceAvatarRenderer _renderer = new PresenceAvatarRenderer(); private PresenceModelEntry _localModel; private PresenceModelEntry _lastCommittedModel; private bool _localModelReady; private bool _autoRepair; private Transform _appliedModelRoot; private bool _savePending; private float _saveIn; private bool _previewVisible; private InteractionPlayerController _ipc; private Transform _playerTransform; private Transform _cameraTransform; private Vector3 _lastStatePosition; private bool _hasLastStatePosition; private float _lastStateTime; private bool _shutdown; private int _modelGeneration; internal Action ModelChanged; internal static PresenceService Active { get; private set; } internal int ModelGeneration => _modelGeneration; internal bool CanUndo => _undo.Count > 0; internal bool CanRedo => _redo.Count > 0; internal PresenceService(CoopRuntimeContext context) { _context = context ?? throw new ArgumentNullException("context"); Active = this; _renderer.RemoteAvatarsChanged = PresenceApi.NotifyRemoteAvatarsChanged; } internal void Tick(float deltaTime) { if (_shutdown) { return; } if (_context.InGame()) { ResolvePlayer(); Func preloadHold = _context.PreloadHold; if (preloadHold == null || !preloadHold()) { PresenceAvatarRenderer.ViewCamera = _cameraTransform; _renderer.Tick(deltaTime); _renderer.UpdatePreview(_previewVisible ? EnsureLocalModel() : null, _playerTransform, _cameraTransform, _previewVisible); } else { _renderer.DestroyPreview(); } } else { _renderer.DestroyPreview(); } FlushModelSave(deltaTime); } internal bool TryGetLocalState(out PresenceStateMessage state) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: 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) ResolvePlayer(); if ((Object)(object)_playerTransform == (Object)null) { state = null; return false; } Vector3 position = _playerTransform.position; object obj; if (!((Object)(object)_cameraTransform != (Object)null)) { Camera main = Camera.main; obj = ((main != null) ? ((Component)main).transform : null); } else { obj = _cameraTransform; } Transform val = (Transform)obj; float num = (((Object)(object)val != (Object)null) ? val.eulerAngles.y : _playerTransform.eulerAngles.y); Vector3 cameraPosition = (((Object)(object)val != (Object)null) ? val.position : position); Quaternion cameraRotation = (((Object)(object)val != (Object)null) ? val.rotation : Quaternion.Euler(0f, num, 0f)); float unscaledTime = Time.unscaledTime; float speed = 0f; if (_hasLastStatePosition) { float num2 = Mathf.Max(0.0001f, unscaledTime - _lastStateTime); Vector3 val2 = position - _lastStatePosition; val2.y = 0f; speed = Mathf.Clamp(((Vector3)(ref val2)).magnitude / num2, 0f, 6f); } _lastStatePosition = position; _lastStateTime = unscaledTime; _hasLastStatePosition = true; state = new PresenceStateMessage { Position = position, Yaw = num, CameraPosition = cameraPosition, CameraRotation = cameraRotation, Speed = speed }; return IsFinite(state); } internal void InvalidateLocalHold() { _lastHoldKind = -1; } internal bool TryConsumeLocalHoldChange(out PresenceHoldMessage hold) { hold = null; byte b = ComputeHoldState(); if (!LocalHoldChanged(b)) { return false; } RememberLocalHold(b); hold = new PresenceHoldMessage { Hold = b, HoldTypes = ((b == 3) ? null : new List(_holdTypes)), HoldCards = ((b == 3) ? new List(_holdCards) : null) }; return true; } private bool LocalHoldChanged(byte kind) { if (kind != _lastHoldKind) { return true; } if (kind == 3) { if (_holdCards.Count != _lastHoldCards.Count) { return true; } for (int i = 0; i < _holdCards.Count; i++) { if (!SameCard(_holdCards[i], _lastHoldCards[i])) { return true; } } return false; } if (_holdTypes.Count != _lastHoldTypes.Count) { return true; } for (int j = 0; j < _holdTypes.Count; j++) { if (_holdTypes[j] != _lastHoldTypes[j]) { return true; } } return false; } private void RememberLocalHold(byte kind) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown _lastHoldKind = kind; _lastHoldTypes.Clear(); _lastHoldTypes.AddRange(_holdTypes); _lastHoldCards.Clear(); for (int i = 0; i < _holdCards.Count; i++) { CardData val = new CardData(); val.CopyData(_holdCards[i]); _lastHoldCards.Add(val); } } private static bool SameCard(CardData a, CardData b) { //IL_000a: 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_0018: 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_0026: 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) if (a != null && b != null && a.expansionType == b.expansionType && a.monsterType == b.monsterType && a.borderType == b.borderType && a.isFoil == b.isFoil && a.isDestiny == b.isDestiny && a.isChampionCard == b.isChampionCard && a.isNew == b.isNew && a.cardGrade == b.cardGrade) { return a.gradedCardIndex == b.gradedCardIndex; } return false; } internal bool TryGetLocalPosition(out Vector3 position) { //IL_0020: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) position = (Vector3)(((Object)(object)ResolvePlayer() != (Object)null) ? _playerTransform.position : default(Vector3)); if ((Object)(object)_playerTransform != (Object)null) { return IsFinite(position); } return false; } internal void SetName(int id, string name) { _renderer.SetName(id, name); } internal void SetModel(int id, PresenceModelEntry model) { if (model != null) { _renderer.SetModel(id, model.Female, model.ModelIndex, model.CustomizationJson); } } internal void ApplyAuthoritativeLocalModel(PresenceModelEntry model) { if (model != null) { _localModel = Clone(model); _localModelReady = true; _autoRepair = false; _lastCommittedModel = Clone(model); _appliedModelRoot = null; } } internal void ApplyPredictionLocalModel(PresenceModelEntry model) { if (model != null) { _localModel = Clone(model); _localModelReady = true; _autoRepair = false; _lastCommittedModel = Clone(model); _appliedModelRoot = null; CharacterCustomization editorCustomization = _renderer.GetEditorCustomization(_localModel.Female); if ((Object)(object)editorCustomization != (Object)null) { _renderer.ApplyLocalModel(editorCustomization, _localModel); _appliedModelRoot = ((Component)editorCustomization).transform; } QueueModelSave(); } } internal void ApplyState(int avatarId, PresenceStateMessage state) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) _renderer.UpdateState(avatarId, state.Position, state.Yaw, state.CameraPosition, state.CameraRotation); } internal void ApplyHold(int avatarId, PresenceHoldMessage hold) { _renderer.UpdateHold(avatarId, hold.Hold, hold.HoldTypes, hold.HoldCards); } internal bool TryGetRemoteCarryAnchor(int holderConnectionId, out Transform anchor) { anchor = null; int num = MapHolderToAvatarId(holderConnectionId); if (num != 0) { return _renderer.TryGetCarryAnchor(num, out anchor); } return false; } private static int MapHolderToAvatarId(int holderConnectionId) { if ((Object)(object)PresenceHostBehaviour.Active != (Object)null) { return holderConnectionId; } if (holderConnectionId != 0) { return 1000 + holderConnectionId; } return 1; } internal void ShowEmote(int id) { _renderer.ShowEmote(id); } internal void ShowActivity(int id, int pack) { _renderer.ShowTag(id, "opening a pack!", 3f); _renderer.ShowPackOpen(id, pack); } internal void Remove(int id) { _renderer.Remove(id); } internal void ClearRemoteAvatars() { _renderer.Clear(); } internal bool TryGetPeerCamera(int id, out Vector3 position, out Quaternion rotation) { return _renderer.TryGetPlacementCamera(id, out position, out rotation); } internal bool TryGetPeerCameraForHolder(int holderConnectionId, out Vector3 position, out Quaternion rotation) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) int num = MapHolderToAvatarId(holderConnectionId); if (num != 0) { return _renderer.TryGetPlacementCamera(num, out position, out rotation); } position = default(Vector3); rotation = Quaternion.identity; return false; } internal PresenceModelEntry EnsureLocalModel() { if (!_localModelReady) { bool num = PresenceModelStore.TryLoad(out _localModel); _localModelReady = true; if (!num) { ResolvePlayer(); _localModel = (((Object)(object)_playerTransform != (Object)null) ? _renderer.CaptureLocalModel(_playerTransform) : null); if (_localModel == null) { _localModel = _renderer.CreateDefaultModel(); } } _autoRepair = NeedsAutoRepair(_localModel); } if (_autoRepair) { TryUpgradeAutoModel(); } CharacterCustomization editorCustomization = _renderer.GetEditorCustomization(_localModel.Female); if ((Object)(object)editorCustomization != (Object)null && (Object)(object)_appliedModelRoot != (Object)(object)((Component)editorCustomization).transform) { string customizationJson = _localModel.CustomizationJson; _renderer.ApplyLocalModel(editorCustomization, _localModel); _appliedModelRoot = ((Component)editorCustomization).transform; if (customizationJson != _localModel.CustomizationJson) { _lastCommittedModel = Clone(_localModel); QueueModelSave(); } } if (_lastCommittedModel == null) { _lastCommittedModel = Clone(_localModel); } return _localModel; } internal PresenceModelEntry GetLocalModel() { return Clone(EnsureLocalModel()); } internal CharacterCustomization GetLocalCustomization() { PresenceModelEntry presenceModelEntry = EnsureLocalModel(); CharacterCustomization editorCustomization = _renderer.GetEditorCustomization(presenceModelEntry.Female); if ((Object)(object)editorCustomization != (Object)null && (Object)(object)_appliedModelRoot != (Object)(object)((Component)editorCustomization).transform) { _renderer.ApplyLocalModel(editorCustomization, presenceModelEntry); _appliedModelRoot = ((Component)editorCustomization).transform; } return editorCustomization; } internal List GetLocalSliders() { return _renderer.GetBlendshapes(GetLocalCustomization()); } internal void SetPreview(bool active) { _previewVisible = active; } internal void CommitCustomization() { PresenceModelEntry presenceModelEntry = EnsureLocalModel(); RecordChange(); CharacterCustomization localCustomization = GetLocalCustomization(); if (!((Object)(object)localCustomization == (Object)null)) { _autoRepair = false; _localModel = _renderer.CaptureLocalModel(localCustomization, presenceModelEntry.Female, presenceModelEntry.ModelIndex); _lastCommittedModel = Clone(_localModel); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); _modelGeneration++; } } internal bool Undo() { EnsureLocalModel(); if (_undo.Count == 0) { return false; } Push(_redo, _lastCommittedModel ?? _localModel); _localModel = _undo[_undo.Count - 1]; _undo.RemoveAt(_undo.Count - 1); _lastCommittedModel = Clone(_localModel); _appliedModelRoot = null; GetLocalCustomization(); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); _modelGeneration++; return true; } internal bool Redo() { EnsureLocalModel(); if (_redo.Count == 0) { return false; } Push(_undo, _lastCommittedModel ?? _localModel); _localModel = _redo[_redo.Count - 1]; _redo.RemoveAt(_redo.Count - 1); _lastCommittedModel = Clone(_localModel); _appliedModelRoot = null; GetLocalCustomization(); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); _modelGeneration++; return true; } internal List GetPresetNames() { List list = new List(); List list2 = GetLocalCustomization()?.Presets?.Presets; if (list2 == null) { return list; } string value = (EnsureLocalModel().Female ? "Female" : "Male"); foreach (CC_CharacterData item in list2) { if (item != null && (item.CharacterName ?? "").StartsWith(value, StringComparison.OrdinalIgnoreCase)) { list.Add(item.CharacterName); } } return list; } internal void ApplyPreset(string presetName) { if (string.IsNullOrEmpty(presetName)) { return; } List list = GetLocalCustomization()?.Presets?.Presets; if (list == null) { return; } CC_CharacterData preset = null; foreach (CC_CharacterData item in list) { if (item != null && item.CharacterName == presetName) { preset = item; break; } } PresenceModelEntry presenceModelEntry = PresenceAvatarRenderer.ModelFromPreset(preset); if (presenceModelEntry != null) { RecordChange(); _autoRepair = false; _localModel = presenceModelEntry; _appliedModelRoot = null; GetLocalCustomization(); _lastCommittedModel = Clone(_localModel); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); _modelGeneration++; } } internal void SetModel(bool female, int modelIndex) { EnsureLocalModel(); RecordChange(); bool num = _localModel.Female != female; _localModel.Female = female; _localModel.ModelIndex = Mathf.Max(0, modelIndex); if (num) { PresenceModelEntry presenceModelEntry = _renderer.TryCreateRandomPresetModel(female); _localModel.CustomizationJson = presenceModelEntry?.CustomizationJson; if (presenceModelEntry != null) { _localModel.ModelIndex = presenceModelEntry.ModelIndex; } } _autoRepair = false; _appliedModelRoot = null; GetLocalCustomization(); _lastCommittedModel = Clone(_localModel); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); _modelGeneration++; } internal bool SetSlider(string propertyName, float value) { EnsureLocalModel(); RecordChange(); CharacterCustomization localCustomization = GetLocalCustomization(); if (!_renderer.SetBlendshape(localCustomization, propertyName, value)) { return false; } _autoRepair = false; _localModel = _renderer.CaptureLocalModel(localCustomization, _localModel.Female, _localModel.ModelIndex); _lastCommittedModel = Clone(_localModel); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); return true; } internal bool ClearHair(int slot) { if (!_renderer.ClearHair(GetLocalCustomization(), slot)) { return false; } CommitCustomization(); return true; } internal bool ClearApparel(int slot) { if (!_renderer.ClearApparel(GetLocalCustomization(), slot)) { return false; } CommitCustomization(); return true; } internal bool SetHairColor(int slot, Color color) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!_renderer.SetHairColor(GetLocalCustomization(), slot, color)) { return false; } CommitCustomization(); return true; } internal bool SetApparelTint(int slot, Color color) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!_renderer.SetApparelTint(GetLocalCustomization(), slot, color)) { return false; } CommitCustomization(); return true; } internal void SetNsfwAllowed(bool allowed) { if (CoopPlugin.AllowNsfw.Value == allowed) { return; } CoopPlugin.AllowNsfw.Value = allowed; EnsureLocalModel(); if (!allowed && PresenceAvatarRenderer.IsNude(_localModel)) { PresenceModelEntry presenceModelEntry = _renderer.TryCreateRandomPresetModel(_localModel.Female); if (presenceModelEntry != null) { RecordChange(); _localModel = presenceModelEntry; _autoRepair = false; _appliedModelRoot = null; _lastCommittedModel = Clone(_localModel); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); _modelGeneration++; } else { _autoRepair = true; } } _renderer.RefreshRemoteAvatars(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; if (Active == this) { Active = null; } _renderer.Clear(); _undo.Clear(); _redo.Clear(); _localModel = null; _lastCommittedModel = null; _ipc = null; _playerTransform = null; _cameraTransform = null; ModelChanged = null; } } private Transform ResolvePlayer() { if ((Object)(object)_playerTransform != (Object)null) { return _playerTransform; } InteractionPlayerController val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return null; } _ipc = val; _playerTransform = (((Object)(object)val.m_WalkerCtrl != (Object)null) ? ((Component)val.m_WalkerCtrl).transform : ((Component)val).transform); _cameraTransform = (((Object)(object)val.m_Cam != (Object)null) ? ((Component)val.m_Cam).transform : null); return _playerTransform; } private void TryUpgradeAutoModel() { PresenceModelEntry presenceModelEntry = _renderer.TryCreateRandomPresetModel(_localModel.Female); if (presenceModelEntry != null) { _localModel = presenceModelEntry; _autoRepair = false; _appliedModelRoot = null; _lastCommittedModel = Clone(_localModel); QueueModelSave(); ModelChanged?.Invoke(Clone(_localModel)); } } private void RecordChange() { EnsureLocalModel(); Push(_undo, _lastCommittedModel ?? _localModel); _redo.Clear(); } private static void Push(List history, PresenceModelEntry model) { if (model != null) { history.Add(Clone(model)); if (history.Count > 30) { history.RemoveAt(0); } } } private void QueueModelSave() { _savePending = true; _saveIn = 0.4f; } private void FlushModelSave(float deltaTime) { if (_savePending && _localModel != null) { _saveIn -= deltaTime; if (!(_saveIn > 0f)) { _savePending = false; PresenceModelStore.Save(_localModel); } } } private static bool NeedsAutoRepair(PresenceModelEntry model) { if (model != null && !string.IsNullOrEmpty(model.CustomizationJson)) { if (!CoopPlugin.AllowNsfw.Value) { return PresenceAvatarRenderer.IsNude(model); } return false; } return true; } private static PresenceModelEntry Clone(PresenceModelEntry model) { if (model != null) { return new PresenceModelEntry { Id = model.Id, Female = model.Female, ModelIndex = model.ModelIndex, CustomizationJson = model.CustomizationJson }; } return new PresenceModelEntry(); } private byte ComputeHoldState() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected I4, but got Unknown //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected I4, but got Unknown _holdTypes.Clear(); _holdCards.Clear(); if ((Object)(object)_ipc == (Object)null) { return 0; } try { bool flag = AppendActiveHeldItem(); if (HoldItemsField?.GetValue(_ipc) is List { Count: >0 } list) { for (int i = 0; i < list.Count; i++) { if ((Object)(object)list[i] != (Object)null) { _holdTypes.Add((int)list[i].GetItemType()); } } if (!flag) { AppendActiveHeldItem(); } return 2; } if (AppendActiveHeldItem()) { return 2; } object obj2; if (HoldingBoxField?.GetValue(_ipc) != null || HoldingItemBoxField?.GetValue(_ipc) != null) { object obj = HoldingItemBoxField?.GetValue(_ipc); if (obj != null && ItemBoxTypeField?.GetValue(obj) is EItemType val) { List holdTypes = _holdTypes; obj2 = ItemBoxBigField?.GetValue(obj); bool flag2 = default(bool); int num; if (obj2 is bool) { flag2 = (bool)obj2; num = 1; } else { num = 0; } holdTypes.Add((((uint)num & (flag2 ? 1u : 0u)) != 0) ? 1 : 0); _holdTypes.Add((int)val); } return 1; } if (HoldCardsField?.GetValue(_ipc) is List { Count: >0 } list2) { for (int j = 0; j < list2.Count; j++) { 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) { _holdCards.Add(val2.m_Card3dUI.m_CardUI.GetCardData()); } } if (_holdCards.Count > 0) { return 3; } } obj2 = ViewAlbumField?.GetValue(_ipc); bool flag3 = default(bool); int num2; if (obj2 is bool) { flag3 = (bool)obj2; num2 = 1; } else { num2 = 0; } if (((uint)num2 & (flag3 ? 1u : 0u)) != 0) { return 4; } } catch (Exception e) { Swallow.Log(e, "ComputeHoldState", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Presence/PresenceService.cs", 868); } return 0; } private bool AppendActiveHeldItem() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected I4, but got Unknown object? obj = ActiveSprayField?.GetValue(_ipc); Item val = (Item)((obj is Item) ? obj : null); if ((Object)(object)val == (Object)null) { return false; } int item = (int)val.GetItemType(); if (_holdTypes.Contains(item)) { return true; } _holdTypes.Add(item); return true; } private static bool IsFinite(PresenceStateMessage state) { //IL_0004: 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_0038: Unknown result type (might be due to invalid IL or missing references) if (state != null && IsFinite(state.Position) && IsFinite(state.CameraPosition) && IsFinite(state.Yaw) && IsFinite(state.Speed)) { return IsFinite(state.CameraRotation); } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(value.x) && IsFinite(value.y)) { return IsFinite(value.z); } return false; } private static bool IsFinite(Quaternion value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(value.x) && IsFinite(value.y) && IsFinite(value.z)) { return IsFinite(value.w); } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } } namespace CardShopCoop.Modules.Prediction { public static class PredictionApi { private sealed class Prediction { internal Guid Id; internal string Key; internal Action Apply; internal Action Undo; internal Action Rejected; } private static readonly Dictionary ById = new Dictionary(); private static readonly Dictionary> ByKey = new Dictionary>(StringComparer.Ordinal); private static readonly List Deferred = new List(); private static bool _active; private static bool _reconciling; private static int _applying; public static bool IsReconciling => _reconciling; public static bool IsActive => _active; public static bool IsApplying => _applying > 0; public static event Action PredictionRetired; internal static void Start() { Clear(); _active = true; } internal static void Stop() { _active = false; Clear(); } public static Guid Predict(string key, Action send, Action apply, Action undo) { return Predict(key, send, apply, undo, null); } public static Guid Predict(string key, Action send, Action apply, Action undo, Action rejected) { return Record(key, send, apply, undo, rejected); } private static Guid Record(string key, Action send, Action apply, Action undo, Action rejected) { if (!_active) { throw new InvalidOperationException("Client prediction is not active."); } if (string.IsNullOrEmpty(key)) { throw new ArgumentException("A prediction key is required.", "key"); } if (send == null) { throw new ArgumentNullException("send"); } if (apply == null) { throw new ArgumentNullException("apply"); } if (undo == null) { throw new ArgumentNullException("undo"); } Prediction prediction = new Prediction { Id = Guid.NewGuid(), Key = key, Apply = apply, Undo = undo, Rejected = rejected }; ById.Add(prediction.Id, prediction); if (_reconciling) { Deferred.Add(prediction); } else { GetOrAdd(key).Add(prediction); } try { send(prediction.Id); } catch { Remove(prediction); throw; } return prediction.Id; } public static void Ack(Guid predictionId) { if (!(predictionId == Guid.Empty) && ById.TryGetValue(predictionId, out var value)) { Remove(value); } } public static void AckOrApply(Guid predictionId, Action apply) { if (apply == null) { throw new ArgumentNullException("apply"); } if (predictionId != Guid.Empty && ById.TryGetValue(predictionId, out var value)) { Remove(value); } else { RunApplying(apply); } } public static void Confirm(Guid predictionId, Action apply) { if (apply == null) { throw new ArgumentNullException("apply"); } if (predictionId != Guid.Empty && ById.TryGetValue(predictionId, out var value)) { Remove(value); } RunApplying(apply); } public static void ApplyAuthoritative(Guid predictionId, Action apply) { if (apply == null) { throw new ArgumentNullException("apply"); } if (predictionId != Guid.Empty && ById.TryGetValue(predictionId, out var value)) { Reconcile(value, apply); } else { RunApplying(apply); } } private static void RunApplying(Action apply) { _applying++; try { apply(); } finally { _applying--; } } public static bool IsPending(Guid predictionId) { if (predictionId != Guid.Empty) { return ById.ContainsKey(predictionId); } return false; } public static void Reject(ICoopContext context, int connectionId, Guid predictionId) { if (!(predictionId == Guid.Empty)) { if (context == null) { throw new ArgumentNullException("context"); } context.Send(connectionId, (INetMessage)(object)new PredictionRollbackMessage { PredictionId = predictionId }); } } public static void Rollback(ICoopContext context, int connectionId, Guid predictionId) { if (!(predictionId == Guid.Empty)) { if (context == null) { throw new ArgumentNullException("context"); } ManualLogSource log = CoopPlugin.Log; string text = connectionId.ToString(); Guid guid = predictionId; log.LogInfo((object)("prediction rollback -> conn " + text + " id=" + guid)); Reject(context, connectionId, predictionId); } } internal static void Rollback(Guid predictionId) { if (!(predictionId == Guid.Empty)) { Guid guid; if (!ById.TryGetValue(predictionId, out var value)) { ManualLogSource log = CoopPlugin.Log; guid = predictionId; log.LogInfo((object)("prediction rollback ignored for already-resolved prediction " + guid.ToString() + ".")); return; } ManualLogSource log2 = CoopPlugin.Log; string[] obj = new string[5] { "prediction rollback client key=", value.Key, " id=", null, null }; guid = predictionId; obj[3] = guid.ToString(); obj[4] = "."; log2.LogInfo((object)string.Concat(obj)); Action rejected = value.Rejected; Reconcile(value, null); rejected?.Invoke(); } } private static void Reconcile(Prediction prediction, Action authoritative) { if (!ByKey.TryGetValue(prediction.Key, out var value)) { Guid id = prediction.Id; throw new InvalidOperationException("Prediction key is inconsistent: " + id.ToString() + "."); } int num = value.IndexOf(prediction); if (num < 0) { Guid id = prediction.Id; throw new InvalidOperationException("Prediction key is inconsistent: " + id.ToString() + "."); } List list = new List(value.Count - num - 1); for (int i = num + 1; i < value.Count; i++) { list.Add(value[i]); } List list2 = new List(list.Count + 1) { prediction }; list2.AddRange(list); _reconciling = true; try { value.RemoveAt(num); ById.Remove(prediction.Id); PredictionApi.PredictionRetired?.Invoke(prediction.Id); for (int num2 = list2.Count - 1; num2 >= 0; num2--) { list2[num2].Undo(); } authoritative?.Invoke(); for (int j = 0; j < list.Count; j++) { Prediction prediction2 = list[j]; if (ById.ContainsKey(prediction2.Id)) { try { prediction2.Apply(); } catch (Exception ex) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[6] { "Prediction follower replay failed; retiring id=", null, null, null, null, null }; Guid id = prediction2.Id; obj[1] = id.ToString(); obj[2] = " key="; obj[3] = prediction2.Key; obj[4] = ": "; obj[5] = ex?.ToString(); log.LogWarning((object)string.Concat(obj)); Remove(prediction2); } } } } finally { _reconciling = false; if (value.Count == 0) { ByKey.Remove(prediction.Key); } FlushDeferred(); } } private static void FlushDeferred() { if (Deferred.Count == 0) { return; } for (int i = 0; i < Deferred.Count; i++) { Prediction prediction = Deferred[i]; if (ById.ContainsKey(prediction.Id)) { GetOrAdd(prediction.Key).Add(prediction); } } Deferred.Clear(); } private static List GetOrAdd(string key) { if (!ByKey.TryGetValue(key, out var value)) { value = new List(); ByKey.Add(key, value); } return value; } private static void Remove(Prediction prediction) { if (ById.Remove(prediction.Id)) { PredictionApi.PredictionRetired?.Invoke(prediction.Id); } if (!Deferred.Remove(prediction) && ByKey.TryGetValue(prediction.Key, out var value)) { value.Remove(prediction); if (value.Count == 0) { ByKey.Remove(prediction.Key); } } } private static void Clear() { ById.Clear(); ByKey.Clear(); Deferred.Clear(); _reconciling = false; _applying = 0; } } [ClientBehaviour] public sealed class PredictionClientBehaviour : CoopBehaviour { private bool _shutdown; private void OnEnable() { if (!_shutdown) { PredictionApi.Start(); base.RuntimeContext.Messages.RegisterAttributedHandlers(this); } } [MessageHandler(typeof(PredictionRollbackMessage))] private void Rollback(MessageContext _, PredictionRollbackMessage message) { PredictionApi.Rollback(message.PredictionId); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; base.RuntimeContext.Messages.UnregisterAttributedHandlers(this); PredictionApi.Stop(); } } private void OnDestroy() { Shutdown(); } } internal static class PredictionHost { public static bool Admit(CoopRuntimeContext context, int connectionId, Guid predictionId, int used, int capacity, int requested) { if (requested > 0 && used >= 0 && used + requested <= capacity) { return true; } Reject(context, connectionId, predictionId); return false; } public static bool Resolve(CoopRuntimeContext context, int connectionId, Guid predictionId, Func apply) { if (apply == null || !apply()) { ManualLogSource log = CoopPlugin.Log; string[] obj = new string[5] { "prediction rejected -> conn ", connectionId.ToString(), " id=", null, null }; Guid guid = predictionId; obj[3] = guid.ToString(); obj[4] = " (host apply refused)."; log.LogWarning((object)string.Concat(obj)); Reject(context, connectionId, predictionId); return false; } return true; } public static void Reject(CoopRuntimeContext context, int connectionId, Guid predictionId) { PredictionApi.Reject((ICoopContext)(object)context, connectionId, predictionId); } } [NetworkMessage] public sealed class PredictionRollbackMessage : INetMessage { public Guid PredictionId; } } namespace CardShopCoop.Modules.PlayTable { [ClientBehaviour] public sealed class PlayTableClientBehaviour : CoopBehaviour { private sealed class AppliedVisual { internal InteractablePlayTable Table; internal TableGameItemSet Set; internal SeatState State; } private struct SeatState { internal bool Active; internal bool PlayerSeat; internal int PlayMat; internal int DeckBox; internal int Comic; } private sealed class BeforeClickSnapshot { internal byte Seat; internal PlayTableSeatSnapshot Values; } private sealed class TableActionCapture { internal InteractablePlayTable Table; internal int Key; internal bool WasOccupied; internal bool WasBoxed; internal PlayTableEntry Before; internal Vector3 Position; internal Quaternion Rotation; } private sealed class LaunchState { internal InteractablePlayTable Table; internal PlayTableSeatSnapshot Before; internal PlayTableMatchEntry Match; internal bool Entered; internal float StartedAt; } private static class MovePatch { internal static void Prefix(InteractablePlayTable __instance, out TableActionCapture __state) { __state = null; if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null) && !__instance.GetIsTournamentPlayTable() && __instance.GetCurrentPlayerCount() > 0 && PlayTablePlacementInterop.TryGetTableKey(__instance, out var key)) { __state = new TableActionCapture { Table = __instance, Key = key, WasOccupied = true, Before = CaptureEntry(__instance, key) }; } } internal static void Postfix(TableActionCapture __state) { PlayTableClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && __state != null && __state.WasOccupied && __state.Table.GetCurrentPlayerCount() <= 0) { active.SendKickIntent(__state.Table, __state.Key, __state.Before); } } } private static class BoxPatch { internal static void Prefix(InteractablePlayTable __instance, bool holdBox, out TableActionCapture __state) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) __state = null; PlayTableClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !((Object)(object)__instance == (Object)null) && active._applyingPrediction == 0 && holdBox && PlayTablePlacementInterop.TryGetTableKey(__instance, out var key)) { __state = new TableActionCapture { Table = __instance, Key = key, WasBoxed = ((InteractableObject)__instance).GetIsBoxedUp(), Position = ((Component)__instance).transform.position, Rotation = ((Component)__instance).transform.rotation }; } } internal static void Postfix(TableActionCapture __state) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) PlayTableClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && __state != null && !__state.WasBoxed && ((InteractableObject)__state.Table).GetIsBoxedUp()) { active.SendBoxIntent(__state.Table, __state.WasBoxed, __state.Position, __state.Rotation); } } } private static class FinishLeavePatch { internal static void Postfix(PlayTableGame __instance) { _active?.HandleFinishLeave(__instance); } } private static class EndGameGiftPatch { internal static bool Prefix() { if (!((Object)(object)_active == (Object)null)) { return !_active._suppressEndGameGift; } return true; } } private static class SeatPatch { internal static void Prefix(InteractablePlayTable __instance, out BeforeClickSnapshot __state) { __state = null; if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null) && PlayTableInterop.TryGetClickedSeat(__instance, out var seat) && PlayTablePlacementInterop.TryGetTableKey(__instance, out var _)) { __state = new BeforeClickSnapshot { Seat = (byte)seat, Values = PlayTableInterop.CaptureSeat(__instance, (byte)seat) }; } } internal static void Postfix(InteractablePlayTable __instance, BeforeClickSnapshot __state) { PlayTableClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !((Object)(object)__instance == (Object)null) && __state != null && !__state.Values.SeatOccupied && PlayTableInterop.IsPlayerSeat(__instance, __state.Seat) && PlayTableInterop.HasEnteredTable(__instance)) { active.RequestMatchObserved(__instance, __state); } } } private static class TableReadyPatch { internal static void Postfix() { _active?.ApplyLatestBaseline(); _active?.ApplyPendingVisuals(); _active?.ReconcileLaunches(); } } private const string PredictionScope = "play-table"; private static PlayTableClientBehaviour _active; private static int _nextMatchNonce; private readonly Dictionary _visualEntries = new Dictionary(); private readonly Dictionary _occupied = new Dictionary(); private readonly Dictionary _matchesById = new Dictionary(); private readonly Dictionary _matchByTable = new Dictionary(); private readonly Dictionary _applied = new Dictionary(); private readonly Dictionary _launches = new Dictionary(); private readonly Dictionary _pendingVisuals = new Dictionary(); private CoopRuntimeContext _context; private Harmony _harmony; private PlayTableBaselineMessage _latestBaseline; private bool _shutdown; private int _applyingState; private int _applyingPrediction; private bool _suppressEndGameGift; internal static PlayTableClientBehaviour Active => _active; private void OnEnable() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown if (!_shutdown && _context == null) { _context = base.RuntimeContext; _active = this; PlacementApi.StructureChanged += OnPlacementStructureChanged; _context.Messages.RegisterAttributedHandlers(this); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.play-table.client"); ApplyPatches(); } } [MessageHandler(typeof(PlayTableBaselineMessage))] private void HandleBaseline(MessageContext _, PlayTableBaselineMessage message) { _latestBaseline = message; ApplyLatestBaseline(); } [MessageHandler(typeof(PlayTableVisualDeltaMessage))] private void HandleVisualDelta(MessageContext _, PlayTableVisualDeltaMessage message) { if (!_shutdown && message != null) { if (message.Operation != 3) { SupersedePendingVisuals(message.TableKey); } if (!TryApplyVisualDelta(message)) { DeferVisualDelta(message); } } } [MessageHandler(typeof(PlayTableMatchDeltaMessage))] private void HandleMatchDelta(MessageContext _, PlayTableMatchDeltaMessage message) { if (!_shutdown && message != null) { ApplyMatchDelta(message); } } private void ApplyLatestBaseline() { if (_latestBaseline != null && PlayTableInterop.Tables(PlayTableInterop.FindShelfManager()) != null) { _visualEntries.Clear(); _occupied.Clear(); _matchesById.Clear(); _matchByTable.Clear(); for (int i = 0; i < _latestBaseline.Tables.Count; i++) { PlayTableEntry playTableEntry = CopyEntry(_latestBaseline.Tables[i]); _visualEntries[playTableEntry.TableKey] = playTableEntry; _occupied[playTableEntry.TableKey] = playTableEntry.Occupied; } for (int j = 0; j < _latestBaseline.Matches.Count; j++) { AddMatch(_latestBaseline.Matches[j]); } _latestBaseline = null; ReconcileLaunches(); ApplyVisualMirrors(); ApplyPendingVisuals(); } } private void OnPlacementStructureChanged(int _) { if (!_shutdown) { ApplyPendingVisuals(); ReconcileLaunches(); ApplyVisualMirrors(); } } private void ApplyPendingVisuals() { List list = new List(_pendingVisuals.Values); for (int i = 0; i < list.Count; i++) { if (list[i].Operation != 3 && TryApplyVisualDelta(list[i])) { _pendingVisuals.Remove(VisualKey(list[i])); } } list = new List(_pendingVisuals.Values); for (int j = 0; j < list.Count; j++) { if (list[j].Operation == 3 && TryApplyVisualDelta(list[j])) { _pendingVisuals.Remove(VisualKey(list[j])); } } } private void DeferVisualDelta(PlayTableVisualDeltaMessage message) { string key = VisualKey(message); if (_pendingVisuals.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingVisuals[key] = message; } private void SupersedePendingVisuals(int tableKey) { List list = new List(); foreach (KeyValuePair pendingVisual in _pendingVisuals) { if (pendingVisual.Value.TableKey == tableKey) { PredictionApi.Ack(pendingVisual.Value.PredictionId); list.Add(pendingVisual.Key); } } for (int i = 0; i < list.Count; i++) { _pendingVisuals.Remove(list[i]); } } private static string VisualKey(PlayTableVisualDeltaMessage message) { if (message.Operation != 3) { return message.TableKey + ":table"; } return message.TableKey + ":seat:" + message.Seat; } private bool TryApplyVisualDelta(PlayTableVisualDeltaMessage message) { InteractablePlayTable table = default(InteractablePlayTable); ref InteractablePlayTable reference = ref table; Component obj = PlacementApi.ResolveObjectByKey(message.TableKey); reference = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if (message.Operation != 2 && (Object)(object)table == (Object)null) { return false; } if (message.Operation == 3 && !_visualEntries.ContainsKey(message.TableKey)) { return false; } _applyingState++; try { PredictionApi.Confirm(message.PredictionId, delegate { ApplyVisualDelta(message, table); }); } finally { _applyingState--; } ApplyVisualMirrors(); return true; } private void ApplyVisualDelta(PlayTableVisualDeltaMessage message, InteractablePlayTable table) { switch (message.Operation) { case 2: _visualEntries.Remove(message.TableKey); _occupied.Remove(message.TableKey); ClearAppliedTable(message.TableKey); break; case 1: _visualEntries[message.TableKey] = new PlayTableEntry { TableKey = message.TableKey, Occupied = message.Occupied, Boxed = message.Boxed, Seats = CopySeats(message.Seats) }; _occupied[message.TableKey] = message.Occupied; break; case 3: { if (_visualEntries.TryGetValue(message.TableKey, out var value)) { while (value.Seats.Count <= message.Seat) { value.Seats.Add(new PlayTableSeatEntry()); } value.Seats[message.Seat] = CopySeat(message.SeatState); } break; } default: throw new InvalidOperationException("unknown play-table visual delta operation"); } } private void ApplyMatchDelta(PlayTableMatchDeltaMessage message) { PredictionApi.Confirm(message.PredictionId, delegate { if (message.Operation == 1) { AddMatch(message.Match); } else { if (message.Operation != 2) { throw new InvalidOperationException("unknown play-table match delta operation"); } if (_matchesById.TryGetValue(message.MatchId, out var value)) { _matchesById.Remove(message.MatchId); _matchByTable.Remove(value.TableKey); RemoveLaunchLocal(value.TableKey); } } ReconcileLaunches(); }); } internal bool HasActiveMatch(int tableKey) { return _matchByTable.ContainsKey(tableKey); } internal bool TryGetMatch(int tableKey, out PlayTableMatchEntry entry) { if (_matchByTable.TryGetValue(tableKey, out var value) && _matchesById.TryGetValue(value, out entry)) { return true; } entry = null; return false; } private void ReconcileLaunches() { foreach (int item in new List(_launches.Keys)) { LaunchState launchState = _launches[item]; if (TryGetMatch(item, out var entry)) { if (entry.OwnerConn != CoopCore.LocalConnectionId || entry.MatchId != launchState.Match.MatchId) { RemoveLaunchLocal(item); } else { launchState.Match = entry; } } } } private void HandleFinishLeave(PlayTableGame game) { if (TryGetLaunch(game, out var launch)) { SendRelease(launch); } } private void SendRelease(LaunchState launch) { if (launch != null) { int tableKey = launch.Match.TableKey; PredictionApi.Predict("play-table", delegate(Guid predictionId) { SendMatchIntent(3, launch.Match, predictionId); }, delegate { RemoveMatchLocal(launch.Match); _launches.Remove(tableKey); }, delegate { AddMatch(launch.Match); RestoreLaunch(launch); }); RemoveMatchLocal(launch.Match); _launches.Remove(tableKey); } } private void SendMatchIntent(byte op, PlayTableMatchEntry match, Guid predictionId, int deckCardCount = 0) { _context.Send(1, (INetMessage)(object)new PlayTableMatchIntentMessage { PredictionId = predictionId, Op = op, MatchId = match.MatchId, TableKey = match.TableKey, Seat = match.Seat, SideA = match.SideA, DeckCardCount = deckCardCount }); } private bool TryGetLaunch(PlayTableGame game, out LaunchState launch) { launch = null; if (PlayTablePlacementInterop.TryGetTableKey(PlayTableInterop.CurrentTable(game), out var key)) { return _launches.TryGetValue(key, out launch); } return false; } private void RequestMatchObserved(InteractablePlayTable table, BeforeClickSnapshot before) { if ((Object)(object)table == (Object)null || before == null || !PlayTablePlacementInterop.TryGetTableKey(table, out var tableKey)) { PlayTableInterop.RestoreSeat(table, before?.Values, before?.Seat ?? 0); return; } PlayTableMatchEntry match = new PlayTableMatchEntry { MatchId = "playtable-" + CoopCore.LocalConnectionId + "-" + NextMatchNonce(), OwnerConn = CoopCore.LocalConnectionId, TableKey = tableKey, Seat = before.Seat, SideA = (before.Seat == 0), Phase = 2 }; LaunchState launch = new LaunchState { Table = table, Before = before.Values, Match = match, Entered = true, StartedAt = Time.realtimeSinceStartup }; _launches[tableKey] = launch; PredictionApi.Predict("play-table", delegate(Guid predictionId) { SendMatchIntent(0, match, predictionId, PlayTableInterop.DeckCardCount()); }, delegate { ReplayLaunch(table, before.Seat, launch); }, delegate { UndoLaunch(tableKey, launch); }); SendMatchIntent(2, match, Guid.Empty); } private static void ReplayLaunch(InteractablePlayTable table, byte seat, LaunchState launch) { if (!((Object)(object)table == (Object)null)) { PlayTableInterop.ReserveSeat(table, seat, out var _); PlayCardGameManager.SetPlayTable(table, launch.Match.SideA); } } private void UndoLaunch(int tableKey, LaunchState launch) { RemoveMatchLocal(launch.Match); _launches.Remove(tableKey); PlayCardGameManager val = SceneRef.Get(); if ((Object)(object)val?.m_PlayTableGame != (Object)null && PlayTableInterop.HasEnteredTable(launch.Table)) { _suppressEndGameGift = true; try { val.m_PlayTableGame.FinishLeaveGame(false); return; } finally { _suppressEndGameGift = false; } } PlayTableInterop.RestoreReservedSeat(launch.Table, launch.Before, launch.Match.Seat); } private void SendKickIntent(InteractablePlayTable table, int key, PlayTableEntry before) { PredictionApi.Predict("play-table", delegate(Guid predictionId) { SendTableIntent(1, key, predictionId); }, delegate { ApplyLocalKick(table); }, delegate { RestoreEntry(table, before); }); } private void SendBoxIntent(InteractablePlayTable table, bool wasBoxed, Vector3 position, Quaternion rotation) { //IL_001c: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)table == (Object)null || !PlayTablePlacementInterop.TryGetTableKey(table, out var key)) { return; } InteractablePackagingBox_Shelf packagingBoxShelf = ((InteractableObject)table).GetPackagingBoxShelf(); Guid id; Guid boxId = (((Object)(object)packagingBoxShelf != (Object)null && WorldClientBehaviour.TryGetBoxId((InteractablePackagingBox)(object)packagingBoxShelf, out id)) ? id : Guid.Empty); PredictionApi.Predict("play-table", delegate(Guid predictionId) { SendTableIntent(2, key, predictionId, boxId); }, delegate { _applyingPrediction++; try { ((InteractableObject)table).BoxUpObject(true); } finally { _applyingPrediction--; } }, delegate { //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) if (!wasBoxed && ((InteractableObject)table).GetIsBoxedUp()) { PlacementInterop.PlaceBoxedObject((InteractableObject)(object)table, position, rotation); } }); } private void SendTableIntent(byte action, int key, Guid predictionId, Guid boxNetworkId = default(Guid)) { _context.Send(1, (INetMessage)(object)new PlayTableIntentMessage { PredictionId = predictionId, Action = action, ObjectKey = key, BoxNetworkId = boxNetworkId }); } private void RemoveLaunchLocal(int tableKey) { if (_launches.TryGetValue(tableKey, out var value)) { _launches.Remove(tableKey); if (!value.Entered) { PlayTableInterop.RestoreSeat(value.Table, value.Before, value.Match.Seat); } } } private void RestoreLaunch(LaunchState launch) { if (launch != null) { _launches[launch.Match.TableKey] = launch; if (!launch.Entered) { PlayTableInterop.ReserveSeat(launch.Table, launch.Match.Seat, out var _); } } } private void RemoveMatchLocal(PlayTableMatchEntry match) { if (match != null) { _matchesById.Remove(match.MatchId); if (_matchByTable.TryGetValue(match.TableKey, out var value) && value == match.MatchId) { _matchByTable.Remove(match.TableKey); } } } private void ApplyVisualMirrors() { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected I4, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected I4, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected I4, but got Unknown if (PlayTableInterop.Tables(PlayTableInterop.FindShelfManager()) == null) { return; } ClearMirrors(); foreach (KeyValuePair visualEntry in _visualEntries) { Component obj = PlacementApi.ResolveObjectByKey(visualEntry.Key); InteractablePlayTable val = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if (!((Object)(object)val == (Object)null) && !PlayTableInterop.IsLocalGameplayTable(val, HasLocalLaunch)) { List tableGameItemSetList = val.m_TableGameItemSetList; List seats = visualEntry.Value.Seats; for (int i = 0; i < seats.Count && i < tableGameItemSetList.Count; i++) { ApplySeat(visualEntry.Key, i, val, tableGameItemSetList[i], new SeatState { Active = seats[i].Active, PlayerSeat = seats[i].PlayerSeat, PlayMat = (int)seats[i].PlayMat, DeckBox = (int)seats[i].DeckBox, Comic = (int)seats[i].Comic }); } } } } private void ApplySeat(int tableKey, int seat, InteractablePlayTable table, TableGameItemSet set, SeatState want) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown long key = ((long)tableKey << 8) | (byte)seat; _applied.TryGetValue(key, out var value); if ((Object)(object)value?.Set != (Object)null && (value.Set != set || value.Table != table) && !PlayTableInterop.IsLocalGameplayTable(value.Table, HasLocalLaunch)) { ((Component)value.Set).gameObject.SetActive(false); } if (want.Active) { set.SpecificSetup(new TableGameItemSetData { playMatType = (EItemType)want.PlayMat, deckBoxType = (EItemType)want.DeckBox, comicBookType = (EItemType)want.Comic }); ((Component)set).gameObject.SetActive(true); } else { ((Component)set).gameObject.SetActive(false); } ApplyPlayerSeat(table, seat, want.PlayerSeat); _applied[key] = new AppliedVisual { Table = table, Set = set, State = want }; } private void ApplyPlayerSeat(InteractablePlayTable table, int seat, bool playerSeat) { if (!PlayTableInterop.IsRosterManaged(table, HasActiveMatch, HasLocalLaunch)) { table.m_IsPlayerSeat[seat] = playerSeat; } } private void ClearMirrors() { foreach (KeyValuePair item in _applied) { if (item.Value.State.Active && (Object)(object)item.Value.Set != (Object)null && !PlayTableInterop.IsLocalGameplayTable(item.Value.Table, HasLocalLaunch)) { ((Component)item.Value.Set).gameObject.SetActive(false); } } _applied.Clear(); } private void ClearAppliedTable(int tableKey) { List list = new List(); foreach (KeyValuePair item in _applied) { if ((int)(item.Key >> 8) == tableKey) { if ((Object)(object)item.Value.Set != (Object)null) { ((Component)item.Value.Set).gameObject.SetActive(false); } list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { _applied.Remove(list[i]); } } private bool HasLocalLaunch(int tableKey) { return _launches.ContainsKey(tableKey); } private static int NextMatchNonce() { if (_nextMatchNonce == int.MaxValue) { _nextMatchNonce = 0; } return ++_nextMatchNonce; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { _latestBaseline = null; _visualEntries.Clear(); _occupied.Clear(); _matchesById.Clear(); _matchByTable.Clear(); ClearPendingVisuals(); foreach (int item in new List(_launches.Keys)) { RemoveLaunchLocal(item); } ClearMirrors(); } private static PlayTableEntry CaptureEntry(InteractablePlayTable table, int key) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) List list = table?.m_TableGameItemSetList; PlayTableEntry playTableEntry = new PlayTableEntry { TableKey = key, Occupied = ((Object)(object)table != (Object)null && table.GetCurrentPlayerCount() > 0), Boxed = ((Object)(object)table != (Object)null && ((InteractableObject)table).GetIsBoxedUp()) }; int num = 0; while (list != null && num < list.Count) { TableGameItemSet val = list[num]; bool flag = (Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf; TableGameItemSetData val2 = val?.m_TableGameItemSetData; playTableEntry.Seats.Add(new PlayTableSeatEntry { Active = flag, PlayerSeat = PlayTableInterop.IsPlayerSeat(table, num), PlayMat = (EItemType)((flag && val2 != null) ? ((int)val2.playMatType) : 0), DeckBox = (EItemType)((flag && val2 != null) ? ((int)val2.deckBoxType) : 0), Comic = (EItemType)((flag && val2 != null) ? ((int)val2.comicBookType) : 0) }); num++; } return playTableEntry; } private void ApplyLocalKick(InteractablePlayTable table) { _applyingPrediction++; try { PlayTableInterop.InvokeStopTableGame(table); } finally { _applyingPrediction--; } } private void RestoreEntry(InteractablePlayTable table, PlayTableEntry entry) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown if ((Object)(object)table == (Object)null || entry == null) { return; } for (int i = 0; i < entry.Seats.Count && i < table.m_TableGameItemSetList.Count; i++) { TableGameItemSet val = table.m_TableGameItemSetList[i]; PlayTableSeatEntry playTableSeatEntry = entry.Seats[i]; if (playTableSeatEntry.Active) { val.SpecificSetup(new TableGameItemSetData { playMatType = playTableSeatEntry.PlayMat, deckBoxType = playTableSeatEntry.DeckBox, comicBookType = playTableSeatEntry.Comic }); } ((Component)val).gameObject.SetActive(playTableSeatEntry.Active); if (i < table.m_IsPlayerSeat.Count) { table.m_IsPlayerSeat[i] = playTableSeatEntry.PlayerSeat; } } _visualEntries[entry.TableKey] = CopyEntry(entry); _occupied[entry.TableKey] = entry.Occupied; } private void AddMatch(PlayTableMatchEntry match) { if (match != null) { PlayTableMatchEntry playTableMatchEntry = CopyMatch(match); _matchesById[playTableMatchEntry.MatchId] = playTableMatchEntry; _matchByTable[playTableMatchEntry.TableKey] = playTableMatchEntry.MatchId; } } private static PlayTableEntry CopyEntry(PlayTableEntry source) { if (source != null) { return new PlayTableEntry { TableKey = source.TableKey, Occupied = source.Occupied, Boxed = source.Boxed, Seats = CopySeats(source.Seats) }; } return null; } private static List CopySeats(List source) { List list = new List(); int num = 0; while (source != null && num < source.Count) { list.Add(CopySeat(source[num])); num++; } return list; } private static PlayTableSeatEntry CopySeat(PlayTableSeatEntry source) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (source != null) { return new PlayTableSeatEntry { Active = source.Active, PlayerSeat = source.PlayerSeat, PlayMat = source.PlayMat, DeckBox = source.DeckBox, Comic = source.Comic }; } return null; } private static PlayTableMatchEntry CopyMatch(PlayTableMatchEntry source) { if (source != null) { return new PlayTableMatchEntry { MatchId = source.MatchId, OwnerConn = source.OwnerConn, TableKey = source.TableKey, Seat = source.Seat, SideA = source.SideA, Phase = source.Phase }; } return null; } private void ApplyPatches() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0046: Expected O, but got Unknown //IL_006d: 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_008c: Expected O, but got Unknown //IL_008c: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_0148: Expected O, but got Unknown //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Expected O, but got Unknown Patch(AccessTools.Method(typeof(InteractablePlayTable), "StartMoveObject", (Type[])null, (Type[])null), new HarmonyMethod(typeof(MovePatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(MovePatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(InteractablePlayTable), "BoxUpObject", (Type[])null, (Type[])null), new HarmonyMethod(typeof(BoxPatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(BoxPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(PlayTableGame), "FinishLeaveGame", new Type[1] { typeof(bool) }, (Type[])null), null, new HarmonyMethod(typeof(FinishLeavePatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(PlayTableGame), "EvaluateEndGameGift", (Type[])null, (Type[])null), new HarmonyMethod(typeof(EndGameGiftPatch), "Prefix", (Type[])null), null); Patch(AccessTools.Method(typeof(InteractablePlayTable), "OnRightMouseButtonUp", (Type[])null, (Type[])null), new HarmonyMethod(typeof(SeatPatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(SeatPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(ShelfManager), "InitPlayTable", new Type[1] { typeof(InteractablePlayTable) }, (Type[])null), null, new HarmonyMethod(typeof(TableReadyPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(PlayTableGame), "Awake", (Type[])null, (Type[])null), null, new HarmonyMethod(typeof(TableReadyPatch), "Postfix", (Type[])null)); } private void Patch(MethodInfo original, HarmonyMethod prefix, HarmonyMethod postfix) { if (original == null) { CoopPlugin.Log.LogWarning((object)"play-table client patch target missing"); } else { _harmony.Patch((MethodBase)original, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; PlacementApi.StructureChanged -= OnPlacementStructureChanged; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; ClearMirrors(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _launches.Clear(); _visualEntries.Clear(); _occupied.Clear(); _matchesById.Clear(); _matchByTable.Clear(); ClearPendingVisuals(); if (_active == this) { _active = null; } _harmony = null; _context = null; } } private void OnDestroy() { Shutdown(); } private void ClearPendingVisuals() { foreach (PlayTableVisualDeltaMessage value in _pendingVisuals.Values) { PredictionApi.Ack(value.PredictionId); } _pendingVisuals.Clear(); } } [ServerBehaviour] public sealed class PlayTableHostBehaviour : CoopBehaviour { private sealed class Reservation { internal PlayTableMatchEntry Match; internal InteractablePlayTable Table; internal PlayTableSeatSnapshot SeatBefore; } private static class TableChangedPatch { internal static void Postfix(InteractablePlayTable __instance) { _active?.NotifyTableChanged(__instance, Guid.Empty, force: false); } } private static class TableAddedPatch { internal static void Postfix() { _active?.RefreshVisuals(publish: true); } } private static class TableRemovedPatch { internal static void Prefix(InteractablePlayTable __instance) { _active?.NotifyTableRemoved(__instance); } } private static class TableStoppedPatch { internal static void Postfix(InteractablePlayTable __instance) { PlayTableHostBehaviour active = _active; if (!((Object)(object)active == (Object)null)) { if (PlayTablePlacementInterop.TryGetTableKey(__instance, out var key)) { active.Release(key, "table-stopped", Guid.Empty); } if (!active._suppressStoppedVisual) { active.NotifyTableChanged(__instance, Guid.Empty, force: true); } } } } private const int MaxTables = 250; private const int MaxSeats = 8; private static PlayTableHostBehaviour _active; private readonly Dictionary _reservations = new Dictionary(); private readonly Dictionary _knownVisuals = new Dictionary(); private readonly HashSet _fullyJoined = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _suppressStoppedVisual; internal static PlayTableHostBehaviour Active => _active; private void OnEnable() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown if (!_shutdown && _context == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.play-table.host"); ApplyPatches(); RefreshVisuals(publish: false); } } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State)) { _fullyJoined.Add(connection.Id); SendBaseline(connection.Id); } } [OnClientDisconnected] private void ReleaseConnection(PeerConnection connection, DisconnectInfo _) { if (connection == null) { return; } _fullyJoined.Remove(connection.Id); List list = new List(); foreach (KeyValuePair reservation in _reservations) { if (reservation.Value.Match.OwnerConn == connection.Id) { list.Add(reservation.Key); } } for (int i = 0; i < list.Count; i++) { Release(list[i], "disconnect", Guid.Empty); } } [MessageHandler(typeof(PlayTableMatchIntentMessage))] private void HandleMatchIntent(MessageContext messageContext, PlayTableMatchIntentMessage request) { if (IsPeerMessage(messageContext) && request != null) { int id = messageContext.Connection.Id; switch (request.Op) { case 0: StartMatch(request, id); break; case 1: CancelMatch(request, id); break; case 2: MarkMatchStarted(request, id); break; case 3: ReleaseMatch(request, id); break; default: Reject(id, request.PredictionId, "unknown match intent"); break; } } } [MessageHandler(typeof(PlayTableIntentMessage))] private void HandleTableIntent(MessageContext messageContext, PlayTableIntentMessage message) { if (!IsPeerMessage(messageContext) || message == null) { return; } int id = messageContext.Connection.Id; if (message.Kind != 1 || (message.Action != 1 && message.Action != 2)) { Reject(id, message.PredictionId, "invalid table intent"); return; } Component obj = PlacementApi.ResolveObjectByKey(message.ObjectKey); InteractablePlayTable val = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if ((Object)(object)val == (Object)null || !PlayTablePlacementInterop.TryGetTableKey(val, out var key) || key != message.ObjectKey) { Reject(id, message.PredictionId, "stale table identity"); return; } if (message.Action == 1) { if (val.GetIsTournamentPlayTable() || val.GetCurrentPlayerCount() <= 0 || _reservations.ContainsKey(message.ObjectKey)) { Reject(id, message.PredictionId, "table is not occupied by a kickable game"); return; } _suppressStoppedVisual = true; try { PlayTableInterop.InvokeStopTableGame(val); } finally { _suppressStoppedVisual = false; } NotifyTableChanged(val, message.PredictionId, force: true); return; } if (((InteractableObject)val).GetIsBoxedUp() || val.GetCurrentPlayerCount() > 0 || _reservations.ContainsKey(message.ObjectKey) || PlayTableInterop.HostPlayingAt(val)) { Reject(id, message.PredictionId, "occupied or reserved table cannot be boxed"); return; } if (val.GetTournamentPlayTableNumber() > 0) { Reject(id, message.PredictionId, "tournament-numbered table cannot be boxed"); return; } if (!WorldHostBehaviour.PushCreatedBoxId(message.BoxNetworkId)) { Reject(id, message.PredictionId, "package box id is not fresh"); return; } WorldHostBehaviour.Active?.SetNextMutationPrediction(message.PredictionId); try { ((InteractableObject)val).BoxUpObject(true); } finally { WorldHostBehaviour.Active?.SetNextMutationPrediction(Guid.Empty); } if (!((InteractableObject)val).GetIsBoxedUp() || (Object)(object)((InteractableObject)val).GetPackagingBoxShelf() == (Object)null) { WorldHostBehaviour.CancelCreatedBoxId(message.BoxNetworkId); Reject(id, message.PredictionId, "table boxing did not produce a package box"); } } internal bool HasActiveMatch(int tableKey) { return _reservations.ContainsKey(tableKey); } internal static bool IsPlacementMoveAllowed(InteractableObject obj) { InteractablePlayTable val = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if (val == null) { return true; } if (!PlayTablePlacementInterop.TryGetTableKey(val, out var key)) { return false; } if (((Object)(object)_active == (Object)null || !_active._reservations.ContainsKey(key)) && val.GetCurrentPlayerCount() <= 0) { return !PlayTableInterop.HostPlayingAt(val); } return false; } private void StartMatch(PlayTableMatchIntentMessage request, int owner) { Component obj = PlacementApi.ResolveObjectByKey(request.TableKey); InteractablePlayTable val = (InteractablePlayTable)(object)((obj is InteractablePlayTable) ? obj : null); if ((Object)(object)val == (Object)null || !PlayTablePlacementInterop.TryGetTableKey(val, out var key) || request.TableKey != key) { Reject(owner, request.PredictionId, "table key mismatch"); return; } if (_reservations.ContainsKey(request.TableKey)) { Reject(owner, request.PredictionId, "table already reserved"); return; } foreach (Reservation value in _reservations.Values) { if (value.Match.OwnerConn == owner) { Reject(owner, request.PredictionId, "owner already has a match"); return; } if (value.Match.MatchId == request.MatchId) { Reject(owner, request.PredictionId, "match id is already active"); return; } } if (PlayTableInterop.HostPlayingAt(val) || request.Seat > 1) { Reject(owner, request.PredictionId, "table or seat is unavailable"); return; } List isSeatOccupied = val.m_IsSeatOccupied; List isSeatBooked = val.m_IsSeatBooked; List isQueueOccupied = val.m_IsQueueOccupied; int num = ((request.Seat == 0) ? 1 : 0); if (isSeatOccupied == null || isSeatBooked == null || isQueueOccupied == null || isSeatOccupied.Count <= request.Seat || isSeatOccupied[request.Seat] || isSeatBooked.Count <= request.Seat || isSeatBooked[request.Seat] || isQueueOccupied.Count <= request.Seat || isQueueOccupied[request.Seat] || isSeatOccupied.Count <= num || !isSeatOccupied[num] || request.SideA != (request.Seat == 0)) { Reject(owner, request.PredictionId, "requested seat must be free and the customer seat occupied"); return; } int maxDeckCardCount = GameInstance.GetMaxDeckCardCount(); if (request.DeckCardCount < 1 || request.DeckCardCount > maxDeckCardCount) { Reject(owner, request.PredictionId, "invalid deck size"); return; } if (string.IsNullOrEmpty(request.MatchId) || request.MatchId.Length > 64) { Reject(owner, request.PredictionId, "invalid match id"); return; } if (!PlayTableInterop.ReserveSeat(val, request.Seat, out var before)) { Reject(owner, request.PredictionId, "reservation race"); return; } Reservation reservation = new Reservation { Table = val, SeatBefore = before, Match = new PlayTableMatchEntry { MatchId = request.MatchId, OwnerConn = owner, TableKey = request.TableKey, Seat = request.Seat, SideA = request.SideA, Phase = 1 } }; _reservations.Add(request.TableKey, reservation); NotifyTableChanged(val, request.PredictionId, force: false); PublishMatchUpsert(reservation.Match, request.PredictionId); } private void CancelMatch(PlayTableMatchIntentMessage request, int owner) { if (!TryGetOwnedReservation(request, owner, out var reservation)) { Reject(owner, request.PredictionId, "cancel is not authenticated"); } else { Release(reservation.Match.TableKey, "cancelled", request.PredictionId); } } private void MarkMatchStarted(PlayTableMatchIntentMessage request, int owner) { if (!TryGetOwnedReservation(request, owner, out var reservation)) { Reject(owner, request.PredictionId, "match is not owned by sender"); return; } reservation.Match.Phase = 2; PublishMatchUpsert(reservation.Match, request.PredictionId); } private void ReleaseMatch(PlayTableMatchIntentMessage request, int owner) { if (!TryGetOwnedReservation(request, owner, out var reservation)) { Reject(owner, request.PredictionId, "match release is not authenticated"); } else { Release(reservation.Match.TableKey, "gameplay-finished", request.PredictionId); } } private bool TryGetOwnedReservation(PlayTableMatchIntentMessage request, int owner, out Reservation reservation) { if (_reservations.TryGetValue(request.TableKey, out reservation) && reservation.Match.OwnerConn == owner) { return reservation.Match.MatchId == request.MatchId; } return false; } private bool Release(int tableKey, string reason, Guid predictionId) { if (!_reservations.TryGetValue(tableKey, out var value)) { return false; } _reservations.Remove(tableKey); PlayTableInterop.RestoreReservedSeat(value.Table, value.SeatBefore, value.Match.Seat); CoopPlugin.Log.LogInfo((object)("play-table match released table=" + tableKey + " reason=" + reason)); NotifyTableChanged(value.Table, predictionId, force: false); PublishMatchRelease(value.Match, predictionId); return true; } private void Reject(int connectionId, Guid predictionId, string reason) { CoopPlugin.Log.LogWarning((object)("play-table intent rejected for connection=" + connectionId + ": " + reason)); if (predictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, connectionId, predictionId); } } private PlayTableBaselineMessage BuildBaseline() { List list = PlayTableInterop.Tables(PlayTableInterop.FindShelfManager()); if (list == null) { return null; } PlayTableBaselineMessage playTableBaselineMessage = new PlayTableBaselineMessage(); int num = Math.Min(list.Count, 250); for (int i = 0; i < num; i++) { playTableBaselineMessage.Tables.Add(BuildTableEntry(list[i])); } foreach (Reservation value in _reservations.Values) { playTableBaselineMessage.Matches.Add(CopyMatch(value.Match)); } RefreshVisuals(publish: false); return playTableBaselineMessage; } private void SendBaseline(int connectionId) { if (connectionId > 0 && _context.InGame()) { PlayTableBaselineMessage playTableBaselineMessage = BuildBaseline(); if (playTableBaselineMessage != null) { _context.Send(connectionId, (INetMessage)(object)playTableBaselineMessage); } } } private void SendBaselinesToJoined() { foreach (int item in new List(_fullyJoined)) { SendBaseline(item); } } private void PublishVisual(PlayTableVisualDeltaMessage delta) { if (!_shutdown && _context.InGame()) { _context.Broadcast((INetMessage)(object)delta); } } private void NotifyTableChanged(InteractablePlayTable table, Guid predictionId, bool force) { if ((Object)(object)table == (Object)null || !PlayTablePlacementInterop.TryGetTableKey(table, out var key)) { return; } PlayTableEntry playTableEntry = BuildTableEntry(table); if (!_knownVisuals.TryGetValue(key, out var value)) { PublishVisual(CreateTableDelta(playTableEntry, predictionId)); } else if (force || value.Occupied != playTableEntry.Occupied || value.Boxed != playTableEntry.Boxed) { PublishVisual(CreateTableDelta(playTableEntry, predictionId)); } else { for (int i = 0; i < playTableEntry.Seats.Count; i++) { if (i >= value.Seats.Count || !SameSeat(value.Seats[i], playTableEntry.Seats[i])) { PublishVisual(new PlayTableVisualDeltaMessage { PredictionId = predictionId, Operation = 3, TableKey = key, Seat = (byte)i, SeatState = CopySeat(playTableEntry.Seats[i]) }); } } } _knownVisuals[key] = playTableEntry; } private void NotifyTableRemoved(InteractablePlayTable table) { if (!((Object)(object)table == (Object)null) && PlayTablePlacementInterop.TryGetTableKey(table, out var key)) { Release(key, "table-removed", Guid.Empty); if (_knownVisuals.Remove(key)) { PublishVisual(new PlayTableVisualDeltaMessage { Operation = 2, TableKey = key }); } } } private void RefreshVisuals(bool publish) { Dictionary dictionary = new Dictionary(); List list = PlayTableInterop.Tables(PlayTableInterop.FindShelfManager()); int num = Math.Min(list?.Count ?? 0, 250); int num2 = 0; while (list != null && num2 < num) { InteractablePlayTable val = list[num2]; if ((Object)(object)val != (Object)null && PlayTablePlacementInterop.TryGetTableKey(val, out var key)) { dictionary[key] = BuildTableEntry(val); } num2++; } List list2 = new List(); foreach (KeyValuePair knownVisual in _knownVisuals) { if (!dictionary.ContainsKey(knownVisual.Key)) { list2.Add(knownVisual.Key); } } for (int i = 0; i < list2.Count; i++) { if (publish) { PublishVisual(new PlayTableVisualDeltaMessage { Operation = 2, TableKey = list2[i] }); } _knownVisuals.Remove(list2[i]); } foreach (KeyValuePair item in dictionary) { if (publish && !_knownVisuals.ContainsKey(item.Key)) { PublishVisual(CreateTableDelta(item.Value, Guid.Empty)); } _knownVisuals[item.Key] = item.Value; } } private void PublishMatchUpsert(PlayTableMatchEntry match, Guid predictionId) { _context.Broadcast((INetMessage)(object)new PlayTableMatchDeltaMessage { PredictionId = predictionId, Operation = 1, MatchId = match.MatchId, TableKey = match.TableKey, Match = CopyMatch(match) }); } private void PublishMatchRelease(PlayTableMatchEntry match, Guid predictionId) { _context.Broadcast((INetMessage)(object)new PlayTableMatchDeltaMessage { PredictionId = predictionId, Operation = 2, MatchId = match.MatchId, TableKey = match.TableKey }); } private static PlayTableVisualDeltaMessage CreateTableDelta(PlayTableEntry entry, Guid predictionId) { return new PlayTableVisualDeltaMessage { PredictionId = predictionId, Operation = 1, TableKey = entry.TableKey, Occupied = entry.Occupied, Boxed = entry.Boxed, Seats = CopySeats(entry.Seats) }; } private static PlayTableEntry BuildTableEntry(InteractablePlayTable table) { //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_0105: Unknown result type (might be due to invalid IL or missing references) List list = table?.m_TableGameItemSetList; int num = Math.Min(list?.Count ?? 0, 8); PlayTablePlacementInterop.TryGetTableKey(table, out var key); PlayTableEntry playTableEntry = new PlayTableEntry { TableKey = key, Occupied = ((Object)(object)table != (Object)null && table.GetCurrentPlayerCount() > 0), Boxed = ((Object)(object)table != (Object)null && ((InteractableObject)table).GetIsBoxedUp()) }; for (int i = 0; i < num; i++) { TableGameItemSet val = list[i]; bool flag = (Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf; TableGameItemSetData val2 = val?.m_TableGameItemSetData; playTableEntry.Seats.Add(new PlayTableSeatEntry { Active = flag, PlayerSeat = PlayTableInterop.IsPlayerSeat(table, i), PlayMat = (EItemType)((flag && val2 != null) ? ((int)val2.playMatType) : 0), DeckBox = (EItemType)((flag && val2 != null) ? ((int)val2.deckBoxType) : 0), Comic = (EItemType)((flag && val2 != null) ? ((int)val2.comicBookType) : 0) }); } return playTableEntry; } private static bool SameSeat(PlayTableSeatEntry left, PlayTableSeatEntry right) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (left != null && right != null && left.Active == right.Active && left.PlayerSeat == right.PlayerSeat && left.PlayMat == right.PlayMat && left.DeckBox == right.DeckBox) { return left.Comic == right.Comic; } return false; } private static List CopySeats(List source) { List list = new List(); int num = 0; while (source != null && num < source.Count) { list.Add(CopySeat(source[num])); num++; } return list; } private static PlayTableSeatEntry CopySeat(PlayTableSeatEntry source) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (source != null) { return new PlayTableSeatEntry { Active = source.Active, PlayerSeat = source.PlayerSeat, PlayMat = source.PlayMat, DeckBox = source.DeckBox, Comic = source.Comic }; } return null; } private static PlayTableMatchEntry CopyMatch(PlayTableMatchEntry source) { return new PlayTableMatchEntry { MatchId = source.MatchId, OwnerConn = source.OwnerConn, TableKey = source.TableKey, Seat = source.Seat, SideA = source.SideA, Phase = source.Phase }; } private bool IsPeerMessage(MessageContext context) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && _context.InGame() && context?.Connection != null) { return IsJoinPhase(context.Connection.State); } return false; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { _reservations.Clear(); _knownVisuals.Clear(); RefreshVisuals(publish: false); SendBaselinesToJoined(); } private void ApplyPatches() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0046: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown Patch(AccessTools.Method(typeof(InteractablePlayTable), "OnDestroyed", (Type[])null, (Type[])null), new HarmonyMethod(typeof(TableRemovedPatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(TableChangedPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(ShelfManager), "InitPlayTable", new Type[1] { typeof(InteractablePlayTable) }, (Type[])null), null, new HarmonyMethod(typeof(TableAddedPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(InteractablePlayTable), "CustomerHasReached", (Type[])null, (Type[])null), null, new HarmonyMethod(typeof(TableChangedPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(InteractablePlayTable), "StopTableGame", (Type[])null, (Type[])null), null, new HarmonyMethod(typeof(TableStoppedPatch), "Postfix", (Type[])null)); Patch(AccessTools.Method(typeof(InteractablePlayTable), "OnRightMouseButtonUp", (Type[])null, (Type[])null), null, new HarmonyMethod(typeof(TableChangedPatch), "Postfix", (Type[])null)); } private void Patch(MethodInfo original, HarmonyMethod prefix, HarmonyMethod postfix) { if (original == null) { CoopPlugin.Log.LogWarning((object)"play-table host patch target missing"); } else { _harmony.Patch((MethodBase)original, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _reservations.Clear(); _knownVisuals.Clear(); _fullyJoined.Clear(); if (_active == this) { _active = null; } _harmony = null; _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class PlayTablePlacementInterop { internal const int TableIdentityKind = 6; internal static bool TryGetTableKey(InteractablePlayTable table, out int key) { key = 0; if ((Object)(object)table == (Object)null) { return false; } if (PlacementApi.TryMakeObjectKey(6, (InteractableObject)(object)table, out key)) { return true; } CoopPlugin.Log.LogWarning((object)("play-table: failed to resolve table key; table=" + ((Object)table).name + " type=" + ((object)Unsafe.As(ref ((InteractableObject)table).m_ObjectType)/*cast due to .constrained prefix*/).ToString())); return false; } } internal sealed class PlayTableSeatSnapshot { internal bool PlayerOccupied; internal bool SeatOccupied; internal bool SeatBooked; internal bool QueueOccupied; internal bool PlayerSeat; } internal static class PlayTableInterop { private static readonly FieldInfo PlayerOccupiedField = AccessTools.Field(typeof(InteractablePlayTable), "m_IsPlayerOccupied"); private static readonly FieldInfo TableModeField = AccessTools.Field(typeof(PlayTableGame), "m_IsPlayTableGameMode"); private static readonly FieldInfo CurrentTableField = AccessTools.Field(typeof(PlayTableGame), "m_CurrentInteractablePlayTable"); private static readonly MethodInfo StopTableGameMethod = AccessTools.Method(typeof(InteractablePlayTable), "StopTableGame", (Type[])null, (Type[])null); internal static ShelfManager FindShelfManager() { return SceneRef.Get(); } internal static List Tables(ShelfManager manager) { if (!((Object)(object)manager == (Object)null)) { return manager.m_PlayTableList; } return null; } internal static bool IsPlayerSeat(InteractablePlayTable table, int seat) { List list = table?.m_IsPlayerSeat; if (list != null && seat >= 0 && seat < list.Count) { return list[seat]; } return false; } internal static bool IsRosterManaged(InteractablePlayTable table, Func hasActiveMatch, Func hasLocalLaunch) { if ((Object)(object)table != (Object)null && PlayTablePlacementInterop.TryGetTableKey(table, out var key)) { if (!hasActiveMatch(key)) { return hasLocalLaunch(key); } return true; } return false; } internal static bool IsLocalGameplayTable(InteractablePlayTable table, Func hasLocalLaunch) { if ((Object)(object)table != (Object)null) { if (!HasEnteredTable(table)) { if (PlayTablePlacementInterop.TryGetTableKey(table, out var key)) { return hasLocalLaunch(key); } return false; } return true; } return false; } internal static bool HasEnteredTable(InteractablePlayTable table) { PlayTableGame val = SceneRef.Get()?.m_PlayTableGame; InteractablePlayTable val2 = (InteractablePlayTable)(((Object)(object)val == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val != (Object)null && TableModeField != null && (bool)TableModeField.GetValue(val)) { return val2 == table; } return false; } internal static InteractablePlayTable CurrentTable(PlayTableGame game) { if (!((Object)(object)game == (Object)null)) { object? obj = CurrentTableField?.GetValue(game); return (InteractablePlayTable)((obj is InteractablePlayTable) ? obj : null); } return null; } internal static bool HostPlayingAt(InteractablePlayTable table) { return HasEnteredTable(table); } internal static bool TryGetClickedSeat(InteractablePlayTable table, out int seat) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) seat = -1; List list = table?.m_IsSeatOccupied; Collider val = SceneRef.Get()?.m_PlayerCollider; if ((Object)(object)table == (Object)null || list == null || list.Count < 2 || (Object)(object)val == (Object)null) { return false; } Vector3 val2 = ((Component)table).transform.position - ((Component)val).transform.position; val2.y = 0f; if (Vector3.Dot(((Vector3)(ref val2)).normalized, ((Component)table).transform.right) > 0f) { if (list[1] && !list[0]) { seat = 0; } } else if (list[0] && !list[1]) { seat = 1; } return seat >= 0; } internal static PlayTableSeatSnapshot CaptureSeat(InteractablePlayTable table, byte seat) { return new PlayTableSeatSnapshot { PlayerOccupied = ((Object)(object)table != (Object)null && (bool)(PlayerOccupiedField?.GetValue(table) ?? ((object)false))), SeatOccupied = ReadSeat(table?.m_IsSeatOccupied, seat), SeatBooked = ReadSeat(table?.m_IsSeatBooked, seat), QueueOccupied = ReadSeat(table?.m_IsQueueOccupied, seat), PlayerSeat = ReadSeat(table?.m_IsPlayerSeat, seat) }; } internal static void RestoreSeat(InteractablePlayTable table, PlayTableSeatSnapshot snapshot, byte seat) { if (!((Object)(object)table == (Object)null) && snapshot != null) { PlayerOccupiedField?.SetValue(table, snapshot.PlayerOccupied); SetSeat(table.m_IsSeatOccupied, snapshot.SeatOccupied, seat); SetSeat(table.m_IsSeatBooked, snapshot.SeatBooked, seat); SetSeat(table.m_IsQueueOccupied, snapshot.QueueOccupied, seat); SetSeat(table.m_IsPlayerSeat, snapshot.PlayerSeat, seat); } } internal static bool ReserveSeat(InteractablePlayTable table, byte seat, out PlayTableSeatSnapshot before) { before = null; if ((Object)(object)table == (Object)null || seat >= 2 || table.m_IsSeatOccupied == null || table.m_IsSeatBooked == null || table.m_IsQueueOccupied == null || table.m_IsPlayerSeat == null || seat >= table.m_IsSeatOccupied.Count || seat >= table.m_IsSeatBooked.Count || seat >= table.m_IsQueueOccupied.Count || seat >= table.m_IsPlayerSeat.Count || table.m_IsSeatOccupied[seat]) { return false; } if (table.m_IsSeatBooked[seat] || table.m_IsQueueOccupied[seat]) { return false; } before = CaptureSeat(table, seat); PlayerOccupiedField?.SetValue(table, true); SetSeat(table.m_IsSeatOccupied, value: true, seat); SetSeat(table.m_IsSeatBooked, value: true, seat); SetSeat(table.m_IsQueueOccupied, value: true, seat); SetSeat(table.m_IsPlayerSeat, value: true, seat); return true; } internal static void RestoreReservedSeat(InteractablePlayTable table, PlayTableSeatSnapshot before, byte seat) { if (!((Object)(object)table == (Object)null) && before != null) { RestoreSeat(table, before, seat); } } internal static int DeckCardCount() { List deckCompactCardDataList = CPlayerData.m_DeckCompactCardDataList; int currentSelectedDeckIndex = CPlayerData.m_CurrentSelectedDeckIndex; if (deckCompactCardDataList == null || currentSelectedDeckIndex < 0 || currentSelectedDeckIndex >= deckCompactCardDataList.Count || deckCompactCardDataList[currentSelectedDeckIndex] == null) { return 0; } return deckCompactCardDataList[currentSelectedDeckIndex].GetTotalCardCount(); } internal static void InvokeStopTableGame(InteractablePlayTable table) { if (StopTableGameMethod == null) { throw new MissingMethodException(typeof(InteractablePlayTable).FullName, "StopTableGame"); } StopTableGameMethod.Invoke(table, null); } private static bool ReadSeat(List values, byte seat) { if (values != null && seat < values.Count) { return values[seat]; } return false; } private static void SetSeat(List values, bool value, byte seat) { if (values != null && seat < values.Count) { values[seat] = value; } } } [NetworkMessage] public sealed class PlayTableBaselineMessage : INetMessage { public List Tables = new List(); public List Matches = new List(); } [NetworkMessage] public sealed class PlayTableVisualDeltaMessage : IPredictedMessage, INetMessage { public const byte TableUpsert = 1; public const byte TableRemove = 2; public const byte SeatUpdate = 3; public byte Operation; public int TableKey; public bool Occupied; public bool Boxed; public byte Seat = byte.MaxValue; public PlayTableSeatEntry SeatState; public List Seats = new List(); public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PlayTableMatchDeltaMessage : IPredictedMessage, INetMessage { public const byte Upsert = 1; public const byte Release = 2; public byte Operation; public string MatchId; public int TableKey; public PlayTableMatchEntry Match; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PlayTableMatchIntentMessage : IPredictedMessage, INetMessage { public const byte OpStart = 0; public const byte OpCancel = 1; public const byte OpStarted = 2; public const byte OpRelease = 3; public byte Op; public string MatchId; public int TableKey; public byte Seat; public bool SideA; public int DeckCardCount; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class PlayTableIntentMessage : IPredictedMessage, INetMessage { public const byte IntentKindPlayTable = 1; public const byte IntentKickTable = 1; public const byte IntentBoxTable = 2; public byte Kind = 1; public byte Action; public int ObjectKey; public Guid BoxNetworkId; public Guid PredictionId { get; set; } } public sealed class PlayTableEntry { public int TableKey; public bool Occupied; public bool Boxed; public List Seats = new List(); } public sealed class PlayTableSeatEntry { public bool Active; public bool PlayerSeat; public EItemType PlayMat; public EItemType DeckBox; public EItemType Comic; } public sealed class PlayTableMatchEntry { public const int StateReleased = 0; public const int StateReserved = 1; public const int StateStarted = 2; public string MatchId; public int OwnerConn; public int TableKey; public byte Seat; public bool SideA; public int Phase; } } namespace CardShopCoop.Modules.Npc { internal sealed class NpcNativeActivationGuard : MonoBehaviour { private void OnEnable() { NpcClientBehaviour.SuppressNativeActivation(((Component)this).gameObject); } } [ClientBehaviour] public sealed class NpcClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(CustomerManager), "Update")] private static class CustomerManagerPatch { [HarmonyPrefix] private static bool Prefix() { return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(Customer), "Update")] private static class CustomerPatch { [HarmonyPrefix] private static bool Prefix() { return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(Customer), "Start")] private static class CustomerStartPatch { [HarmonyPrefix] private static bool Prefix() { return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(Customer), "ActivateCustomer")] private static class CustomerActivationPatch { [HarmonyPrefix] private static bool Prefix(Customer __instance) { _active?.TrackNativeRoot((__instance != null) ? ((Component)__instance).gameObject : null); NpcClientBehaviour.CustomerPoolChanged?.Invoke(); return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(Customer), "DeactivateCustomer")] private static class CustomerDeactivationPatch { [HarmonyPrefix] private static bool Prefix(Customer __instance) { if (!((Object)(object)_active == (Object)null)) { return !IsExistingCustomer(__instance); } return true; } } [HarmonyPatch(typeof(WorkerManager), "ActivateWorker")] private static class WorkerManagerPatch { [HarmonyPrefix] private static bool Prefix() { return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(CustomerManager), "Start")] private static class CustomerManagerStartPatch { [HarmonyPostfix] private static void Postfix(CustomerManager __instance) { _active?.SuppressInitialPopulation(__instance); _active?.SignalCustomerManagerReady(); } } [HarmonyPatch(typeof(WorkerManager), "Start")] private static class WorkerManagerStartPatch { [HarmonyPostfix] private static void Postfix() { _active?.SuppressInitialPopulation(null); _active?.SignalWorkerManagerReady(); } } [HarmonyPatch(typeof(Worker), "ActivateWorker")] private static class WorkerActivationPatch { [HarmonyPrefix] private static bool Prefix(Worker __instance) { _active?.TrackNativeRoot((__instance != null) ? ((Component)__instance).gameObject : null); return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(Worker), "Update")] private static class WorkerUpdatePatch { [HarmonyPrefix] private static bool Prefix() { return (Object)(object)_active == (Object)null; } } [HarmonyPatch(typeof(CharacterCustomization), "Initialize")] private static class CustomizationPatch { [HarmonyPostfix] private static void Postfix() { if (!((Object)(object)_active == (Object)null) && !_active._shutdown && !_active._dressing) { _active.ApplyPendingBaseline(); _active.ApplyPendingIdentities(); } } } [Flags] private enum NpcFlags : ushort { None = 0, HoldingBag = 1, HandingOverCash = 2, IsSitting = 4, IsPlaying = 8, IsHoldingBox = 0x10, Smelly = 0x20, Exclaim = 0x40, Female = 0x80, Sprayed = 0x100, Cleaned = 0x200 } private sealed class ExistingCustomer { public Customer Customer; public int RootInstanceId; public int Generation; public readonly Snap[] Buf = new Snap[4]; public int BufHead; public int BufCount; public NpcFlags Flags; public int AppliedFlags = -1; public Vector3 PrevRenderedPos; public float RenderYaw; public float AnimSpeed; public float AppliedAnimSpeed = float.NaN; public bool KeepPuppetVisible; public bool PuppetReadyNotified; public int GrabSequence = int.MinValue; } private struct Snap { public Vector3 Pos; public float Yaw; public float Speed; public NpcFlags Flags; public float Time; } private class Puppet { public GameObject Go; public bool VisualFromLive; public int RootInstanceId; public Animator Anim; public CharacterCustomization Custom; public GameObject Bag; public GameObject Cash; public GameObject CardFan; public GameObject CardSingle; public GameObject Smelly; public GameObject Clean; public GameObject Exclaim; public bool Female; public byte Kind; public int Identity; public bool HasIdentity; public string CharName = ""; public readonly Snap[] Buf = new Snap[4]; public int BufHead; public int BufCount; public NpcFlags Flags; public int AppliedFlags = -1; public float RenderYaw; public Vector3 PrevRenderedPos; public float AnimSpeed; public float AppliedAnimSpeed = float.NaN; public Transform HoldBox; public GameObject BoxProp; public bool BoxPropBig; public EItemType BoxPropType; public GameObject BoxPropStaticMesh; public GameObject BoxPropRigMesh; public GameObject BoxPropOpen; public GameObject BoxPropClosed; public GameObject BoxPropOutlineOpen; public GameObject BoxPropOutlineClosed; public Guid HoldBoxNetworkId; public bool HoldBoxOpened; public Guid AppliedHeldBoxNetworkId; public bool HoldBig; public EItemType HoldItemType; public int PendingIdentity; public bool PendingFemale; public int GrabSequence = int.MinValue; } private sealed class PooledVisual { public VisualKey Key; public GameObject Go; public bool FromLiveSource; public int RootInstanceId; public Animator Anim; public CharacterCustomization Custom; public GameObject Bag; public GameObject Cash; public GameObject CardFan; public GameObject CardSingle; public GameObject Smelly; public GameObject Clean; public GameObject Exclaim; public Transform HoldBox; } private readonly struct VisualKey : IEquatable { internal byte Kind { get; } internal bool Female { get; } internal string CharName { get; } internal VisualKey(byte kind, bool female, string charName) { Kind = kind; Female = female; CharName = charName ?? ""; } public bool Equals(VisualKey other) { if (Kind == other.Kind && Female == other.Female) { return string.Equals(CharName, other.CharName, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { if (obj is VisualKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (int)(((uint)(Kind * 397) ^ (Female ? 1u : 0u)) * 397) ^ StringComparer.Ordinal.GetHashCode(CharName); } public override string ToString() { return "kind=" + Kind + " female=" + Female + " name=" + CharName; } } private readonly struct NpcDeltaKey : IEquatable { internal byte Kind { get; } internal ushort Index { get; } internal int Identity { get; } internal NpcDeltaKey(byte kind, ushort index, int identity) { Kind = kind; Index = index; Identity = identity; } public bool Equals(NpcDeltaKey other) { if (Kind == other.Kind && Index == other.Index) { return Identity == other.Identity; } return false; } public override bool Equals(object obj) { if (obj is NpcDeltaKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((Kind * 397) ^ Index) * 397) ^ Identity; } } private static NpcClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private const byte KindCustomer = 0; private const byte KindWorker = 1; private const int UnsetActionSequence = int.MinValue; private const float InterpDelay = 0.15f; private static readonly int HashMoveSpeed = Animator.StringToHash("MoveSpeed"); private static readonly int HashHoldingBag = Animator.StringToHash("HoldingBag"); private static readonly int HashHandingOverCash = Animator.StringToHash("HandingOverCash"); private static readonly int HashIsSitting = Animator.StringToHash("IsSitting"); private static readonly int HashIsPlaying = Animator.StringToHash("IsPlaying"); private static readonly int HashIsHoldingBox = Animator.StringToHash("IsHoldingBox"); private static readonly int HashIsBeingSprayed = Animator.StringToHash("IsBeingSprayed"); private static readonly MethodInfo MiEvaluateWorkerAttribute = AccessTools.Method(typeof(Worker), "EvaluateWorkerAttribute", (Type[])null, (Type[])null); private static readonly MethodInfo MiEvaluateSkillLevel = AccessTools.Method(typeof(Worker), "EvaluateSkillLevel", (Type[])null, (Type[])null); private static readonly FieldInfo FiCustomizationHairObjects = AccessTools.Field(typeof(CharacterCustomization), "HairObjects"); private static readonly FieldInfo FiCustomizationApparelObjects = AccessTools.Field(typeof(CharacterCustomization), "ApparelObjects"); private readonly Dictionary _existing = new Dictionary(); private readonly HashSet _nativeNpcRoots = new HashSet(); private readonly HashSet _allowedNativeRoots = new HashSet(); private readonly HashSet _puppetRoots = new HashSet(); private readonly HashSet _knownNonNpcRoots = new HashSet(); private readonly Dictionary _puppets = new Dictionary(); private NpcBaselineMessage _pendingBaseline; private readonly Dictionary _pendingIdentities = new Dictionary(); private readonly Dictionary _pendingStates = new Dictionary(); private bool _dressing; private float _now; private float _clockOffset; private bool _clockInit; private const int VisualPoolCapacity = 12; private readonly Dictionary> _visualPool = new Dictionary>(); private readonly List _visualPoolOrder = new List(); public static readonly HashSet SuppressedCustomer = new HashSet(); private static CustomerManager s_diagCm; public int PuppetCount => _puppets.Count; public static int ExistingMirrorCount { get { if (!((Object)(object)_active == (Object)null)) { return _active._existing.Count; } return 0; } } internal static event Action ExistingCustomerPuppetReady; internal static event Action CustomerManagerReady; internal static event Action WorkerManagerReady; internal static event Action CustomerPoolChanged; internal static event Action CustomerCapacityChanged; internal static event Action ExistingCustomerChanged; private void OnEnable() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (_harmony == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.npc.client"); _harmony.CreateClassProcessor(typeof(CustomerManagerPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerActivationPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerDeactivationPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerManagerPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerManagerStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerManagerStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerActivationPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerUpdatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomizationPatch)).Patch(); SuppressInitialPopulation(SceneRef.Get()); SignalCustomerManagerReady(); SignalWorkerManagerReady(); } } private void Update() { using (PerfProbe.Sample("module.npc.client-update")) { UpdateInner(); } } private void UpdateInner() { bool inGame = _context.InGame(); float deltaTime = Time.deltaTime; TickPuppets(deltaTime, inGame); } private void SuppressInitialPopulation(CustomerManager manager) { CustomerManager obj = manager ?? SceneRef.Get(); List list = ((obj != null) ? obj.GetCustomerList() : null); if (list != null) { for (int i = 0; i < list.Count; i++) { Customer val = list[i]; TrackNativeRoot((val != null) ? ((Component)val).gameObject : null); if ((Object)(object)val != (Object)null && !RegisterClientBehaviour.IsCarrier(val) && !IsExistingCustomer(i, val) && ((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(false); } } } List workers = NpcInterop.Workers; if (workers == null) { return; } for (int j = 0; j < workers.Count; j++) { Worker obj2 = workers[j]; TrackNativeRoot((obj2 != null) ? ((Component)obj2).gameObject : null); if ((Object)(object)workers[j] != (Object)null && ((Component)workers[j]).gameObject.activeSelf) { ((Component)workers[j]).gameObject.SetActive(false); } } } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { Reset(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; SceneManager.sceneLoaded -= OnSceneLoaded; Reset(); _context.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnDestroy() { Shutdown(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (!_shutdown && _active == this) { Reset(); } } [MessageHandler(typeof(NpcStateDeltaMessage))] private void HandleState(MessageContext context, NpcStateDeltaMessage message) { if (!_context.InGame() || !IsNpcSceneReady()) { BufferPendingState(message); } else { ApplyStateDelta(message); } } [MessageHandler(typeof(NpcIdentityDeltaMessage))] private void HandleIdentity(MessageContext context, NpcIdentityDeltaMessage message) { if (!_context.InGame() || !IsNpcSceneReady()) { BufferPendingIdentity(message); return; } ApplyIdentity(message); _pendingIdentities.Remove((message.Kind << 16) | message.Index); ApplyPendingStates(message.Kind, message.Index, message.Identity); } [MessageHandler(typeof(NpcBaselineMessage))] private void HandleBaseline(MessageContext context, NpcBaselineMessage message) { _pendingBaseline = message; CoopPlugin.Log.LogInfo((object)("[npc] baseline received entries=" + ((message == null || message.Entries == null) ? (-1) : message.Entries.Count) + " capacity=" + (message?.CustomerCapacity ?? (-1)) + " inGame=" + _context.InGame() + " sceneReady=" + IsNpcSceneReady())); ApplyPendingBaseline(); } [MessageHandler(typeof(NpcSpeechMessage))] private void HandleSpeech(MessageContext context, NpcSpeechMessage message) { ShowSpeech(message, _context.InGame()); } [MessageHandler(typeof(NpcMoneyPopupMessage))] private void HandleMoney(MessageContext context, NpcMoneyPopupMessage message) { ShowMoneyPopup(message, _context.InGame()); } private static bool IsNpcSceneReady() { CustomerManager customerManager = NpcInterop.CustomerManager; if (((customerManager != null) ? customerManager.GetCustomerList() : null) != null && (Object)(object)NpcInterop.WorkerManager != (Object)null) { return NpcInterop.Workers != null; } return false; } private void ApplyIdentity(NpcIdentityDeltaMessage message) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) bool female = message.Female || HasFemaleNamePrefix(message.CharName); if (message.Kind == 0) { using (PerfProbe.Sample("module.npc.identity.capacity")) { EnsureCustomerCapacity(message.Index, female); } } int key = (message.Kind << 16) | message.Index; if (!_puppets.TryGetValue(key, out var value)) { value = new Puppet(); _puppets.Add(key, value); } bool flag = value.HasIdentity && value.Identity != message.Identity && SameDressedLook(value, message.Kind, female, message.CharName); if (value.HasIdentity && value.Identity != message.Identity && !flag) { using (PerfProbe.Sample("module.npc.identity.destroy")) { ClearPendingIdentity(value); ParkOrDestroyVisual(value); value.Go = null; value.Custom = null; value.CharName = ""; value.Identity = 0; value.HasIdentity = false; value.BufCount = 0; value.GrabSequence = int.MinValue; } } value.Kind = message.Kind; if (flag) { ResetPuppetForIncarnation(value, message); } SetPendingIdentity(value, message.Identity, female); using (PerfProbe.Sample("module.npc.identity.redress")) { Redress(value, message.CharName, message.Position, female, message.Kind, message.Index); } } private static bool HasFemaleNamePrefix(string charName) { if (!string.IsNullOrEmpty(charName)) { return charName.StartsWith("Female", StringComparison.Ordinal); } return false; } private static bool SameDressedLook(Puppet puppet, byte kind, bool female, string charName) { if ((Object)(object)puppet.Go != (Object)null && (Object)(object)puppet.Custom != (Object)null && puppet.Kind == kind && puppet.Female == female) { return puppet.CharName == charName; } return false; } private void ResetPuppetForIncarnation(Puppet p, NpcIdentityDeltaMessage message) { //IL_0009: 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) ReleaseWorkerBoxProp(p); ResetPuppetIncarnationState(p, message.Position); if ((Object)(object)p.Go != (Object)null) { p.Go.transform.position = message.Position; p.Go.SetActive(!IsPuppetSuppressed(message.Kind, message.Index)); } } private static void ResetPuppetIncarnationState(Puppet p, Vector3 prevRenderedPos) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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) p.BufHead = 0; p.BufCount = 0; p.Flags = NpcFlags.None; p.AppliedFlags = -1; p.AppliedAnimSpeed = float.NaN; p.AnimSpeed = 0f; p.RenderYaw = 0f; p.PrevRenderedPos = prevRenderedPos; p.HoldBig = false; p.HoldItemType = (EItemType)(-1); p.HoldBoxNetworkId = Guid.Empty; p.HoldBoxOpened = false; p.AppliedHeldBoxNetworkId = Guid.Empty; p.GrabSequence = int.MinValue; } private bool IsPuppetSuppressed(byte kind, ushort index) { if (kind == 0 && SuppressedCustomer.Contains(index)) { if (_existing.TryGetValue(index, out var value)) { return !value.KeepPuppetVisible; } return true; } return false; } private void BufferPendingIdentity(NpcIdentityDeltaMessage message) { int key = (message.Kind << 16) | message.Index; _pendingIdentities[key] = message; } private static void ClearPendingIdentity(Puppet puppet) { puppet.PendingIdentity = 0; } private static void SetPendingIdentity(Puppet puppet, int identity, bool female) { puppet.PendingIdentity = identity; puppet.PendingFemale = female; } private static void CommitIdentity(Puppet puppet) { puppet.Identity = puppet.PendingIdentity; puppet.HasIdentity = true; puppet.Female = puppet.PendingFemale; puppet.PendingIdentity = 0; } private void SignalCustomerManagerReady() { CustomerManager customerManager = NpcInterop.CustomerManager; if (((customerManager != null) ? customerManager.GetCustomerList() : null) != null) { NpcClientBehaviour.CustomerManagerReady?.Invoke(); ApplyPendingBaseline(); ApplyPendingIdentities(); NpcClientBehaviour.CustomerPoolChanged?.Invoke(); } } private void SignalWorkerManagerReady() { if (!((Object)(object)NpcInterop.WorkerManager == (Object)null) && NpcInterop.Workers != null) { NpcClientBehaviour.WorkerManagerReady?.Invoke(); ApplyPendingBaseline(); ApplyPendingIdentities(); } } private void ApplyPendingBaseline() { if (_pendingBaseline == null || !_context.InGame() || !IsNpcSceneReady()) { return; } NpcBaselineMessage pendingBaseline = _pendingBaseline; CoopPlugin.Log.LogInfo((object)("[npc] baseline applying entries=" + pendingBaseline.Entries.Count + " capacity=" + pendingBaseline.CustomerCapacity + " females=" + ((pendingBaseline.CustomerFemales == null) ? (-1) : pendingBaseline.CustomerFemales.Count))); EnsureCustomerCapacity(pendingBaseline.CustomerCapacity, pendingBaseline.CustomerFemales); HashSet hashSet = new HashSet(); for (int i = 0; i < pendingBaseline.Entries.Count; i++) { NpcEntry npcEntry = pendingBaseline.Entries[i]; hashSet.Add((npcEntry.Kind << 16) | npcEntry.Index); } RemovePuppetsAbsentFromBaseline(hashSet); foreach (Puppet value in _puppets.Values) { value.BufCount = 0; } foreach (ExistingCustomer value2 in _existing.Values) { value2.BufCount = 0; } ApplyEntries(pendingBaseline.HostTime, pendingBaseline.Entries); _pendingBaseline = null; ApplyPendingIdentities(); ApplyPendingStates(); CoopPlugin.Log.LogInfo((object)("[npc] baseline applied puppets=" + _puppets.Count + " mirrors=" + _existing.Count)); } private void RemovePuppetsAbsentFromBaseline(HashSet seen) { List list = new List(); foreach (KeyValuePair puppet in _puppets) { if (!seen.Contains(puppet.Key)) { ClearPendingIdentity(puppet.Value); ReleaseWorkerBoxProp(puppet.Value); DestroyPuppetObject(puppet.Value); _pendingIdentities.Remove(puppet.Key); list.Add(puppet.Key); } } for (int i = 0; i < list.Count; i++) { _puppets.Remove(list[i]); } } private void ApplyPendingIdentities() { if (_context.InGame() && IsNpcSceneReady() && _pendingIdentities.Count != 0) { List list = new List(_pendingIdentities.Values); for (int i = 0; i < list.Count; i++) { NpcIdentityDeltaMessage npcIdentityDeltaMessage = list[i]; ApplyIdentity(npcIdentityDeltaMessage); int key = (npcIdentityDeltaMessage.Kind << 16) | npcIdentityDeltaMessage.Index; _pendingIdentities.Remove(key); ApplyPendingStates(npcIdentityDeltaMessage.Kind, npcIdentityDeltaMessage.Index, npcIdentityDeltaMessage.Identity); } } } public void Reset() { foreach (ExistingCustomer value in _existing.Values) { DeactivateCustomerRoot(value?.Customer); } _existing.Clear(); ClearPuppets(); _now = 0f; _clockOffset = 0f; s_diagCm = null; _nativeNpcRoots.Clear(); _allowedNativeRoots.Clear(); _knownNonNpcRoots.Clear(); } private static void DeactivateCustomerRoot(Customer customer) { if (customer != null && !((Object)(object)customer == (Object)null)) { GameObject gameObject = ((Component)customer).gameObject; if ((Object)(object)gameObject != (Object)null && gameObject.activeSelf) { gameObject.SetActive(false); } } } private void TrackNativeRoot(GameObject root) { if (!((Object)(object)root == (Object)null)) { int instanceID = ((Object)root).GetInstanceID(); _nativeNpcRoots.Add(instanceID); _knownNonNpcRoots.Remove(instanceID); if ((Object)(object)root.GetComponent() == (Object)null) { root.AddComponent(); } } } internal static void SuppressNativeActivation(GameObject root) { if ((Object)(object)_active != (Object)null && _active.ShouldSuppressNativeRoot(root, value: true)) { root.SetActive(false); } } private bool ShouldSuppressNativeRoot(GameObject root, bool value) { if (!value || (Object)(object)root == (Object)null || _shutdown) { return false; } int instanceID = ((Object)root).GetInstanceID(); if (_puppetRoots.Contains(instanceID) || _allowedNativeRoots.Contains(instanceID)) { return false; } if (!_nativeNpcRoots.Contains(instanceID) && !_knownNonNpcRoots.Contains(instanceID)) { Customer component = root.GetComponent(); Worker val = (((Object)(object)component == (Object)null) ? root.GetComponent() : null); if ((Object)(object)component != (Object)null || (Object)(object)val != (Object)null) { TrackNativeRoot(root); } else { _knownNonNpcRoots.Add(instanceID); } } return _nativeNpcRoots.Contains(instanceID); } private void RegisterPuppetRoot(GameObject root) { if (!((Object)(object)root == (Object)null)) { int instanceID = ((Object)root).GetInstanceID(); _puppetRoots.Add(instanceID); _nativeNpcRoots.Remove(instanceID); _knownNonNpcRoots.Remove(instanceID); } } private void UnregisterPuppetRoot(int rootInstanceId) { if (rootInstanceId != 0) { _puppetRoots.Remove(rootInstanceId); } } private void DestroyPuppetObject(Puppet puppet) { if (puppet != null) { GameObject go = puppet.Go; int rootInstanceId = puppet.RootInstanceId; puppet.RootInstanceId = 0; puppet.Go = null; puppet.Anim = null; puppet.Custom = null; UnregisterPuppetRoot(rootInstanceId); if (go != null && (Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } } } public static Transform GetWorkerHoldAnchor(int index) { if ((Object)(object)_active == (Object)null) { return null; } int key = 0x10000 | index; if (!_active._puppets.TryGetValue(key, out var value)) { return null; } return value.HoldBox; } public static Worker GetWorkerPuppet(int index) { if ((Object)(object)_active == (Object)null) { return null; } int key = 0x10000 | index; if (!_active._puppets.TryGetValue(key, out var value) || !((Object)(object)value.Go != (Object)null)) { return null; } return value.Go.GetComponent(); } public static void SetWorkerBoxVisual(int index, bool visible, bool isBig, EItemType itemType, Guid boxNetworkId, bool opened) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_active == (Object)null) { return; } int key = 0x10000 | index; if (!_active._puppets.TryGetValue(key, out var value) || (Object)(object)value.Go == (Object)null) { return; } if (!visible) { _active.ReleaseWorkerBoxProp(value); return; } if ((Object)(object)value.BoxProp != (Object)null && value.BoxPropBig == isBig && value.BoxPropType == itemType) { value.BoxProp.SetActive(true); } else { _active.ReleaseWorkerBoxProp(value); RestockManager val = SceneRef.Get(); InteractablePackagingBox_Item obj = (isBig ? val.m_PackageBoxPrefab : val.m_PackageBoxSmallPrefab); GameObject val2 = new GameObject("CoopWorkerBoxProp_tmp"); val2.SetActive(false); GameObject val3 = Object.Instantiate(((Component)obj).gameObject, val2.transform); InteractablePackagingBox_Item component = val3.GetComponent(); if ((Object)(object)component != (Object)null) { value.BoxPropStaticMesh = component.m_StaticMeshGrp; value.BoxPropRigMesh = component.m_RigMeshGrp; value.BoxPropOpen = component.m_OpenBox; value.BoxPropClosed = component.m_ClosedBox; value.BoxPropOutlineOpen = component.m_OutlineOpenBox; value.BoxPropOutlineClosed = component.m_OutlineClosedBox; } else { value.BoxPropStaticMesh = null; value.BoxPropRigMesh = null; value.BoxPropOpen = null; value.BoxPropClosed = null; value.BoxPropOutlineOpen = null; value.BoxPropOutlineClosed = null; } MonoBehaviour[] componentsInChildren = val3.GetComponentsInChildren(true); foreach (MonoBehaviour val4 in componentsInChildren) { if ((Object)(object)val4 != (Object)null) { Object.DestroyImmediate((Object)(object)val4); } } Rigidbody[] componentsInChildren2 = val3.GetComponentsInChildren(true); foreach (Rigidbody val5 in componentsInChildren2) { if ((Object)(object)val5 != (Object)null) { Object.DestroyImmediate((Object)(object)val5); } } Collider[] componentsInChildren3 = val3.GetComponentsInChildren(true); foreach (Collider val6 in componentsInChildren3) { if ((Object)(object)val6 != (Object)null) { Object.DestroyImmediate((Object)(object)val6); } } val3.transform.SetParent(value.HoldBox, false); val3.transform.localPosition = Vector3.zero; val3.transform.localRotation = Quaternion.identity; ((Object)val3).name = "CoopWorkerBoxProp"; val3.SetActive(true); Object.Destroy((Object)(object)val2); value.BoxProp = val3; value.BoxPropBig = isBig; value.BoxPropType = itemType; } ApplyWorkerBoxOpenState(value, opened); if (boxNetworkId != Guid.Empty) { if (boxNetworkId != value.AppliedHeldBoxNetworkId) { value.AppliedHeldBoxNetworkId = boxNetworkId; WorldClientBehaviour.ApplyWorkerHeldBox(boxNetworkId); } } else { value.AppliedHeldBoxNetworkId = Guid.Empty; } } private static void ApplyWorkerBoxOpenState(Puppet p, bool opened) { SetActive(p.BoxPropStaticMesh, active: true); SetActive(p.BoxPropRigMesh, active: false); SetActive(p.BoxPropOpen, opened); SetActive(p.BoxPropClosed, !opened); SetActive(p.BoxPropOutlineOpen, opened); SetActive(p.BoxPropOutlineClosed, !opened); } private static void SetActive(GameObject target, bool active) { if ((Object)(object)target != (Object)null && target.activeSelf != active) { target.SetActive(active); } } public static void RefreshWorkerUi(int index, WorkerSaveData data) { //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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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) if ((Object)(object)_active == (Object)null || data == null) { return; } int key = 0x10000 | index; if (!_active._puppets.TryGetValue(key, out var value) || (Object)(object)value.Go == (Object)null) { return; } Worker component = value.Go.GetComponent(); if ((Object)(object)component == (Object)null) { return; } component.m_PrimaryTask = data.primaryTask; component.m_SecondaryTask = data.secondaryTask; component.m_WorkerTask = data.workerTask; component.m_CurrentState = data.currentState; component.m_IsBonusBoosted = data.isBonusBoosted; component.m_BonusBoostedCount = data.bonusBoostedCount; component.SetRestockShelfWithNoLabel(data.isFillShelfWithoutLabel); component.UpdateSetPriceOption(data.isRoundUpPrice, data.isAvoidSetCardPrice, data.setPriceMultiplier); component.UpdateSetCardPriceOption(data.isRoundUpCardPrice, data.isAvoidSetCardPriceWhileRestock, data.setCardPriceMultiplier); if (data.cardPackItemTypeEnabledList != null) { List cardPackItemTypeEnabledList = component.GetCardPackItemTypeEnabledList(); while (cardPackItemTypeEnabledList.Count < data.cardPackItemTypeEnabledList.Count) { cardPackItemTypeEnabledList.Add(item: true); } for (int i = 0; i < data.cardPackItemTypeEnabledList.Count; i++) { component.SetCardPackItemTypeEnabled(i, data.cardPackItemTypeEnabledList[i]); } } if (data.expList != null) { component.m_ExpList.Clear(); component.m_ExpList.AddRange(data.expList); } MiEvaluateSkillLevel?.Invoke(component, null); } private static void EnsureCardPackList(Worker worker) { if (!((Object)(object)worker == (Object)null)) { InventoryBase obj = Inv(); List cardPackItemTypeEnabledList = worker.GetCardPackItemTypeEnabledList(); int count = obj.m_StockItemData_SO.m_CardPackItemTypeList.Count; while (cardPackItemTypeEnabledList.Count < count) { cardPackItemTypeEnabledList.Add(item: true); } } } private static InventoryBase Inv() { return SceneRef.Get(); } private void ReleaseWorkerBoxProp(Puppet p) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (p != null) { GameObject boxProp = p.BoxProp; p.BoxProp = null; p.BoxPropBig = false; p.BoxPropType = (EItemType)(-1); p.BoxPropStaticMesh = null; p.BoxPropRigMesh = null; p.BoxPropOpen = null; p.BoxPropClosed = null; p.BoxPropOutlineOpen = null; p.BoxPropOutlineClosed = null; p.AppliedHeldBoxNetworkId = Guid.Empty; if (boxProp != null && !((Object)(object)boxProp == (Object)null)) { Object.Destroy((Object)(object)boxProp); } } } private PooledVisual CaptureVisual(Puppet p) { PooledVisual result = new PooledVisual { Go = p.Go, RootInstanceId = p.RootInstanceId, Anim = p.Anim, Custom = p.Custom, Bag = p.Bag, Cash = p.Cash, CardFan = p.CardFan, CardSingle = p.CardSingle, Smelly = p.Smelly, Clean = p.Clean, Exclaim = p.Exclaim, HoldBox = p.HoldBox, FromLiveSource = p.VisualFromLive }; if ((Object)(object)p.Go != (Object)null) { p.Go.SetActive(false); } ReleaseWorkerBoxProp(p); p.Go = null; p.RootInstanceId = 0; p.Anim = null; p.Custom = null; p.Bag = null; p.Cash = null; p.CardFan = null; p.CardSingle = null; p.Smelly = null; p.Clean = null; p.Exclaim = null; p.HoldBox = null; p.VisualFromLive = false; return result; } private void ParkOrDestroyVisual(Puppet p) { if (p == null) { return; } if ((Object)(object)p.Go == (Object)null || (Object)(object)p.Custom == (Object)null || !p.HasIdentity || string.IsNullOrEmpty(p.CharName)) { DestroyPuppetObject(p); return; } PooledVisual pooledVisual = CaptureVisual(p); VisualKey key = (pooledVisual.Key = new VisualKey(p.Kind, p.Female, p.CharName)); EvictVisualsToCapacity(); if (!_visualPool.TryGetValue(key, out var value)) { value = new List(); _visualPool.Add(key, value); } value.Add(pooledVisual); _visualPoolOrder.Add(pooledVisual); CoopPlugin.Log.LogDebug((object)("[npc-pool] park " + key.ToString() + " count=" + _visualPoolOrder.Count)); } private void EvictVisualsToCapacity() { while (_visualPoolOrder.Count >= 12) { PooledVisual pooledVisual = _visualPoolOrder[0]; _visualPoolOrder.RemoveAt(0); RemoveFromVisualPool(pooledVisual); GameObject go = pooledVisual.Go; int rootInstanceId = pooledVisual.RootInstanceId; pooledVisual.Go = null; pooledVisual.RootInstanceId = 0; UnregisterPuppetRoot(rootInstanceId); CoopPlugin.Log.LogDebug((object)("[npc-pool] evict " + pooledVisual.Key.ToString() + " count=" + _visualPoolOrder.Count)); if ((Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } } } private void RemoveFromVisualPool(PooledVisual visual) { if (_visualPool.TryGetValue(visual.Key, out var value)) { value.Remove(visual); if (value.Count == 0) { _visualPool.Remove(visual.Key); } } } private bool TryTakeVisual(VisualKey key, bool fromLiveSource, out PooledVisual visual) { if (_visualPool.TryGetValue(key, out var value)) { for (int num = value.Count - 1; num >= 0; num--) { PooledVisual pooledVisual = value[num]; if (pooledVisual.FromLiveSource == fromLiveSource) { value.RemoveAt(num); if (value.Count == 0) { _visualPool.Remove(key); } _visualPoolOrder.Remove(pooledVisual); if ((Object)(object)pooledVisual.Go != (Object)null) { visual = pooledVisual; return true; } UnregisterPuppetRoot(pooledVisual.RootInstanceId); } } } visual = null; return false; } private void ApplyVisual(Puppet p, PooledVisual visual, string charName, Vector3 pos) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) p.Go = visual.Go; p.RootInstanceId = visual.RootInstanceId; p.Anim = visual.Anim; p.Custom = visual.Custom; p.Bag = visual.Bag; p.Cash = visual.Cash; p.CardFan = visual.CardFan; p.CardSingle = visual.CardSingle; p.Smelly = visual.Smelly; p.Clean = visual.Clean; p.Exclaim = visual.Exclaim; p.HoldBox = visual.HoldBox; p.VisualFromLive = visual.FromLiveSource; ((Object)p.Go).name = "CoopNpc_" + charName; p.Go.transform.position = pos; p.Go.SetActive(true); } private void DestroyVisualPool() { for (int i = 0; i < _visualPoolOrder.Count; i++) { PooledVisual pooledVisual = _visualPoolOrder[i]; GameObject go = pooledVisual.Go; int rootInstanceId = pooledVisual.RootInstanceId; pooledVisual.Go = null; pooledVisual.RootInstanceId = 0; UnregisterPuppetRoot(rootInstanceId); if ((Object)(object)go != (Object)null) { Object.Destroy((Object)(object)go); } } _visualPoolOrder.Clear(); _visualPool.Clear(); } public static int CountUnexpectedActiveNpcs() { s_diagCm = SceneRef.Get(); int num = 0; if ((Object)(object)s_diagCm != (Object)null) { List customerList = s_diagCm.GetCustomerList(); for (int i = 0; i < customerList.Count; i++) { Customer val = customerList[i]; if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf && !RegisterClientBehaviour.IsCarrier(val) && !IsExistingCustomer(val)) { num++; } } } List workers = NpcInterop.Workers; if (workers != null) { for (int j = 0; j < workers.Count; j++) { if ((Object)(object)workers[j] != (Object)null && ((Component)workers[j]).gameObject.activeSelf) { num++; } } } return num; } public void ClearPuppets() { foreach (Puppet value in _puppets.Values) { ReleaseWorkerBoxProp(value); DestroyPuppetObject(value); } _puppets.Clear(); DestroyVisualPool(); _puppetRoots.Clear(); _pendingBaseline = null; _pendingIdentities.Clear(); _pendingStates.Clear(); _dressing = false; SuppressedCustomer.Clear(); _clockInit = false; } private Transform ResolveCustomerAnchor(ushort index, int identity) { int key = 0 | index; if (_existing.TryGetValue(index, out var value) && value.Generation == identity && (Object)(object)value.Customer != (Object)null) { return ((Component)value.Customer).transform; } if (_puppets.TryGetValue(key, out var value2) && value2.HasIdentity && value2.Identity == identity && (Object)(object)value2.Go != (Object)null) { return value2.Go.transform; } return null; } public void ShowSpeech(NpcSpeechMessage message, bool inGame) { if (!inGame) { return; } Transform val = ResolveCustomerAnchor(message.Index, message.Identity); if (!((Object)(object)val == (Object)null)) { PricePopupSpawner val2 = SceneRef.Get(); if (!((Object)(object)val2 == (Object)null)) { val2.ShowTextPopup(message.Text, message.OffsetUp, val); } } } public void ShowMoneyPopup(NpcMoneyPopupMessage message, bool inGame) { if (!inGame) { return; } Transform val = ResolveCustomerAnchor(message.Index, message.Identity); if (!((Object)(object)val == (Object)null)) { PricePopupSpawner val2 = SceneRef.Get(); if (!((Object)(object)val2 == (Object)null)) { val2.ShowPricePopup(message.Amount, message.OffsetUp, val); } } } private static int CustomerRootId(Customer customer) { if (customer == null || !((Object)(object)customer != (Object)null) || !((Object)(object)((Component)customer).gameObject != (Object)null)) { return 0; } return ((Object)((Component)customer).gameObject).GetInstanceID(); } public static void DetachExistingCustomer(int index, Customer customer) { if ((Object)(object)_active == (Object)null) { return; } int key = 0 | index; ExistingCustomer value; bool num = _active._existing.TryGetValue(index, out value) && ((Object)(object)customer == (Object)null || value.Customer == customer); int num2 = CustomerRootId(customer); if (num && value.RootInstanceId != 0) { _active._allowedNativeRoots.Remove(value.RootInstanceId); } else if (num2 != 0) { _active._allowedNativeRoots.Remove(num2); } if (num) { _active._existing.Remove(index); SuppressedCustomer.Remove(index); if (_active._puppets.TryGetValue(key, out var value2) && (Object)(object)value2.Go != (Object)null && value2.BufCount > 0) { value2.Go.SetActive(true); } } DeactivateCustomerRoot(customer); NpcClientBehaviour.CustomerPoolChanged?.Invoke(); NpcClientBehaviour.ExistingCustomerChanged?.Invoke(index, value?.Generation ?? 0, customer); } public static void AttachExistingCustomer(int index, int generation, Customer customer, bool keepPuppetVisible = false) { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_active == (Object)null || (Object)(object)customer == (Object)null) { return; } _active._allowedNativeRoots.Add(CustomerRootId(customer)); _active.TrackNativeRoot(((Component)customer).gameObject); if (!_active._existing.TryGetValue(index, out var value) || value.Generation != generation) { if (value != null && value.RootInstanceId != 0 && value.Customer != customer) { _active._allowedNativeRoots.Remove(value.RootInstanceId); } value = new ExistingCustomer { Customer = customer, RootInstanceId = CustomerRootId(customer), Generation = generation, PrevRenderedPos = ((Component)customer).transform.position, RenderYaw = ((Component)customer).transform.eulerAngles.y, KeepPuppetVisible = keepPuppetVisible }; _active._existing[index] = value; } else { if (value.RootInstanceId != 0 && value.Customer != customer) { _active._allowedNativeRoots.Remove(value.RootInstanceId); value.PuppetReadyNotified = false; } value.Customer = customer; value.RootInstanceId = CustomerRootId(customer); value.KeepPuppetVisible = keepPuppetVisible; } int key = 0 | index; if (_active._puppets.TryGetValue(key, out var value2)) { GameObject go = value2.Go; if (go != null) { go.SetActive(!keepPuppetVisible); } } NpcClientBehaviour.CustomerPoolChanged?.Invoke(); NpcClientBehaviour.ExistingCustomerChanged?.Invoke(index, generation, customer); } public static bool IsExistingCustomer(Customer customer) { if ((Object)(object)_active == (Object)null || (Object)(object)customer == (Object)null) { return false; } foreach (ExistingCustomer value in _active._existing.Values) { if (value.Customer == customer) { return true; } } return false; } public static bool IsExistingCustomerPuppetReady(int index, int generation) { if ((Object)(object)_active == (Object)null || !_active._existing.TryGetValue(index, out var value) || value.Generation != generation || !value.KeepPuppetVisible) { return false; } int key = 0 | index; if (_active._puppets.TryGetValue(key, out var value2) && (Object)(object)value2.Go != (Object)null && value2.HasIdentity && value2.Identity == generation) { return value2.Go.activeSelf; } return false; } internal static bool TryGetCustomerGeneration(int index, out int generation) { generation = 0; if ((Object)(object)_active == (Object)null) { return false; } if (_active._existing.TryGetValue(index, out var value) && value != null) { generation = value.Generation; return generation > 0; } int key = 0 | index; if (_active._pendingIdentities.TryGetValue(key, out var value2)) { generation = value2.Identity; return generation > 0; } if (!_active._puppets.TryGetValue(key, out var value3) || !value3.HasIdentity) { return false; } generation = value3.Identity; return generation > 0; } private bool IsExistingCustomer(int index, Customer customer) { if ((Object)(object)customer != (Object)null && _existing.TryGetValue(index, out var value)) { return value.Customer == customer; } return false; } private static void EnsureCustomerCapacity(int index, bool female) { CustomerManager customerManager = NpcInterop.CustomerManager; List list = ((customerManager != null) ? customerManager.GetCustomerList() : null); bool flag = false; while (list.Count <= index) { Customer newCustomerPrefab = customerManager.GetNewCustomerPrefab(female); _active.TrackNativeRoot(((Component)newCustomerPrefab).gameObject); ((Component)newCustomerPrefab).gameObject.SetActive(false); flag = true; } if (flag) { NpcClientBehaviour.CustomerPoolChanged?.Invoke(); NpcClientBehaviour.CustomerCapacityChanged?.Invoke(index); } } private static void EnsureCustomerCapacity(int capacity, List females) { CustomerManager customerManager = NpcInterop.CustomerManager; List list = ((customerManager != null) ? customerManager.GetCustomerList() : null); bool flag = false; while (list.Count < capacity) { int count = list.Count; Customer newCustomerPrefab = customerManager.GetNewCustomerPrefab(females[count]); _active.TrackNativeRoot(((Component)newCustomerPrefab).gameObject); if (((Component)newCustomerPrefab).gameObject.activeSelf) { ((Component)newCustomerPrefab).gameObject.SetActive(false); } flag = true; } if (flag) { NpcClientBehaviour.CustomerPoolChanged?.Invoke(); NpcClientBehaviour.CustomerCapacityChanged?.Invoke(capacity - 1); } } private void BufferPendingState(NpcStateDeltaMessage message) { _pendingStates[new NpcDeltaKey(message.Kind, message.Index, message.Identity)] = message; } private bool HasMatchingIdentity(NpcStateDeltaMessage message) { if (message.Kind == 0 && TryGetCustomerGeneration(message.Index, out var generation)) { return generation == message.Identity; } int key = (message.Kind << 16) | message.Index; if (_puppets.TryGetValue(key, out var value) && value.HasIdentity) { return value.Identity == message.Identity; } return false; } private void ApplyPendingStates() { if (_pendingStates.Count != 0) { List list = new List(_pendingStates.Values); for (int i = 0; i < list.Count; i++) { ApplyStateDelta(list[i]); } } } private void ApplyPendingStates(byte kind, ushort index, int identity) { NpcDeltaKey key = new NpcDeltaKey(kind, index, identity); if (_pendingStates.TryGetValue(key, out var value)) { ApplyStateDelta(value); } } private void ApplyStateDelta(NpcStateDeltaMessage message) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) NpcDeltaKey key = new NpcDeltaKey(message.Kind, message.Index, message.Identity); if (!message.Active) { _pendingStates.Remove(key); RemoveEntity(message.Kind, message.Index, message.Identity); return; } if (!HasMatchingIdentity(message)) { BufferPendingState(message); return; } ApplyEntries(message.HostTime, new List { new NpcEntry { Kind = message.Kind, Index = message.Index, Identity = message.Identity, Position = message.Position, Yaw = message.Yaw, Speed = message.Speed, Flags = message.Flags, ActionSequence = message.ActionSequence, ActionKind = message.ActionKind, HoldBig = message.HoldBig, HoldItemType = message.HoldItemType, HoldBoxNetworkId = message.HoldBoxNetworkId, HoldBoxOpened = message.HoldBoxOpened } }); _pendingStates.Remove(key); } private void RemoveEntity(byte kind, ushort index, int identity) { int num = (kind << 16) | index; if (_pendingIdentities.TryGetValue(num, out var value) && value.Identity == identity) { _pendingIdentities.Remove(num); } if (kind == 0 && _existing.TryGetValue(index, out var value2) && value2.Generation == identity) { DetachExistingCustomer(index, value2.Customer); } int key = num; if (_puppets.TryGetValue(key, out var value3) && (!value3.HasIdentity || value3.Identity == identity)) { ParkOrDestroyVisual(value3); _puppets.Remove(key); } } private void ApplyEntries(float hostTime, IList entries) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_0516: Unknown result type (might be due to invalid IL or missing references) //IL_0518: Unknown result type (might be due to invalid IL or missing references) //IL_04c6: Unknown result type (might be due to invalid IL or missing references) //IL_04cb: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) int count = entries.Count; float num = _now - hostTime; if (!_clockInit || Mathf.Abs(num - _clockOffset) > 1f) { _clockOffset = num; _clockInit = true; } else { _clockOffset += 0.1f * (num - _clockOffset); } float time = hostTime + _clockOffset; for (int i = 0; i < count; i++) { NpcEntry npcEntry = entries[i]; byte kind = npcEntry.Kind; ushort index = npcEntry.Index; int identity = npcEntry.Identity; bool hasName = npcEntry.HasName; string charName = npcEntry.CharName; bool flag = npcEntry.Female || (hasName && HasFemaleNamePrefix(charName)); Vector3 position = npcEntry.Position; float yaw = npcEntry.Yaw; float speed = npcEntry.Speed; NpcFlags flags = (NpcFlags)npcEntry.Flags; int actionSequence = npcEntry.ActionSequence; byte actionKind = npcEntry.ActionKind; int key = (kind << 16) | index; if (kind == 0 && _existing.TryGetValue(index, out var value) && value.Generation == identity) { ExistingCustomer existingCustomer = value; if (existingCustomer == null) { continue; } existingCustomer.BufHead = (existingCustomer.BufHead + 1) & 3; existingCustomer.Buf[existingCustomer.BufHead] = new Snap { Pos = position, Yaw = yaw, Speed = speed, Flags = flags, Time = time }; if (existingCustomer.BufCount < 4) { existingCustomer.BufCount++; } existingCustomer.Flags = flags; if ((Object)(object)existingCustomer.Customer != (Object)null) { ApplyActionTrigger(existingCustomer.Customer.m_Anim, kind, actionKind, actionSequence, ref existingCustomer.GrabSequence); } int key2 = 0 | index; _puppets.TryGetValue(key2, out var value2); if (value2 == null || (Object)(object)value2.Go == (Object)null) { existingCustomer.PuppetReadyNotified = false; } if (value2 != null && value2.HasIdentity && (value2.Identity != identity || (hasName && value2.Female != flag))) { ClearPendingIdentity(value2); _active.ParkOrDestroyVisual(value2); value2.Go = null; value2.CharName = ""; value2.Identity = 0; value2.HasIdentity = false; value2.BufCount = 0; value2.GrabSequence = int.MinValue; existingCustomer.PuppetReadyNotified = false; } if ((value2 == null || (Object)(object)value2.Go == (Object)null) && hasName) { if (value2 == null) { value2 = new Puppet(); _puppets[key2] = value2; } value2.Kind = 0; SetPendingIdentity(value2, identity, flag); Redress(value2, charName, position, flag, 0, index); } if (value2 != null) { value2.Flags = flags; value2.BufHead = (value2.BufHead + 1) & 3; value2.Buf[value2.BufHead] = new Snap { Pos = position, Yaw = yaw, Speed = speed, Flags = flags, Time = time }; if (value2.BufCount < 4) { value2.BufCount++; } ApplyActionTrigger(value2.Anim, kind, actionKind, actionSequence, ref value2.GrabSequence); GameObject go = value2.Go; if (go != null) { go.SetActive(existingCustomer.KeepPuppetVisible); } if (existingCustomer.KeepPuppetVisible && (Object)(object)value2.Go != (Object)null && !existingCustomer.PuppetReadyNotified) { existingCustomer.PuppetReadyNotified = true; NpcClientBehaviour.ExistingCustomerPuppetReady?.Invoke(index, identity, existingCustomer.Customer); } } } else { if (!_puppets.TryGetValue(key, out var value3)) { value3 = new Puppet(); _puppets[key] = value3; } if (value3.HasIdentity && (value3.Identity != identity || (hasName && value3.Female != flag))) { ClearPendingIdentity(value3); _active.ParkOrDestroyVisual(value3); value3.Go = null; value3.CharName = ""; value3.Identity = 0; value3.HasIdentity = false; value3.BufCount = 0; value3.GrabSequence = int.MinValue; } value3.Kind = kind; if (hasName) { SetPendingIdentity(value3, identity, flag); Redress(value3, charName, position, flag, kind, index); } if (kind == 1) { value3.HoldBig = npcEntry.HoldBig; value3.HoldItemType = npcEntry.HoldItemType; value3.HoldBoxNetworkId = npcEntry.HoldBoxNetworkId; value3.HoldBoxOpened = npcEntry.HoldBoxOpened; } value3.BufHead = (value3.BufHead + 1) & 3; value3.Buf[value3.BufHead] = new Snap { Pos = position, Yaw = yaw, Speed = speed, Flags = flags, Time = time }; if (value3.BufCount < 4) { value3.BufCount++; } value3.Flags = flags; ApplyActionTrigger(value3.Anim, kind, actionKind, actionSequence, ref value3.GrabSequence); if (kind == 0 && SuppressedCustomer.Contains(index) && (Object)(object)value3.Go != (Object)null) { value3.Go.SetActive(false); } } } } private static void ApplyActionTrigger(Animator anim, byte kind, byte actionKind, int actionSequence, ref int appliedSequence) { if (!((Object)(object)anim == (Object)null) && actionSequence != appliedSequence) { bool num = appliedSequence == int.MinValue; appliedSequence = actionSequence; if (!num) { anim.SetTrigger((kind == 1) ? "ScanItem" : ((actionKind == 2) ? "GrabItemHigh" : "GrabItem")); } } } private static void ApplyExistingFlags(Customer customer, NpcFlags flags, float speed) { if (!((Object)(object)customer.m_Anim == (Object)null)) { customer.m_Anim.SetFloat(HashMoveSpeed, speed); customer.m_Anim.SetBool(HashHoldingBag, (flags & NpcFlags.HoldingBag) != 0); customer.m_Anim.SetBool(HashHandingOverCash, (flags & NpcFlags.HandingOverCash) != 0); customer.m_Anim.SetBool(HashIsSitting, (flags & NpcFlags.IsSitting) != 0); customer.m_Anim.SetBool(HashIsPlaying, (flags & NpcFlags.IsPlaying) != 0); customer.m_Anim.SetBool(HashIsHoldingBox, (flags & NpcFlags.IsHoldingBox) != 0); customer.m_Anim.SetBool(HashIsBeingSprayed, (flags & NpcFlags.Sprayed) != 0); Transform shoppingBagTransform = customer.m_ShoppingBagTransform; if (shoppingBagTransform != null) { ((Component)shoppingBagTransform).gameObject.SetActive((flags & NpcFlags.HoldingBag) != 0); } InteractableCustomerCash customerCash = customer.m_CustomerCash; if (customerCash != null) { ((Component)customerCash).gameObject.SetActive((flags & NpcFlags.HandingOverCash) != 0); } GameObject gameCardFanOut = customer.m_GameCardFanOut; if (gameCardFanOut != null) { gameCardFanOut.SetActive((flags & NpcFlags.IsPlaying) != 0); } GameObject gameCardSingle = customer.m_GameCardSingle; if (gameCardSingle != null) { gameCardSingle.SetActive((flags & NpcFlags.IsPlaying) != 0); } GameObject smellyFX = customer.m_SmellyFX; if (smellyFX != null) { smellyFX.SetActive((flags & NpcFlags.Smelly) != 0); } GameObject cleanFX = customer.m_CleanFX; if (cleanFX != null) { cleanFX.SetActive((flags & NpcFlags.Cleaned) != 0); } GameObject exclaimationMesh = customer.m_ExclaimationMesh; if (exclaimationMesh != null) { exclaimationMesh.SetActive((flags & NpcFlags.Exclaim) != 0); } } } private void Redress(Puppet puppet, string charName, Vector3 pos, bool female, byte kind, ushort index) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) _dressing = true; try { ReDress(puppet, charName, pos, female, kind, index); } finally { _dressing = false; } } private void ReDress(Puppet p, string charName, Vector3 pos, bool female, byte kind, ushort index) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) bool flag = (Object)(object)p.Go != (Object)null && p.Female != female; if ((Object)(object)p.Go == (Object)null || (Object)(object)p.Custom == (Object)null || flag) { int pendingIdentity = p.PendingIdentity; if ((Object)(object)p.Go != (Object)null) { ClearPendingIdentity(p); ParkOrDestroyVisual(p); p.Go = null; p.CharName = ""; p.Identity = 0; p.HasIdentity = false; } p.Kind = kind; SetPendingIdentity(p, pendingIdentity, female); Spawn(p, charName, pos, female, kind, index); CommitIdentity(p); return; } if (p.CharName == charName) { CommitIdentity(p); return; } VisualKey key = new VisualKey(kind, female, charName); PooledVisual visual = null; using (PerfProbe.Sample("module.npc.identity.pool")) { TryTakeVisual(key, p.VisualFromLive, out visual); } if (visual != null) { CoopPlugin.Log.LogDebug((object)("[npc-pool] take " + key.ToString() + " count=" + _visualPoolOrder.Count)); ParkOrDestroyVisual(p); p.Female = female; ApplyVisual(p, visual, charName, pos); ResetPuppetIncarnationState(p, pos); SeedWorkerUi(p, index); CommitSpawnTail(p, charName, pos); CommitIdentity(p); } else { p.Custom.CharacterName = charName; p.Custom.Initialize(); p.CharName = charName; p.Female = female; ((Object)p.Go).name = "CoopNpc_" + charName; CommitIdentity(p); } } public void TickPuppets(float dt, bool inGame) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0513: Unknown result type (might be due to invalid IL or missing references) //IL_0518: Unknown result type (might be due to invalid IL or missing references) //IL_051a: Unknown result type (might be due to invalid IL or missing references) //IL_051f: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0547: Unknown result type (might be due to invalid IL or missing references) //IL_0537: Unknown result type (might be due to invalid IL or missing references) //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_0540: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_0549: Unknown result type (might be due to invalid IL or missing references) //IL_054d: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_0586: Unknown result type (might be due to invalid IL or missing references) //IL_0594: Unknown result type (might be due to invalid IL or missing references) //IL_0598: Unknown result type (might be due to invalid IL or missing references) //IL_059d: Unknown result type (might be due to invalid IL or missing references) //IL_05a2: Unknown result type (might be due to invalid IL or missing references) //IL_05c2: Unknown result type (might be due to invalid IL or missing references) //IL_05c4: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) if (!inGame || dt <= 0f) { return; } _now += dt; float renderTime = _now - 0.15f; float num = 1f - Mathf.Exp(-18f * dt); float num2 = 1f - Mathf.Exp(-14f * dt); float num3 = 1f - Mathf.Exp(-8f * dt); Vector3 val; foreach (KeyValuePair puppet in _puppets) { Puppet value = puppet.Value; if (puppet.Key < 65536 && SuppressedCustomer.Contains(puppet.Key) && (!_existing.TryGetValue(puppet.Key, out var value2) || !value2.KeepPuppetVisible) && (Object)(object)value.Go != (Object)null) { value.Go.SetActive(false); } if ((Object)(object)value.Go == (Object)null || value.BufCount == 0) { continue; } Sample(value, renderTime, out var target, out var targetYaw); Transform transform = value.Go.transform; val = transform.position - target; bool flag = ((Vector3)(ref val)).sqrMagnitude > 25f; Vector3 val2 = (transform.position = (flag ? target : Vector3.Lerp(transform.position, target, num))); value.RenderYaw = (flag ? targetYaw : Mathf.LerpAngle(value.RenderYaw, targetYaw, num2)); transform.rotation = Quaternion.Euler(0f, value.RenderYaw, 0f); float num4; if (!flag) { val = val2 - value.PrevRenderedPos; num4 = Mathf.Min(((Vector3)(ref val)).magnitude / dt, 10f); } else { num4 = 0f; } float num5 = num4; value.PrevRenderedPos = val2; value.AnimSpeed = Mathf.Lerp(value.AnimSpeed, num5, num3); if (value.AnimSpeed < 0.05f) { value.AnimSpeed = 0f; } if ((Object)(object)value.Anim != (Object)null && (float.IsNaN(value.AppliedAnimSpeed) || Mathf.Abs(value.AppliedAnimSpeed - value.AnimSpeed) > 0.01f)) { value.Anim.SetFloat(HashMoveSpeed, value.AnimSpeed); value.AppliedAnimSpeed = value.AnimSpeed; } if ((int)value.Flags != value.AppliedFlags) { if ((Object)(object)value.Anim != (Object)null) { value.Anim.SetBool(HashHoldingBag, (value.Flags & NpcFlags.HoldingBag) != 0); value.Anim.SetBool(HashHandingOverCash, (value.Flags & NpcFlags.HandingOverCash) != 0); value.Anim.SetBool(HashIsSitting, (value.Flags & NpcFlags.IsSitting) != 0); value.Anim.SetBool(HashIsPlaying, (value.Flags & NpcFlags.IsPlaying) != 0); value.Anim.SetBool(HashIsHoldingBox, (value.Flags & NpcFlags.IsHoldingBox) != 0); value.Anim.SetBool(HashIsBeingSprayed, (value.Flags & NpcFlags.Sprayed) != 0); } Toggle(value.Bag, (value.Flags & NpcFlags.HoldingBag) != 0); Toggle(value.Cash, (value.Flags & NpcFlags.HandingOverCash) != 0); Toggle(value.CardFan, (value.Flags & NpcFlags.IsPlaying) != 0); Toggle(value.CardSingle, (value.Flags & NpcFlags.IsPlaying) != 0); Toggle(value.Smelly, (value.Flags & NpcFlags.Smelly) != 0); Toggle(value.Clean, (value.Flags & NpcFlags.Cleaned) != 0); Toggle(value.Exclaim, (value.Flags & NpcFlags.Exclaim) != 0); value.AppliedFlags = (int)value.Flags; } if (value.Kind != 1) { continue; } if ((value.Flags & NpcFlags.IsHoldingBox) != NpcFlags.None) { SetWorkerBoxVisual(puppet.Key & 0xFFFF, visible: true, value.HoldBig, value.HoldItemType, value.HoldBoxNetworkId, value.HoldBoxOpened); continue; } Guid appliedHeldBoxNetworkId = value.AppliedHeldBoxNetworkId; ReleaseWorkerBoxProp(value); if (appliedHeldBoxNetworkId != Guid.Empty) { WorldClientBehaviour.RestoreWorkerDroppedBox(appliedHeldBoxNetworkId); } } foreach (KeyValuePair item in _existing) { ExistingCustomer value3 = item.Value; if (!((Object)(object)value3.Customer == (Object)null) && value3.BufCount != 0) { SampleExisting(value3, renderTime, out var target2, out var targetYaw2); float num6 = 1f - Mathf.Exp(-18f * dt); float num7 = 1f - Mathf.Exp(-14f * dt); float num8 = 1f - Mathf.Exp(-8f * dt); Transform transform2 = ((Component)value3.Customer).transform; val = transform2.position - target2; bool flag2 = ((Vector3)(ref val)).sqrMagnitude > 25f; Vector3 val4 = (transform2.position = (flag2 ? target2 : Vector3.Lerp(transform2.position, target2, num6))); value3.RenderYaw = (flag2 ? targetYaw2 : Mathf.LerpAngle(value3.RenderYaw, targetYaw2, num7)); transform2.rotation = Quaternion.Euler(0f, value3.RenderYaw, 0f); float num9; if (!flag2) { val = val4 - value3.PrevRenderedPos; num9 = Mathf.Min(((Vector3)(ref val)).magnitude / dt, 10f); } else { num9 = 0f; } float num10 = num9; value3.PrevRenderedPos = val4; value3.AnimSpeed = Mathf.Lerp(value3.AnimSpeed, num10, num8); if (value3.AnimSpeed < 0.05f) { value3.AnimSpeed = 0f; } if ((Object)(object)value3.Customer.m_Anim != (Object)null && (float.IsNaN(value3.AppliedAnimSpeed) || Mathf.Abs(value3.AppliedAnimSpeed - value3.AnimSpeed) > 0.01f)) { value3.Customer.m_Anim.SetFloat(HashMoveSpeed, value3.AnimSpeed); value3.AppliedAnimSpeed = value3.AnimSpeed; } if ((int)value3.Flags != value3.AppliedFlags) { ApplyExistingFlags(value3.Customer, value3.Flags, value3.AnimSpeed); value3.AppliedAnimSpeed = value3.AnimSpeed; value3.AppliedFlags = (int)value3.Flags; } } } } private static void SampleExisting(ExistingCustomer mirror, float renderTime, out Vector3 target, out float targetYaw) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: 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_0180: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) Snap snap = mirror.Buf[mirror.BufHead]; if (snap.Time <= renderTime) { Vector3 val = Vector3.zero; if (mirror.BufCount >= 2) { Snap snap2 = mirror.Buf[(mirror.BufHead + 3) & 3]; float num = snap.Time - snap2.Time; if (num > 0.001f) { val = (snap.Pos - snap2.Pos) / num; val.y = 0f; val = Vector3.ClampMagnitude(val, 5f); } } float num2 = Mathf.Min(renderTime - snap.Time, 0.25f); val *= Mathf.Exp(-3f * num2); target = snap.Pos + val * num2; targetYaw = snap.Yaw; return; } Snap snap3 = snap; for (int i = 1; i < mirror.BufCount; i++) { Snap snap4 = mirror.Buf[(mirror.BufHead - i + 4) & 3]; if (snap4.Time <= renderTime) { float num3 = snap3.Time - snap4.Time; float num4 = ((num3 > 0.0001f) ? ((renderTime - snap4.Time) / num3) : 1f); target = Vector3.Lerp(snap4.Pos, snap3.Pos, num4); targetYaw = Mathf.LerpAngle(snap4.Yaw, snap3.Yaw, num4); return; } snap3 = snap4; } target = snap3.Pos; targetYaw = snap3.Yaw; } private static void Sample(Puppet p, float renderTime, out Vector3 target, out float targetYaw) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: 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_0180: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) Snap snap = p.Buf[p.BufHead]; if (snap.Time <= renderTime) { Vector3 val = Vector3.zero; if (p.BufCount >= 2) { Snap snap2 = p.Buf[(p.BufHead + 3) & 3]; float num = snap.Time - snap2.Time; if (num > 0.001f) { val = (snap.Pos - snap2.Pos) / num; val.y = 0f; val = Vector3.ClampMagnitude(val, 5f); } } float num2 = Mathf.Min(renderTime - snap.Time, 0.25f); val *= Mathf.Exp(-3f * num2); target = snap.Pos + val * num2; targetYaw = snap.Yaw; return; } Snap snap3 = snap; for (int i = 1; i < p.BufCount; i++) { Snap snap4 = p.Buf[(p.BufHead - i + 4) & 3]; if (snap4.Time <= renderTime) { float num3 = snap3.Time - snap4.Time; float num4 = ((num3 > 0.0001f) ? ((renderTime - snap4.Time) / num3) : 1f); target = Vector3.Lerp(snap4.Pos, snap3.Pos, num4); targetYaw = Mathf.LerpAngle(snap4.Yaw, snap3.Yaw, num4); return; } snap3 = snap4; } target = snap3.Pos; targetYaw = snap3.Yaw; } private static void Toggle(GameObject go, bool on) { if ((Object)(object)go != (Object)null && go.activeSelf != on) { go.SetActive(on); } } private static void ResetCustomizationCollection(CharacterCustomization custom, FieldInfo field) { if ((Object)(object)custom == (Object)null || field == null) { return; } if (!(field.GetValue(custom) is IList list)) { IList value = new List(); field.SetValue(custom, value); return; } for (int i = 0; i < list.Count; i++) { object? obj = list[i]; GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } list.Clear(); } private static void InitializePuppetCustomization(CharacterCustomization custom, string charName) { if (custom.m_HasInit) { ResetCustomizationCollection(custom, FiCustomizationHairObjects); ResetCustomizationCollection(custom, FiCustomizationApparelObjects); custom.m_HasInit = false; } custom.CharacterName = charName; custom.Initialize(); } private static void EnsureWorkerCollections(Worker worker) { if (!((Object)(object)worker == (Object)null)) { while (worker.m_TaskLevel.Count < 100) { worker.m_TaskLevel.Add(0); } while (worker.m_ExpList.Count < 100) { worker.m_ExpList.Add(0); } EnsureCardPackList(worker); } } private static Worker FindLiveWorker(ushort index, bool female) { List workers = NpcInterop.Workers; if (workers == null || index >= workers.Count) { return null; } Worker val = workers[index]; if (!((Object)(object)val != (Object)null) || val.m_IsFemale != female) { return null; } return val; } private static Customer FindPooledCustomer(CustomerManager manager, bool female) { List list = ((manager != null) ? manager.GetCustomerList() : null); if (list == null) { return null; } Customer val = null; Customer val2 = null; for (int i = 0; i < list.Count; i++) { Customer val3 = list[i]; if (!((Object)(object)val3 == (Object)null) && val3.m_IsFemale == female && !((Object)(object)val3.m_CharacterCustom == (Object)null)) { if (!val3.m_IsActive && !val3.m_CharacterCustom.m_HasInit) { return val3; } if (!val3.m_IsActive && (Object)(object)val == (Object)null) { val = val3; } if ((Object)(object)val2 == (Object)null) { val2 = val3; } } } return val ?? val2; } private static void RebuildCustomizationCollections(CharacterCustomization cloneCustom, CharacterCustomization sourceCustom) { RebuildCustomizationCollection(cloneCustom, sourceCustom, FiCustomizationHairObjects); RebuildCustomizationCollection(cloneCustom, sourceCustom, FiCustomizationApparelObjects); } private static void RebuildCustomizationCollection(CharacterCustomization cloneCustom, CharacterCustomization sourceCustom, FieldInfo field) { if ((Object)(object)cloneCustom == (Object)null || (Object)(object)sourceCustom == (Object)null || field == null) { return; } IList list = field.GetValue(sourceCustom) as IList; IList list2 = field.GetValue(cloneCustom) as IList; if (list != null && list2 != null && list != list2) { List list3 = new List(list.Count); for (int i = 0; i < list.Count; i++) { object? obj = list[i]; GameObject val = (GameObject)((obj is GameObject) ? obj : null); Transform val2 = (((Object)(object)val == (Object)null) ? null : ResolveCloneChild(((Component)sourceCustom).transform, ((Component)cloneCustom).transform, val.transform)); list3.Add(((Object)(object)val2 == (Object)null) ? null : ((Component)val2).gameObject); } list2.Clear(); for (int j = 0; j < list3.Count; j++) { list2.Add(list3[j]); } } } private static Transform ResolveCloneChild(Transform sourceRoot, Transform cloneRoot, Transform sourceChild) { if ((Object)(object)sourceRoot == (Object)null || (Object)(object)cloneRoot == (Object)null || (Object)(object)sourceChild == (Object)null) { return null; } List list = new List(); Transform val = sourceChild; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)sourceRoot) { list.Add(val.GetSiblingIndex()); val = val.parent; } if ((Object)(object)val != (Object)(object)sourceRoot) { return null; } Transform val2 = cloneRoot; for (int num = list.Count - 1; num >= 0; num--) { if ((Object)(object)val2 == (Object)null || list[num] >= val2.childCount) { return null; } val2 = val2.GetChild(list[num]); } return val2; } private void Spawn(Puppet p, string charName, Vector3 pos, bool femaleHint, byte kind, ushort index) { //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_0626: Unknown result type (might be due to invalid IL or missing references) bool flag = (p.Female = femaleHint); p.Kind = kind; CharacterCustomization sourceCustom = null; bool flag2 = false; GameObject gameObject; using (PerfProbe.Sample("module.npc.identity.spawn.source")) { if (kind == 1) { WorkerManager val = SceneRef.Get(); Worker val2 = FindLiveWorker(index, flag); if ((Object)(object)val2 != (Object)null) { gameObject = ((Component)val2).gameObject; flag2 = true; } else { gameObject = ((Component)(flag ? val.m_WorkerFemalePrefab : val.m_WorkerPrefab)).gameObject; } } else { CustomerManager val3 = SceneRef.Get(); Customer val4 = FindPooledCustomer(val3, flag); if ((Object)(object)val4 != (Object)null) { gameObject = ((Component)val4).gameObject; sourceCustom = val4.m_CharacterCustom; flag2 = true; } else { gameObject = ((Component)(flag ? val3.m_CustomerFemalePrefab : val3.m_CustomerPrefab)).gameObject; } } } PooledVisual visual = null; if (!string.IsNullOrEmpty(charName)) { using (PerfProbe.Sample("module.npc.identity.pool")) { TryTakeVisual(new VisualKey(kind, flag, charName), flag2, out visual); } } if (visual != null) { CoopPlugin.Log.LogDebug((object)("[npc-pool] take " + visual.Key.ToString() + " count=" + _visualPoolOrder.Count)); ApplyVisual(p, visual, charName, pos); ResetPuppetIncarnationState(p, pos); SeedWorkerUi(p, index); CommitSpawnTail(p, charName, pos); return; } GameObject val6; int instanceID; using (PerfProbe.Sample("module.npc.identity.spawn.instantiate")) { GameObject val5 = new GameObject("CoopNpcHolder_tmp"); val5.SetActive(false); val6 = Object.Instantiate(gameObject, val5.transform); instanceID = ((Object)val6).GetInstanceID(); RegisterPuppetRoot(val6); val6.transform.SetParent((Transform)null, false); val6.transform.position = pos; Object.Destroy((Object)(object)val5); } Customer component = val6.GetComponent(); Worker component2 = val6.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_IsFemale = flag; } if ((Object)(object)component2 != (Object)null) { component2.m_IsFemale = flag; component2.m_WorkerIndex = index; } p.Custom = (((Object)(object)component != (Object)null) ? component.m_CharacterCustom : component2?.m_CharacterCustom); if ((Object)(object)p.Custom != (Object)null) { using (PerfProbe.Sample("module.npc.identity.spawn.dress")) { if (!flag2) { InitializePuppetCustomization(p.Custom, charName); } else if (kind == 1) { p.Custom.CharacterName = charName; } else { RebuildCustomizationCollections(p.Custom, sourceCustom); p.Custom.CharacterName = charName; p.Custom.Initialize(); } } } if ((Object)(object)component2 != (Object)null) { EnsureWorkerCollections(component2); MiEvaluateWorkerAttribute?.Invoke(component2, null); MiEvaluateSkillLevel?.Invoke(component2, null); } p.Custom.Autoload = false; if ((Object)(object)component != (Object)null) { Transform shoppingBagTransform = component.m_ShoppingBagTransform; p.Bag = ((shoppingBagTransform != null) ? ((Component)shoppingBagTransform).gameObject : null); InteractableCustomerCash customerCash = component.m_CustomerCash; p.Cash = ((customerCash != null) ? ((Component)customerCash).gameObject : null); p.CardFan = component.m_GameCardFanOut; p.CardSingle = component.m_GameCardSingle; p.Smelly = component.m_SmellyFX; p.Clean = component.m_CleanFX; p.Exclaim = component.m_ExclaimationMesh; Toggle(p.Bag, on: false); Toggle(p.Cash, on: false); Toggle(p.CardFan, on: false); Toggle(p.CardSingle, on: false); GameObject cleanFX = component.m_CleanFX; if (cleanFX != null) { cleanFX.SetActive(false); } GameObject exclaimationMesh = component.m_ExclaimationMesh; if (exclaimationMesh != null) { exclaimationMesh.SetActive(false); } GameObject interactCollider = component.m_InteractCollider; if (interactCollider != null) { interactCollider.SetActive(false); } GameObject smellyFX = component.m_SmellyFX; if (smellyFX != null) { smellyFX.SetActive(false); } } else if ((Object)(object)component2 != (Object)null) { p.Exclaim = component2.m_ExclaimationMesh; p.HoldBox = component2.m_HoldBoxLoc; GameObject exclaim = p.Exclaim; if (exclaim != null) { exclaim.SetActive(false); } } using (PerfProbe.Sample("module.npc.identity.spawn.strip")) { MonoBehaviour[] componentsInChildren = val6.GetComponentsInChildren(true); foreach (MonoBehaviour val7 in componentsInChildren) { if ((Object)(object)val7 == (Object)null) { continue; } switch (((object)val7).GetType().Name) { case "Worker": case "Customer": case "WorkerCollider": case "NavMeshAgent": case "NavMeshObstacle": case "Seeker": case "FunnelModifier": case "InteractableObject": { Behaviour val8 = (Behaviour)(object)val7; if ((Object)(object)val8 != (Object)null) { val8.enabled = false; } break; } } } Collider[] componentsInChildren2 = val6.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].enabled = false; } Rigidbody[] componentsInChildren3 = val6.GetComponentsInChildren(true); foreach (Rigidbody obj in componentsInChildren3) { obj.isKinematic = true; obj.detectCollisions = false; } } if ((Object)(object)component2 != (Object)null && (Object)(object)component2.m_WorkerCollider != (Object)null) { ((Behaviour)component2.m_WorkerCollider).enabled = true; Collider[] componentsInChildren2 = ((Component)component2.m_WorkerCollider).GetComponents(); for (int i = 0; i < componentsInChildren2.Length; i++) { componentsInChildren2[i].enabled = true; } } using (PerfProbe.Sample("module.npc.identity.spawn.finish")) { ((Object)val6).name = "CoopNpc_" + charName; p.VisualFromLive = flag2; p.Go = val6; p.RootInstanceId = instanceID; SeedWorkerUi(p, index); p.Anim = (((Object)(object)component2 != (Object)null && (Object)(object)component2.m_Anim != (Object)null) ? component2.m_Anim : val6.GetComponentInChildren(true)); CommitSpawnTail(p, charName, pos); } } private void SeedWorkerUi(Puppet p, ushort index) { List workerSaveDataList = CPlayerData.m_WorkerSaveDataList; if (p.Kind == 1 && workerSaveDataList != null && index < workerSaveDataList.Count) { RefreshWorkerUi(index, workerSaveDataList[index]); } } private void CommitSpawnTail(Puppet p, string charName, Vector3 pos) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) p.CharName = charName; p.PrevRenderedPos = pos; p.RenderYaw = 0f; p.AnimSpeed = 0f; p.AppliedFlags = -1; p.Go.SetActive(true); } } [ServerBehaviour] public sealed class NpcHostBehaviour : CoopBehaviour { [Flags] private enum NpcFlags : ushort { None = 0, HoldingBag = 1, HandingOverCash = 2, IsSitting = 4, IsPlaying = 8, IsHoldingBox = 0x10, Smelly = 0x20, Exclaim = 0x40, Female = 0x80, Sprayed = 0x100, Cleaned = 0x200 } private readonly struct NpcKey : IEquatable { public byte Kind { get; } public int Index { get; } public NpcKey(byte kind, int index) { Kind = kind; Index = index; } public bool Equals(NpcKey other) { if (Kind == other.Kind) { return Index == other.Index; } return false; } public override bool Equals(object obj) { if (obj is NpcKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (Index * 397) ^ Kind; } } private sealed class NpcData { public bool Active; public int Generation; public ECustomerState CustomerState; public int CustomerGrabSequence; public int WorkerActionSequence; public byte WorkerActionKind; public Guid LastHeldBoxId; public string IdentityName; public bool IdentityFemale; public int IdentityGeneration; } [HarmonyPatch(typeof(CustomerManager), "Start")] private static class CustomerManagerStartPatch { [HarmonyPostfix] private static void Postfix(CustomerManager __instance) { _active?.RegisterInitialPopulation(__instance); _active?.SignalCustomerManagerReady(); } } [HarmonyPatch(typeof(Customer), "ActivateCustomer")] [HarmonyPatch(typeof(Customer), "DeactivateCustomer")] private static class CustomerPopulationPatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null)) { if (__instance.m_IsActive && ((Component)__instance).gameObject.activeSelf) { _active.CustomerActivated(__instance); } else { _active.CustomerDeactivated(__instance); } } } } [HarmonyPatch(typeof(Customer), "Update")] private static class CustomerUpdatePatch { [HarmonyPostfix] private static void Postfix(Customer __instance) { _active?.ObserveCustomer(__instance, "Customer.Update"); } } [HarmonyPatch(typeof(WorkerManager), "Start")] private static class WorkerManagerStartPatch { [HarmonyPostfix] private static void Postfix(WorkerManager __instance) { _active?.RegisterInitialWorkers(__instance); _active?.SignalWorkerManagerReady(); } } [HarmonyPatch(typeof(Worker), "ActivateWorker")] [HarmonyPatch(typeof(Worker), "DeactivateWorker")] private static class WorkerPopulationPatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null)) { if (__instance.m_IsActive && ((Component)__instance).gameObject.activeSelf) { _active.WorkerActivated(__instance); } else { _active.WorkerDeactivated(__instance); } } } } [HarmonyPatch(typeof(Worker), "Update")] private static class WorkerUpdatePatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { _active?.ObserveWorker(__instance, "Worker.Update"); } } [HarmonyPatch(typeof(Worker), "PlayWorkerActionAnim")] private static class WorkerActionPatch { [HarmonyPostfix] private static void Postfix(Worker __instance) { RecordWorkerAction(__instance); } } [HarmonyPatch(typeof(PricePopupSpawner), "ShowTextPopup")] private static class SpeechPatch { [HarmonyPostfix] private static void Postfix(PricePopupSpawner __instance, string text, float offsetUp, Transform followTransform) { if ((Object)(object)_active == (Object)null || (Object)(object)__instance == (Object)null || string.IsNullOrEmpty(text) || (Object)(object)followTransform == (Object)null) { return; } List pricePopupList = __instance.m_PricePopupList; if (pricePopupList == null) { return; } for (int i = 0; i < pricePopupList.Count; i++) { PricePopupUI val = pricePopupList[i]; if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf && (Object)(object)val.m_FollowTransform == (Object)(object)followTransform && (Object)(object)val.m_Text != (Object)null && ((TMP_Text)val.m_Text).text == text && TryGetCustomerIdentity(followTransform, out var index, out var identity)) { _active._context.Broadcast((INetMessage)(object)new NpcSpeechMessage { Kind = 0, Index = index, Identity = identity, Text = text, OffsetUp = offsetUp }); break; } } } } [HarmonyPatch(typeof(PricePopupSpawner), "ShowPricePopup")] private static class MoneyPatch { [HarmonyPostfix] private static void Postfix(PricePopupSpawner __instance, float price, float offsetUp, Transform followTransform) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)__instance == (Object)null) && !(price <= 0f) && !((Object)(object)followTransform == (Object)null) && TryGetCustomerIdentity(followTransform, out var index, out var identity)) { _active._context.Broadcast((INetMessage)(object)new NpcMoneyPopupMessage { Index = index, Identity = identity, Amount = price, OffsetUp = offsetUp }); } } } private static NpcHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _customerManagerReady; private bool _workerManagerReady; private readonly Dictionary _customers = new Dictionary(); private readonly Dictionary _customerIndices = new Dictionary(); private readonly Dictionary _workers = new Dictionary(); private readonly Dictionary _joinedConnections = new Dictionary(); private const byte KindCustomer = 0; private const byte KindWorker = 1; private const float SendInterval = 0.125f; private static readonly int HashMoveSpeed = Animator.StringToHash("MoveSpeed"); private static readonly int HashHoldingBag = Animator.StringToHash("HoldingBag"); private static readonly int HashHandingOverCash = Animator.StringToHash("HandingOverCash"); private static readonly int HashIsSitting = Animator.StringToHash("IsSitting"); private static readonly int HashIsPlaying = Animator.StringToHash("IsPlaying"); private static readonly int HashIsHoldingBox = Animator.StringToHash("IsHoldingBox"); private static readonly int HashIsBeingSprayed = Animator.StringToHash("IsBeingSprayed"); private static readonly FieldInfo FiCurrentHoldItemBox = AccessTools.Field(typeof(Worker), "m_CurrentHoldItemBox"); private float _sendTimer; private readonly Dictionary _npcStates = new Dictionary(); private bool _missingHoldFieldLogged; private static CustomerManager s_diagCm; private void OnEnable() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown if (_harmony == null) { _context = base.RuntimeContext; _active = this; _context.Messages.RegisterAttributedHandlers(this); _harmony = new Harmony("dev.meepen.cardshopcoop.npc.host"); _harmony.CreateClassProcessor(typeof(CustomerManagerStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerPopulationPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerUpdatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerManagerStartPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerPopulationPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerUpdatePatch)).Patch(); _harmony.CreateClassProcessor(typeof(WorkerActionPatch)).Patch(); _harmony.CreateClassProcessor(typeof(SpeechPatch)).Patch(); _harmony.CreateClassProcessor(typeof(MoneyPatch)).Patch(); SceneManager.sceneLoaded += OnSceneLoaded; CEventManager.AddListener((EventDelegate)OnGameDataFinishLoaded); RegisterInitialPopulation(NpcInterop.CustomerManager); RegisterInitialWorkers(NpcInterop.WorkerManager); SignalCustomerManagerReady(); SignalWorkerManagerReady(); } } private void Update() { if (!_context.InGame()) { return; } using (PerfProbe.Sample("module.npc.host-update")) { UpdateInner(); } } private void UpdateInner() { List list = HostCollect(Time.deltaTime); if (list != null) { for (int i = 0; i < list.Count; i++) { _context.Broadcast((INetMessage)(object)list[i]); } } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _joinedConnections.Clear(); CEventManager.RemoveListener((EventDelegate)OnGameDataFinishLoaded); SceneManager.sceneLoaded -= OnSceneLoaded; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } private void OnDestroy() { Shutdown(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State)) { _joinedConnections[connection.Id] = connection; SendBaselineWhenReady(connection.Id); } } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _joinedConnections.Remove(connection.Id); } } private void SendBaselineWhenReady(int connectionId) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (_context.InGame() && _customerManagerReady && _workerManagerReady && _joinedConnections.TryGetValue(connectionId, out var value) && value != null && IsJoinPhase(value.State)) { SendBaseline(connectionId); } } private void SendBaselinesToJoined() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (!_context.InGame() || !_customerManagerReady || !_workerManagerReady) { return; } foreach (KeyValuePair joinedConnection in _joinedConnections) { if (joinedConnection.Value != null && IsJoinPhase(joinedConnection.Value.State)) { SendBaseline(joinedConnection.Key); } } } private void SendBaseline(int connectionId) { if (_customerManagerReady && _workerManagerReady) { NpcBaselineMessage npcBaselineMessage = BuildBaseline(); if (npcBaselineMessage != null) { _context.Send(connectionId, (INetMessage)(object)npcBaselineMessage); } } } private NpcBaselineMessage BuildBaseline() { CustomerManager customerManager = GetCustomerManager(); List list = ((customerManager != null) ? customerManager.GetCustomerList() : null); if (!_customerManagerReady || !_workerManagerReady || list == null || (Object)(object)NpcInterop.WorkerManager == (Object)null || NpcInterop.Workers == null) { return null; } float unscaledTime = Time.unscaledTime; NpcBaselineMessage npcBaselineMessage = new NpcBaselineMessage { HostTime = unscaledTime, CustomerCapacity = list.Count, CustomerFemales = new List(list.Count) }; for (int i = 0; i < list.Count; i++) { npcBaselineMessage.CustomerFemales.Add((Object)(object)list[i] != (Object)null && list[i].m_IsFemale); } List list2 = new List(_customers.Keys); list2.Sort(); for (int j = 0; j < list2.Count; j++) { int num = list2[j]; if (_customers.TryGetValue(num, out var value) && IsCustomerActive(value)) { NpcEntry npcEntry = BuildCustomerEntry(value, num, unscaledTime); if (npcEntry != null) { npcBaselineMessage.Entries.Add(npcEntry); } } } List list3 = new List(_workers.Keys); list3.Sort(); for (int k = 0; k < list3.Count; k++) { int num2 = list3[k]; if (_workers.TryGetValue(num2, out var value2) && IsWorkerActive(value2)) { NpcEntry npcEntry2 = BuildWorkerEntry(value2, num2, unscaledTime); if (npcEntry2 != null) { npcBaselineMessage.Entries.Add(npcEntry2); } } } CoopPlugin.Log.LogInfo((object)("[npc] host baseline entries=" + npcBaselineMessage.Entries.Count + " capacity=" + npcBaselineMessage.CustomerCapacity + " customersTracked=" + _customers.Count + " workersTracked=" + _workers.Count)); return npcBaselineMessage; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } private void SignalCustomerManagerReady() { CustomerManager customerManager = NpcInterop.CustomerManager; _customerManagerReady = ((customerManager != null) ? customerManager.GetCustomerList() : null) != null; SendBaselinesToJoined(); } private void SignalWorkerManagerReady() { _workerManagerReady = (Object)(object)NpcInterop.WorkerManager != (Object)null && NpcInterop.Workers != null; SendBaselinesToJoined(); } private void OnGameDataFinishLoaded(CEventPlayer_GameDataFinishLoaded _) { SignalCustomerManagerReady(); SignalWorkerManagerReady(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { if (!_shutdown) { ResetSceneState(); } } private void ResetSceneState() { _customerManagerReady = false; _workerManagerReady = false; _sendTimer = 0f; _missingHoldFieldLogged = false; _npcStates.Clear(); _customers.Clear(); _customerIndices.Clear(); _workers.Clear(); } internal void ResetState() { _missingHoldFieldLogged = false; _sendTimer = 0f; _npcStates.Clear(); _customers.Clear(); _customerIndices.Clear(); _workers.Clear(); _joinedConnections.Clear(); } public CustomerManager GetCustomerManager() { return NpcInterop.CustomerManager; } private NpcData GetNpcData(byte kind, int index) { NpcKey key = new NpcKey(kind, index); if (!_npcStates.TryGetValue(key, out var value)) { value = new NpcData(); _npcStates.Add(key, value); } return value; } public List HostCollect(float dt) { _active = this; _sendTimer += dt; CustomerManager customerManager = GetCustomerManager(); List list = ((customerManager != null) ? customerManager.GetCustomerList() : null); if ((Object)(object)customerManager == (Object)null || list == null) { return null; } if (_sendTimer < 0.125f) { return null; } _sendTimer -= 0.125f; if (_sendTimer > 0.125f) { _sendTimer = 0.125f; } List list2 = new List(_customers.Count + _workers.Count); List list3 = new List(_customers.Keys); list3.Sort(); for (int i = 0; i < list3.Count; i++) { int num = list3[i]; if (_customers.TryGetValue(num, out var value)) { NpcStateDeltaMessage npcStateDeltaMessage = CollectCustomerState(value, num, Time.unscaledTime); if (npcStateDeltaMessage != null) { list2.Add(npcStateDeltaMessage); } } } List list4 = new List(_workers.Keys); list4.Sort(); for (int j = 0; j < list4.Count; j++) { int num2 = list4[j]; if (_workers.TryGetValue(num2, out var value2)) { NpcStateDeltaMessage npcStateDeltaMessage2 = CollectWorkerState(value2, num2, Time.unscaledTime); if (npcStateDeltaMessage2 != null) { list2.Add(npcStateDeltaMessage2); } } } if (list2.Count == 0) { return null; } return list2; } private void RegisterInitialPopulation(CustomerManager manager) { List list = ((manager != null) ? manager.GetCustomerList() : null); int num = 0; while (list != null && num < list.Count) { RegisterCustomer(list[num], "CustomerManager.Start"); num++; } } private void RegisterInitialWorkers(WorkerManager manager) { List workers = NpcInterop.Workers; int num = 0; while (workers != null && num < workers.Count) { RegisterWorker(workers[num], "WorkerManager.Start"); num++; } } private void RegisterCustomer(Customer customer, string source) { if ((Object)(object)customer == (Object)null || !NpcInterop.TryGetCustomerIndex(customer, out var index)) { LogUnresolved(0, (Object)(object)customer, source); return; } Customer value; bool flag = _customers.TryGetValue(index, out value) && value != customer; _customers[index] = customer; _customerIndices[customer] = index; NpcData npcData = GetNpcData(0, index); bool flag2 = IsCustomerActive(customer); if (flag2 && (!npcData.Active || flag)) { npcData.Generation++; } bool active = npcData.Active; npcData.Active = flag2; if (active && !flag2) { SendStateDelta(0, (ushort)index, npcData.Generation, active: false, Time.unscaledTime); } if (flag2) { SendCurrentIdentity(0, index, customer, npcData); } } private void RegisterWorker(Worker worker, string source) { if ((Object)(object)worker == (Object)null || !NpcInterop.TryGetWorkerIndex(worker, out var index)) { LogUnresolved(1, (Object)(object)worker, source); return; } Worker value; bool flag = _workers.TryGetValue(index, out value) && value != worker; _workers[index] = worker; NpcData npcData = GetNpcData(1, index); bool flag2 = IsWorkerActive(worker); if (flag2 && (!npcData.Active || flag)) { npcData.Generation++; } bool active = npcData.Active; npcData.Active = flag2; if (active && !flag2) { SendStateDelta(1, (ushort)index, npcData.Generation, active: false, Time.unscaledTime); } if (flag2) { SendCurrentIdentity(1, index, worker, npcData); } } private void ObserveCustomer(Customer customer, string source) { if ((Object)(object)customer == (Object)null) { LogUnresolved(0, (Object)(object)customer, source); return; } if (!_customerIndices.TryGetValue(customer, out var value) && !NpcInterop.TryGetCustomerIndex(customer, out value)) { LogUnresolved(0, (Object)(object)customer, source); return; } if (!_customers.TryGetValue(value, out var value2) || value2 != customer) { RegisterCustomer(customer, source); return; } NpcData npcData = GetNpcData(0, value); bool flag = IsCustomerActive(customer); if (flag && !npcData.Active) { npcData.Generation++; } bool active = npcData.Active; npcData.Active = flag; if (active && !flag) { SendStateDelta(0, (ushort)value, npcData.Generation, active: false, Time.unscaledTime); } if (flag) { SendCurrentIdentity(0, value, customer, npcData); } } private void ObserveWorker(Worker worker, string source) { if ((Object)(object)worker == (Object)null || !NpcInterop.TryGetWorkerIndex(worker, out var index)) { LogUnresolved(1, (Object)(object)worker, source); return; } if (!_workers.TryGetValue(index, out var value) || value != worker) { RegisterWorker(worker, source); return; } NpcData npcData = GetNpcData(1, index); bool flag = IsWorkerActive(worker); if (flag && !npcData.Active) { npcData.Generation++; } bool active = npcData.Active; npcData.Active = flag; if (active && !flag) { SendStateDelta(1, (ushort)index, npcData.Generation, active: false, Time.unscaledTime); } if (flag) { SendCurrentIdentity(1, index, worker, npcData); } } private static void LogUnresolved(byte kind, Object instance, string source) { CoopPlugin.Log.LogWarning((object)("Npc host: " + ((kind == 0) ? "customer" : "worker") + " mutation from " + source + " has no stable identity (" + ((instance == (Object)null) ? "" : instance.name) + ")")); } private void CustomerActivated(Customer customer) { RegisterCustomer(customer, "Customer.ActivateCustomer"); } private void CustomerDeactivated(Customer customer) { ObserveCustomer(customer, "Customer.DeactivateCustomer"); } private void WorkerActivated(Worker worker) { RegisterWorker(worker, "Worker.ActivateWorker"); } private void WorkerDeactivated(Worker worker) { ObserveWorker(worker, "Worker.DeactivateWorker"); } private NpcStateDeltaMessage CollectCustomerState(Customer customer, int index, float hostTime) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Invalid comparison between Unknown and I4 NpcData npcData = GetNpcData(0, index); bool flag = IsCustomerActive(customer); if (flag && !npcData.Active) { npcData.Generation++; SendCurrentIdentity(0, index, customer, npcData); } bool active = npcData.Active; npcData.Active = flag; if (!flag) { if (!active) { return null; } return NewInactiveDelta(0, index, npcData.Generation, hostTime); } CharacterCustomization characterCustom = customer.m_CharacterCustom; if ((Object)(object)characterCustom == (Object)null || string.IsNullOrEmpty(characterCustom.CharacterName)) { return null; } NpcFlags flags = CollectFlags(customer.m_Anim); flags = AddCustomerReactionFlags(customer, flags); int grabSequence = GetGrabSequence(index, customer.m_CurrentState); byte actionKind = (byte)(((int)customer.m_CurrentState != 27) ? 1 : 2); if (customer.IsSmelly()) { flags |= NpcFlags.Smelly; } if ((Object)(object)customer.m_ExclaimationMesh != (Object)null && customer.m_ExclaimationMesh.activeSelf) { flags |= NpcFlags.Exclaim; } return NewStateDelta(0, index, npcData.Generation, hostTime, ((Component)customer).transform, customer.m_CurrentMoveSpeed, flags, grabSequence, actionKind, holdBig: false, (EItemType)0, Guid.Empty, holdBoxOpened: false); } private NpcStateDeltaMessage CollectWorkerState(Worker worker, int index, float hostTime) { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) NpcData npcData = GetNpcData(1, index); bool flag = IsWorkerActive(worker); if (flag && !npcData.Active) { npcData.Generation++; SendCurrentIdentity(1, index, worker, npcData); } bool active = npcData.Active; npcData.Active = flag; if (!flag) { ReleaseTrackedWorkerBox(npcData); if (!active) { return null; } return NewInactiveDelta(1, index, npcData.Generation, hostTime); } CharacterCustomization characterCustom = worker.m_CharacterCustom; if ((Object)(object)characterCustom == (Object)null || string.IsNullOrEmpty(characterCustom.CharacterName)) { return null; } NpcFlags npcFlags = CollectFlags(worker.m_Anim); float speed = (((Object)(object)worker.m_Anim == (Object)null) ? 0f : worker.m_Anim.GetFloat(HashMoveSpeed)); if (FiCurrentHoldItemBox == null && !_missingHoldFieldLogged) { _missingHoldFieldLogged = true; CoopPlugin.Log.LogError((object)"NpcHostBehaviour: Worker hold-item field is missing; holding-box visuals disabled"); } InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)((FiCurrentHoldItemBox == null) ? null : /*isinst with value type is only supported in some contexts*/); bool holdBig = (Object)(object)val != (Object)null && val.m_IsBigBox; EItemType holdItemType = (EItemType)((!((Object)(object)val == (Object)null)) ? ((int)val.GetItemType()) : 0); Guid boxNetworkId = Guid.Empty; bool holdBoxOpened = false; if ((Object)(object)val != (Object)null) { WorldHostBehaviour.TryGetHeldBoxId((InteractablePackagingBox)(object)val, out boxNetworkId); holdBoxOpened = WorldHostBehaviour.IsHeldBoxOpen((InteractablePackagingBox)(object)val); } if (npcData.LastHeldBoxId != Guid.Empty && npcData.LastHeldBoxId != boxNetworkId) { WorldHostBehaviour.NotifyWorkerReleasedBox(npcData.LastHeldBoxId); } npcData.LastHeldBoxId = boxNetworkId; if ((Object)(object)val != (Object)null) { npcFlags |= NpcFlags.IsHoldingBox; } if ((Object)(object)worker.m_ExclaimationMesh != (Object)null && worker.m_ExclaimationMesh.activeSelf) { npcFlags |= NpcFlags.Exclaim; } if (worker.m_IsFemale) { npcFlags |= NpcFlags.Female; } return NewStateDelta(1, index, npcData.Generation, hostTime, ((Component)worker).transform, speed, npcFlags, npcData.WorkerActionSequence, npcData.WorkerActionKind, holdBig, holdItemType, boxNetworkId, holdBoxOpened); } private static bool IsCustomerActive(Customer customer) { if ((Object)(object)customer != (Object)null && customer.m_IsActive) { return ((Component)customer).gameObject.activeSelf; } return false; } private static bool IsWorkerActive(Worker worker) { if ((Object)(object)worker != (Object)null && worker.m_IsActive) { return ((Component)worker).gameObject.activeSelf; } return false; } private static void ReleaseTrackedWorkerBox(NpcData state) { if (state != null && !(state.LastHeldBoxId == Guid.Empty)) { WorldHostBehaviour.NotifyWorkerReleasedBox(state.LastHeldBoxId); state.LastHeldBoxId = Guid.Empty; } } private static NpcStateDeltaMessage NewInactiveDelta(byte kind, int index, int identity, float hostTime) { return new NpcStateDeltaMessage { HostTime = hostTime, Kind = kind, Index = (ushort)index, Identity = identity, Active = false }; } private static NpcStateDeltaMessage NewStateDelta(byte kind, int index, int identity, float hostTime, Transform transform, float speed, NpcFlags flags, int actionSequence, byte actionKind, bool holdBig, EItemType holdItemType, Guid holdBoxNetworkId, bool holdBoxOpened) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) return new NpcStateDeltaMessage { HostTime = hostTime, Kind = kind, Index = (ushort)index, Identity = identity, Active = true, Position = transform.position, Yaw = transform.eulerAngles.y, Speed = speed, Flags = (ushort)flags, ActionSequence = actionSequence, ActionKind = actionKind, HoldBig = holdBig, HoldItemType = holdItemType, HoldBoxNetworkId = holdBoxNetworkId, HoldBoxOpened = holdBoxOpened }; } private NpcEntry BuildCustomerEntry(Customer customer, int index, float hostTime) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Invalid comparison between Unknown and I4 NpcData npcData = GetNpcData(0, index); CharacterCustomization characterCustom = customer.m_CharacterCustom; if ((Object)(object)characterCustom == (Object)null || string.IsNullOrEmpty(characterCustom.CharacterName)) { return null; } NpcFlags flags = CollectFlags(customer.m_Anim); flags = AddCustomerReactionFlags(customer, flags); if (customer.IsSmelly()) { flags |= NpcFlags.Smelly; } if ((Object)(object)customer.m_ExclaimationMesh != (Object)null && customer.m_ExclaimationMesh.activeSelf) { flags |= NpcFlags.Exclaim; } return NewEntry(0, index, npcData.Generation, characterCustom.CharacterName, customer.m_IsFemale, ((Component)customer).transform, customer.m_CurrentMoveSpeed, flags, GetGrabSequence(index, customer.m_CurrentState), (byte)(((int)customer.m_CurrentState != 27) ? 1 : 2), holdBig: false, (EItemType)0, Guid.Empty, holdBoxOpened: false); } private NpcEntry BuildWorkerEntry(Worker worker, int index, float hostTime) { //IL_0120: Unknown result type (might be due to invalid IL or missing references) NpcData npcData = GetNpcData(1, index); CharacterCustomization characterCustom = worker.m_CharacterCustom; if ((Object)(object)characterCustom == (Object)null || string.IsNullOrEmpty(characterCustom.CharacterName)) { return null; } NpcFlags npcFlags = CollectFlags(worker.m_Anim); float speed = (((Object)(object)worker.m_Anim == (Object)null) ? 0f : worker.m_Anim.GetFloat(HashMoveSpeed)); InteractablePackagingBox_Item val = (InteractablePackagingBox_Item)((FiCurrentHoldItemBox == null) ? null : /*isinst with value type is only supported in some contexts*/); Guid boxNetworkId = Guid.Empty; bool holdBoxOpened = false; if ((Object)(object)val != (Object)null) { WorldHostBehaviour.TryGetHeldBoxId((InteractablePackagingBox)(object)val, out boxNetworkId); holdBoxOpened = WorldHostBehaviour.IsHeldBoxOpen((InteractablePackagingBox)(object)val); npcFlags |= NpcFlags.IsHoldingBox; } if ((Object)(object)worker.m_ExclaimationMesh != (Object)null && worker.m_ExclaimationMesh.activeSelf) { npcFlags |= NpcFlags.Exclaim; } if (worker.m_IsFemale) { npcFlags |= NpcFlags.Female; } return NewEntry(1, index, npcData.Generation, characterCustom.CharacterName, worker.m_IsFemale, ((Component)worker).transform, speed, npcFlags, npcData.WorkerActionSequence, npcData.WorkerActionKind, (Object)(object)val != (Object)null && val.m_IsBigBox, (EItemType)((!((Object)(object)val == (Object)null)) ? ((int)val.GetItemType()) : 0), boxNetworkId, holdBoxOpened); } private static NpcEntry NewEntry(byte kind, int index, int identity, string name, bool female, Transform transform, float speed, NpcFlags flags, int actionSequence, byte actionKind, bool holdBig, EItemType holdItemType, Guid holdBoxNetworkId, bool holdBoxOpened) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0079: 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) return new NpcEntry { Kind = kind, Index = (ushort)index, Identity = identity, Female = female, HasName = true, CharName = name, Position = transform.position, Yaw = transform.eulerAngles.y, Speed = speed, Flags = (ushort)flags, ActionSequence = actionSequence, ActionKind = actionKind, HoldBig = holdBig, HoldItemType = holdItemType, HoldBoxNetworkId = holdBoxNetworkId, HoldBoxOpened = holdBoxOpened }; } private int GetGrabSequence(int index, ECustomerState state) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0023: 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_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 NpcData npcData = GetNpcData(0, index); ECustomerState customerState = npcData.CustomerState; if (((int)state == 3 || (int)state == 11 || (int)state == 21 || (int)state == 27) && customerState != state) { npcData.CustomerGrabSequence++; } npcData.CustomerState = state; return npcData.CustomerGrabSequence; } private void SendIdentityToPeers(byte kind, ushort index, int identity, bool female, string name, Vector3 position) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(name)) { return; } foreach (KeyValuePair joinedConnection in _joinedConnections) { PeerConnection value = joinedConnection.Value; if (value != null && IsJoinPhase(value.State)) { _context.Send(joinedConnection.Key, (INetMessage)(object)new NpcIdentityDeltaMessage { Kind = kind, Index = index, Identity = identity, Female = female, CharName = name, Position = position }); } } } private void SendCurrentIdentity(byte kind, int index, Customer customer, NpcData state) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)customer == (Object)null) && state != null && state.Active && state.Generation > 0 && !((Object)(object)customer.m_CharacterCustom == (Object)null) && !string.IsNullOrEmpty(customer.m_CharacterCustom.CharacterName)) { SendCurrentIdentity(kind, index, customer.m_CharacterCustom.CharacterName, customer.m_IsFemale, state.Generation, ((Component)customer).transform.position, state); } } private void SendCurrentIdentity(byte kind, int index, Worker worker, NpcData state) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)worker == (Object)null) && state != null && state.Active && state.Generation > 0 && !((Object)(object)worker.m_CharacterCustom == (Object)null) && !string.IsNullOrEmpty(worker.m_CharacterCustom.CharacterName)) { SendCurrentIdentity(kind, index, worker.m_CharacterCustom.CharacterName, worker.m_IsFemale, state.Generation, ((Component)worker).transform.position, state); } } private void SendCurrentIdentity(byte kind, int index, string name, bool female, int identity, Vector3 position, NpcData state) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(name) && identity > 0 && index >= 0 && index < 256 && (state.IdentityGeneration != identity || !(state.IdentityName == name) || state.IdentityFemale != female)) { state.IdentityName = name; state.IdentityFemale = female; state.IdentityGeneration = identity; SendIdentityToPeers(kind, (ushort)index, identity, female, name, position); } } private void SendStateDelta(byte kind, ushort index, int identity, bool active, float hostTime) { if (!_shutdown && _context.InGame() && identity > 0) { _context.Broadcast((INetMessage)(object)new NpcStateDeltaMessage { HostTime = hostTime, Kind = kind, Index = index, Identity = identity, Active = active }); } } private static NpcFlags CollectFlags(Animator anim) { NpcFlags npcFlags = NpcFlags.None; if ((Object)(object)anim == (Object)null) { return npcFlags; } if (anim.GetBool(HashHoldingBag)) { npcFlags |= NpcFlags.HoldingBag; } if (anim.GetBool(HashHandingOverCash)) { npcFlags |= NpcFlags.HandingOverCash; } if (anim.GetBool(HashIsSitting)) { npcFlags |= NpcFlags.IsSitting; } if (anim.GetBool(HashIsPlaying)) { npcFlags |= NpcFlags.IsPlaying; } if (anim.GetBool(HashIsHoldingBox)) { npcFlags |= NpcFlags.IsHoldingBox; } return npcFlags; } private static NpcFlags AddCustomerReactionFlags(Customer customer, NpcFlags flags) { if ((Object)(object)customer.m_Anim != (Object)null && customer.m_Anim.GetBool(HashIsBeingSprayed)) { flags |= NpcFlags.Sprayed; } if ((Object)(object)customer.m_CleanFX != (Object)null && customer.m_CleanFX.activeSelf) { flags |= NpcFlags.Cleaned; } return flags; } public static void RecordWorkerAction(Worker worker) { if (!((Object)(object)_active == (Object)null) && !((Object)(object)worker == (Object)null)) { if (!NpcInterop.TryGetWorkerIndex(worker, out var index)) { CoopPlugin.Log.LogWarning((object)"Npc host: worker action has no stable worker identity"); return; } NpcData npcData = _active.GetNpcData(1, index); npcData.WorkerActionSequence++; npcData.WorkerActionKind = 3; } } public static int CountLocalActiveNpcs() { s_diagCm = NpcInterop.CustomerManager; int num = 0; if ((Object)(object)s_diagCm != (Object)null) { List customerList = s_diagCm.GetCustomerList(); for (int i = 0; i < customerList.Count; i++) { if ((Object)(object)customerList[i] != (Object)null && ((Component)customerList[i]).gameObject.activeSelf) { num++; } } } List workers = NpcInterop.Workers; if (workers != null) { for (int j = 0; j < workers.Count; j++) { if ((Object)(object)workers[j] != (Object)null && ((Component)workers[j]).gameObject.activeSelf) { num++; } } } return num; } public static int GetCustomerGeneration(Customer customer) { if ((Object)(object)_active == (Object)null || (Object)(object)customer == (Object)null) { return 0; } if (!_active._customerIndices.TryGetValue(customer, out var value) && !NpcInterop.TryGetCustomerIndex(customer, out value)) { return 0; } NpcData npcData = _active.GetNpcData(0, value); if (npcData.Generation == 0) { npcData.Generation = 1; } bool flag = customer.m_IsActive && ((Component)customer).gameObject.activeSelf; bool active = npcData.Active; if (flag && !active) { npcData.Generation++; } npcData.Active = flag; return npcData.Generation; } public static bool TryGetCustomerIdentity(Transform transform, out ushort index, out int identity) { index = 0; identity = 0; if ((Object)(object)_active == (Object)null || (Object)(object)transform == (Object)null) { return false; } foreach (KeyValuePair customer in _active._customers) { Customer value = customer.Value; if ((Object)(object)value != (Object)null && (Object)(object)((Component)value).transform == (Object)(object)transform) { index = (ushort)customer.Key; identity = GetCustomerGeneration(value); return true; } } CoopPlugin.Log.LogWarning((object)"Npc host: customer transform has no stable identity"); return false; } } internal static class NpcInterop { private static readonly FieldInfo WorkerList = AccessTools.Field(typeof(WorkerManager), "m_WorkerList"); internal static CustomerManager CustomerManager => SceneRef.Get(); internal static WorkerManager WorkerManager => SceneRef.Get(); internal static List Workers { get { WorkerManager workerManager = WorkerManager; if (!((Object)(object)workerManager == (Object)null) && !(WorkerList == null)) { return WorkerList.GetValue(workerManager) as List; } return null; } } internal static bool TryGetCustomerIndex(Customer customer, out int index) { index = -1; CustomerManager customerManager = CustomerManager; List list = ((customerManager != null) ? customerManager.GetCustomerList() : null); if (list == null || (Object)(object)customer == (Object)null) { return false; } index = list.IndexOf(customer); return index >= 0; } internal static bool TryGetWorkerIndex(Worker worker, out int index) { index = (((Object)(object)worker == (Object)null) ? (-1) : worker.m_WorkerIndex); List workers = Workers; if (workers != null && index >= 0 && index < workers.Count) { return workers[index] == worker; } return false; } } [NetworkMessage] public sealed class NpcBaselineMessage : INetMessage { public float HostTime; public int CustomerCapacity = -1; public List CustomerFemales; public List Entries = new List(); } [NetworkMessage(/*Could not decode attribute arguments.*/)] public sealed class NpcStateDeltaMessage : INetMessage { public float HostTime; public byte Kind; public ushort Index; public int Identity; public bool Active; public Vector3 Position; public float Yaw; public float Speed; public ushort Flags; public int ActionSequence; public byte ActionKind; public bool HoldBig; public EItemType HoldItemType; public Guid HoldBoxNetworkId; public bool HoldBoxOpened; } [NetworkMessage] public sealed class NpcIdentityDeltaMessage : INetMessage { public byte Kind; public ushort Index; public int Identity; public bool Female; public string CharName; public Vector3 Position; } [NetworkMessage] public sealed class NpcSpeechMessage : INetMessage { public byte Kind; public ushort Index; public int Identity; public string Text; public float OffsetUp; } [NetworkMessage] public sealed class NpcMoneyPopupMessage : INetMessage { public ushort Index; public int Identity; public float Amount; public float OffsetUp; } public sealed class NpcEntry { public byte Kind; public ushort Index; public int Identity; public bool Female; public bool HasName; public string CharName; public Vector3 Position; public float Yaw; public float Speed; public ushort Flags; public int ActionSequence; public byte ActionKind; public bool HoldBig; public EItemType HoldItemType; public Guid HoldBoxNetworkId; public bool HoldBoxOpened; } } namespace CardShopCoop.Modules.Light { [ClientBehaviour] public sealed class LightClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(LightManager), "ToggleShopLight")] private static class LightSwitchPatch { private struct ToggleState { public bool Armed; public bool Prior; } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(out ToggleState __state) { __state = default(ToggleState); LightClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active._joined && active._context != null && active._context.InGame() && LightSwitchState.TryGet(out var isActive)) { __state = new ToggleState { Armed = true, Prior = isActive }; } } [HarmonyPostfix] private static void Postfix(ToggleState __state) { _active?.ObserveToggle(__state.Armed, __state.Prior); } } [HarmonyPatch(typeof(LightManager), "Awake")] private static class LightReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.TryApplyPending(); } } private const string PredictionScope = "light"; private static LightClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private LightSwitchStateMessage _pendingState; private void OnEnable() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; bool flag2 = false; try { ResetSessionState(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; flag2 = true; _harmony = new Harmony("dev.meepen.cardshopcoop.light.client"); _harmony.CreateClassProcessor(typeof(LightSwitchPatch)).Patch(); _harmony.CreateClassProcessor(typeof(LightReadyPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Light client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag2) { CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; } if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } ResetSessionState(); _context = null; throw; } } [MessageHandler(typeof(LightSwitchStateMessage))] private void HandleState(MessageContext context, LightSwitchStateMessage message) { if (!_shutdown) { if (_pendingState != null) { PredictionApi.Ack(_pendingState.PredictionId); } _pendingState = message; TryApplyPending(); } } private void TryApplyPending() { if (!_shutdown && _pendingState != null && _context != null && _context.InGame() && LightSwitchState.TryGet(out var _)) { LightSwitchStateMessage pending = _pendingState; PredictionApi.ApplyAuthoritative(pending.PredictionId, delegate { LightSwitchState.Apply(pending.IsActive); }); _pendingState = null; } } private void ObserveToggle(bool armed, bool prior) { if (armed && _context != null && _joined && _context.InGame() && LightSwitchState.TryGet(out var after) && after != prior) { PredictionApi.Predict("light", delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new LightSwitchIntentMessage { PredictionId = predictionId, IsActive = after }); }, delegate { LightSwitchState.Apply(after); }, delegate { LightSwitchState.Apply(prior); }); LightSwitchState.Apply(after); } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { TryApplyPending(); } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; TryApplyPending(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ResetSessionState(); } } private void ResetSessionState() { _joined = false; _pendingState = null; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } ResetSessionState(); _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class LightHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(LightManager), "ToggleShopLight")] private static class LightSwitchPatch { [HarmonyPostfix] private static void Postfix() { _active?.OnShopLightToggled(); } } [HarmonyPatch(typeof(LightManager), "Awake")] private static class LightReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.SendStateToJoined(); } } private static LightHostBehaviour _active; private readonly HashSet _fullyJoined = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private void OnEnable() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; bool flag2 = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; flag2 = true; _harmony = new Harmony("dev.meepen.cardshopcoop.light.host"); _harmony.CreateClassProcessor(typeof(LightSwitchPatch)).Patch(); _harmony.CreateClassProcessor(typeof(LightReadyPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Light host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag2) { CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; } if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } [OnFullyJoined] private void SendInitialState(PeerConnection connection) { if (connection != null) { _fullyJoined.Add(connection.Id); SendState(connection.Id); } } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _fullyJoined.Remove(connection.Id); } } [MessageHandler(typeof(LightSwitchIntentMessage))] private void HandleIntent(MessageContext context, LightSwitchIntentMessage message) { if (IsPeerMessage(context)) { if (!LightInterop.IsSceneReady(LightInterop.FindSceneManager())) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); return; } LightSwitchState.Apply(message.IsActive); BroadcastState(message.PredictionId); } } private void BroadcastState(Guid predictionId) { if (!_shutdown && _context != null && _context.InGame() && LightSwitchState.TryGet(out var isActive)) { _context.Broadcast((INetMessage)(object)new LightSwitchStateMessage { PredictionId = predictionId, IsActive = isActive }); } } private void SendState(int peer) { if (!_shutdown && _context != null && _context.InGame() && LightSwitchState.TryGet(out var isActive)) { _context.Send(peer, (INetMessage)(object)new LightSwitchStateMessage { PredictionId = Guid.Empty, IsActive = isActive }); } } private void SendStateToJoined() { foreach (int item in new List(_fullyJoined)) { SendState(item); } } private void OnShopLightToggled() { if (!_shutdown && _context != null && _context.InGame() && LightSwitchState.TryGet(out var isActive)) { LightSwitchState.Apply(isActive); BroadcastState(Guid.Empty); } } private bool IsPeerMessage(MessageContext context) { if (!_shutdown && context?.Connection != null) { return _fullyJoined.Contains(context.Connection.Id); } return false; } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { SendStateToJoined(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { SendStateToJoined(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _fullyJoined.Clear(); _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class LightInterop { internal static readonly MethodInfo EvaluateWorldUIBrightness = ReflectionSurface.RequiredMethod(typeof(LightManager), "EvaluateWorldUIBrightness"); internal static readonly FieldInfo FinishLoading = ReflectionSurface.RequiredField(typeof(LightManager), "m_FinishLoading"); internal static bool IsSceneReady(LightManager manager) { if ((Object)(object)manager != (Object)null) { return (bool)FinishLoading.GetValue(manager); } return false; } internal static LightManager FindSceneManager() { return SceneRef.Get(); } } [NetworkMessage] public sealed class LightSwitchStateMessage : IPredictedMessage, INetMessage { public bool IsActive; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class LightSwitchIntentMessage : IPredictedMessage, INetMessage { public bool IsActive; public Guid PredictionId { get; set; } } internal static class LightSwitchState { internal static bool TryGet(out bool isActive) { isActive = false; LightManager val = LightInterop.FindSceneManager(); if (!LightInterop.IsSceneReady(val) || (Object)(object)val.m_ShoplightGrp == (Object)null) { return false; } isActive = val.m_ShoplightGrp.activeSelf; return true; } internal static void Apply(bool isActive) { LightManager val = LightInterop.FindSceneManager(); if (val.m_ShoplightGrp.activeSelf != isActive) { val.m_ShoplightGrp.SetActive(isActive); LightInterop.EvaluateWorldUIBrightness.Invoke(val, null); } ApplySwitchModels(isActive); } private static void ApplySwitchModels(bool isActive) { //IL_001d: 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) InteractableLightSwitch[] array = Resources.FindObjectsOfTypeAll(); foreach (InteractableLightSwitch val in array) { if ((Object)(object)val == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { if ((Object)(object)val.m_SwitchOnModel != (Object)null) { val.m_SwitchOnModel.SetActive(isActive); } if ((Object)(object)val.m_SwitchOffModel != (Object)null) { val.m_SwitchOffModel.SetActive(!isActive); } } } } } } namespace CardShopCoop.Modules.Hud { internal static class EconomyActionScope { private static int _depth; internal static bool Active => _depth > 0; internal static void Enter() { _depth++; } internal static void Exit() { if (_depth > 0) { _depth--; } } } public static class HudApi { public static string ToastText => HudPresentationState.ToastText; public static float ToastRemaining => HudPresentationState.ToastRemaining; public static bool HasToast => HudPresentationState.HasToast; public static string HostTimeText => HudPresentationState.HostTimeText; public static bool NextDayWaitActive => HudPresentationState.NextDayWaitActive; public static string NextDayWaitText => HudPresentationState.NextDayWaitText; public static bool EndOfDayPendingActive => HudPresentationState.EndOfDayPendingActive; public static string EndOfDayPendingText => HudPresentationState.EndOfDayPendingText; internal static void SetToast(string text, float seconds = 8f) { HudPresentationState.SetToast(text, seconds); } internal static void SetStatusLine(string text, float seconds) { HudPresentationState.SetStatusLine(text, seconds); } internal static void SetHostTime(int day, int hour, int minute) { HudPresentationState.SetHostTime(day, hour, minute); } internal static void SetHostTime(string text) { HudPresentationState.SetHostTime(text); } internal static void SetNextDayWait(bool active, string text) { HudPresentationState.SetNextDayWait(active, text); } internal static void SetEndOfDayPending(bool active, string text) { HudPresentationState.SetEndOfDayPending(active, text); } internal static void Clear() { HudPresentationState.Clear(); } } [ClientBehaviour] public sealed class HudClientBehaviour : CoopBehaviour { private readonly struct PendingContribution { internal readonly HudContributionKind Kind; internal readonly float Value; internal readonly HudAuthoritativeState Previous; internal PendingContribution(HudContributionKind kind, float value, HudAuthoritativeState previous) { Kind = kind; Value = value; Previous = previous; } } [HarmonyPatch(typeof(CEventManager), "QueueEvent")] private static class EconomyQueuePatch { [HarmonyPrefix] private static void Prefix(CEvent evt) { HudClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._shutdown && active._context != null && active._context.InGame() && evt != null && active._applyingSnapshot == 0 && !EconomyActionScope.Active && !PredictionApi.IsApplying && !PredictionApi.IsReconciling && TryGetContribution(evt, out var kind, out var value)) { active._pendingContributions[evt] = new PendingContribution(kind, value, active.Capture()); } } private static bool TryGetContribution(CEvent evt, out HudContributionKind kind, out float value) { CEventPlayer_AddCoin val = (CEventPlayer_AddCoin)(object)((evt is CEventPlayer_AddCoin) ? evt : null); if (val == null) { CEventPlayer_ReduceCoin val2 = (CEventPlayer_ReduceCoin)(object)((evt is CEventPlayer_ReduceCoin) ? evt : null); if (val2 == null) { CEventPlayer_AddShopExp val3 = (CEventPlayer_AddShopExp)(object)((evt is CEventPlayer_AddShopExp) ? evt : null); if (val3 == null) { CEventPlayer_AddFame val4 = (CEventPlayer_AddFame)(object)((evt is CEventPlayer_AddFame) ? evt : null); if (val4 != null) { kind = HudContributionKind.AddFame; value = val4.m_FameValue; return true; } kind = (HudContributionKind)0; value = 0f; return false; } kind = HudContributionKind.AddShopExperience; value = val3.m_ExpValue; return true; } kind = HudContributionKind.ReduceCoin; value = val2.m_CoinValue; return true; } kind = HudContributionKind.AddCoin; value = val.m_CoinValue; return true; } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnAddCoin")] private static class ObservedAddCoinPatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_AddCoin __0) { _active?.PublishObserved((CEvent)(object)__0); } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnReduceCoin")] private static class ObservedReduceCoinPatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_ReduceCoin __0) { _active?.PublishObserved((CEvent)(object)__0); } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnAddShopExp")] private static class ObservedAddShopExpPatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_AddShopExp __0) { HudClientBehaviour active = _active; if (!((Object)(object)active == (Object)null)) { active.NoteLocalShopLevel(); active.PublishObserved((CEvent)(object)__0); } } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnAddFame")] private static class ObservedAddFamePatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_AddFame __0) { _active?.PublishObserved((CEvent)(object)__0); } } [HarmonyPatch(typeof(GameUIScreen), "OnEnable")] private static class GameUiReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.ApplyPendingIfReady(); } } private const string PredictionScope = "hud"; private CoopRuntimeContext _context; private HudAuthoritativeState _pending; private bool _shutdown; private Harmony _harmony; private static HudClientBehaviour _active; private int _highestNotifiedLevel = -1; private bool _hasAuthoritativeLevel; private int _walletVersion; private int _progressVersion; private int _fameVersion; private int _applyingSnapshot; private GameUIScreen _gameUi; private readonly Dictionary _pendingContributions = new Dictionary(); private void OnEnable() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown if (_shutdown || _context != null || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.hud.client"); _harmony.CreateClassProcessor(typeof(EconomyQueuePatch)).Patch(); _harmony.CreateClassProcessor(typeof(ObservedAddCoinPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ObservedReduceCoinPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ObservedAddShopExpPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ObservedAddFamePatch)).Patch(); _harmony.CreateClassProcessor(typeof(GameUiReadyPatch)).Patch(); SceneManager.sceneLoaded += OnSceneLoaded; } catch (Exception arg) { CoopPlugin.Log.LogError((object)$"HUD client initialization failed: {arg}"); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if ((Object)(object)_active == (Object)(object)this) { _active = null; } _context = null; throw; } } private void OnSceneLoaded(Scene _, LoadSceneMode __) { _gameUi = null; } private void Update() { if (!_shutdown) { HudPresentationState.Tick(Time.deltaTime); } } private void ApplyPendingIfReady() { if (!_shutdown && _context.InGame() && IsGameUiReady() && _pending != null) { HudAuthoritativeState pending = _pending; _pending = null; Apply(pending); } } private bool IsGameUiReady() { //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) _gameUi = SceneRef.Get(); if ((Object)(object)_gameUi != (Object)null) { Scene scene = ((Component)_gameUi).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return ((Behaviour)_gameUi).isActiveAndEnabled; } } return false; } [OnFullyJoined] private void Joined(PeerConnection _) { _highestNotifiedLevel = -1; _hasAuthoritativeLevel = false; _gameUi = null; } private void ForwardObserved(HudContributionKind kind, float value, HudAuthoritativeState previous) { if (!_shutdown && _context != null && _context.InGame()) { int version = VersionFor(kind); PredictionApi.Predict("hud", delegate(Guid predictionId) { _context.Send(1, (INetMessage)(object)new HudContributionIntent { PredictionId = predictionId, Kind = kind, Value = value }); }, delegate { ApplyContribution(kind, value); }, delegate { RestoreUnlessSuperseded(kind, version, previous); }); } } private int VersionFor(HudContributionKind kind) { return kind switch { HudContributionKind.AddShopExperience => _progressVersion, HudContributionKind.AddFame => _fameVersion, _ => _walletVersion, }; } private void RestoreUnlessSuperseded(HudContributionKind kind, int version, HudAuthoritativeState previous) { if (VersionFor(kind) == version) { Restore(previous); } } private void PublishObserved(CEvent evt) { if (evt != null && _pendingContributions.TryGetValue(evt, out var value)) { _pendingContributions.Remove(evt); ForwardObserved(value.Kind, value.Value, value.Previous); } } [MessageHandler(typeof(HudAuthoritativeState))] private void State(MessageContext _, HudAuthoritativeState state) { if (state != null) { _pending = Clone(state); ApplyPendingIfReady(); } } [MessageHandler(typeof(HudWalletDeltaMessage))] private void WalletDelta(MessageContext _, HudWalletDeltaMessage message) { if (message != null) { PredictionApi.Confirm(message.PredictionId, delegate { ApplyWallet(message.Coins, message.CoinDisplay); }); } } [MessageHandler(typeof(HudProgressDeltaMessage))] private void ProgressDelta(MessageContext _, HudProgressDeltaMessage message) { if (message != null) { PredictionApi.Confirm(message.PredictionId, delegate { ApplyProgress(message.Experience, message.Level); }); } } [MessageHandler(typeof(HudFameDeltaMessage))] private void FameDelta(MessageContext _, HudFameDeltaMessage message) { if (message != null) { PredictionApi.Confirm(message.PredictionId, delegate { ApplyFame(message.Fame); }); } } private void Apply(HudAuthoritativeState state) { _walletVersion++; _progressVersion++; _fameVersion++; ApplyValues(state.Coins, state.CoinDisplay, state.Experience, state.Level, state.Fame); NotifyLevel(state.Level); } private void ApplyWallet(double coins, float coinDisplay) { _walletVersion++; UpdatePending(delegate(HudAuthoritativeState state) { state.Coins = coins; state.CoinDisplay = coinDisplay; }); ApplyValues(coins, coinDisplay, CPlayerData.m_ShopExpPoint, CPlayerData.m_ShopLevel, CPlayerData.m_FamePoint); } private void ApplyProgress(int experience, int level) { _progressVersion++; UpdatePending(delegate(HudAuthoritativeState state) { state.Experience = experience; state.Level = level; }); ApplyValues(CPlayerData.m_CoinAmountDouble, CPlayerData.m_CoinAmount, experience, level, CPlayerData.m_FamePoint); NotifyLevel(level); } private void ApplyFame(int fame) { _fameVersion++; UpdatePending(delegate(HudAuthoritativeState state) { state.Fame = fame; }); ApplyValues(CPlayerData.m_CoinAmountDouble, CPlayerData.m_CoinAmount, CPlayerData.m_ShopExpPoint, CPlayerData.m_ShopLevel, fame); } private void ApplyContribution(HudContributionKind kind, float value) { HudAuthoritativeState state = Capture(); switch (kind) { case HudContributionKind.AddCoin: state.CoinDisplay += value; state.Coins += value; RoundCoins(ref state); break; case HudContributionKind.ReduceCoin: state.CoinDisplay -= value; state.Coins -= value; RoundCoins(ref state); break; case HudContributionKind.AddShopExperience: AddExperience(state, value); break; case HudContributionKind.AddFame: state.Fame = Mathf.Clamp(state.Fame + Mathf.RoundToInt(value), 0, 99999999); break; } ApplyValues(state.Coins, state.CoinDisplay, state.Experience, state.Level, state.Fame); } private void ApplyValues(double coins, float coinDisplay, int experience, int level, int fame) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown _applyingSnapshot++; try { CPlayerData.m_CoinAmountDouble = coins; CPlayerData.m_CoinAmount = coinDisplay; CPlayerData.m_ShopExpPoint = experience; CPlayerData.m_ShopLevel = level; CPlayerData.m_FamePoint = fame; CEventManager.QueueEvent((CEvent)new CEventPlayer_SetCoin(coinDisplay, coins)); CEventManager.QueueEvent((CEvent)new CEventPlayer_SetShopExp(experience)); CEventManager.QueueEvent((CEvent)new CEventPlayer_SetFame(fame)); } finally { _applyingSnapshot--; } } private void Restore(HudAuthoritativeState state) { ApplyValues(state.Coins, state.CoinDisplay, state.Experience, state.Level, state.Fame); } private void NoteLocalShopLevel() { int shopLevel = CPlayerData.m_ShopLevel; if (!_hasAuthoritativeLevel || shopLevel > _highestNotifiedLevel) { _highestNotifiedLevel = shopLevel; _hasAuthoritativeLevel = true; } } private void NotifyLevel(int level) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (!_hasAuthoritativeLevel) { _highestNotifiedLevel = level; _hasAuthoritativeLevel = true; return; } if (level < _highestNotifiedLevel) { _highestNotifiedLevel = level; return; } for (int i = _highestNotifiedLevel + 1; i <= level; i++) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ShopLeveledUp(i)); } if (level > _highestNotifiedLevel) { _highestNotifiedLevel = level; } } private void UpdatePending(Action update) { if (_pending != null) { update(_pending); } } private HudAuthoritativeState Capture() { return new HudAuthoritativeState { Coins = CPlayerData.m_CoinAmountDouble, CoinDisplay = CPlayerData.m_CoinAmount, Experience = CPlayerData.m_ShopExpPoint, Level = CPlayerData.m_ShopLevel, Fame = CPlayerData.m_FamePoint }; } private static HudAuthoritativeState Clone(HudAuthoritativeState state) { return new HudAuthoritativeState { Coins = state.Coins, CoinDisplay = state.CoinDisplay, Experience = state.Experience, Level = state.Level, Fame = state.Fame }; } private static void RoundCoins(ref HudAuthoritativeState state) { float currencyConversionRate = GameInstance.GetCurrencyConversionRate(); state.Coins = Math.Round(state.Coins, (currencyConversionRate > 1f) ? 3 : 2, MidpointRounding.AwayFromZero); if (state.CoinDisplay < -100000000f) { state.CoinDisplay = 0f; state.Coins = 0.0; } } private static void AddExperience(HudAuthoritativeState state, float value) { state.Experience += Mathf.RoundToInt(value); while (state.Experience >= CPlayerData.GetExpRequiredToLevelUp()) { state.Experience -= CPlayerData.GetExpRequiredToLevelUp(); state.Level++; } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _pending = null; _applyingSnapshot = 0; _pendingContributions.Clear(); _highestNotifiedLevel = -1; _hasAuthoritativeLevel = false; _gameUi = null; HudPresentationState.Clear(); SceneManager.sceneLoaded -= OnSceneLoaded; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if ((Object)(object)_active == (Object)(object)this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class HudHostBehaviour : CoopBehaviour { private readonly struct PendingContribution { internal readonly HudContributionKind Kind; internal readonly Guid PredictionId; internal PendingContribution(HudContributionKind kind, Guid predictionId) { Kind = kind; PredictionId = predictionId; } } [HarmonyPatch(typeof(CEventManager), "QueueEvent")] private static class EventQueuePatch { [HarmonyPrefix] private static void Prefix(CEvent evt) { HudHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._shutdown && active._context != null && active._context.InGame() && TryGetContributionKind(evt, out var kind)) { active._contributionsByEvent[evt] = new PendingContribution(kind, active._pendingPredictionId); } } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnAddCoin")] private static class AddCoinPatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_AddCoin __0) { _active?.PublishContribution(HudContributionKind.AddCoin, (CEvent)(object)__0); } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnReduceCoin")] private static class ReduceCoinPatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_ReduceCoin __0) { _active?.PublishContribution(HudContributionKind.ReduceCoin, (CEvent)(object)__0); } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnAddShopExp")] private static class AddShopExperiencePatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_AddShopExp __0) { _active?.PublishContribution(HudContributionKind.AddShopExperience, (CEvent)(object)__0); } } [HarmonyPatch(typeof(CPlayerData), "CPlayer_OnAddFame")] private static class AddFamePatch { [HarmonyPostfix] private static void Postfix(CEventPlayer_AddFame __0) { _active?.PublishContribution(HudContributionKind.AddFame, (CEvent)(object)__0); } } private const float MaxContribution = 100000000f; private const int MaxProgressContribution = 100000000; private static HudHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private Guid _pendingPredictionId; private readonly Dictionary _contributionsByEvent = new Dictionary(); private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.hud.host"); _harmony.CreateClassProcessor(typeof(EventQueuePatch)).Patch(); _harmony.CreateClassProcessor(typeof(AddCoinPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ReduceCoinPatch)).Patch(); _harmony.CreateClassProcessor(typeof(AddShopExperiencePatch)).Patch(); _harmony.CreateClassProcessor(typeof(AddFamePatch)).Patch(); } catch (Exception arg) { CoopPlugin.Log.LogError((object)$"HUD host initialization failed: {arg}"); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void Update() { if (!_shutdown) { HudPresentationState.Tick(Time.deltaTime); } } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { if (!_shutdown && connection != null) { SendBaseline(connection.Id); } } [MessageHandler(typeof(HudContributionIntent))] private void HandleContribution(MessageContext context, HudContributionIntent message) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown if (!IsAuthenticatedSender(context) || message == null) { return; } if (!TryValidateContribution(message)) { Reject(context, message.PredictionId); return; } switch (message.Kind) { case HudContributionKind.AddCoin: QueueContribution(message.PredictionId, (CEvent)new CEventPlayer_AddCoin(message.Value, false)); break; case HudContributionKind.ReduceCoin: { if (!EconomyAuthority.TryReserveHostSpend(message.Value, out var reservation)) { Reject(context, message.PredictionId); _pendingPredictionId = Guid.Empty; break; } _pendingPredictionId = message.PredictionId; bool flag; try { flag = EconomyAuthority.QueueHostSpend(reservation); } finally { _pendingPredictionId = Guid.Empty; } if (!flag) { reservation.Dispose(); Reject(context, message.PredictionId); } break; } case HudContributionKind.AddShopExperience: QueueContribution(message.PredictionId, (CEvent)new CEventPlayer_AddShopExp((int)message.Value, false)); break; case HudContributionKind.AddFame: QueueContribution(message.PredictionId, (CEvent)new CEventPlayer_AddFame((int)message.Value, false)); break; } } private bool IsAuthenticatedSender(MessageContext context) { if (!_shutdown && _context.InGame() && context?.Connection != null) { return context.IsAuthenticated; } return false; } private void Reject(MessageContext context, Guid predictionId) { if (predictionId != Guid.Empty && context?.Connection != null) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, predictionId); } } private static bool TryValidateContribution(HudContributionIntent message) { if (message == null || !Enum.IsDefined(typeof(HudContributionKind), message.Kind) || float.IsNaN(message.Value) || float.IsInfinity(message.Value) || message.Value <= 0f || message.Value > 100000000f) { return false; } if (message.Kind != HudContributionKind.AddShopExperience && message.Kind != HudContributionKind.AddFame) { return true; } if (message.Value <= 100000000f) { return message.Value == (float)(int)message.Value; } return false; } private void QueueContribution(Guid predictionId, CEvent contribution) { _pendingPredictionId = predictionId; try { CEventManager.QueueEvent(contribution); } finally { _pendingPredictionId = Guid.Empty; } } private void PublishContribution(HudContributionKind kind, CEvent evt) { if (evt == null || !_contributionsByEvent.TryGetValue(evt, out var value)) { return; } _contributionsByEvent.Remove(evt); if (!_shutdown && _context.InGame()) { if (value.Kind != kind) { throw new InvalidOperationException("HUD contribution event kind is inconsistent."); } HudAuthoritativeState hudAuthoritativeState = Snapshot(); switch (kind) { case HudContributionKind.AddCoin: case HudContributionKind.ReduceCoin: _context.Broadcast((INetMessage)(object)new HudWalletDeltaMessage { PredictionId = value.PredictionId, Coins = hudAuthoritativeState.Coins, CoinDisplay = hudAuthoritativeState.CoinDisplay }); break; case HudContributionKind.AddShopExperience: _context.Broadcast((INetMessage)(object)new HudProgressDeltaMessage { PredictionId = value.PredictionId, Experience = hudAuthoritativeState.Experience, Level = hudAuthoritativeState.Level }); break; case HudContributionKind.AddFame: _context.Broadcast((INetMessage)(object)new HudFameDeltaMessage { PredictionId = value.PredictionId, Fame = hudAuthoritativeState.Fame }); break; } } } private static bool TryGetContributionKind(CEvent evt, out HudContributionKind kind) { if (evt is CEventPlayer_AddCoin) { kind = HudContributionKind.AddCoin; return true; } if (evt is CEventPlayer_ReduceCoin) { kind = HudContributionKind.ReduceCoin; return true; } if (evt is CEventPlayer_AddShopExp) { kind = HudContributionKind.AddShopExperience; return true; } if (evt is CEventPlayer_AddFame) { kind = HudContributionKind.AddFame; return true; } kind = (HudContributionKind)0; return false; } private void SendBaseline(int connectionId) { HudAuthoritativeState arg = Snapshot(); _context.Send(connectionId, (INetMessage)(object)arg); } private static HudAuthoritativeState Snapshot() { return new HudAuthoritativeState { Coins = CPlayerData.m_CoinAmountDouble, CoinDisplay = CPlayerData.m_CoinAmount, Experience = CPlayerData.m_ShopExpPoint, Level = CPlayerData.m_ShopLevel, Fame = CPlayerData.m_FamePoint }; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _pendingPredictionId = Guid.Empty; _contributionsByEvent.Clear(); HudPresentationState.Clear(); _context = null; } } private void OnDestroy() { Shutdown(); } } public enum HudContributionKind : byte { AddCoin = 1, ReduceCoin, AddShopExperience, AddFame } [NetworkMessage] public sealed class HudContributionIntent : IPredictedMessage, INetMessage { public HudContributionKind Kind; public float Value; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class HudAuthoritativeState : INetMessage { public double Coins; public float CoinDisplay; public int Experience; public int Level; public int Fame; } [NetworkMessage] public sealed class HudWalletDeltaMessage : IPredictedMessage, INetMessage { public double Coins; public float CoinDisplay; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class HudProgressDeltaMessage : IPredictedMessage, INetMessage { public int Experience; public int Level; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class HudFameDeltaMessage : IPredictedMessage, INetMessage { public int Fame; public Guid PredictionId { get; set; } } internal static class HudPresentationState { private const float DefaultToastSeconds = 8f; private static string _toastText = ""; private static float _toastRemaining; private static string _hostTimeText = ""; private static bool _nextDayWaitActive; private static string _nextDayWaitText = ""; private static bool _endOfDayPendingActive; private static string _endOfDayPendingText = ""; public static string ToastText => _toastText; public static float ToastRemaining => _toastRemaining; public static string HostTimeText => _hostTimeText; public static bool NextDayWaitActive => _nextDayWaitActive; public static string NextDayWaitText => _nextDayWaitText; public static bool EndOfDayPendingActive => _endOfDayPendingActive; public static string EndOfDayPendingText => _endOfDayPendingText; public static bool HasToast { get { if (!string.IsNullOrEmpty(_toastText)) { return _toastRemaining > 0f; } return false; } } internal static void SetToast(string text, float seconds = 8f) { _toastText = text ?? ""; _toastRemaining = (string.IsNullOrEmpty(_toastText) ? 0f : Math.Max(0f, seconds)); if (_toastRemaining <= 0f) { _toastText = ""; } } internal static void SetStatusLine(string text, float seconds) { SetToast(text, seconds); } internal static void SetHostTime(int day, int hour, int minute) { _hostTimeText = $"Day {day + 1} {hour:00}:{minute:00}"; } internal static void SetHostTime(string text) { _hostTimeText = text ?? ""; } internal static void SetNextDayWait(bool active, string text) { _nextDayWaitActive = active && !string.IsNullOrEmpty(text); _nextDayWaitText = (_nextDayWaitActive ? text : ""); } internal static void SetEndOfDayPending(bool active, string text) { _endOfDayPendingActive = active && !string.IsNullOrEmpty(text); _endOfDayPendingText = (_endOfDayPendingActive ? text : ""); } internal static void Tick(float deltaTime) { if (!(_toastRemaining <= 0f)) { _toastRemaining = Math.Max(0f, _toastRemaining - Math.Max(0f, deltaTime)); if (_toastRemaining <= 0f) { _toastText = ""; } } } internal static void Clear() { _toastText = ""; _toastRemaining = 0f; _hostTimeText = ""; _nextDayWaitActive = false; _nextDayWaitText = ""; _endOfDayPendingActive = false; _endOfDayPendingText = ""; } } } namespace CardShopCoop.Modules.Grading { public static class GradingApi { public static bool Present => GradingInterop.Present; public static int Encoded(CardData card) { return GradingInterop.Encoded(card); } public static int Actual(int encoded) { return GradingInterop.Actual(encoded); } public static bool Remember(CardData card) { return GradingInterop.Remember(card); } public static void OnCardRemovalRefused(CardData card, int amount) { GradingClientBehaviour.OnCardRemovalRefused(card, amount); } public static void Reset() { GradingInterop.Reset(); } } [ClientBehaviour] public sealed class GradingClientBehaviour : CoopBehaviour { internal struct SubmissionCapture { internal bool Armed; internal GradeCardSubmitSet Previous; internal GradeCardSubmitSet Job; internal Guid JobId; internal List Cards; internal int ServiceLevel; internal int Cap; internal GradedCardSubmitSelectScreen Screen; internal float SupplyCostBefore; } private static readonly FieldInfo FiShowingAlpha = ReflectionSurface.OptionalField(typeof(GradedCardSubmitSelectScreen), "m_IsShowingCanvasGrpAlpha"); private static readonly FieldInfo FiHidingAlpha = ReflectionSurface.OptionalField(typeof(GradedCardSubmitSelectScreen), "m_IsHidingCanvasGrpAlpha"); private static GradingClientBehaviour _active; private readonly Dictionary _jobIds = new Dictionary(); private readonly Dictionary _localJobSets = new Dictionary(); private CoopRuntimeContext _context; private Harmony _harmony; private GradingStateMessage _pendingState; private readonly Dictionary _pendingDeltas = new Dictionary(); private bool _contentReady; private bool _shutdown; private GradeCardWebsiteUIScreen _website; private GradedCardSubmitSelectScreen _submitScreen; internal static GradingClientBehaviour Active => _active; private void OnEnable() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown if (_shutdown || _context != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _active = this; _context.Messages.RegisterAttributedHandlers(this); flag = true; PredictionApi.PredictionRetired += RetireLocalJob; _harmony = new Harmony("dev.meepen.cardshopcoop.grading.client"); GradingPatches.ApplyClient(_harmony); CEventManager.AddListener((EventDelegate)OnReady); } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; PredictionApi.PredictionRetired -= RetireLocalJob; CEventManager.RemoveListener((EventDelegate)OnReady); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } throw; } } private void OnReady(CEventPlayer_GameDataFinishLoaded _) { _contentReady = true; ApplyPending(); } private void RetireLocalJob(Guid predictionId) { _localJobSets.Remove(predictionId); } [MessageHandler(typeof(GradingStateMessage))] private void HandleState(MessageContext context, GradingStateMessage message) { if (!_shutdown) { _pendingState = message; ApplyPending(); } } internal void CaptureSubmission(GradedCardSubmitSelectScreen screen, out SubmissionCapture state) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) state = default(SubmissionCapture); if (_shutdown) { return; } CoopRuntimeContext context = _context; if (context == null || !context.InGame() || IsShowingOrHiding(screen)) { return; } GradeCardSubmitSet currentGradeCardSubmitSet = CPlayerData.m_CurrentGradeCardSubmitSet; if (currentGradeCardSubmitSet?.m_CardDataList == null) { return; } List list = new List(); for (int i = 0; i < currentGradeCardSubmitSet.m_CardDataList.Count; i++) { CardData val = currentGradeCardSubmitSet.m_CardDataList[i]; if (val != null && (int)val.monsterType != 0) { list.Add(Clone(val)); } } if (list.Count != 0) { state.Armed = true; state.Previous = CloneSet(currentGradeCardSubmitSet); state.Job = currentGradeCardSubmitSet; state.JobId = Guid.NewGuid(); state.Cards = list; state.ServiceLevel = currentGradeCardSubmitSet.m_ServiceLevel; state.Cap = GradingInterop.MaxSubmitSlots; state.Screen = screen; state.SupplyCostBefore = CPlayerData.m_GameReportDataCollectPermanent.supplyCost; _submitScreen = screen; EconomyActionScope.Enter(); } } internal void ObserveSubmission(SubmissionCapture state) { if (!state.Armed || _shutdown) { return; } CoopRuntimeContext context = _context; if (context == null || !context.InGame()) { return; } List gradeCardInProgressList = CPlayerData.m_GradeCardInProgressList; GradeCardSubmitSet jobSet = state.Job; if (gradeCardInProgressList == null || jobSet == null || !gradeCardInProgressList.Contains(jobSet)) { return; } float spent = state.SupplyCostBefore - CPlayerData.m_GameReportDataCollectPermanent.supplyCost; PredictionApi.Predict("grading", delegate(Guid predictionId) { _localJobSets[predictionId] = jobSet; _jobIds[jobSet] = state.JobId; try { _context.Send(1, (INetMessage)(object)new GradingOpMessage { PredictionId = predictionId, JobId = state.JobId, ServiceLevel = (byte)state.ServiceLevel, CompanyId = (byte)GradingInterop.CurrentCompanyId, Cards = state.Cards }); } catch { _localJobSets.Remove(predictionId); _jobIds.Remove(jobSet); throw; } }, delegate { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown List gradeCardInProgressList2 = CPlayerData.m_GradeCardInProgressList; if (jobSet != null && gradeCardInProgressList2 != null && !gradeCardInProgressList2.Contains(jobSet)) { gradeCardInProgressList2.Add(jobSet); } ApplyLocalSubmission(state.ServiceLevel, state.Cap, state.Screen); if (spent > 0.0001f) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin(spent, false)); } }, delegate { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown RemoveLocalJob(jobSet); CPlayerData.m_CurrentGradeCardSubmitSet = state.Previous; if (spent > 0.0001f) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin(spent, true)); } RefreshWebsite(); }, ReopenSubmissionScreen); } private void RemoveLocalJob(GradeCardSubmitSet set) { if (set != null) { _jobIds.Remove(set); CPlayerData.m_GradeCardInProgressList?.Remove(set); } } private void ApplyPending() { if (_shutdown || !_contentReady || !_context.InGame()) { return; } IGradingWorldBridge current = GradingWorldBridge.Current; if (current == null || !current.SceneReady) { return; } if (_pendingState != null) { GradingStateMessage pendingState = _pendingState; ApplyState(pendingState); _pendingState = null; } if (_pendingDeltas.Count <= 0) { return; } List> list = new List>(_pendingDeltas); for (int i = 0; i < list.Count; i++) { if (ApplyDelta(list[i].Value)) { _pendingDeltas.Remove(list[i].Key); } } } [MessageHandler(typeof(GradingJobDeltaMessage))] private void HandleDelta(MessageContext context, GradingJobDeltaMessage message) { if (_shutdown) { return; } if (_contentReady && _context.InGame()) { IGradingWorldBridge current = GradingWorldBridge.Current; if (current != null && current.SceneReady) { ApplyDelta(message); return; } } DeferDelta(message); } private void ApplyState(GradingStateMessage message) { Dictionary dictionary = new Dictionary(); foreach (KeyValuePair jobId in _jobIds) { if (jobId.Key != null) { dictionary[jobId.Value] = jobId.Key; } } List list = new List(message.Sets.Count); Dictionary dictionary2 = new Dictionary(); for (int i = 0; i < message.Sets.Count; i++) { GradingSetDto gradingSetDto = message.Sets[i]; GradeCardSubmitSet value; GradeCardSubmitSet val = ((dictionary.TryGetValue(gradingSetDto.Id, out value) && value != null) ? ApplySetDto(value, gradingSetDto) : ToGameSet(gradingSetDto)); list.Add(val); dictionary2[val] = gradingSetDto.Id; } foreach (KeyValuePair jobId2 in _jobIds) { if (jobId2.Key != null && !dictionary2.ContainsValue(jobId2.Value)) { list.Add(jobId2.Key); dictionary2[jobId2.Key] = jobId2.Value; } } _jobIds.Clear(); foreach (KeyValuePair item in dictionary2) { _jobIds[item.Key] = item.Value; } CPlayerData.m_GradeCardInProgressList = list; RefreshWebsite(); } private static GradeCardSubmitSet ApplySetDto(GradeCardSubmitSet set, GradingSetDto dto) { set.m_ServiceLevel = dto.ServiceLevel; set.m_DayPassed = dto.DayPassed; set.m_MinutePassed = dto.MinutePassed; List list = set.m_CardDataList ?? (set.m_CardDataList = new List(dto.Cards.Count)); list.Clear(); for (int i = 0; i < dto.Cards.Count; i++) { list.Add(Clone(dto.Cards[i])); } return set; } private bool ApplyDelta(GradingJobDeltaMessage message) { if (message.PredictionId != Guid.Empty && _localJobSets.TryGetValue(message.PredictionId, out var value)) { _localJobSets.Remove(message.PredictionId); if (value != null) { _jobIds[value] = message.Id; } PredictionApi.Ack(message.PredictionId); _submitScreen = null; RefreshWebsite(); return true; } PredictionApi.AckOrApply(message.PredictionId, delegate { List list = CPlayerData.m_GradeCardInProgressList ?? new List(); int num = FindSet(list, message.Id); if (message.Removed) { if (num >= 0) { _jobIds.Remove(list[num]); list.RemoveAt(num); } } else { GradeCardSubmitSet val = ToGameSet(message); if (num >= 0) { _jobIds.Remove(list[num]); list[num] = val; } else { list.Add(val); } _jobIds[val] = message.Id; } CPlayerData.m_GradeCardInProgressList = list; RefreshWebsite(); }); return true; } private void DeferDelta(GradingJobDeltaMessage message) { if (_pendingDeltas.TryGetValue(message.Id, out var value)) { _localJobSets.Remove(value.PredictionId); PredictionApi.Ack(value.PredictionId); } _pendingDeltas[message.Id] = message; } private int FindSet(List list, Guid id) { for (int i = 0; i < list.Count; i++) { if (_jobIds.TryGetValue(list[i], out var value) && value == id) { return i; } } return -1; } private void ApplyLocalSubmission(int serviceLevel, int cap, GradedCardSubmitSelectScreen screen) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown GradeCardSubmitSet val = new GradeCardSubmitSet { m_ServiceLevel = serviceLevel, m_CardDataList = new List(cap) }; for (int i = 0; i < cap; i++) { val.m_CardDataList.Add(new CardData()); } CPlayerData.m_CurrentGradeCardSubmitSet = val; if (screen != null) { ((UIScreenBase)screen).CloseScreen(); } RefreshWebsite(); SetStatus("cards sent for grading - they mature on the host's days", 4f); SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); } internal static void OnCardRemovalRefused(CardData card, int amount) { GradingClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && card != null && amount > 0) { active.StripRefusedSelection(card, amount); } } private void StripRefusedSelection(CardData card, int amount) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown GradeCardSubmitSet currentGradeCardSubmitSet = CPlayerData.m_CurrentGradeCardSubmitSet; if (currentGradeCardSubmitSet?.m_CardDataList == null) { return; } int num = 0; for (int i = 0; i < currentGradeCardSubmitSet.m_CardDataList.Count; i++) { if (num >= amount) { break; } CardData val = currentGradeCardSubmitSet.m_CardDataList[i]; if (val != null && (int)val.monsterType != 0 && SameSelectionCard(val, card)) { currentGradeCardSubmitSet.m_CardDataList[i] = new CardData(); num++; UpdateSubmitPanel(i, currentGradeCardSubmitSet.m_CardDataList[i]); } } if (num != 0) { CoopPlugin.Log.LogWarning((object)("grading: the host refused to reserve " + num + " selected card(s) (" + GradingInterop.DescribeCard(card) + "); removed them from the submission selection")); SetStatus("the host could not reserve those cards - they were returned to the album", 4f); RefreshWebsite(); } } private static void UpdateSubmitPanel(int index, CardData card) { List list = SceneRef.Get()?.m_GradeCardPanelUIList; if (list != null && index >= 0 && index < list.Count) { list[index].UpdateCardUI(card); } } private static bool SameSelectionCard(CardData left, CardData right) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (left != null && right != null && left.expansionType == right.expansionType && left.monsterType == right.monsterType && left.borderType == right.borderType && left.isFoil == right.isFoil && left.isDestiny == right.isDestiny && left.isChampionCard == right.isChampionCard && left.gradedCardIndex == right.gradedCardIndex) { return GradingInterop.Encoded(left) == GradingInterop.Encoded(right); } return false; } private void ReopenSubmissionScreen() { GradedCardSubmitSelectScreen submitScreen = _submitScreen; if (!((Object)(object)submitScreen == (Object)null) && !((UIScreenBase)submitScreen).IsScreenOpened()) { if (_website == null) { _website = SceneRef.Get(); } if ((Object)(object)_website != (Object)null && ((Component)_website).gameObject.activeInHierarchy && CPlayerData.m_GradeCardInProgressList != null && CPlayerData.m_GradeCardInProgressList.Count < 4) { _website.OnPressNewSubmissionButton(); } else { ((UIScreenBase)submitScreen).OpenScreen(); } } } private static GradeCardSubmitSet CloneSet(GradeCardSubmitSet source) { //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_0011: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown GradeCardSubmitSet val = new GradeCardSubmitSet { m_ServiceLevel = source.m_ServiceLevel, m_DayPassed = source.m_DayPassed, m_MinutePassed = source.m_MinutePassed, m_CardDataList = new List(source.m_CardDataList?.Count ?? 0) }; if (source.m_CardDataList != null) { for (int i = 0; i < source.m_CardDataList.Count; i++) { val.m_CardDataList.Add(Clone(source.m_CardDataList[i])); } } return val; } private static GradeCardSubmitSet ToGameSet(GradingSetDto dto) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown return ApplySetDto(new GradeCardSubmitSet(), dto); } private static GradeCardSubmitSet ToGameSet(GradingJobDeltaMessage dto) { //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_0011: 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown GradeCardSubmitSet val = new GradeCardSubmitSet { m_ServiceLevel = dto.ServiceLevel, m_DayPassed = dto.DayPassed, m_MinutePassed = dto.MinutePassed, m_CardDataList = new List(dto.Cards.Count) }; for (int i = 0; i < dto.Cards.Count; i++) { val.m_CardDataList.Add(Clone(dto.Cards[i])); } return val; } private static bool IsShowingOrHiding(GradedCardSubmitSelectScreen screen) { if ((Object)(object)screen == (Object)null || FiShowingAlpha == null || FiHidingAlpha == null) { return true; } if (!(bool)FiShowingAlpha.GetValue(screen)) { return (bool)FiHidingAlpha.GetValue(screen); } return true; } private void RefreshWebsite() { if (_website == null) { _website = SceneRef.Get(); } if ((Object)(object)_website != (Object)null && ((Component)_website).gameObject.activeInHierarchy) { _website.UpdateSubmissionProgressPanelUI(); } } private void SetStatus(string text, float seconds) { _context?.SetStatusLine?.Invoke(text, seconds); } private static CardData Clone(CardData card) { //IL_0003: 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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0027: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (card != null) { return new CardData { expansionType = card.expansionType, monsterType = card.monsterType, borderType = card.borderType, isFoil = card.isFoil, isDestiny = card.isDestiny, isChampionCard = card.isChampionCard, isNew = card.isNew, cardGrade = GradingInterop.Encoded(card), gradedCardIndex = card.gradedCardIndex }; } return null; } internal static void InventoryReset() { if (!((Object)(object)_active == (Object)null)) { _active._pendingState = null; _active.RetainPendingLocalJobs(); _active.ResetPendingDeltas(); _active._contentReady = false; _active._jobIds.Clear(); _active._submitScreen = null; } } private void RetainPendingLocalJobs() { List list = new List(); foreach (KeyValuePair localJobSet in _localJobSets) { if (!PredictionApi.IsPending(localJobSet.Key)) { list.Add(localJobSet.Key); } } for (int i = 0; i < list.Count; i++) { _localJobSets.Remove(list[i]); } } private void ResetPendingDeltas() { List> list = new List>(); foreach (KeyValuePair pendingDelta in _pendingDeltas) { if (pendingDelta.Value.PredictionId != Guid.Empty && _localJobSets.ContainsKey(pendingDelta.Value.PredictionId)) { list.Add(pendingDelta); } else { PredictionApi.Ack(pendingDelta.Value.PredictionId); } } _pendingDeltas.Clear(); for (int i = 0; i < list.Count; i++) { _pendingDeltas[list[i].Key] = list[i].Value; } } private void ClearPendingDeltas() { foreach (GradingJobDeltaMessage value in _pendingDeltas.Values) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas.Clear(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); PredictionApi.PredictionRetired -= RetireLocalJob; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); _pendingState = null; ClearPendingDeltas(); _jobIds.Clear(); _localJobSets.Clear(); _submitScreen = null; GradingInterop.Reset(); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class GradingHostBehaviour : CoopBehaviour { private sealed class GoJobSnapshot { internal int ServiceLevel; internal List Cards; internal int CompanyId; internal bool UseCheats; internal int JobId; internal bool RegisterAttempted; internal bool PreRollAttempted; } private const int VanillaSlots = 8; private static GradingHostBehaviour _active; private readonly Dictionary _setIds = new Dictionary(); private readonly HashSet _joined = new HashSet(); private readonly HashSet _baselinePending = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; internal static GradingHostBehaviour Active => _active; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _context != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _active = this; _context.Messages.RegisterAttributedHandlers(this); flag = true; _harmony = new Harmony("dev.meepen.cardshopcoop.grading.host"); GradingPatches.ApplyHost(_harmony); CEventManager.AddListener((EventDelegate)OnReady); } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } throw; } } private void OnReady(CEventPlayer_GameDataFinishLoaded _) { SendPendingBaselines(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection peer) { if (peer != null) { _joined.Add(peer.Id); if (!FullUpdate(peer.Id)) { _baselinePending.Add(peer.Id); } } } [OnClientDisconnected] private void ForgetPeer(PeerConnection peer, DisconnectInfo _) { if (peer != null) { _joined.Remove(peer.Id); _baselinePending.Remove(peer.Id); } } [MessageHandler(typeof(GradingOpMessage))] private void HandleSubmission(MessageContext context, GradingOpMessage message) { if (context?.Connection != null) { HostApply(message, context.Connection.Id); } } internal static void InventoryReset() { _active?.ResetInventory(); } private void ResetInventory() { foreach (KeyValuePair item in new Dictionary(_setIds)) { BroadcastJobDelta(Guid.Empty, item.Value, null, removed: true); } _setIds.Clear(); GradingInterop.Reset(); } internal bool FullUpdate(int connectionId) { if (!_shutdown && _context.InGame()) { IGradingWorldBridge current = GradingWorldBridge.Current; if (current != null && current.SceneReady) { List gradeCardInProgressList = CPlayerData.m_GradeCardInProgressList; if (gradeCardInProgressList == null) { return false; } _context.Send(connectionId, (INetMessage)(object)BuildState(gradeCardInProgressList)); _baselinePending.Remove(connectionId); return true; } } return false; } private void SendPendingBaselines() { foreach (int item in new List(_baselinePending)) { if (!_joined.Contains(item)) { _baselinePending.Remove(item); } else { FullUpdate(item); } } } internal void PushCurrentSets() { if (_shutdown || !_context.InGame()) { return; } List gradeCardInProgressList = CPlayerData.m_GradeCardInProgressList; HashSet hashSet = new HashSet(); if (gradeCardInProgressList != null) { for (int i = 0; i < gradeCardInProgressList.Count; i++) { GradeCardSubmitSet val = gradeCardInProgressList[i]; if (val != null) { Guid setId = GetSetId(val); hashSet.Add(setId); BroadcastJobDelta(Guid.Empty, setId, val, removed: false); } } } List list = new List(); foreach (KeyValuePair setId2 in _setIds) { if (!hashSet.Contains(setId2.Value)) { BroadcastJobDelta(Guid.Empty, setId2.Value, null, removed: true); list.Add(setId2.Key); } } for (int j = 0; j < list.Count; j++) { _setIds.Remove(list[j]); } } private void HostApply(GradingOpMessage message, int senderConnection) { //IL_04a9: Unknown result type (might be due to invalid IL or missing references) //IL_04b3: Expected O, but got Unknown //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Expected O, but got Unknown //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03d2: Expected O, but got Unknown IGradingWorldBridge current = GradingWorldBridge.Current; List list = message?.Cards; if (message == null || list == null || list.Count == 0) { Reject(senderConnection, message?.PredictionId ?? Guid.Empty, list, "grading-submit-cards", current); return; } if (message.JobId == Guid.Empty || _setIds.ContainsValue(message.JobId)) { Reject(senderConnection, message.PredictionId, list, "grading-submit-job", current); return; } if (current == null || !current.SceneReady) { Reject(senderConnection, message.PredictionId, list, "grading-submit-not-ready", current); return; } int maxSubmitSlots = GradingInterop.MaxSubmitSlots; if (list.Count > maxSubmitSlots) { Reject(senderConnection, message.PredictionId, list, "grading-submit-cap", current); return; } for (int i = 0; i < list.Count; i++) { if (list[i] == null) { Reject(senderConnection, message.PredictionId, list, "grading-submit-card", current); return; } if (!GradingInterop.ValidSubmissionCard(list[i], out var reason) || !current.CardSetInstalled(list[i])) { CoopPlugin.Log.LogWarning((object)("grading submission card " + i + "/" + list.Count + " refused for connection " + senderConnection + ": " + (reason ?? "card set is not installed on the host") + " - " + GradingInterop.DescribeCard(list[i]))); Reject(senderConnection, message.PredictionId, list, "grading-submit-card", current); return; } } if (CPlayerData.m_GradeCardInProgressList == null || CPlayerData.m_GradeCardInProgressList.Count >= 4) { Reject(senderConnection, message.PredictionId, list, "grading-submit-slots", current); return; } byte serviceLevel = message.ServiceLevel; if (!current.TryGetVanillaServiceCost(serviceLevel, list.Count, out var total) || float.IsNaN(total) || float.IsInfinity(total) || total < 0f) { Reject(senderConnection, message.PredictionId, list, "grading-submit-fee", current); return; } if (GradingInterop.Present && (!GradingInterop.GoCompatible || !GradingInterop.IsAllowedCompany(message.CompanyId))) { Reject(senderConnection, message.PredictionId, list, "grading-submit-service", current); return; } if (!EconomyAuthority.TryReserveHostSpend(total, out var reservation)) { Reject(senderConnection, message.PredictionId, list, "grading-submit-wallet", current); return; } if (!current.TryReserveCards(senderConnection, list, out var reservation2, out var reason2) || reservation2 == null) { CoopPlugin.Log.LogWarning((object)("grading submission refused: " + reason2)); Reject(senderConnection, message.PredictionId, list, "grading-submit-ownership", current); return; } GradeCardSubmitSet val = null; float supplyCost = CPlayerData.m_GameReportDataCollect.supplyCost; float supplyCost2 = CPlayerData.m_GameReportDataCollectPermanent.supplyCost; bool flag = false; GoJobSnapshot snapshot = null; try { if (!EconomyAuthority.QueueHostSpend(reservation)) { throw new InvalidOperationException("grading wallet debit could not be admitted"); } PriceChangeManager.AddTransaction(0f - total, (ETransactionType)11, (int)serviceLevel, 0, (CardData)null); flag = true; CPlayerData.m_GameReportDataCollect.supplyCost -= total; CPlayerData.m_GameReportDataCollectPermanent.supplyCost -= total; val = new GradeCardSubmitSet { m_ServiceLevel = serviceLevel, m_DayPassed = 0, m_MinutePassed = 0f, m_CardDataList = new List(Math.Max(list.Count, 8)) }; for (int j = 0; j < list.Count; j++) { val.m_CardDataList.Add(Clone(list[j])); } CPlayerData.m_GradeCardInProgressList.Add(val); _setIds[val] = message.JobId; if (GradingInterop.Present) { if (!TryEnrollGoJob(val, message.CompanyId, serviceLevel, out snapshot)) { throw new InvalidOperationException("Grading Overhaul rejected the service/company"); } } else { while (val.m_CardDataList.Count < 8) { val.m_CardDataList.Add(new CardData()); } } reservation2.Commit(); Guid jobId = message.JobId; BroadcastJobDelta(message.PredictionId, jobId, val, removed: false); GradeCardWebsiteUIScreen obj = SceneRef.Get(); if (obj != null) { obj.UpdateSubmissionProgressPanelUI(); } ManualLogSource log = CoopPlugin.Log; string text = senderConnection.ToString(); Guid guid = jobId; log.LogInfo((object)("grading submission accepted for connection " + text + " as set " + guid)); } catch (Exception ex) { try { reservation2.Rollback(); if (val != null) { CPlayerData.m_GradeCardInProgressList?.Remove(val); _setIds.Remove(val); } RollbackGoJob(val, snapshot); CPlayerData.m_GameReportDataCollect.supplyCost = supplyCost; CPlayerData.m_GameReportDataCollectPermanent.supplyCost = supplyCost2; if (flag) { PriceChangeManager.AddTransaction(total, (ETransactionType)11, (int)serviceLevel, 0, (CardData)null); } CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin(total, true)); } catch (Exception ex2) { CoopPlugin.Log.LogError((object)("grading local compensation failed: " + ex2)); } CoopPlugin.Log.LogError((object)("grading submission failed after local validation: " + ex)); Reject(senderConnection, message.PredictionId, list, "grading-submit-failure", current); } } private bool TryEnrollGoJob(GradeCardSubmitSet set, int companyId, int serviceLevel, out GoJobSnapshot snapshot) { snapshot = null; if (!GradingInterop.GoCompatible || !GradingInterop.IsAllowedCompany(companyId)) { return false; } bool hostUseCheatsWebsite = GradingInterop.HostUseCheatsWebsite; int num = GradingInterop.NextJobId(); int num2 = GradingInterop.EncodeServiceLevel(companyId, serviceLevel, num); if (num <= 0 || num2 <= 0 || !GradingInterop.TryDecodeServiceLevel(num2, out var companyId2, out var tier) || companyId2 != companyId || tier != serviceLevel) { return false; } snapshot = new GoJobSnapshot { ServiceLevel = set.m_ServiceLevel, Cards = CloneCards(set.m_CardDataList), CompanyId = companyId, UseCheats = hostUseCheatsWebsite, JobId = num, RegisterAttempted = true }; if (!GradingInterop.RegisterJobCompany(set, companyId, hostUseCheatsWebsite)) { return false; } set.m_ServiceLevel = num2; snapshot.PreRollAttempted = true; if (!GradingInterop.PreRollJob(set, companyId, hostUseCheatsWebsite, num)) { return false; } return true; } private static void RollbackGoJob(GradeCardSubmitSet set, GoJobSnapshot snapshot) { if (set != null && snapshot != null) { if (snapshot.PreRollAttempted && !GradingInterop.RollbackPreRoll(set, snapshot.CompanyId, snapshot.UseCheats, snapshot.JobId)) { CoopPlugin.Log.LogError((object)("Grading Overhaul pre-roll compensation was refused for job " + snapshot.JobId)); } if (snapshot.RegisterAttempted && !GradingInterop.RollbackJobCompany(set, snapshot.CompanyId, snapshot.UseCheats, snapshot.JobId)) { CoopPlugin.Log.LogError((object)("Grading Overhaul job enrollment compensation was refused for job " + snapshot.JobId)); } set.m_ServiceLevel = snapshot.ServiceLevel; for (int i = 0; i < Math.Min(set.m_CardDataList?.Count ?? 0, snapshot.Cards?.Count ?? 0); i++) { set.m_CardDataList[i].cardGrade = snapshot.Cards[i].cardGrade; set.m_CardDataList[i].gradedCardIndex = snapshot.Cards[i].gradedCardIndex; } } } private void Reject(int connectionId, Guid predictionId, List cards, string reason, IGradingWorldBridge bridge) { if (cards == null) { cards = new List(); } if (bridge != null) { for (int i = 0; i < cards.Count; i++) { if (cards[i] != null && !bridge.CardSetInstalled(cards[i])) { bridge.WarnRefused(cards[i], reason); } } } CoopPlugin.Log.LogWarning((object)("grading submission rejected for connection " + connectionId + ": " + reason)); if (predictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)_context, connectionId, predictionId); } } private Guid GetSetId(GradeCardSubmitSet set) { if (!_setIds.TryGetValue(set, out var value)) { value = Guid.NewGuid(); _setIds[set] = value; } return value; } private GradingStateMessage BuildState(List sets) { GradingStateMessage gradingStateMessage = new GradingStateMessage(); if (sets == null) { return gradingStateMessage; } for (int i = 0; i < sets.Count; i++) { GradeCardSubmitSet val = sets[i]; if (val != null) { GradingSetDto gradingSetDto = new GradingSetDto { Id = GetSetId(val), ServiceLevel = val.m_ServiceLevel, DayPassed = (byte)Mathf.Clamp(val.m_DayPassed, 0, 255), MinutePassed = val.m_MinutePassed }; List cardDataList = val.m_CardDataList; int num = Math.Min(cardDataList?.Count ?? 0, GradingInterop.MaxSubmitSlots); for (int j = 0; j < num; j++) { gradingSetDto.Cards.Add(Clone(cardDataList[j])); } gradingStateMessage.Sets.Add(gradingSetDto); } } return gradingStateMessage; } private void BroadcastJobDelta(Guid predictionId, Guid id, GradeCardSubmitSet set, bool removed) { if (_shutdown || !_context.InGame()) { return; } GradingJobDeltaMessage gradingJobDeltaMessage = new GradingJobDeltaMessage { PredictionId = predictionId, Id = id, Removed = removed }; if (set != null) { gradingJobDeltaMessage.ServiceLevel = set.m_ServiceLevel; gradingJobDeltaMessage.DayPassed = (byte)Mathf.Clamp(set.m_DayPassed, 0, 255); gradingJobDeltaMessage.MinutePassed = set.m_MinutePassed; List cardDataList = set.m_CardDataList; int num = Math.Min(cardDataList?.Count ?? 0, GradingInterop.MaxSubmitSlots); for (int i = 0; i < num; i++) { gradingJobDeltaMessage.Cards.Add(Clone(cardDataList[i])); } } _context.Broadcast((INetMessage)(object)gradingJobDeltaMessage); } private static List CloneCards(List cards) { List list = new List(cards?.Count ?? 0); if (cards == null) { return list; } for (int i = 0; i < cards.Count; i++) { list.Add(Clone(cards[i])); } return list; } private static CardData Clone(CardData card) { //IL_0003: 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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0027: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (card != null) { return new CardData { expansionType = card.expansionType, monsterType = card.monsterType, borderType = card.borderType, isFoil = card.isFoil, isDestiny = card.isDestiny, isChampionCard = card.isChampionCard, isNew = card.isNew, cardGrade = GradingInterop.Encoded(card), gradedCardIndex = card.gradedCardIndex }; } return null; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); _setIds.Clear(); _joined.Clear(); _baselinePending.Clear(); GradingInterop.Reset(); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class GradingInterop { internal const string GradingAssembly = "Grading Overhaul"; internal const int NoCompany = 255; internal const int VanillaSubmitSlots = 8; private const int GoSubmitSlotsFallback = 52; private static readonly Type TRegistry = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.EncodedGradeRegistry", "Grading Overhaul"); private static readonly Type THelper = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.Helper", "Grading Overhaul"); private static readonly Type TTheme = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.GradingWebsiteThemeController", "Grading Overhaul"); private static readonly Type TCompany = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.GradingCompany", "Grading Overhaul"); private static readonly Type TJobRegistry = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.GradingJobCompanyRegistry", "Grading Overhaul"); private static readonly Type TPreRoll = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.PreRollManager", "Grading Overhaul"); private static readonly Type TCodec = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.ServiceLevelCodec", "Grading Overhaul"); private static readonly Type TConfig = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.ConfigSettings", "Grading Overhaul"); private static readonly Type TSlotExpansion = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.GradingSlotExpansionPatches", "Grading Overhaul"); private static readonly Type TSaveManager = ReflectionSurface.OptionalType("TCGCardShopSimulator.GradingOverhaul.GradingOverhaulSaveManager", "Grading Overhaul"); private static readonly Type TDayStartPatch = ModParity.ResolveType("TCGCardShopSimulator.GradingOverhaul.CompanyStamp_RestockManager_OnDayStartedPatch", "Grading Overhaul"); private static readonly Type TSubmitValidatorPatch = ModParity.ResolveType("TCGCardShopSimulator.GradingOverhaul.GradingSubmit_CompanyValidation_Patch", "Grading Overhaul"); private static readonly MethodInfo MiRemember = ((TRegistry == null) ? null : ResolveExactMethod(TRegistry, "RememberForExternalMod", typeof(CardData), typeof(int))); private static readonly MethodInfo MiEncoded = ((TRegistry == null) ? null : ReflectionSurface.OptionalMethod(TRegistry, "GetEncodedOrCurrent", typeof(CardData))); private static readonly MethodInfo MiActual = ((THelper == null) ? null : ReflectionSurface.OptionalMethod(THelper, "GetActualGrade", typeof(int))); private static readonly MethodInfo MiDecodeFull = ((THelper == null || TCompany == null) ? null : ReflectionSurface.OptionalMethod(THelper, "DecodeGradeFull", typeof(int), TCompany.MakeByRefType(), typeof(int).MakeByRefType(), typeof(int).MakeByRefType())); private static readonly MethodInfo MiCheat = ((THelper == null) ? null : ReflectionSurface.OptionalMethod(THelper, "IsCheatFlagged", typeof(int))); private static readonly PropertyInfo PiCurrentCompany = ((TTheme == null) ? null : ReflectionSurface.OptionalProperty(TTheme, "CurrentWebsiteCompany")); private static readonly FieldInfo FiAllowedCompanies = ((TTheme == null) ? null : ReflectionSurface.OptionalField(TTheme, "AllowedCompanies")); private static readonly FieldInfo FiUseCheats = ((TConfig == null) ? null : ReflectionSurface.OptionalField(TConfig, "UseCheatsWebsite")); private static readonly MethodInfo MiOnJobSubmitted = ((TJobRegistry == null || TCompany == null) ? null : ResolveExactMethod(TJobRegistry, "OnJobSubmitted", typeof(GradeCardSubmitSet), TCompany, typeof(bool), TCompany)); private static readonly MethodInfo MiNextJobId = ((TPreRoll == null) ? null : ResolveExactMethod(TPreRoll, "GetNextJobId")); private static readonly MethodInfo MiPreRoll = ((TPreRoll == null || TCompany == null) ? null : ResolveExactMethod(TPreRoll, "PreRollOnSubmit", typeof(GradeCardSubmitSet), TCompany, typeof(bool), typeof(int))); private static readonly MethodInfo MiEncode = ((TCodec == null || TCompany == null) ? null : ReflectionSurface.OptionalMethod(TCodec, "Encode", TCompany, typeof(int), typeof(int))); private static readonly MethodInfo MiTryDecode = ((TCodec == null || TCompany == null) ? null : ReflectionSurface.OptionalMethod(TCodec, "TryDecode", typeof(int), TCompany.MakeByRefType(), typeof(int).MakeByRefType())); private static readonly MethodInfo MiHasBinding = ((TSaveManager == null || TCompany == null) ? null : ReflectionSurface.OptionalMethod(TSaveManager, "HasCertBinding", TCompany, typeof(int))); private static readonly MethodInfo MiIsBoundTo = ((TSaveManager == null || TCompany == null) ? null : ReflectionSurface.OptionalMethod(TSaveManager, "IsCertBoundToCard", TCompany, typeof(int), typeof(CardData))); private static readonly MethodInfo MiRollbackJobCompany = ((TJobRegistry == null || TCompany == null) ? null : ResolveExactMethod(TJobRegistry, "OnJobCancelled", typeof(GradeCardSubmitSet), TCompany, typeof(bool), typeof(int))); private static readonly MethodInfo MiRollbackPreRoll = ((TPreRoll == null || TCompany == null) ? null : ResolveExactMethod(TPreRoll, "RollbackOnSubmit", typeof(GradeCardSubmitSet), TCompany, typeof(bool), typeof(int))); private static readonly MethodInfo MiDayStart = ((TDayStartPatch == null) ? null : ResolveExactMethod(TDayStartPatch, "Prefix")); private static readonly MethodInfo MiSubmitValidator = ((TSubmitValidatorPatch == null) ? null : ResolveExactMethod(TSubmitValidatorPatch, "Prefix", typeof(GradedCardSubmitSelectScreen))); private static readonly Type[] BoolOrVoidReturn = new Type[2] { typeof(bool), typeof(void) }; private static bool _logged; private static bool _compatibilityLogged; private static bool _rollbackUnavailableLogged; private static bool _compatibilityResolved; private static string _compatibilityFailure; private static bool _dayStartSurfaceChecked; private static bool _dayStartSurfaceReady; private static int _maxSubmitSlots; private static readonly HashSet RefusalWarnings = new HashSet(); internal static bool Present { get { bool goCompatible = GoCompatible; if (GoDetected && !_logged) { _logged = true; if (goCompatible) { CoopPlugin.Log.LogInfo((object)"Grading Overhaul detected - the grading module will preserve encoded certificates"); LogCompanies(); } else { CoopPlugin.Log.LogWarning((object)"Grading Overhaul is present but not compatible with this CardShopCoopCommunity build - running the vanilla grading fallback (8 slots, raw grades, no Grading Overhaul integration)"); } } return GoDetected && goCompatible; } } internal static bool GoCompatible { get { string text = CompatibilityFailure(); if (text != null) { LogCompatibilityFailure(text); } if (GoDetected) { return text == null; } return false; } } internal static bool GoDetected { get { if (!(TRegistry != null) && !(THelper != null) && !(TCompany != null) && !(TJobRegistry != null) && !(TPreRoll != null) && !(TTheme != null) && !(TCodec != null) && !(TConfig != null) && !(TSlotExpansion != null) && !(TSaveManager != null) && !(TDayStartPatch != null)) { return TSubmitValidatorPatch != null; } return true; } } internal static int CurrentCompanyId { get { if (PiCurrentCompany == null) { return 255; } try { object value = PiCurrentCompany.GetValue(null, null); return (value == null) ? 255 : Convert.ToInt32(value); } catch (Exception e) { Swallow.Log(e, "CurrentCompanyId", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 390); return 255; } } } internal static bool HostUseCheatsWebsite { get { try { return FiUseCheats?.GetValue(null) is ConfigEntry val && val.Value; } catch (Exception e) { Swallow.Log(e, "HostUseCheatsWebsite", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 407); return false; } } } internal static MethodInfo GoDayStartMethod => MiDayStart; internal static bool GoDayStartMethodMatches => IsFlexibleMethod(MiDayStart, TDayStartPatch, true, BoolOrVoidReturn); internal static MethodInfo GoSubmitValidatorMethod => MiSubmitValidator; internal static int MaxSubmitSlots { get { if (!Present) { return 8; } if (_maxSubmitSlots > 0) { return _maxSubmitSlots; } int num = 52; try { FieldInfo fieldInfo = ((TSlotExpansion == null) ? null : ReflectionSurface.OptionalField(TSlotExpansion, "MAX_SLOTS")); if (fieldInfo != null && fieldInfo.IsLiteral && fieldInfo.FieldType == typeof(int)) { int num2 = Convert.ToInt32(fieldInfo.GetRawConstantValue()); if (num2 >= 8 && num2 <= 255) { num = num2; } else { CoopPlugin.Log.LogWarning((object)("GradingInterop: GO submit slot count is outside the wire-safe range; using " + num)); } } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop: could not read GO submit slot count - " + ex.Message)); } _maxSubmitSlots = num; CoopPlugin.Log.LogInfo((object)("GradingInterop: grading submit slot cap = " + num)); return num; } } private static string CompatibilityFailure() { if (_compatibilityResolved) { return _compatibilityFailure; } _compatibilityFailure = ComputeCompatibilityFailure(); _compatibilityResolved = true; return _compatibilityFailure; } private static string ComputeCompatibilityFailure() { if (!GoDetected) { return null; } List list = new List(); if (!IsFlexibleMethod(MiRemember, TRegistry, true, BoolOrVoidReturn, typeof(CardData), typeof(int))) { list.Add("RememberForExternalMod must be declared by EncodedGradeRegistry, static, (CardData, int), and return bool or void"); } if (TCompany == null || !TCompany.IsEnum) { list.Add("GradingCompany must be an enum"); } if (!IsExactMethod(MiEncoded, TRegistry, true, typeof(int), typeof(CardData))) { list.Add("GetEncodedOrCurrent must be declared by EncodedGradeRegistry, static, (CardData), and return int"); } if (!IsExactMethod(MiActual, THelper, true, typeof(int), typeof(int))) { list.Add("GetActualGrade must be declared by Helper, static, (int), and return int"); } if (!IsExactMethod(MiDecodeFull, THelper, true, typeof(void), typeof(int), TCompany?.MakeByRefType(), typeof(int).MakeByRefType(), typeof(int).MakeByRefType())) { list.Add("DecodeGradeFull must have the exact encoded/company/grade/certificate signature"); } if (!IsExactMethod(MiCheat, THelper, true, typeof(bool), typeof(int))) { list.Add("IsCheatFlagged must be declared by Helper, static, (int), and return bool"); } if (!IsExactMethod(MiHasBinding, TSaveManager, true, typeof(bool), TCompany, typeof(int))) { list.Add("HasCertBinding must have the exact company/certificate signature"); } if (!IsExactMethod(MiIsBoundTo, TSaveManager, true, typeof(bool), TCompany, typeof(int), typeof(CardData))) { list.Add("IsCertBoundToCard must have the exact company/certificate/card signature"); } if (!IsFlexibleMethod(MiDayStart, TDayStartPatch, true, BoolOrVoidReturn)) { list.Add("day-start Prefix must be declared by CompanyStamp_RestockManager_OnDayStartedPatch, static, (), and return bool or void"); } else if (_dayStartSurfaceChecked && !_dayStartSurfaceReady) { list.Add("the exact day-start maturation guard could not be installed"); } if (!IsExactMethod(MiSubmitValidator, TSubmitValidatorPatch, true, typeof(bool), typeof(GradedCardSubmitSelectScreen))) { list.Add("submit validator Prefix must be declared by GradingSubmit_CompanyValidation_Patch, static, (GradedCardSubmitSelectScreen), and return bool"); } if (!IsExactMethod(MiOnJobSubmitted, TJobRegistry, true, typeof(void), typeof(GradeCardSubmitSet), TCompany, typeof(bool), TCompany)) { list.Add("OnJobSubmitted apply method has an unexpected declaring type, static/instance mode, parameter list, or return type"); } if (!IsExactMethod(MiPreRoll, TPreRoll, true, typeof(void), typeof(GradeCardSubmitSet), TCompany, typeof(bool), typeof(int))) { list.Add("PreRollOnSubmit apply method has an unexpected declaring type, static/instance mode, parameter list, or return type"); } if (MiRollbackJobCompany != null && !IsExactMethod(MiRollbackJobCompany, TJobRegistry, true, typeof(bool), typeof(GradeCardSubmitSet), TCompany, typeof(bool), typeof(int))) { list.Add("OnJobCancelled rollback method has an unexpected declaring type, static/instance mode, parameter list, or return type"); } if (MiRollbackPreRoll != null && !IsExactMethod(MiRollbackPreRoll, TPreRoll, true, typeof(bool), typeof(GradeCardSubmitSet), TCompany, typeof(bool), typeof(int))) { list.Add("RollbackOnSubmit rollback method has an unexpected declaring type, static/instance mode, parameter list, or return type"); } if (!IsExactMethod(MiNextJobId, TPreRoll, true, typeof(int))) { list.Add("GetNextJobId must be declared by PreRollManager, static, (), and return int"); } if (!IsExactMethod(MiEncode, TCodec, true, typeof(int), TCompany, typeof(int), typeof(int))) { list.Add("ServiceLevelCodec.Encode has an unexpected exact signature"); } if (!IsExactMethod(MiTryDecode, TCodec, true, typeof(bool), typeof(int), TCompany?.MakeByRefType(), typeof(int).MakeByRefType())) { list.Add("ServiceLevelCodec.TryDecode has an unexpected exact signature"); } if (list.Count == 0 && (MiRollbackJobCompany == null || MiRollbackPreRoll == null)) { LogRollbackUnavailable(); } if (list.Count != 0) { return string.Join("; ", list); } return null; } private static void LogCompatibilityFailure(string failure) { if (!_compatibilityLogged && CoopPlugin.Log != null) { _compatibilityLogged = true; CoopPlugin.Log.LogError((object)("Grading Overhaul grading disabled fail-closed: " + failure)); } } private static MethodInfo ResolveExactMethod(Type declaringType, string name, params Type[] parameterTypes) { if (declaringType == null) { return null; } return declaringType.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null); } private static bool IsExactMethod(MethodInfo method, Type declaringType, bool isStatic, Type returnType, params Type[] parameterTypes) { if (method == null || method.DeclaringType != declaringType || method.IsStatic != isStatic || method.ReturnType != returnType) { return false; } ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != parameterTypes.Length) { return false; } for (int i = 0; i < parameters.Length; i++) { if (parameters[i].ParameterType != parameterTypes[i]) { return false; } } return true; } private static bool IsFlexibleMethod(MethodInfo method, Type declaringType, bool isStatic, Type[] allowedReturnTypes, params Type[] parameterTypes) { if (method == null || method.DeclaringType != declaringType || method.IsStatic != isStatic) { return false; } bool flag = false; for (int i = 0; i < allowedReturnTypes.Length; i++) { if (method.ReturnType == allowedReturnTypes[i]) { flag = true; break; } } if (!flag) { return false; } ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != parameterTypes.Length) { return false; } for (int j = 0; j < parameters.Length; j++) { if (parameters[j].ParameterType != parameterTypes[j]) { return false; } } return true; } private static void LogRollbackUnavailable() { if (!_rollbackUnavailableLogged && CoopPlugin.Log != null) { _rollbackUnavailableLogged = true; CoopPlugin.Log.LogWarning((object)"Grading Overhaul has no post-submit rollback hooks (OnJobCancelled/RollbackOnSubmit) on this build; a local commit failure after job enrollment cannot be compensated. Grading stays enabled."); } } private static void InvalidateCompatibility() { _compatibilityResolved = false; _compatibilityFailure = null; } internal static void SetDayStartSurface(bool ready, string reason) { _dayStartSurfaceChecked = true; _dayStartSurfaceReady = ready; InvalidateCompatibility(); if (!ready && GoDetected) { CoopPlugin.Log.LogError((object)("Grading Overhaul grading disabled fail-closed: " + (reason ?? "the exact day-start maturation guard is unavailable"))); } } internal static int Encoded(CardData card) { if (card == null) { return 0; } if (GoDetected && !GoCompatible) { return card.cardGrade; } if (MiEncoded == null) { return card.cardGrade; } try { return Convert.ToInt32(MiEncoded.Invoke(null, new object[1] { card })); } catch (Exception e) { Swallow.Log(e, "Encoded", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 488); return card.cardGrade; } } internal static int Actual(int encoded) { if (encoded <= 10 || (GoDetected && !GoCompatible) || MiActual == null) { return encoded; } try { return Convert.ToInt32(MiActual.Invoke(null, new object[1] { encoded })); } catch (Exception e) { Swallow.Log(e, "Actual", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 504); return encoded; } } internal static bool Remember(CardData card) { if (!GoCompatible || card == null || MiRemember == null) { return false; } int num = Encoded(card); if (num <= 10 || !CertFreeForCard(card, num)) { return false; } try { return !(MiRemember.Invoke(null, new object[2] { card, num }) is bool flag) || flag; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop.Remember: " + ex.Message)); return false; } } internal static bool IsAllowedCompany(int id) { if (TCompany == null || !TCompany.IsEnum) { return false; } try { object value = Enum.ToObject(TCompany, id); if (!Enum.IsDefined(TCompany, value)) { return false; } if (!(FiAllowedCompanies?.GetValue(null) is Array array)) { string name = Enum.GetName(TCompany, value); return name != null && name != "Custom"; } for (int i = 0; i < array.Length; i++) { if (Convert.ToInt32(array.GetValue(i)) == id) { return true; } } return false; } catch (Exception e) { Swallow.Log(e, "IsAllowedCompany", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 560); return false; } } internal static bool ValidSubmissionCard(CardData card) { string reason; return ValidSubmissionCard(card, out reason); } internal static bool ValidSubmissionCard(CardData card, out string reason) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) reason = null; if (card == null) { reason = "card is null"; return false; } if ((int)card.expansionType == -1 || (int)card.monsterType == 0 || !Enum.IsDefined(typeof(ECardBorderType), card.borderType)) { reason = "card enum fields are invalid (" + DescribeCard(card) + ")"; return false; } if (card.isChampionCard || card.cardGrade < 0 || card.gradedCardIndex < 0 || card.gradedCardIndex > 1000000) { reason = "card identity fields are invalid (" + DescribeCard(card) + ")"; return false; } try { int num = Encoded(card); if (num < 0 || (num > 10 && !Present) || (num > 10 && (Actual(num) < 1 || Actual(num) > 10))) { reason = "encoded grade " + num + " is invalid for the active grading backend (present=" + Present + ", compatible=" + GoCompatible + ")"; return false; } if (num > 10 && !DecodeCert(num, out var _, out var _)) { reason = "encoded grade " + num + " did not decode to a certificate"; return false; } if (num == 0 && card.gradedCardIndex != 0) { reason = "ungraded card carried gradedCardIndex " + card.gradedCardIndex; return false; } int cardSaveIndex = CPlayerData.GetCardSaveIndex(card); List cardCollectedList = CPlayerData.GetCardCollectedList(card.expansionType, card.isDestiny); if (cardSaveIndex < 0 || cardCollectedList == null || cardSaveIndex >= cardCollectedList.Count) { reason = "save index " + cardSaveIndex + " is outside the live collection (collected=" + ((cardCollectedList == null) ? "null" : cardCollectedList.Count.ToString()) + ") for " + DescribeCard(card); return false; } CardData cardData = CPlayerData.GetCardData(cardSaveIndex, card.expansionType, card.isDestiny); if (cardData == null) { reason = "no canonical card at save index " + cardSaveIndex + " for " + DescribeCard(card); return false; } if (!SameCardFields(cardData, card)) { reason = "card fields do not match save index " + cardSaveIndex + ": sent " + DescribeCard(card) + ", canonical " + DescribeCard(cardData); return false; } return true; } catch (Exception ex) { reason = "card validation threw: " + ex.Message + " for " + DescribeCard(card); CoopPlugin.Log.LogWarning((object)("grading submission card validation failed: " + ex.Message)); return false; } } private static bool SameCardFields(CardData left, CardData right) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (left != null && right != null && left.expansionType == right.expansionType && left.monsterType == right.monsterType && left.borderType == right.borderType && left.isFoil == right.isFoil && left.isDestiny == right.isDestiny) { return left.isChampionCard == right.isChampionCard; } return false; } internal static string DescribeCard(CardData card) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected I4, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected I4, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected I4, but got Unknown if (card == null) { return "(null card)"; } return "exp=" + (int)card.expansionType + " monster=" + (int)card.monsterType + " border=" + (int)card.borderType + (card.isFoil ? " foil" : "") + (card.isDestiny ? " destiny" : "") + (card.isChampionCard ? " champion" : "") + " grade=" + card.cardGrade + " gidx=" + card.gradedCardIndex; } internal static bool RegisterJobCompany(GradeCardSubmitSet set, int companyId, bool useCheats) { if (set == null || MiOnJobSubmitted == null || TCompany == null) { return false; } try { object obj = Enum.ToObject(TCompany, companyId); MiOnJobSubmitted.Invoke(null, new object[4] { set, obj, useCheats, obj }); return true; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop.RegisterJobCompany: " + ex.Message)); return false; } } internal static int NextJobId() { if (MiNextJobId == null) { return 0; } try { return Convert.ToInt32(MiNextJobId.Invoke(null, null)); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop.NextJobId: " + ex.Message)); return 0; } } internal static int EncodeServiceLevel(int companyId, int tier, int jobId) { if (MiEncode == null || TCompany == null) { return 0; } try { return Convert.ToInt32(MiEncode.Invoke(null, new object[3] { Enum.ToObject(TCompany, companyId), tier, jobId })); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop.EncodeServiceLevel: " + ex.Message)); return 0; } } internal static bool TryDecodeServiceLevel(int raw, out int companyId, out int tier) { companyId = -1; tier = raw; if (MiTryDecode == null || TCompany == null) { return false; } try { object[] array = new object[3] { raw, Enum.ToObject(TCompany, 0), 0 }; if (!(bool)MiTryDecode.Invoke(null, array)) { return false; } companyId = Convert.ToInt32(array[1]); tier = Convert.ToInt32(array[2]); return companyId >= 0 && Enum.IsDefined(TCompany, companyId) && tier >= 0 && tier <= 255; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop.TryDecodeServiceLevel: " + ex.Message)); return false; } } internal static bool PreRollJob(GradeCardSubmitSet set, int companyId, bool useCheats, int jobId) { if (set == null || MiPreRoll == null || TCompany == null) { return false; } try { MiPreRoll.Invoke(null, new object[4] { set, Enum.ToObject(TCompany, companyId), useCheats, jobId }); return true; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop.PreRollJob: " + ex.Message)); return false; } } internal static bool RollbackJobCompany(GradeCardSubmitSet set, int companyId, bool useCheats, int jobId) { return InvokeRollback(MiRollbackJobCompany, set, companyId, useCheats, jobId, "job enrollment"); } internal static bool RollbackPreRoll(GradeCardSubmitSet set, int companyId, bool useCheats, int jobId) { return InvokeRollback(MiRollbackPreRoll, set, companyId, useCheats, jobId, "job pre-roll"); } internal static bool DecodeCert(int encoded, out int companyId, out int cert) { companyId = -1; cert = 0; if (encoded <= 10 || !GoCompatible || MiDecodeFull == null || TCompany == null) { return false; } try { object[] array = new object[4] { encoded, Enum.ToObject(TCompany, 0), 0, 0 }; MiDecodeFull.Invoke(null, array); companyId = Convert.ToInt32(array[1]); int num = Convert.ToInt32(array[2]); cert = Convert.ToInt32(array[3]); return Enum.IsDefined(TCompany, companyId) && num >= 1 && num <= 10 && cert > 0; } catch (Exception e) { Swallow.Log(e, "DecodeCert", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 782); return false; } } internal static bool CheatFlagged(int encoded) { if (MiCheat == null) { return false; } try { return (bool)MiCheat.Invoke(null, new object[1] { encoded }); } catch (Exception e) { Swallow.Log(e, "CheatFlagged", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 797); return false; } } internal static void Reset() { _maxSubmitSlots = 0; RefusalWarnings.Clear(); InvalidateCompatibility(); } private static bool CertFreeForCard(CardData card, int encoded) { if (CheatFlagged(encoded)) { return true; } if (MiHasBinding == null || MiIsBoundTo == null || TCompany == null) { return false; } if (!DecodeCert(encoded, out var companyId, out var cert)) { return false; } try { object obj = Enum.ToObject(TCompany, companyId); if (!(bool)MiHasBinding.Invoke(null, new object[2] { obj, cert }) || (bool)MiIsBoundTo.Invoke(null, new object[3] { obj, cert, card })) { return true; } } catch (Exception e) { Swallow.Log(e, "CertFreeForCard", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Grading/GradingInterop.cs", 831); return false; } long item = ((long)companyId << 32) | (uint)cert; if (!RefusalWarnings.Add(item)) { CoopPlugin.Log.LogWarning((object)("graded cert refused again: " + cert + " (company " + companyId + ")")); return false; } CoopPlugin.Log.LogWarning((object)("graded cert refused: certificate " + cert + " (company " + companyId + ") is already bound to a different card on this PC; the incoming card is not registered")); return false; } private static bool InvokeRollback(MethodInfo method, GradeCardSubmitSet set, int companyId, bool useCheats, int jobId, string operation) { if (method == null || method.ReturnType != typeof(bool) || set == null || TCompany == null) { return false; } try { object obj = method.Invoke(null, new object[4] { set, Enum.ToObject(TCompany, companyId), useCheats, jobId }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop rollback " + operation + " failed: " + ex.Message)); return false; } } private static void LogCompanies() { try { if (!(TCompany == null) && TCompany.IsEnum) { string[] names = Enum.GetNames(TCompany); string[] array = new string[names.Length]; for (int i = 0; i < names.Length; i++) { array[i] = names[i] + "=" + Convert.ToInt32(Enum.Parse(TCompany, names[i])); } CoopPlugin.Log.LogInfo((object)("GradingInterop: GO GradingCompany = " + string.Join(", ", array))); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("GradingInterop: could not enumerate GO companies - " + ex.Message)); } } } [NetworkMessage] public sealed class GradingOpMessage : IPredictedMessage, INetMessage { public byte ServiceLevel; public List Cards = new List(); public byte CompanyId; public Guid JobId; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class GradingStateMessage : INetMessage { public List Sets = new List(); } [NetworkMessage] public sealed class GradingJobDeltaMessage : IPredictedMessage, INetMessage { public Guid Id; public bool Removed; public int ServiceLevel; public byte DayPassed; public float MinutePassed; public List Cards = new List(); public Guid PredictionId { get; set; } } public sealed class GradingSetDto { public Guid Id; public int ServiceLevel; public byte DayPassed; public float MinutePassed; public List Cards = new List(); } internal static class GradingPatches { internal static void ApplyClient(Harmony harmony) { //IL_0020: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_0054: Expected O, but got Unknown //IL_0054: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown Try(harmony, typeof(GradedCardSubmitSelectScreen), "OnPressSubmitButton", new HarmonyMethod(typeof(GradingPatches), "SubmitCapturePrefix", (Type[])null), new HarmonyMethod(typeof(GradingPatches), "SubmitObservePostfix", (Type[])null), new HarmonyMethod(typeof(GradingPatches), "SubmitFinalizer", (Type[])null)); Try(harmony, typeof(RestockManager), "OnDayStarted", new HarmonyMethod(typeof(GradingPatches), "ClientMaturationPrefix", (Type[])null)); TryPatchGoDayStart(harmony); ValidateGuestDayStartSurface(harmony); InstallDataLifecyclePatches(harmony); } internal static void ApplyHost(Harmony harmony) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown ValidateGoDayStartSurface(); Try(harmony, typeof(GradedCardSubmitSelectScreen), "OnPressSubmitButton", null, new HarmonyMethod(typeof(GradingPatches), "HostSubmitPostfix", (Type[])null)); Try(harmony, typeof(RestockManager), "OnDayStarted", null, new HarmonyMethod(typeof(GradingPatches), "HostDayStartedPostfix", (Type[])null)); InstallDataLifecyclePatches(harmony); } private static void ValidateGoDayStartSurface() { if (!GradingInterop.GoDetected) { GradingInterop.SetDayStartSurface(ready: true, null); } else if (!GradingInterop.GoDayStartMethodMatches) { GradingInterop.SetDayStartSurface(ready: false, "exact type/method signature was not found: CompanyStamp_RestockManager_OnDayStartedPatch.Prefix() -> bool"); } else { GradingInterop.SetDayStartSurface(ready: true, null); } } private static void Try(Harmony harmony, Type type, string name, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod finalizer = null) { try { MethodInfo methodInfo = ReflectionSurface.OptionalMethod(type, name); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("Grading patch target missing: " + type.Name + "." + name)); } else { harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, finalizer, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Grading patch failed for " + type.Name + "." + name + ": " + ex.Message)); } } private static void SubmitCapturePrefix(GradedCardSubmitSelectScreen __instance, out GradingClientBehaviour.SubmissionCapture __state) { __state = default(GradingClientBehaviour.SubmissionCapture); GradingClientBehaviour active = GradingClientBehaviour.Active; if (!((Object)(object)active == (Object)null)) { active.CaptureSubmission(__instance, out __state); } } private static void SubmitObservePostfix(GradingClientBehaviour.SubmissionCapture __state) { GradingClientBehaviour.Active?.ObserveSubmission(__state); } private static void SubmitFinalizer(GradingClientBehaviour.SubmissionCapture __state) { if (__state.Armed) { EconomyActionScope.Exit(); } } private static void TryPatchGoDayStart(Harmony harmony) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown if (!GradingInterop.GoDetected) { return; } if (!GradingInterop.GoDayStartMethodMatches) { CoopPlugin.Log.LogError((object)"Grading Overhaul's day-start prefix does not match this CardShopCoopCommunity build (expected CompanyStamp_RestockManager_OnDayStartedPatch.Prefix() -> bool/void); the guest cannot block its local grading."); return; } try { harmony.Patch((MethodBase)GradingInterop.GoDayStartMethod, new HarmonyMethod(typeof(GradingPatches), "ClientMaturationPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { harmony.Unpatch((MethodBase)GradingInterop.GoDayStartMethod, (HarmonyPatchType)0, harmony.Id); CoopPlugin.Log.LogError((object)("Grading Overhaul's day-start guard failed to install: " + ex.Message)); } } private static void ValidateGuestDayStartSurface(Harmony harmony) { if (!IsGuardInstalled(ReflectionSurface.OptionalMethod(typeof(RestockManager), "OnDayStarted"), harmony)) { FailGuestDayStartSurface("the guest day-start block on RestockManager.OnDayStarted is not installed"); } else if (GradingInterop.GoDetected && (!GradingInterop.GoDayStartMethodMatches || !IsGuardInstalled(GradingInterop.GoDayStartMethod, harmony))) { FailGuestDayStartSurface("Grading Overhaul's day-start prefix could not be blocked on the guest"); } else { GradingInterop.SetDayStartSurface(ready: true, null); } } private static void FailGuestDayStartSurface(string reason) { if (!GradingInterop.GoDetected) { CoopPlugin.Log.LogError((object)("Grading: " + reason + "; the guest would grade its own jobs. Running the vanilla grading fallback.")); } GradingInterop.SetDayStartSurface(ready: false, reason); } private static bool IsGuardInstalled(MethodInfo original, Harmony harmony) { if (original == null) { return false; } try { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)original); if (patchInfo?.Prefixes == null) { return false; } foreach (Patch prefix in patchInfo.Prefixes) { if (prefix.owner == harmony.Id && prefix.PatchMethod?.Name == "ClientMaturationPrefix") { return true; } } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Grading day-start guard probe failed for " + original.DeclaringType?.Name + "." + original.Name + ": " + ex.Message)); } return false; } private static bool ClientMaturationPrefix() { return (Object)(object)GradingClientBehaviour.Active == (Object)null; } private static void HostDayStartedPostfix() { GradingHostBehaviour.Active?.PushCurrentSets(); } private static void HostSubmitPostfix() { GradingHostBehaviour.Active?.PushCurrentSets(); } private static void InstallDataLifecyclePatches(Harmony harmony) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown Try(harmony, typeof(CPlayerData), "ResetData", null, new HarmonyMethod(typeof(GradingPatches), "DataLifecyclePostfix", (Type[])null)); Try(harmony, typeof(CPlayerData), "CreateDefaultData", null, new HarmonyMethod(typeof(GradingPatches), "DataLifecyclePostfix", (Type[])null)); Try(harmony, typeof(CGameData), "PropagateLoadData", null, new HarmonyMethod(typeof(GradingPatches), "DataLifecyclePostfix", (Type[])null)); } private static void DataLifecyclePostfix() { GradingHostBehaviour.InventoryReset(); GradingClientBehaviour.InventoryReset(); } } public readonly struct GradingOffer { public readonly int ConnectionId; public readonly string Who; public readonly int Count; public GradingOffer(int connectionId, string who, int count) { ConnectionId = connectionId; Who = who; Count = count; } } public static class GradingUiApi { private static readonly IReadOnlyList Empty = new List(); public static IReadOnlyList Offers => Empty; public static bool Adopt(int connectionId) { return false; } } public interface IGradingWorldBridge { bool SceneReady { get; } bool CardSetInstalled(CardData card); bool TryGetVanillaServiceCost(int serviceLevel, int cardCount, out float total); bool TryReserveCards(int connectionId, IList cards, out IGradingCardReservation reservation, out string reason); bool TryAddCard(CardData card, int amount); void NotifyCardsChanged(); void WarnRefused(CardData card, string context); } public interface IGradingCardReservation : IDisposable { void Commit(); void Rollback(); } public static class GradingWorldBridge { private static IGradingWorldBridge _current; public static IGradingWorldBridge Current => _current; public static void Attach(IGradingWorldBridge bridge) { if (bridge == null) { throw new ArgumentNullException("bridge"); } if (_current != null && _current != bridge) { throw new InvalidOperationException("A different grading World bridge is already attached."); } _current = bridge; } public static void Detach(IGradingWorldBridge bridge) { if (_current == bridge) { _current = null; } } } } namespace CardShopCoop.Modules.GameTime { [ClientBehaviour] public sealed class GameTimeClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(LightManager), "Init")] private static class LightManagerReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.OnLightManagerReady(); } } private static GameTimeClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private DayTimeMessage _pendingMessage; private bool _hasHostDay; private bool _shutdown; private void OnEnable() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown if (!_shutdown && _context == null) { _context = base.RuntimeContext; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.game-time.client"); _harmony.CreateClassProcessor(typeof(LightManagerReadyPatch)).Patch(); _context.Messages.RegisterAttributedHandlers(this); } } [MessageHandler(typeof(DayTimeMessage))] private void HandleDayTime(MessageContext context, DayTimeMessage message) { if (!_shutdown) { _pendingMessage = message; TryApplyPending(); } } private void Apply(LightManager manager, DayTimeMessage message) { int num = (_hasHostDay ? CPlayerData.m_CurrentDay : message.Day); bool flag = message.Hour == 8 && message.Minute == 0 && !message.HasDayEnded && !message.ShopOnceOpen; bool flag2 = message.Day > num && !message.HasDayEnded; CPlayerData.m_CurrentDay = message.Day; if (flag || flag2) { if (flag2 && !flag) { CoopPlugin.Log.LogDebug((object)"game-time day advance applied off the morning frame; running the vanilla dawn sequence."); } CPlayerData.m_IsShopOpen = false; CPlayerData.m_IsShopOnceOpen = false; GameTimeInterop.ResetSunlightIntensity.Invoke(manager, null); } else { CPlayerData.m_IsShopOnceOpen = message.ShopOnceOpen; } _hasHostDay = true; GameTimeInterop.TimeHour.SetValue(manager, message.Hour); GameTimeInterop.TimeMin.SetValue(manager, message.Minute); GameTimeInterop.TimeMinFloat.SetValue(manager, message.MinuteFloat); GameTimeInterop.TimeOfDayIndex.SetValue(manager, message.TimeOfDayIndex); GameTimeInterop.HasDayEnded.SetValue(manager, message.HasDayEnded); GameTimeInterop.EvaluateTimeClock.Invoke(manager, null); } private void TryApplyPending() { if (!_shutdown && _pendingMessage != null && _context != null && _context.InGame()) { LightManager manager = GameTimeInterop.FindSceneManager(); if (!GameTimeInterop.IsSceneReady(manager)) { CoopPlugin.Log.LogDebug((object)"game-time baseline held until the world clock is ready."); return; } DayTimeMessage pendingMessage = _pendingMessage; Apply(manager, pendingMessage); _pendingMessage = null; } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { TryApplyPending(); } private void OnLightManagerReady() { TryApplyPending(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { _pendingMessage = null; _hasHostDay = false; } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _pendingMessage = null; _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class GameTimeHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(LightManager), "EvaluateTimeClock")] private static class TimeChangedPatch { [HarmonyPostfix] private static void Postfix(LightManager __instance) { _active?.BroadcastWhenTimeChanged(__instance); } } private static GameTimeHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _hasObservedTime; private int _observedDay; private int _observedHour; private int _observedMinute; private bool _shutdown; private void OnEnable() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (!_shutdown && _harmony == null) { _context = base.RuntimeContext; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.game-time.host"); _harmony.CreateClassProcessor(typeof(TimeChangedPatch)).Patch(); } } [OnFullyJoined] private void SendJoinBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State) && _context.InGame() && TryBuildMessage(null, out var message)) { _context.Send(connection.Id, (INetMessage)(object)message); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _context = null; _hasObservedTime = false; } } private void OnDestroy() { Shutdown(); } private void BroadcastWhenTimeChanged(LightManager manager) { if (!_shutdown && _context.InGame() && TryBuildMessage(manager, out var message) && (!_hasObservedTime || _observedDay != message.Day || _observedHour != message.Hour || _observedMinute != message.Minute)) { RecordObservedTime(message); _context.Broadcast((INetMessage)(object)message); } } private static bool TryBuildMessage(LightManager knownManager, out DayTimeMessage message) { LightManager val = knownManager ?? GameTimeInterop.FindSceneManager(); if (!GameTimeInterop.IsSceneReady(val)) { message = null; return false; } int num = (int)GameTimeInterop.TimeHour.GetValue(val); int num2 = (int)GameTimeInterop.TimeMin.GetValue(val); message = new DayTimeMessage { Day = CPlayerData.m_CurrentDay, Hour = num, Minute = num2, MinuteFloat = (float)GameTimeInterop.TimeMinFloat.GetValue(val), ShopOnceOpen = (CPlayerData.m_IsShopOnceOpen && (num != 8 || num2 != 0 || (bool)GameTimeInterop.HasDayEnded.GetValue(val))), TimeOfDayIndex = (int)GameTimeInterop.TimeOfDayIndex.GetValue(val), HasDayEnded = (bool)GameTimeInterop.HasDayEnded.GetValue(val) }; if ((Object)(object)_active != (Object)null && _active._hasObservedTime && message.Day > _active._observedDay) { message.ShopOnceOpen = false; } return true; } private void RecordObservedTime(DayTimeMessage message) { _hasObservedTime = true; _observedDay = message.Day; _observedHour = message.Hour; _observedMinute = message.Minute; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } } internal static class GameTimeInterop { internal static readonly FieldInfo TimeHour = ReflectionSurface.RequiredField(typeof(LightManager), "m_TimeHour"); internal static readonly FieldInfo TimeMin = ReflectionSurface.RequiredField(typeof(LightManager), "m_TimeMin"); internal static readonly FieldInfo TimeMinFloat = ReflectionSurface.RequiredField(typeof(LightManager), "m_TimeMinFloat"); internal static readonly FieldInfo HasDayEnded = ReflectionSurface.RequiredField(typeof(LightManager), "m_HasDayEnded"); internal static readonly FieldInfo TimeOfDayIndex = ReflectionSurface.RequiredField(typeof(LightManager), "m_TImeOfDayIndex"); internal static readonly FieldInfo FinishLoading = ReflectionSurface.RequiredField(typeof(LightManager), "m_FinishLoading"); internal static readonly MethodInfo EvaluateTimeClock = ReflectionSurface.RequiredMethod(typeof(LightManager), "EvaluateTimeClock"); internal static readonly MethodInfo ResetSunlightIntensity = ReflectionSurface.RequiredMethod(typeof(LightManager), "ResetSunlightIntensity"); internal static bool IsSceneReady(LightManager manager) { if ((Object)(object)manager != (Object)null) { return (bool)FinishLoading.GetValue(manager); } return false; } internal static LightManager FindSceneManager() { return SceneRef.Get(); } } [NetworkMessage] public sealed class DayTimeMessage : INetMessage { public int Day; public int Hour; public int Minute; public float MinuteFloat; public bool ShopOnceOpen; public int TimeOfDayIndex; public bool HasDayEnded; } } namespace CardShopCoop.Modules.Expansion { [ClientBehaviour] public sealed class ExpansionClientBehaviour : CoopBehaviour { private sealed class PurchaseCapture { public UnlockRoomManager Manager; public ExpansionInterop.StateSnapshot Snapshot; public int Rooms; public int WarehouseRooms; public bool WarehouseUnlocked; public float TotalCost; public int Index; public bool IsShopB; } [HarmonyPatch(typeof(ExpansionShopUIScreen), "EvaluateCartCheckout")] private static class RoomCheckoutPatch { [HarmonyPrefix] private static void Prefix(ExpansionShopUIScreen __instance, float totalCost, int index, bool isShopB, out PurchaseCapture __state) { __state = Capture(totalCost, index, isShopB); if (__state != null) { EconomyActionScope.Enter(); } } [HarmonyPostfix] private static void Postfix(ExpansionShopUIScreen __instance, float totalCost, int index, bool isShopB, PurchaseCapture __state) { if (__state == null) { return; } byte b = (isShopB ? ((byte)1) : ((byte)0)); if ((b == 0) ? (CPlayerData.m_UnlockRoomCount != __state.Rooms) : (CPlayerData.m_UnlockWarehouseRoomCount != __state.WarehouseRooms)) { Observe(__state, b, delegate { __instance.EvaluateCartCheckout(__state.TotalCost, __state.Index, __state.IsShopB); }); } } [HarmonyFinalizer] private static void Finalizer(PurchaseCapture __state) { if (__state != null) { EconomyActionScope.Exit(); } } } [HarmonyPatch(typeof(ExpansionShopUIScreen), "OnPressUnlockShopB")] private static class LotBCheckoutPatch { [HarmonyPrefix] private static void Prefix(ExpansionShopUIScreen __instance, out PurchaseCapture __state) { __state = Capture(0f, 0, isShopB: true); if (__state != null) { EconomyActionScope.Enter(); } } [HarmonyPostfix] private static void Postfix(ExpansionShopUIScreen __instance, PurchaseCapture __state) { if (__state != null && CPlayerData.m_IsWarehouseRoomUnlocked != __state.WarehouseUnlocked) { Observe(__state, 2, delegate { __instance.OnPressUnlockShopB(); }); } } [HarmonyFinalizer] private static void Finalizer(PurchaseCapture __state) { if (__state != null) { EconomyActionScope.Exit(); } } } [HarmonyPatch(typeof(UnlockRoomManager), "Init")] private static class ExpansionManagerReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.OnManagerReady(); } } private static ExpansionClientBehaviour _active; private static int _applyingPrediction; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private ExpansionBaselineMessage _latestBaseline; private ExpansionDeltaMessage _pendingShopDelta; private ExpansionDeltaMessage _pendingWarehouseDelta; private ExpansionDeltaMessage _pendingWarehouseUnlockDelta; private bool _baselineNeedsManagerInitialization; private void OnEnable() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.expansion.client"); _harmony.CreateClassProcessor(typeof(RoomCheckoutPatch)).Patch(); _harmony.CreateClassProcessor(typeof(LotBCheckoutPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ExpansionManagerReadyPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Expansion client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; _context = null; throw; } } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; ApplyLatestState(); } [MessageHandler(typeof(ExpansionBaselineMessage))] private void HandleBaseline(MessageContext context, ExpansionBaselineMessage message) { if (!_shutdown) { _latestBaseline = message; ApplyLatestState(); } } [MessageHandler(typeof(ExpansionDeltaMessage))] private void HandleDelta(MessageContext context, ExpansionDeltaMessage message) { if (!_shutdown && message != null) { switch (message.Area) { case 0: ReplacePending(ref _pendingShopDelta, message); break; case 1: ReplacePending(ref _pendingWarehouseDelta, message); break; case 2: ReplacePending(ref _pendingWarehouseUnlockDelta, message); break; default: throw new InvalidOperationException("Unknown expansion delta area " + message.Area + "."); } ApplyLatestState(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _latestBaseline = null; ClearPendingDeltas(); _baselineNeedsManagerInitialization = false; _joined = false; _context = null; } } private void OnDestroy() { Shutdown(); } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { ApplyLatestState(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { ApplyLatestState(); } private void OnManagerReady() { ApplyLatestState(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { _latestBaseline = null; ClearPendingDeltas(); _baselineNeedsManagerInitialization = false; _joined = false; } } private void ApplyLatestState() { if (_shutdown || !_joined || !_context.InGame() || ExpansionInterop.ApplyingAuthoritativeState) { return; } UnlockRoomManager val = ExpansionInterop.FindUnlockManager(); if (_latestBaseline != null) { ExpansionBaselineMessage latestBaseline = _latestBaseline; ExpansionInterop.ApplyBaseline(val, latestBaseline); ExpansionInterop.RefreshOpenScreen(ExpansionInterop.FindExpansionScreen()); _baselineNeedsManagerInitialization = (Object)(object)val == (Object)null; _latestBaseline = null; } if ((Object)(object)val == (Object)null) { ApplyPendingDelta(null, ref _pendingShopDelta); ApplyPendingDelta(null, ref _pendingWarehouseDelta); ApplyPendingDelta(null, ref _pendingWarehouseUnlockDelta); return; } if (_baselineNeedsManagerInitialization) { ExpansionInterop.InitializeRooms(val); _baselineNeedsManagerInitialization = false; } ApplyPendingDelta(val, ref _pendingShopDelta); ApplyPendingDelta(val, ref _pendingWarehouseDelta); ApplyPendingDelta(val, ref _pendingWarehouseUnlockDelta); } private static void ApplyPendingDelta(UnlockRoomManager manager, ref ExpansionDeltaMessage pending) { if (pending != null) { ExpansionDeltaMessage delta = pending; pending = null; PredictionApi.Confirm(delta.PredictionId, delegate { ExpansionInterop.ApplyDelta(manager, delta); ExpansionInterop.RefreshOpenScreen(ExpansionInterop.FindExpansionScreen()); }); } } private static void ReplacePending(ref ExpansionDeltaMessage pending, ExpansionDeltaMessage replacement) { if (pending != null && pending != replacement) { PredictionApi.Ack(pending.PredictionId); } pending = replacement; } private void ClearPendingDeltas() { ReplacePending(ref _pendingShopDelta, null); ReplacePending(ref _pendingWarehouseDelta, null); ReplacePending(ref _pendingWarehouseUnlockDelta, null); } private static PurchaseCapture Capture(float totalCost, int index, bool isShopB) { ExpansionClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._shutdown || !active._joined || active._context == null || !active._context.InGame() || _applyingPrediction != 0 || ExpansionInterop.ApplyingAuthoritativeState) { return null; } return new PurchaseCapture { Manager = ExpansionInterop.FindUnlockManager(), Snapshot = ExpansionInterop.CaptureState(), Rooms = CPlayerData.m_UnlockRoomCount, WarehouseRooms = CPlayerData.m_UnlockWarehouseRoomCount, WarehouseUnlocked = CPlayerData.m_IsWarehouseRoomUnlocked, TotalCost = totalCost, Index = index, IsShopB = isShopB }; } private static void Observe(PurchaseCapture capture, byte kind, Action replay) { ExpansionClientBehaviour client = _active; if ((Object)(object)client == (Object)null || !client._joined || client._context == null || !client._context.InGame()) { return; } CoopPlugin.Log.LogInfo((object)("[expansion] observed purchase kind=" + kind + " rooms=" + CPlayerData.m_UnlockRoomCount + " warehouseRooms=" + CPlayerData.m_UnlockWarehouseRoomCount + " unlocked=" + CPlayerData.m_IsWarehouseRoomUnlocked + ".")); PredictionApi.Predict("expansion:" + kind, delegate(Guid id) { client._context.Send(1, (INetMessage)(object)new ExpansionPurchaseMessage { PredictionId = id, Kind = kind }); }, delegate { WithPrediction(replay); }, delegate { WithPrediction(delegate { ExpansionInterop.RollbackState(capture.Manager, capture.Snapshot); }); }); } private static void WithPrediction(Action action) { _applyingPrediction++; try { action(); } finally { _applyingPrediction--; } } } [ServerBehaviour] public sealed class ExpansionHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(UnlockRoomManager), "SetUnlockWarehouseRoom")] private static class ExpansionChangedPatch { [HarmonyPostfix] private static void Postfix() { if ((Object)(object)_active != (Object)null && !_active._shutdown && !_active._purchaseInProgress) { _active.BroadcastDelta(2, Guid.Empty); } } } [HarmonyPatch(typeof(UnlockRoomManager), "StartUnlockNextRoom")] private static class ShopRoomChangedPatch { [HarmonyPostfix] private static void Postfix() { if ((Object)(object)_active != (Object)null && !_active._shutdown && !_active._purchaseInProgress) { _active.BroadcastDelta(0, Guid.Empty); } } } [HarmonyPatch(typeof(UnlockRoomManager), "StartUnlockNextWarehouseRoom")] private static class WarehouseRoomChangedPatch { [HarmonyPostfix] private static void Postfix() { if ((Object)(object)_active != (Object)null && !_active._shutdown && !_active._purchaseInProgress) { _active.BroadcastDelta(1, Guid.Empty); } } } private static ExpansionHostBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _purchaseInProgress; private void OnEnable() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.expansion.host"); _harmony.CreateClassProcessor(typeof(ExpansionChangedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(ShopRoomChangedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(WarehouseRoomChangedPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Expansion host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } [OnFullyJoined] private void SendBaseline(PeerConnection connection) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!_shutdown && connection != null && IsJoinPhase(connection.State)) { _context.Send(connection.Id, (INetMessage)(object)BuildBaseline()); } } [MessageHandler(typeof(ExpansionPurchaseMessage))] private void HandlePurchase(MessageContext context, ExpansionPurchaseMessage message) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (_shutdown || message == null || context?.Connection == null || !_context.InGame() || !IsJoinPhase(context.Connection.State)) { return; } if (message.Kind > 2) { CoopPlugin.Log.LogWarning((object)("Expansion purchase rejected peer=" + context.Connection.Id + " kind=" + message.Kind + " reason=unsupported purchase kind")); PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); return; } bool flag = false; try { flag = HostPurchase(message.Kind, context.Connection.Id, out var _); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Expansion purchase failed for peer " + context.Connection.Id + ": " + ex)); PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); return; } if (!flag) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); } else { BroadcastDelta(message.Kind, message.PredictionId); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; _context = null; } } private void OnDestroy() { Shutdown(); } private bool HostPurchase(byte kind, int peerId, out string rejection) { //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Expected O, but got Unknown rejection = null; UnlockRoomManager val = ExpansionInterop.FindUnlockManager(); CGameManager val2 = SceneRef.Get(); if ((Object)(object)val == (Object)null) { rejection = "unlock manager is unavailable"; LogRejectedPurchase(peerId, kind, rejection); return false; } if (val2 != null && val2.m_IsPrologue) { rejection = "purchases are disabled during the prologue"; LogRejectedPurchase(peerId, kind, rejection); return false; } if (!ExpansionInterop.TryValidatePurchase(val, kind, out var index, out var cost, out rejection)) { LogRejectedPurchase(peerId, kind, rejection); return false; } if (!ExpansionInterop.TryReserveExpansion(cost, out var token)) { rejection = "host wallet reservation failed for " + cost; LogRejectedPurchase(peerId, kind, rejection); return false; } ExpansionInterop.StateSnapshot snapshot = ExpansionInterop.CaptureState(); _purchaseInProgress = true; try { if (!ExpansionInterop.CommitExpansion(token)) { ExpansionInterop.ReleaseExpansion(token); rejection = "expansion debit could not be admitted"; LogRejectedPurchase(peerId, kind, rejection); return false; } try { switch (kind) { case 2: val.SetUnlockWarehouseRoom(true); if (!CPlayerData.m_IsWarehouseRoomUnlocked) { throw new InvalidOperationException("warehouse room unlock did not apply"); } break; case 1: val.StartUnlockNextWarehouseRoom(); if (CPlayerData.m_UnlockWarehouseRoomCount != snapshot.WarehouseRooms + 1) { throw new InvalidOperationException("warehouse expansion unlock did not apply"); } break; default: val.StartUnlockNextRoom(); if (CPlayerData.m_UnlockRoomCount != snapshot.Rooms + 1) { throw new InvalidOperationException("shop expansion unlock did not apply"); } break; } } catch (Exception ex) { ExpansionInterop.CancelExpansion(token); ExpansionInterop.RollbackState(val, snapshot); rejection = "unlock failed after debit admission: " + ex.Message; LogRejectedPurchase(peerId, kind, rejection); return false; } switch (kind) { case 2: PriceChangeManager.AddTransaction(0f - cost, (ETransactionType)3, 1, -1, (CardData)null); AchievementManager.OnShopLotBUnlocked(); break; case 1: PriceChangeManager.AddTransaction(0f - cost, (ETransactionType)3, 0, index, (CardData)null); break; default: PriceChangeManager.AddTransaction(0f - cost, (ETransactionType)3, 1, index, (CardData)null); break; } CEventManager.QueueEvent((CEvent)new CEventPlayer_AddShopExp(Mathf.Clamp(Mathf.RoundToInt(cost / 100f), 5, 100), false)); CPlayerData.m_GameReportDataCollect.upgradeCost -= cost; CPlayerData.m_GameReportDataCollectPermanent.upgradeCost -= cost; SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); ExpansionInterop.SaveShelfData(); return true; } finally { _purchaseInProgress = false; } } private static void LogRejectedPurchase(int peerId, byte kind, string reason) { CoopPlugin.Log.LogWarning((object)("Expansion purchase rejected peer=" + peerId + " kind=" + kind + " reason=" + reason + " prologue=" + (SceneRef.Get()?.m_IsPrologue ?? false) + " shopLevel=" + CPlayerData.m_ShopLevel + " shopRooms=" + CPlayerData.m_UnlockRoomCount + " warehouseUnlocked=" + CPlayerData.m_IsWarehouseRoomUnlocked + " warehouseRooms=" + CPlayerData.m_UnlockWarehouseRoomCount + " coins=" + CPlayerData.m_CoinAmountDouble)); } private void BroadcastDelta(byte kind, Guid predictionId) { if (!_shutdown && _context != null && _context.InGame()) { ExpansionDeltaMessage expansionDeltaMessage = new ExpansionDeltaMessage { PredictionId = predictionId, Area = kind }; switch (kind) { case 0: expansionDeltaMessage.Count = CPlayerData.m_UnlockRoomCount; break; case 1: expansionDeltaMessage.Count = CPlayerData.m_UnlockWarehouseRoomCount; break; case 2: expansionDeltaMessage.Unlocked = CPlayerData.m_IsWarehouseRoomUnlocked; break; default: throw new InvalidOperationException("Unknown expansion delta area " + kind + "."); } _context.Broadcast((INetMessage)(object)expansionDeltaMessage); } } private static ExpansionBaselineMessage BuildBaseline() { return new ExpansionBaselineMessage { UnlockRoomCount = CPlayerData.m_UnlockRoomCount, UnlockWarehouseRoomCount = CPlayerData.m_UnlockWarehouseRoomCount, IsWarehouseRoomUnlocked = CPlayerData.m_IsWarehouseRoomUnlocked }; } private static bool IsJoinPhase(ConnectionState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 2) { return (int)state == 3; } return true; } } internal static class ExpansionInterop { internal readonly struct SpendToken { internal bool IsValid => Reservation != null; internal EconomyAuthority.HostSpendReservation Reservation { get; } internal SpendToken(double amount, EconomyAuthority.HostSpendReservation reservation) { Reservation = reservation; } } internal readonly struct StateSnapshot { internal int Rooms { get; } internal int WarehouseRooms { get; } internal bool WarehouseUnlocked { get; } internal StateSnapshot(int rooms, int warehouseRooms, bool warehouseUnlocked) { Rooms = rooms; WarehouseRooms = warehouseRooms; WarehouseUnlocked = warehouseUnlocked; } } private static readonly MethodInfo MiEvaluatePanel = AccessTools.Method(typeof(ExpansionShopUIScreen), "EvaluateShopPanelUI", (Type[])null, (Type[])null); private static readonly MethodInfo MiInitializeRooms = AccessTools.Method(typeof(UnlockRoomManager), "Init", (Type[])null, (Type[])null); private static readonly FieldInfo FiShopBUnlockLevelRequired = ReflectionSurface.RequiredField(typeof(UnlockRoomManager), "m_ShopB_UnlockLevelRequired"); private static readonly FieldInfo FiShopBUnlockPrice = ReflectionSurface.RequiredField(typeof(UnlockRoomManager), "m_ShopB_UnlockPrice"); internal static bool ApplyingAuthoritativeState { get; private set; } internal static UnlockRoomManager FindUnlockManager() { return SceneRef.Get(); } internal static ExpansionShopUIScreen FindExpansionScreen() { return SceneRef.Get(); } internal static bool IsOpen(ExpansionShopUIScreen screen) { if ((Object)(object)screen == (Object)null) { return false; } try { return ((UIScreenBase)screen).IsScreenOpened(); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Expansion screen state could not be read: " + ex.Message)); return false; } } internal static void RefreshOpenScreen(ExpansionShopUIScreen screen) { if (IsOpen(screen)) { MiEvaluatePanel?.Invoke(screen, null); } } internal static void SaveShelfData() { ShelfManager obj = SceneRef.Get(); if (obj != null) { obj.SaveInteractableObjectData(false); } } internal static bool TryValidatePurchase(UnlockRoomManager manager, byte kind, out int index, out float cost, out string rejection) { index = -1; cost = 0f; rejection = null; if ((Object)(object)manager == (Object)null) { rejection = "unlock manager is unavailable"; return false; } switch (kind) { case 2: { int num3 = Convert.ToInt32(FiShopBUnlockLevelRequired.GetValue(manager)); cost = Convert.ToSingle(FiShopBUnlockPrice.GetValue(manager)); if (CPlayerData.m_IsWarehouseRoomUnlocked) { rejection = "shop lot B is already unlocked"; } else if (CPlayerData.m_ShopLevel + 1 < num3) { rejection = "shop level " + (CPlayerData.m_ShopLevel + 1) + " is below required level " + num3; } else if (!HasEnoughCoins(cost)) { rejection = "host has " + CPlayerData.m_CoinAmountDouble + " coins but needs " + cost; } return rejection == null; } default: rejection = "unsupported purchase kind " + kind; return false; case 0: case 1: { bool flag = kind == 1; index = (flag ? CPlayerData.m_UnlockWarehouseRoomCount : CPlayerData.m_UnlockRoomCount); int num = (flag ? manager.m_LockedWarehouseRoomBlockerList : manager.m_LockedRoomBlockerList)?.Count ?? 0; if (index < 0 || index >= num) { rejection = (flag ? "warehouse" : "shop") + " expansion index " + index + " is outside blocker count " + num; return false; } if (flag && !CPlayerData.m_IsWarehouseRoomUnlocked) { rejection = "warehouse room is not unlocked"; return false; } int num2 = (flag ? (20 + index * 5 + index / 4 * 10) : (Mathf.Clamp(index * 2 + 1, 2, 60) + index / 4 * 2)); if (CPlayerData.m_ShopLevel + 1 < num2) { rejection = (flag ? "warehouse" : "shop") + " expansion " + index + " requires shop level " + num2 + ", host is at " + (CPlayerData.m_ShopLevel + 1); return false; } cost = (flag ? CPlayerData.GetUnlockWarehouseRoomCost(index) : CPlayerData.GetUnlockShopRoomCost(index)); if (!HasEnoughCoins(cost)) { rejection = "host has " + CPlayerData.m_CoinAmountDouble + " coins but needs " + cost; return false; } return true; } } } internal static void ApplyBaseline(UnlockRoomManager manager, ExpansionBaselineMessage message) { ApplyingAuthoritativeState = true; try { CPlayerData.m_UnlockRoomCount = message.UnlockRoomCount; CPlayerData.m_UnlockWarehouseRoomCount = message.UnlockWarehouseRoomCount; CPlayerData.m_IsWarehouseRoomUnlocked = message.IsWarehouseRoomUnlocked; if ((Object)(object)manager != (Object)null) { MiInitializeRooms?.Invoke(manager, null); } } finally { ApplyingAuthoritativeState = false; } } internal static void ApplyDelta(UnlockRoomManager manager, ExpansionDeltaMessage message) { ApplyingAuthoritativeState = true; try { switch (message.Area) { case 0: CPlayerData.m_UnlockRoomCount = message.Count; break; case 1: CPlayerData.m_UnlockWarehouseRoomCount = message.Count; break; case 2: CPlayerData.m_IsWarehouseRoomUnlocked = message.Unlocked; break; default: throw new InvalidOperationException("Unknown expansion delta area " + message.Area + "."); } if ((Object)(object)manager != (Object)null) { MiInitializeRooms?.Invoke(manager, null); } } finally { ApplyingAuthoritativeState = false; } } internal static void InitializeRooms(UnlockRoomManager manager) { if ((Object)(object)manager != (Object)null) { MiInitializeRooms?.Invoke(manager, null); } } internal static bool TryReserveExpansion(double amount, out SpendToken token) { if (double.IsNaN(amount) || double.IsInfinity(amount) || amount < 0.0 || !EconomyAuthority.TryReserveHostSpend(amount, out var reservation)) { token = new SpendToken(amount, null); return false; } token = new SpendToken(amount, reservation); return true; } internal static bool CommitExpansion(SpendToken token) { if (token.IsValid) { return EconomyAuthority.QueueHostSpend(token.Reservation); } return false; } internal static void ReleaseExpansion(SpendToken token) { if (token.IsValid) { EconomyAuthority.ReleaseHostSpend(token.Reservation); } } internal static void CancelExpansion(SpendToken token) { if (token.IsValid) { EconomyAuthority.CancelHostSpend(token.Reservation); } } internal static StateSnapshot CaptureState() { return new StateSnapshot(CPlayerData.m_UnlockRoomCount, CPlayerData.m_UnlockWarehouseRoomCount, CPlayerData.m_IsWarehouseRoomUnlocked); } internal static void RollbackState(UnlockRoomManager manager, StateSnapshot snapshot) { CPlayerData.m_UnlockRoomCount = snapshot.Rooms; CPlayerData.m_UnlockWarehouseRoomCount = snapshot.WarehouseRooms; CPlayerData.m_IsWarehouseRoomUnlocked = snapshot.WarehouseUnlocked; MiInitializeRooms?.Invoke(manager, null); } private static bool HasEnoughCoins(double amount) { if (!double.IsNaN(amount) && !double.IsInfinity(amount) && amount >= 0.0) { return CPlayerData.m_CoinAmountDouble >= amount; } return false; } } [NetworkMessage] public sealed class ExpansionBaselineMessage : INetMessage { public int UnlockRoomCount; public int UnlockWarehouseRoomCount; public bool IsWarehouseRoomUnlocked; } [NetworkMessage] public sealed class ExpansionDeltaMessage : IPredictedMessage, INetMessage { public byte Area; public int Count; public bool Unlocked; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class ExpansionPurchaseMessage : IPredictedMessage, INetMessage { public byte Kind; public Guid PredictionId { get; set; } } } namespace CardShopCoop.Modules.Economy { public static class EconomyAuthority { public sealed class HostSpendReservation : IDisposable { private readonly Type _owner = typeof(EconomyAuthority); internal double Amount { get; } internal Type Owner => _owner; internal bool Consumed { get; set; } internal HostSpendReservation(double amount) { Amount = amount; } public void Dispose() { } } private static object _owner; private static CoopRuntimeContext _context; private static bool _suppressVanillaReduction; internal static double ReservedHostSpend => 0.0; internal static bool Activate(object owner, CoopRuntimeContext context) { if (owner == null || context == null) { return false; } if (_owner != null && _owner != owner) { throw new InvalidOperationException("The economy authority is already active."); } _owner = owner; _context = context; return true; } internal static void Deactivate(object owner) { if (owner != null && _owner == owner) { _suppressVanillaReduction = false; _owner = null; _context = null; } } public static bool TryReserveHostSpend(double amount, out HostSpendReservation reservation) { reservation = null; if (_owner == null || !IsValidAmount(amount) || CPlayerData.m_CoinAmountDouble < amount) { return false; } reservation = new HostSpendReservation(amount); return true; } public static bool QueueHostSpend(HostSpendReservation reservation) { return QueueHostSpend(reservation, null); } internal static bool QueueHostSpend(HostSpendReservation reservation, Action completion) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (!Owns(reservation) || reservation.Consumed) { return false; } if (reservation.Amount > 0.0) { if ((Object)(object)SceneRef.Get() == (Object)null) { throw new InvalidOperationException("The host wallet event manager is not ready."); } CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin((float)reservation.Amount, false)); } reservation.Consumed = true; completion?.Invoke(obj: true); return true; } internal static void RefundHostSpend(double amount) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if (_owner != null && IsValidAmount(amount) && !(amount <= 0.0) && !((Object)(object)SceneRef.Get() == (Object)null)) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin((float)amount, true)); } } public static void ReleaseHostSpend(HostSpendReservation reservation) { } public static void CancelHostSpend(HostSpendReservation reservation) { } internal static bool RunWithVanillaSpend(HostSpendReservation reservation, Func action) { if (!Owns(reservation) || action == null || !QueueHostSpend(reservation)) { return false; } _suppressVanillaReduction = true; try { return action(); } finally { _suppressVanillaReduction = false; } } internal static bool DeferVanillaEvent(CEvent evt) { if (!_suppressVanillaReduction || !(evt is CEventPlayer_ReduceCoin)) { return false; } _suppressVanillaReduction = false; return true; } private static bool Owns(HostSpendReservation reservation) { if (reservation != null && reservation.Owner == typeof(EconomyAuthority)) { return !reservation.Consumed; } return false; } private static bool IsValidAmount(double amount) { if (!double.IsNaN(amount) && !double.IsInfinity(amount) && amount >= 0.0) { return amount <= 3.4028234663852886E+38; } return false; } } [ServerBehaviour] public sealed class EconomyHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(CEventManager), "QueueEvent", new Type[] { typeof(CEvent) })] private static class QueueEventPatch { [HarmonyPrefix] private static bool Prefix(CEvent evt) { return !EconomyAuthority.DeferVanillaEvent(evt); } } private bool _active; private bool _shutdown; private Harmony _harmony; private void OnEnable() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if (_shutdown || _active) { return; } if (!EconomyAuthority.Activate(this, base.RuntimeContext)) { throw new InvalidOperationException("Economy host initialization failed."); } try { _harmony = new Harmony("dev.meepen.cardshopcoop.economy"); _harmony.CreateClassProcessor(typeof(QueueEventPatch)).Patch(); _active = true; } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; EconomyAuthority.Deactivate(this); throw; } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active) { EconomyAuthority.Deactivate(this); _active = false; } } } private void OnDestroy() { Shutdown(); } } } namespace CardShopCoop.Modules.Deodorant { [ClientBehaviour] public sealed class DeodorantClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(InteractionPlayerController), "RaycastHoldSprayState")] private static class SprayPatch { private struct SprayState { public bool Armed; public Vector3 Position; public float Before; } [HarmonyPrefix] private static void Prefix(InteractionPlayerController __instance, out SprayState __state) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) __state = default(SprayState); DeodorantClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active._joined && active._context != null && active._context.InGame() && DeodorantInterop.TryGetSpray(__instance, out var position) && DeodorantInterop.TryGetContent(__instance, out var content)) { __state = new SprayState { Armed = true, Position = position, Before = content }; } } [HarmonyPostfix] private static void Postfix(InteractionPlayerController __instance, SprayState __state) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) DeodorantClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && __state.Armed && DeodorantInterop.TryGetContent(__instance, out var content) && !(content >= __state.Before)) { active.Forward(__instance, __state.Position, __state.Before, content); } } } [HarmonyPatch(typeof(CustomerManager), "OnEnable")] private static class CustomerManagerReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.ApplyPendingStates(); } } private const string PredictionScope = "deodorant.spray"; private static DeodorantClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private readonly Dictionary _pendingCustomerStates = new Dictionary(); private void OnEnable() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; try { _active = this; _harmony = new Harmony("dev.meepen.cardshopcoop.deodorant.client"); _harmony.CreateClassProcessor(typeof(SprayPatch)).Patch(); _harmony.CreateClassProcessor(typeof(CustomerManagerReadyPatch)).Patch(); NpcClientBehaviour.CustomerPoolChanged += ApplyPendingStates; } catch (Exception arg) { CoopPlugin.Log.LogError((object)$"Deodorant client initialization failed: {arg}"); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; NpcClientBehaviour.CustomerPoolChanged -= ApplyPendingStates; if (_active == this) { _active = null; } _context = null; throw; } } [OnFullyJoined] private void Joined(PeerConnection _) { _joined = true; _pendingCustomerStates.Clear(); } [OnClientDisconnected] private void Disconnected(PeerConnection connection, DisconnectInfo _) { if (connection != null && connection.Id == 1) { _joined = false; _pendingCustomerStates.Clear(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; if (_active == this) { _active = null; } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; NpcClientBehaviour.CustomerPoolChanged -= ApplyPendingStates; _joined = false; _pendingCustomerStates.Clear(); _context = null; } } private void OnDestroy() { Shutdown(); } private void Forward(InteractionPlayerController controller, Vector3 position, float before, float after) { //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) if (_joined && _context != null && _context.InGame()) { PredictionApi.Predict("deodorant.spray", delegate(Guid predictionId) { //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) _context.Send(1, (INetMessage)(object)new HandheldSprayIntentMessage { PredictionId = predictionId, Position = position, Content = after }); }, delegate { DeodorantInterop.TrySetContent(controller, after); }, delegate { DeodorantInterop.TrySetContent(controller, before); }); } } [MessageHandler(typeof(HandheldSprayEventMessage))] private void HandleEvent(MessageContext _, HandheldSprayEventMessage message) { if (message != null) { PredictionApi.Confirm(message.PredictionId, delegate { ApplyEvent(message); }); } } private void ApplyEvent(HandheldSprayEventMessage message) { if (PredictionApi.IsReconciling) { DeodorantInterop.TrySetContent(SceneRef.Get(), message.Content); } ApplyCustomers(message.Customers); } private void ApplyPendingStates() { if (_pendingCustomerStates.Count == 0 || (Object)(object)SceneRef.Get() == (Object)null) { return; } List list = new List(_pendingCustomerStates.Values); for (int i = 0; i < list.Count; i++) { if (ApplyCustomerState(list[i])) { _pendingCustomerStates.Remove(CustomerKey(list[i])); } } } private void ApplyCustomers(IList states) { if (states == null) { return; } for (int i = 0; i < states.Count; i++) { DeodorantCustomerState deodorantCustomerState = states[i]; if (!ApplyCustomerState(deodorantCustomerState) && deodorantCustomerState != null) { string key = CustomerKey(deodorantCustomerState); _pendingCustomerStates[key] = deodorantCustomerState; } } } private bool ApplyCustomerState(DeodorantCustomerState state) { CustomerManager obj = SceneRef.Get(); List list = ((obj != null) ? obj.GetCustomerList() : null); if (state == null || list == null || state.Index < 0 || state.Index >= list.Count || (Object)(object)list[state.Index] == (Object)null) { return false; } if (!NpcClientBehaviour.TryGetCustomerGeneration(state.Index, out var generation)) { return false; } if (generation != state.Generation) { return generation > state.Generation; } DeodorantInterop.ApplyCustomerState(list[state.Index], state.IsSmelly, state.SmellyMeter); return true; } private static string CustomerKey(DeodorantCustomerState state) { return state.Index + ":" + state.Generation; } } [ServerBehaviour] public sealed class DeodorantHostBehaviour : CoopBehaviour { private CoopRuntimeContext _context; private bool _shutdown; private void OnEnable() { if (_shutdown || _context != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; } catch (Exception arg) { CoopPlugin.Log.LogError((object)$"Deodorant host initialization failed: {arg}"); if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } _context = null; throw; } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); _context = null; } } private void OnDestroy() { Shutdown(); } [MessageHandler(typeof(HandheldSprayIntentMessage))] private void Handle(MessageContext context, HandheldSprayIntentMessage message) { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) if (!IsAuthenticatedSender(context) || message == null) { return; } if (!IsFinite(message.Position.x) || !IsFinite(message.Position.y) || !IsFinite(message.Position.z) || !IsFinite(message.Content) || message.Content < 0f || message.Content > 1f) { Reject(context, message.PredictionId); return; } if (!_context.PeerPresence.TryGet(context.Connection.Id, out var presence)) { Reject(context, message.PredictionId); return; } if (presence.Age > TimeSpan.FromSeconds(2.0) || presence.Hold != 2 || !HasDeodorant(presence.HoldTypes)) { Reject(context, message.PredictionId); return; } if (Vector3.Distance(presence.Position, message.Position) > 3.5f) { Reject(context, message.PredictionId); return; } CustomerManager customerManager = GetCustomerManager(); if ((Object)(object)customerManager == (Object)null) { Reject(context, message.PredictionId); return; } List customerList = customerManager.GetCustomerList(); List list = new List(); for (int i = 0; i < customerList.Count; i++) { Customer val = customerList[i]; if (!((Object)(object)val == (Object)null)) { bool num = val.IsSmelly(); int smellyMeter = DeodorantInterop.GetSmellyMeter(val); val.DeodorantSprayCheck(message.Position, 2.5f, 1); bool flag = val.IsSmelly(); int smellyMeter2 = DeodorantInterop.GetSmellyMeter(val); if (num != flag || smellyMeter != smellyMeter2) { list.Add(new DeodorantCustomerState { Index = i, Generation = NpcHostBehaviour.GetCustomerGeneration(val), IsSmelly = flag, SmellyMeter = smellyMeter2 }); } } } _context.Broadcast((INetMessage)(object)new HandheldSprayEventMessage { PredictionId = message.PredictionId, Position = message.Position, Content = message.Content, Customers = list }); } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private CustomerManager GetCustomerManager() { return SceneRef.Get(); } private bool IsAuthenticatedSender(MessageContext context) { if (!_shutdown && _context.InGame() && context?.Connection != null) { return context.IsAuthenticated; } return false; } private void Reject(MessageContext context, Guid predictionId) { if (predictionId != Guid.Empty && context?.Connection != null) { PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, predictionId); } } private static bool HasDeodorant(IReadOnlyList holdTypes) { if (holdTypes == null) { return false; } for (int i = 0; i < holdTypes.Count; i++) { if (holdTypes[i] == 23) { return true; } } return false; } } internal static class DeodorantInterop { private static readonly FieldInfo HeldItem = AccessTools.Field(typeof(InteractionPlayerController), "m_CurrentHoldSprayItem"); private static readonly FieldInfo MouseDown = AccessTools.Field(typeof(InteractionPlayerController), "m_IsHoldingMouseDown"); private static readonly FieldInfo MouseDownTime = AccessTools.Field(typeof(InteractionPlayerController), "m_MouseDownTime"); private static readonly FieldInfo MouseDownTimeTotal = AccessTools.Field(typeof(InteractionPlayerController), "m_MouseDownTimeTotal"); private static readonly FieldInfo HoldAutoFireRate = AccessTools.Field(typeof(InteractionPlayerController), "m_MouseHoldAutoFireRate"); private static readonly FieldInfo CustomerSmelly = AccessTools.Field(typeof(Customer), "m_IsSmelly"); private static readonly FieldInfo CustomerSmellyMeter = AccessTools.Field(typeof(Customer), "m_SmellyMeter"); private static readonly FieldInfo CustomerSmellyFx = AccessTools.Field(typeof(Customer), "m_SmellyFX"); internal static bool IsHoldingSpray(InteractionPlayerController controller) { Item item; return TryGetHeldSpray(controller, out item); } internal static bool TryGetSpray(InteractionPlayerController controller, out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); if (!TryGetHeldSpray(controller, out var item)) { return false; } position = ((Component)item).transform.position; return IsFinite(position); } internal static bool TryGetContent(InteractionPlayerController controller, out float content) { content = 0f; if (!TryGetHeldSpray(controller, out var item)) { return false; } content = item.GetContentFill(); return IsFinite(content); } internal static bool TrySetContent(InteractionPlayerController controller, float content) { if (!TryGetHeldSpray(controller, out var item) || !IsFinite(content)) { return false; } item.SetContentFill(content); return true; } internal static int GetSmellyMeter(Customer customer) { if (!((Object)(object)customer == (Object)null) && !(CustomerSmellyMeter == null)) { return (int)(CustomerSmellyMeter.GetValue(customer) ?? ((object)0)); } return 0; } internal static void ApplyCustomerState(Customer customer, bool isSmelly, int smellyMeter) { if ((Object)(object)customer == (Object)null) { return; } CustomerSmelly?.SetValue(customer, isSmelly); CustomerSmellyMeter?.SetValue(customer, Math.Max(0, smellyMeter)); object? obj = CustomerSmellyFx?.GetValue(customer); object? obj2 = ((obj is GameObject) ? obj : null); if (obj2 != null) { ((GameObject)obj2).SetActive(isSmelly); } CustomerManager val = SceneRef.Get(); if (!((Object)(object)val == (Object)null)) { if (isSmelly) { val.AddToSmellyCustomerList(customer); } else { val.RemoveFromSmellyCustomerList(customer); } } } internal static bool TryGetSprayTick(InteractionPlayerController controller, out Vector3 position, out float before, out float after) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); before = 0f; after = 0f; if (!TryGetSpray(controller, out position) || !TryGetContent(controller, out before) || MouseDownTime == null || MouseDownTimeTotal == null || HoldAutoFireRate == null || !(MouseDownTime.GetValue(controller) is float num) || !(MouseDownTimeTotal.GetValue(controller) is float num2) || !(HoldAutoFireRate.GetValue(controller) is float num3)) { return false; } if (num + Time.deltaTime < num3 || num2 + Time.deltaTime <= 0.2f || before <= 0f) { return false; } after = Mathf.Max(0f, before - Time.fixedDeltaTime / 2.5f); return true; } private static bool TryGetHeldSpray(InteractionPlayerController controller, out Item item) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Invalid comparison between Unknown and I4 item = null; if (!((Object)(object)controller == (Object)null) && !(HeldItem == null) && !(MouseDown == null)) { object value = MouseDown.GetValue(controller); if (value is bool && (bool)value) { object? value2 = HeldItem.GetValue(controller); item = (Item)((value2 is Item) ? value2 : null); if ((Object)(object)item != (Object)null) { return (int)item.GetItemType() == 23; } return false; } } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(value.x) && IsFinite(value.y)) { return IsFinite(value.z); } return false; } } [NetworkMessage] public sealed class HandheldSprayIntentMessage : IPredictedMessage, INetMessage { public Vector3 Position; public float Content; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class HandheldSprayEventMessage : IPredictedMessage, INetMessage { public Vector3 Position; public float Content; public List Customers = new List(); public Guid PredictionId { get; set; } } public sealed class DeodorantCustomerState { public int Index; public int Generation; public bool IsSmelly; public int SmellyMeter; } } namespace CardShopCoop.Modules.Decoration { [ClientBehaviour] public sealed class DecorationClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(PlaceDecoUIScreen), "OnPressSwitchShopDeco")] private static class EquipPatch { private sealed class State { public DecorationSnapshot Before; public int Category; public int Index; public bool LotB; } [HarmonyPrefix] private static void Prefix(PlaceDecoUIScreen __instance, int shopDecoIndex, bool isShopLotB, out State __state) { __state = null; if (IsClientReady() && _active._applyingState == 0 && !PredictionApi.IsReconciling) { __state = new State { Before = DecorationInterop.Snapshot(authoritative: false), Category = DecorationInterop.CategoryFor(__instance), Index = shopDecoIndex, LotB = isShopLotB }; } } [HarmonyPostfix] private static void Postfix(State __state) { if (__state != null && IsClientReady() && _active._applyingState == 0 && !PredictionApi.IsReconciling) { DecorationIntentMessage message = new DecorationIntentMessage { Action = 1, Category = __state.Category, Index = __state.Index, LotB = __state.LotB }; Predict(message, delegate { DecorationInterop.ApplyDelta(new DecorationDeltaMessage { Action = message.Action, Category = message.Category, Index = message.Index, LotB = message.LotB }); }, delegate { UndoState(__state.Before); }); } } } private struct BuyCapture { public bool Armed; public DecorationSnapshot Before; public float UpgradeCostBefore; public float SupplyCostBefore; } [HarmonyPatch(typeof(ShopBuyDecoUIScreen), "OnPressBuyShopDeco")] private static class BuyPatch { [HarmonyPrefix] private static void Prefix(out BuyCapture __state) { CaptureBuy(out __state); } [HarmonyPostfix] private static void Postfix(int shopDecoIndex, BuyCapture __state) { ObserveBuy(new DecorationIntentMessage { Action = 2, Category = DecorationInterop.CategoryFor((ShopBuyDecoUIScreen)null), Index = shopDecoIndex }, __state); } [HarmonyFinalizer] private static void Finalizer(BuyCapture __state) { ReleaseBuy(__state); } } [HarmonyPatch(typeof(ShopBuyDecoUIScreen), "OnPressBuyShopDecoItem")] private static class BuyItemPatch { [HarmonyPrefix] private static void Prefix(out BuyCapture __state) { CaptureBuy(out __state); } [HarmonyPostfix] private static void Postfix(EDecoObject itemType, BuyCapture __state) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) ObserveBuy(new DecorationIntentMessage { Action = 3, DecorationType = itemType }, __state); } [HarmonyFinalizer] private static void Finalizer(BuyCapture __state) { ReleaseBuy(__state); } } [HarmonyPatch(typeof(InteractableObject), "PlaceMovedObject")] private static class PlaceAttemptPatch { [HarmonyPrefix] private static void Prefix(InteractableObject __instance) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) DecorationClientBehaviour active = _active; if (!((Object)(object)active == (Object)null) && IsClientReady() && active._applyingState == 0 && !PredictionApi.IsReconciling && !((Object)(object)__instance == (Object)null) && (int)__instance.m_DecoObjectType != 0 && __instance.GetIsMovingObject()) { _placingInstance = __instance; } } [HarmonyFinalizer] private static void Finalizer() { _placingInstance = null; } } [HarmonyPatch(typeof(InteractableObject), "OnPlacedMovedObject")] private static class PlacedPatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) DecorationClientBehaviour active = _active; if ((Object)(object)active == (Object)null || (Object)(object)__instance == (Object)null || _placingInstance != __instance || !IsClientReady() || active._applyingState != 0 || PredictionApi.IsReconciling) { return; } DecorationPose pose = DecorationInterop.ReadPose(__instance); long num = DecorationInterop.ClientIdFor(__instance); DecorationIntentMessage message = new DecorationIntentMessage { Action = 4, DecorationType = pose.DecorationType, Position = pose.Position, Rotation = pose.Rotation, Vertical = pose.Vertical, WarehouseWallSnap = pose.WarehouseWallSnap, WallIndex = pose.Wall, ObjectId = num }; InteractableObject instance = __instance; CoopPlugin.Log.LogInfo((object)("[decoration] place committed id=" + num + " type=" + ((object)Unsafe.As(ref pose.DecorationType)/*cast due to .constrained prefix*/).ToString() + ".")); Predict(message, delegate { DecorationInterop.ApplyPredictedPose(instance, pose); InteractionPlayerController obj = SceneRef.Get(); if (obj != null) { obj.OnExitMoveObjectMode(); } }, delegate { }, (num > 0) ? null : __instance); } } [HarmonyPatch(typeof(InteractableObject), "BoxUpObject")] private static class RemovePatch { private sealed class State { public DecorationSnapshot Before; public long ObjectId; public EDecoObject Type; public InteractableObject Instance; } [HarmonyPrefix] private static void Prefix(InteractableObject __instance, out State __state) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) __state = null; if (IsClientReady() && _active._applyingState == 0 && !((Object)(object)__instance == (Object)null) && (int)__instance.m_DecoObjectType != 0) { long num = DecorationInterop.ClientIdFor(__instance); if (num > 0) { __state = new State { Before = DecorationInterop.Snapshot(authoritative: false), ObjectId = num, Type = __instance.m_DecoObjectType, Instance = __instance }; } } } [HarmonyPostfix] private static void Postfix(State __state) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (__state != null && IsClientReady() && _active._applyingState == 0 && !PredictionApi.IsReconciling) { DecorationIntentMessage message = new DecorationIntentMessage { Action = 5, DecorationType = __state.Type, ObjectId = __state.ObjectId }; InteractableObject instance = __state.Instance; Predict(message, delegate { DecorationInterop.FinalizeMovedObject(instance); DecorationInterop.RemoveLocalPlacedObject(instance); }, delegate { UndoState(__state.Before); }); } } } [HarmonyPatch(typeof(ShelfManager), "Awake")] private static class ShelfReadyPatch { [HarmonyPostfix] private static void Postfix() { _active?.OnShelfReady(); } } private static DecorationClientBehaviour _active; private static InteractableObject _placingInstance; private CoopRuntimeContext _context; private Harmony _harmony; private DecorationStateMessage _pendingState; private readonly Dictionary _placementPreviews = new Dictionary(); private bool _shutdown; private int _applyingState; private bool _joined; private void OnEnable() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; bool flag2 = false; try { ResetSessionState(); DecorationInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; PredictionApi.PredictionRetired += OnPredictionRetired; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; flag2 = true; _harmony = new Harmony("dev.meepen.cardshopcoop.decoration.client"); _harmony.CreateClassProcessor(typeof(EquipPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BuyPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BuyItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlaceAttemptPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlacedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(RemovePatch)).Patch(); _harmony.CreateClassProcessor(typeof(ShelfReadyPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Decoration client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag2) { CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; } if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } PredictionApi.PredictionRetired -= OnPredictionRetired; if (_active == this) { _active = null; } ResetSessionState(); DecorationInterop.Reset(); _context = null; throw; } } [MessageHandler(typeof(DecorationStateMessage))] private void HandleState(MessageContext context, DecorationStateMessage message) { if (!_shutdown) { _pendingState = message; TryApplyPending(); } } [MessageHandler(typeof(DecorationDeltaMessage))] private void HandleDelta(MessageContext context, DecorationDeltaMessage message) { if (!_shutdown) { InteractableObject preview = null; if (message.Action == 4 && _placementPreviews.TryGetValue(message.PredictionId, out var value)) { preview = value; _placementPreviews.Remove(message.PredictionId); } PredictionApi.Confirm(message.PredictionId, delegate { ApplyDelta(message, preview); }); } } private void OnPredictionRetired(Guid predictionId) { if (_placementPreviews.TryGetValue(predictionId, out var value)) { _placementPreviews.Remove(predictionId); DecorationInterop.DiscardPreview(value); } } private static void ApplyDelta(DecorationDeltaMessage message, InteractableObject preview = null) { if ((Object)(object)_active != (Object)null) { _active._applyingState++; } try { DecorationInterop.ApplyDelta(message, preview); } finally { if ((Object)(object)_active != (Object)null) { _active._applyingState--; } } } private void TryApplyPending() { if (_shutdown || _pendingState == null || _context == null || !_context.InGame() || !DecorationInterop.IsSceneReady()) { return; } _applyingState++; try { DecorationInterop.ApplySnapshot(_pendingState, removeUnlisted: true, _placementPreviews.ContainsValue); _pendingState = null; } finally { _applyingState--; } } private void Send(DecorationIntentMessage message) { if (!_shutdown && _joined && _context != null && _context.InGame() && message != null) { _context.Send(1, (INetMessage)(object)message); } } private static void Predict(DecorationIntentMessage message, Action apply, Action undo, InteractableObject preview = null) { DecorationClientBehaviour client = _active; if ((Object)(object)client == (Object)null || client._shutdown || !client._joined || client._context == null || !client._context.InGame()) { return; } PredictionApi.Predict("decoration", delegate(Guid predictionId) { message.PredictionId = predictionId; if ((Object)(object)preview != (Object)null) { client._placementPreviews[predictionId] = preview; } client._context.Send(1, (INetMessage)(object)message); }, apply, undo); } private static void Charge(float amount) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (amount > 0.0001f) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ReduceCoin(amount, false)); } } private static void Refund(float amount) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown if (amount > 0.0001f) { CEventManager.QueueEvent((CEvent)new CEventPlayer_AddCoin(amount, true)); } } private static DecorationStateMessage SnapshotMessage(DecorationSnapshot snapshot) { return new DecorationStateMessage { Wall = snapshot.Wall, WallB = snapshot.WallB, Floor = snapshot.Floor, FloorB = snapshot.FloorB, Ceiling = snapshot.Ceiling, CeilingB = snapshot.CeilingB, WallUnlocks = snapshot.WallUnlocks, FloorUnlocks = snapshot.FloorUnlocks, CeilingUnlocks = snapshot.CeilingUnlocks, Inventory = snapshot.Inventory, Placed = snapshot.Placed }; } private static void PredictState(DecorationIntentMessage message, DecorationSnapshot before) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) DecorationDeltaMessage decorationDeltaMessage = new DecorationDeltaMessage { Action = message.Action, Category = message.Category, Index = message.Index, DecorationType = message.DecorationType, LotB = message.LotB, ObjectId = message.ObjectId, Pose = ((message.Action == 4) ? new DecorationPose { Id = message.ObjectId, DecorationType = message.DecorationType, Position = message.Position, Rotation = message.Rotation, Vertical = message.Vertical, WarehouseWallSnap = message.WarehouseWallSnap, Wall = message.WallIndex } : null) }; if (message.Action == 3) { decorationDeltaMessage.InventoryCount = DecorationInterop.InventoryCount(message.DecorationType) + 1; } ApplyDelta(decorationDeltaMessage); if (message.Action == 4) { InteractionPlayerController obj = SceneRef.Get(); if (obj != null) { obj.OnExitMoveObjectMode(); } } } private static void UndoState(DecorationSnapshot before) { if ((Object)(object)_active != (Object)null) { _active._applyingState++; } try { DecorationInterop.ApplySnapshot(SnapshotMessage(before), removeUnlisted: false); } finally { if ((Object)(object)_active != (Object)null) { _active._applyingState--; } } } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { DecorationInterop.Reset(); TryApplyPending(); } private void OnShelfReady() { TryApplyPending(); } private static bool IsClientReady() { if ((Object)(object)_active != (Object)null && !_active._shutdown && _active._joined && _active._context != null) { return _active._context.InGame(); } return false; } private void ResetSessionState() { _joined = false; _pendingState = null; _placingInstance = null; _placementPreviews.Clear(); _applyingState = 0; } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; TryApplyPending(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ResetSessionState(); } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); PredictionApi.PredictionRetired -= OnPredictionRetired; CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } ResetSessionState(); DecorationInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } private static void CaptureBuy(out BuyCapture state) { state = default(BuyCapture); if (IsClientReady()) { state.Armed = true; state.Before = DecorationInterop.Snapshot(authoritative: false); state.UpgradeCostBefore = CPlayerData.m_GameReportDataCollectPermanent.upgradeCost; state.SupplyCostBefore = CPlayerData.m_GameReportDataCollectPermanent.supplyCost; EconomyActionScope.Enter(); } } private static void ReleaseBuy(BuyCapture state) { if (state.Armed) { EconomyActionScope.Exit(); } } private static void ObserveBuy(DecorationIntentMessage message, BuyCapture state) { if (!state.Armed) { return; } DecorationClientBehaviour active = _active; if ((Object)(object)active == (Object)null || active._shutdown || !active._joined || active._context == null || !active._context.InGame()) { return; } float spent = state.UpgradeCostBefore - CPlayerData.m_GameReportDataCollectPermanent.upgradeCost + (state.SupplyCostBefore - CPlayerData.m_GameReportDataCollectPermanent.supplyCost); if (!(spent <= 0.0001f)) { DecorationSnapshot before = state.Before; Predict(message, delegate { PredictState(message, before); Charge(spent); }, delegate { UndoState(before); Refund(spent); }); } } } [ServerBehaviour] public sealed class DecorationHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(PlaceDecoUIScreen), "OnPressSwitchShopDeco")] private static class EquipPatch { [HarmonyPostfix] private static void Postfix(int shopDecoIndex, bool isShopLotB) { if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(new DecorationDeltaMessage { Action = 1, Category = DecorationInterop.CategoryFor((PlaceDecoUIScreen)null), Index = shopDecoIndex, LotB = isShopLotB, PredictionId = Guid.Empty }); } } } [HarmonyPatch(typeof(ShopBuyDecoUIScreen), "OnPressBuyShopDeco")] private static class BuyPatch { [HarmonyPostfix] private static void Postfix(int shopDecoIndex) { if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(new DecorationDeltaMessage { Action = 2, Category = DecorationInterop.CategoryFor((ShopBuyDecoUIScreen)null), Index = shopDecoIndex, PredictionId = Guid.Empty }); } } } [HarmonyPatch(typeof(ShopBuyDecoUIScreen), "OnPressBuyShopDecoItem")] private static class BuyItemPatch { [HarmonyPostfix] private static void Postfix(EDecoObject itemType) { //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_0033: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(new DecorationDeltaMessage { Action = 3, DecorationType = itemType, InventoryCount = DecorationInterop.InventoryCount(itemType), PredictionId = Guid.Empty }); } } } [HarmonyPatch(typeof(InteractableObject), "PlaceMovedObject")] private static class PlaceAttemptPatch { [HarmonyPrefix] private static void Prefix(InteractableObject __instance) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent && !((Object)(object)__instance == (Object)null) && (int)__instance.m_DecoObjectType != 0 && __instance.GetIsMovingObject()) { _placingInstance = __instance; } } [HarmonyFinalizer] private static void Finalizer() { _placingInstance = null; } } [HarmonyPatch(typeof(InteractableObject), "OnPlacedMovedObject")] private static class PlacedPatch { [HarmonyPostfix] private static void Postfix(InteractableObject __instance) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) DecorationHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && !active._applyingIntent && !((Object)(object)__instance == (Object)null) && _placingInstance == __instance && (int)__instance.m_DecoObjectType != 0) { DecorationDeltaMessage decorationDeltaMessage = BuildDelta(new DecorationIntentMessage { Action = 4, DecorationType = __instance.m_DecoObjectType, ObjectId = DecorationInterop.HostIdFor(__instance) }, __instance); if (decorationDeltaMessage != null) { CoopPlugin.Log.LogInfo((object)("[decoration] host placed id=" + decorationDeltaMessage.ObjectId + " type=" + ((object)Unsafe.As(ref decorationDeltaMessage.DecorationType)/*cast due to .constrained prefix*/).ToString() + ".")); active.BroadcastDelta(decorationDeltaMessage); } } } } [HarmonyPatch(typeof(InteractableObject), "BoxUpObject")] private static class RemovePatch { private struct State { public long Id; public EDecoObject Type; } [HarmonyPrefix] private static void Prefix(InteractableObject __instance, out State __state) { //IL_0019: 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) __state = new State { Id = DecorationInterop.HostIdFor(__instance), Type = __instance.m_DecoObjectType }; } [HarmonyPostfix] private static void Postfix(InteractableObject __instance, State __state) { //IL_0023: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent && !((Object)(object)__instance == (Object)null) && (int)__instance.m_DecoObjectType != 0) { _active.BroadcastDelta(new DecorationDeltaMessage { Action = 5, ObjectId = __state.Id, DecorationType = __state.Type, InventoryCount = DecorationInterop.InventoryCount(__state.Type), PredictionId = Guid.Empty }); } } } private static DecorationHostBehaviour _active; private static InteractableObject _placingInstance; private readonly HashSet _fullyJoined = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _applyingIntent; private void OnEnable() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { DecorationInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.decoration.host"); _harmony.CreateClassProcessor(typeof(EquipPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BuyPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BuyItemPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlaceAttemptPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PlacedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(RemovePatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Decoration host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } DecorationInterop.Reset(); _context = null; throw; } } [OnFullyJoined] private void SendInitialState(PeerConnection connection) { if (connection != null) { _fullyJoined.Add(connection.Id); SendState(connection.Id); } } [OnClientDisconnected] private void ForgetConnection(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _fullyJoined.Remove(connection.Id); } } [MessageHandler(typeof(DecorationIntentMessage))] private void HandleIntent(MessageContext context, DecorationIntentMessage message) { if (!IsPeerMessage(context) || message == null) { return; } bool flag = false; InteractableObject placed = null; try { _applyingIntent = true; try { flag = ApplyIntent(message, out placed); } finally { _applyingIntent = false; } } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Decoration host action failed action=" + message.Action + ": " + ex)); } if (flag) { DecorationDeltaMessage decorationDeltaMessage = BuildDelta(message, placed); if (decorationDeltaMessage != null) { BroadcastDelta(decorationDeltaMessage); } } else if (message.PredictionId != Guid.Empty) { CoopPlugin.Log.LogInfo((object)("[decoration] intent rejected action=" + message.Action + " type=" + ((object)Unsafe.As(ref message.DecorationType)/*cast due to .constrained prefix*/).ToString() + " prediction=" + message.PredictionId.ToString() + ".")); PredictionApi.Rollback((ICoopContext)(object)_context, context.Connection.Id, message.PredictionId); SendState(context.Connection.Id); } } private bool ApplyIntent(DecorationIntentMessage message, out InteractableObject placed) { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) placed = null; switch (message.Action) { case 1: if (message.Category >= 0 && message.Category <= 2 && message.Index >= 0 && DecorationInterop.IsUnlocked(message.Category, message.Index)) { return DecorationInterop.TryEquip(message.Category, message.Index, message.LotB); } return false; case 2: { if (DecorationInterop.TryGetCategoryPrice(message.Category, message.Index, out var shopPrice) && !DecorationInterop.IsUnlocked(message.Category, message.Index)) { return TryApplyReservedPurchase(shopPrice, "category=" + message.Category + " index=" + message.Index, () => DecorationInterop.TryBuyCategory(message.Category, message.Index, shopPrice)); } return false; } case 3: { if (DecorationInterop.TryGetItemPrice(message.DecorationType, out var itemPrice)) { return TryApplyReservedPurchase(itemPrice, "item=" + ((object)Unsafe.As(ref message.DecorationType)/*cast due to .constrained prefix*/).ToString(), () => DecorationInterop.TryBuyItem(message.DecorationType, itemPrice)); } return false; } case 4: { if (!DecorationInterop.ValidPose(message.Position, message.Rotation) || message.WallIndex < -1 || message.WallIndex > 1024 || (message.ObjectId <= 0 && !DecorationInterop.HasInventory(message.DecorationType))) { return false; } DecorationPose pose = new DecorationPose { DecorationType = message.DecorationType, Position = message.Position, Rotation = message.Rotation, Vertical = message.Vertical, WarehouseWallSnap = message.WarehouseWallSnap, Wall = message.WallIndex }; bool flag = message.ObjectId <= 0; if (flag) { DecorationInterop.AdjustInventory(message.DecorationType, -1); } if (!DecorationInterop.TryPlace(pose, message.ObjectId, out placed)) { if (flag) { DecorationInterop.AdjustInventory(message.DecorationType, 1); } return false; } return true; } case 5: if (message.ObjectId > 0) { return DecorationInterop.TryRemove(message.ObjectId); } return false; default: return false; } } private static bool TryApplyReservedPurchase(float price, string context, Func applyVanilla) { if (!EconomyAuthority.TryReserveHostSpend(price, out var reservation)) { return false; } try { return EconomyAuthority.RunWithVanillaSpend(reservation, applyVanilla); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Decoration purchase failed (" + context + "): " + ex)); reservation.Dispose(); return false; } } private void BroadcastDelta(DecorationDeltaMessage message) { if (!_shutdown && _context != null && _context.InGame() && message != null) { _context.Broadcast((INetMessage)(object)message); } } private void SendState(int peer) { if (!_shutdown && _context != null && _context.InGame() && TryBuildState(out var message)) { _context.Send(peer, (INetMessage)(object)message); } } private bool TryBuildState(out DecorationStateMessage message) { message = null; if (_context == null || !_context.InGame() || !DecorationInterop.IsSceneReady()) { return false; } DecorationSnapshot decorationSnapshot = DecorationInterop.Snapshot(); message = new DecorationStateMessage { Wall = decorationSnapshot.Wall, WallB = decorationSnapshot.WallB, Floor = decorationSnapshot.Floor, FloorB = decorationSnapshot.FloorB, Ceiling = decorationSnapshot.Ceiling, CeilingB = decorationSnapshot.CeilingB, WallUnlocks = decorationSnapshot.WallUnlocks, FloorUnlocks = decorationSnapshot.FloorUnlocks, CeilingUnlocks = decorationSnapshot.CeilingUnlocks, Inventory = decorationSnapshot.Inventory, Placed = decorationSnapshot.Placed }; return true; } private bool IsPeerMessage(MessageContext context) { if (!_shutdown && context?.Connection != null) { return _fullyJoined.Contains(context.Connection.Id); } return false; } private static DecorationDeltaMessage BuildDelta(DecorationIntentMessage intent, InteractableObject placed) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) DecorationDeltaMessage decorationDeltaMessage = new DecorationDeltaMessage { PredictionId = intent.PredictionId, Action = intent.Action, Category = intent.Category, Index = intent.Index, DecorationType = intent.DecorationType, LotB = intent.LotB, ObjectId = intent.ObjectId }; if (intent.Action == 3) { decorationDeltaMessage.InventoryCount = DecorationInterop.InventoryCount(intent.DecorationType); } else if (intent.Action == 5) { decorationDeltaMessage.InventoryCount = DecorationInterop.InventoryCount(intent.DecorationType); } else if (intent.Action == 4) { if ((Object)(object)placed == (Object)null) { return null; } DecorationPose decorationPose = DecorationInterop.ReadPose(placed); decorationPose.Id = DecorationInterop.EnsureHostId(placed); decorationDeltaMessage.Pose = decorationPose; decorationDeltaMessage.ObjectId = decorationPose.Id; decorationDeltaMessage.InventoryCount = DecorationInterop.InventoryCount(intent.DecorationType); } return decorationDeltaMessage; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { DecorationInterop.Reset(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _fullyJoined.Clear(); DecorationInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class DecorationInterop { private sealed class ReferenceComparer : IEqualityComparer { public bool Equals(InteractableObject left, InteractableObject right) { return left == right; } public int GetHashCode(InteractableObject value) { return RuntimeHelpers.GetHashCode(value); } } private static readonly MethodInfo FindObjectsWithInactive = typeof(Object).GetMethod("FindObjectsOfType", new Type[2] { typeof(Type), typeof(bool) }); private static readonly MethodInfo FindObjects = typeof(Object).GetMethod("FindObjectsOfType", new Type[1] { typeof(Type) }); private static readonly FieldInfo FiWall = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_EquippedWallDecoIndex"); private static readonly FieldInfo FiWallB = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_EquippedWallDecoIndexB"); private static readonly FieldInfo FiFloor = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_EquippedFloorDecoIndex"); private static readonly FieldInfo FiFloorB = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_EquippedFloorDecoIndexB"); private static readonly FieldInfo FiCeiling = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_EquippedCeilingDecoIndex"); private static readonly FieldInfo FiCeilingB = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_EquippedCeilingDecoIndexB"); private static readonly FieldInfo FiWallUnlocks = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_UnlockedDecoWallList"); private static readonly FieldInfo FiFloorUnlocks = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_UnlockedDecoFloorList"); private static readonly FieldInfo FiCeilingUnlocks = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_UnlockedDecoCeilingList"); private static readonly FieldInfo FiInventory = ReflectionSurface.RequiredField(typeof(CPlayerData), "m_DecorationInventoryList"); private static readonly MethodInfo MiAddInventory = ReflectionSurface.RequiredMethod(typeof(CPlayerData), "AddDecoItemToInventory", typeof(EDecoObject), typeof(int)); private static readonly MethodInfo MiGetInventory = ReflectionSurface.RequiredMethod(typeof(CPlayerData), "GetDecoItemInventoryCount", typeof(EDecoObject)); private static readonly MethodInfo MiSpawn = ReflectionSurface.RequiredMethod(typeof(ShelfManager), "SpawnDecoObject", typeof(EDecoObject)); private static readonly MethodInfo MiGetList = ReflectionSurface.RequiredMethod(typeof(ShelfManager), "GetDecoObjectList"); private static readonly MethodInfo MiRemoveFromList = ReflectionSurface.RequiredMethod(typeof(ShelfManager), "RemoveDecoObject", typeof(InteractableObject)); private static readonly MethodInfo MiInit = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "Init"); private static readonly MethodInfo MiStartMoveObject = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "StartMoveObject"); private static readonly MethodInfo MiSetTargetMovePosition = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "SetTargetMovePosition", typeof(Vector3), typeof(Vector3), typeof(Transform)); private static readonly MethodInfo MiUpdate = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "Update"); private static readonly MethodInfo MiPlaceMovedObject = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "PlaceMovedObject"); private static readonly MethodInfo MiOnPlacedMovedObject = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "OnPlacedMovedObject"); private static readonly MethodInfo MiOnDestroyed = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "OnDestroyed"); private static readonly MethodInfo MiBoxUpObject = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "BoxUpObject", typeof(bool)); private static readonly MethodInfo MiSetSnap = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "SetVerticalSnapToWarehouseWall", typeof(bool), typeof(int)); private static readonly FieldInfo FiValidPlacement = ReflectionSurface.RequiredField(typeof(InteractableObject), "m_IsMovingObjectValidState"); private static readonly FieldInfo FiObjectType = ReflectionSurface.RequiredField(typeof(InteractableObject), "m_DecoObjectType"); private static readonly FieldInfo FiVertical = ReflectionSurface.RequiredField(typeof(InteractableObject), "m_IsDecorationVertical"); private static readonly FieldInfo FiPlacedBlocker = ReflectionSurface.RequiredField(typeof(InteractableObject), "m_DecoPlaccedLockedRoomBlocker"); private static readonly MethodInfo MiGetSnap = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "GetIsVerticalSnapToWarehouseWall"); private static readonly MethodInfo MiGetWall = ReflectionSurface.RequiredMethod(typeof(InteractableObject), "GetVerticalSnapWallIndex"); private static readonly FieldInfo FiShopCategory = ReflectionSurface.RequiredField(typeof(ShopBuyDecoUIScreen), "m_CategoryIndex"); private static readonly MethodInfo MiBuyShop = ReflectionSurface.RequiredMethod(typeof(ShopBuyDecoUIScreen), "OnPressBuyShopDeco", typeof(int), typeof(float)); private static readonly MethodInfo MiBuyItem = ReflectionSurface.RequiredMethod(typeof(ShopBuyDecoUIScreen), "OnPressBuyShopDecoItem", typeof(EDecoObject), typeof(float)); private static readonly MethodInfo MiSwitchShop = ReflectionSurface.RequiredMethod(typeof(PlaceDecoUIScreen), "OnPressSwitchShopDeco", typeof(int), typeof(bool)); private static readonly FieldInfo FiObjectData = ReflectionSurface.RequiredField(typeof(InventoryBase), "m_ObjectData_SO"); private static readonly FieldInfo FiWallData = ReflectionSurface.RequiredField(typeof(ShelfData_ScriptableObject), "m_WallDecoDataList"); private static readonly FieldInfo FiFloorData = ReflectionSurface.RequiredField(typeof(ShelfData_ScriptableObject), "m_FloorDecoDataList"); private static readonly FieldInfo FiCeilingData = ReflectionSurface.RequiredField(typeof(ShelfData_ScriptableObject), "m_CeilingDecoDataList"); private static readonly FieldInfo FiItemData = ReflectionSurface.RequiredField(typeof(ShelfData_ScriptableObject), "m_DecoPurchaseDataList"); private static readonly FieldInfo FiPrice = ReflectionSurface.RequiredField(typeof(ShopDecoData), "price"); private static readonly FieldInfo FiItemPrice = ReflectionSurface.RequiredField(typeof(DecoPurchaseData), "price"); private static readonly Dictionary HostIds = new Dictionary(new ReferenceComparer()); private static readonly Dictionary ClientObjects = new Dictionary(); private static readonly Dictionary LocalIds = new Dictionary(new ReferenceComparer()); private static readonly Dictionary LocalObjects = new Dictionary(); private static long _nextHostId = 1L; private static long _nextLocalId = -1L; internal static void Reset() { HostIds.Clear(); ClientObjects.Clear(); LocalIds.Clear(); LocalObjects.Clear(); _nextHostId = 1L; _nextLocalId = -1L; } internal static DecorationSnapshot Snapshot(bool authoritative = true) { //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) if (!IsSceneReady()) { throw new InvalidOperationException("Decoration scene is not ready."); } DecorationSnapshot decorationSnapshot = new DecorationSnapshot { Wall = GetInt(FiWall), WallB = GetInt(FiWallB), Floor = GetInt(FiFloor), FloorB = GetInt(FiFloorB), Ceiling = GetInt(FiCeiling), CeilingB = GetInt(FiCeilingB), WallUnlocks = CopyBools(FiWallUnlocks), FloorUnlocks = CopyBools(FiFloorUnlocks), CeilingUnlocks = CopyBools(FiCeilingUnlocks), Inventory = CopyInts(FiInventory) }; List liveDecorations = GetLiveDecorations(); HashSet hashSet = new HashSet(liveDecorations, new ReferenceComparer()); List list = new List(); foreach (KeyValuePair hostId in HostIds) { if (!hashSet.Contains(hostId.Key)) { list.Add(hostId.Key); } } for (int i = 0; i < list.Count; i++) { HostIds.Remove(list[i]); } List list2 = new List(); foreach (KeyValuePair localId in LocalIds) { if (!hashSet.Contains(localId.Key)) { list2.Add(localId.Key); } } for (int j = 0; j < list2.Count; j++) { InteractableObject key = list2[j]; if (LocalIds.TryGetValue(key, out var value)) { LocalIds.Remove(key); LocalObjects.Remove(value); } } for (int k = 0; k < liveDecorations.Count; k++) { InteractableObject val = liveDecorations[k]; if (!((Object)(object)val == (Object)null) && !val.GetIsMovingObject()) { long id = EnsureSnapshotId(val, authoritative); decorationSnapshot.Placed.Add(new DecorationPose { Id = id, DecorationType = (EDecoObject)FiObjectType.GetValue(val), Position = ((Component)val).transform.position, Rotation = ((Component)val).transform.rotation, Vertical = GetVertical(val), WarehouseWallSnap = GetWarehouseWallSnap(val), Wall = GetWallIndex(val) }); } } return decorationSnapshot; } private static long EnsureSnapshotId(InteractableObject obj, bool authoritative) { if ((Object)(object)obj == (Object)null) { return 0L; } if (TryGetHoldId(obj, out var id)) { return id; } if (authoritative) { return EnsureHostId(obj); } if (!LocalIds.TryGetValue(obj, out var value)) { value = _nextLocalId--; LocalIds.Add(obj, value); LocalObjects.Add(value, obj); } return value; } internal static void ApplySnapshot(DecorationStateMessage message, bool removeUnlisted = true, Func keepUnlisted = null) { ApplyList(FiWallUnlocks, message.WallUnlocks); ApplyList(FiFloorUnlocks, message.FloorUnlocks); ApplyList(FiCeilingUnlocks, message.CeilingUnlocks); ApplyList(FiInventory, message.Inventory); SetInt(FiWall, message.Wall); SetInt(FiWallB, message.WallB); SetInt(FiFloor, message.Floor); SetInt(FiFloorB, message.FloorB); SetInt(FiCeiling, message.Ceiling); SetInt(FiCeilingB, message.CeilingB); ApplyMaterials(message); Reconcile(message.Placed, removeUnlisted, keepUnlisted); RefreshUi(); } internal static bool IsUnlocked(int category, int index) { if (UnlockField(category)?.GetValue(null) is IList list && index >= 0 && index < list.Count) { return Convert.ToBoolean(list[index]); } return false; } internal static bool TryGetCategoryPrice(int category, int index, out float price) { price = 0f; IList list = CategoryData(category); if (list == null || index < 0 || index >= list.Count || list[index] == null) { return false; } price = Convert.ToSingle(FiPrice.GetValue(list[index])); if (price >= 0f && !float.IsNaN(price)) { return !float.IsInfinity(price); } return false; } internal static bool TryGetItemPrice(EDecoObject type, out float price) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected I4, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) price = 0f; int num = (int)type; IList list = ItemData(); if (!IsValidDeco(type) || list == null || num < 0 || num >= list.Count || list[num] == null) { return false; } price = Convert.ToSingle(FiItemPrice.GetValue(list[num])); if (price >= 0f && !float.IsNaN(price)) { return !float.IsInfinity(price); } return false; } internal static bool HasInventory(EDecoObject type) { //IL_0000: 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) if (!IsValidDeco(type)) { return false; } return Convert.ToInt32(MiGetInventory.Invoke(null, new object[1] { type })) > 0; } internal static int InventoryCount(EDecoObject type) { //IL_0000: 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) if (!IsValidDeco(type)) { return 0; } return Convert.ToInt32(MiGetInventory.Invoke(null, new object[1] { type })); } internal static void AdjustInventory(EDecoObject type, int amount) { //IL_0000: 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_000d: Unknown result type (might be due to invalid IL or missing references) if (!IsValidDeco(type)) { throw new ArgumentOutOfRangeException("type", type, "Unknown decoration type."); } MiAddInventory.Invoke(null, new object[2] { type, amount }); } internal static void ApplyInventoryCount(EDecoObject type, int count) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (count >= 0 && IsValidDeco(type)) { int num = InventoryCount(type); if (num != count) { MiAddInventory.Invoke(null, new object[2] { type, count - num }); } } } internal static bool TryBuyCategory(int category, int index, float price) { object obj = FindFirst(typeof(ShopBuyDecoUIScreen)); if (obj == null) { return false; } int num = Convert.ToInt32(FiShopCategory.GetValue(obj)); try { FiShopCategory.SetValue(obj, category); MiBuyShop.Invoke(obj, new object[2] { index, price }); } finally { FiShopCategory.SetValue(obj, num); } return IsUnlocked(category, index); } internal static bool TryBuyItem(EDecoObject type, float price) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) object obj = FindFirst(typeof(ShopBuyDecoUIScreen)); if (obj == null || !IsValidDeco(type)) { return false; } int num = Convert.ToInt32(MiGetInventory.Invoke(null, new object[1] { type })); MiBuyItem.Invoke(obj, new object[2] { type, price }); return Convert.ToInt32(MiGetInventory.Invoke(null, new object[1] { type })) > num; } internal static bool TryEquip(int category, int index, bool lotB) { object obj = FindFirst(typeof(PlaceDecoUIScreen)); if (obj == null) { return false; } int num = Convert.ToInt32(FiShopCategory.GetValue(obj)); try { FiShopCategory.SetValue(obj, category); MiSwitchShop.Invoke(obj, new object[2] { index, lotB }); } finally { FiShopCategory.SetValue(obj, num); } return EquippedIndex(category, lotB) == index; } internal static bool TryPlace(DecorationPose pose, out InteractableObject placed) { return TryPlace(pose, 0L, out placed); } internal static bool TryPlace(DecorationPose pose, long existingId, out InteractableObject placed) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) placed = null; if (pose == null || !IsValidDeco(pose.DecorationType) || !ValidPose(pose.Position, pose.Rotation) || !ValidWallIndex(pose.WarehouseWallSnap, pose.Wall)) { return false; } InteractableObject obj = null; bool flag = existingId != 0; if (flag && !TryResolveHoldId(existingId, out obj)) { return false; } if (!flag) { object? obj2 = MiSpawn.Invoke(null, new object[1] { pose.DecorationType }); obj = (InteractableObject)((obj2 is InteractableObject) ? obj2 : null); } if ((Object)(object)obj == (Object)null || (EDecoObject)FiObjectType.GetValue(obj) != pose.DecorationType || GetVertical(obj) != pose.Vertical) { return false; } DecorationPose original = (flag ? ReadPose(obj) : null); PlacementHoldInteraction.BeginSuppressLocalStarts(); try { if (!flag) { MiInit.Invoke(obj, null); } MiStartMoveObject.Invoke(obj, null); ((Component)obj).transform.SetPositionAndRotation(pose.Position, pose.Rotation); Vector3 val = (pose.Vertical ? (pose.Rotation * Vector3.forward) : Vector3.up); MiSetTargetMovePosition.Invoke(obj, new object[3] { pose.Position, val, null }); MiUpdate.Invoke(obj, null); if (!Convert.ToBoolean(FiValidPlacement.GetValue(obj))) { if (flag) { RestoreMovedObject(obj, original); } else { MiOnDestroyed.Invoke(obj, null); } return false; } MiPlaceMovedObject.Invoke(obj, null); RebindWallBlocker(obj, pose.WarehouseWallSnap, pose.Wall); placed = obj; return true; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Decoration placement failed: " + ex.Message)); if (flag) { RestoreMovedObject(obj, original); return false; } try { MiOnDestroyed.Invoke(obj, null); } catch (Exception ex2) { CoopPlugin.Log.LogWarning((object)("Decoration placement cleanup failed: " + ex2.Message)); } return false; } finally { PlacementHoldInteraction.EndSuppressLocalStarts(); } } private static void RestoreMovedObject(InteractableObject obj, DecorationPose original) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)obj == (Object)null || original == null) { return; } try { ((Component)obj).transform.SetPositionAndRotation(original.Position, original.Rotation); Vector3 val = (original.Vertical ? (original.Rotation * Vector3.forward) : Vector3.up); MiSetTargetMovePosition.Invoke(obj, new object[3] { original.Position, val, null }); MiUpdate.Invoke(obj, null); if (Convert.ToBoolean(FiValidPlacement.GetValue(obj))) { MiPlaceMovedObject.Invoke(obj, null); RebindWallBlocker(obj, original.WarehouseWallSnap, original.Wall); } else { CoopPlugin.Log.LogError((object)"Decoration move rollback failed legal validation."); } } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Decoration move rollback failed: " + ex)); } } internal static bool TryRemove(long id) { if (id <= 0 || !TryResolveHoldId(id, out var obj)) { return false; } MiBoxUpObject.Invoke(obj, new object[1] { false }); HostIds.Remove(obj); return true; } internal static bool ValidPose(Vector3 position, Quaternion rotation) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) float num = rotation.x * rotation.x + rotation.y * rotation.y + rotation.z * rotation.z + rotation.w * rotation.w; if (((Vector3)(ref position)).sqrMagnitude <= 1000000f && num > 0.99f && num < 1.01f && !float.IsNaN(position.x) && !float.IsNaN(position.y) && !float.IsNaN(position.z) && !float.IsNaN(rotation.x) && !float.IsNaN(rotation.y) && !float.IsNaN(rotation.z)) { return !float.IsNaN(rotation.w); } return false; } private static bool ValidWallIndex(bool warehouse, int wall) { if (wall == -1) { return !warehouse; } if (wall < 0) { return false; } UnlockRoomManager val = SceneRef.Get(); if ((Object)(object)val == (Object)null) { return !IsSceneReady(); } List list = (warehouse ? val.m_LockedWarehouseRoomBlockerList : val.m_LockedRoomBlockerList); if (list != null) { return wall < list.Count; } return false; } internal static int CategoryFor(PlaceDecoUIScreen screen) { if ((Object)(object)screen == (Object)null) { object obj = FindFirst(typeof(PlaceDecoUIScreen)); screen = (PlaceDecoUIScreen)((obj is PlaceDecoUIScreen) ? obj : null); } if (!((Object)(object)screen == (Object)null)) { return Convert.ToInt32(FiShopCategory.GetValue(screen)); } return -1; } internal static int CategoryFor(ShopBuyDecoUIScreen screen) { if ((Object)(object)screen == (Object)null) { object obj = FindFirst(typeof(ShopBuyDecoUIScreen)); screen = (ShopBuyDecoUIScreen)((obj is ShopBuyDecoUIScreen) ? obj : null); } if (!((Object)(object)screen == (Object)null)) { return Convert.ToInt32(FiShopCategory.GetValue(screen)); } return -1; } internal static DecorationPose ReadPose(InteractableObject obj) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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) return new DecorationPose { DecorationType = (EDecoObject)FiObjectType.GetValue(obj), Position = ((Component)obj).transform.position, Rotation = ((Component)obj).transform.rotation, Vertical = GetVertical(obj), WarehouseWallSnap = GetWarehouseWallSnap(obj), Wall = GetWallIndex(obj) }; } internal static long ClientIdFor(InteractableObject obj) { foreach (KeyValuePair clientObject in ClientObjects) { if (clientObject.Key > 0 && clientObject.Value == obj) { return clientObject.Key; } } return 0L; } internal static long HostIdFor(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return 0L; } if (!HostIds.TryGetValue(obj, out var value)) { return 0L; } return value; } internal static long EnsureHostId(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return 0L; } if (TryGetHoldId(obj, out var id)) { return id; } id = _nextHostId++; HostIds.Add(obj, id); return id; } internal static bool IsDecoration(InteractableObject obj) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if ((Object)(object)obj != (Object)null && FiObjectType.GetValue(obj) is EDecoObject val) { return (int)val > 0; } return false; } internal static bool TryGetHoldId(InteractableObject obj, out long id) { id = HostIdFor(obj); if (id <= 0) { id = ClientIdFor(obj); } return id > 0; } internal static bool TryResolveHoldId(long id, out InteractableObject obj) { if (id == 0L) { obj = null; return false; } if (id < 0) { obj = ((LocalObjects.TryGetValue(id, out var value) && (Object)(object)value != (Object)null) ? value : null); return (Object)(object)obj != (Object)null; } obj = GetMapped(id); if (!((Object)(object)obj != (Object)null)) { return TryFindHostObject(id, out obj); } return true; } internal static void ApplyDelta(DecorationDeltaMessage message, InteractableObject predictedPreview = null) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) switch (message.Action) { case 1: TryEquip(message.Category, message.Index, message.LotB); break; case 2: if (UnlockField(message.Category)?.GetValue(null) is IList list && message.Index >= 0 && message.Index < list.Count) { list[message.Index] = true; } break; case 3: if (IsValidDeco(message.DecorationType)) { int num = InventoryCount(message.DecorationType); if (num != message.InventoryCount) { MiAddInventory.Invoke(null, new object[2] { message.DecorationType, message.InventoryCount - num }); } } break; case 4: if (message.Pose != null) { InteractableObject val = ResolvePlacement(message.Pose, predictedPreview); if ((Object)(object)val != (Object)null) { ClientObjects[message.Pose.Id] = val; } } ApplyInventoryCount(message.DecorationType, message.InventoryCount); break; case 5: TryRemove(message.ObjectId); ApplyInventoryCount(message.DecorationType, message.InventoryCount); break; } RefreshUi(); } private static InteractableObject ResolvePlacement(DecorationPose pose, InteractableObject predictedPreview) { if (TryResolveHoldId(pose.Id, out var obj)) { DiscardPreview(predictedPreview); FinalizePlacement(obj, pose); CoopPlugin.Log.LogInfo((object)("[decoration] place id=" + pose.Id + " type=" + ((object)Unsafe.As(ref pose.DecorationType)/*cast due to .constrained prefix*/).ToString() + " applied-existing.")); return obj; } if ((Object)(object)predictedPreview != (Object)null) { FinalizePlacement(predictedPreview, pose); CoopPlugin.Log.LogInfo((object)("[decoration] place id=" + pose.Id + " type=" + ((object)Unsafe.As(ref pose.DecorationType)/*cast due to .constrained prefix*/).ToString() + " adopted preview.")); return predictedPreview; } InteractableObject placed; bool flag = TryPlace(pose, 0L, out placed); CoopPlugin.Log.LogInfo((object)("[decoration] place id=" + pose.Id + " type=" + ((object)Unsafe.As(ref pose.DecorationType)/*cast due to .constrained prefix*/).ToString() + " spawned ok=" + flag + ".")); if (!flag) { return null; } return placed; } internal static void DiscardPreview(InteractableObject obj) { if (!((Object)(object)obj == (Object)null)) { CoopPlugin.Log.LogInfo((object)"[decoration] retiring predicted preview without confirmation."); FinalizeMovedObject(obj); RemoveLocalPlacedObject(obj); } } internal static void RemoveLocalPlacedObject(InteractableObject obj) { if ((Object)(object)obj == (Object)null) { return; } MiOnDestroyed.Invoke(obj, null); foreach (KeyValuePair item in new List>(ClientObjects)) { if (item.Value == obj) { ClientObjects.Remove(item.Key); break; } } if ((Object)(object)obj != (Object)null && LocalIds.TryGetValue(obj, out var value)) { LocalIds.Remove(obj); LocalObjects.Remove(value); } } internal static void ApplyPredictedPose(InteractableObject obj, DecorationPose pose) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)obj == (Object)null) && pose != null) { ((Component)obj).transform.SetPositionAndRotation(pose.Position, pose.Rotation); RebindWallBlocker(obj, pose.WarehouseWallSnap, pose.Wall); } } internal static void FinalizePlacement(InteractableObject obj, DecorationPose pose) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)obj == (Object)null) && pose != null) { bool isMovingObject = obj.GetIsMovingObject(); ((Component)obj).transform.SetPositionAndRotation(pose.Position, pose.Rotation); if (isMovingObject) { MiOnPlacedMovedObject.Invoke(obj, null); } RebindWallBlocker(obj, pose.WarehouseWallSnap, pose.Wall); } } internal static void FinalizeMovedObject(InteractableObject obj) { if ((Object)(object)obj == (Object)null || !obj.GetIsMovingObject()) { return; } try { MiOnPlacedMovedObject.Invoke(obj, null); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("Decoration move finalisation failed: " + ex.Message)); } } internal static List GetLiveDecorations() { IList list = MiGetList.Invoke(null, null) as IList; List list2 = new List(list?.Count ?? 0); if (list == null) { return list2; } for (int i = 0; i < list.Count; i++) { object? obj = list[i]; InteractableObject val = (InteractableObject)((obj is InteractableObject) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { list2.Add(val); } } return list2; } private static void Reconcile(IList poses, bool removeUnlisted, Func keepUnlisted) { HashSet hashSet = new HashSet(new ReferenceComparer()); Dictionary dictionary = new Dictionary(); List liveDecorations = GetLiveDecorations(); for (int i = 0; i < poses.Count; i++) { DecorationPose decorationPose = poses[i]; InteractableObject val = GetMapped(decorationPose.Id); if ((Object)(object)val == (Object)null || !liveDecorations.Contains(val) || hashSet.Contains(val)) { val = null; } if ((Object)(object)val == (Object)null && TryPlace(decorationPose, decorationPose.Id, out var placed) && !hashSet.Contains(placed)) { val = placed; } if ((Object)(object)val == (Object)null && TryPlace(decorationPose, out var placed2)) { val = placed2; liveDecorations.Add(val); } if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("Decoration reconciliation could not create type " + ((object)Unsafe.As(ref decorationPose.DecorationType)/*cast due to .constrained prefix*/).ToString() + " for host object " + decorationPose.Id + "."); } hashSet.Add(val); dictionary[decorationPose.Id] = val; ApplyPose(val, decorationPose); } if (removeUnlisted) { for (int j = 0; j < liveDecorations.Count; j++) { InteractableObject val2 = liveDecorations[j]; if ((Object)(object)val2 != (Object)null && !hashSet.Contains(val2) && !val2.GetIsMovingObject() && (keepUnlisted == null || !keepUnlisted(val2))) { RemoveLocalPlacedObject(val2); } } } else { foreach (KeyValuePair clientObject in ClientObjects) { if ((Object)(object)clientObject.Value != (Object)null && !dictionary.ContainsKey(clientObject.Key)) { dictionary[clientObject.Key] = clientObject.Value; } } } ClientObjects.Clear(); foreach (KeyValuePair item in dictionary) { if (item.Key > 0) { ClientObjects[item.Key] = item.Value; } } } private static InteractableObject GetMapped(long id) { if (id > 0) { if (!ClientObjects.TryGetValue(id, out var value)) { return null; } return value; } if (id < 0) { if (!LocalObjects.TryGetValue(id, out var value2)) { return null; } return value2; } return null; } internal static void ApplyPose(InteractableObject obj, DecorationPose pose) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) ((Component)obj).transform.SetPositionAndRotation(pose.Position, pose.Rotation); RebindWallBlocker(obj, pose.WarehouseWallSnap, pose.Wall); } private static void RebindWallBlocker(InteractableObject obj, bool warehouseWall, int wall) { object? value = FiPlacedBlocker.GetValue(obj); LockedRoomBlocker val = (LockedRoomBlocker)((value is LockedRoomBlocker) ? value : null); if ((Object)(object)val != (Object)null) { val.RemoveFromVerticalDecoObjectList(obj); FiPlacedBlocker.SetValue(obj, null); } MiSetSnap.Invoke(obj, new object[2] { warehouseWall, wall }); } private static bool TryFindHostObject(long id, out InteractableObject result) { foreach (KeyValuePair hostId in HostIds) { if (hostId.Value == id) { result = hostId.Key; return (Object)(object)result != (Object)null; } } result = null; return false; } private static void ApplyMaterials(DecorationStateMessage message) { InvokeMaterial("ChangeWallMaterial", message.Wall, lotB: false); InvokeMaterial("ChangeWallMaterial", message.WallB, lotB: true); InvokeMaterial("ChangeFloorMaterial", message.Floor, lotB: false); InvokeMaterial("ChangeFloorMaterial", message.FloorB, lotB: true); InvokeMaterial("ChangeCeilingMaterial", message.Ceiling, lotB: false); InvokeMaterial("ChangeCeilingMaterial", message.CeilingB, lotB: true); } private static void InvokeMaterial(string name, int index, bool lotB) { MethodInfo? method = typeof(ShopCustomizationManager).GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(int), typeof(bool) }, null); if (method == null) { throw new MissingMethodException(typeof(ShopCustomizationManager).FullName, name); } method.Invoke(null, new object[2] { index, lotB }); } private static FieldInfo UnlockField(int category) { return category switch { 2 => FiCeilingUnlocks, 1 => FiFloorUnlocks, 0 => FiWallUnlocks, _ => null, }; } private static int EquippedIndex(int category, bool lotB) { return category switch { 0 => GetInt(lotB ? FiWallB : FiWall), 1 => GetInt(lotB ? FiFloorB : FiFloor), 2 => GetInt(lotB ? FiCeilingB : FiCeiling), _ => -1, }; } private static IList CategoryData(int category) { InventoryBase val = SceneRef.Get(); object obj = (((Object)(object)val == (Object)null) ? null : FiObjectData.GetValue(val)); return ((FieldInfo)(category switch { 2 => FiCeilingData, 1 => FiFloorData, 0 => FiWallData, _ => null, }))?.GetValue(obj) as IList; } private static IList ItemData() { InventoryBase val = SceneRef.Get(); object obj = (((Object)(object)val == (Object)null) ? null : FiObjectData.GetValue(val)); return FiItemData.GetValue(obj) as IList; } private static List CopyBools(FieldInfo field) { IEnumerable enumerable = field.GetValue(null) as IEnumerable; List list = new List(); if (enumerable != null) { foreach (object item in enumerable) { list.Add(Convert.ToBoolean(item)); } } return list; } private static List CopyInts(FieldInfo field) { IEnumerable enumerable = field.GetValue(null) as IEnumerable; List list = new List(); if (enumerable != null) { foreach (object item in enumerable) { list.Add(Convert.ToInt32(item)); } } return list; } private static void ApplyList(FieldInfo field, IList incoming) { if (!(field.GetValue(null) is IList list) || incoming == null) { throw new InvalidOperationException("Decoration state list is unavailable."); } list.Clear(); for (int i = 0; i < incoming.Count; i++) { list.Add(incoming[i]); } } private static int GetInt(FieldInfo field) { return Convert.ToInt32(field.GetValue(null)); } private static void SetInt(FieldInfo field, int value) { field.SetValue(null, value); } private static bool IsValidDeco(EDecoObject type) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((int)type != 0) { return Enum.IsDefined(typeof(EDecoObject), type); } return false; } private static bool GetVertical(InteractableObject obj) { return Convert.ToBoolean(FiVertical.GetValue(obj)); } private static bool GetWarehouseWallSnap(InteractableObject obj) { return Convert.ToBoolean(MiGetSnap.Invoke(obj, null)); } private static int GetWallIndex(InteractableObject obj) { return Convert.ToInt32(MiGetWall.Invoke(obj, null)); } internal static bool IsSceneReady() { if ((Object)(object)SceneRef.Get() != (Object)null) { return (Object)(object)SceneRef.Get() != (Object)null; } return false; } private static object FindFirst(Type type) { MethodInfo methodInfo = FindObjectsWithInactive ?? FindObjects; if (methodInfo == null) { throw new MissingMethodException("Unity Object.FindObjectsOfType is unavailable."); } object[] parameters = ((!(methodInfo == FindObjectsWithInactive)) ? new object[1] { type } : new object[2] { type, true }); if (methodInfo.Invoke(null, parameters) is Array { Length: not 0 } array) { return array.GetValue(0); } return null; } private static void RefreshUi() { InvokePage(typeof(PlaceDecoUIScreen)); InvokePage(typeof(ShopBuyDecoUIScreen)); } private static void InvokePage(Type type) { object obj = FindFirst(type); if (obj != null) { type.GetMethod("EvaluatePanelUIPage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(obj, new object[1] { 0 }); } } } internal sealed class DecorationSnapshot { internal int Wall; internal int WallB; internal int Floor; internal int FloorB; internal int Ceiling; internal int CeilingB; internal List WallUnlocks = new List(); internal List FloorUnlocks = new List(); internal List CeilingUnlocks = new List(); internal List Inventory = new List(); internal List Placed = new List(); } internal static class DecorationActions { internal const byte Equip = 1; internal const byte BuyShopDecoration = 2; internal const byte BuyItemDecoration = 3; internal const byte Place = 4; internal const byte Remove = 5; } [NetworkMessage] public sealed class DecorationStateMessage : INetMessage { public bool Full = true; public int Wall; public int WallB; public int Floor; public int FloorB; public int Ceiling; public int CeilingB; public List WallUnlocks = new List(); public List FloorUnlocks = new List(); public List CeilingUnlocks = new List(); public List Inventory = new List(); public List Placed = new List(); } [NetworkMessage] public sealed class DecorationIntentMessage : IPredictedMessage, INetMessage { public byte Action; public int Category; public int Index; public EDecoObject DecorationType; public bool LotB; public Vector3 Position; public Quaternion Rotation; public bool Vertical; public bool WarehouseWallSnap; public int WallIndex; public long ObjectId; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class DecorationDeltaMessage : IPredictedMessage, INetMessage { public byte Action; public int Category; public int Index; public EDecoObject DecorationType; public bool LotB; public int InventoryCount; public long ObjectId; public DecorationPose Pose; public Guid PredictionId { get; set; } } public sealed class DecorationPose { public long Id; public EDecoObject DecorationType; public Vector3 Position; public Quaternion Rotation; public bool Vertical; public bool WarehouseWallSnap; public int Wall; } } namespace CardShopCoop.Modules.DebugTools { [PersistentBehaviour] public sealed class DebugToolsProcessBehaviour : DebugToolsBehaviour { private void OnEnable() { StartDebugTools("process"); } internal void Shutdown() { StopDebugTools(); } private void OnDestroy() { StopDebugTools(); } } public abstract class DebugToolsBehaviour : CoopBehaviour { private Harmony _harmony; private bool _shutdown; protected void StartDebugTools(string role) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!_shutdown && _harmony == null) { DebugToolsRuntime.Attach(this); _harmony = new Harmony("dev.meepen.cardshopcoop.debug-tools." + role); DebugToolsPatches.Apply(_harmony); DebugToolsRuntime.BootstrapCheatManager(); } } protected void StopDebugTools() { if (!_shutdown) { _shutdown = true; DebugToolsRuntime.Detach(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; } } } internal static class DebugToolsPatches { internal static void Apply(Harmony harmony) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown //IL_0055: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown Type cheatManagerType = DebugToolsRuntime.CheatManagerType; if (cheatManagerType == null) { CoopPlugin.Log.LogInfo((object)"DebugTools: game CheatManager is absent; cheat testing is disabled on this build"); return; } TryPatch(harmony, cheatManagerType, "SetMenuOpen", new HarmonyMethod(typeof(DebugToolsPatches), "CheatMenuOpenPrefix", (Type[])null), new HarmonyMethod(typeof(DebugToolsPatches), "CheatMenuChangedPostfix", (Type[])null)); TryPatch(harmony, cheatManagerType, "Start", new HarmonyMethod(typeof(DebugToolsPatches), "CheatManagerStartPrefix", (Type[])null)); TryPatch(harmony, cheatManagerType, "ApplyCheat", new HarmonyMethod(typeof(DebugToolsPatches), "CheatApplyPrefix", (Type[])null)); TryPatch(harmony, cheatManagerType, "GiveAllCards", new HarmonyMethod(typeof(DebugToolsPatches), "CheatGiveCardsPrefix", (Type[])null)); CoopPlugin.Log.LogInfo((object)"DebugTools: reflected CheatManager hooks installed"); } internal static bool CheatMenuOpenPrefix(bool open) { bool flag = !open || DebugToolsRuntime.CheatsEnabledForHost; if (open && flag) { SessionInputRuntime.PrepareVanillaModalOpen(); } return flag; } internal static void CheatMenuChangedPostfix(bool open) { DebugToolsRuntime.CheatMenuChanged(open && DebugToolsRuntime.CheatsEnabledForHost); } internal static bool CheatManagerStartPrefix() { return false; } internal static bool CheatApplyPrefix() { return DebugToolsRuntime.CheatsEnabledForHost; } internal static bool CheatGiveCardsPrefix() { return DebugToolsRuntime.CheatsEnabledForHost; } private static void TryPatch(Harmony harmony, Type type, string method, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { try { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); if (methodInfo == null) { CoopPlugin.Log.LogWarning((object)("DebugTools reflected patch target missing: " + type.Name + "." + method)); } else { harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("DebugTools reflected patch failed for " + type.Name + "." + method + ": " + ex.Message)); } } } internal static class DebugToolsRuntime { private static readonly Type _cheatManagerType = typeof(CGameManager).Assembly.GetType("CheatManager", throwOnError: false); private static DebugToolsBehaviour _active; private static Component _ownedManager; private static bool _cheatMenuOpen; internal static Type CheatManagerType => _cheatManagerType; internal static bool CheatsEnabledForHost { get { if ((Object)(object)_active != (Object)null && CoopCore.Role != CoopRole.Client && CoopPlugin.ShowHiddenCategory != null && CoopPlugin.ShowHiddenCategory.Value && CoopPlugin.EnableGameCheatMenu != null) { return CoopPlugin.EnableGameCheatMenu.Value; } return false; } } internal static void Attach(DebugToolsBehaviour behaviour) { if ((Object)(object)_active != (Object)null && _active != behaviour) { throw new InvalidOperationException("DebugTools is already active for another session."); } _active = behaviour; _ownedManager = null; _cheatMenuOpen = false; } internal static void Detach(DebugToolsBehaviour behaviour) { if (_active == behaviour) { if (_cheatMenuOpen) { _cheatMenuOpen = false; SessionInputRuntime.CheatMenuChanged(open: false); } if ((Object)(object)_ownedManager != (Object)null) { Object.Destroy((Object)(object)_ownedManager.gameObject); } _ownedManager = null; _cheatMenuOpen = false; _active = null; } } internal static void CheatMenuChanged(bool open) { if (!((Object)(object)_active == (Object)null)) { _cheatMenuOpen = open; SessionInputRuntime.CheatMenuChanged(open); } } internal static void CloseForSessionStop() { if ((Object)(object)_active == (Object)null || !_cheatMenuOpen || _cheatManagerType == null) { return; } try { Component obj = (((Object)(object)_ownedManager != (Object)null) ? _ownedManager : FindSceneComponent(_cheatManagerType)); _cheatManagerType.GetMethod("SetMenuOpen", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null)?.Invoke(obj, new object[1] { false }); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("DebugTools could not close cheat menu at session end: " + ex.Message)); } finally { _cheatMenuOpen = false; SessionInputRuntime.CheatMenuChanged(open: false); } } internal static void BootstrapCheatManager() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown if ((Object)(object)_active == (Object)null || _cheatManagerType == null) { return; } try { if (!typeof(Component).IsAssignableFrom(_cheatManagerType)) { CoopPlugin.Log.LogWarning((object)"DebugTools: reflected CheatManager is not a Component"); } else if (!((Object)(object)FindSceneComponent(_cheatManagerType) != (Object)null)) { GameObject val = new GameObject("CardShopCoopGameCheatManager"); Object.DontDestroyOnLoad((Object)(object)val); Component val2 = val.AddComponent(_cheatManagerType); if ((Object)(object)val2 == (Object)null) { Object.Destroy((Object)(object)val); CoopPlugin.Log.LogWarning((object)"DebugTools: reflected CheatManager could not be added"); } else { AccessTools.Field(_cheatManagerType, "m_CheatCanvasPrefab")?.SetValue(val2, Resources.Load("CheatUI_Root")); _ownedManager = val2; CoopPlugin.Log.LogInfo((object)"DebugTools: game cheat manager bootstrapped for gated host/testing use"); } } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("DebugTools cheat manager bootstrap failed: " + ex.Message)); } } private static Component FindSceneComponent(Type componentType) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Object[] array = Resources.FindObjectsOfTypeAll(componentType); foreach (Object obj in array) { Component val = (Component)(object)((obj is Component) ? obj : null); if ((Object)(object)val != (Object)null) { Scene scene = val.gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return val; } } } return null; } } } namespace CardShopCoop.Modules.Catalog { public static class CatalogApi { internal sealed class PurchaseLicenseState { internal readonly CatalogInterop.LicenseState Value; internal PurchaseLicenseState(CatalogInterop.LicenseState value) { Value = value; } } private static bool _enumLendWarned; internal static bool IsWireableItemType(EItemType itemType, string context = null) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return CatalogInterop.IsWireableItemType(itemType, context); } internal static bool IsDefinedEnumValue(EnumKind kind, int value) { return CatalogIdMap.IsDefined(kind, value); } public static string EnumLendState() { try { if (!CatalogParity.HostEnumInstalled()) { return null; } if (!_enumLendWarned) { _enumLendWarned = true; CoopPlugin.Log.LogWarning((object)"CardShopCoopCommunity: 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 (Exception e) { Swallow.Log(e, "EnumLendState", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogApi.cs", 54); return null; } } public static bool RestoreEnumBackup(out string message) { return CatalogParity.RestoreEnumBackup(out message); } internal static bool TryGetProduct(int index, out RestockData data) { return CatalogInterop.TryAt(index, out data); } internal static bool TryResolveProduct(EItemType itemType, bool isBigBox, string name, out int index, out RestockData data) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return CatalogInterop.TryResolve(itemType, isBigBox, name, out index, out data); } internal static bool TryGetLicense(int index, out bool unlocked) { bool transient; return CatalogInterop.TryGetLicense(index, out unlocked, out transient); } internal static bool TryCapturePurchaseState(out PurchaseLicenseState state) { state = null; if (!CatalogInterop.TryCaptureLicenseState(out var state2)) { return false; } state = new PurchaseLicenseState(state2); return true; } internal static bool HostRestorePurchaseState(PurchaseLicenseState state) { if (state != null) { return CatalogHostBehaviour.RestoreProductLicenseState(state.Value); } return false; } internal static bool HostRestoreScannerLicense(bool unlocked) { return CatalogHostBehaviour.RestoreScannerUnlock(unlocked); } internal static bool HostApplyProductLicense(EItemType itemType, bool isBigBox, string name, bool applyVanillaEntitlementSideEffects, bool publish, out bool changed) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return CatalogHostBehaviour.ApplyProductLicense(itemType, isBigBox, name, applyVanillaEntitlementSideEffects, publish, out changed); } internal static bool HostApplyScannerLicense(bool publish, out bool changed) { return CatalogHostBehaviour.ApplyScannerUnlock(publish, out changed); } internal static bool HostPublishProductLicense(RestockData data, Guid predictionId) { return CatalogHostBehaviour.PublishProductLicense(data, predictionId); } internal static bool HostPublishScannerLicense(Guid predictionId) { return CatalogHostBehaviour.PublishScannerUnlock(predictionId); } internal static void HostApplyProductEntitlementSideEffects(RestockData data) { CatalogHostBehaviour.ApplyProductEntitlementSideEffects(data); } internal static void ApplyClientProductLicense(int index, bool unlocked) { CatalogClientBehaviour.ApplyPurchaseProductLicense(index, unlocked); } internal static void ApplyClientScannerLicense(bool unlocked) { CatalogClientBehaviour.ApplyPurchaseScannerLicense(unlocked); } internal static void RefreshScannerLicenseUi() { CatalogHostBehaviour.RefreshScannerLicenseUi(); } } [ClientBehaviour] public sealed class CatalogClientBehaviour : CoopBehaviour { private static readonly FieldInfo PanelScreen = typeof(RestockItemPanelUI).GetField("m_RestockItemScreen", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PanelIndex = typeof(RestockItemPanelUI).GetField("m_Index", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo ScannerPage = typeof(ScannerRestockScreen).GetField("m_PageIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo ScannerEvaluatePage = typeof(ScannerRestockScreen).GetMethod("EvaluatePanelUIPage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo ScannerUpdateTotals = typeof(ScannerRestockScreen).GetMethod("UpdateTotalCostAndBoxCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static CatalogClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private CatalogLicenseStateMessage _pendingState; private readonly Dictionary _pendingDeltas = new Dictionary(); private readonly HashSet _productSideEffectsApplied = new HashSet(); private bool _contentReady; private bool _shutdown; private void OnEnable() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CatalogInterop.ProbeOptionalSurfaces(); _harmony = new Harmony("dev.meepen.cardshopcoop.catalog.client"); CatalogPatches.ApplyClientLifecycle(_harmony); CEventManager.AddListener((EventDelegate)OnReady); SceneManager.sceneLoaded += OnSceneLoaded; } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void OnReady(CEventPlayer_GameDataFinishLoaded _) { ContentReady(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { ContentReady(); } internal static void ContentReady() { if (!((Object)(object)_active == (Object)null) && !_active._shutdown) { _active._contentReady = true; _active.ApplyPendingState(); } } [MessageHandler(typeof(CatalogLicenseStateMessage))] private void HandleState(MessageContext context, CatalogLicenseStateMessage message) { if (!_shutdown) { _pendingState = message; ApplyPendingState(); } } private void ApplyPendingState() { if (_shutdown || !_contentReady || !_context.InGame() || !CatalogInterop.IsSceneReady) { return; } if (_pendingState != null) { ApplyState(_pendingState); _pendingState = null; } if (_pendingDeltas.Count <= 0) { return; } List> list = new List>(_pendingDeltas); for (int i = 0; i < list.Count; i++) { if (ApplyDelta(list[i].Value)) { _pendingDeltas.Remove(list[i].Key); } } } private void ApplyState(CatalogLicenseStateMessage state) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown for (int i = 0; i < CatalogInterop.Count; i++) { RestockData val = CatalogInterop.At(i); if (val == null) { throw new InvalidOperationException("Catalog row " + i + " could not be applied"); } bool flag = false; for (int j = 0; j < state.Entries.Count; j++) { if (string.Equals(CatalogInterop.IdentityKey(val), CatalogInterop.IdentityKey(state.Entries[j]), StringComparison.Ordinal)) { flag = true; break; } } bool isItemLicenseUnlocked = CPlayerData.GetIsItemLicenseUnlocked(i); if (!flag) { _productSideEffectsApplied.Remove(i); } if (isItemLicenseUnlocked != flag) { if (!CatalogInterop.SetLicense(i, flag)) { throw new InvalidOperationException("Catalog license apply failed for row " + i); } if (!isItemLicenseUnlocked && flag) { ApplyProductEntitlementSideEffectsOnce(i, val); } } } if (CPlayerData.m_IsScannerRestockUnlocked != state.ScannerUnlocked) { CPlayerData.m_IsScannerRestockUnlocked = state.ScannerUnlocked; if (state.ScannerUnlocked) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ScannerRestockUnlocked()); } } RefreshOpenProductPanels(); RefreshOpenScannerScreens(); } [MessageHandler(typeof(CatalogLicenseDeltaMessage))] private void HandleDelta(MessageContext context, CatalogLicenseDeltaMessage message) { if (!_shutdown) { if (!_contentReady || !_context.InGame() || !CatalogInterop.IsSceneReady) { DeferDelta(message); } else { ApplyDelta(message); } } } private bool ApplyDelta(CatalogLicenseDeltaMessage message) { int sideEffectIndex = -1; RestockData sideEffectData = null; PredictionApi.AckOrApply(message.PredictionId, delegate { //IL_0042: 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_0037: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 if (message.Scanner) { CPlayerData.m_IsScannerRestockUnlocked = message.Unlocked; if (message.Unlocked) { CEventManager.QueueEvent((CEvent)new CEventPlayer_ScannerRestockUnlocked()); } } else { if (!CatalogInterop.TryResolve(message.ItemType, message.IsBigBox, message.Name ?? "", out var index, out var data) || data == null) { if ((int)message.ItemType == -1) { return; } throw new InvalidOperationException("Catalog delta product could not be resolved"); } bool isItemLicenseUnlocked = CPlayerData.GetIsItemLicenseUnlocked(index); if (!CatalogInterop.SetLicense(index, message.Unlocked)) { throw new InvalidOperationException("Catalog delta license apply failed"); } if (!message.Unlocked) { _active._productSideEffectsApplied.Remove(index); } else if (!isItemLicenseUnlocked) { sideEffectIndex = index; sideEffectData = data; } } RefreshOpenProductPanels(); RefreshOpenScannerScreens(); }); if (sideEffectData != null) { ApplyProductEntitlementSideEffectsOnce(sideEffectIndex, sideEffectData); } return true; } private void DeferDelta(CatalogLicenseDeltaMessage message) { string key = DeltaKey(message); if (_pendingDeltas.TryGetValue(key, out var value)) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas[key] = message; } private static string DeltaKey(CatalogLicenseDeltaMessage message) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected I4, but got Unknown if (message.Scanner) { return "scanner"; } return "product:" + (int)message.ItemType + ":" + (message.IsBigBox ? "1" : "0") + ":" + message.Name; } internal static void ApplyPurchaseProductLicense(int index, bool unlocked) { _active?.ApplyLocalProduct(index, unlocked); } internal static void ApplyPurchaseScannerLicense(bool unlocked) { _active?.ApplyLocalScanner(unlocked); } private void ApplyLocalProduct(int index, bool unlocked) { if (!CatalogInterop.SetLicense(index, unlocked)) { throw new InvalidOperationException("Catalog local license apply failed"); } RefreshOpenProductPanels(); } private void ApplyLocalScanner(bool unlocked) { CPlayerData.m_IsScannerRestockUnlocked = unlocked; RefreshOpenScannerScreens(); } private void ApplyProductEntitlementSideEffectsOnce(int index, RestockData data) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 if (!_productSideEffectsApplied.Contains(index)) { AchievementManager.OnItemLicenseUnlocked(data.itemType); GameInstance.m_IsItemLicenseUnlocked = true; if ((int)data.itemType == 1) { TutorialManager.AddTaskValue((ETutorialTaskCondition)14, 1f); } _productSideEffectsApplied.Add(index); } } internal static void InventoryReset() { if (!((Object)(object)_active == (Object)null) && !_active._shutdown) { _active._pendingState = null; _active.ClearPendingDeltas(); _active._productSideEffectsApplied.Clear(); _active._contentReady = false; } } private void ClearPendingDeltas() { foreach (CatalogLicenseDeltaMessage value in _pendingDeltas.Values) { PredictionApi.Ack(value.PredictionId); } _pendingDeltas.Clear(); } private static void RefreshOpenProductPanels() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) List list = SceneComponentRegistry.Snapshot(SceneManager.GetActiveScene(), activeOnly: true); for (int i = 0; i < list.Count; i++) { RestockItemPanelUI val = list[i]; if ((Object)(object)val == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (!((Scene)(ref scene)).IsValid() || !((Component)val).gameObject.activeInHierarchy || !(PanelIndex?.GetValue(val) is int num) || num < 0) { continue; } object? obj = PanelScreen?.GetValue(val); RestockItemScreen val2 = (RestockItemScreen)((obj is RestockItemScreen) ? obj : null); if ((Object)(object)val2 != (Object)null) { val.Init(val2, num); continue; } bool isItemLicenseUnlocked = CPlayerData.GetIsItemLicenseUnlocked(num); GameObject licenseUIGrp = val.m_LicenseUIGrp; if (licenseUIGrp != null) { licenseUIGrp.SetActive(!isItemLicenseUnlocked); } GameObject uIGrp = ((UIElementBase)val).m_UIGrp; if (uIGrp != null) { uIGrp.SetActive(isItemLicenseUnlocked); } } } private static void RefreshOpenScannerScreens() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) List list = SceneComponentRegistry.Snapshot(SceneManager.GetActiveScene(), activeOnly: true); for (int i = 0; i < list.Count; i++) { ScannerRestockScreen val = list[i]; if ((Object)(object)val == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid() && ((Component)val).gameObject.activeInHierarchy) { bool isScannerRestockUnlocked = CPlayerData.m_IsScannerRestockUnlocked; GameObject unlockedGrp = val.m_UnlockedGrp; if (unlockedGrp != null) { unlockedGrp.SetActive(isScannerRestockUnlocked); } GameObject lockedGrp = val.m_LockedGrp; if (lockedGrp != null) { lockedGrp.SetActive(!isScannerRestockUnlocked); } if (isScannerRestockUnlocked && ScannerPage?.GetValue(val) is int num) { ScannerEvaluatePage?.Invoke(val, new object[1] { num }); } ScannerUpdateTotals?.Invoke(val, null); } } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); SceneManager.sceneLoaded -= OnSceneLoaded; _pendingState = null; ClearPendingDeltas(); _productSideEffectsApplied.Clear(); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } internal sealed class CatalogHelloData { internal string EnumHash; internal string CardsHash; internal List CardsList; internal List EnumIdentity; } internal sealed class CatalogBlobData { internal readonly List Lines; internal readonly bool IsValid; internal readonly string FailureReason; internal CatalogBlobData(List lines, bool isValid, string failureReason = null) { Lines = lines ?? new List(); IsValid = isValid; FailureReason = failureReason; } } internal sealed class CatalogWelcomeBlobs { internal List EnumIdentity; internal byte[] CardsBlob; } internal sealed class CatalogEnumValidation { internal List Conflicts; internal bool HashesAreConsistent; internal string HashFailureReason; } internal static class CatalogHandshake { internal const int CatalogBlobCap = 262144; private static readonly Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); internal static CatalogHelloData BuildHelloData() { List list = SafeIdentity(); List list2 = CatalogParity.CardsList(); return new CatalogHelloData { EnumHash = CatalogParity.EnumHashForIdentity(list), CardsHash = CatalogParity.CardsHashForLines(list2), CardsList = list2, EnumIdentity = list }; } internal static CatalogWelcomeBlobs BuildWelcomeBlobs() { List enumIdentity = SafeIdentity(); List lines = CatalogParity.CardsList(); return new CatalogWelcomeBlobs { EnumIdentity = enumIdentity, CardsBlob = GzipLines(lines) }; } private static List SafeIdentity() { try { return CatalogParity.EnumIdentity() ?? new List(); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("catalog enum identity: " + ex.Message)); return new List(); } } internal static CatalogBlobData ReadBlob(byte[] gz) { List list = new List(); if (gz == null || gz.Length == 0) { return new CatalogBlobData(list, isValid: false, "catalog gzip payload is empty"); } if (!TryGunzipCapped(gz, out var result, out var failureReason)) { return new CatalogBlobData(list, isValid: false, failureReason); } try { string[] array = StrictUtf8.GetString(result).Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } return new CatalogBlobData(CanonicalLines(list), isValid: true); } catch (Exception ex) { string text2 = "catalog blob is not valid UTF-8: " + ex.Message; CoopPlugin.Log.LogWarning((object)text2); return new CatalogBlobData(list, isValid: false, text2); } } internal static CatalogEnumValidation ValidateEnums(string peerName, string peerHash, List peerIdentity) { List list = SafeIdentity(); string text = CatalogParity.EnumHashForIdentity(peerIdentity); string text2 = CatalogParity.EnumHashForIdentity(list); bool flag = string.Equals(peerHash, text, StringComparison.Ordinal); string hashFailureReason = null; if (!flag) { hashFailureReason = "client EnumHash does not match its decoded enum identity (reported " + (peerHash ?? "") + ", decoded " + text + ")"; } else if (!string.Equals(CatalogParity.EnumHash(), text2, StringComparison.Ordinal)) { flag = false; hashFailureReason = "host enum identity changed while the handshake was being built (expected " + text2 + ", reported " + CatalogParity.EnumHash() + ")"; } if (MemberCount(list) == 0) { CoopPlugin.Log.LogWarning((object)("enum check: the host resolved NO enum identity members, so " + peerName + " was not key-checked at all (the six enum types did not resolve this session; see the 'enum identity source' line at startup)")); } return new CatalogEnumValidation { Conflicts = EnumKeyConflicts(peerIdentity, list), HashesAreConsistent = flag, HashFailureReason = hashFailureReason }; } internal static bool IsIdentityUsable(List identity) { if (identity == null) { return false; } if (identity.Count == 0) { return true; } HashSet hashSet = new HashSet(); foreach (EnumKindIdentityDto item in identity) { if (item == null || item.Members == null || !hashSet.Add(item.Kind)) { return false; } HashSet hashSet2 = new HashSet(StringComparer.Ordinal); foreach (EnumMemberDto member in item.Members) { if (member == null || string.IsNullOrEmpty(member.Name) || !hashSet2.Add(member.Name)) { return false; } } } return true; } private static int MemberCount(List identity) { int num = 0; if (identity == null) { return 0; } foreach (EnumKindIdentityDto item in identity) { num += ((item != null && item.Members != null) ? item.Members.Count : 0); } return num; } internal static string DescribeEnumKeysMismatch(List conflicts) { return "your custom content does not match the host's - both players need the same custom card/item packs installed (identical names; the numeric ids underneath may differ). Differences: " + DescribeConflicts(conflicts ?? new List()); } internal static List EnumKeyConflicts(List theirs, List ours) { List list = new List(); HashSet hashSet = EnumKeySet(theirs); HashSet hashSet2 = EnumKeySet(ours); foreach (string item in hashSet) { if (!hashSet2.Contains(item)) { list.Add("client-only " + item); } } foreach (string item2 in hashSet2) { if (!hashSet.Contains(item2)) { list.Add("host-only " + item2); } } list.Sort(StringComparer.Ordinal); return list; } private static HashSet EnumKeySet(List identity) { HashSet hashSet = new HashSet(StringComparer.Ordinal); if (identity == null) { return hashSet; } foreach (EnumKindIdentityDto item in identity) { if (item == null || item.Members == null) { continue; } string name; try { name = CatalogIdMap.WireType((EnumKind)item.Kind).Name; } catch { continue; } foreach (EnumMemberDto member in item.Members) { if (member != null && member.Name != null) { hashSet.Add(name + ":" + member.Name); } } } return hashSet; } internal static bool TryValidateCards(string peerHash, List peerCards, out string reason) { reason = null; if (!CatalogParity.TryCanonicalizeCardLines(peerCards, out var canonical, out var failureReason)) { reason = "invalid CardForge card list: " + failureReason; return false; } List list; try { list = CatalogParity.CardsList(); } catch (Exception ex) { reason = "the host has an invalid installed CardForge configuration: " + ex.Message; CoopPlugin.Log.LogError((object)("catalog card validation failed: " + reason)); return false; } string text = CatalogParity.CardsHashForLines(canonical); if (!string.Equals(peerHash, text, StringComparison.Ordinal)) { reason = "catalog card hash is inconsistent with the client's advertised card list (reported " + (peerHash ?? "") + ", decoded " + text + ")"; return false; } string b = CatalogParity.CardsHashForLines(list); if (string.Equals(peerHash, b, StringComparison.Ordinal) && CardListsEqual(canonical, list)) { return true; } string text2 = DescribeCardsDiff(canonical, list); reason = text2 ?? "your custom cards differ from the host's - both players need the same custom cards installed (identical files + IDs), then restart. Share the exact card package (e.g. from CardForge)."; return false; } private static bool CardListsEqual(List left, List right) { if (left == null || right == null || left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { if (!string.Equals(left[i], right[i], StringComparison.Ordinal)) { return false; } } return true; } internal static string DescribeConflicts(List 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; } internal static bool TryGunzipCapped(byte[] data, out byte[] result, out string failureReason) { result = null; failureReason = null; if (data == null || data.Length == 0) { failureReason = "catalog gzip payload is empty"; return false; } 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 > 262144) { failureReason = "catalog gzip expands beyond the " + 262144 + "-byte resource cap"; CoopPlugin.Log.LogWarning((object)("catalog blob rejected: " + failureReason + " (" + data.Length + " compressed bytes)")); return false; } memoryStream.Write(array, 0, num); } result = memoryStream.ToArray(); return true; } catch (Exception ex) { failureReason = "catalog gzip could not be decompressed: " + ex.Message; CoopPlugin.Log.LogWarning((object)("catalog blob rejected: " + failureReason)); return false; } } private static byte[] GzipLines(List lines) { try { string[] array = (lines ?? new List()).ToArray(); byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\n", array)); if (bytes.Length > 262144) { CoopPlugin.Log.LogWarning((object)("enum identity blob is OVER THE WIRE CAP: " + array.Length + " lines, " + bytes.Length + " bytes uncompressed vs a " + 262144 + "-byte cap - the receiving PC cannot decode it and the join will be refused")); } return Msg.Gzip(bytes); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("registry blob: " + ex.Message)); return Msg.Gzip(new byte[0]); } } private static List CanonicalLines(List source) { List list = new List(); if (source != null) { foreach (string item in source) { string text = ((item == null) ? "" : item.Trim()); if (text.Length > 0) { list.Add(text); } } } list.Sort(StringComparer.Ordinal); return list; } private static string DescribeCardsDiff(List theirs, List ours) { return DescribeDiff(theirs, ours, "custom cards differ - ", "ID differs"); } private static string DescribeDiff(List theirs, List ours, string head, string diffLabel) { if (theirs == null || theirs.Count == 0 || ours == null || ours.Count == 0) { return null; } Dictionary dictionary = DiffMap(theirs); Dictionary dictionary2 = DiffMap(ours); List list = new List(); List list2 = new List(); List list3 = new List(); foreach (KeyValuePair item in dictionary2) { if (!dictionary.ContainsKey(item.Key)) { list.Add(item.Key); } } foreach (KeyValuePair 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 list4 = new List(); 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) { return text; } return text.Substring(0, 697) + "..."; } private static Dictionary DiffMap(List entries) { Dictionary dictionary = new Dictionary(); 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 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(); } } [ServerBehaviour] public sealed class CatalogHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(CPlayerData), "SetUnlockItemLicense")] private static class ProductLicensePatch { [HarmonyPostfix] private static void Postfix(int index) { if ((Object)(object)_active != (Object)null && !_active._applying) { RestockData val = CatalogInterop.At(index); if (val != null) { _active.BroadcastProductDelta(Guid.Empty, val, unlocked: true); } } } } [HarmonyPatch(typeof(ScannerRestockScreen), "OnPressUnlockButton")] private static class ScannerUnlockPatch { [HarmonyPostfix] private static void Postfix() { if ((Object)(object)_active != (Object)null && !_active._applying) { _active.BroadcastScannerDelta(Guid.Empty, CPlayerData.m_IsScannerRestockUnlocked); } } } private static readonly FieldInfo ScannerPage = typeof(ScannerRestockScreen).GetField("m_PageIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo ScannerEvaluatePage = typeof(ScannerRestockScreen).GetMethod("EvaluatePanelUIPage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly MethodInfo ScannerUpdateTotals = typeof(ScannerRestockScreen).GetMethod("UpdateTotalCostAndBoxCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static CatalogHostBehaviour _active; private readonly HashSet _joined = new HashSet(); private readonly HashSet _baselinePending = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _applying; private void OnEnable() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CatalogInterop.ProbeOptionalSurfaces(); _harmony = new Harmony("dev.meepen.cardshopcoop.catalog.host"); _harmony.CreateClassProcessor(typeof(ProductLicensePatch)).Patch(); _harmony.CreateClassProcessor(typeof(ScannerUnlockPatch)).Patch(); CEventManager.AddListener((EventDelegate)OnReady); SceneManager.sceneLoaded += OnSceneLoaded; } catch { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } _context = null; throw; } } private void OnReady(CEventPlayer_GameDataFinishLoaded _) { SendPendingBaselines(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { SendPendingBaselines(); } [OnFullyJoined] private void SendJoinBaseline(PeerConnection peer) { if (peer != null) { _joined.Add(peer.Id); if (!SendState(peer.Id)) { _baselinePending.Add(peer.Id); } } } [OnClientDisconnected] private void ForgetPeer(PeerConnection peer, DisconnectInfo _) { if (peer != null) { _joined.Remove(peer.Id); _baselinePending.Remove(peer.Id); } } internal static bool ApplyProductLicense(EItemType itemType, bool isBigBox, string name, bool applyVanillaEntitlementSideEffects, bool publish, out bool changed) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) changed = false; CatalogHostBehaviour active = _active; if ((Object)(object)active == (Object)null || !CatalogInterop.TryResolve(itemType, isBigBox, name, out var index, out var data) || data == null || string.IsNullOrEmpty(data.name) || !CatalogInterop.IsWireableItemType(data.itemType, "catalog product")) { return false; } if (!CatalogInterop.TryGetLicense(index, out var unlocked, out var transient) || unlocked) { return unlocked; } active._applying = true; try { if (!CatalogInterop.SetLicense(index, unlocked: true) || !CatalogInterop.TryGetLicense(index, out var unlocked2, out transient) || !unlocked2) { return false; } } finally { active._applying = false; } changed = true; if (applyVanillaEntitlementSideEffects) { ApplyProductEntitlementSideEffects(data); } if (publish) { active.BroadcastProductDelta(Guid.Empty, data, unlocked: true); } return true; } internal static bool PublishProductLicense(RestockData data, Guid predictionId) { if ((Object)(object)_active == (Object)null || data == null) { return false; } _active.BroadcastProductDelta(predictionId, data, unlocked: true); return true; } internal static bool ApplyScannerUnlock(bool publish, out bool changed) { changed = false; if ((Object)(object)_active == (Object)null) { return false; } if (CPlayerData.m_IsScannerRestockUnlocked) { return true; } _active._applying = true; try { CPlayerData.m_IsScannerRestockUnlocked = true; } finally { _active._applying = false; } changed = true; if (publish) { _active.BroadcastScannerDelta(Guid.Empty, unlocked: true); } return true; } internal static void RefreshScannerLicenseUi() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) List list = SceneComponentRegistry.Snapshot(SceneManager.GetActiveScene(), activeOnly: true); for (int i = 0; i < list.Count; i++) { ScannerRestockScreen val = list[i]; if ((Object)(object)val == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid() && ((Component)val).gameObject.activeInHierarchy) { bool isScannerRestockUnlocked = CPlayerData.m_IsScannerRestockUnlocked; GameObject unlockedGrp = val.m_UnlockedGrp; if (unlockedGrp != null) { unlockedGrp.SetActive(isScannerRestockUnlocked); } GameObject lockedGrp = val.m_LockedGrp; if (lockedGrp != null) { lockedGrp.SetActive(!isScannerRestockUnlocked); } if (isScannerRestockUnlocked && ScannerPage?.GetValue(val) is int num) { ScannerEvaluatePage?.Invoke(val, new object[1] { num }); } ScannerUpdateTotals?.Invoke(val, null); } } } internal static bool PublishScannerUnlock(Guid predictionId) { if ((Object)(object)_active == (Object)null) { return false; } _active.BroadcastScannerDelta(predictionId, unlocked: true); return true; } internal static bool RestoreProductLicenseState(CatalogInterop.LicenseState state) { if ((Object)(object)_active == (Object)null || state == null) { return false; } _active._applying = true; try { if (!CatalogInterop.TryRestoreLicenseState(state, out var changed)) { return false; } for (int i = 0; i < changed.Count; i++) { RestockData val = CatalogInterop.At(changed[i].Index); if (val != null) { _active.BroadcastProductDelta(Guid.Empty, val, changed[i].Unlocked); } } } finally { _active._applying = false; } return true; } internal static bool RestoreScannerUnlock(bool unlocked) { if ((Object)(object)_active == (Object)null) { return false; } CPlayerData.m_IsScannerRestockUnlocked = unlocked; _active.BroadcastScannerDelta(Guid.Empty, unlocked); return true; } internal static void ApplyProductEntitlementSideEffects(RestockData data) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (data != null) { AchievementManager.OnItemLicenseUnlocked(data.itemType); GameInstance.m_IsItemLicenseUnlocked = true; if ((int)data.itemType == 1) { TutorialManager.AddTaskValue((ETutorialTaskCondition)14, 1f); } } } private bool SendState(int connectionId) { if (_shutdown || !_context.InGame() || !CatalogInterop.IsSceneReady || !CatalogInterop.TryBuildLicenseEntries(out var entries, out var scannerUnlocked)) { return false; } _context.Send(connectionId, (INetMessage)(object)new CatalogLicenseStateMessage { ScannerUnlocked = scannerUnlocked, Entries = entries }); _baselinePending.Remove(connectionId); return true; } private void BroadcastProductDelta(Guid predictionId, RestockData data, bool unlocked) { //IL_0045: 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) if (!_shutdown && _context.InGame() && data != null) { _context.Broadcast((INetMessage)(object)new CatalogLicenseDeltaMessage { PredictionId = predictionId, Scanner = false, Unlocked = unlocked, ItemType = data.itemType, IsBigBox = data.isBigBox, Name = (data.name ?? "") }); } } private void BroadcastScannerDelta(Guid predictionId, bool unlocked) { if (!_shutdown && _context.InGame()) { _context.Broadcast((INetMessage)(object)new CatalogLicenseDeltaMessage { PredictionId = predictionId, Scanner = true, Unlocked = unlocked }); } } private void SendPendingBaselines() { foreach (int item in new List(_baselinePending)) { if (!_joined.Contains(item)) { _baselinePending.Remove(item); } else { SendState(item); } } } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; CEventManager.RemoveListener((EventDelegate)OnReady); SceneManager.sceneLoaded -= OnSceneLoaded; _joined.Clear(); _baselinePending.Clear(); if (_active == this) { _active = null; } _context = null; } } private void OnDestroy() { Shutdown(); } } public enum EnumKind { ItemType, ObjectType, DecoObject, CardExpansion, MonsterType, Rarity, CollectionPack } internal static class CatalogIdMap { private sealed class EnumValueMap { private readonly Dictionary> _hostToLocal = new Dictionary>(); private readonly Dictionary> _localToHost = new Dictionary>(); public int Kinds => _hostToLocal.Count; public void Add(EnumKind kind, int hostValue, int localValue) { if (!_hostToLocal.TryGetValue(kind, out var value)) { value = (_hostToLocal[kind] = new Dictionary()); } if (!value.ContainsKey(hostValue)) { value[hostValue] = localValue; } if (!_localToHost.TryGetValue(kind, out var value2)) { value2 = (_localToHost[kind] = new Dictionary()); } if (!value2.ContainsKey(localValue)) { value2[localValue] = hostValue; } } public bool TryHostToLocal(EnumKind kind, int hostValue, out int localValue) { if (_hostToLocal.TryGetValue(kind, out var value) && value.TryGetValue(hostValue, out var value2)) { localValue = value2; return true; } localValue = hostValue; return false; } public bool TryLocalToHost(EnumKind kind, int localValue, out int hostValue) { if (_localToHost.TryGetValue(kind, out var value) && value.TryGetValue(localValue, out var value2)) { hostValue = value2; return true; } hostValue = localValue; return false; } } private static readonly object _mapLock = new object(); private static EnumValueMap _enumMap; private static string _lastSummary; private static int _buildCount; internal static bool HasEnumMap => _enumMap != null; public static int Sentinel(EnumKind kind) { return kind switch { EnumKind.DecoObject => 0, EnumKind.MonsterType => 0, _ => -1, }; } internal static int ToHostValue(EnumKind kind, int localValue) { EnumValueMap enumMap = _enumMap; if (enumMap != null && enumMap.TryLocalToHost(kind, localValue, out var hostValue)) { return hostValue; } return localValue; } internal static int FromHostValue(EnumKind kind, int hostValue) { EnumValueMap enumMap = _enumMap; if (enumMap != null && enumMap.TryHostToLocal(kind, hostValue, out var localValue)) { return localValue; } return hostValue; } internal static Type WireType(EnumKind kind) { return kind switch { EnumKind.ItemType => typeof(EItemType), EnumKind.ObjectType => typeof(EObjectType), EnumKind.DecoObject => typeof(EDecoObject), EnumKind.CardExpansion => typeof(ECardExpansionType), EnumKind.MonsterType => typeof(EMonsterType), EnumKind.Rarity => typeof(ERarity), EnumKind.CollectionPack => typeof(ECollectionPackType), _ => throw new ArgumentOutOfRangeException("kind", kind, "Unknown enum kind"), }; } internal static bool IsDefined(EnumKind kind, int value) { try { return Enum.IsDefined(WireType(kind), value); } catch (Exception) { return false; } } public static void Clear() { lock (_mapLock) { _enumMap = null; } } public static void Build(List hostEnumIdentity) { Clear(); _buildCount++; try { EnumValueMap enumValueMap = BuildEnumMap(hostEnumIdentity); lock (_mapLock) { _enumMap = enumValueMap; } string text = "value map ready (" + enumValueMap.Kinds + " enum kinds)"; if (!string.Equals(text, _lastSummary, StringComparison.Ordinal)) { _lastSummary = text; Log(text); } else { Log("value map ready (unchanged, join #" + _buildCount + ")"); } } catch (Exception ex) { Clear(); LogWarn("value map could not be built (" + ex.Message + ") - running with local ids"); } } private static EnumValueMap BuildEnumMap(List hostIdentity) { EnumValueMap enumValueMap = new EnumValueMap(); if (hostIdentity == null || hostIdentity.Count == 0) { return enumValueMap; } Dictionary> dictionary = new Dictionary>(); foreach (EnumKindIdentityDto item in CatalogParity.EnumIdentity()) { if (item == null || item.Members == null) { continue; } Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); foreach (EnumMemberDto member in item.Members) { if (member != null && member.Name != null && member.Value >= int.MinValue && member.Value <= int.MaxValue) { dictionary2[member.Name] = (int)member.Value; } } dictionary[item.Kind] = dictionary2; } foreach (EnumKindIdentityDto item2 in hostIdentity) { if (item2 == null || item2.Members == null || !dictionary.TryGetValue(item2.Kind, out var value)) { continue; } EnumKind kind = (EnumKind)item2.Kind; int num = 0; int num2 = 0; foreach (EnumMemberDto member2 in item2.Members) { if (member2 != null && member2.Name != null && member2.Value >= int.MinValue && member2.Value <= int.MaxValue && value.TryGetValue(member2.Name, out var value2)) { enumValueMap.Add(kind, (int)member2.Value, value2); num++; if ((int)member2.Value != value2) { num2++; } if (member2.Name == "BaseSetBoosterPacks" || member2.Name == "ykOPDenDenMushi_Mugiwaras" || member2.Name == "BaseSet") { Log("sample " + kind.ToString() + " " + member2.Name + " host=" + member2.Value + " local=" + value2); } } } Log("map " + kind.ToString() + " mapped=" + num + " changed=" + num2); } return enumValueMap; } private static void Log(string s) { try { CoopPlugin.Log.LogInfo((object)("CatalogIdMap: " + s)); } catch (Exception e) { Swallow.Log(e, "Log", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogIdMap.cs", 348); } } private static void LogWarn(string s) { try { CoopPlugin.Log.LogWarning((object)("CatalogIdMap: " + s)); } catch (Exception e) { Swallow.Log(e, "LogWarn", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogIdMap.cs", 357); } } } internal static class CatalogInterop { internal sealed class LicenseState { internal readonly List RawBits = new List(); internal readonly List Bits = new List(); } internal sealed class LicenseBit { internal int Index; internal bool Unlocked; } internal const int MaxEntries = 32768; private const BindingFlags AnyMember = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static bool _eplProbed; private static bool _eplLogged; private static bool _eplSurfaceFailed; private static int _eplExtraCountLogged = -1; private static object _eplItemLibraryInstance; private static PropertyInfo _eplAssets; private static PropertyInfo _eplItemLibrary; private static PropertyInfo _eplRestockEntries; private static PropertyInfo _eplServices; private static PropertyInfo _eplSaveDataManager; private static MethodInfo _eplTryGetSaveData; private static PropertyInfo _eplSmallUnlocked; private static PropertyInfo _eplBigUnlocked; private static FieldInfo _licenseList; private static readonly HashSet ReportedAmbiguities = new HashSet(StringComparer.Ordinal); private static readonly Dictionary ReportedUnwireableItemTypes = new Dictionary(); private const long UnwireableItemLogIntervalTicks = 600000000L; internal static int Count => (SceneInventory()?.m_StockItemData_SO?.m_RestockDataList?.Count).GetValueOrDefault() + EplExtraCount(); internal static int RawCount => (SceneInventory()?.m_StockItemData_SO?.m_RestockDataList?.Count).GetValueOrDefault(); internal static bool IsSceneReady { get { if ((Object)(object)SceneInventory() != (Object)null) { return Count > 0; } return false; } } internal static bool IsWireableItemType(EItemType itemType, string context = null) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Expected I4, but got Unknown //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_004e: Unknown result type (might be due to invalid IL or missing references) int num = (int)itemType; if ((int)itemType == -1) { return false; } if ((int)itemType == 130) { CoopPlugin.Log.LogWarning((object)("Catalog rejected EItemType.Max sentinel" + (string.IsNullOrEmpty(context) ? "" : (" while building " + context)))); return false; } if (num >= 0 && Enum.GetName(typeof(EItemType), itemType) != null) { return true; } long ticks = DateTime.UtcNow.Ticks; if (!ReportedUnwireableItemTypes.TryGetValue(num, out var value) || ticks - value >= 600000000) { ReportedUnwireableItemTypes[num] = ticks; CoopPlugin.Log.LogWarning((object)("Catalog rejected non-wireable EItemType " + num + (string.IsNullOrEmpty(context) ? "" : (" while building " + context)) + "; the raw price-list slot was skipped.")); } return false; } internal static RestockData At(int index) { if (!TryAt(index, out var data)) { return null; } return data; } internal static bool TryAt(int index, out RestockData data) { data = null; if (index < 0) { return false; } try { data = InventoryBase.GetRestockData(index); return data != null; } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)$"Catalog row {index} could not be resolved: {ex.Message}"); return false; } } internal static bool TryResolve(EItemType itemType, bool isBigBox, string name, out int index, out RestockData data) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) bool transient; return TryResolve(itemType, isBigBox, name, out index, out data, out transient); } internal static bool TryResolve(EItemType itemType, bool isBigBox, string name, out int index, out RestockData data, out bool transient) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) index = -1; data = null; transient = false; if (!IsWireableItemType(itemType, "catalog product resolution")) { return false; } int count = Count; int num = 0; int num2 = 0; int num3 = -1; RestockData val = null; if (!string.IsNullOrEmpty(name)) { for (int i = 0; i < count; i++) { if (!TryAt(i, out var data2)) { transient = true; } else if (data2 != null && data2.isBigBox == isBigBox && string.Equals(data2.name, name, StringComparison.Ordinal)) { num++; if (data2.itemType == itemType) { num2++; num3 = i; val = data2; } } } if (transient) { return false; } if (num2 == 1) { index = num3; data = val; return true; } if (num2 > 1) { ReportAmbiguous(name, isBigBox, itemType, num2); return false; } if (num > 0) { ReportAmbiguous(name, isBigBox, itemType, num); return false; } } int num4 = 0; int num5 = -1; RestockData val2 = null; for (int j = 0; j < count; j++) { if (!TryAt(j, out var data3)) { transient = true; } else if (data3 != null && data3.itemType == itemType && data3.isBigBox == isBigBox) { num4++; num5 = j; val2 = data3; } } if (transient) { return false; } if (num4 == 1) { index = num5; data = val2; return true; } if (num4 > 1) { ReportAmbiguous(name, isBigBox, itemType, num4); } return false; } internal static bool TryGetLicense(int index, out bool unlocked, out bool transient) { unlocked = false; transient = false; if (index < 0) { return false; } try { unlocked = CPlayerData.GetIsItemLicenseUnlocked(index); return true; } catch (Exception ex) { transient = true; CoopPlugin.Log.LogWarning((object)$"Catalog license read failed for row {index}: {ex.Message}"); return false; } } internal static bool SetLicense(int index, bool unlocked) { if (index < 0 || index >= Count) { return false; } try { if (unlocked) { CPlayerData.SetUnlockItemLicense(index); bool unlocked2; bool transient; return TryGetLicense(index, out unlocked2, out transient) && unlocked2; } if (index < RawCount) { List list = LicenseList(); if (list == null || index >= list.Count) { return false; } list[index] = false; return true; } return SetEplVirtualLicense(index, unlocked: false); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)$"Catalog license write failed for row {index}: {ex.Message}"); return false; } } internal static bool TryCaptureLicenseState(out LicenseState state) { state = new LicenseState(); List list = LicenseList(); if (list == null) { CoopPlugin.Log.LogWarning((object)"Catalog license snapshot failed: raw license list is missing"); return false; } state.RawBits.AddRange(list); int count = Count; if (count < 0 || count > 32768) { CoopPlugin.Log.LogWarning((object)("Catalog license snapshot failed: invalid row count " + count)); return false; } for (int i = 0; i < count; i++) { if (!TryGetLicense(i, out var unlocked, out var transient)) { CoopPlugin.Log.LogWarning((object)("Catalog license snapshot failed for row " + i + (transient ? ": state was transiently unreadable" : ": state was unavailable"))); return false; } state.Bits.Add(new LicenseBit { Index = i, Unlocked = unlocked }); } return true; } internal static bool TryRestoreLicenseState(LicenseState state, out List changed) { changed = new List(); if (state == null || state.RawBits == null || state.Bits == null) { return false; } List list = LicenseList(); int count = Count; if (list == null || count != state.Bits.Count) { CoopPlugin.Log.LogError((object)"Catalog license restore refused: catalog shape changed during the purchase transaction"); return false; } List list2 = new List(count); bool transient; for (int i = 0; i < count; i++) { if (!TryGetLicense(i, out var unlocked, out transient)) { CoopPlugin.Log.LogError((object)("Catalog license restore refused: row " + i + " is unreadable")); return false; } list2.Add(unlocked); } for (int j = 0; j < state.Bits.Count; j++) { LicenseBit licenseBit = state.Bits[j]; if (list2[j] != licenseBit.Unlocked) { if (!SetLicense(licenseBit.Index, licenseBit.Unlocked)) { CoopPlugin.Log.LogError((object)("Catalog license restore failed for row " + licenseBit.Index)); return false; } changed.Add(licenseBit); } } list.Clear(); list.AddRange(state.RawBits); for (int k = 0; k < state.Bits.Count; k++) { if (!TryGetLicense(k, out var unlocked2, out transient) || unlocked2 != state.Bits[k].Unlocked) { CoopPlugin.Log.LogError((object)("Catalog license restore verification failed for row " + k)); return false; } } return true; } internal static string IdentityKey(RestockData data) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (data == null) { return null; } return IdentityKey(data.name, data.isBigBox, data.itemType); } internal static string IdentityKey(CatalogLicenseEntry entry) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (entry == null) { return null; } return IdentityKey(entry.Name, entry.IsBigBox, entry.ItemType); } internal static void ReportAmbiguousIdentity(RestockData data) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (data != null) { ReportAmbiguous(data.name, data.isBigBox, data.itemType, 2); } } internal static void ReportAmbiguousIdentity(CatalogLicenseEntry entry) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (entry != null) { ReportAmbiguous(entry.Name, entry.IsBigBox, entry.ItemType, 2); } } private static string IdentityKey(string name, bool isBigBox, EItemType itemType) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected I4, but got Unknown return name + "\u001f" + (isBigBox ? "1" : "0") + "\u001f" + ((int)itemType).ToString(CultureInfo.InvariantCulture); } private unsafe static void ReportAmbiguous(string name, bool isBigBox, EItemType itemType, int count) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) string item = IdentityKey(name, isBigBox, itemType); if (ReportedAmbiguities.Add(item)) { CoopPlugin.Log.LogWarning((object)("Catalog identity is ambiguous; refusing to choose among " + count + " rows for name='" + name + "' big=" + isBigBox + " itemType=" + ((object)(*(EItemType*)(&itemType))/*cast due to .constrained prefix*/).ToString())); } } private static InventoryBase SceneInventory() { return SceneRef.Get(); } internal static bool TryBuildLicenseEntries(out List entries, out bool scannerUnlocked) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) entries = new List(); scannerUnlocked = false; int count = Count; if (count <= 0 || count > 32768) { return false; } scannerUnlocked = CPlayerData.m_IsScannerRestockUnlocked; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < count; i++) { if (!TryAt(i, out var data)) { entries = new List(); return false; } if (data != null && !string.IsNullOrEmpty(data.name) && IsWireableItemType(data.itemType, "catalog license state")) { string key = IdentityKey(data); dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; } } for (int j = 0; j < count; j++) { if (!TryAt(j, out var data2)) { entries = new List(); return false; } if (data2 == null || string.IsNullOrEmpty(data2.name) || !dictionary.TryGetValue(IdentityKey(data2), out var value2)) { continue; } bool unlocked; bool transient; if (value2 != 1) { ReportAmbiguousIdentity(data2); } else if (!TryGetLicense(j, out unlocked, out transient)) { if (transient) { entries = new List(); return false; } } else if (unlocked) { entries.Add(new CatalogLicenseEntry { ItemType = data2.itemType, IsBigBox = data2.isBigBox, Name = data2.name }); } } return true; } private static List LicenseList() { if ((object)_licenseList == null) { _licenseList = typeof(CPlayerData).GetField("m_IsItemLicenseUnlocked", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } return _licenseList?.GetValue(null) as List; } private static int EplExtraCount() { if (_eplSurfaceFailed) { return 0; } ProbeEplIfNeeded(); if (_eplRestockEntries == null || _eplItemLibraryInstance == null) { return 0; } int num; try { num = (_eplRestockEntries.GetValue(_eplItemLibraryInstance) as ICollection)?.Count ?? 0; } catch (Exception error) { FailEplSurface(error); return 0; } if (num > 0 && num != _eplExtraCountLogged) { _eplExtraCountLogged = num; CoopPlugin.Log.LogInfo((object)("Catalog: EPL virtual products enabled (" + num + " rows).")); } return num; } internal static void ProbeOptionalSurfaces() { ProbeEplIfNeeded(); } private static void ProbeEplIfNeeded() { if (_eplProbed) { return; } _eplProbed = true; try { Type type = CatalogParity.ResolveType("EnhancedPrefabLoader.Core.EplRuntimeData", "EnhancedPrefabLoader"); _eplAssets = type?.GetProperty("Assets", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj = _eplAssets?.GetValue(null); _eplItemLibrary = obj?.GetType().GetProperty("ItemLibrary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _eplItemLibraryInstance = _eplItemLibrary?.GetValue(obj); _eplRestockEntries = _eplItemLibraryInstance?.GetType().GetProperty("RestockEntries", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _eplServices = type?.GetProperty("Services", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj2 = _eplServices?.GetValue(null); _eplSaveDataManager = obj2?.GetType().GetProperty("SaveDataManager", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); ResolveEplSaveDataSurface(_eplSaveDataManager?.GetValue(obj2)); if ((_eplRestockEntries == null || _eplItemLibraryInstance == null) && !_eplLogged) { _eplLogged = true; CoopPlugin.Log.LogInfo((object)"Catalog: EPL virtual products unavailable; using vanilla rows."); } } catch (Exception error) { FailEplSurface(error); } } private static void FailEplSurface(Exception error) { _eplAssets = null; _eplItemLibrary = null; _eplItemLibraryInstance = null; _eplRestockEntries = null; _eplServices = null; _eplSaveDataManager = null; _eplSurfaceFailed = true; _eplLogged = true; CoopPlugin.Log.LogWarning((object)("Catalog: EPL surface unavailable (" + error.GetType().Name + ": " + error.Message + "); using vanilla rows.")); } private static void ResolveEplSaveDataSurface(object saveDataManager) { if (saveDataManager == null || _eplTryGetSaveData != null) { return; } MethodInfo[] methods = saveDataManager.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "TryGetSaveData") && methodInfo.IsGenericMethodDefinition && methodInfo.GetGenericArguments().Length == 2 && methodInfo.GetParameters().Length == 2) { Type obj = (_eplTryGetSaveData = methodInfo.MakeGenericMethod(typeof(EItemType), CatalogParity.ResolveType("EnhancedPrefabLoader.Core.Models.SaveData.ItemSaveData", "EnhancedPrefabLoader"))).GetGenericArguments()[1]; _eplBigUnlocked = obj.GetProperty("IsBigBoxUnlocked", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _eplSmallUnlocked = obj.GetProperty("IsSmallBoxUnlocked", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); break; } } } private static bool SetEplVirtualLicense(int index, bool unlocked) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) ProbeEplIfNeeded(); int valueOrDefault = (SceneInventory()?.m_StockItemData_SO?.m_RestockDataList?.Count).GetValueOrDefault(); RestockData val = At(index); if (val == null || index < valueOrDefault || _eplTryGetSaveData == null) { return false; } object obj = _eplServices?.GetValue(null); object obj2 = _eplSaveDataManager?.GetValue(obj); if (obj2 == null) { return false; } object[] array = new object[2] { val.itemType, null }; if (!(bool)_eplTryGetSaveData.Invoke(obj2, array) || array[1] == null) { return false; } PropertyInfo propertyInfo = (val.isBigBox ? _eplBigUnlocked : _eplSmallUnlocked); if (propertyInfo == null || !propertyInfo.CanWrite) { return false; } propertyInfo.SetValue(array[1], unlocked); return true; } } [NetworkMessage] public sealed class CatalogLicenseStateMessage : INetMessage { public bool ScannerUnlocked; public List Entries = new List(); } [NetworkMessage] public sealed class CatalogLicenseDeltaMessage : IPredictedMessage, INetMessage { public bool Scanner; public bool Unlocked; public EItemType ItemType; public bool IsBigBox; public string Name = ""; public Guid PredictionId { get; set; } } public sealed class CatalogLicenseEntry { public EItemType ItemType; public bool IsBigBox; public string Name = ""; } internal static class CatalogParity { private static string _enum; private static readonly (string TypeName, EnumKind Kind)[] ModdedEnumTypes = new(string, EnumKind)[6] { ("EObjectType", EnumKind.ObjectType), ("EDecoObject", EnumKind.DecoObject), ("EItemType", EnumKind.ItemType), ("ECardExpansionType", EnumKind.CardExpansion), ("ERarity", EnumKind.Rarity), ("ECollectionPackType", EnumKind.CollectionPack) }; private static int _enumFilterVersion = -1; private static bool _enumSourceLogged; private static readonly string[] EplSentinelTypeNames = new string[2] { "EnhancedPrefabLoader.Core.EplRuntimeData", "EnhancedPrefabLoader.Core.Models.SaveData.ItemSaveData" }; private const string EplAssemblyName = "EnhancedPrefabLoader"; private static bool _eplLoaded; private static bool _eplProbeComplete; private static bool _eplLoadedLogged; internal static string CardsHashForLines(List lines) { if (!TryCanonicalizeCardLines(lines, out var canonical, out var failureReason)) { throw new InvalidDataException("cannot hash invalid CardForge card list: " + failureReason); } if (canonical.Count != 0) { return Short(Sha1(string.Join(";", canonical))); } return "none"; } public static List CardsList() { return CardEntries(); } private static List CardEntries() { string path = Path.Combine(Paths.BepInExRootPath, "patchers", "CreateCardsPreloader", "MonsterConfigs"); List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(); if (Directory.Exists(path)) { string[] files = Directory.GetFiles(path, "*.ini"); foreach (string text in files) { string text2 = null; string s = null; bool flag = false; bool flag2 = false; string[] array = File.ReadAllLines(text); foreach (string text3 in array) { int num = text3.IndexOf('='); if (num <= 0) { continue; } string text4 = text3.Substring(0, num).Trim(); if (text4.Equals("Monster Type", StringComparison.OrdinalIgnoreCase)) { if (flag) { throw new InvalidDataException("CardForge config '" + text + "' contains duplicate Monster Type entries"); } flag = true; text2 = text3.Substring(num + 1).Trim(); } else if (text4.Equals("Monster Type ID", StringComparison.OrdinalIgnoreCase)) { if (flag2) { throw new InvalidDataException("CardForge config '" + text + "' contains duplicate Monster Type ID entries"); } flag2 = true; s = text3.Substring(num + 1).Trim(); } } if (!flag || string.IsNullOrEmpty(text2)) { throw new InvalidDataException("CardForge config '" + text + "' has no nonempty Monster Type"); } if (text2.IndexOf('=') >= 0 || text2.IndexOf('\r') >= 0 || text2.IndexOf('\n') >= 0) { throw new InvalidDataException("CardForge config '" + text + "' has a Monster Type that cannot be represented canonically"); } if (!flag2 || !int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { throw new InvalidDataException("CardForge config '" + text + "' has a non-integer Monster Type ID"); } string item = text2 + "=" + result.ToString(CultureInfo.InvariantCulture); if (!hashSet.Add(text2)) { throw new InvalidDataException("CardForge configs contain duplicate Monster Type name '" + text2 + "'"); } if (!hashSet2.Add(result)) { throw new InvalidDataException("CardForge configs contain duplicate Monster Type ID " + result.ToString(CultureInfo.InvariantCulture)); } list.Add(item); } } list.Sort(StringComparer.Ordinal); return list; } internal static bool TryCanonicalizeCardLines(IList source, out List canonical, out string failureReason) { canonical = new List(); failureReason = null; if (source == null) { failureReason = "CardForge card list is missing"; return false; } HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(); for (int i = 0; i < source.Count; i++) { string text = source[i]; if (text == null) { failureReason = "CardForge card line " + i + " is null"; return false; } string text2 = text.Trim(); int num = text2.IndexOf('='); if (text2.Length == 0 || !string.Equals(text, text2, StringComparison.Ordinal) || num <= 0 || num != text2.LastIndexOf('=')) { failureReason = "CardForge card line " + i + " is not canonical name=integer-id"; return false; } string text3 = text2.Substring(0, num); string s = text2.Substring(num + 1); if (text3.Length == 0 || text3.Trim().Length != text3.Length || text3.IndexOf('\r') >= 0 || text3.IndexOf('\n') >= 0 || !int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { failureReason = "CardForge card line " + i + " is not canonical name=integer-id"; return false; } string text4 = text3 + "=" + result.ToString(CultureInfo.InvariantCulture); if (!string.Equals(text2, text4, StringComparison.Ordinal)) { failureReason = "CardForge card line " + i + " is not canonical name=integer-id"; return false; } if (!hashSet.Add(text3)) { failureReason = "CardForge card list contains duplicate name '" + text3 + "'"; return false; } if (!hashSet2.Add(result)) { failureReason = "CardForge card list contains duplicate ID " + result.ToString(CultureInfo.InvariantCulture); return false; } canonical.Add(text4); } canonical.Sort(StringComparer.Ordinal); return true; } public static Type ResolveType(string typeName, string assemblySimpleName) { try { Type type = Type.GetType(typeName + ", " + assemblySimpleName, throwOnError: false); if (type != null) { return type; } } catch (Exception e) { Swallow.Log(e, "ResolveType", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 224); } try { return AccessTools.TypeByName(typeName); } catch (Exception e2) { Swallow.Log(e2, "ResolveType", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 229); return null; } } public static string EnumHash() { if (_enum != null && _enumFilterVersion == EplActiveEnums.Version) { return _enum; } try { _enum = EnumHashForIdentity(EnumIdentity()); } catch { _enum = "none"; } _enumFilterVersion = EplActiveEnums.Version; return _enum; } internal static string EnumHashForIdentity(List identity) { List list = CanonicalHashLines(IdentityLines(identity)); if (list.Count != 0) { return Short(Sha1(string.Join("\n", list))); } return "none"; } public static List EnumIdentity() { try { int filtered; List list = BuildEnumIdentity(out filtered); int num = 0; foreach (EnumKindIdentityDto item in list) { num += ((item.Members != null) ? item.Members.Count : 0); } if (num == 0) { LogEnumSourceOnce("runtime enum walk found no members (the enum types did not resolve)"); } else { LogEnumSourceOnce("runtime enums (" + num + " members)" + ((filtered > 0) ? ("; EPL active filter dropped " + filtered + " leftover members") : "")); } return list; } catch (Exception e) { Swallow.Log(e, "EnumIdentity", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 330); return new List(); } } private static List IdentityLines(List identity) { List list = new List(); if (identity == null) { return list; } foreach (EnumKindIdentityDto item in identity) { if (item == null || item.Members == null) { continue; } string name; try { name = CatalogIdMap.WireType((EnumKind)item.Kind).Name; } catch { continue; } foreach (EnumMemberDto member in item.Members) { if (member != null && member.Name != null) { list.Add(name + ":" + member.Name + "=" + member.Value); } } } return list; } private static void LogEnumSourceOnce(string source) { if (_enumSourceLogged) { return; } _enumSourceLogged = true; try { CoopPlugin.Log.LogInfo((object)("enum identity source: " + source)); } catch (Exception e) { Swallow.Log(e, "LogEnumSourceOnce", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 388); } } private static List BuildEnumIdentity(out int filtered) { List list = new List(); EplActiveEnums.ActiveEnumFilter activeEnumFilter = EplActiveEnums.Current(); filtered = 0; (string, EnumKind)[] moddedEnumTypes = ModdedEnumTypes; for (int i = 0; i < moddedEnumTypes.Length; i++) { var (text, kind) = moddedEnumTypes[i]; try { Type type = AccessTools.TypeByName(text); if (type == null || !type.IsEnum) { continue; } string[] names = Enum.GetNames(type); Array values = Enum.GetValues(type); int num = Math.Min(names.Length, values.Length); List list2 = new List(num); for (int j = 0; j < num; j++) { long num2; try { num2 = Convert.ToInt64(values.GetValue(j)); } catch { continue; } if (activeEnumFilter != null && !activeEnumFilter.IsActive(text, names[j], num2)) { filtered++; continue; } list2.Add(new EnumMemberDto { Name = names[j], Value = num2 }); } list2.Sort((EnumMemberDto left, EnumMemberDto right) => string.CompareOrdinal(left.Name, right.Name)); list.Add(new EnumKindIdentityDto { Kind = (int)kind, Members = list2 }); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("enum walk (" + text + "): " + ex.Message)); } } return list; } public static string EnumFilePath() { return Path.Combine(Application.persistentDataPath, "PrefabLoader", "enum_values.json"); } private static string EnumMarkerPath() { return EnumFilePath() + ".hostlend"; } public static bool EplLoaded() { if (_eplProbeComplete) { return _eplLoaded; } _eplProbeComplete = true; string[] eplSentinelTypeNames = EplSentinelTypeNames; foreach (string text in eplSentinelTypeNames) { try { if (Type.GetType(text + ", EnhancedPrefabLoader", throwOnError: false) == null) { continue; } _eplLoaded = true; break; } catch (Exception e) { Swallow.Log(e, "EplLoaded", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 523); } } if (!_eplLoaded) { try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { BepInPlugin val = ((pluginInfo.Value != null) ? pluginInfo.Value.Metadata : null); if (val != null && (Mentions(pluginInfo.Key, "enhancedprefabloader") || Mentions(val.Name, "enhancedprefabloader"))) { _eplLoaded = true; break; } } } catch (Exception e2) { Swallow.Log(e2, "EplLoaded", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 544); } } if (_eplLoaded && !_eplLoadedLogged) { _eplLoadedLogged = true; try { CoopPlugin.Log.LogInfo((object)"EnhancedPrefabLoader detected - a custom id registry is in play"); } catch (Exception e3) { Swallow.Log(e3, "EplLoaded", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 553); } } return _eplLoaded; } private static bool Mentions(string haystack, string needle) { if (haystack != null) { return haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } public static bool HostEnumInstalled() { try { if (!EplLoaded()) { return false; } return File.Exists(EnumMarkerPath()); } catch (Exception e) { Swallow.Log(e, "HostEnumInstalled", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 585); return false; } } public static bool RestoreEnumBackup(out string message) { string text = EnumFilePath(); try { string directoryName = Path.GetDirectoryName(text); string text2 = null; if (Directory.Exists(directoryName)) { string[] files = Directory.GetFiles(directoryName, Path.GetFileName(text) + ".coopbak-*"); Array.Sort(files, (IComparer?)StringComparer.Ordinal); if (files.Length != 0) { text2 = files[^1]; } } if (text2 == null) { try { File.Delete(EnumMarkerPath()); } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("enum marker clear: " + ex.Message)); } bool flag; try { flag = !File.Exists(EnumMarkerPath()); } catch { flag = false; } message = (flag ? "no backup of your card database was found, so your card database was left exactly as it is - the 'borrowed from a host' flag has been cleared. If your solo saves still won't load, put your own enum_values.json back by hand (see mod page)." : "no backup of your card database was found, and the co-op marker could not be deleted - delete enum_values.json.hostlend by hand (it sits next to enum_values.json; see mod page)"); CoopPlugin.Log.LogWarning((object)("enum restore: " + message)); return flag; } if (File.Exists(text)) { File.Copy(text, text + ".hostcopy", overwrite: true); } File.Copy(text2, text, overwrite: true); try { File.Delete(EnumMarkerPath()); } catch (Exception e) { Swallow.Log(e, "RestoreEnumBackup", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/CatalogParity.cs", 647); } message = "your card database was restored from backup - RESTART the game before loading your solo saves"; CoopPlugin.Log.LogInfo((object)("enum restore: " + message + " (from " + Path.GetFileName(text2) + ")")); return true; } catch (Exception ex2) { CoopPlugin.Log.LogWarning((object)("enum restore failed: " + ex2.Message)); message = "could not restore your card database automatically - put your own enum_values.json backup back by hand (see mod page)"; return false; } } private static string Sha1(string s) { return Sha1Bytes(Encoding.UTF8.GetBytes(s)); } private static string Sha1Bytes(byte[] data) { using SHA1 sHA = SHA1.Create(); return BitConverter.ToString(sHA.ComputeHash(data)).Replace("-", ""); } private static List CanonicalHashLines(List lines) { List list = new List(); if (lines != null) { foreach (string line in lines) { string text = ((line == null) ? "" : line.Trim()); if (text.Length > 0) { list.Add(text); } } } list.Sort(StringComparer.Ordinal); return list; } private static string Short(string hex) { if (hex.Length <= 16) { return hex; } return hex.Substring(0, 16); } } internal static class CatalogPatches { [HarmonyPatch(typeof(CPlayerData), "ResetData")] private static class ResetData { [HarmonyPostfix] private static void Postfix() { CatalogClientBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CPlayerData), "CreateDefaultData")] private static class CreateDefaultData { [HarmonyPostfix] private static void Postfix() { CatalogClientBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CGameData), "PropagateLoadData")] private static class PropagateLoadData { [HarmonyPostfix] private static void Postfix() { CatalogClientBehaviour.InventoryReset(); } } [HarmonyPatch(typeof(CSaveLoad), "Load")] private static class SaveLoad { [HarmonyPostfix] private static void Postfix() { if (!SaveEnumRemap.TryConsume()) { return; } try { object obj = typeof(CSaveLoad).GetField("m_SavedGame", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (obj != null) { SaveEnumRemap.Apply(obj); CoopPlugin.Log.LogInfo((object)"save enum remap: translated the borrowed world's ids."); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("save enum remap failed: " + ex.Message)); } } } internal static void ApplyClientLifecycle(Harmony harmony) { harmony.CreateClassProcessor(typeof(ResetData)).Patch(); harmony.CreateClassProcessor(typeof(CreateDefaultData)).Patch(); harmony.CreateClassProcessor(typeof(PropagateLoadData)).Patch(); harmony.CreateClassProcessor(typeof(SaveLoad)).Patch(); } } internal static class EplActiveEnums { private sealed class PrepatchState { internal Dictionary> MintedValues; internal Dictionary> ActiveNames; } internal sealed class ActiveEnumFilter { private readonly Dictionary> _mintedValues; private readonly Dictionary> _activeNames; private readonly Dictionary> _runtimeValues; internal ActiveEnumFilter(Dictionary> mintedValues, Dictionary> activeNames, Dictionary> runtimeValues) { _mintedValues = mintedValues; _activeNames = activeNames; _runtimeValues = runtimeValues; } internal bool IsActive(string enumTypeName, string memberName, long memberValue) { if (!_mintedValues.TryGetValue(enumTypeName, out var value) || !value.Contains(memberValue)) { return true; } if (_activeNames.TryGetValue(enumTypeName, out var value2) && value2.Contains(memberName)) { return true; } if (_runtimeValues != null && _runtimeValues.TryGetValue(enumTypeName, out var value3)) { return value3.Contains(memberValue); } return false; } } private const BindingFlags AnyMember = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private const string PrepatchAssemblyName = "EnhancedPrefabLoaderPrepatch"; private const string PrepatchPatcherTypeName = "EnhancedPrefabLoader.Prepatch.Patcher"; private const string PrepatchManagerFieldName = "patchDataManager"; private const string EplApiAssemblyName = "EnhancedPrefabLoader.API"; private const string EplApiTypeName = "EnhancedPrefabLoader.API.Epl"; private const string EplCoreAssemblyName = "EnhancedPrefabLoader"; private const string EplRuntimeDataTypeName = "EnhancedPrefabLoader.Core.EplRuntimeData"; private static readonly Regex EplWhitespace = new Regex("\\s", RegexOptions.Compiled); private static readonly Regex EplNonWord = new Regex("[^\\w]", RegexOptions.Compiled); private static readonly object Gate = new object(); private static bool _prepatchProbed; private static PrepatchState _prepatch; private static bool _runtimeProbed; private static Dictionary> _runtimeValues; private static int _sourceMask; private static int _version; private static bool _unavailableLogged; internal static int Version { get { lock (Gate) { return _version; } } } internal static ActiveEnumFilter Current() { lock (Gate) { if (!CatalogParity.EplLoaded()) { return null; } if (!_prepatchProbed) { _prepatchProbed = true; _prepatch = ProbePrepatch(); } if (_prepatch == null) { LogUnavailable("the prepatcher's live enum registry is unavailable"); return null; } if (!_runtimeProbed) { _runtimeValues = ProbeRuntimeValues(); if (_runtimeValues != null) { _runtimeProbed = true; } } int num = ((_runtimeValues == null) ? 1 : 3); if (num != _sourceMask) { _sourceMask = num; _version++; LogSources(num == 3); } return new ActiveEnumFilter(_prepatch.MintedValues, _prepatch.ActiveNames, _runtimeValues); } } private static PrepatchState ProbePrepatch() { try { object obj = (FindLoadedAssembly("EnhancedPrefabLoaderPrepatch")?.GetType("EnhancedPrefabLoader.Prepatch.Patcher", throwOnError: false))?.GetField("patchDataManager", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null); if (obj == null) { return null; } Type type = obj.GetType(); if (!(type.GetProperty("SavedData", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is IDictionary dictionary) || !(type.GetProperty("UserData", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is IDictionary dictionary2)) { return null; } PrepatchState prepatchState = new PrepatchState { MintedValues = new Dictionary>(StringComparer.Ordinal), ActiveNames = new Dictionary>(StringComparer.Ordinal) }; foreach (DictionaryEntry item in dictionary) { if (!(item.Key is string key) || !(item.Value is IDictionary dictionary3)) { continue; } HashSet hashSet = new HashSet(); foreach (DictionaryEntry item2 in dictionary3) { if (item2.Value is int num) { hashSet.Add(num); } } prepatchState.MintedValues[key] = hashSet; } foreach (DictionaryEntry item3 in dictionary2) { if (!(item3.Key is string key2) || !(item3.Value is IEnumerable enumerable)) { continue; } HashSet hashSet2 = new HashSet(StringComparer.Ordinal); foreach (object item4 in enumerable) { if (item4 is string value) { hashSet2.Add(SanitizeEplName(value)); } } prepatchState.ActiveNames[key2] = hashSet2; } if (prepatchState.ActiveNames.Count == 0) { return null; } return prepatchState; } catch (Exception e) { Swallow.Log(e, "ProbePrepatch", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 232); return null; } } private static Dictionary> ProbeRuntimeValues() { try { Type type = FindLoadedAssembly("EnhancedPrefabLoader")?.GetType("EnhancedPrefabLoader.Core.EplRuntimeData", throwOnError: false); if (type == null) { return null; } object obj = type.GetProperty("IsBundleLoadingComplete", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (!(obj is bool) || !(bool)obj) { return null; } object obj2 = type.GetProperty("Assets", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (obj2 == null) { return null; } Dictionary> result = new Dictionary>(StringComparer.Ordinal); object obj3 = TryGetApiBundleRegistry() ?? obj2.GetType().GetProperty("BundleRegistry", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); if (obj3 != null && obj3.GetType().GetProperty("BundleInfo", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj3) is IDictionary dictionary) { foreach (DictionaryEntry item in dictionary) { object value = item.Value; if (value != null) { AddList(result, "EItemType", value, "ItemTypes"); AddList(result, "EObjectType", value, "FurnitureTypes"); AddList(result, "EDecoObject", value, "DecoObjects"); AddList(result, "ECardExpansionType", value, "ExpansionTypes"); } } } object obj4 = obj2.GetType().GetProperty("LookupLibrary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); if (obj4?.GetType().GetProperty("ExpansionLookup", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj4) is IDictionary dictionary2) { AddValues(result, "ECollectionPackType", dictionary2.Keys); } object obj5 = obj2.GetType().GetProperty("CardExpansionLibrary", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); if (obj5?.GetType().GetProperty("CardExpansionData", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj5) is IDictionary dictionary3) { foreach (DictionaryEntry item2 in dictionary3) { object value2 = item2.Value; if (value2?.GetType().GetProperty("Rarities", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value2) is IEnumerable values) { AddValues(result, "ERarity", values); } } } return result; } catch (Exception e) { Swallow.Log(e, "ProbeRuntimeValues", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 315); return null; } } private static object TryGetApiBundleRegistry() { try { Type type = FindLoadedAssembly("EnhancedPrefabLoader.API")?.GetType("EnhancedPrefabLoader.API.Epl", throwOnError: false); if (type == null) { return null; } object obj = type.GetProperty("IsAvailable", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); if (!(obj is bool) || !(bool)obj) { return null; } object obj2 = type.GetProperty("Api", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); return obj2?.GetType().GetProperty("BundleRegistry", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); } catch (Exception e) { Swallow.Log(e, "TryGetApiBundleRegistry", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 344); return null; } } private static Assembly FindLoadedAssembly(string simpleName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (string.Equals(assembly.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase)) { return assembly; } } catch (Exception e) { Swallow.Log(e, "FindLoadedAssembly", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 365); } } return null; } private static void AddList(Dictionary> result, string enumTypeName, object owner, string propertyName) { if (owner.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(owner) is IEnumerable values) { AddValues(result, enumTypeName, values); } } private static void AddValues(Dictionary> result, string enumTypeName, IEnumerable values) { if (!result.TryGetValue(enumTypeName, out var value)) { value = (result[enumTypeName] = new HashSet()); } foreach (object value2 in values) { try { value.Add(Convert.ToInt64(value2, CultureInfo.InvariantCulture)); } catch (Exception e) { Swallow.Log(e, "AddValues", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 396); } } } private static string SanitizeEplName(string value) { if (string.IsNullOrWhiteSpace(value)) { return "_Invalid"; } string text = EplNonWord.Replace(EplWhitespace.Replace(value, ""), "_"); if (text.Length <= 0 || char.IsDigit(text[0])) { return "_" + text; } return text; } private static void LogSources(bool runtimeIncluded) { try { CoopPlugin.Log.LogInfo((object)(runtimeIncluded ? "EPL active enum filter: prepatch descriptor registry + runtime bundle registry" : "EPL active enum filter: prepatch descriptor registry (runtime registry not ready)")); } catch (Exception e) { Swallow.Log(e, "LogSources", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 424); } } private static void LogUnavailable(string reason) { if (_unavailableLogged) { return; } _unavailableLogged = true; try { CoopPlugin.Log.LogWarning((object)("EPL detected, but the active-enum filter is unavailable (" + reason + "); the enum identity keeps every minted member, as before.")); } catch (Exception e) { Swallow.Log(e, "LogUnavailable", "/_work/cardshopcoop-07AWXgkEaDYJk/CardShopCoop/CardShopCoop/src/CardShopCoop/Modules/Catalog/EplActiveEnums.cs", 442); } } } internal static class SaveEnumRemap { private sealed class RemapStats { public int Changed; private readonly List _samples = new List(); public void Record(string label, int hostValue, int localValue) { Changed++; if (_samples.Count < 12) { _samples.Add(label + " " + hostValue + "->" + localValue); } } public string Describe() { if (_samples.Count != 0) { return " " + string.Join("; ", _samples); } return ""; } } private sealed class ReferenceComparer : IEqualityComparer { public static readonly ReferenceComparer Instance = new ReferenceComparer(); public new bool Equals(object x, object y) { return x == y; } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } private static int _armed; private const int MaxDepth = 8; public static void Arm() { Interlocked.Exchange(ref _armed, 1); } public static void Disarm() { Interlocked.Exchange(ref _armed, 0); } public static bool TryConsume() { return Interlocked.Exchange(ref _armed, 0) == 1; } public static void Apply(object root) { if (root != null) { RemapStats remapStats = new RemapStats(); Walk(root, new HashSet(ReferenceComparer.Instance), 0, remapStats); CoopPlugin.Log.LogInfo((object)("save enum remap: changed " + remapStats.Changed + " gated value(s)." + remapStats.Describe())); } } private static void Walk(object node, HashSet seen, int depth, RemapStats stats) { if (node == null || depth > 8) { return; } Type type = node.GetType(); if (type.IsPrimitive || type.IsEnum || type == typeof(string) || type.IsArray || !IsGameSaveType(type) || !seen.Add(node)) { return; } FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { object value; try { value = fieldInfo.GetValue(node); } catch (Exception) { continue; } if (value == null) { continue; } if (TryKindForEnum(fieldInfo.FieldType, out var kind)) { SetEnumField(fieldInfo, node, fieldInfo.FieldType, kind, value, stats); continue; } if (!fieldInfo.FieldType.IsGenericType || fieldInfo.FieldType.GetGenericTypeDefinition() != typeof(List<>)) { if (IsGameSaveType(fieldInfo.FieldType)) { Walk(value, seen, depth + 1, stats); } continue; } Type type2 = fieldInfo.FieldType.GetGenericArguments()[0]; if (!(value is IList { Count: not 0 } list)) { continue; } if (TryKindForEnum(type2, out var kind2)) { for (int j = 0; j < list.Count; j++) { object obj = list[j]; if (obj != null) { int num = SafeToInt32(obj); int num2 = CatalogIdMap.FromHostValue(kind2, num); if (num != num2) { list[j] = Enum.ToObject(type2, num2); stats.Record(fieldInfo.DeclaringType.Name + "." + fieldInfo.Name + "[" + j + "]", num, num2); } } } } else if (IsGameSaveType(type2)) { for (int k = 0; k < list.Count; k++) { Walk(list[k], seen, depth + 1, stats); } } } } private static void SetEnumField(FieldInfo field, object node, Type enumType, EnumKind kind, object value, RemapStats stats) { try { int num = SafeToInt32(value); int num2 = CatalogIdMap.FromHostValue(kind, num); if (num != num2) { field.SetValue(node, Enum.ToObject(enumType, num2)); stats.Record(field.DeclaringType.Name + "." + field.Name, num, num2); } } catch (Exception ex) { CoopPlugin.Log.LogWarning((object)("save enum remap: " + enumType.Name + "." + field.Name + ": " + ex.Message)); } } private static int SafeToInt32(object value) { try { return Convert.ToInt32(value, CultureInfo.InvariantCulture); } catch (Exception) { return 0; } } private static bool TryKindForEnum(Type type, out EnumKind kind) { for (int i = 0; i <= 6; i++) { EnumKind enumKind = (EnumKind)i; try { if (CatalogIdMap.WireType(enumKind) == type) { kind = enumKind; return true; } } catch (Exception) { } } kind = EnumKind.ItemType; return false; } private static bool IsGameSaveType(Type type) { if (type != null && type.IsClass && !type.IsAbstract) { return type.Assembly == typeof(CGameData).Assembly; } return false; } } } namespace CardShopCoop.Modules.Bills { [ClientBehaviour] public sealed class BillsClientBehaviour : CoopBehaviour { [HarmonyPatch(typeof(RentBillScreen), "OnPressPayRentBill")] private static class PayRentPatch { [HarmonyPrefix] private static bool Prefix(bool forcePay) { return BeginPayment(1, forcePay); } } [HarmonyPatch(typeof(RentBillScreen), "OnPressPayElectricBill")] private static class PayElectricPatch { [HarmonyPrefix] private static bool Prefix(bool forcePay) { return BeginPayment(2, forcePay); } } [HarmonyPatch(typeof(RentBillScreen), "OnPressPaySalaryBill")] private static class PaySalaryPatch { [HarmonyPrefix] private static bool Prefix(bool forcePay) { return BeginPayment(3, forcePay); } } [HarmonyPatch(typeof(RentBillScreen), "OnPressPayAllBill")] private static class PayAllPatch { [HarmonyPrefix] private static bool Prefix() { return BeginPayment(0, forcePay: false); } } [HarmonyPatch(typeof(RentBillScreen), "EvaluateNewDayBill")] private static class AccrualPatch { [HarmonyPrefix] private static bool Prefix() { return false; } } private static BillsClientBehaviour _active; private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private bool _joined; private BillsStateMessage _pendingState; private void OnEnable() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; bool flag2 = false; try { ResetSessionState(); BillsInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; CEventManager.AddListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded += OnSceneLoaded; flag2 = true; _harmony = new Harmony("dev.meepen.cardshopcoop.bills.client"); _harmony.CreateClassProcessor(typeof(PayRentPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PayElectricPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PaySalaryPatch)).Patch(); _harmony.CreateClassProcessor(typeof(PayAllPatch)).Patch(); _harmony.CreateClassProcessor(typeof(AccrualPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Bills client initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (flag2) { CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; } if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } ResetSessionState(); BillsInterop.Reset(); _context = null; throw; } } [MessageHandler(typeof(BillsStateMessage))] private void HandleState(MessageContext context, BillsStateMessage message) { if (!_shutdown) { _pendingState = message; TryApplyPending(); } } [MessageHandler(typeof(BillsDeltaMessage))] private void HandleDelta(MessageContext context, BillsDeltaMessage message) { if (!_shutdown) { PredictionApi.ApplyAuthoritative(message.PredictionId, delegate { ApplyDelta(message); }); } } [MessageHandler(typeof(BillPopupMessage))] private void HandlePopup(MessageContext context, BillPopupMessage message) { if (!_shutdown && _context.InGame() && BillsInterop.IsSceneReady()) { NotEnoughResourceTextPopup.ShowText((ENotEnoughResourceText)message.Text); } } private void TryApplyPending() { if (!_shutdown && _pendingState != null && _context != null && _context.InGame() && BillsInterop.IsSceneReady()) { Apply(_pendingState); BillsInterop.Refresh(BillsInterop.FindScreen()); _pendingState = null; } } private static void Apply(BillsStateMessage message) { ApplyBill((EBillType)1, message.Rent); ApplyBill((EBillType)2, message.Electric); ApplyBill((EBillType)3, message.Employee); } private static void ApplyDelta(BillsDeltaMessage message) { if (message.All) { ApplyBill((EBillType)1, message.Rent); ApplyBill((EBillType)2, message.Electric); ApplyBill((EBillType)3, message.Employee); } else { ApplyBill((EBillType)message.BillType, message.Value); } BillsInterop.Refresh(BillsInterop.FindScreen()); } private static void ApplyBill(EBillType type, BillValue value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) BillData bill = CPlayerData.GetBill(type); bill.billDayPassed = value.DayPassed; bill.amountToPay = value.AmountToPay; } private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _) { TryApplyPending(); } private void OnSceneLoaded(Scene _, LoadSceneMode __) { BillsInterop.Reset(); TryApplyPending(); } [OnFullyJoined] private void MarkJoined(PeerConnection _) { _joined = true; TryApplyPending(); } [OnClientDisconnected] private void ForgetHost(PeerConnection connection, DisconnectInfo info) { if (connection != null && connection.Id == 1) { ResetSessionState(); } } private static bool BeginPayment(byte billType, bool forcePay) { BillsClientBehaviour client = _active; if ((Object)(object)client == (Object)null || client._shutdown || !client._joined || client._context == null || !client._context.InGame()) { return true; } if (forcePay) { return true; } BillValue rent = Copy(CPlayerData.GetBill((EBillType)1)); BillValue electric = Copy(CPlayerData.GetBill((EBillType)2)); BillValue employee = Copy(CPlayerData.GetBill((EBillType)3)); float num = PaidAmount(rent, electric, employee, billType); if (num <= 0.0001f || CPlayerData.m_CoinAmountDouble < (double)num) { return true; } ApplyPayment(billType, rent, electric, employee, refund: false); BillsInterop.Refresh(BillsInterop.FindScreen()); SoundManager.PlayAudio("SFX_CustomerBuy", 0.6f, 1f); PredictionApi.Predict("bills", delegate(Guid predictionId) { client._context.Send(1, (INetMessage)(object)new BillPaymentMessage { PredictionId = predictionId, BillType = billType }); }, delegate { ApplyPayment(billType, rent, electric, employee, refund: false); BillsInterop.Refresh(BillsInterop.FindScreen()); }, delegate { ApplyPayment(billType, rent, electric, employee, refund: true); BillsInterop.Refresh(BillsInterop.FindScreen()); }); return false; } private static float PaidAmount(BillValue rent, BillValue electric, BillValue employee, byte billType) { BillValue value; switch (billType) { case 0: return Positive(rent) + Positive(electric) + Positive(employee); default: value = employee; break; case 2: value = electric; break; case 1: value = rent; break; } return Positive(value); } private static float Positive(BillValue value) { if (value == null || !(value.AmountToPay > 0f)) { return 0f; } return value.AmountToPay; } private static void ApplyPayment(byte billType, BillValue rent, BillValue electric, BillValue employee, bool refund) { int sign = (refund ? 1 : (-1)); if (billType == 0 || billType == 1) { AddBill((EBillType)1, rent, sign); } if (billType == 0 || billType == 2) { AddBill((EBillType)2, electric, sign); } if (billType == 0 || billType == 3) { AddBill((EBillType)3, employee, sign); } } private static void AddBill(EBillType type, BillValue captured, int sign) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) BillData bill = CPlayerData.GetBill(type); bill.billDayPassed += sign * captured.DayPassed; bill.amountToPay += (float)sign * captured.AmountToPay; } private static BillValue Copy(BillData bill) { return new BillValue { DayPassed = (bill?.billDayPassed ?? 0), AmountToPay = (bill?.amountToPay ?? 0f) }; } private void ResetSessionState() { _joined = false; _pendingState = null; } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); CEventManager.RemoveListener((EventDelegate)OnWorldReady); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } ResetSessionState(); BillsInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } } [ServerBehaviour] public sealed class BillsHostBehaviour : CoopBehaviour { [HarmonyPatch(typeof(CPlayerData), "UpdateBill")] private static class BillChangedPatch { [HarmonyPostfix] private static void Postfix(EBillType billType) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(BuildDelta((byte)billType, Guid.Empty)); } } } [HarmonyPatch(typeof(CPlayerData), "SetBill")] private static class BillSetPatch { [HarmonyPostfix] private static void Postfix(EBillType billType) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_active == (Object)null) && !_active._applyingIntent) { _active.BroadcastDelta(BuildDelta((byte)billType, Guid.Empty)); } } } [HarmonyPatch(typeof(NotEnoughResourceTextPopup), "ShowText")] private static class BillPopupPatch { [HarmonyPostfix] private static void Postfix(ENotEnoughResourceText __0) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected I4, but got Unknown BillsHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && active._forwardedRequester > 0 && active._context != null) { active._context.Send(active._forwardedRequester, (INetMessage)(object)new BillPopupMessage { Text = (int)__0 }); } } } private static BillsHostBehaviour _active; private readonly HashSet _fullyJoined = new HashSet(); private CoopRuntimeContext _context; private Harmony _harmony; private bool _shutdown; private int _forwardedRequester; private bool _applyingIntent; private void OnEnable() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown if (_shutdown || _harmony != null) { return; } _context = base.RuntimeContext; bool flag = false; try { BillsInterop.Reset(); _context.Messages.RegisterAttributedHandlers(this); flag = true; _active = this; SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony("dev.meepen.cardshopcoop.bills.host"); _harmony.CreateClassProcessor(typeof(BillChangedPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BillSetPatch)).Patch(); _harmony.CreateClassProcessor(typeof(BillPopupPatch)).Patch(); } catch (Exception ex) { CoopPlugin.Log.LogError((object)("Bills host initialization failed: " + ex)); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; SceneManager.sceneLoaded -= OnSceneLoaded; if (flag) { _context.Messages.UnregisterAttributedHandlers(this); } if (_active == this) { _active = null; } BillsInterop.Reset(); _context = null; throw; } } [OnFullyJoined] private void SendInitialState(PeerConnection connection) { if (connection != null) { _fullyJoined.Add(connection.Id); SendState(connection.Id); } } [OnClientDisconnected] private void ForgetPeer(PeerConnection connection, DisconnectInfo info) { if (connection != null) { _fullyJoined.Remove(connection.Id); } } [MessageHandler(typeof(BillPaymentMessage))] private void HandlePayment(MessageContext context, BillPaymentMessage message) { if (!IsPeerMessage(context) || message == null) { return; } int id = context.Connection.Id; bool flag = false; try { if (message.BillType != 0 && !Enum.IsDefined(typeof(EBillType), (int)message.BillType)) { Rollback(context, message); return; } RentBillScreen screen = BillsInterop.FindScreen(); if ((Object)(object)screen == (Object)null) { Rollback(context, message); return; } if (!BillsInterop.TryReservePayment(message.BillType, out var token)) { _forwardedRequester = id; try { NotEnoughResourceTextPopup.ShowText((ENotEnoughResourceText)0); } finally { _forwardedRequester = 0; } Rollback(context, message); return; } _forwardedRequester = id; _applyingIntent = true; try { flag = BillsInterop.CommitPayment(token, delegate { switch (message.BillType) { case 0: screen.OnPressPayAllBill(); break; case 1: screen.OnPressPayRentBill(false); break; case 2: screen.OnPressPayElectricBill(false); break; case 3: screen.OnPressPaySalaryBill(false); break; } }); } finally { _applyingIntent = false; } _forwardedRequester = 0; if (flag) { BroadcastDelta(BuildDelta(message.BillType, message.PredictionId)); } else { Rollback(context, message); } } catch (Exception ex) { _forwardedRequester = 0; CoopPlugin.Log.LogError((object)("Bills payment failed for peer " + id + ": " + ex)); Rollback(context, message); } } private void SendState(int peer) { if (!_shutdown && _context != null && _context.InGame()) { _context.Send(peer, (INetMessage)(object)BuildState()); } } private void BroadcastDelta(BillsDeltaMessage message) { if (!_shutdown && _context != null && _context.InGame() && message != null) { _context.Broadcast((INetMessage)(object)message); } } private static BillsStateMessage BuildState() { BillData bill = CPlayerData.GetBill((EBillType)1); BillData bill2 = CPlayerData.GetBill((EBillType)2); BillData bill3 = CPlayerData.GetBill((EBillType)3); return new BillsStateMessage { Rent = new BillValue { DayPassed = bill.billDayPassed, AmountToPay = bill.amountToPay }, Electric = new BillValue { DayPassed = bill2.billDayPassed, AmountToPay = bill2.amountToPay }, Employee = new BillValue { DayPassed = bill3.billDayPassed, AmountToPay = bill3.amountToPay } }; } private static BillsDeltaMessage BuildDelta(byte billType, Guid predictionId) { BillsDeltaMessage billsDeltaMessage = new BillsDeltaMessage { PredictionId = predictionId, BillType = billType, All = (billType == 0) }; if (billsDeltaMessage.All) { billsDeltaMessage.Rent = ToValue(CPlayerData.GetBill((EBillType)1)); billsDeltaMessage.Electric = ToValue(CPlayerData.GetBill((EBillType)2)); billsDeltaMessage.Employee = ToValue(CPlayerData.GetBill((EBillType)3)); } else { billsDeltaMessage.Value = ToValue(CPlayerData.GetBill((EBillType)billType)); } return billsDeltaMessage; } private static BillValue ToValue(BillData bill) { return new BillValue { DayPassed = (bill?.billDayPassed ?? 0), AmountToPay = (bill?.amountToPay ?? 0f) }; } private static void Rollback(MessageContext context, BillPaymentMessage message) { BillsHostBehaviour active = _active; if (!((Object)(object)active == (Object)null) && context?.Connection != null && message != null) { active.SendRejectionState(context.Connection.Id, message); if (message.PredictionId != Guid.Empty) { PredictionApi.Rollback((ICoopContext)(object)active._context, context.Connection.Id, message.PredictionId); } } } private void SendRejectionState(int peer, BillPaymentMessage message) { if (!_shutdown && _context != null && _context.InGame()) { BillsDeltaMessage billsDeltaMessage = BuildDelta(0, message.PredictionId); CoopPlugin.Log.LogInfo((object)("[bills] rejection refresh to conn " + peer + " rent=" + billsDeltaMessage.Rent.AmountToPay + " electric=" + billsDeltaMessage.Electric.AmountToPay + " employee=" + billsDeltaMessage.Employee.AmountToPay + " pred=" + message.PredictionId.ToString() + ".")); _context.Send(peer, (INetMessage)(object)billsDeltaMessage); } } private bool IsPeerMessage(MessageContext context) { if (!_shutdown && context?.Connection != null) { return _fullyJoined.Contains(context.Connection.Id); } return false; } private void OnSceneLoaded(Scene _, LoadSceneMode __) { BillsInterop.Reset(); } internal void Shutdown() { if (!_shutdown) { _shutdown = true; _context?.Messages.UnregisterAttributedHandlers(this); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; if (_active == this) { _active = null; } _fullyJoined.Clear(); BillsInterop.Reset(); _context = null; } } private void OnDestroy() { Shutdown(); } } internal static class BillsInterop { internal readonly struct SpendToken { internal bool IsValid => Reservation != null; internal EconomyAuthority.HostSpendReservation Reservation { get; } internal SpendToken(double amount, EconomyAuthority.HostSpendReservation reservation) { Reservation = reservation; } } private static RentBillScreen _screen; private static readonly MethodInfo MiEvaluateUi = AccessTools.Method(typeof(RentBillScreen), "EvaluateUI", (Type[])null, (Type[])null); private static readonly MethodInfo MiEvaluateNotification = AccessTools.Method(typeof(RentBillScreen), "EvaluateBillNotification", (Type[])null, (Type[])null); internal static void Reset() { _screen = null; } internal static RentBillScreen FindScreen() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_screen != (Object)null) { return _screen; } RentBillScreen[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null) { Scene scene = ((Component)array[i]).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return _screen = array[i]; } } } return null; } internal static void Refresh(RentBillScreen screen) { if (!((Object)(object)screen == (Object)null)) { MiEvaluateUi?.Invoke(screen, null); MiEvaluateNotification?.Invoke(screen, null); } } internal static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } internal static bool TryReservePayment(byte billType, out SpendToken token) { double num = 0.0; if (billType == 0) { BillData bill = CPlayerData.GetBill((EBillType)1); BillData bill2 = CPlayerData.GetBill((EBillType)2); BillData bill3 = CPlayerData.GetBill((EBillType)3); num = (bill?.amountToPay ?? 0f) + (bill2?.amountToPay ?? 0f) + (bill3?.amountToPay ?? 0f); } else { if (!Enum.IsDefined(typeof(EBillType), (int)billType)) { token = new SpendToken(0.0, null); return false; } num = CPlayerData.GetBill((EBillType)billType)?.amountToPay ?? 0f; } if (!IsFinite((float)num) || num <= 0.0 || !EconomyAuthority.TryReserveHostSpend(num, out var reservation)) { token = new SpendToken(num, null); return false; } token = new SpendToken(num, reservation); return true; } internal static bool IsSceneReady() { return (Object)(object)FindScreen() != (Object)null; } internal static bool CommitPayment(SpendToken token, Action payment) { if (!token.IsValid || payment == null) { return false; } return EconomyAuthority.RunWithVanillaSpend(token.Reservation, delegate { payment(); return true; }); } } [NetworkMessage] public sealed class BillsStateMessage : INetMessage { public bool Full = true; public BillValue Rent = new BillValue(); public BillValue Electric = new BillValue(); public BillValue Employee = new BillValue(); } [NetworkMessage] public sealed class BillsDeltaMessage : IPredictedMessage, INetMessage { public byte BillType; public bool All; public BillValue Value = new BillValue(); public BillValue Rent; public BillValue Electric; public BillValue Employee; public Guid PredictionId { get; set; } } [NetworkMessage] public sealed class BillPaymentMessage : IPredictedMessage, INetMessage { public byte BillType; public Guid PredictionId { get; set; } } public sealed class BillValue { public int DayPassed; public float AmountToPay; } [NetworkMessage] public sealed class BillPopupMessage : INetMessage { public int Text; } }