using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.UI; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("DrawableSuits")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+4aec450a9737f33a67a55ab1962cf6ed017afa14")] [assembly: AssemblyProduct("DrawableSuits")] [assembly: AssemblyTitle("DrawableSuits")] [assembly: AssemblyVersion("1.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 DrawableSuits { internal static class DrawableSuitDesignCode { [Serializable] internal sealed class Payload { public int formatVersion; public string modVersion; public string designName; public string baseSuitName; public int sourceSuitId; public int width; public int height; public string exportedUtc; public string pngBase64; } internal struct CodeInfo { internal int FormatVersion; internal string DesignName; internal string BaseSuitName; internal int SourceSuitId; internal int Width; internal int Height; internal int PngBytes; internal int JsonBytes; internal int PayloadBytes; internal int CompressedBytes; internal int CodeLength; } internal const string PrefixV1 = "DSUIT1:"; internal const string PrefixV2 = "DSUIT2:"; internal const string Prefix = "DSUIT2:"; private const int FormatVersionV1 = 1; private const int FormatVersionV2 = 2; private const int MaxDecodedJsonBytes = 25165824; private const int MaxDecodedBinaryBytes = 18874368; private const int MaxPngBytes = 16777216; internal static bool HasSupportedPrefix(string code) { if (string.IsNullOrWhiteSpace(code)) { return false; } string text = code.TrimStart(); if (!text.StartsWith("DSUIT2:", StringComparison.Ordinal)) { return text.StartsWith("DSUIT1:", StringComparison.Ordinal); } return true; } internal static bool TryExport(SuitTextureState state, string designName, out string code, out CodeInfo info, out string failureReason) { code = string.Empty; info = default(CodeInfo); failureReason = string.Empty; if ((Object)(object)state?.EditableTexture == (Object)null) { failureReason = "No editable suit texture is available."; return false; } string text = TextureTools.SanitizeFileName(designName); if (string.IsNullOrWhiteSpace(text)) { failureReason = "Design name is empty."; return false; } byte[] array = ImageConversion.EncodeToPNG(state.EditableTexture); if (array == null || array.Length == 0) { failureReason = "Failed to encode the editable texture."; return false; } if (array.Length > 16777216) { failureReason = $"Encoded texture is too large ({array.Length} bytes)."; return false; } byte[] array2 = BuildV2PayloadBytes(text, state.SuitName ?? string.Empty, state.SuitId, ((Texture)state.EditableTexture).width, ((Texture)state.EditableTexture).height, DateTime.UtcNow.ToString("o"), array); byte[] array3 = Compress(array2); code = "DSUIT2:" + ToBase64Url(array3); info = new CodeInfo { FormatVersion = 2, DesignName = text, BaseSuitName = (state.SuitName ?? string.Empty), SourceSuitId = state.SuitId, Width = ((Texture)state.EditableTexture).width, Height = ((Texture)state.EditableTexture).height, PngBytes = array.Length, JsonBytes = 0, PayloadBytes = array2.Length, CompressedBytes = array3.Length, CodeLength = code.Length }; return true; } internal static bool TryDecode(string code, int maxTextureSize, out Payload payload, out Texture2D texture, out CodeInfo info, out string failureReason) { payload = null; texture = null; info = default(CodeInfo); failureReason = string.Empty; if (string.IsNullOrWhiteSpace(code)) { failureReason = "Design code is empty."; return false; } code = code.Trim(); if (code.StartsWith("DSUIT2:", StringComparison.Ordinal)) { return TryDecodeV2(code, maxTextureSize, out payload, out texture, out info, out failureReason); } if (code.StartsWith("DSUIT1:", StringComparison.Ordinal)) { return TryDecodeV1(code, maxTextureSize, out payload, out texture, out info, out failureReason); } failureReason = "Design code must start with DSUIT2: or DSUIT1:"; return false; } private static byte[] BuildV2PayloadBytes(string designName, string baseSuitName, int sourceSuitId, int width, int height, string exportedUtc, byte[] pngBytes) { using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(2); binaryWriter.Write("0.5.77"); binaryWriter.Write(designName ?? string.Empty); binaryWriter.Write(baseSuitName ?? string.Empty); binaryWriter.Write(sourceSuitId); binaryWriter.Write(width); binaryWriter.Write(height); binaryWriter.Write(exportedUtc ?? string.Empty); binaryWriter.Write((pngBytes != null) ? pngBytes.Length : 0); if (pngBytes != null && pngBytes.Length != 0) { binaryWriter.Write(pngBytes); } } return memoryStream.ToArray(); } private static bool TryDecodeV2(string code, int maxTextureSize, out Payload payload, out Texture2D texture, out CodeInfo info, out string failureReason) { payload = null; texture = null; info = default(CodeInfo); failureReason = string.Empty; byte[] array; try { array = FromBase64Url(RemoveWhitespace(code.Substring("DSUIT2:".Length))); } catch (Exception ex) { failureReason = "Design code is not valid Base64Url data (" + ex.GetType().Name + ")."; return false; } byte[] array2; try { array2 = Decompress(array, 18874368); } catch (Exception ex2) { failureReason = "Design code payload could not be decompressed (" + ex2.GetType().Name + ")."; return false; } byte[] array3; try { using MemoryStream memoryStream = new MemoryStream(array2); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); int num = binaryReader.ReadInt32(); if (num != 2) { failureReason = $"Unsupported design code format {num}."; return false; } payload = new Payload { formatVersion = num, modVersion = binaryReader.ReadString(), designName = binaryReader.ReadString(), baseSuitName = binaryReader.ReadString(), sourceSuitId = binaryReader.ReadInt32(), width = binaryReader.ReadInt32(), height = binaryReader.ReadInt32(), exportedUtc = binaryReader.ReadString(), pngBase64 = string.Empty }; int num2 = binaryReader.ReadInt32(); if (num2 <= 0) { failureReason = "Design code PNG data is empty."; return false; } if (num2 > 16777216) { failureReason = $"Design code PNG data is too large ({num2} bytes)."; return false; } if (memoryStream.Length - memoryStream.Position < num2) { failureReason = "Design code PNG data is truncated."; return false; } array3 = binaryReader.ReadBytes(num2); if (array3.Length != num2) { failureReason = "Design code PNG data is truncated."; return false; } } catch (Exception ex3) { payload = null; failureReason = "Design code binary payload could not be parsed (" + ex3.GetType().Name + ")."; return false; } return TryLoadDecodedTexture(code, maxTextureSize, payload, array3, array2.Length, 0, array.Length, out texture, out info, out failureReason); } private static bool TryDecodeV1(string code, int maxTextureSize, out Payload payload, out Texture2D texture, out CodeInfo info, out string failureReason) { payload = null; texture = null; info = default(CodeInfo); failureReason = string.Empty; byte[] array; try { array = FromBase64Url(RemoveWhitespace(code.Substring("DSUIT1:".Length))); } catch (Exception ex) { failureReason = "Design code is not valid Base64Url data (" + ex.GetType().Name + ")."; return false; } byte[] array2; try { array2 = Decompress(array, 25165824); } catch (Exception ex2) { failureReason = "Design code payload could not be decompressed (" + ex2.GetType().Name + ")."; return false; } try { payload = JsonUtility.FromJson(Encoding.UTF8.GetString(array2)); } catch (Exception ex3) { failureReason = "Design code JSON could not be parsed (" + ex3.GetType().Name + ")."; return false; } if (payload == null) { failureReason = "Design code payload is missing."; return false; } if (payload.formatVersion != 1) { failureReason = $"Unsupported design code format {payload.formatVersion}."; return false; } if (string.IsNullOrWhiteSpace(payload.pngBase64)) { failureReason = "Design code is missing PNG data."; return false; } byte[] pngBytes; try { pngBytes = Convert.FromBase64String(payload.pngBase64); } catch (Exception ex4) { failureReason = "Design code PNG data is invalid (" + ex4.GetType().Name + ")."; return false; } return TryLoadDecodedTexture(code, maxTextureSize, payload, pngBytes, 0, array2.Length, array.Length, out texture, out info, out failureReason); } private static bool TryLoadDecodedTexture(string code, int maxTextureSize, Payload payload, byte[] pngBytes, int payloadBytes, int jsonBytes, int compressedBytes, out Texture2D texture, out CodeInfo info, out string failureReason) { texture = null; info = default(CodeInfo); failureReason = string.Empty; if (payload == null) { failureReason = "Design code payload is missing."; return false; } if (string.IsNullOrWhiteSpace(payload.designName)) { failureReason = "Design code is missing a design name."; return false; } if (payload.width <= 0 || payload.height <= 0) { failureReason = "Design code has invalid texture dimensions."; return false; } if (payload.width > maxTextureSize || payload.height > maxTextureSize) { failureReason = $"Design code texture is too large ({payload.width}x{payload.height}, max {maxTextureSize})."; return false; } if (pngBytes == null || pngBytes.Length == 0) { failureReason = "Design code PNG data is empty."; return false; } if (pngBytes.Length > 16777216) { failureReason = $"Design code PNG data is too large ({pngBytes.Length} bytes)."; return false; } texture = TextureTools.LoadImageBytes(pngBytes, "DrawableSuitsDesignCodeTexture", maxTextureSize, resizeOversized: false); if ((Object)(object)texture == (Object)null) { failureReason = $"Design code PNG data could not be loaded as an image or exceeds max size {maxTextureSize}."; return false; } if (((Texture)texture).width > maxTextureSize || ((Texture)texture).height > maxTextureSize) { int width = ((Texture)texture).width; int height = ((Texture)texture).height; Object.Destroy((Object)(object)texture); texture = null; failureReason = $"Decoded texture is too large ({width}x{height}, max {maxTextureSize})."; return false; } info = new CodeInfo { FormatVersion = payload.formatVersion, DesignName = payload.designName, BaseSuitName = payload.baseSuitName, SourceSuitId = payload.sourceSuitId, Width = ((Texture)texture).width, Height = ((Texture)texture).height, PngBytes = pngBytes.Length, JsonBytes = jsonBytes, PayloadBytes = payloadBytes, CompressedBytes = compressedBytes, CodeLength = code.Length }; return true; } private static byte[] Compress(byte[] bytes) { using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, leaveOpen: true)) { gZipStream.Write(bytes, 0, bytes.Length); } return memoryStream.ToArray(); } private static byte[] Decompress(byte[] bytes, int maxBytes) { using MemoryStream stream = new MemoryStream(bytes); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int num; while ((num = gZipStream.Read(array, 0, array.Length)) > 0) { if (memoryStream.Length + num > maxBytes) { throw new InvalidDataException("Decoded payload exceeds the maximum allowed size."); } memoryStream.Write(array, 0, num); } return memoryStream.ToArray(); } private static string ToBase64Url(byte[] bytes) { return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-') .Replace('/', '_'); } private static byte[] FromBase64Url(string value) { value = value.Replace('-', '+').Replace('_', '/'); switch (value.Length % 4) { case 2: value += "=="; break; case 3: value += "="; break; default: throw new FormatException("Invalid Base64Url padding."); case 0: break; } return Convert.FromBase64String(value); } private static string RemoveWhitespace(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length); for (int i = 0; i < value.Length; i++) { if (!char.IsWhiteSpace(value[i])) { stringBuilder.Append(value[i]); } } return stringBuilder.ToString(); } } internal sealed class DrawableSuitsConfig { public ConfigEntry OpenEditorKey { get; } public ConfigEntry EmergencyOpenKey { get; } public ConfigEntry MaxTextureSize { get; } public ConfigEntry MaxUndoStates { get; } public ConfigEntry MaxSyncBytes { get; } public ConfigEntry SyncChunkBytes { get; } public ConfigEntry ControllerCursorSpeed { get; } public ConfigEntry EnableNetworkSync { get; } public ConfigEntry EnableExperimentalModelPreview { get; } public ConfigEntry StartInUvFallbackMode { get; } public ConfigEntry ThirdPersonCameraDistance { get; } public ConfigEntry RecentColors { get; } public ConfigEntry ApplyLocalFirstPersonArms { get; } public DrawableSuitsConfig(ConfigFile config) { OpenEditorKey = config.Bind("Input", "OpenEditorKey", (KeyCode)289, "Keyboard key that opens or closes the suit editor."); EmergencyOpenKey = config.Bind("Input", "EmergencyOpenKey", (KeyCode)291, "Keyboard key that opens the editor even when suit/player detection is incomplete."); ControllerCursorSpeed = config.Bind("Input", "ControllerCursorSpeed", 900f, "Virtual cursor speed in pixels per second."); MaxTextureSize = config.Bind("Textures", "MaxTextureSize", 1024, "Maximum width and height for editable and synced suit textures."); MaxUndoStates = config.Bind("Editor", "MaxUndoStates", 12, "Maximum undo snapshots kept while editing."); EnableNetworkSync = config.Bind("Multiplayer", "EnableNetworkSync", true, "Sync applied and saved suit designs to other DrawableSuits users."); MaxSyncBytes = config.Bind("Multiplayer", "MaxSyncBytes", 4194304, "Maximum PNG payload size allowed for multiplayer sync."); SyncChunkBytes = config.Bind("Multiplayer", "SyncChunkBytes", 48000, "Byte size for each Netcode custom-message texture chunk."); EnableExperimentalModelPreview = config.Bind("Editor", "EnableExperimentalModelPreview", false, "Disabled by default. Uses the old camera/RenderTexture 3D model preview only for diagnostics; third-person world painting is the default."); StartInUvFallbackMode = config.Bind("Editor", "StartInUvFallbackMode", false, "Open directly into the old UV texture fallback instead of third-person world painting. Useful for diagnostics."); ThirdPersonCameraDistance = config.Bind("Editor", "ThirdPersonCameraDistance", 3.4f, "Default third-person editor camera orbit distance."); RecentColors = config.Bind("Editor", "RecentColors", string.Empty, "Recent brush colors as comma-separated #RRGGBB values. Colors are added only after Paint, Fill, or Text writes to the suit."); ApplyLocalFirstPersonArms = config.Bind("Compatibility", "ApplyLocalFirstPersonArms", false, "Experimental. When false, DrawableSuits does not apply edited materials to the local first-person arms/body outside the editor."); } } internal static class DrawableSuitsDiagnostics { private static readonly object SyncRoot = new object(); private static string _logPath; private static bool _initialized; public static string LogPath => _logPath ?? Path.Combine(DrawableSuitsPaths.Logs, "diagnostics.log"); public static void Initialize() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) try { DrawableSuitsPaths.EnsureCreated(); _logPath = Path.Combine(DrawableSuitsPaths.Logs, "diagnostics.log"); Directory.CreateDirectory(Path.GetDirectoryName(_logPath)); File.WriteAllText(_logPath, string.Empty); _initialized = true; Info("Diagnostics initialized."); Info("DrawableSuits 0.5.77 (com.toxcgang.drawablesuits) starting."); Info($"UnityVersion={Application.unityVersion}; ApplicationVersion={Application.version}; Platform={Application.platform}; ProductName={Application.productName}"); Info("DataPath=" + Application.dataPath + "; PersistentDataPath=" + Application.persistentDataPath + "; ConfigRoot=" + DrawableSuitsPaths.Root); Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; activeScene = SceneManager.GetActiveScene(); Info($"ActiveScene={name}; SceneBuildIndex={((Scene)(ref activeScene)).buildIndex}"); AssemblyName[] referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies(); foreach (AssemblyName assemblyName in referencedAssemblies) { Info($"Reference={assemblyName.Name}, Version={assemblyName.Version}"); } } catch (Exception arg) { _initialized = false; LogToBepInEx((LogLevel)2, $"DrawableSuits diagnostics failed to initialize: {arg}"); } } public static void Info(string message) { Write((LogLevel)16, message); } public static void Warn(string message) { Write((LogLevel)4, message); } public static void Error(string message) { Write((LogLevel)2, message); } public static void Exception(string context, Exception exception) { if (exception == null) { Error(context + ": unknown exception."); return; } Write((LogLevel)2, context + ": " + exception.GetType().FullName + ": " + exception.Message + Environment.NewLine + exception.StackTrace); } private static void Write(LogLevel level, string message) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) string text = $"[{DateTime.Now:O}] [{level}] {message ?? string.Empty}"; LogToBepInEx(level, message ?? string.Empty); if (!_initialized) { return; } try { lock (SyncRoot) { File.AppendAllText(LogPath, text + Environment.NewLine); } } catch { } } private static void LogToBepInEx(LogLevel level, string message) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 ManualLogSource modLogger = DrawableSuitsPlugin.ModLogger; if (modLogger == null) { Debug.Log((object)("[DrawableSuits] " + message)); } else if ((int)level != 2) { if ((int)level != 4) { if ((int)level == 32) { modLogger.LogDebug((object)message); } else { modLogger.LogInfo((object)message); } } else { modLogger.LogWarning((object)message); } } else { modLogger.LogError((object)message); } } } internal static class DrawableSuitsInput { private static bool _legacyUnavailable; private static bool _legacyUnavailableLogged; internal static bool WasKeyPressed(Key defaultKey, KeyCode configuredKey) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Key key = MapKeyCode(configuredKey, defaultKey); if (Keyboard.current != null) { return WasInputSystemKeyPressed(key); } return WasLegacyKeyPressedOnceGuarded(configuredKey); } internal static bool WasKeyPressed(Key key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return WasInputSystemKeyPressed(key); } internal static bool IsKeyPressed(Key key) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) try { Keyboard current = Keyboard.current; return current != null && ((ButtonControl)current[key]).isPressed; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception($"Input System key held polling failed for {key}", exception); return false; } } internal static bool TryGetMousePosition(out Vector2 position) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) try { Mouse current = Mouse.current; if (current != null) { position = ((InputControl)(object)((Pointer)current).position).ReadValue(); return true; } } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Input System mouse position polling failed", exception); } position = default(Vector2); return false; } internal static bool IsLeftMousePressed() { return IsMouseButtonPressed((Mouse mouse) => mouse.leftButton.isPressed, "left"); } internal static bool IsRightMousePressed() { return IsMouseButtonPressed((Mouse mouse) => mouse.rightButton.isPressed, "right"); } internal static float MouseDeltaX() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) try { Mouse current = Mouse.current; return (current != null) ? ((InputControl)(object)((Pointer)current).delta).ReadValue().x : 0f; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Input System mouse delta polling failed", exception); return 0f; } } internal static float MouseDeltaY() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) try { Mouse current = Mouse.current; return (current != null) ? ((InputControl)(object)((Pointer)current).delta).ReadValue().y : 0f; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Input System mouse delta polling failed", exception); return 0f; } } internal static bool WasMouseUsedThisFrame() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { Mouse current = Mouse.current; if (current == null) { return false; } Vector2 val = ((InputControl)(object)((Pointer)current).delta).ReadValue(); return ((Vector2)(ref val)).sqrMagnitude > 0.01f || current.leftButton.isPressed || current.rightButton.isPressed || Mathf.Abs(((InputControl)(object)current.scroll).ReadValue().y) > 0.01f; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Input System mouse activity polling failed", exception); return false; } } internal static float MouseScrollY() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) try { Mouse current = Mouse.current; if (current == null) { return 0f; } float y = ((InputControl)(object)current.scroll).ReadValue().y; return (Mathf.Abs(y) > 10f) ? (y / 120f) : y; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Input System mouse scroll polling failed", exception); return 0f; } } private static bool IsMouseButtonPressed(Func accessor, string buttonName) { try { Mouse current = Mouse.current; return current != null && accessor(current); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Input System " + buttonName + " mouse button polling failed", exception); return false; } } private static bool WasInputSystemKeyPressed(Key key) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) try { Keyboard current = Keyboard.current; return current != null && ((ButtonControl)current[key]).wasPressedThisFrame; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception($"Input System key polling failed for {key}", exception); return false; } } private static bool WasLegacyKeyPressedOnceGuarded(KeyCode key) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (_legacyUnavailable) { return false; } try { return Input.GetKeyDown(key); } catch (InvalidOperationException ex) { _legacyUnavailable = true; if (!_legacyUnavailableLogged) { _legacyUnavailableLogged = true; DrawableSuitsDiagnostics.Warn($"Legacy UnityEngine.Input is disabled; using Unity Input System only. First legacy failure for {key}: {ex.Message}"); } return false; } catch (Exception exception) { DrawableSuitsDiagnostics.Exception($"Legacy key polling failed once for {key}", exception); return false; } } private unsafe static Key MapKeyCode(KeyCode keyCode, Key fallback) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (TryMapSpecialKey(keyCode, out var key)) { return key; } if (Enum.TryParse(((object)(*(KeyCode*)(&keyCode))/*cast due to .constrained prefix*/).ToString(), ignoreCase: true, out key)) { return key; } return fallback; } private static bool TryMapSpecialKey(KeyCode keyCode, out Key key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected I4, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected I4, but got Unknown //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected I4, but got Unknown if ((int)keyCode <= 27) { if ((int)keyCode <= 9) { if ((int)keyCode == 8) { key = (Key)65; return true; } if ((int)keyCode == 9) { key = (Key)3; return true; } } else { if ((int)keyCode == 13) { key = (Key)2; return true; } if ((int)keyCode == 27) { key = (Key)60; return true; } } } else if ((int)keyCode <= 57) { if ((int)keyCode == 32) { key = (Key)1; return true; } switch (keyCode - 48) { case 0: key = (Key)50; return true; case 1: key = (Key)41; return true; case 2: key = (Key)42; return true; case 3: key = (Key)43; return true; case 4: key = (Key)44; return true; case 5: key = (Key)45; return true; case 6: key = (Key)46; return true; case 7: key = (Key)47; return true; case 8: key = (Key)48; return true; case 9: key = (Key)49; return true; } } else { if ((int)keyCode == 127) { key = (Key)71; return true; } switch (keyCode - 273) { case 0: key = (Key)63; return true; case 1: key = (Key)64; return true; case 3: key = (Key)61; return true; case 2: key = (Key)62; return true; case 4: key = (Key)70; return true; case 5: key = (Key)68; return true; case 6: key = (Key)69; return true; case 7: key = (Key)67; return true; case 8: key = (Key)66; return true; } switch (keyCode - 303) { case 1: key = (Key)51; return true; case 0: key = (Key)52; return true; case 3: key = (Key)55; return true; case 2: key = (Key)56; return true; case 5: key = (Key)53; return true; case 4: key = (Key)54; return true; } } key = (Key)0; return false; } } internal static class DrawableSuitsPaths { public static string Root => Path.Combine(Paths.ConfigPath, "DrawableSuits"); public static string Saves => Path.Combine(Root, "Saves"); public static string Textures => Path.Combine(Root, "Textures"); public static string Decals => Path.Combine(Root, "Decals"); public static string Logs => Path.Combine(Root, "Logs"); public static void EnsureCreated() { Directory.CreateDirectory(Root); Directory.CreateDirectory(Saves); Directory.CreateDirectory(Textures); Directory.CreateDirectory(Decals); Directory.CreateDirectory(Logs); } } [BepInPlugin("com.toxcgang.drawablesuits", "DrawableSuits", "0.5.77")] [BepInProcess("Lethal Company.exe")] public sealed class DrawableSuitsPlugin : BaseUnityPlugin { private static GameObject _runtimeRoot; private Harmony _harmony; internal static DrawableSuitsPlugin Instance { get; private set; } internal static ManualLogSource ModLogger { get; private set; } internal static DrawableSuitsConfig ModConfig { get; private set; } internal static SuitTextureRegistry Registry { get; private set; } internal static SuitEditorController Editor { get; private set; } internal static DrawableSuitsRuntimeHost RuntimeHost { get; private set; } internal static SuitSyncManager Sync { get; private set; } internal static bool IsEditorOpen { get { if ((Object)(object)Editor != (Object)null) { return Editor.IsOpenForDiagnostics; } return false; } } private void Awake() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Expected O, but got Unknown Instance = this; ModLogger = ((BaseUnityPlugin)this).Logger; ModConfig = new DrawableSuitsConfig(((BaseUnityPlugin)this).Config); DrawableSuitsPaths.EnsureCreated(); DrawableSuitsDiagnostics.Initialize(); string[] obj = new string[6] { "Plugin Awake. pluginInstance=", DescribeUnityObject((Object)(object)this), "; pluginGameObject=", DescribeUnityObject((Object)(object)((Component)this).gameObject), "; scene=", null }; Scene activeScene = SceneManager.GetActiveScene(); obj[5] = ((Scene)(ref activeScene)).name; DrawableSuitsDiagnostics.Info(string.Concat(obj)); DrawableSuitsDiagnostics.Info($"Config OpenEditorKey={ModConfig.OpenEditorKey.Value}; EmergencyOpenKey={ModConfig.EmergencyOpenKey.Value}; ControllerCursorSpeed={ModConfig.ControllerCursorSpeed.Value}; MaxTextureSize={ModConfig.MaxTextureSize.Value}; MaxUndoStates={ModConfig.MaxUndoStates.Value}; NetworkSync={ModConfig.EnableNetworkSync.Value}; MaxSyncBytes={ModConfig.MaxSyncBytes.Value}; SyncChunkBytes={ModConfig.SyncChunkBytes.Value}; ExperimentalModelPreview={ModConfig.EnableExperimentalModelPreview.Value}; StartInUvFallbackMode={ModConfig.StartInUvFallbackMode.Value}; ThirdPersonCameraDistance={ModConfig.ThirdPersonCameraDistance.Value}; ApplyLocalFirstPersonArms={ModConfig.ApplyLocalFirstPersonArms.Value}"); _harmony = new Harmony("com.toxcgang.drawablesuits"); try { DrawableSuitsDiagnostics.Info("Applying Harmony patches."); _harmony.PatchAll(); PlayerControllerBPatches.ApplyOptionalGameplayInputPatches(_harmony); DrawableSuitsDiagnostics.Info("Harmony patches applied."); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Harmony PatchAll failed", exception); throw; } SceneManager.sceneLoaded += OnSceneLoaded; EnsureRuntimeReady("Plugin.Awake"); DrawableSuitsDiagnostics.Info("DrawableSuits 0.5.77 loaded with GUID com.toxcgang.drawablesuits."); } private void Start() { DrawableSuitsDiagnostics.Info("Plugin Start. plugin=" + DescribeUnityObject((Object)(object)this) + "; host=" + DescribeUnityObject((Object)(object)RuntimeHost) + "; editor=" + DescribeUnityObject((Object)(object)Editor)); EnsureRuntimeReady("Plugin.Start"); SessionSafetyGuard.Run("Plugin.Start", forceLog: true); } private void Update() { EnsureRuntimeReady("Plugin.Update"); } private void OnDestroy() { DrawableSuitsDiagnostics.Info("DrawableSuitsPlugin.OnDestroy called."); if ((Object)(object)Editor != (Object)null && Editor.IsOpenForDiagnostics) { Editor.CloseEditorForPluginDestroy(); } SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } Registry = null; Sync = null; Editor = null; RuntimeHost = null; if ((Object)(object)_runtimeRoot != (Object)null) { Object.Destroy((Object)(object)_runtimeRoot); _runtimeRoot = null; } if (Instance == this) { Instance = null; } } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) DrawableSuitsDiagnostics.Info($"Scene loaded. name={((Scene)(ref scene)).name}; buildIndex={((Scene)(ref scene)).buildIndex}; mode={mode}"); bool num = ShouldCloseEditorForScene(scene, mode); EnsureRuntimeReady("SceneLoaded:" + ((Scene)(ref scene)).name); if (num && (Object)(object)Editor != (Object)null && Editor.IsOpenForDiagnostics) { DrawableSuitsDiagnostics.Info($"Closing editor because scene changed to '{((Scene)(ref scene)).name}' with mode={mode}."); Editor.CloseEditorForSceneChange(); } Registry?.ReapplyAllIfReady("SceneLoaded:" + ((Scene)(ref scene)).name); SessionSafetyGuard.Run("SceneLoaded:" + ((Scene)(ref scene)).name, forceLog: true); } internal static bool HasSessionSafetyContext() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (!((Object)(object)StartOfRound.Instance != (Object)null) && !((Object)(object)Object.FindObjectOfType()?.localPlayerController != (Object)null)) { return string.Equals(name, "SampleSceneRelay", StringComparison.OrdinalIgnoreCase); } return true; } internal static bool HasGameplayEditorContext() { if ((Object)(object)StartOfRound.Instance?.localPlayerController != (Object)null) { return (Object)(object)Camera.main != (Object)null; } return false; } private static bool ShouldCloseEditorForScene(Scene scene, LoadSceneMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)mode != 1) { return true; } if (!string.Equals(((Scene)(ref scene)).name, "MainMenu", StringComparison.OrdinalIgnoreCase) && !string.Equals(((Scene)(ref scene)).name, "InitScene", StringComparison.OrdinalIgnoreCase)) { return string.Equals(((Scene)(ref scene)).name, "InitSceneLaunchOptions", StringComparison.OrdinalIgnoreCase); } return true; } internal static bool EnsureRuntimeReady(string reason) { if ((Object)(object)Instance == (Object)null) { DrawableSuitsDiagnostics.Warn("EnsureRuntimeReady(" + reason + ") failed: plugin instance is null."); return false; } GameObject orCreateRuntimeRoot = GetOrCreateRuntimeRoot(reason); if ((Object)(object)orCreateRuntimeRoot == (Object)null) { DrawableSuitsDiagnostics.Warn("EnsureRuntimeReady(" + reason + ") failed: runtime root is null."); return false; } Registry = EnsureComponent(Registry, orCreateRuntimeRoot, reason, "registry"); Sync = EnsureComponent(Sync, orCreateRuntimeRoot, reason, "sync"); Editor = EnsureComponent(Editor, orCreateRuntimeRoot, reason, "editor"); RuntimeHost = EnsureComponent(RuntimeHost, orCreateRuntimeRoot, reason, "runtime host"); bool flag = (Object)(object)Registry != (Object)null && (Object)(object)Sync != (Object)null && (Object)(object)Editor != (Object)null && (Object)(object)RuntimeHost != (Object)null; if (!flag) { DrawableSuitsDiagnostics.Warn("EnsureRuntimeReady(" + reason + ") incomplete. registry=" + DescribeUnityObject((Object)(object)Registry) + "; sync=" + DescribeUnityObject((Object)(object)Sync) + "; editor=" + DescribeUnityObject((Object)(object)Editor) + "; host=" + DescribeUnityObject((Object)(object)RuntimeHost)); } return flag; } private static GameObject GetOrCreateRuntimeRoot(string reason) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)_runtimeRoot != (Object)null) { return _runtimeRoot; } _runtimeRoot = new GameObject("DrawableSuitsRuntimeRoot"); ((Object)_runtimeRoot).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)_runtimeRoot); DrawableSuitsDiagnostics.Info("Runtime root created. reason=" + reason + "; root=" + DescribeUnityObject((Object)(object)_runtimeRoot)); return _runtimeRoot; } internal static bool RequestOpenEditor(string source) { bool flag = EnsureRuntimeReady("RequestOpenEditor:" + source); DrawableSuitsDiagnostics.Info($"RequestOpenEditor source={source}; runtimeReady={flag}; editor={DescribeUnityObject((Object)(object)Editor)}"); if (!flag || (Object)(object)Editor == (Object)null) { return false; } bool flag2 = Editor.OpenEditor(source); DrawableSuitsDiagnostics.Info($"RequestOpenEditor result source={source}; opened={flag2}; editorOpen={Editor.IsOpenForDiagnostics}; canvasActive={Editor.CanvasActiveForDiagnostics}"); return flag2; } internal static bool RequestToggleEditor(string source) { bool flag = EnsureRuntimeReady("RequestToggleEditor:" + source); DrawableSuitsDiagnostics.Info($"RequestToggleEditor source={source}; runtimeReady={flag}; editor={DescribeUnityObject((Object)(object)Editor)}"); if (!flag || (Object)(object)Editor == (Object)null) { return false; } if (!Editor.IsOpenForDiagnostics) { return RequestOpenEditor(source); } return CloseEditor(source); } internal static bool CloseEditor(string source) { EnsureRuntimeReady("CloseEditor:" + source); if ((Object)(object)Editor == (Object)null) { DrawableSuitsDiagnostics.Warn("CloseEditor source=" + source + " ignored because editor is null."); return false; } DrawableSuitsDiagnostics.Info("CloseEditor source=" + source + "; editor=" + DescribeUnityObject((Object)(object)Editor)); Editor.CloseEditor(source); return true; } internal static string DescribeUnityObject(Object unityObject) { if (unityObject == (Object)null) { return "null"; } return $"{((object)unityObject).GetType().Name}(id={unityObject.GetInstanceID()}, name={unityObject.name})"; } private static T EnsureComponent(T current, GameObject hostObject, string reason, string label) where T : Component { if ((Object)(object)current != (Object)null) { return current; } T component = hostObject.GetComponent(); if ((Object)(object)component != (Object)null) { DrawableSuitsDiagnostics.Info("EnsureRuntimeReady(" + reason + ") recovered existing " + label + ": " + DescribeUnityObject((Object)(object)component)); return component; } T val = hostObject.AddComponent(); DrawableSuitsDiagnostics.Info("EnsureRuntimeReady(" + reason + ") created " + label + ": " + DescribeUnityObject((Object)(object)val) + " on " + DescribeUnityObject((Object)(object)hostObject)); return val; } } internal sealed class DrawableSuitsRuntimeHost : MonoBehaviour { private bool _firstUpdateLogged; private bool _controllerOpenChordWasHeld; private void Awake() { DrawableSuitsDiagnostics.Info("RuntimeHost Awake. host=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)this) + "; gameObject=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)((Component)this).gameObject)); } private void Start() { DrawableSuitsDiagnostics.Info("RuntimeHost Start. editor=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)DrawableSuitsPlugin.Editor)); } private void Update() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!_firstUpdateLogged) { _firstUpdateLogged = true; object arg = Time.frameCount; Scene activeScene = SceneManager.GetActiveScene(); DrawableSuitsDiagnostics.Info($"RuntimeHost first Update. frame={arg}; scene={((Scene)(ref activeScene)).name}; editor={DrawableSuitsPlugin.DescribeUnityObject((Object)(object)DrawableSuitsPlugin.Editor)}"); } DrawableSuitsPlugin.EnsureRuntimeReady("RuntimeHost.Update"); HandleInput(); } private void HandleInput() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (DrawableSuitsInput.WasKeyPressed((Key)103, DrawableSuitsPlugin.ModConfig.EmergencyOpenKey.Value)) { DrawableSuitsDiagnostics.Info("RuntimeHost F10 emergency open. editor=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)DrawableSuitsPlugin.Editor)); DrawableSuitsPlugin.RequestOpenEditor("F10Emergency"); } if (DrawableSuitsInput.WasKeyPressed((Key)101, DrawableSuitsPlugin.ModConfig.OpenEditorKey.Value)) { DrawableSuitsDiagnostics.Info("RuntimeHost F8 toggle. editor=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)DrawableSuitsPlugin.Editor)); RequestGameplayToggle("F8Keyboard"); } if (WasControllerOpenChordPressed()) { DrawableSuitsDiagnostics.Info("RuntimeHost controller open chord. editor=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)DrawableSuitsPlugin.Editor)); RequestGameplayToggle("ControllerViewBackY"); } } private static void RequestGameplayToggle(string source) { SuitEditorController editor = DrawableSuitsPlugin.Editor; if (editor != null && editor.IsOpenForDiagnostics) { DrawableSuitsPlugin.RequestToggleEditor(source); } else if (!DrawableSuitsPlugin.HasGameplayEditorContext()) { DrawableSuitsDiagnostics.Info("RuntimeHost ignored " + source + " open because no gameplay player/camera context is available. Use F10 for diagnostics."); } else { DrawableSuitsPlugin.RequestToggleEditor(source); } } private bool WasControllerOpenChordPressed() { Gamepad current = Gamepad.current; if (current == null) { _controllerOpenChordWasHeld = false; return false; } ButtonControl val = current.selectButton ?? ((InputControl)current).TryGetChildControl("backButton"); bool flag = val != null && val.isPressed && current.buttonNorth.isPressed; bool result = flag && !_controllerOpenChordWasHeld; _controllerOpenChordWasHeld = flag; return result; } } [HarmonyPatch(typeof(StartOfRound))] internal static class StartOfRoundPatches { [HarmonyPostfix] [HarmonyPatch("LoadUnlockables")] private static void LoadUnlockablesPostfix() { RunPatch("StartOfRound.LoadUnlockables", delegate { DrawableSuitsPlugin.Registry?.ReapplyAllIfReady("StartOfRound.LoadUnlockables"); SessionSafetyGuard.Run("StartOfRound.LoadUnlockables", forceLog: true); }); } [HarmonyPostfix] [HarmonyPatch("PositionSuitsOnRack")] private static void PositionSuitsOnRackPostfix() { RunPatch("StartOfRound.PositionSuitsOnRack", delegate { DrawableSuitsPlugin.Registry?.ReapplyAllIfReady("StartOfRound.PositionSuitsOnRack"); }); } [HarmonyPostfix] [HarmonyPatch("SyncSuitsClientRpc")] private static void SyncSuitsClientRpcPostfix() { RunPatch("StartOfRound.SyncSuitsClientRpc", delegate { DrawableSuitsPlugin.Registry?.ReapplyAllIfReady("StartOfRound.SyncSuitsClientRpc"); }); } [HarmonyPostfix] [HarmonyPatch("OnPlayerConnectedClientRpc")] private static void OnPlayerConnectedClientRpcPostfix() { RunPatch("StartOfRound.OnPlayerConnectedClientRpc", delegate { DrawableSuitsPlugin.Registry?.ReapplyAllIfReady("StartOfRound.OnPlayerConnectedClientRpc"); SessionSafetyGuard.Run("StartOfRound.OnPlayerConnectedClientRpc", forceLog: true); DrawableSuitsPlugin.Sync?.RequestActiveDesigns(); }); } private static void RunPatch(string context, Action action) { try { DrawableSuitsPlugin.EnsureRuntimeReady(context); action(); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Patch failed: " + context, exception); } } } [HarmonyPatch(typeof(UnlockableSuit))] internal static class UnlockableSuitPatches { [HarmonyPostfix] [HarmonyPatch("SwitchSuitForPlayer")] private static void SwitchSuitForPlayerPostfix(PlayerControllerB player) { RunPatch("UnlockableSuit.SwitchSuitForPlayer", delegate { DrawableSuitsPlugin.Registry?.RecordPlayerSuitSnapshot(player, "UnlockableSuit.SwitchSuitForPlayer"); DrawableSuitsPlugin.Registry?.ApplyToPlayer(player); DrawableSuitsPlugin.Registry?.ScheduleReapplyAll("UnlockableSuit.SwitchSuitForPlayer"); }); } [HarmonyPostfix] [HarmonyPatch("SwitchSuitForAllPlayers")] private static void SwitchSuitForAllPlayersPostfix() { RunPatch("UnlockableSuit.SwitchSuitForAllPlayers", delegate { DrawableSuitsPlugin.Registry?.ReapplyAllIfReady("UnlockableSuit.SwitchSuitForAllPlayers"); }); } private static void RunPatch(string context, Action action) { try { DrawableSuitsPlugin.EnsureRuntimeReady(context); action(); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Patch failed: " + context, exception); } } } [HarmonyPatch(typeof(PlayerControllerB))] internal static class PlayerControllerBPatches { private static readonly string[] OptionalBlockedInputMethods = new string[25] { "ScrollMouse_performed", "ScrollMouse_canceled", "SwitchItem_performed", "SwitchItem_canceled", "NextItem_performed", "PreviousItem_performed", "Scan_performed", "PingScan_performed", "ItemPrimaryUse_performed", "ItemInteract_performed", "ItemSecondaryUse_performed", "ItemTertiaryUse_performed", "ActivateItem_performed", "ActivateItem_canceled", "Discard_performed", "Crouch_performed", "InspectItem_performed", "UseUtilitySlot_performed", "Emote1_performed", "Emote2_performed", "SetFreeCamera_performed", "SpeedCheat_performed", "BuildMode_performed", "ConfirmBuildMode_performed", "Delete_performed" }; private static readonly HashSet OptionalPatchedMethods = new HashSet(StringComparer.Ordinal); private static float _lastBlockedInputLogTime; private static string _lastBlockedInputMethod; internal static void ApplyOptionalGameplayInputPatches(Harmony harmony) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown if (harmony == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(PlayerControllerBPatches), "OptionalGameplayInputPrefix", (Type[])null, (Type[])null); if (methodInfo == null) { DrawableSuitsDiagnostics.Warn("Optional gameplay input prefix was not found; scan/inventory fallback patches were not applied."); return; } List list = new List(); List list2 = new List(); for (int i = 0; i < OptionalBlockedInputMethods.Length; i++) { string text = OptionalBlockedInputMethods[i]; if (OptionalPatchedMethods.Contains(text)) { continue; } MethodInfo methodInfo2 = null; try { methodInfo2 = AccessTools.Method(typeof(PlayerControllerB), text, (Type[])null, (Type[])null); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Optional PlayerControllerB input method lookup failed for " + text, exception); list2.Add(text); continue; } if (methodInfo2 == null) { list2.Add(text); continue; } try { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); OptionalPatchedMethods.Add(text); list.Add(text); } catch (Exception exception2) { DrawableSuitsDiagnostics.Exception("Failed to apply optional PlayerControllerB input block for " + text, exception2); } } DrawableSuitsDiagnostics.Info("Optional gameplay input patches applied. patched=[" + string.Join(", ", list) + "]; missing=[" + string.Join(", ", list2) + "]"); } [HarmonyPostfix] [HarmonyPatch("ConnectClientToPlayerObject")] private static void ConnectClientToPlayerObjectPostfix(PlayerControllerB __instance) { RunPatch("PlayerControllerB.ConnectClientToPlayerObject", delegate { DrawableSuitsPlugin.Registry?.RecordPlayerSuitSnapshot(__instance, "PlayerControllerB.ConnectClientToPlayerObject"); DrawableSuitsPlugin.Registry?.ApplyToPlayer(__instance); DrawableSuitsPlugin.Registry?.ScheduleReapplyAll("PlayerControllerB.ConnectClientToPlayerObject"); SessionSafetyGuard.Run("PlayerControllerB.ConnectClientToPlayerObject", forceLog: true); DrawableSuitsPlugin.Sync?.RequestActiveDesigns(); }); } [HarmonyPostfix] [HarmonyPatch("SpawnPlayerAnimation")] private static void SpawnPlayerAnimationPostfix(PlayerControllerB __instance) { RunPatch("PlayerControllerB.SpawnPlayerAnimation", delegate { DrawableSuitsPlugin.Registry?.RecordPlayerSuitSnapshot(__instance, "PlayerControllerB.SpawnPlayerAnimation"); DrawableSuitsPlugin.Registry?.ApplyToPlayer(__instance); DrawableSuitsPlugin.Registry?.ScheduleReapplyAll("PlayerControllerB.SpawnPlayerAnimation"); SessionSafetyGuard.Run("PlayerControllerB.SpawnPlayerAnimation", forceLog: true); }); } [HarmonyPostfix] [HarmonyPatch("SpawnDeadBody", new Type[] { typeof(int), typeof(Vector3), typeof(int), typeof(PlayerControllerB), typeof(int), typeof(Transform), typeof(Transform), typeof(Vector3) })] private static void SpawnDeadBodyPostfix(int playerId, PlayerControllerB deadPlayerController) { RunPatch("PlayerControllerB.SpawnDeadBody", delegate { DrawableSuitsPlugin.Registry?.RecordPlayerSuitSnapshot(deadPlayerController, "PlayerControllerB.SpawnDeadBody"); DeadBodyInfo val = deadPlayerController?.deadBody; if ((Object)(object)val != (Object)null) { DrawableSuitsPlugin.Registry?.ApplyToDeadBody(val, "PlayerControllerB.SpawnDeadBody:deadPlayerController.deadBody"); DrawableSuitsPlugin.Registry?.ScheduleDeadBodyReapply(val, "PlayerControllerB.SpawnDeadBody:deadPlayerController.deadBody"); DrawableSuitsDiagnostics.Info($"DeadBodySuitSpawnPatch: playerId={playerId}; exactBody={((Object)val).name}; deadPlayer={DrawableSuitsPlugin.DescribeUnityObject((Object)(object)deadPlayerController)}"); } else { DeadBodyInfo[] array = Object.FindObjectsOfType(); int num = 0; if (array != null) { foreach (DeadBodyInfo val2 in array) { if (!((Object)(object)val2 == (Object)null) && val2.playerObjectId == playerId) { DrawableSuitsPlugin.Registry?.ApplyToDeadBody(val2, "PlayerControllerB.SpawnDeadBody"); DrawableSuitsPlugin.Registry?.ScheduleDeadBodyReapply(val2, "PlayerControllerB.SpawnDeadBody"); num++; } } } DrawableSuitsDiagnostics.Info($"DeadBodySuitSpawnPatch: playerId={playerId}; matchedBodies={num}; deadPlayer={DrawableSuitsPlugin.DescribeUnityObject((Object)(object)deadPlayerController)}"); } }); } [HarmonyPrefix] [HarmonyPatch("Jump_performed")] private static bool JumpPerformedPrefix(PlayerControllerB __instance) { return AllowGameplayInput("Jump_performed", __instance); } [HarmonyPrefix] [HarmonyPatch("Interact_performed")] private static bool InteractPerformedPrefix(PlayerControllerB __instance) { return AllowGameplayInput("Interact_performed", __instance); } [HarmonyPrefix] [HarmonyPatch("QEItemInteract_performed")] private static bool QeItemInteractPerformedPrefix(PlayerControllerB __instance) { return AllowGameplayInput("QEItemInteract_performed", __instance); } [HarmonyPrefix] [HarmonyPatch("Look_performed")] private static bool LookPerformedPrefix(PlayerControllerB __instance) { return AllowGameplayInput("Look_performed", __instance); } private static bool OptionalGameplayInputPrefix(PlayerControllerB __instance, MethodBase __originalMethod) { return AllowGameplayInput(__originalMethod?.Name ?? "OptionalGameplayInput", __instance); } private static void RunPatch(string context, Action action) { try { DrawableSuitsPlugin.EnsureRuntimeReady(context); action(); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Patch failed: " + context, exception); } } private static bool AllowGameplayInput(string method, PlayerControllerB player) { if (!DrawableSuitsPlugin.IsEditorOpen) { return true; } if (Time.unscaledTime - _lastBlockedInputLogTime > 0.75f || !string.Equals(_lastBlockedInputMethod, method, StringComparison.Ordinal)) { _lastBlockedInputLogTime = Time.unscaledTime; _lastBlockedInputMethod = method; DrawableSuitsDiagnostics.Info("Blocked PlayerControllerB." + method + " while DrawableSuits editor is open. player=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)player)); } return false; } } [HarmonyPatch(typeof(DeadBodyInfo))] internal static class DeadBodyInfoPatches { [HarmonyPrefix] [HarmonyPatch("ChangeMesh")] private static void ChangeMeshPrefix(DeadBodyInfo __instance, ref Material changeMaterial) { try { DrawableSuitsPlugin.EnsureRuntimeReady("DeadBodyInfo.ChangeMesh.prefix"); if ((Object)(object)DrawableSuitsPlugin.Registry != (Object)null && DrawableSuitsPlugin.Registry.TryGetDeadBodyRuntimeMaterial(__instance, "DeadBodyInfo.ChangeMesh.prefix", out var material, out var reason)) { string text = (((Object)(object)changeMaterial != (Object)null) ? ((Object)changeMaterial).name : "null"); changeMaterial = material; DrawableSuitsDiagnostics.Info("DeadBodySuitMaterialSubstituted: context=DeadBodyInfo.ChangeMesh.prefix; previousMaterial=" + text + "; newMaterial=" + ((Object)material).name + "; reason=" + reason + "; body=" + DrawableSuitsPlugin.DescribeUnityObject((Object)(object)__instance)); } } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Patch failed: DeadBodyInfo.ChangeMesh.prefix", exception); } } [HarmonyPostfix] [HarmonyPatch("Start")] private static void StartPostfix(DeadBodyInfo __instance) { RunPatch("DeadBodyInfo.Start", __instance); } [HarmonyPostfix] [HarmonyPatch("ChangeMesh")] private static void ChangeMeshPostfix(DeadBodyInfo __instance) { RunPatch("DeadBodyInfo.ChangeMesh", __instance); } private static void RunPatch(string context, DeadBodyInfo body) { try { DrawableSuitsPlugin.EnsureRuntimeReady(context); DrawableSuitsPlugin.Registry?.ApplyToDeadBody(body, context); DrawableSuitsPlugin.Registry?.ScheduleDeadBodyReapply(body, context); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Patch failed: " + context, exception); } } } [HarmonyPatch(typeof(QuickMenuManager))] internal static class QuickMenuManagerPatches { [HarmonyPostfix] [HarmonyPatch("Start")] private static void StartPostfix(QuickMenuManager __instance) { RunPatch("QuickMenuManager.Start", delegate { PauseMenuButtonInjector.EnsureButton(__instance); }); } [HarmonyPostfix] [HarmonyPatch("OpenQuickMenu")] private static void OpenQuickMenuPostfix(QuickMenuManager __instance) { RunPatch("QuickMenuManager.OpenQuickMenu", delegate { PauseMenuButtonInjector.EnsureButton(__instance); PauseMenuButtonInjector.SelectIfNeeded(__instance); }); } private static void RunPatch(string context, Action action) { try { DrawableSuitsPlugin.EnsureRuntimeReady(context); action(); } catch (Exception exception) { DrawableSuitsDiagnostics.Exception("Patch failed: " + context, exception); } } } internal static class PauseMenuButtonInjector { private sealed class MenuRow { public GameObject GameObject { get; } public RectTransform RectTransform { get; } public Button Button { get; } public string LabelText { get; } public bool IsDrawable { get { if (!(((Object)GameObject).name == "DrawableSuitsButton")) { return Normalize(LabelText).Contains("drawablesuits"); } return true; } } public MenuRow(GameObject gameObject, RectTransform rectTransform, Button button, string labelText) { GameObject = gameObject; RectTransform = rectTransform; Button = button; LabelText = labelText ?? string.Empty; } } private const string ButtonName = "DrawableSuitsButton"; private const string ButtonText = "DrawableSuits"; private const string ButtonTextWithInlinePrefix = "> DrawableSuits"; private const string PrefixText = ">"; private const float MinimumRowSpacing = 36f; private const float FallbackRowSpacing = 92f; public static void EnsureButton(QuickMenuManager quickMenu) { //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)quickMenu == (Object)null || (Object)(object)quickMenu.mainButtonsPanel == (Object)null) { DrawableSuitsDiagnostics.Warn($"EnsureButton skipped. quickMenuNull={(Object)(object)quickMenu == (Object)null}; mainButtonsPanelNull={(Object)(object)quickMenu?.mainButtonsPanel == (Object)null}"); return; } RectTransform component = quickMenu.mainButtonsPanel.GetComponent(); if ((Object)(object)component == (Object)null) { DrawableSuitsDiagnostics.Warn("EnsureButton skipped because QuickMenuManager.mainButtonsPanel has no RectTransform."); return; } List list = CollectRows((Transform)(object)component); DrawableSuitsDiagnostics.Info($"EnsureButton started. menuOpen={quickMenu.isMenuOpen}; panel={((Object)component).name}; rowsFound={list.Count}; labels=[{DescribeRows(list)}]"); MenuRow menuRow = ChooseTemplateRow(list); DrawableSuitsDiagnostics.Info("Pause-menu DrawableSuits template selected: label=" + (menuRow?.LabelText ?? "none") + "; object=" + (((menuRow != null) ? ((Object)menuRow.GameObject).name : null) ?? "none") + "; sibling=" + (((menuRow != null) ? ((Transform)menuRow.RectTransform).GetSiblingIndex().ToString() : null) ?? "none")); GameObject val = FindExistingButton((Transform)(object)component); if ((Object)(object)val != (Object)null && menuRow != null) { DrawableSuitsDiagnostics.Info("Replacing existing DrawableSuits pause button with a fresh clone so native prefix/arrow children match the current menu template."); val.SetActive(false); Object.Destroy((Object)(object)val); val = null; } if ((Object)(object)val == (Object)null) { DrawableSuitsDiagnostics.Info("No existing DrawableSuits pause button. template=" + ((menuRow != null) ? (menuRow.LabelText + "/" + ((Object)menuRow.GameObject).name) : "none") + "."); val = ((menuRow != null) ? CloneTemplate(menuRow) : CreateFallbackButton((Transform)(object)component)); } else { DrawableSuitsDiagnostics.Info($"Reusing existing DrawableSuits pause button. active={val.activeSelf}; siblingIndex={val.transform.GetSiblingIndex()}"); } if ((Object)(object)val == (Object)null) { DrawableSuitsDiagnostics.Warn("Could not create pause-menu DrawableSuits button."); return; } RemoveDuplicateButtons((Transform)(object)component, val); ConfigureButton(val, quickMenu, menuRow); PlaceButtonAndRows(component, val); RebuildNavigation(component); RectTransform component2 = val.GetComponent(); Button component3 = val.GetComponent