Decompiled source of SprayPaint v0.1.3
BepInEx\plugins\SprayPaint\SprayPaint.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
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