Decompiled source of SprayPaint v0.1.3

BepInEx\plugins\SprayPaint\SprayPaint.dll

Decompiled 2 days ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Enemies;
using GTFO.API;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using LevelGeneration;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SprayPaint
{
	internal static class SprayFileTransfer
	{
		private struct RequestMessage
		{
			internal ulong Sender;

			internal ulong RequestId;

			internal string FileName;

			internal string Hash;
		}

		private struct ChunkMessage
		{
			internal ulong RequestId;

			internal int Total;

			internal int Index;

			internal byte[] Data;

			internal ulong Sender;
		}

		private sealed class Incoming
		{
			internal string FileName;

			internal string Hash;

			internal int Total;

			internal int Received;

			internal byte[][] Chunks;

			internal string SenderName;

			internal ulong Sender;

			internal float StartedAt;

			internal float LastChunkAt;

			internal float LastAckAt;
		}

		private sealed class LocalRequest
		{
			internal string FileName;

			internal string Hash;

			internal string SenderName;

			internal byte[] Payload;

			internal float LastBroadcast;

			internal bool Broadcasted;
		}

		private sealed class Outgoing
		{
			internal SNet_Player Target;

			internal ulong RequestId;

			internal byte[][] Chunks;

			internal int[] Indices;

			internal int NextIndex;

			internal float CompletedAt;
		}

		private struct AckMessage
		{
			internal ulong Sender;

			internal ulong RequestId;

			internal int Total;

			internal byte[] Bitmap;
		}

		private const string RequestEvent = "SprayPaint.FileRequest";

		private const string ChunkEvent = "SprayPaint.FileChunk";

		private const string AckEvent = "SprayPaint.FileAck";

		private const string PolicyEvent = "SprayPaint.TransferPolicy";

		private const int ChunkSize = 4096;

		private const int MaxChunksPerFrame = 8;

		private static readonly ConcurrentQueue<RequestMessage> s_requests = new ConcurrentQueue<RequestMessage>();

		private static readonly ConcurrentQueue<ChunkMessage> s_chunks = new ConcurrentQueue<ChunkMessage>();

		private static readonly ConcurrentQueue<AckMessage> s_acks = new ConcurrentQueue<AckMessage>();

		private static readonly Dictionary<ulong, Incoming> s_incoming = new Dictionary<ulong, Incoming>();

		private static readonly Dictionary<ulong, LocalRequest> s_localRequests = new Dictionary<ulong, LocalRequest>();

		private static readonly Dictionary<ulong, Outgoing> s_completedOutgoing = new Dictionary<ulong, Outgoing>();

		private static readonly Queue<Outgoing> s_outgoing = new Queue<Outgoing>();

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

		private static readonly HashSet<string> s_sprayedImages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly ConcurrentDictionary<string, string> s_missingImages = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, float> s_lastRequestAt = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, int> s_requestAttempts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, string> s_hashCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private static bool s_hashIndexBuilt;

		private static float s_nextRetryAt;

		private static bool s_registered;

		private static bool s_hostAllowTransfer = true;

		private static int s_hostPolicyVersion = -1;

		private static float s_nextPolicyBroadcast;

		internal static int PendingCount => s_incoming.Count + s_outgoing.Count;

		internal static int MissingCount => s_missingImages.Count;

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool CreateHardLink(string newFileName, string existingFileName, IntPtr securityAttributes);

		internal static void ResetSession()
		{
			try
			{
				RequestMessage result;
				while (s_requests.TryDequeue(out result))
				{
				}
				ChunkMessage result2;
				while (s_chunks.TryDequeue(out result2))
				{
				}
				s_incoming.Clear();
				s_outgoing.Clear();
				s_completedOutgoing.Clear();
				s_localRequests.Clear();
				AckMessage result3;
				while (s_acks.TryDequeue(out result3))
				{
				}
				s_requested.Clear();
				s_missingImages.Clear();
				s_sprayedImages.Clear();
				s_lastRequestAt.Clear();
				s_requestAttempts.Clear();
				s_nextRetryAt = 0f;
			}
			catch
			{
			}
		}

		private static string HashIndexPath()
		{
			try
			{
				string text = Path.Combine(SprayPaintPlugin.SprayFolder, ".cache");
				Directory.CreateDirectory(text);
				return Path.Combine(text, "hashindex.tsv");
			}
			catch
			{
				return null;
			}
		}

		private static bool LoadHashIndex()
		{
			try
			{
				string text = HashIndexPath();
				if (string.IsNullOrEmpty(text) || !File.Exists(text))
				{
					return false;
				}
				string[] array = File.ReadAllLines(text);
				foreach (string text2 in array)
				{
					if (string.IsNullOrEmpty(text2))
					{
						continue;
					}
					int num = text2.IndexOf('\t');
					if (num > 0 && num + 1 < text2.Length)
					{
						string text3 = text2.Substring(0, num);
						string text4 = text2.Substring(num + 1);
						if (text3.Length >= 32 && File.Exists(text4))
						{
							s_hashCache[text3] = text4;
						}
					}
				}
				return s_hashCache.Count > 0;
			}
			catch
			{
				return false;
			}
		}

		internal static void SaveHashIndex()
		{
			try
			{
				string text = HashIndexPath();
				if (string.IsNullOrEmpty(text))
				{
					return;
				}
				string text2 = text + ".tmp";
				using (StreamWriter streamWriter = new StreamWriter(text2, append: false, Encoding.UTF8))
				{
					foreach (KeyValuePair<string, string> item in s_hashCache)
					{
						if (!string.IsNullOrEmpty(item.Key) && !string.IsNullOrEmpty(item.Value) && File.Exists(item.Value))
						{
							streamWriter.Write(item.Key);
							streamWriter.Write('\t');
							streamWriter.WriteLine(item.Value);
						}
					}
				}
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				File.Move(text2, text);
			}
			catch
			{
			}
		}

		internal static void BuildHashIndex()
		{
			if (s_hashIndexBuilt)
			{
				return;
			}
			s_hashIndexBuilt = true;
			try
			{
				if (!LoadHashIndex())
				{
					IndexFolder(SprayPaintPlugin.SprayFolder);
					IndexFolder(SprayPaintPlugin.ReceivedFolder);
					SaveHashIndex();
				}
				SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] content hash cache=" + s_hashCache.Count));
			}
			catch
			{
			}
		}

		private static void IndexFolder(string folder)
		{
			try
			{
				if (string.IsNullOrEmpty(folder) || !Directory.Exists(folder))
				{
					return;
				}
				string[] files = Directory.GetFiles(folder);
				for (int i = 0; i < files.Length; i++)
				{
					if (!IsImageFile(files[i]))
					{
						continue;
					}
					try
					{
						string text = HashFile(files[i]);
						if (!string.IsNullOrEmpty(text) && !s_hashCache.ContainsKey(text))
						{
							s_hashCache[text] = files[i];
						}
					}
					catch
					{
					}
				}
			}
			catch
			{
			}
		}

		internal static void RegisterHash(string path)
		{
			try
			{
				if (!string.IsNullOrEmpty(path) && File.Exists(path) && IsImageFile(path))
				{
					string text = HashFile(path);
					if (!string.IsNullOrEmpty(text))
					{
						s_hashCache[text] = path;
						SaveHashIndex();
					}
				}
			}
			catch
			{
			}
		}

		internal static void RegisterHash(string path, string knownHash)
		{
			try
			{
				if (!string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(knownHash))
				{
					s_hashCache[knownHash] = path;
					SaveHashIndex();
				}
			}
			catch
			{
			}
		}

		internal static bool TryResolveHash(string hash, out string path)
		{
			path = null;
			if (string.IsNullOrEmpty(hash))
			{
				return false;
			}
			BuildHashIndex();
			if (s_hashCache.TryGetValue(hash, out path) && !string.IsNullOrEmpty(path) && File.Exists(path))
			{
				return true;
			}
			path = null;
			return false;
		}

		internal static bool MaterializeHash(string hash, string fileName, out string targetPath)
		{
			targetPath = null;
			try
			{
				if (!TryResolveHash(hash, out var path))
				{
					return false;
				}
				string text = SafeFileName(fileName);
				if (string.IsNullOrEmpty(text))
				{
					return false;
				}
				Directory.CreateDirectory(SprayPaintPlugin.ReceivedFolder);
				string text2 = Path.Combine(SprayPaintPlugin.ReceivedFolder, text);
				if (File.Exists(text2))
				{
					try
					{
						if (string.Equals(HashFile(text2), hash, StringComparison.OrdinalIgnoreCase))
						{
							SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] hash cache hit " + text));
							targetPath = text2;
							return true;
						}
					}
					catch
					{
					}
					try
					{
						File.Delete(text2);
					}
					catch
					{
						return false;
					}
				}
				bool flag = false;
				try
				{
					flag = CreateHardLink(text2, path, IntPtr.Zero);
				}
				catch
				{
				}
				if (!flag)
				{
					File.Copy(path, text2, overwrite: true);
				}
				s_hashCache[hash] = text2;
				SaveHashIndex();
				SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] hash cache hit " + text));
				targetPath = text2;
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal static void MarkSprayed(string image)
		{
			try
			{
				string text = SafeFileName(image);
				if (!string.IsNullOrEmpty(text))
				{
					s_sprayedImages.Add(text);
				}
			}
			catch
			{
			}
		}

		internal static void NoteMissing(string image, string hash)
		{
			try
			{
				string text = SafeFileName(image);
				if (!string.IsNullOrEmpty(text) && IsImageFile(text))
				{
					s_missingImages[text] = hash ?? "";
				}
			}
			catch
			{
			}
		}

		internal static void RequestMissing()
		{
			try
			{
				foreach (KeyValuePair<string, string> s_missingImage in s_missingImages)
				{
					if (File.Exists(SprayPaintPlugin.ResolveImagePath(s_missingImage.Key)))
					{
						s_missingImages.TryRemove(s_missingImage.Key, out var _);
						continue;
					}
					s_requestAttempts.Remove(s_missingImage.Key);
					s_lastRequestAt.Remove(s_missingImage.Key);
					s_requested.Remove(s_missingImage.Key);
					Request(s_missingImage.Key, s_missingImage.Value, 0uL);
				}
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] request missing failed: " + ex.Message));
			}
		}

		internal static void RequestCurrent()
		{
			try
			{
				string selectedImage = SprayPaintPlugin.SelectedImage;
				if (!string.IsNullOrEmpty(selectedImage) && !File.Exists(SprayPaintPlugin.ResolveImagePath(SafeFileName(selectedImage))))
				{
					s_requested.Remove(SafeFileName(selectedImage));
					Request(selectedImage, "", 0uL);
				}
			}
			catch
			{
			}
		}

		internal static void TryRegisterNetwork()
		{
			if (s_registered)
			{
				return;
			}
			try
			{
				if (!NetworkAPI.IsEventRegistered("SprayPaint.FileRequest"))
				{
					NetworkAPI.RegisterFreeSizedEvent("SprayPaint.FileRequest", (Action<ulong, byte[]>)OnRequest);
					NetworkAPI.RegisterFreeSizedEvent("SprayPaint.FileChunk", (Action<ulong, byte[]>)OnChunk);
					NetworkAPI.RegisterFreeSizedEvent("SprayPaint.FileAck", (Action<ulong, byte[]>)OnAck);
					NetworkAPI.RegisterFreeSizedEvent("SprayPaint.TransferPolicy", (Action<ulong, byte[]>)OnPolicy);
				}
				s_registered = true;
				SprayPaintPlugin.LogSource.LogInfo((object)"[SprayPaint] file transfer events registered");
			}
			catch
			{
			}
		}

		internal static bool CanTransfer()
		{
			try
			{
				if (!SNet.IsInLobby)
				{
					return SprayPaintPlugin.CfgHostAllowTransfer == null || SprayPaintPlugin.CfgHostAllowTransfer.Value;
				}
				if (SNet.IsMaster)
				{
					return SprayPaintPlugin.CfgHostAllowTransfer == null || SprayPaintPlugin.CfgHostAllowTransfer.Value;
				}
				return s_hostAllowTransfer;
			}
			catch
			{
				return true;
			}
		}

		private static void OnPolicy(ulong senderLookup, byte[] payload)
		{
			try
			{
				if (payload == null || payload.Length < 1)
				{
					return;
				}
				if (!SNet.IsMaster)
				{
					SNet_Player master = SNet.Master;
					SNet_Player val = default(SNet_Player);
					if ((Object)(object)master == (Object)null || !SNet.TryGetPlayer(senderLookup, ref val) || (Object)(object)val != (Object)(object)master)
					{
						return;
					}
				}
				bool flag = payload[0] != 0;
				if (s_hostAllowTransfer != flag)
				{
					s_hostAllowTransfer = flag;
					SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] host transfer policy=" + (flag ? "ON" : "OFF")));
				}
			}
			catch
			{
			}
		}

		private static void UpdateHostPolicy()
		{
			try
			{
				if (SNet.IsInLobby && SNet.IsMaster)
				{
					bool flag = SprayPaintPlugin.CfgHostAllowTransfer == null || SprayPaintPlugin.CfgHostAllowTransfer.Value;
					int num = (flag ? 1 : 0);
					if (num != s_hostPolicyVersion || !(Time.unscaledTime < s_nextPolicyBroadcast))
					{
						s_nextPolicyBroadcast = Time.unscaledTime + 2f;
						s_hostPolicyVersion = num;
						byte[] array = new byte[1] { flag ? ((byte)1) : ((byte)0) };
						NetworkAPI.InvokeFreeSizedEvent("SprayPaint.TransferPolicy", array, (SNet_ChannelType)2);
					}
				}
			}
			catch
			{
			}
		}

		private static void OnRequest(ulong senderLookup, byte[] payload)
		{
			try
			{
				if (!CanTransfer() || payload == null)
				{
					return;
				}
				if (!SprayPaintPlugin.CfgServeFiles.Value)
				{
					SprayPaintPlugin.LogSource.LogInfo((object)"[SprayPaint] ServeFiles=false, ignored request");
					return;
				}
				using MemoryStream input = new MemoryStream(payload);
				using BinaryReader binaryReader = new BinaryReader(input, Encoding.UTF8);
				ulong requestId = binaryReader.ReadUInt64();
				string fileName = binaryReader.ReadString();
				string hash = binaryReader.ReadString();
				s_requests.Enqueue(new RequestMessage
				{
					Sender = senderLookup,
					RequestId = requestId,
					FileName = fileName,
					Hash = hash
				});
			}
			catch
			{
			}
		}

		private static void OnChunk(ulong senderLookup, byte[] payload)
		{
			try
			{
				if (!CanTransfer() || payload == null)
				{
					return;
				}
				using MemoryStream input = new MemoryStream(payload);
				using BinaryReader binaryReader = new BinaryReader(input, Encoding.UTF8);
				ulong requestId = binaryReader.ReadUInt64();
				int num = binaryReader.ReadInt32();
				int num2 = binaryReader.ReadInt32();
				int num3 = binaryReader.ReadInt32();
				if (num3 >= 0 && num3 <= 4096 && num2 >= 0 && num > 0 && num <= 8192 && binaryReader.BaseStream.Position + num3 <= binaryReader.BaseStream.Length)
				{
					byte[] data = binaryReader.ReadBytes(num3);
					s_chunks.Enqueue(new ChunkMessage
					{
						RequestId = requestId,
						Total = num,
						Index = num2,
						Data = data,
						Sender = senderLookup
					});
				}
			}
			catch
			{
			}
		}

		private static void OnAck(ulong senderLookup, byte[] payload)
		{
			try
			{
				if (!CanTransfer() || payload == null)
				{
					return;
				}
				using MemoryStream memoryStream = new MemoryStream(payload);
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
				ulong requestId = binaryReader.ReadUInt64();
				int num = binaryReader.ReadInt32();
				int num2 = binaryReader.ReadInt32();
				if (num > 0 && num <= 8192 && num2 > 0 && num2 <= 1024 && memoryStream.Position + num2 <= memoryStream.Length)
				{
					s_acks.Enqueue(new AckMessage
					{
						Sender = senderLookup,
						RequestId = requestId,
						Total = num,
						Bitmap = binaryReader.ReadBytes(num2)
					});
				}
			}
			catch
			{
			}
		}

		internal static void Request(string fileName, string hash, ulong senderLookup)
		{
			try
			{
				if (!CanTransfer() || !s_registered)
				{
					return;
				}
				fileName = SafeFileName(fileName);
				if (string.IsNullOrEmpty(fileName) || !IsImageFile(fileName) || !string.IsNullOrEmpty(SprayPaintPlugin.ResolveImagePath(fileName)) || s_requested.Contains(fileName))
				{
					return;
				}
				s_requested.Add(fileName);
				s_lastRequestAt[fileName] = Time.unscaledTime;
				s_requestAttempts.TryGetValue(fileName, out var value);
				s_requestAttempts[fileName] = value + 1;
				using MemoryStream memoryStream = new MemoryStream();
				using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
				ulong num = (ulong)(DateTime.UtcNow.Ticks ^ Random.Range(1, int.MaxValue));
				string text = "";
				try
				{
					SNet_Player val = default(SNet_Player);
					if (SNet.TryGetPlayer(senderLookup, ref val) && (Object)(object)val != (Object)null)
					{
						text = val.NickName;
					}
				}
				catch
				{
				}
				if (string.IsNullOrEmpty(text))
				{
					text = senderLookup.ToString();
				}
				binaryWriter.Write(num);
				binaryWriter.Write(fileName);
				binaryWriter.Write(hash ?? "");
				byte[] array = memoryStream.ToArray();
				bool flag = false;
				SNet_Player val2 = FindPlayer(senderLookup);
				if ((Object)(object)val2 != (Object)null)
				{
					try
					{
						NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileRequest", array, val2, (SNet_ChannelType)2);
						flag = true;
					}
					catch
					{
						flag = false;
					}
				}
				s_localRequests[num] = new LocalRequest
				{
					FileName = fileName,
					Hash = (hash ?? ""),
					SenderName = text,
					Payload = array,
					LastBroadcast = Time.unscaledTime,
					Broadcasted = !flag
				};
				if (!flag)
				{
					NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileRequest", array, (SNet_ChannelType)2);
				}
				SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] requested image=" + fileName + " from=" + senderLookup + " targeted=" + flag));
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] request failed: " + ex.Message));
			}
		}

		internal static string ProgressText()
		{
			try
			{
				foreach (KeyValuePair<ulong, Incoming> item in s_incoming)
				{
					Incoming value = item.Value;
					if (value != null && value.Total > 0)
					{
						int num = Mathf.Clamp(Mathf.RoundToInt((float)value.Received * 100f / (float)value.Total), 0, 100);
						return value.FileName + " " + num + "%";
					}
				}
			}
			catch
			{
			}
			return "";
		}

		private static void RetryMissing()
		{
			if (Time.unscaledTime < s_nextRetryAt)
			{
				return;
			}
			s_nextRetryAt = Time.unscaledTime + 3f;
			try
			{
				foreach (KeyValuePair<string, string> s_missingImage in s_missingImages)
				{
					string key = s_missingImage.Key;
					if (!string.IsNullOrEmpty(key) && !File.Exists(SprayPaintPlugin.ResolveImagePath(key)) && (!s_requestAttempts.TryGetValue(key, out var value) || value < 3) && (!s_lastRequestAt.TryGetValue(key, out var value2) || !(Time.unscaledTime - value2 < 3f)))
					{
						s_requested.Remove(key);
						Request(key, s_missingImage.Value, 0uL);
						SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] retry request=" + key));
					}
				}
			}
			catch
			{
			}
		}

		private static void CleanupTimedOutIncoming()
		{
			if (s_incoming.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			List<ulong> list = null;
			foreach (KeyValuePair<ulong, Incoming> item in s_incoming)
			{
				Incoming value = item.Value;
				if (value == null || unscaledTime - value.StartedAt > 120f)
				{
					if (list == null)
					{
						list = new List<ulong>();
					}
					list.Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				ulong key = list[i];
				if (!s_incoming.TryGetValue(key, out var value2))
				{
					continue;
				}
				s_incoming.Remove(key);
				s_localRequests.Remove(key);
				if (value2 != null)
				{
					s_requested.Remove(value2.FileName);
					s_lastRequestAt.Remove(value2.FileName);
					s_requestAttempts.Remove(value2.FileName);
					try
					{
						string path = Path.Combine(SprayPaintPlugin.SprayFolder, value2.FileName + ".part");
						if (File.Exists(path))
						{
							File.Delete(path);
						}
					}
					catch
					{
					}
				}
				SprayPaintPlugin.LogSource.LogWarning((object)"[SprayPaint] transfer timed out");
			}
		}

		private static void RebroadcastPending()
		{
			if (s_localRequests.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			foreach (KeyValuePair<ulong, LocalRequest> s_localRequest in s_localRequests)
			{
				LocalRequest value = s_localRequest.Value;
				if (value != null && !value.Broadcasted && value.Payload != null && !s_incoming.ContainsKey(s_localRequest.Key) && !(unscaledTime - value.LastBroadcast < 2f))
				{
					try
					{
						NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileRequest", value.Payload, (SNet_ChannelType)2);
						value.Broadcasted = true;
						value.LastBroadcast = unscaledTime;
						SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] fallback broadcast request=" + value.FileName));
					}
					catch
					{
					}
				}
			}
		}

		private static void SendMissingAcks()
		{
			if (s_incoming.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			foreach (KeyValuePair<ulong, Incoming> item in s_incoming)
			{
				Incoming value = item.Value;
				if (value == null || value.Total <= 0 || value.Received >= value.Total || unscaledTime - value.LastChunkAt < 1.5f || unscaledTime - value.LastAckAt < 1.5f)
				{
					continue;
				}
				try
				{
					byte[] array = new byte[(value.Total + 7) / 8];
					for (int i = 0; i < value.Total; i++)
					{
						if (i < value.Chunks.Length && value.Chunks[i] != null)
						{
							array[i >> 3] |= (byte)(1 << (i & 7));
						}
					}
					using (MemoryStream memoryStream = new MemoryStream())
					{
						using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
						binaryWriter.Write(item.Key);
						binaryWriter.Write(value.Total);
						binaryWriter.Write(array.Length);
						binaryWriter.Write(array);
						byte[] array2 = memoryStream.ToArray();
						SNet_Player val = FindPlayer(value.Sender);
						if ((Object)(object)val != (Object)null)
						{
							NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileAck", array2, val, (SNet_ChannelType)2);
						}
						else
						{
							NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileAck", array2, (SNet_ChannelType)2);
						}
					}
					value.LastAckAt = unscaledTime;
				}
				catch
				{
				}
			}
		}

		private static void HandleAck(AckMessage ack)
		{
			try
			{
				if (!s_completedOutgoing.TryGetValue(ack.RequestId, out var value) || value == null || ack.Total != value.Chunks.Length || ack.Bitmap == null)
				{
					return;
				}
				List<int> list = new List<int>();
				for (int i = 0; i < value.Chunks.Length; i++)
				{
					if (i / 8 >= ack.Bitmap.Length || (ack.Bitmap[i / 8] & (1 << (i & 7))) == 0)
					{
						list.Add(i);
					}
				}
				if (list.Count == 0)
				{
					s_completedOutgoing.Remove(ack.RequestId);
					return;
				}
				s_outgoing.Enqueue(new Outgoing
				{
					Target = FindPlayer(ack.Sender),
					RequestId = ack.RequestId,
					Chunks = value.Chunks,
					Indices = list.ToArray(),
					NextIndex = 0
				});
				SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] resend missing chunks=" + list.Count + " request=" + ack.RequestId));
			}
			catch
			{
			}
		}

		private static void CleanupCompletedOutgoing()
		{
			if (s_completedOutgoing.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			List<ulong> list = null;
			foreach (KeyValuePair<ulong, Outgoing> item in s_completedOutgoing)
			{
				if (item.Value == null || unscaledTime - item.Value.CompletedAt > 45f)
				{
					if (list == null)
					{
						list = new List<ulong>();
					}
					list.Add(item.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					s_completedOutgoing.Remove(list[i]);
				}
			}
		}

		internal static void Update()
		{
			try
			{
				UpdateHostPolicy();
				if (!CanTransfer())
				{
					s_outgoing.Clear();
					s_incoming.Clear();
					s_localRequests.Clear();
					s_requested.Clear();
					return;
				}
				RequestMessage result;
				while (s_requests.TryDequeue(out result))
				{
					HandleRequest(result);
				}
				int i = 0;
				for (int num = ((!SprayPaintPlugin.CfgRemoteThrottle.Value) ? int.MaxValue : ((SprayPaintPlugin.CfgIncomingChunksPerFrame.Value <= 0) ? int.MaxValue : SprayPaintPlugin.CfgIncomingChunksPerFrame.Value)); i < num; i++)
				{
					if (!s_chunks.TryDequeue(out var result2))
					{
						break;
					}
					HandleChunk(result2);
				}
				AckMessage result3;
				while (s_acks.TryDequeue(out result3))
				{
					HandleAck(result3);
				}
				SendMissingAcks();
				CleanupTimedOutIncoming();
				CleanupCompletedOutgoing();
				RebroadcastPending();
				int num2 = 0;
				while (num2 < 8 && s_outgoing.Count > 0)
				{
					Outgoing outgoing = s_outgoing.Peek();
					int num3 = ((outgoing.Indices != null) ? outgoing.Indices.Length : outgoing.Chunks.Length);
					if (outgoing.NextIndex >= num3)
					{
						s_outgoing.Dequeue();
						outgoing.CompletedAt = Time.unscaledTime;
						s_completedOutgoing[outgoing.RequestId] = outgoing;
						continue;
					}
					int num4 = ((outgoing.Indices != null && outgoing.NextIndex < outgoing.Indices.Length) ? outgoing.Indices[outgoing.NextIndex] : outgoing.NextIndex);
					byte[] array = BuildChunkPayload(outgoing.RequestId, outgoing.Chunks.Length, num4, outgoing.Chunks[num4]);
					if ((Object)(object)outgoing.Target != (Object)null)
					{
						NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileChunk", array, outgoing.Target, (SNet_ChannelType)2);
					}
					else
					{
						NetworkAPI.InvokeFreeSizedEvent("SprayPaint.FileChunk", array, (SNet_ChannelType)2);
					}
					outgoing.NextIndex++;
					num2++;
				}
				RetryMissing();
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] file transfer update failed: " + ex.Message));
			}
		}

		private static byte[] GetTransferBytes(string path, string fileName)
		{
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Expected O, but got Unknown
			if (SprayPaintPlugin.CfgCompressTransfer == null || !SprayPaintPlugin.CfgCompressTransfer.Value)
			{
				return File.ReadAllBytes(path);
			}
			string text = fileName.ToLowerInvariant();
			bool flag = text.EndsWith(".gif");
			if (!text.EndsWith(".png") && !text.EndsWith(".jpg") && !text.EndsWith(".jpeg") && (!flag || !SprayPaintPlugin.CfgCompressGifTransfer.Value))
			{
				return File.ReadAllBytes(path);
			}
			try
			{
				int max = Mathf.Clamp(SprayPaintPlugin.CfgTransferMaxTextureSize.Value, 128, 4096);
				string text2 = Path.Combine(SprayPaintPlugin.SprayFolder, ".sendcache");
				Directory.CreateDirectory(text2);
				bool flag2 = !flag && text.EndsWith(".png") && SprayPaintPlugin.CfgTransferPngAsJpeg.Value;
				int num = Mathf.Clamp(SprayPaintPlugin.CfgTransferJpegQuality.Value, 1, 100);
				string text3 = Path.Combine(text2, fileName + "." + max + "." + num + (flag ? ".static" : (flag2 ? ".jpg" : ".bin")));
				if (File.Exists(text3) && File.GetLastWriteTimeUtc(text3) >= File.GetLastWriteTimeUtc(path))
				{
					return File.ReadAllBytes(text3);
				}
				byte[] array = File.ReadAllBytes(path);
				Texture2D val;
				if (flag)
				{
					val = GifDecoder.GetPreview(path);
					if ((Object)(object)val == (Object)null)
					{
						return array;
					}
				}
				else
				{
					val = new Texture2D(2, 2, (TextureFormat)4, false, false);
					if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array)))
					{
						Object.Destroy((Object)(object)val);
						return array;
					}
				}
				Texture2D val2 = SprayDecalFactory.ResizeTexture(val, max);
				byte[] array2 = null;
				if (text.EndsWith(".jpg") || text.EndsWith(".jpeg") || flag2)
				{
					int num2 = Mathf.Clamp(SprayPaintPlugin.CfgTransferJpegQuality.Value, 1, 100);
					array2 = Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)ImageConversion.EncodeToJPG(val2, num2));
				}
				else
				{
					array2 = Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)ImageConversion.EncodeToPNG(val2));
				}
				if ((Object)(object)val2 != (Object)(object)val)
				{
					Object.Destroy((Object)(object)val2);
				}
				if (!flag)
				{
					Object.Destroy((Object)(object)val);
				}
				if (array2 == null || array2.Length == 0)
				{
					return array;
				}
				File.WriteAllBytes(text3, array2);
				try
				{
					int num3 = ((SprayPaintPlugin.CfgTransferCacheMaxMB != null) ? SprayPaintPlugin.CfgTransferCacheMaxMB.Value : 0);
					if (num3 > 0)
					{
						FileInfo[] files = new DirectoryInfo(text2).GetFiles();
						long num4 = 0L;
						FileInfo[] array3 = files;
						foreach (FileInfo fileInfo in array3)
						{
							num4 += fileInfo.Length;
						}
						if (num4 > (long)num3 * 1024L * 1024)
						{
							Array.Sort(files, (FileInfo a, FileInfo b) => a.LastWriteTimeUtc.CompareTo(b.LastWriteTimeUtc));
							array3 = files;
							foreach (FileInfo fileInfo2 in array3)
							{
								if (num4 <= (long)num3 * 1024L * 1024)
								{
									break;
								}
								if (!(fileInfo2.FullName == text3))
								{
									num4 -= fileInfo2.Length;
									try
									{
										fileInfo2.Delete();
									}
									catch
									{
									}
								}
							}
						}
					}
				}
				catch
				{
				}
				SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] compressed transfer " + fileName + " " + array.Length + " -> " + array2.Length + " bytes"));
				return array2;
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] transfer compress failed: " + ex.Message));
				return File.ReadAllBytes(path);
			}
		}

		private static void HandleRequest(RequestMessage request)
		{
			try
			{
				string text = SafeFileName(request.FileName);
				if (string.IsNullOrEmpty(text) || !IsImageFile(text))
				{
					return;
				}
				string path = SprayPaintPlugin.ResolveImagePath(text);
				if (!File.Exists(path))
				{
					return;
				}
				if (SprayPaintPlugin.CfgDownloadOnlySprayed.Value && !s_sprayedImages.Contains(text))
				{
					SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] refused unsprayed image request: " + text));
					return;
				}
				string a = HashFile(path);
				if (!string.IsNullOrEmpty(request.Hash) && !string.Equals(a, request.Hash, StringComparison.OrdinalIgnoreCase))
				{
					SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] image hash mismatch: " + text));
					return;
				}
				byte[] transferBytes = GetTransferBytes(path, text);
				int num = Mathf.Max(1, SprayPaintPlugin.CfgMaxDownloadMB.Value) * 1024 * 1024;
				if (transferBytes.Length > num)
				{
					return;
				}
				int num2 = (transferBytes.Length + 4096 - 1) / 4096;
				if (num2 > 0 && num2 <= 8192)
				{
					byte[][] array = new byte[num2][];
					for (int i = 0; i < num2; i++)
					{
						int num3 = i * 4096;
						int num4 = Math.Min(4096, transferBytes.Length - num3);
						array[i] = new byte[num4];
						Buffer.BlockCopy(transferBytes, num3, array[i], 0, num4);
					}
					s_outgoing.Enqueue(new Outgoing
					{
						Target = FindPlayer(request.Sender),
						RequestId = request.RequestId,
						Chunks = array,
						NextIndex = 0
					});
					SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] sending image=" + text + " chunks=" + num2 + " to=" + request.Sender));
				}
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] handle request failed: " + ex.Message));
			}
		}

		private static bool TryGetImageSize(byte[] data, out int w, out int h)
		{
			w = 0;
			h = 0;
			try
			{
				if (data == null || data.Length < 10)
				{
					return false;
				}
				if (data[0] == 137 && data[1] == 80 && data[2] == 78 && data[3] == 71)
				{
					w = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19];
					h = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23];
					return w > 0 && h > 0;
				}
				if (data[0] == 71 && data[1] == 73 && data[2] == 70)
				{
					w = data[6] | (data[7] << 8);
					h = data[8] | (data[9] << 8);
					return w > 0 && h > 0;
				}
				if (data[0] == 66 && data[1] == 77 && data.Length >= 26)
				{
					w = data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24);
					h = data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24);
					if (h < 0)
					{
						h = -h;
					}
					return w > 0 && h > 0;
				}
				if (data[0] == byte.MaxValue && data[1] == 216)
				{
					int num = 2;
					while (num + 9 < data.Length)
					{
						if (data[num] != byte.MaxValue)
						{
							num++;
							continue;
						}
						int num2 = data[num + 1];
						switch (num2)
						{
						case 1:
						case 208:
						case 209:
						case 210:
						case 211:
						case 212:
						case 213:
						case 214:
						case 215:
						case 216:
							num += 2;
							continue;
						default:
						{
							int num3 = (data[num + 2] << 8) | data[num + 3];
							if (num3 >= 2 && num + 2 + num3 <= data.Length)
							{
								if ((num2 >= 192 && num2 <= 195) || (num2 >= 197 && num2 <= 199) || (num2 >= 201 && num2 <= 203) || (num2 >= 205 && num2 <= 207))
								{
									h = (data[num + 5] << 8) | data[num + 6];
									w = (data[num + 7] << 8) | data[num + 8];
									return w > 0 && h > 0;
								}
								num += 2 + num3;
								continue;
							}
							break;
						}
						}
						break;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private static void HandleChunk(ChunkMessage chunk)
		{
			//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c7: Expected O, but got Unknown
			//IL_03ce: Expected O, but got Unknown
			try
			{
				if (!s_incoming.TryGetValue(chunk.RequestId, out var value))
				{
					if (!s_localRequests.TryGetValue(chunk.RequestId, out var value2) || chunk.Total <= 0 || chunk.Total > 8192 || chunk.Index < 0 || chunk.Index >= chunk.Total)
					{
						return;
					}
					value = new Incoming
					{
						FileName = value2.FileName,
						Hash = value2.Hash,
						Total = chunk.Total,
						Chunks = new byte[chunk.Total][],
						SenderName = value2.SenderName,
						Sender = chunk.Sender,
						StartedAt = Time.unscaledTime
					};
					s_incoming[chunk.RequestId] = value;
				}
				if (value.Total != chunk.Total || value.Sender != chunk.Sender || chunk.Index < 0 || chunk.Index >= value.Total || value.Chunks[chunk.Index] != null)
				{
					return;
				}
				value.Chunks[chunk.Index] = chunk.Data;
				value.Received++;
				value.LastChunkAt = Time.unscaledTime;
				if (value.Received % 25 == 0 || value.Received == value.Total)
				{
					SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] receiving image=" + value.FileName + " chunks=" + value.Received + "/" + value.Total));
				}
				if (value.Received < value.Total)
				{
					return;
				}
				using MemoryStream memoryStream = new MemoryStream();
				for (int i = 0; i < value.Total; i++)
				{
					byte[] array = value.Chunks[i];
					if (array == null)
					{
						s_incoming.Remove(chunk.RequestId);
						return;
					}
					memoryStream.Write(array, 0, array.Length);
				}
				byte[] array2 = memoryStream.ToArray();
				int num = Mathf.Max(1, SprayPaintPlugin.CfgMaxDownloadMB.Value) * 1024 * 1024;
				if (array2.Length > num || array2.Length == 0)
				{
					s_incoming.Remove(chunk.RequestId);
					s_localRequests.Remove(chunk.RequestId);
					return;
				}
				int num2 = ((SprayPaintPlugin.CfgMaxReceiveImageDimension == null) ? 4096 : SprayPaintPlugin.CfgMaxReceiveImageDimension.Value);
				if (num2 > 0 && TryGetImageSize(array2, out var w, out var h) && (w > num2 || h > num2))
				{
					s_incoming.Remove(chunk.RequestId);
					s_localRequests.Remove(chunk.RequestId);
					s_requested.Remove(value.FileName);
					SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] received image too large: " + value.FileName + " " + w + "x" + h + " limit=" + num2));
					return;
				}
				string text = Path.Combine(SprayPaintPlugin.SprayFolder, value.FileName + ".part");
				File.WriteAllBytes(text, array2);
				string text2 = HashFile(text);
				bool flag = SprayPaintPlugin.CfgCompressTransfer != null && SprayPaintPlugin.CfgCompressTransfer.Value;
				if (!string.IsNullOrEmpty(value.Hash) && !string.Equals(text2, value.Hash, StringComparison.OrdinalIgnoreCase))
				{
					bool flag2 = false;
					if (flag)
					{
						try
						{
							Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false, false);
							flag2 = ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array2));
							Object.Destroy((Object)val);
						}
						catch
						{
							flag2 = false;
						}
					}
					if (!flag2)
					{
						try
						{
							File.Delete(text);
						}
						catch
						{
						}
						s_incoming.Remove(chunk.RequestId);
						s_localRequests.Remove(chunk.RequestId);
						s_requested.Remove(value.FileName);
						SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] download hash mismatch, rejected: " + value.FileName));
						return;
					}
					SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] downloaded hash differs (compressed transfer), image validated: " + value.FileName));
				}
				Directory.CreateDirectory(SprayPaintPlugin.ReceivedFolder);
				string text3 = Path.Combine(SprayPaintPlugin.ReceivedFolder, value.FileName);
				if (File.Exists(text3))
				{
					File.Delete(text3);
				}
				File.Move(text, text3);
				RegisterHash(text3, string.IsNullOrEmpty(value.Hash) ? text2 : value.Hash);
				try
				{
					File.WriteAllText(Path.Combine(SprayPaintPlugin.ReceivedFolder, value.FileName + ".source.txt"), string.IsNullOrEmpty(value.SenderName) ? "unknown" : value.SenderName);
				}
				catch
				{
				}
				s_incoming.Remove(chunk.RequestId);
				s_localRequests.Remove(chunk.RequestId);
				s_requested.Remove(value.FileName);
				s_missingImages.TryRemove(value.FileName, out var _);
				s_lastRequestAt.Remove(value.FileName);
				s_requestAttempts.Remove(value.FileName);
				SprayPaintPlugin.LogSource.LogInfo((object)("[SprayPaint] downloaded image=" + value.FileName + " bytes=" + array2.Length));
				if (SprayPaintPlugin.CfgPrewarmDownloaded.Value)
				{
					SprayPaintState.PrewarmQueue.Enqueue(value.FileName);
				}
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] handle chunk failed: " + ex.Message));
			}
		}

		private static byte[] BuildChunkPayload(ulong requestId, int total, int index, byte[] data)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
			binaryWriter.Write(requestId);
			binaryWriter.Write(total);
			binaryWriter.Write(index);
			binaryWriter.Write(data.Length);
			binaryWriter.Write(data);
			return memoryStream.ToArray();
		}

		internal static string HashFile(string path)
		{
			using SHA256 sHA = SHA256.Create();
			using FileStream inputStream = File.OpenRead(path);
			byte[] array = sHA.ComputeHash(inputStream);
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			for (int i = 0; i < array.Length; i++)
			{
				stringBuilder.Append(array[i].ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		internal static bool IsImageFile(string name)
		{
			string text = Path.GetExtension(name ?? "").ToLowerInvariant();
			switch (text)
			{
			default:
				return text == ".tga";
			case ".gif":
			case ".png":
			case ".jpg":
			case ".jpeg":
			case ".jfif":
			case ".bmp":
				return true;
			}
		}

		internal static string SafeFileName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			try
			{
				string fileName = Path.GetFileName(name.Trim());
				if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
				{
					return "";
				}
				return fileName;
			}
			catch
			{
				return "";
			}
		}

		private static SNet_Player FindPlayer(ulong lookup)
		{
			try
			{
				if (PlayerManager.PlayerAgentsInLevel == null)
				{
					return null;
				}
				for (int i = 0; i < PlayerManager.PlayerAgentsInLevel.Count; i++)
				{
					PlayerAgent val = PlayerManager.PlayerAgentsInLevel[i];
					if ((Object)(object)val != (Object)null && (Object)(object)val.Owner != (Object)null && val.Owner.Lookup == lookup)
					{
						return val.Owner;
					}
				}
			}
			catch
			{
			}
			return null;
		}
	}
	internal sealed class GifFrame
	{
		internal Texture2D Texture;

		internal float Delay;

		internal byte[] Rgba;

		internal int Width;

		internal int Height;
	}
	internal static class GifDecoder
	{
		private sealed class RawJob
		{
			internal Task<GifFrame[]> Task;

			internal int Index;

			internal readonly List<GifFrame> Built = new List<GifFrame>();
		}

		private static readonly Dictionary<string, GifFrame[]> s_cache = new Dictionary<string, GifFrame[]>();

		private static readonly Dictionary<string, Texture2D> s_previewCache = new Dictionary<string, Texture2D>();

		private static readonly Dictionary<string, RawJob> s_rawJobs = new Dictionary<string, RawJob>();

		internal static void ClearCache()
		{
			s_cache.Clear();
			s_previewCache.Clear();
			s_rawJobs.Clear();
		}

		private static string CachePath(string path)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(path);
				string s = path + "|" + fileInfo.Length + "|" + fileInfo.LastWriteTimeUtc.Ticks;
				byte[] array = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(s));
				StringBuilder stringBuilder = new StringBuilder();
				for (int i = 0; i < 8; i++)
				{
					stringBuilder.Append(array[i].ToString("x2"));
				}
				string text = Path.Combine(SprayPaintPlugin.SprayFolder, ".cache");
				Directory.CreateDirectory(text);
				return Path.Combine(text, stringBuilder.ToString() + ".gifcache");
			}
			catch
			{
				return null;
			}
		}

		private static GifFrame[] ReadCache(string cachePath)
		{
			try
			{
				if (!File.Exists(cachePath))
				{
					return null;
				}
				using FileStream input = File.OpenRead(cachePath);
				using BinaryReader binaryReader = new BinaryReader(input);
				if (binaryReader.ReadString() != "SPGFC1")
				{
					return null;
				}
				int num = binaryReader.ReadInt32();
				if (num <= 0 || num > 4096)
				{
					return null;
				}
				GifFrame[] array = new GifFrame[num];
				for (int i = 0; i < num; i++)
				{
					int num2 = binaryReader.ReadInt32();
					int num3 = binaryReader.ReadInt32();
					float delay = binaryReader.ReadSingle();
					int num4 = binaryReader.ReadInt32();
					if (num2 <= 0 || num3 <= 0 || num2 > 2048 || num3 > 2048 || num4 != num2 * num3 * 4)
					{
						return null;
					}
					GifFrame gifFrame = new GifFrame();
					gifFrame.Width = num2;
					gifFrame.Height = num3;
					gifFrame.Delay = delay;
					gifFrame.Rgba = binaryReader.ReadBytes(num4);
					array[i] = gifFrame;
				}
				return array;
			}
			catch
			{
				return null;
			}
		}

		private static void WriteCache(string cachePath, GifFrame[] frames)
		{
			try
			{
				if (string.IsNullOrEmpty(cachePath) || frames == null || frames.Length == 0)
				{
					return;
				}
				string text = cachePath + ".tmp";
				using (FileStream output = File.Create(text))
				{
					using BinaryWriter binaryWriter = new BinaryWriter(output);
					binaryWriter.Write("SPGFC1");
					binaryWriter.Write(frames.Length);
					foreach (GifFrame gifFrame in frames)
					{
						binaryWriter.Write(gifFrame.Width);
						binaryWriter.Write(gifFrame.Height);
						binaryWriter.Write(gifFrame.Delay);
						binaryWriter.Write((gifFrame.Rgba != null) ? gifFrame.Rgba.Length : 0);
						if (gifFrame.Rgba != null)
						{
							binaryWriter.Write(gifFrame.Rgba);
						}
					}
				}
				if (File.Exists(cachePath))
				{
					File.Delete(cachePath);
				}
				File.Move(text, cachePath);
				try
				{
					int num = ((SprayPaintPlugin.CfgGifDiskCacheMaxMB != null) ? SprayPaintPlugin.CfgGifDiskCacheMaxMB.Value : 0);
					if (num <= 0)
					{
						return;
					}
					FileInfo[] files = new DirectoryInfo(Path.GetDirectoryName(cachePath)).GetFiles("*.gifcache");
					long num2 = 0L;
					FileInfo[] array = files;
					foreach (FileInfo fileInfo in array)
					{
						num2 += fileInfo.Length;
					}
					if (num2 <= (long)num * 1024L * 1024)
					{
						return;
					}
					Array.Sort(files, (FileInfo a, FileInfo b) => a.LastWriteTimeUtc.CompareTo(b.LastWriteTimeUtc));
					array = files;
					foreach (FileInfo fileInfo2 in array)
					{
						if (num2 <= (long)num * 1024L * 1024)
						{
							break;
						}
						if (!(fileInfo2.FullName == cachePath))
						{
							num2 -= fileInfo2.Length;
							try
							{
								fileInfo2.Delete();
							}
							catch
							{
							}
						}
					}
				}
				catch
				{
				}
			}
			catch
			{
			}
		}

		internal static void BeginAsync(string path)
		{
			try
			{
				if (string.IsNullOrEmpty(path) || !File.Exists(path) || s_cache.ContainsKey(path) || s_rawJobs.ContainsKey(path))
				{
					return;
				}
				byte[] data = File.ReadAllBytes(path);
				bool useCache = true;
				try
				{
					useCache = SprayPaintPlugin.CfgGifDiskCache == null || SprayPaintPlugin.CfgGifDiskCache.Value;
				}
				catch
				{
				}
				string cachePath = (useCache ? CachePath(path) : null);
				RawJob rawJob = new RawJob();
				rawJob.Task = Task.Factory.StartNew(delegate
				{
					try
					{
						Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
					}
					catch
					{
					}
					if (useCache && cachePath != null)
					{
						GifFrame[] array = ReadCache(cachePath);
						if (array != null)
						{
							return array;
						}
					}
					GifFrame[] array2 = Decode(data, int.MaxValue, createTextures: false);
					if (useCache && cachePath != null)
					{
						WriteCache(cachePath, array2);
					}
					return array2;
				}, TaskCreationOptions.LongRunning);
				s_rawJobs[path] = rawJob;
			}
			catch (Exception ex)
			{
				SprayPaintPlugin.LogSource.LogWarning((object)("[SprayPaint] gif async start failed: " + ex.Message));
			}
		}

		internal static void ProcessUploads(int budget)
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Expected O, but got Unknown
			if (s_rawJobs.Count == 0 || budget <= 0)
			{
				return;
			}
			List<string> list = null;
			foreach (KeyValuePair<string, RawJob> s_rawJob in s_rawJobs)
			{
				RawJob value = s_rawJob.Value;
				if (value.Task == null || !value.Task.IsCompleted)
				{
					continue;
				}
				GifFrame[] array = null;
				try
				{
					array = value.Task.Result;
				}
				catch
				{
				}
				if (array == null || array.Length == 0)
				{
					if (list == null)
					{
						list = new List<string>();
					}
					list.Add(s_rawJob.Key);
					continue;
				}
				int num = 1;
				int num2 = 0;
				try
				{
					if (SprayPaintPlugin.CfgGifFrameSkip != null)
					{
						num = Mathf.Clamp(SprayPaintPlugin.CfgGifFrameSkip.Value, 1, 8);
					}
					if (SprayPaintPlugin.CfgGifMaxTextureSize != null)
					{
						num2 = Mathf.Max(0, SprayPaintPlugin.CfgGifMaxTextureSize.Value);
					}
				}
				catch
				{
				}
				while (budget > 0 && value.Index < array.Length)
				{
					GifFrame gifFrame = array[value.Index];
					Texture2D val = new Texture2D(gifFrame.Width, gifFrame.Height, (TextureFormat)4, false);
					((Object)val).name = "SprayGifFrame_" + value.Index;
					((Texture)val).wrapMode = (TextureWrapMode)1;
					((Texture)val).filterMode = (FilterMode)1;
					val.SetPixels32(Il2CppStructArray<Color32>.op_Implicit(ToColors32(gifFrame.Rgba, gifFrame.Width, gifFrame.Height)));
					val.Apply(false, true);
					if (num2 > 0 && (((Texture)val).width > num2 || ((Texture)val).height > num2))
					{
						Texture2D val2 = SprayDecalFactory.ResizeTexture(val, num2);
						if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)val)
						{
							Object.Destroy((Object)(object)val);
							val = val2;
						}
					}
					GifFrame gifFrame2 = new GifFrame();
					gifFrame2.Texture = val;
					gifFrame2.Delay = Mathf.Clamp(gifFrame.Delay * (float)num, 0.02f, 2f);
					value.Built.Add(gifFrame2);
					value.Index += num;
					budget--;
				}
				if (value.Index >= array.Length)
				{
					s_cache[s_rawJob.Key] = value.Built.ToArray();
					if (list == null)
					{
						list = new List<string>();
					}
					list.Add(s_rawJob.Key);
				}
			}
			if (list != null)
			{
				for (int i = 0; i < list.Count; i++)
				{
					s_rawJobs.Remove(list[i]);
				}
			}
		}

		internal static bool TryGetPreview(string path, out Texture2D texture)
		{
			texture = null;
			if (s_previewCache.TryGetValue(path, out texture))
			{
				return (Object)(object)texture != (Object)null;
			}
			return false;
		}

		internal static Texture2D GetPreview(string path)
		{
			if (s_previewCache.TryGetValue(path, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			try
			{
				GifFrame[] array = Decode(File.ReadAllBytes(path), 1, createTextures: true);
				if (array == null || array.Length == 0)
				{
					return null;
				}
				value = array[0].Texture;
				s_previewCache[path] = value;
				return value;
			}
			catch
			{
				return null;
			}
		}

		internal static GifFrame[] GetFrames(string path)
		{
			if (s_cache.TryGetValue(path, out var value))
			{
				return value;
			}
			BeginAsync(path);
			return null;
		}

		private static GifFrame[] Decode(byte[] data, int maxFrames, bool createTextures)
		{
			//IL_0394: Unknown result type (might be due to invalid IL or missing references)
			//IL_039b: Expected O, but got Unknown
			if (data == null || data.Length < 13)
			{
				return null;
			}
			string text = Encoding.ASCII.GetString(data, 0, 6);
			if (text != "GIF87a" && text != "GIF89a")
			{
				return null;
			}
			int num = data[6] | (data[7] << 8);
			int num2 = data[8] | (data[9] << 8);
			if (num <= 0 || num2 <= 0 || num > 1024 || num2 > 1024)
			{
				return null;
			}
			int num3 = data[10];
			_ = data[11];
			int pos = 13;
			byte[] array = null;
			if ((num3 & 0x80) != 0)
			{
				int size = 1 << (num3 & 7) + 1;
				array = ReadColorTable(data, ref pos, size);
			}
			byte[] array2 = new byte[num * num2 * 4];
			List<GifFrame> list = new List<GifFrame>();
			int[] array3 = new int[4];
			bool flag = false;
			while (pos < data.Length)
			{
				switch (data[pos++])
				{
				case 33:
					if (pos >= data.Length)
					{
						break;
					}
					if (data[pos++] == 249)
					{
						int num19 = data[pos++];
						if (num19 < 4 || pos + num19 > data.Length)
						{
							if (list.Count <= 0)
							{
								return null;
							}
							return list.ToArray();
						}
						int num20 = data[pos];
						array3[0] = (num20 >> 2) & 7;
						array3[1] = data[pos + 1] | (data[pos + 2] << 8);
						int num21 = data[pos + 3];
						array3[2] = num21;
						array3[3] = (((num20 & 1) != 0) ? 1 : 0);
						pos += num19;
						flag = true;
						SkipSubBlocks(data, ref pos);
					}
					else
					{
						SkipSubBlocks(data, ref pos);
					}
					continue;
				case 44:
				{
					if (pos + 9 > data.Length)
					{
						break;
					}
					int num4 = data[pos] | (data[pos + 1] << 8);
					int num5 = data[pos + 2] | (data[pos + 3] << 8);
					int num6 = data[pos + 4] | (data[pos + 5] << 8);
					int num7 = data[pos + 6] | (data[pos + 7] << 8);
					int num8 = data[pos + 8];
					pos += 9;
					if (num6 <= 0 || num7 <= 0 || num6 > num || num7 > num2)
					{
						break;
					}
					byte[] array4 = array;
					if ((num8 & 0x80) != 0)
					{
						int size2 = 1 << (num8 & 7) + 1;
						array4 = ReadColorTable(data, ref pos, size2);
					}
					if (array4 == null)
					{
						break;
					}
					int minCodeSize = data[pos++];
					byte[] array5 = DecodeLzw(ReadSubBlocks(data, ref pos), minCodeSize, num6 * num7);
					if (array5 == null)
					{
						break;
					}
					if ((num8 & 0x40) != 0)
					{
						array5 = Deinterlace(array5, num6, num7);
					}
					byte[] array6 = null;
					int num9 = (flag ? array3[0] : 0);
					if (num9 == 3)
					{
						array6 = (byte[])array2.Clone();
					}
					for (int i = 0; i < num7; i++)
					{
						int num10 = num5 + i;
						if (num10 < 0 || num10 >= num2)
						{
							continue;
						}
						for (int j = 0; j < num6; j++)
						{
							int num11 = num4 + j;
							if (num11 < 0 || num11 >= num)
							{
								continue;
							}
							int num12 = array5[i * num6 + j];
							if (!flag || array3[3] != 1 || num12 != array3[2])
							{
								int num13 = num12 * 3;
								if (num13 + 2 < array4.Length)
								{
									int num14 = (num10 * num + num11) * 4;
									array2[num14] = array4[num13];
									array2[num14 + 1] = array4[num13 + 1];
									array2[num14 + 2] = array4[num13 + 2];
									array2[num14 + 3] = byte.MaxValue;
								}
							}
						}
					}
					int num15 = (flag ? array3[1] : 10);
					GifFrame gifFrame = new GifFrame();
					gifFrame.Delay = Mathf.Clamp((float)num15 / 100f, 0.02f, 2f);
					if (createTextures)
					{
						Texture2D val = new Texture2D(num, num2, (TextureFormat)4, false);
						((Object)val).name = "SprayGifFrame_" + list.Count;
						((Texture)val).wrapMode = (TextureWrapMode)1;
						((Texture)val).filterMode = (FilterMode)1;
						val.SetPixels32(Il2CppStructArray<Color32>.op_Implicit(ToColors32(array2, num, num2)));
						val.Apply(false, true);
						gifFrame.Texture = val;
					}
					else
					{
						gifFrame.Rgba = (byte[])array2.Clone();
						gifFrame.Width = num;
						gifFrame.Height = num2;
					}
					list.Add(gifFrame);
					if (maxFrames > 0 && list.Count >= maxFrames)
					{
						return list.ToArray();
					}
					switch (num9)
					{
					case 2:
					{
						for (int k = 0; k < num7; k++)
						{
							int num16 = num5 + k;
							if (num16 < 0 || num16 >= num2)
							{
								continue;
							}
							for (int l = 0; l < num6; l++)
							{
								int num17 = num4 + l;
								if (num17 >= 0 && num17 < num)
								{
									int num18 = (num16 * num + num17) * 4;
									array2[num18] = 0;
									array2[num18 + 1] = 0;
									array2[num18 + 2] = 0;
									array2[num18 + 3] = 0;
								}
							}
						}
						break;
					}
					case 3:
						if (array6 != null)
						{
							Buffer.BlockCopy(array6, 0, array2, 0, array2.Length);
						}
						break;
					}
					flag = false;
					continue;
				}
				}
				break;
			}
			if (list.Count <= 0)
			{
				return null;
			}
			return list.ToArray();
		}

		private static byte[] ReadColorTable(byte[] data, ref int pos, int size)
		{
			int num = size * 3;
			if (pos + num > data.Length)
			{
				return null;
			}
			byte[] array = new byte[num];
			Buffer.BlockCopy(data, pos, array, 0, num);
			pos += num;
			return array;
		}

		private static void SkipSubBlocks(byte[] data, ref int pos)
		{
			while (pos < data.Length)
			{
				int num = data[pos++];
				if (num != 0)
				{
					pos += num;
					continue;
				}
				break;
			}
		}

		private static byte[] ReadSubBlocks(byte[] data, ref int pos)
		{
			using MemoryStream memoryStream = new MemoryStream();
			while (pos < data.Length)
			{
				int num = data[pos++];
				if (num == 0 || pos + num > data.Length)
				{
					break;
				}
				memoryStream.Write(data, pos, num);
				pos += num;
			}
			return memoryStream.ToArray();
		}

		private static Color32[] ToColors32(byte[] rgba, int width, int height)
		{
			//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)
			Color32[] array = (Color32[])(object)new Color32[width * height];
			float brightness = 1f;
			float gamma = 1f;
			try
			{
				if (SprayPaintPlugin.CfgImageBrightness != null)
				{
					brightness = Mathf.Clamp(SprayPaintPlugin.CfgImageBrightness.Value, 0.05f, 1f);
				}
				if (SprayPaintPlugin.CfgImageGamma != null)
				{
					gamma = Mathf.Clamp(SprayPaintPlugin.CfgImageGamma.Value, 0.1f, 5f);
				}
			}
			catch
			{
			}
			for (int i = 0; i < height; i++)
			{
				int num = i * width;
				int num2 = (height - 1 - i) * width;
				for (int j = 0; j < width; j++)
				{
					int num3 = (num + j) * 4;
					int num4 = num2 + j;
					array[num4] = new Color32(Correct(rgba[num3], brightness, gamma), Correct(rgba[num3 + 1], brightness, gamma), Correct(rgba[num3 + 2], brightness, gamma), rgba[num3 + 3]);
				}
			}
			return array;
		}

		private static byte Correct(byte value, float brightness, float gamma)
		{
			return (byte)Mathf.Clamp(Mathf.Pow((float)(int)value / 255f * brightness, gamma) * 255f, 0f, 255f);
		}

		private static byte[] Deinterlace(byte[] src, int width, int height)
		{
			byte[] array = new byte[src.Length];
			int[] array2 = new int[4] { 0, 4, 2, 1 };
			int[] array3 = new int[4] { 8, 8, 4, 2 };
			int num = 0;
			for (int i = 0; i < 4; i++)
			{
				for (int j = array2[i]; j < height; j += array3[i])
				{
					Buffer.BlockCopy(src, num * width, array, j * width, width);
					num++;
				}
			}
			return array;
		}

		private static byte[] DecodeLzw(byte[] data, int minCodeSize, int pixelCount)
		{
			if (minCodeSize < 2 || minCodeSize > 11 || data == null || data.Length == 0)
			{
				return null;
			}
			int num = 1 << minCodeSize;
			int num2 = num + 1;
			int num3 = minCodeSize + 1;
			int num4 = num2 + 1;
			List<byte[]> list = new List<byte[]>(4096);
			for (int i = 0; i < num; i++)
			{
				list.Add(new byte[1] { (byte)i });
			}
			list.Add(null);
			list.Add(null);
			byte[] array = new byte[pixelCount];
			int num5 = 0;
			int num6 = 0;
			int num7 = data.Length * 8;
			byte[] array2 = null;
			while (num5 < pixelCount && num6 + num3 <= num7)
			{
				int num8 = 0;
				for (int j = 0; j < num3; j++)
				{
					int num9 = (data[num6 + j >> 3] >> ((num6 + j) & 7)) & 1;
					num8 |= num9 << j;
				}
				num6 += num3;
				if (num8 == num)
				{
					num3 = minCodeSize + 1;
					num4 = num2 + 1;
					list.Clear();
					for (int k = 0; k < num; k++)
					{
						list.Add(new byte[1] { (byte)k });
					}
					list.Add(null);
					list.Add(null);
					array2 = null;
					continue;
				}
				if (num8 == num2)
				{
					break;
				}
				byte[] array3;
				if (num8 < list.Count && list[num8] != null)
				{
					array3 = list[num8];
				}
				else
				{
					if (num8 != num4 || array2 == null)
					{
						break;
					}
					array3 = new byte[array2.Length + 1];
					Buffer.BlockCopy(array2, 0, array3, 0, array2.Length);
					array3[array2.Length] = array2[0];
				}
				for (int l = 0; l < array3.Length; l++)
				{
					if (num5 >= pixelCount)
					{
						break;
					}
					array[num5++] = array3[l];
				}
				if (array2 != null && num4 < 4096)
				{
					byte[] array4 = new byte[array2.Length + 1];
					Buffer.BlockCopy(array2, 0, array4, 0, array2.Length);
					array4[array2.Length] = array3[0];
					list.Add(array4);
					num4++;
					if (num4 == 1 << num3 && num3 < 12)
					{
						num3++;
					}
				}
				array2 = array3;
			}
			if (num5 != pixelCount)
			{
				return null;
			}
			return array;
		}
	}
	[BepInPlugin("SprayPaint", "Spray Paint", "0.1.3")]
	public class SprayPaintPlugin : BasePlugin
	{
		internal static ManualLogSource LogSource;

		internal const string ModTag = "[SprayPaint]";

		internal const string NetworkEvent = "SprayPaint.Place";

		internal static ConfigEntry<bool> CfgEnabled;

		internal static ConfigEntry<string> CfgKey;

		internal static ConfigEntry<string> CfgColor;

		internal static ConfigEntry<float> CfgSize;

		internal static ConfigEntry<float> CfgDistance;

		internal static ConfigEntry<int> CfgMaxDecals;

		internal static ConfigEntry<bool> CfgNetwork;

		internal static ConfigEntry<float> CfgInterval;

		internal static ConfigEntry<float> CfgOffset;

		internal static ConfigEntry<bool> CfgOrientToView;

		internal static ConfigEntry<bool> CfgCompressTransfer;

		internal static ConfigEntry<int> CfgTransferMaxTextureSize;

		internal static ConfigEntry<int> CfgTransferJpegQuality;

		internal static ConfigEntry<bool> CfgRemoteThrottle;

		internal static ConfigEntry<bool> CfgCompressGifTransfer;

		internal static ConfigEntry<bool> CfgPreloadDuringLoad;

		internal static ConfigEntry<bool> CfgShowReceivedInMainGallery;

		internal static ConfigEntry<int> CfgPreloadAllLobbyCount;

		internal static ConfigEntry<bool> CfgTransferPngAsJpeg;

		internal static ConfigEntry<int> CfgIncomingChunksPerFrame;

		internal static ConfigEntry<int> CfgRemoteSpawnPerFrame;

		internal static ConfigEntry<int> CfgRemotePlaceholderPerFrame;

		internal static ConfigEntry<int> CfgRemoteRespawnPerFrame;

		internal static ConfigEntry<bool> CfgPrewarmDownloaded;

		internal static ConfigEntry<bool> CfgAllowTriggers;

		internal static ConfigEntry<bool> CfgIgnoreInvisibleColliders;

		internal static ConfigEntry<bool> CfgRemoveMovingSurface;

		internal static ConfigEntry<int> CfgShape;

		internal static ConfigEntry<string> CfgExtraColors;

		internal static ConfigEntry<string> CfgFavoriteColors;

		internal static ConfigEntry<string> CfgRecentColors;

		internal static ConfigEntry<string> CfgFullPreset1;

		internal static ConfigEntry<string> CfgFullPreset2;

		internal static ConfigEntry<string> CfgFullPreset3;

		internal static ConfigEntry<string> CfgPurePreset1;

		internal static ConfigEntry<string> CfgPurePreset2;

		internal static ConfigEntry<string> CfgPurePreset3;

		internal static ConfigEntry<string> CfgFullPreset4;

		internal static ConfigEntry<string> CfgFullPreset5;

		internal static ConfigEntry<string> CfgFullPreset6;

		internal static ConfigEntry<string> CfgPurePreset4;

		internal static ConfigEntry<string> CfgPurePreset5;

		internal static ConfigEntry<string> CfgPurePreset6;

		internal static ConfigEntry<bool> CfgCullFarDecals;

		internal static ConfigEntry<int> CfgPreviewCacheLimit;

		internal static ConfigEntry<int> CfgMaterialCacheLimit;

		internal static ConfigEntry<int> CfgTextureCacheLimit;

		internal static ConfigEntry<bool> CfgDoubleSided;

		internal static ConfigEntry<bool> CfgExperimentalDxtCache;

		internal static ConfigEntry<bool> CfgGroundSingleSided;

		internal static ConfigEntry<float> CfgPlayerSprayCooldown;

		internal static ConfigEntry<bool> CfgSpraySound;

		internal static ConfigEntry<int> CfgMaxReceiveImageDimension;

		internal static ConfigEntry<float> CfgRemoteSpraySize;

		internal static ConfigEntry<bool> CfgHostAllowTransfer;

		internal static ConfigEntry<bool> CfgDistanceScale;

		internal static ConfigEntry<float> CfgDistanceScaleStart;

		internal static ConfigEntry<float> CfgDistanceScaleEnd;

		internal static ConfigEntry<float> CfgDistanceScaleMin;

		internal static ConfigEntry<float> CfgCullDistance;

		internal static ConfigEntry<bool> CfgDebugLog;

		internal static ConfigEntry<string> CfgImageFile;

		internal static ConfigEntry<bool> CfgAllowTerminal;

		internal static ConfigEntry<bool> CfgRandomRotation;

		internal static ConfigEntry<float> CfgImageBrightness;

		internal static ConfigEntry<float> CfgImageGamma;

		internal static ConfigEntry<float> CfgOpacity;

		internal static ConfigEntry<float> CfgLifetime;

		internal static ConfigEntry<bool> CfgKeepAspect;

		internal static ConfigEntry<bool> CfgAutoDownload;

		internal static ConfigEntry<int> CfgMaxDownloadMB;

		internal static ConfigEntry<bool> CfgDownloadOnlySprayed;

		internal static ConfigEntry<float> CfgDownloadWaitSeconds;

		internal static ConfigEntry<bool> CfgShowMissingPlaceholder;

		internal static ConfigEntry<bool> CfgFlipStaticImages;

		internal static ConfigEntry<bool> CfgServeFiles;

		internal static ConfigEntry<bool> CfgEnableMenu;

		internal static ConfigEntry<string> CfgMenuKey;

		internal static ConfigEntry<float> CfgMenuOpacity;

		internal static ConfigEntry<string> CfgClearKey;

		internal static ConfigEntry<string> CfgUndoKey;

		internal static ConfigEntry<string> CfgRequestKey;

		internal static ConfigEntry<float> CfgMenuX;

		internal static ConfigEntry<float> CfgMenuY;

		internal static ConfigEntry<bool> CfgAvoidOverlap;

		internal static ConfigEntry<int> CfgMaxTextureSize;

		internal static ConfigEntry<bool> CfgGifDiskCache;

		internal static ConfigEntry<int> CfgGifDiskCacheMaxMB;

		internal static ConfigEntry<int> CfgTransferCacheMaxMB;

		internal static ConfigEntry<bool> CfgStaticTextureCache;

		internal static ConfigEntry<int> CfgStaticTextureCacheMaxMB;

		internal static ConfigEntry<int> CfgGifMaxTextureSize;

		internal static ConfigEntry<int> CfgGifFrameSkip;

		internal static ConfigEntry<float> CfgOverlapDistance;

		internal static ConfigEntry<float> CfgOverlapSizeRatio;

		internal static ConfigEntry<float> CfgOverlapNormalDot;

		internal static ConfigEntry<float> CfgOverlapEdgeRatio;

		internal static ConfigEntry<float> CfgMinSize;

		internal static ConfigEntry<float> CfgMaxSize;

		internal static ConfigEntry<float> CfgSizeStep;

		internal static ConfigEntry<string> CfgLanguage;

		internal static ConfigEntry<string> CfgBrowserRoot;

		internal static ConfigFile ConfigFile;

		internal static bool NetworkRegistered;

		private static string s_sprayFolder;

		internal static string SprayFolder
		{
			get
			{
				if (!string.IsNullOrEmpty(s_sprayFolder))
				{
					return s_sprayFolder;
				}
				return Path.Combine(AssemblyDirectory(), "Sprays");
			}
		}

		internal static string ReceivedFolder => Path.Combine(SprayFolder, "Received");

		internal static string SelectedImage => ((CfgImageFile != null && CfgImageFile.Value != null) ? Path.GetFileName(CfgImageFile.Value.Trim()) : "") ?? "";

		internal static string SelectedImageHash
		{
			get
			{
				string selectedImage = SelectedImage;
				if (string.IsNullOrEmpty(selectedImage))
				{
					return "";
				}
				try
				{
					string path = ResolveImagePath(selectedImage);
					return File.Exists(path) ? SprayFileTransfer.HashFile(path) : "";
				}
				catch
				{
					return "";
				}
			}
		}

		public override void Load()
		{
			//IL_0de2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0de7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0def: Unknown result type (might be due to invalid IL or missing references)
			//IL_0df5: Expected O, but got Unknown
			//IL_0df5: Unknown result type (might be due to invalid IL or missing references)
			LogSource = ((BasePlugin)this).Log;
			ConfigFile = ((BasePlugin)this).Config;
			CfgEnabled = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable spray paint.");
			CfgKey = ((BasePlugin)this).Config.Bind<string>("General", "Key", "H", "Hold this key to spray.");
			CfgColor = ((BasePlugin)this).Config.Bind<string>("Paint", "Color", "#FF8A00", "HTML color, e.g. #FF8A00.");
			CfgSize = ((BasePlugin)this).Config.Bind<float>("Paint", "Size", 0.33f, "Paint quad size in meters.");
			CfgDistance = ((BasePlugin)this).Config.Bind<float>("Paint", "Distance", 4f, "Maximum spray distance in meters.");
			CfgMaxDecals = ((BasePlugin)this).Config.Bind<int>("Paint", "MaxDecals", 50, "Maximum paint marks kept locally.");
			CfgInterval = ((BasePlugin)this).Config.Bind<float>("Paint", "Interval", 0.3f, "Seconds between marks while holding the key.");
			CfgOffset = ((BasePlugin)this).Config.Bind<float>("Paint", "Offset", 0.02f, "Offset from the hit surface to avoid z-fighting.");
			CfgOrientToView = ((BasePlugin)this).Config.Bind<bool>("Paint", "OrientToView", true, "Orient the image to the player's view on floors/ceilings.");
			CfgCompressTransfer = ((BasePlugin)this).Config.Bind<bool>("Network", "CompressTransfer", true, "Downscale PNG/JPG before sending to teammates.");
			CfgTransferMaxTextureSize = ((BasePlugin)this).Config.Bind<int>("Network", "TransferMaxTextureSize", 1024, "Maximum PNG/JPG dimension when sending to teammates.");
			CfgTransferJpegQuality = ((BasePlugin)this).Config.Bind<int>("Network", "TransferJpegQuality", 85, "JPEG quality for compressed transfers (1-100).");
			CfgRemoteThrottle = ((BasePlugin)this).Config.Bind<bool>("Network", "RemoteThrottle", true, "Enable remote spray/download frame throttling.");
			CfgCompressGifTransfer = ((BasePlugin)this).Config.Bind<bool>("Network", "CompressGifTransfer", false, "Send only the first GIF frame (static) to reduce download/decode spikes.");
			CfgPreloadDuringLoad = ((BasePlugin)this).Config.Bind<bool>("Paint", "PreloadDuringLoading", true, "Preload local images during level generation/elevator loading.");
			CfgShowReceivedInMainGallery = ((BasePlugin)this).Config.Bind<bool>("Paint", "ShowReceivedInMainGallery", false, "Also show received images in the main gallery list; false keeps them only under Received.");
			CfgPreloadAllLobbyCount = ((BasePlugin)this).Config.Bind<int>("Paint", "PreloadAllLobbyCount", 0, "Max images to preload in lobby; 0 = all.");
			CfgTransferPngAsJpeg = ((BasePlugin)this).Config.Bind<bool>("Network", "TransferPngAsJpeg", false, "Encode PNG transfers as JPEG to reduce size (loses transparency).");
			CfgIncomingChunksPerFrame = ((BasePlugin)this).Config.Bind<int>("Network", "IncomingChunksPerFrame", 32, "Max downloaded file chunks processed per frame; 0 = unlimited.");
			CfgRemoteSpawnPerFrame = ((BasePlugin)this).Config.Bind<int>("Network", "RemoteSpawnPerFrame", 2, "Max remote sprays created per frame.");
			CfgRemotePlaceholderPerFrame = ((BasePlugin)this).Config.Bind<int>("Network", "RemotePlaceholderPerFrame", 2, "Max missing-image placeholders created per frame.");
			CfgRemoteRespawnPerFrame = ((BasePlugin)this).Config.Bind<int>("Network", "RemoteRespawnPerFrame", 2, "Max waiting sprays completed per frame.");
			CfgPrewarmDownloaded = ((BasePlugin)this).Config.Bind<bool>("Network", "PrewarmDownloaded", true, "Prewarm downloaded images over several frames.");
			CfgAllowTriggers = ((BasePlugin)this).Config.Bind<bool>("Paint", "AllowTriggers", false, "Allow painting trigger colliders.");
			CfgIgnoreInvisibleColliders = ((BasePlugin)this).Config.Bind<bool>("Paint", "IgnoreInvisibleColliders", true, "Skip colliders without a visible MeshRenderer (scan points/invisible blockers).");
			CfgRemoveMovingSurface = ((BasePlugin)this).Config.Bind<bool>("Paint", "RemoveMovingSurface", true, "Remove a spray when its source surface (door/lift) moves or disappears.");
			CfgShape = ((BasePlugin)this).Config.Bind<int>("Paint", "Shape", 0, "Solid color shape: 0=Rectangle, 1=Circle, 2=Triangle, 3=Diamond, 4=Star, 5=Arrow, 6=Cross.");
			CfgExtraColors = ((BasePlugin)this).Config.Bind<string>("Paint", "ExtraColors", "", "Extra solid colors for the palette, comma separated hex like #112233,#FF8800.");
			CfgFavoriteColors = ((BasePlugin)this).Config.Bind<string>("Paint", "FavoriteColors", "#30FF60,#FF8A00", "Favorite solid colors, comma separated hex.");
			CfgRecentColors = ((BasePlugin)this).Config.Bind<string>("Paint", "RecentColors", "#30FF60,#FF8A00,#FFFFFF,#FF3030,#B060FF,#3070FF,#30FF61", "Recently used solid colors, comma separated hex.");
			CfgFullPreset1 = ((BasePlugin)this).Config.Bind<string>("Presets", "Full1", "|#30FF60|1|1.05|1|0.35|1|0", "Full spray preset slot 1.");
			CfgFullPreset2 = ((BasePlugin)this).Config.Bind<string>("Presets", "Full2", "B495485E57D47BA66148A593D301D90E.png|#30FF60|1|1.05|1|0.35|1|30", "Full spray preset slot 2.");
			CfgFullPreset3 = ((BasePlugin)this).Config.Bind<string>("Presets", "Full3", "", "Full spray preset slot 3.");
			CfgPurePreset1 = ((BasePlugin)this).Config.Bind<string>("Presets", "Pure1", "", "Solid color preset slot 1.");
			CfgPurePreset2 = ((BasePlugin)this).Config.Bind<string>("Presets", "Pure2", "", "Solid color preset slot 2.");
			CfgPurePreset3 = ((BasePlugin)this).Config.Bind<string>("Presets", "Pure3", "", "Solid color preset slot 3.");
			CfgFullPreset4 = ((BasePlugin)this).Config.Bind<string>("Presets", "Full4", "", "Full spray preset slot 4.");
			CfgFullPreset5 = ((BasePlugin)this).Config.Bind<string>("Presets", "Full5", "", "Full spray preset slot 5.");
			CfgFullPreset6 = ((BasePlugin)this).Config.Bind<string>("Presets", "Full6", "", "Full spray preset slot 6.");
			CfgPurePreset4 = ((BasePlugin)this).Config.Bind<string>("Presets", "Pure4", "", "Solid color preset slot 4.");
			CfgPurePreset5 = ((BasePlugin)this).Config.Bind<string>("Presets", "Pure5", "", "Solid color preset slot 5.");
			CfgPurePreset6 = ((BasePlugin)this).Config.Bind<string>("Presets", "Pure6", "", "Solid color preset slot 6.");
			CfgCullFarDecals = ((BasePlugin)this).Config.Bind<bool>("Paint", "CullFarDecals", true, "Disable renderers for spray marks farther than CullDistance.");
			CfgPreviewCacheLimit = ((BasePlugin)this).Config.Bind<int>("Paint", "PreviewCacheLimit", 64, "Maximum cached preview/thumbnail textures.");
			CfgMaterialCacheLimit = ((BasePlugin)this).Config.Bind<int>("Paint", "MaterialCacheLimit", 64, "Maximum cached spray materials; unused ones are evicted.");
			CfgTextureCacheLimit = ((BasePlugin)this).Config.Bind<int>("Paint", "TextureCacheLimit", 64, "Maximum cached full spray textures; unused ones are evicted.");
			CfgDoubleSided = ((BasePlugin)this).Config.Bind<bool>("Paint", "DoubleSided", true, "Render a back face for each spray. Off halves draw calls but the back side becomes invisible.");
			CfgExperimentalDxtCache = ((BasePlugin)this).Config.Bind<bool>("Paint", "ExperimentalDxtCache", false, "EXPERIMENTAL: cache static PNG/JPG as DXT compressed textures. May fail or look blurry; off by default.");
			CfgGroundSingleSided = ((BasePlugin)this).Config.Bind<bool>("Paint", "GroundSingleSided", true, "Render floor sprays as one-sided to save fill rate; other surfaces stay double-sided.");
			CfgPlayerSprayCooldown = ((BasePlugin)this).Config.Bind<float>("Network", "PlayerSprayCooldown", 0.25f, "Minimum seconds between sprays from the same remote player; 0 = off.");
			CfgSpraySound = ((BasePlugin)this).Config.Bind<bool>("UI", "SpraySound", true, "Play a short spray sound when placing a local spray.");
			CfgMaxReceiveImageDimension = ((BasePlugin)this).Config.Bind<int>("Network", "MaxReceiveImageDimension", 4096, "Reject received images whose decoded width or height exceeds this; 0 = unlimited.");
			CfgRemoteSpraySize = ((BasePlugin)this).Config.Bind<float>("Network", "RemoteSpraySize", 0f, "Force received sprays to this size in meters; 0 = use sender size.");
			CfgHostAllowTransfer = ((BasePlugin)this).Config.Bind<bool>("Network", "HostAllowTransfer", true, "Host-only permission: when the host disables this, all clients stop transferring and downloading.");
			CfgDistanceScale = ((BasePlugin)this).Config.Bind<bool>("Paint", "DistanceScale", true, "Shrink distant sprays instead of only culling them.");
			CfgDistanceScaleStart = ((BasePlugin)this).Config.Bind<float>("Paint", "DistanceScaleStart", 8f, "Distance where distance scaling starts.");
			CfgDistanceScaleEnd = ((BasePlugin)this).Config.Bind<float>("Paint", "DistanceScaleEnd", 30f, "Distance where distance scaling reaches its minimum.");
			CfgDistanceScaleMin = ((BasePlugin)this).Config.Bind<float>("Paint", "DistanceScaleMin", 0.5f, "Smallest scale multiplier at DistanceScaleEnd.");
			CfgCullDistance = ((BasePlugin)this).Config.Bind<float>("Paint", "CullDistance", 30f, "Distance in meters for spray culling.");
			CfgDebugLog = ((BasePlugin)this).Config.Bind<bool>("Paint", "DebugLog", false, "Log raycast hits and misses.");
			CfgImageFile = ((BasePlugin)this).Config.Bind<string>("Paint", "ImageFile", "default.gif", "PNG/JPG/GIF in the Sprays folder; empty = solid color.");
			CfgAllowTerminal = ((BasePlugin)this).Config.Bind<bool>("Paint", "AllowTerminal", false, "Allow painting computer terminals.");
			CfgRandomRotation = ((BasePlugin)this).Config.Bind<bool>("Paint", "RandomRotation", false, "Randomly rotate each paint mark around the surface normal.");
			CfgImageBrightness = ((BasePlugin)this).Config.Bind<float>("Paint", "ImageBrightness", 0.35f, "Brightness multiplier for image/GIF sprays (rendering workaround).");
			CfgImageGamma = ((BasePlugin)this).Config.Bind<float>("Paint", "ImageGamma", 1f, "Gamma correction for image/GIF sprays (0.5-2.5).");
			CfgOpacity = ((BasePlugin)this).Config.Bind<float>("Paint", "Opacity", 1f, "Spray opacity 0.05-1.0.");
			CfgLifetime = ((BasePlugin)this).Config.Bind<float>("Paint", "Lifetime", 0f, "Seconds before a spray disappears; 0 = permanent.");
			CfgKeepAspect = ((BasePlugin)this).Config.Bind<bool>("Paint", "KeepAspect", true, "Keep image aspect ratio.");
			CfgAutoDownload = ((BasePlugin)this).Config.Bind<bool>("Network", "AutoDownload", false, "Download missing spray images from the sender when possible.");
			CfgMaxDownloadMB = ((BasePlugin)this).Config.Bind<int>("Network", "MaxDownloadMB", 12, "Maximum auto-download size in MB.");
			CfgDownloadOnlySprayed = ((BasePlugin)this).Config.Bind<bool>("Network", "DownloadOnlySprayed", true, "Only transfer images that were actually sprayed, never the whole Sprays folder.");
			CfgDownloadWaitSeconds = ((BasePlugin)this).Config.Bind<float>("Network", "DownloadWaitSeconds", 120f, "Wait before discarding a spray whose image is still downloading; values below 120 are treated as 120.");
			CfgShowMissingPlaceholder = ((BasePlugin)this).Config.Bind<bool>("UI", "ShowMissingPlaceholder", true, "Show a magenta checker marker when a spray image is missing.");
			CfgFlipStaticImages = ((BasePlugin)this).Config.Bind<bool>("Paint", "FlipStaticImages", true, "Flip PNG/JPG textures horizontally. GIFs are not affected.");
			CfgServeFiles = ((BasePlugin)this).Config.Bind<bool>("Network", "ServeFiles", false, "Respond to other players' image file requests.");
			CfgEnableMenu = ((BasePlugin)this).Config.Bind<bool>("UI", "EnableMenu", true, "Enable the in-game spray menu.");
			CfgMenuKey = ((BasePlugin)this).Config.Bind<string>("UI", "MenuKey", "F7", "Open/close the spray menu.");
			CfgMenuOpacity = ((BasePlugin)this).Config.Bind<float>("UI", "MenuOpacity", 0f, "Menu background opacity, 0.0-1.0.");
			CfgClearKey = ((BasePlugin)this).Config.Bind<string>("Keys", "ClearKey", "F10", "Hotkey to clear local paint.");
			CfgUndoKey = ((BasePlugin)this).Config.Bind<string>("Keys", "UndoKey", "F9", "Hotkey to undo the last paint.");
			CfgRequestKey = ((BasePlugin)this).Config.Bind<string>("Keys", "RequestKey", "F8", "Hotkey to request missing images.");
			CfgMenuX = ((BasePlugin)this).Config.Bind<float>("UI", "MenuX", 20f, "Menu panel X position.");
			CfgMenuY = ((BasePlugin)this).Config.Bind<float>("UI", "MenuY", 60f, "Menu panel Y position.");
			CfgAvoidOverlap = ((BasePlugin)this).Config.Bind<bool>("Paint", "AvoidOverlap", true, "Skip a spray if another mark is almost at the same position, to prevent z-fighting.");
			CfgOverlapDistance = ((BasePlugin)this).Config.Bind<float>("Paint", "OverlapDistance", 0.05f, "Minimum world distance between two spray marks.");
			CfgOverlapSizeRatio = ((BasePlugin)this).Config.Bind<float>("Paint", "OverlapSizeRatio", 0.1f, "Additional overlap distance as a fraction of spray size.");
			CfgOverlapNormalDot = ((BasePlugin)this).Config.Bind<float>("Paint", "OverlapNormalDot", 0.98f, "Only treat marks as overlapping if their surface normals are this similar.");
			CfgOverlapEdgeRatio = ((BasePlugin)this).Config.Bind<float>("Paint", "OverlapEdgeRatio", 0.25f, "Skip if quads overlap by this fraction of their combined size.");
			CfgMaxTextureSize = ((BasePlugin)this).Config.Bind<int>("Paint", "MaxTextureSize", 2048, "Downscale PNG/JPG to this maximum dimension; 0 = unlimited.");
			CfgGifDiskCache = ((BasePlugin)this).Config.Bind<bool>("Paint", "GifDiskCache", true, "Cache decoded GIF frames under Sprays/.cache for faster later loads.");
			CfgGifDiskCacheMaxMB = ((BasePlugin)this).Config.Bind<int>("Paint", "GifDiskCacheMaxMB", 512, "Delete oldest GIF cache files above this total size; 0 = unlimited.");
			CfgTransferCacheMaxMB = ((BasePlugin)this).Config.Bind<int>("Network", "TransferCacheMaxMB", 256, "Delete oldest .sendcache files above this total size; 0 = unlimited.");
			CfgStaticTextureCache = ((BasePlugin)this).Config.Bind<bool>("Paint", "StaticTextureCache", true, "Cache resized PNG/JPG RGBA in Sprays/.texturecache.");
			CfgStaticTextureCacheMaxMB = ((BasePlugin)this).Config.Bind<int>("Paint", "StaticTextureCacheMaxMB", 512, "Delete oldest .texturecache files above this total size; 0 = unlimited.");
			CfgGifMaxTextureSize = ((BasePlugin)this).Config.Bind<int>("Paint", "GifMaxTextureSize", 512, "Downscale GIF frames to this maximum dimension; 0 = unlimited.");
			CfgGifFrameSkip = ((BasePlugin)this).Config.Bind<int>("Paint", "GifFrameSkip", 1, "Use every Nth GIF frame; 1 = all frames, 2 = half frames.");
			CfgMinSize = ((BasePlugin)this).Config.Bind<float>("Paint", "MinSize", 0.05f, "Minimum spray size in meters.");
			CfgMaxSize = ((BasePlugin)this).Config.Bind<float>("Paint", "MaxSize", 5f, "Maximum spray size in meters.");
			CfgSizeStep = ((BasePlugin)this).Config.Bind<float>("Paint", "SizeStep", 0.05f, "Size step per key press.");
			CfgLanguage = ((BasePlugin)this).Config.Bind<string>("UI", "Language", "en", "Menu language: en (default), zh, ko.");
			CfgBrowserRoot = ((BasePlugin)this).Config.Bind<string>("UI", "BrowserRoot", "", "Starting folder for the in-game file browser; empty = Pictures.");
			CfgNetwork = ((BasePlugin)this).Config.Bind<bool>("Network", "Enabled", false, "Broadcast paint to other players with this mod.");
			try
			{
				((BasePlugin)this).Config.Save();
			}
			catch
			{
			}
			try
			{
				ClassInjector.RegisterTypeInIl2Cpp<SprayGifAnimator>();
				ClassInjector.RegisterTypeInIl2Cpp<SprayPaintBehaviour>();
				ClassInjector.RegisterTypeInIl2Cpp<SprayMenuBehaviour>();
				GameObject val = new GameObject("SprayPaint")
				{
					hideFlags = (HideFlags)61
				};
				Object.DontDestroyOnLoad((Object)val);
				val.AddComponent<SprayPaintBehaviour>();
				val.AddComponent<SprayMenuBehaviour>();
				InitializeDataFolders();
				LogSource.LogInfo((object)"[SprayPaint] loaded v0.1.3");
				LogSource.LogInfo((object)("[SprayPaint] sprays folder: " + SprayFolder));
				LogSource.LogInfo((object)("[SprayPaint] selected image=" + SelectedImage + " cfg=" + ((CfgImageFile != null) ? CfgImageFile.Value : "<null>")));
			}
			catch (Exception ex)
			{
				LogSource.LogError((object)("[SprayPaint] init failed: " + ex));
			}
		}

		private static string AssemblyDirectory()
		{
			try
			{
				string location = typeof(SprayPaintPlugin).Assembly.Location;
				if (!string.IsNullOrEmpty(location))
				{
					string directoryName = Path.GetDirectoryName(location);
					if (!string.IsNullOrEmpty(directoryName))
					{
						return directoryName;
					}
				}
			}
			catch
			{
			}
			return Path.Combine(Paths.PluginPath, "SprayPaint");
		}

		private static bool IsImageFile(string path)
		{
			string text = Path.GetExtension(path).ToLowerInvariant();
			switch (text)
			{
			default:
				return text == ".gif";
			case ".png":
			case ".jpg":
			case ".jpeg":
				return true;
			}
		}

		private static void CopyImages(string from, string to)
		{
			try
			{
				if (string.IsNullOrEmpty(from) || string.IsNullOrEmpty(to) || string.Equals(Path.GetFullPath(from), Path.GetFullPath(to), StringComparison.OrdinalIgnoreCase) || !Directory.Exists(from))
				{
					return;
				}
				Directory.CreateDirectory(to);
				string[] files = Directory.GetFiles(from);
				for (int i = 0; i < files.Length; i++)
				{
					if (IsImageFile(files[i]))
					{
						string text = Path.Combine(to, Path.GetFileName(files[i]));
						if (!File.Exists(text))
						{
							File.Copy(files[i], text, overwrite: false);
						}
					}
				}
			}
			catch
			{
			}
		}

		private static void InitializeDataFolders()
		{
			try
			{
				string text = AssemblyDirectory();
				string text2 = Path.Combine(text, "Sprays");
				string text3 = Path.Combine(Paths.PluginPath, "SprayPaint", "Sprays");
				s_sprayFolder = text2;
				Directory.CreateDirectory(text2);
				Directory.CreateDirectory(Path.Combine(text2, "Received"));
				CopyImages(text3, text2);
				CopyImages(text, text2);
				CopyImages(Path.Combine(text3, "Received"), Path.Combine(text2, "Received"));
				CopyImages(Path.Combine(text, "Received"), Path.Combine(text2, "Received"));
			}
			catch
			{
			}
		}

		internal static string ResolveImagePath(string fileName)
		{
			try
			{
				string text = SprayFileTransfer.SafeFileName(fileName);
				if (string.IsNullOrEmpty(text))
				{
					return null;
				}
				string text2 = Path.Combine(SprayFolder, text);
				if (File.Exists(text2))
				{
					return text2;
				}
				string text3 = Path.Combine(ReceivedFolder, text);
				if (File.Exists(text3))
				{
					return text3;
				}
			}
			catch
			{
			}
			return null;
		}

		internal static string GetImageSource(string fileName)
		{
			try
			{
				string path = Path.Combine(ReceivedFolder, fileName + ".source.txt");
				if (File.Exists(path))
				{
					return File.ReadAllText(path).Trim();
				}
			}
			catch
			{
			}
			return "";
		}

		internal static float ReceivedSpraySize(float senderSize)
		{
			if (CfgRemoteSpraySize != null && CfgRemoteSpraySize.Value > 0f)
			{
				return Mathf.Clamp(CfgRemoteSpraySize.Value, 0.05f, 5f);
			}
			return senderSize;
		}

		internal static void SaveConfig()
		{
			try
			{
				ConfigFile.Save();
			}
			catch
			{
			}
		}

		internal static void TryRegisterNetwork()
		{
			if (NetworkRegistered)
			{
				return;
			}
			try
			{
				if (!NetworkAPI.IsEventRegistered("SprayPaint.Place"))
				{
					NetworkAPI.RegisterFreeSizedEvent("SprayPaint.Place", (Action<ulong, byte[]>)OnNetworkSpray);
				}
				NetworkRegistered = true;
				LogSource.LogInfo((object)"[SprayPaint] network event registered");
				SprayFileTransfer.TryRegisterNetwork();
			}
			catch
			{
			}
		}

		private static void OnNetworkSpray(ulong senderId, byte[] data)
		{
			try
			{
				if (CfgEnabled.Value && data != null && data.Length >= 28)
				{
					SprayPaintBehaviour.EnqueueNetwork(senderId, data);
				}
			}
			catch (Exception ex)
			{
				LogSource.LogWarning((object)("[SprayPaint] network receive failed: " + ex.Message));
			}
		}
	}
	internal static class SprayPaintSound
	{
		private static AudioSource s_source;

		private static AudioClip s_clip;

		internal static void Init()
		{
			//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_001f: Expected O, but got Unknown
			if ((Object)(object)s_source != (Object)null)
			{
				return;
			}
			try
			{
				GameObject val = new GameObject("SprayPaintSound");
				Object.DontDestroyOnLoad((Object)val);
				s_source = val.AddComponent<AudioSource>();
				s_source.playOnAwake = false;
				s_source.spatialBlend = 0f;
				s_source.volume = 0.5f;
				int num = 44100;
				int num2 = (int)((float)num * 0.14f);
				float[] array = new float[num2];
				Random random = new Random(1337);
				float num3 = 0f;
				for (int i = 0; i < num2; i++)
				{
					float num4 = (float)(random.NextDouble() * 2.0 - 1.0);
					num3 = num3 * 0.65f + num4 * 0.35f;
					float num5 = 1f - (float)i / (float)num2;
					array[i] = num3 * num5 * 0.35f;
				}
				s_clip = AudioClip.Create("SprayPaintSpray", num2, 1, num, false);
				s_clip.SetData(Il2CppStructArray<float>.op_Implicit(array), 0);
			}
			catch
			{
			}
		}

		internal static void Play()
		{
			try
			{
				if (SprayPaintPlugin.CfgSpraySound != null && SprayPaintPlugin.CfgSpraySound.Value)
				{
					Init();
					if ((Object)(object)s_source != (Object)null && (Object)(object)s_clip != (Object)null)
					{
						s_source.PlayOneShot(s_clip, 0.55f);
					}
				}
			}
			catch
			{
			}
		}
	}
	internal struct SprayData
	{
		internal Vector3 Position;

		internal Vector3 Normal;

		internal Color Color;

		internal float Size;

		internal string Image;

		internal string ImageHash;

		internal int Shape;

		internal int ProtocolVersion;

		internal uint UniqueId;

		internal byte[] ToBytes()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(Position.x);
			binaryWriter.Write(Position.y);
			binaryWriter.Write(Position.z);
			binaryWriter.Write(Normal.x);
			binaryWriter.Write(Normal.y);
			binaryWriter.Write(Normal.z);
			binaryWriter.Write(Color.r);
			binaryWriter.Write(Color.g);
			binaryWriter.Write(Color.b);
			binaryWriter.Write(Color.a);
			binaryWriter.Write(Size);
			string s = Image ?? "";
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			binaryWriter.Write(bytes.Length);
			binaryWriter.Write(bytes);
			string s2 = ImageHash ?? "";
			byte[] bytes2 = Encoding.UTF8.GetBytes(s2);
			binaryWriter.Write(bytes2.Length);
			binaryWriter.Write(bytes2);
			binaryWriter.Write((byte)Shape);
			binaryWriter.Write((byte)ProtocolVersion);
			binaryWriter.Write(UniqueId);
			return memoryStream.ToArray();
		}

		internal static bool TryFromBytes(byte[] data, out SprayData result)
		{
			//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_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_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)
			result = default(SprayData);
			if (data == null || data.Length < 44)
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(data);
				using BinaryReader binaryReader = new BinaryReader(memoryStream);
				result.Position = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
				result.Normal = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
				result.Color = new Color(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
				result.Size = binaryReader.ReadSingle();
				result.Image = "";
				result.ImageHash = "";
				if (memoryStream.Position < memoryStream.Length)
				{
					int num = binaryReader.ReadInt32();
					if (num > 0 && num <= 256 && memoryStream.Position + num <= memoryStream.Length)
					{
						result.Image = Encoding.UTF8.GetString(binaryReader.ReadBytes(num));
					}
				}
				if (memoryStream.Position < memoryStream.Length)
				{
					int num2 = binaryReader.ReadInt32();
					if (num2 > 0 && num2 <= 128 && memoryStream.Position + num2 <= memoryStream.Length)
					{
						result.ImageHash = Encoding.UTF8.GetString(binaryReader.ReadBytes(num2));
					}
				}
				if (memoryStream.Position < memoryStream.Length)
				{
					result.Shape = binaryReader.ReadByte();
				}
				if (memoryStream.Position < memoryStream.Length)
				{
					result.ProtocolVersion = binaryReader.ReadByte();
				}
				if (memoryStream.Position + 4 <= memoryStream.Length)
				{
					result.UniqueId = binaryReader.ReadUInt32();
				}
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class SprayDecalFactory
	{
		private sealed class MarkAttachment
		{
			internal GameObject Mark;

			internal Transform Source;

			internal Vector3 Position;

			internal Quaternion Rotation;
		}

		private static readonly List<GameObject> s_marks = new List<GameObject>();

		private static readonly List<MarkAttachment> s_attachments = new List<MarkAttachment>();

		private static readonly Dictionary<GameObject, MeshRenderer[]> s_markRenderers = new Dictionary<GameObject, MeshRenderer[]>();

		private static readonly Stack<GameObject>[] s_markPools = new Stack<GameObject>[7];

		private static readonly Dictionary<GameObject, int> s_markShapes = new Dictionary<GameObject, int>();

		private static readonly Dictionary<GameObject, float> s_markLifetimes = new Dictionary<GameObject, float>();

		private static readonly Dictionary<GameObject, MeshRenderer> s_poolBackRenderers = new Dictionary<GameObject, MeshRenderer>();

		private static readonly Dictionary<GameObject, bool> s_markSingleSided = new Dictionary<GameObject, bool>();

		private static readonly Dictionary<GameObject, Vector3> s_markBaseScale = new Dictionary<GameObject, Vector3>();

		private static int s_poolCreated;

		private static int s_poolReused;

		private static Vector3 s_lastCullOrigin;

		private static float s_nextCullTime;

		private static Mesh s_mesh;

		private static readonly Dictionary<string, Material> s_materials = new Dictionary<string, Material>();

		private static readonly Queue<string> s_materialOrder = new Queue<string>();

		private static readonly Queue<string> s_textureOrder = new Queue<string>();

		private static readonly Dictionary<string, Texture2D> s_textures = new Dictionary<string, Texture2D>();

		private static bool s_imageCacheDirty;

		internal static int ImageCacheGeneration;

		private static readonly Mesh[] s_meshes = (Mesh[])(object)new Mesh[7];

		private static readonly Dictionary<string, Texture2D> s_filePreviews = new Dictionary<string, Texture2D>();

		private static readonly Dictionary<string, Texture2D> s_thumbnails = new Dictionary<string, Texture2D>();

		private static readonly Queue<string> s_thumbnailOrder = new Queue<string>();

		private static readonly Queue<string> s_filePreviewOrder = new Queue<string>();

		private static Material s_placeholderMaterial;

		private static Texture2D s_placeholderTexture;

		internal static void InvalidateImageCache()
		{
			s_imageCacheDirty = true;
			ImageCacheGeneration++;
		}

		private static void ApplyImageCacheInvalidation()
		{
			if (!s_imageCacheDirty)
			{
				return;
			}
			s_imageCacheDirty = false;
			try
			{
				s_textures.Clear();
				s_materials.Clear();
				s_materialOrder.Clear();
				s_textureOrder.Clear();
				s_thumbnails.Clear();
				s_filePreviews.Clear();
				s_thumbnailOrder.Clear();
				s_filePreviewOrder.Clear();
				GifDecoder.ClearCache();
			}
			catch
			{
			}
		}

		internal static void TrimCaches()
		{
			try
			{
				HashSet<Material> hashSet = new HashSet<Material>();
				HashSet<Texture> hashSet2 = new HashSet<Texture>();
				for (int i = 0; i < s_marks.Count; i++)
				{
					GameObject val = s_marks[i];
					if ((Object)(object)val == (Object)null)
					{
						continue;
					}
					if (!s_markRenderers.TryGetValue(val, out var value) || value == null)
					{
						value = Il2CppArrayBase<MeshRenderer>.op_Implicit(val.GetComponentsInChildren<MeshRenderer>(true));
						s_markRenderers[val] = value;
					}
					for (int j = 0; j < value.Length; j++)
					{
						Material val2 = (((Object)(object)value[j] != (Object)null) ? ((Renderer)value[j]).sharedMaterial : null);
						if (!((Object)(object)val2 == (Object)null))
						{
							hashSet.Add(val2);
							if ((Object)(object)val2.mainTexture != (Object)null)
							{
								hashSet2.Add(val2.mainTexture);
							}
						}
					}
				}
				foreach (Material value4 in s_materials.Values)
				{
					if (!((Object)(object)value4 == (Object)null) && (Object)(object)value4.mainTexture != (Object)null)
					{
						hashSet2.Add(value4.mainTexture);
					}
				}
				int num = ((SprayPaintPlugin.CfgMaterialCacheLimit == null) ? 64 : SprayPaintPlugin.CfgMaterialCacheLimit.Value);
				if (num > 0)
				{
					while (s_materials.Count > num && s_materialOrder.Count > 0)
					{
						string text = s_materialOrder.Dequeue();
						if (!s_materials.TryGetValue(text, out var value2))
						{
							continue;
						}
						if ((Object)(object)value2 == (Object)null)
						{
							s_materials.Remove(text);
							continue;
						}
						if (hashSet.Contains(value2))
						{
							s_materialOrder.Enqueue(text);
							break;
						}
						s_materials.Remove(text);
						Object.Destroy((Object)(object)value2);
					}
				}
				int num2 = ((SprayPaintPlugin.CfgTextureCacheLimit == null) ? 64 : SprayPaintPlugin.CfgTextureCacheLimit.Value);
				if (num2 <= 0)
				{
					return;
				}
				while (s_textures.Count > num2 && s_textureOrder.Count > 0)
				{
					string text2 = s_textureOrder.Dequeue();
					if (!s_textures.TryGetValue(text2, out var value3))
					{
						continue;
					}
					if ((Object)(object)value3 == (Object)null)
					{
						s_textures.Remove(text2);
						continue;
					}
					if (hashSet2.Contains((Texture)(object)value3))
					{
						s_textureOrder.Enqueue(text2);
						break;
					}
					s_textures.Remove(text2);
					Object.Destroy((Object)(object)value3);
				}
			}
			catch
			{
			}
		}

		internal static Mesh GetMesh()
		{
			return GetMesh(0);
		}

		internal static Mesh GetMesh(int shape)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0055: 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_006b: 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_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_0190: 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_01ab: 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_01c6: 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_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: 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_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0236: Unknown result type (might be due to invalid IL or missing references)
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: 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_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02af: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ca: 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_02e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Unknown result type (might be due to invalid IL or missing references)
			//IL_0322: Unknown result type (might be due to invalid IL or missing references)
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			//IL_0338: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0353: Unknown result type (might be due to invalid IL or missing references)
			//IL_0364: Unknown result type (might be due to invalid IL or missing references)
			//IL_0369: Unknown result type (might be due to invalid IL or missing references)
			//IL_0381: Unknown result type (might be due to invalid IL or missing references)
			//IL_0386: Unknown result type (might be due to invalid IL or missing references)
			//IL_038d: Unknown result type (might