using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HornetCloakColor.Client;
using HornetCloakColor.Shared;
using Microsoft.CodeAnalysis;
using UnityEngine;
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("HornetCloakColor")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.7.3.0")]
[assembly: AssemblyInformationalVersion("1.7.3+721a346d2be5cba92c78662e0edfe9b430b63075")]
[assembly: AssemblyProduct("HornetCloakColor")]
[assembly: AssemblyTitle("HornetCloakColor")]
[assembly: InternalsVisibleTo("HornetCloakColor.SSMP")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.7.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.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 BepInEx
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class BepInAutoPluginAttribute : Attribute
{
public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
{
}
}
}
namespace BepInEx.Preloader.Core.Patching
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[Conditional("CodeGeneration")]
internal sealed class PatcherAutoPluginAttribute : Attribute
{
public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
{
}
}
}
namespace HornetCloakColor
{
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("hornet.cloak.color", "HornetCloakColor", "1.7.3")]
public class HornetCloakColorPlugin : BaseUnityPlugin
{
public const string ModVersion = "1.7.3";
public const string Id = "hornet.cloak.color";
internal static HornetCloakColorPlugin? Instance { get; private set; }
internal static ManualLogSource? LogSource { get; private set; }
internal CloakColorConfig ColorConfig { get; private set; }
public static string Name => "HornetCloakColor";
public static string Version => "1.7.3";
private void Awake()
{
//IL_0088: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Expected O, but got Unknown
Instance = this;
LogSource = ((BaseUnityPlugin)this).Logger;
CloakPaletteConfig.Load();
CloakSceneScanner.EnsureCreated();
ColorConfig = new CloakColorConfig(((BaseUnityPlugin)this).Config);
CloakColorApplier.SetLocalSceneColor(ColorConfig.CurrentColor);
MapMaskHarmonyPatcher.Apply();
if (SSMPBridge.TryRegister())
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"SSMP detected — multiplayer cloak sync enabled.");
}
else
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"SSMP not detected — running solo (your cloak only).");
}
ColorConfig.ColorChanged += OnConfigColorChanged;
HeroController.OnHeroInstanceSet += new HeroSetDelegate(OnHeroInstanceSet);
((BaseUnityPlugin)this).Logger.LogInfo((object)(Name + " v1.7.3 loaded."));
}
private void OnHeroInstanceSet(HeroController hero)
{
CloakColor currentColor = ColorConfig.CurrentColor;
CloakColorApplier.Apply(((Component)hero).gameObject, currentColor);
CloakColorApplier.SetLocalSceneColor(currentColor);
SSMPBridge.NotifyLocalColorChanged(currentColor);
}
private void OnConfigColorChanged(CloakColor color)
{
if (CloakPaletteConfig.DebugLogging)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)$"Cloak color changed to {color}");
}
if ((Object)(object)HeroController.SilentInstance != (Object)null)
{
CloakColorApplier.Apply(((Component)HeroController.SilentInstance).gameObject, color);
}
CloakColorApplier.SetLocalSceneColor(color);
SSMPBridge.NotifyLocalColorChanged(color);
LocalMapMaskTint.Refresh(GameManager.instance?.gameMap, color);
MapMaskTint.BroadcastLocalColor(color);
}
}
internal static class SSMPBridge
{
public const string SSMPGuid = "ssmp";
private static bool _registered;
private static Action<CloakColor>? _notifyColorChanged;
private static Func<ushort, CloakColor>? _getRemoteMapColor;
public static bool IsAvailable => Chainloader.PluginInfos.ContainsKey("ssmp");
public static bool IsRegistered => _registered;
public static bool TryRegister()
{
if (_registered)
{
return true;
}
if (!IsAvailable)
{
return false;
}
try
{
if (!TryBindSatellite())
{
return false;
}
_registered = true;
return true;
}
catch (Exception ex)
{
Log.Warn("SSMP detected but addon registration failed: " + ex.Message + ". Multiplayer cloak sync disabled; local recolor still works.");
return false;
}
}
public static void NotifyLocalColorChanged(CloakColor color)
{
if (_registered)
{
_notifyColorChanged?.Invoke(color);
}
}
public static CloakColor GetRemoteMapColorOrDefault(ushort playerId)
{
return _getRemoteMapColor?.Invoke(playerId) ?? CloakColor.Default;
}
private static bool TryBindSatellite()
{
string directoryName = Path.GetDirectoryName(typeof(HornetCloakColorPlugin).Assembly.Location);
if (string.IsNullOrEmpty(directoryName))
{
Log.Warn("Could not resolve plugin directory; cannot load HornetCloakColor.SSMP.dll.");
return false;
}
string text = Path.Combine(directoryName, "HornetCloakColor.SSMP.dll");
if (!File.Exists(text))
{
Log.Warn("SSMP is loaded but HornetCloakColor.SSMP.dll was not found next to HornetCloakColor.dll. Reinstall the mod so both DLLs are in the same folder.");
return false;
}
Type type = Assembly.LoadFrom(text).GetType("HornetCloakColor.SSMPIntegration.SatelliteEntry");
if (type == null)
{
Log.Warn("HornetCloakColor.SSMP.dll is missing the integration entry type.");
return false;
}
type.GetMethod("Register", BindingFlags.Static | BindingFlags.Public)?.Invoke(null, null);
MethodInfo method = type.GetMethod("NotifyLocalColorChanged", BindingFlags.Static | BindingFlags.Public);
if (method != null)
{
_notifyColorChanged = (Action<CloakColor>)Delegate.CreateDelegate(typeof(Action<CloakColor>), method);
}
MethodInfo method2 = type.GetMethod("GetRemoteMapColorOrDefault", BindingFlags.Static | BindingFlags.Public);
if (method2 != null)
{
_getRemoteMapColor = (Func<ushort, CloakColor>)Delegate.CreateDelegate(typeof(Func<ushort, CloakColor>), method2);
}
return true;
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "hornet.cloak.color";
public const string PLUGIN_NAME = "HornetCloakColor";
public const string PLUGIN_VERSION = "1.7.3";
}
}
namespace HornetCloakColor.Shared
{
public readonly struct CloakColor : IEquatable<CloakColor>
{
public byte R { get; }
public byte G { get; }
public byte B { get; }
public static CloakColor Default => new CloakColor(byte.MaxValue, byte.MaxValue, byte.MaxValue);
public CloakColor(byte r, byte g, byte b)
{
R = r;
G = g;
B = b;
}
public Color ToUnityColor()
{
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
return new Color((float)(int)R / 255f, (float)(int)G / 255f, (float)(int)B / 255f, 1f);
}
public void ToHSV(out float h, out float s, out float v)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
Color.RGBToHSV(ToUnityColor(), ref h, ref s, ref v);
}
public static CloakColor FromUnityColor(Color color)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
return new CloakColor((byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255));
}
public static bool TryParse(string value, out CloakColor color)
{
color = Default;
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
string text = value.Trim();
if (text.Contains(","))
{
string[] array = text.Split(',');
if (array.Length != 3)
{
return false;
}
if (!byte.TryParse(array[0].Trim(), out var result))
{
return false;
}
if (!byte.TryParse(array[1].Trim(), out var result2))
{
return false;
}
if (!byte.TryParse(array[2].Trim(), out var result3))
{
return false;
}
color = new CloakColor(result, result2, result3);
return true;
}
string text2 = (text.StartsWith("#") ? text.Substring(1) : text);
if (text2.Length != 6)
{
return false;
}
try
{
byte r = Convert.ToByte(text2.Substring(0, 2), 16);
byte g = Convert.ToByte(text2.Substring(2, 2), 16);
byte b = Convert.ToByte(text2.Substring(4, 2), 16);
color = new CloakColor(r, g, b);
return true;
}
catch
{
return false;
}
}
public override string ToString()
{
return $"#{R:X2}{G:X2}{B:X2}";
}
public bool Equals(CloakColor other)
{
if (R == other.R && G == other.G)
{
return B == other.B;
}
return false;
}
public override bool Equals(object obj)
{
if (obj is CloakColor other)
{
return Equals(other);
}
return false;
}
public override int GetHashCode()
{
return (R << 16) | (G << 8) | B;
}
public static bool operator ==(CloakColor a, CloakColor b)
{
return a.Equals(b);
}
public static bool operator !=(CloakColor a, CloakColor b)
{
return !a.Equals(b);
}
}
internal static class Log
{
private static ManualLogSource? _fallback;
private static ManualLogSource Source => HornetCloakColorPlugin.LogSource ?? _fallback ?? (_fallback = Logger.CreateLogSource("HornetCloakColor"));
public static void Info(string msg)
{
Source.LogInfo((object)msg);
}
public static void Warn(string msg)
{
Source.LogWarning((object)msg);
}
public static void Error(string msg)
{
Source.LogError((object)msg);
}
public static void Debug(string msg)
{
Source.LogDebug((object)msg);
}
}
}
namespace HornetCloakColor.Client
{
internal static class CloakColorApplier
{
public static void Apply(GameObject? playerObject, CloakColor color)
{
if (!((Object)(object)playerObject == (Object)null))
{
bool useCloakShader = (Object)(object)CloakShaderManager.Shader != (Object)null;
CloakRecolor.AttachOrUpdate(playerObject, color, useCloakShader);
}
}
public static void SetLocalSceneColor(CloakColor color)
{
CloakSceneScanner.SetColor(color);
}
}
internal class CloakColorConfig
{
public enum Preset
{
Custom,
Default,
Crimson,
Scarlet,
Amber,
Gold,
Emerald,
Teal,
Azure,
Royal,
Violet,
Magenta,
Obsidian,
Ivory
}
private static readonly CloakColor DefaultCustom = new CloakColor(200, 60, 60);
public ConfigEntry<Preset> PresetChoice { get; }
public ConfigEntry<string> CustomHex { get; }
public CloakColor CurrentColor => Resolve(PresetChoice.Value, CustomHex.Value);
public event Action<CloakColor>? ColorChanged;
public CloakColorConfig(ConfigFile config)
{
PresetChoice = config.Bind<Preset>("Appearance", "Cloak Color Preset", Preset.Default, "Choose 'Custom' for your own hex.");
CustomHex = config.Bind<string>("Appearance", "Custom Cloak Color", DefaultCustom.ToString(), "Custom cloak color used when preset is set to 'Custom'. Accepts #RRGGBB, RRGGBB, or 'r,g,b' (0-255 each).");
PresetChoice.SettingChanged += delegate
{
this.ColorChanged?.Invoke(CurrentColor);
};
CustomHex.SettingChanged += delegate
{
this.ColorChanged?.Invoke(CurrentColor);
};
}
private static CloakColor Resolve(Preset preset, string customHex)
{
CloakColor color;
return preset switch
{
Preset.Default => CloakColor.Default,
Preset.Crimson => new CloakColor(156, 36, 48),
Preset.Scarlet => new CloakColor(220, 56, 72),
Preset.Amber => new CloakColor(230, 140, 40),
Preset.Gold => new CloakColor(232, 190, 64),
Preset.Emerald => new CloakColor(56, 170, 90),
Preset.Teal => new CloakColor(60, 180, 180),
Preset.Azure => new CloakColor(64, 148, 230),
Preset.Royal => new CloakColor(72, 92, 210),
Preset.Violet => new CloakColor(140, 80, 210),
Preset.Magenta => new CloakColor(220, 80, 180),
Preset.Obsidian => new CloakColor(40, 40, 55),
Preset.Ivory => new CloakColor(240, 230, 205),
Preset.Custom => CloakColor.TryParse(customHex, out color) ? color : CloakColor.Default,
_ => CloakColor.Default,
};
}
}
internal static class CloakMaterialApplier
{
public static void Apply(MeshRenderer renderer, tk2dSprite? sprite, CloakColor color, bool useCloakShader, Dictionary<MeshRenderer, Shader> originalShaderByRenderer)
{
if ((Object)(object)renderer == (Object)null)
{
return;
}
Material material = ((Renderer)renderer).material;
if (!((Object)(object)material == (Object)null))
{
if (sprite == null)
{
sprite = ((Component)renderer).GetComponent<tk2dSprite>();
}
if (useCloakShader && (Object)(object)CloakShaderManager.Shader != (Object)null)
{
EnsureCloakShader(renderer, material, originalShaderByRenderer);
ApplyShaderProperties(material, sprite, color);
}
else
{
RestoreOriginalShader(renderer, material, originalShaderByRenderer);
ApplyVertexTint(material, sprite, color);
}
}
}
private static void EnsureCloakShader(MeshRenderer renderer, Material mat, Dictionary<MeshRenderer, Shader> map)
{
Shader shader = CloakShaderManager.Shader;
if (!((Object)(object)mat.shader == (Object)(object)shader))
{
if (!map.ContainsKey(renderer))
{
map[renderer] = mat.shader;
}
Texture mainTexture = mat.mainTexture;
mat.shader = shader;
if ((Object)(object)mainTexture != (Object)null)
{
mat.mainTexture = mainTexture;
}
}
}
private static void RestoreOriginalShader(MeshRenderer renderer, Material mat, Dictionary<MeshRenderer, Shader> map)
{
if (map.TryGetValue(renderer, out Shader value) && !((Object)(object)value == (Object)null) && !((Object)(object)mat.shader == (Object)(object)value))
{
Texture mainTexture = mat.mainTexture;
mat.shader = value;
if ((Object)(object)mainTexture != (Object)null)
{
mat.mainTexture = mainTexture;
}
}
}
private static void ApplyShaderProperties(Material mat, tk2dSprite? sprite, CloakColor color)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)sprite != (Object)null && ((tk2dBaseSprite)sprite).color != Color.white)
{
((tk2dBaseSprite)sprite).color = Color.white;
}
mat.SetVectorArray(CloakShaderManager.SrcColorsId, CloakPaletteConfig.SrcColors);
mat.SetVectorArray(CloakShaderManager.AvoidColorsId, CloakPaletteConfig.AvoidColors);
mat.SetFloat(CloakShaderManager.MatchRadiusId, CloakPaletteConfig.MatchRadius);
mat.SetFloat(CloakShaderManager.AvoidMatchRadiusId, CloakPaletteConfig.AvoidMatchRadius);
if (color.Equals(CloakColor.Default))
{
mat.SetFloat(CloakShaderManager.StrengthId, 0f);
return;
}
color.ToHSV(out var h, out var s, out var v);
mat.SetFloat(CloakShaderManager.TargetHueId, h);
mat.SetFloat(CloakShaderManager.TargetSatId, (s <= 0.001f) ? 0f : 1f);
mat.SetFloat(CloakShaderManager.TargetValId, Mathf.Lerp(0.6f, 1.4f, v));
mat.SetFloat(CloakShaderManager.StrengthId, 1f);
}
private static void ApplyVertexTint(Material mat, tk2dSprite? sprite, CloakColor color)
{
//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_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
Color color2 = color.ToUnityColor();
if ((Object)(object)sprite != (Object)null)
{
((tk2dBaseSprite)sprite).color = color2;
}
mat.color = color2;
}
public static void PruneDestroyed(Dictionary<MeshRenderer, Shader> map)
{
if (map.Count == 0)
{
return;
}
List<MeshRenderer> list = null;
foreach (MeshRenderer key in map.Keys)
{
if (!Object.op_Implicit((Object)(object)key))
{
(list ?? (list = new List<MeshRenderer>())).Add(key);
}
}
if (list == null)
{
return;
}
foreach (MeshRenderer item in list)
{
map.Remove(item);
}
}
}
internal static class CloakPaletteConfig
{
public static Vector4[] SrcColors { get; private set; } = (Vector4[])(object)new Vector4[16];
public static int SrcCount { get; private set; }
public static Vector4[] AvoidColors { get; private set; } = (Vector4[])(object)new Vector4[16];
public static int AvoidCount { get; private set; }
public static float MatchRadius { get; private set; }
public static float AvoidMatchRadius { get; private set; }
public static bool DebugLogging { get; private set; }
public static bool MapIconDebugLogging { get; private set; }
public static bool LogMapIconDiagnostics
{
get
{
if (!DebugLogging)
{
return MapIconDebugLogging;
}
return true;
}
}
public static bool PerfDiagnostics { get; private set; }
public static string[] SceneScanTextureContains { get; private set; } = Array.Empty<string>();
public static string[] SceneScanPathContains { get; private set; } = Array.Empty<string>();
public static int SceneScanIntervalFrames { get; private set; }
public static string[] SceneScanRegistryDenyPathContains { get; private set; } = Array.Empty<string>();
public static int HeroMeshRescanIntervalFrames { get; private set; }
public static bool DumpDiscoveredTextures { get; private set; }
public static void Load()
{
ApplyDefaults();
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (string.IsNullOrEmpty(directoryName))
{
return;
}
string text = Path.Combine(directoryName, "cloak_palette.json");
if (!File.Exists(text))
{
return;
}
try
{
if (TryApplyPaletteJson(File.ReadAllText(text)))
{
Log.Info($"Loaded cloak palette from {text} ({SrcCount} cloak / {AvoidCount} avoid reference color(s)).");
if (MapIconDebugLogging)
{
Log.Info("[MapIcon] mapIconDebugLogging is true — tracing map/compass sync; grep log for \"[MapIcon]\".");
}
if (PerfDiagnostics)
{
Log.Info("[HCC/Perf] perfDiagnostics is true — grep BepInEx log for \"[HCC/Perf]\" (≈2s rolling window).");
}
}
else
{
Log.Warn("cloak_palette.json was not valid; using built-in defaults from the mod DLL.");
}
}
catch (Exception ex)
{
Log.Warn("Could not read cloak_palette.json: " + ex.Message + ". Using defaults.");
}
}
private static void ApplyDefaults()
{
SetSources(ParseHexColors("#79404b", "#d4b8b8", "#7a414c", "#b2808c", "#351c20", "#501f3b", "#562d35", "#3b162b", "#ec7f92", "#955a70", "#efbdd1", "#ae5c6c", "#994d5c", "#a7807b", "#be485e", "#592439"));
SetAvoidSources(ParseHexColors("#ffffff", "#000000", "#c7cbb5", "#16162c", "#808080", "#5b4133", "#231914", "#644662", "#282c34", "#8a6187"));
MatchRadius = 0.135f;
AvoidMatchRadius = 0.12f;
DebugLogging = false;
MapIconDebugLogging = false;
SceneScanTextureContains = new string[1] { "hornet" };
SceneScanPathContains = new string[1] { "hornet" };
SceneScanIntervalFrames = 3;
SceneScanRegistryDenyPathContains = new string[9] { "SpriteCache", "EnemyHitEffects", "Slash Impact", "Hero Dash Puff", "Land Effect", "/HudCamera/", "Thunk", "Warrior Rage", "Barbed Wire" };
HeroMeshRescanIntervalFrames = 30;
DumpDiscoveredTextures = false;
PerfDiagnostics = false;
}
private static bool TryApplyPaletteJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return false;
}
string text = json.TrimStart();
if (!text.StartsWith("{", StringComparison.Ordinal))
{
return false;
}
List<CloakColor> list = ExtractHexArray(text, "cloakColors");
if (list.Count > 0)
{
SetSources(list);
}
if (TryExtractFloat(text, "matchRadius", out var value) && value > 0f && value <= 1f)
{
MatchRadius = value;
}
SetAvoidSources(ExtractHexArray(text, "avoidColors"));
if (TryExtractFloat(text, "avoidMatchRadius", out var value2) && value2 > 0f && value2 <= 1f)
{
AvoidMatchRadius = value2;
}
else
{
AvoidMatchRadius = MatchRadius;
}
if (TryExtractBool(text, "debugLogging", out var value3))
{
DebugLogging = value3;
}
if (TryExtractBool(text, "mapIconDebugLogging", out var value4))
{
MapIconDebugLogging = value4;
}
List<string> list2 = ExtractStringArray(text, "sceneScanTextureContains");
if (list2.Count > 0)
{
SceneScanTextureContains = list2.ToArray();
}
List<string> list3 = ExtractStringArray(text, "sceneScanPathContains");
if (list3.Count > 0)
{
SceneScanPathContains = list3.ToArray();
}
if (TryExtractInt(text, "sceneScanIntervalFrames", out var value5) && value5 > 0 && value5 <= 240)
{
SceneScanIntervalFrames = value5;
}
List<string> list4 = ExtractStringArray(text, "sceneScanRegistryDenyPathContains");
if (list4.Count > 0)
{
SceneScanRegistryDenyPathContains = list4.ToArray();
}
if (TryExtractInt(text, "heroMeshRescanIntervalFrames", out var value6) && value6 > 0 && value6 <= 600)
{
HeroMeshRescanIntervalFrames = value6;
}
if (TryExtractBool(text, "dumpDiscoveredTextures", out var value7))
{
DumpDiscoveredTextures = value7;
}
if (TryExtractBool(text, "perfDiagnostics", out var value8))
{
PerfDiagnostics = value8;
}
return true;
}
private static List<string> ExtractStringArray(string json, string key)
{
List<string> list = new List<string>();
string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*\\[(?<arr>[^\\]]*)\\]";
Match match = Regex.Match(json, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success)
{
return list;
}
foreach (Match item in Regex.Matches(match.Groups["arr"].Value, "\"(?<s>[^\"]*)\"", RegexOptions.CultureInvariant))
{
string value = item.Groups["s"].Value;
if (!string.IsNullOrWhiteSpace(value))
{
list.Add(value);
}
}
return list;
}
private static bool TryExtractInt(string json, string key, out int value)
{
value = 0;
string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*(?<n>-?[0-9]+)";
Match match = Regex.Match(json, pattern, RegexOptions.CultureInvariant);
if (!match.Success)
{
return false;
}
return int.TryParse(match.Groups["n"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
}
private static CloakColor[] ParseHexColors(params string[] hexes)
{
List<CloakColor> list = new List<CloakColor>(hexes.Length);
for (int i = 0; i < hexes.Length; i++)
{
if (CloakColor.TryParse(hexes[i], out var color))
{
list.Add(color);
}
}
return list.ToArray();
}
private static void SetSources(IList<CloakColor> colors)
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
for (int i = 0; i < SrcColors.Length; i++)
{
SrcColors[i] = new Vector4(10f, 10f, 10f, 1f);
}
int num = Math.Min(colors.Count, SrcColors.Length);
for (int j = 0; j < num; j++)
{
Color val = colors[j].ToUnityColor();
SrcColors[j] = new Vector4(val.r, val.g, val.b, 1f);
}
SrcCount = num;
if (colors.Count > SrcColors.Length)
{
Log.Warn($"cloak_palette.json: only the first {SrcColors.Length} cloakColors are used " + $"(found {colors.Count}). Increase MaxCloakColors in the shader to lift this.");
}
}
private static void SetAvoidSources(IList<CloakColor> colors)
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0023: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
for (int i = 0; i < AvoidColors.Length; i++)
{
AvoidColors[i] = new Vector4(10f, 10f, 10f, 1f);
}
int num = Math.Min(colors.Count, AvoidColors.Length);
for (int j = 0; j < num; j++)
{
Color val = colors[j].ToUnityColor();
AvoidColors[j] = new Vector4(val.r, val.g, val.b, 1f);
}
AvoidCount = num;
if (colors.Count > AvoidColors.Length)
{
Log.Warn($"cloak_palette.json: only the first {AvoidColors.Length} avoidColors are used " + $"(found {colors.Count}).");
}
}
private static List<CloakColor> ExtractHexArray(string json, string key)
{
List<CloakColor> list = new List<CloakColor>();
string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*\\[(?<arr>[^\\]]*)\\]";
Match match = Regex.Match(json, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success)
{
return list;
}
foreach (Match item in Regex.Matches(match.Groups["arr"].Value, "\"(?<h>#?[0-9a-fA-F]{6})\"", RegexOptions.CultureInvariant))
{
string text = item.Groups["h"].Value;
if (!text.StartsWith("#", StringComparison.Ordinal))
{
text = "#" + text;
}
if (CloakColor.TryParse(text, out var color))
{
list.Add(color);
}
}
return list;
}
private static bool TryExtractBool(string json, string key, out bool value)
{
value = false;
string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*(?<v>true|false)";
Match match = Regex.Match(json, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success)
{
return false;
}
value = match.Groups["v"].Value.Equals("true", StringComparison.OrdinalIgnoreCase);
return true;
}
private static bool TryExtractFloat(string json, string key, out float value)
{
value = 0f;
string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*(?<n>[0-9]+(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?)";
Match match = Regex.Match(json, pattern, RegexOptions.CultureInvariant);
if (!match.Success)
{
return false;
}
return float.TryParse(match.Groups["n"].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
}
}
[DefaultExecutionOrder(10000)]
[DisallowMultipleComponent]
internal class CloakRecolor : MonoBehaviour
{
private readonly Dictionary<MeshRenderer, Shader> _originalShaderByRenderer = new Dictionary<MeshRenderer, Shader>();
private readonly List<MeshRenderer> _meshCache = new List<MeshRenderer>();
private int _meshRescanCountdown;
private bool _meshCacheInvalid = true;
public CloakColor Color { get; private set; } = CloakColor.Default;
public bool UseCloakShader { get; private set; } = true;
private void OnEnable()
{
_meshCacheInvalid = true;
}
private void LateUpdate()
{
CloakMaterialApplier.PruneDestroyed(_originalShaderByRenderer);
MaybeRefreshMeshCache();
if (PerfDiagnostics.Enabled)
{
Stopwatch stopwatch = Stopwatch.StartNew();
ApplyToCachedMeshRenderersCore();
stopwatch.Stop();
PerfDiagnostics.RecordRecolorLateUpdate(((Object)((Component)this).gameObject).name, _meshCache.Count, stopwatch.Elapsed.TotalMilliseconds);
}
else
{
ApplyToCachedMeshRenderersCore();
}
}
public void Configure(CloakColor color, bool useCloakShader)
{
Color = color;
UseCloakShader = useCloakShader;
_meshCacheInvalid = true;
RebuildMeshCache();
ApplyToCachedMeshRenderersCore();
}
public void SetColor(CloakColor color)
{
Color = color;
ApplyToCachedMeshRenderersCore();
}
private void MaybeRefreshMeshCache()
{
if (_meshCacheInvalid)
{
RebuildMeshCache();
}
else if (--_meshRescanCountdown <= 0)
{
RebuildMeshCache();
}
}
private void RebuildMeshCache()
{
_meshCacheInvalid = false;
_meshRescanCountdown = Mathf.Max(1, CloakPaletteConfig.HeroMeshRescanIntervalFrames);
_meshCache.Clear();
MeshRenderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<MeshRenderer>(true);
if (componentsInChildren == null || componentsInChildren.Length == 0)
{
return;
}
MeshRenderer[] array = componentsInChildren;
foreach (MeshRenderer val in array)
{
if (!((Object)(object)val == (Object)null) && !IsUnderSsmpUsernameObject(((Component)val).transform))
{
_meshCache.Add(val);
}
}
}
private void ApplyToCachedMeshRenderersCore()
{
foreach (MeshRenderer item in _meshCache)
{
if ((Object)(object)item == (Object)null)
{
_meshCacheInvalid = true;
}
else
{
if (IsUnderSsmpUsernameObject(((Component)item).transform))
{
continue;
}
Material sharedMaterial = ((Renderer)item).sharedMaterial;
if ((Object)(object)sharedMaterial != (Object)null)
{
Texture mainTexture = sharedMaterial.mainTexture;
if (HornetTextureRegistry.Register(mainTexture))
{
TextureDumper.TryDump(mainTexture, "hero");
}
}
CloakMaterialApplier.Apply(item, null, Color, UseCloakShader, _originalShaderByRenderer);
}
}
}
private static bool IsUnderSsmpUsernameObject(Transform t)
{
Transform val = t;
while ((Object)(object)val != (Object)null)
{
if (((Object)val).name == "Username")
{
return true;
}
val = val.parent;
}
return false;
}
public static CloakRecolor? AttachOrUpdate(GameObject? playerObject, CloakColor color, bool useCloakShader)
{
if ((Object)(object)playerObject == (Object)null)
{
return null;
}
CloakRecolor cloakRecolor = playerObject.GetComponent<CloakRecolor>();
if ((Object)(object)cloakRecolor == (Object)null)
{
cloakRecolor = playerObject.AddComponent<CloakRecolor>();
}
cloakRecolor.Configure(color, useCloakShader);
return cloakRecolor;
}
}
[DefaultExecutionOrder(10000)]
[DisallowMultipleComponent]
internal class CloakSceneScanner : MonoBehaviour
{
private CloakColor _color = CloakColor.Default;
private readonly Dictionary<MeshRenderer, Shader> _originalShaderByRenderer = new Dictionary<MeshRenderer, Shader>();
private readonly HashSet<int> _loggedTextureIds = new HashSet<int>();
private readonly HashSet<int> _loggedRendererIds = new HashSet<int>();
private int _frameCounter;
public static CloakSceneScanner? Instance { get; private set; }
public static void EnsureCreated()
{
//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_001e: Expected O, but got Unknown
if (!((Object)(object)Instance != (Object)null))
{
GameObject val = new GameObject("HornetCloakColorSceneScanner");
Object.DontDestroyOnLoad((Object)val);
Instance = val.AddComponent<CloakSceneScanner>();
}
}
public static void SetColor(CloakColor color)
{
EnsureCreated();
Instance._color = color;
}
private void LateUpdate()
{
int num = Math.Max(1, CloakPaletteConfig.SceneScanIntervalFrames);
if (_frameCounter++ % num != 0)
{
return;
}
CloakMaterialApplier.PruneDestroyed(_originalShaderByRenderer);
string[] sceneScanTextureContains = CloakPaletteConfig.SceneScanTextureContains;
string[] sceneScanPathContains = CloakPaletteConfig.SceneScanPathContains;
tk2dSprite[] array;
double findObjectsMs;
if (PerfDiagnostics.Enabled)
{
Stopwatch stopwatch = Stopwatch.StartNew();
array = Object.FindObjectsByType<tk2dSprite>((FindObjectsSortMode)0);
stopwatch.Stop();
findObjectsMs = stopwatch.Elapsed.TotalMilliseconds;
}
else
{
array = Object.FindObjectsByType<tk2dSprite>((FindObjectsSortMode)0);
findObjectsMs = 0.0;
}
if (array == null || array.Length == 0)
{
if (PerfDiagnostics.Enabled)
{
PerfDiagnostics.RecordSceneScan(0, 0, findObjectsMs, 0.0);
}
}
else if (PerfDiagnostics.Enabled)
{
Stopwatch stopwatch2 = Stopwatch.StartNew();
int appliedCount = RunScanLoop(array, sceneScanTextureContains, sceneScanPathContains);
stopwatch2.Stop();
double totalMilliseconds = stopwatch2.Elapsed.TotalMilliseconds;
PerfDiagnostics.RecordSceneScan(array.Length, appliedCount, findObjectsMs, totalMilliseconds);
}
else
{
RunScanLoop(array, sceneScanTextureContains, sceneScanPathContains);
}
}
private int RunScanLoop(tk2dSprite[] sprites, string[]? nameFilters, string[]? pathFilters)
{
int num = 0;
foreach (tk2dSprite val in sprites)
{
if ((Object)(object)val == (Object)null)
{
continue;
}
MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>();
if ((Object)(object)component == (Object)null)
{
continue;
}
Material sharedMaterial = ((Renderer)component).sharedMaterial;
if ((Object)(object)sharedMaterial == (Object)null)
{
continue;
}
Texture mainTexture = sharedMaterial.mainTexture;
if ((Object)(object)mainTexture == (Object)null || (Object)(object)((Component)component).GetComponentInParent<CloakRecolor>() != (Object)null || IsCompassIcon(((Component)component).transform))
{
continue;
}
string name = ((Object)mainTexture).name;
string text = null;
bool flag;
if ((flag = HornetTextureRegistry.Contains(mainTexture)) && CloakPaletteConfig.SceneScanRegistryDenyPathContains.Length != 0)
{
text = GetPath(((Component)component).transform);
if (MatchesAnyFilter(text, CloakPaletteConfig.SceneScanRegistryDenyPathContains))
{
flag = false;
}
}
bool flag2 = !flag && nameFilters != null && nameFilters.Length != 0 && MatchesAnyFilter(name, nameFilters);
bool flag3 = false;
if (!flag && !flag2 && pathFilters != null && pathFilters.Length != 0)
{
text = GetPath(((Component)component).transform);
flag3 = MatchesAnyFilter(text, pathFilters);
}
if (!flag && !flag2 && !flag3)
{
if (CloakPaletteConfig.DebugLogging && _loggedTextureIds.Add(((Object)mainTexture).GetInstanceID()))
{
if (text == null)
{
text = GetPath(((Component)component).transform);
}
Log.Info("[Scanner] Ignored texture (no match): " + name + " " + $"(id={((Object)mainTexture).GetInstanceID()}) on '{text}'");
}
continue;
}
if (CloakPaletteConfig.DebugLogging && _loggedRendererIds.Add(((Object)component).GetInstanceID()))
{
if (text == null)
{
text = GetPath(((Component)component).transform);
}
string arg = (flag ? "registry" : (flag2 ? "name-filter" : "path-filter"));
Log.Info("[Scanner] Tinting orphan renderer '" + text + "' " + $"(tex={name}, id={((Object)mainTexture).GetInstanceID()}, via={arg})");
}
if ((flag2 || flag3) && HornetTextureRegistry.Register(mainTexture))
{
TextureDumper.TryDump(mainTexture, flag2 ? "scanner-name" : "scanner-path");
}
CloakMaterialApplier.Apply(component, val, _color, useCloakShader: true, _originalShaderByRenderer);
num++;
}
return num;
}
private static bool IsCompassIcon(Transform t)
{
if ((Object)(object)t == (Object)null)
{
return false;
}
return ((Object)t).name.StartsWith("Compass Icon", StringComparison.Ordinal);
}
private static bool MatchesAnyFilter(string name, string[] filters)
{
foreach (string value in filters)
{
if (!string.IsNullOrEmpty(value) && name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
return false;
}
private static string GetPath(Transform t)
{
if ((Object)(object)t == (Object)null)
{
return "(null)";
}
StringBuilder stringBuilder = new StringBuilder();
while ((Object)(object)t != (Object)null)
{
if (stringBuilder.Length > 0)
{
stringBuilder.Insert(0, '/');
}
stringBuilder.Insert(0, ((Object)t).name);
t = t.parent;
}
return stringBuilder.ToString();
}
}
internal static class CloakShaderManager
{
private const string ShaderName = "HornetCloakColor/CloakHueShift";
private const string ShaderAssetName = "CloakHueShift";
private const string ResourceName = "HornetCloakColor.Resources.cloakshader.bundle";
private static bool _attemptedLoad;
private static AssetBundle? _bundle;
private static Shader? _shader;
public const int MaxCloakColors = 16;
public const int MaxAvoidColors = 16;
public static readonly int TargetHueId = Shader.PropertyToID("_TargetHue");
public static readonly int TargetSatId = Shader.PropertyToID("_TargetSat");
public static readonly int TargetValId = Shader.PropertyToID("_TargetVal");
public static readonly int SrcColorsId = Shader.PropertyToID("_SrcColors");
public static readonly int AvoidColorsId = Shader.PropertyToID("_AvoidColors");
public static readonly int MatchRadiusId = Shader.PropertyToID("_MatchRadius");
public static readonly int AvoidMatchRadiusId = Shader.PropertyToID("_AvoidMatchRadius");
public static readonly int StrengthId = Shader.PropertyToID("_Strength");
public static Shader? Shader
{
get
{
if (_attemptedLoad)
{
return _shader;
}
_attemptedLoad = true;
_shader = LoadShader();
return _shader;
}
}
public static bool BundleMissing
{
get
{
if (_attemptedLoad)
{
return (Object)(object)_shader == (Object)null;
}
return false;
}
}
private static Shader? LoadShader()
{
using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("HornetCloakColor.Resources.cloakshader.bundle");
if (stream == null)
{
Log.Warn("Cloak shader bundle not embedded (HornetCloakColor.Resources.cloakshader.bundle). Cloak-only recolor disabled; falling back to whole-character tint.");
return null;
}
using MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
memoryStream.Position = 0L;
try
{
_bundle = AssetBundle.LoadFromMemory(memoryStream.ToArray());
}
catch (Exception arg)
{
Log.Error($"Failed to load cloak shader bundle: {arg}");
return null;
}
if ((Object)(object)_bundle == (Object)null)
{
Log.Warn("AssetBundle.LoadFromMemory returned null for the cloak shader bundle.");
return null;
}
Shader val = TryLoadShaderFromBundle(_bundle);
if ((Object)(object)val == (Object)null)
{
Log.Warn("Cloak shader not found in embedded bundle (expected asset name 'CloakHueShift' or runtime name 'HornetCloakColor/CloakHueShift'). Rebuild the bundle from Shaders/CloakHueShift.shader.");
return null;
}
Log.Info("Loaded cloak shader '" + ((Object)val).name + "' from embedded bundle.");
return val;
}
private static Shader? TryLoadShaderFromBundle(AssetBundle bundle)
{
Shader val = bundle.LoadAsset<Shader>("CloakHueShift");
if ((Object)(object)val != (Object)null)
{
return val;
}
val = bundle.LoadAsset<Shader>("HornetCloakColor/CloakHueShift");
if ((Object)(object)val != (Object)null)
{
return val;
}
Shader[] array = bundle.LoadAllAssets<Shader>();
if (array == null || array.Length == 0)
{
return null;
}
Shader[] array2 = array;
foreach (Shader val2 in array2)
{
if ((Object)(object)val2 != (Object)null && ((Object)val2).name == "HornetCloakColor/CloakHueShift")
{
return val2;
}
}
if (array.Length != 1)
{
return null;
}
return array[0];
}
}
internal static class HornetTextureRegistry
{
private static readonly HashSet<int> _ids = new HashSet<int>();
private static readonly HashSet<int> _logged = new HashSet<int>();
public static int Count => _ids.Count;
public static bool Register(Texture? tex)
{
if ((Object)(object)tex == (Object)null)
{
return false;
}
int instanceID = ((Object)tex).GetInstanceID();
if (!_ids.Add(instanceID))
{
return false;
}
if (CloakPaletteConfig.DebugLogging && _logged.Add(instanceID))
{
Log.Info($"[Registry] Registered Hornet texture '{((Object)tex).name}' (id={instanceID}); total={_ids.Count}.");
}
return true;
}
public static bool Contains(Texture? tex)
{
if ((Object)(object)tex == (Object)null)
{
return false;
}
return _ids.Contains(((Object)tex).GetInstanceID());
}
}
internal static class MapMaskHarmonyPatcher
{
private const string HarmonyId = "hornet.cloak.color.mapmask";
private static bool _applied;
internal static void Apply()
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Expected O, but got Unknown
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Expected O, but got Unknown
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: Expected O, but got Unknown
//IL_0101: Unknown result type (might be due to invalid IL or missing references)
//IL_010e: Expected O, but got Unknown
//IL_0199: Unknown result type (might be due to invalid IL or missing references)
//IL_01a6: Expected O, but got Unknown
if (_applied)
{
return;
}
_applied = true;
Harmony val = new Harmony("hornet.cloak.color.mapmask");
MethodInfo methodInfo = AccessTools.Method(typeof(GameMap), "PositionCompassAndCorpse", (Type[])null, (Type[])null);
if (methodInfo != null)
{
val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapMaskHarmonyPatcher), "GameMap_PositionCompassAndCorpse_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
Type type = AccessTools.TypeByName("InventoryWideMap");
if (type != null)
{
MethodInfo methodInfo2 = AccessTools.Method(type, "UpdatePositions", (Type[])null, (Type[])null);
if (methodInfo2 != null)
{
val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapMaskHarmonyPatcher), "InventoryWideMap_UpdatePositions_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Log.Info("Hooked InventoryWideMap.UpdatePositions");
}
else
{
Log.Warn("InventoryWideMap.UpdatePositions not found");
}
MethodInfo methodInfo3 = AccessTools.Method(type, "PositionIcon", (Type[])null, (Type[])null);
if (methodInfo3 != null)
{
val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapMaskHarmonyPatcher), "InventoryWideMap_PositionIcon_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Log.Info("Hooked InventoryWideMap.PositionIcon");
}
}
else
{
Log.Warn("InventoryWideMap type not found — wide-map tint disabled");
}
Type type2 = AccessTools.TypeByName("SSMP.Game.Client.MapManager");
Type type3 = AccessTools.TypeByName("SSMP.Math.Vector2");
if (type2 != null && type3 != null)
{
MethodInfo methodInfo4 = AccessTools.Method(type2, "CreatePlayerIcon", new Type[2]
{
typeof(ushort),
type3
}, (Type[])null);
if (methodInfo4 != null)
{
val.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapMaskHarmonyPatcher), "MapManager_CreatePlayerIcon_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Log.Info("Hooked SSMP MapManager.CreatePlayerIcon");
}
else
{
Log.Warn("SSMP MapManager type found but CreatePlayerIcon(ushort, Vector2) not — remote map tint disabled");
}
}
else if (SSMPBridge.IsAvailable)
{
Log.Warn("SSMP plugin loaded but MapManager / Math.Vector2 types not found via reflection — remote map tint disabled. SSMP may have changed namespaces.");
}
ServerMapStateSyncPatcher.TryApply(val);
RemoteMapIconVisibility.TryApplyClientManagerHook(val);
SsmMapCompassBroadcastFixPatcher.TryApply(val);
MapRemoteIconDeferredCreate.TryApply(val);
}
private static void GameMap_PositionCompassAndCorpse_Postfix(GameMap __instance)
{
HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance;
if (!((Object)(object)instance == (Object)null))
{
LocalMapMaskTint.Refresh(__instance, instance.ColorConfig.CurrentColor);
RemoteMapIconVisibility.SyncRemoteMapIconsVisible();
}
}
private static void InventoryWideMap_UpdatePositions_Postfix(object __instance)
{
HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance;
if ((Object)(object)instance == (Object)null)
{
return;
}
try
{
Transform val = ResolveWideMapCompassIcon(__instance);
if ((Object)(object)val == (Object)null)
{
if (CloakPaletteConfig.DebugLogging)
{
Log.Warn("InventoryWideMap: could not resolve compass icon transform in UpdatePositions postfix");
}
return;
}
if (CloakPaletteConfig.DebugLogging)
{
Log.Info("InventoryWideMap.UpdatePositions tint -> " + ((Object)val).name);
}
LocalMapMaskTint.RefreshObject(((Component)val).gameObject, instance.ColorConfig.CurrentColor);
RemoteMapIconVisibility.SyncRemoteMapIconsVisible();
}
catch (Exception ex)
{
Log.Warn("MapMaskHarmonyPatcher: wide-map tint failed: " + ex.Message);
}
}
private static Transform? ResolveWideMapCompassIcon(object instance)
{
object? obj = instance.GetType().GetField("compassIcon", BindingFlags.Instance | BindingFlags.Public)?.GetValue(instance);
Transform val = (Transform)((obj is Transform) ? obj : null);
if (val != null && (Object)(object)val != (Object)null)
{
return val;
}
MonoBehaviour val2 = (MonoBehaviour)((instance is MonoBehaviour) ? instance : null);
if (val2 != null && (Object)(object)val2 != (Object)null)
{
Transform val3 = ((Component)val2).transform.Find("Compass Icon");
if ((Object)(object)val3 != (Object)null)
{
return val3;
}
}
return null;
}
private static void InventoryWideMap_PositionIcon_Postfix(object __instance, Transform icon, bool isActive)
{
HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance;
if ((Object)(object)instance == (Object)null || (Object)(object)icon == (Object)null)
{
return;
}
try
{
Transform val = ResolveWideMapCompassIcon(__instance);
if (((Object)(object)val != (Object)null) ? (icon == val) : (((Object)icon).name == "Compass Icon"))
{
if (CloakPaletteConfig.DebugLogging)
{
Log.Info($"InventoryWideMap.PositionIcon compass tint (isActive={isActive}) -> {((Object)icon).name}");
}
LocalMapMaskTint.RefreshObject(((Component)icon).gameObject, instance.ColorConfig.CurrentColor);
if (isActive)
{
RemoteMapIconVisibility.SyncRemoteMapIconsVisible();
}
}
}
catch (Exception ex)
{
Log.Warn("MapMaskHarmonyPatcher: PositionIcon tint failed: " + ex.Message);
}
}
private static void MapManager_CreatePlayerIcon_Postfix(ushort id, object __instance)
{
GameObject val = TryGetMapIconGameObject(__instance, id);
if ((Object)(object)val == (Object)null)
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Warn($"[MapIcon] CreatePlayerIcon postfix: no GameObject on map entry for player {id} (create failed or entry missing).");
}
return;
}
MapMaskTint mapMaskTint = val.GetComponent<MapMaskTint>();
if ((Object)(object)mapMaskTint == (Object)null)
{
mapMaskTint = val.AddComponent<MapMaskTint>();
}
CloakColor remoteMapColorOrDefault = SSMPBridge.GetRemoteMapColorOrDefault(id);
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Info($"[MapIcon] CreatePlayerIcon → MapMaskTint on '{((Object)val).name}' player {id} (active={val.activeInHierarchy}) color={remoteMapColorOrDefault}");
}
mapMaskTint.InitRemote(id, remoteMapColorOrDefault);
}
private static GameObject? TryGetMapIconGameObject(object mapManager, ushort id)
{
try
{
object obj = mapManager.GetType().GetField("_mapEntries", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(mapManager);
if (obj == null)
{
return null;
}
MethodInfo method = obj.GetType().GetMethod("TryGetValue");
if (method == null)
{
return null;
}
object[] array = new object[2] { id, null };
object obj2 = method.Invoke(obj, array);
if (!(obj2 is bool) || !(bool)obj2 || array[1] == null)
{
return null;
}
object obj3 = array[1];
object? obj4 = obj3.GetType().GetProperty("GameObject", BindingFlags.Instance | BindingFlags.Public)?.GetValue(obj3);
return (GameObject?)((obj4 is GameObject) ? obj4 : null);
}
catch (Exception ex)
{
Log.Warn($"MapMaskHarmonyPatcher: could not read map icon for player {id}: {ex.Message}");
return null;
}
}
}
[DefaultExecutionOrder(20000)]
[DisallowMultipleComponent]
internal sealed class MapMaskTint : MonoBehaviour
{
private static readonly int ColorPropertyId = Shader.PropertyToID("_Color");
private static readonly HashSet<MapMaskTint> LocalInstances = new HashSet<MapMaskTint>();
private MaterialPropertyBlock? _block;
private readonly List<Renderer> _renderers = new List<Renderer>();
private readonly List<tk2dSprite> _sprites = new List<tk2dSprite>();
private readonly List<Image> _images = new List<Image>();
private float _rescanTimer;
private bool _diagDumped;
private ushort? _networkPlayerId;
private CloakColor _color = CloakColor.Default;
internal static void BroadcastLocalColor(CloakColor color)
{
foreach (MapMaskTint localInstance in LocalInstances)
{
if (!((Object)(object)localInstance == (Object)null))
{
localInstance.SetColor(color);
}
}
}
public void InitRemote(ushort networkPlayerId, CloakColor color)
{
if (_networkPlayerId.HasValue && _networkPlayerId.Value != networkPlayerId)
{
PlayerMapMaskTintRegistry.Unregister(_networkPlayerId.Value);
}
LocalInstances.Remove(this);
_networkPlayerId = networkPlayerId;
_color = color;
PlayerMapMaskTintRegistry.Register(networkPlayerId, this);
ApplyNow();
}
public void InitLocal(CloakColor color)
{
if (_networkPlayerId.HasValue)
{
PlayerMapMaskTintRegistry.Unregister(_networkPlayerId.Value);
_networkPlayerId = null;
}
LocalInstances.Add(this);
_color = color;
ApplyNow();
}
public void SetColor(CloakColor color)
{
if (_networkPlayerId.HasValue && !_color.Equals(color) && CloakPaletteConfig.DebugLogging)
{
Log.Info($"MapMaskTint: remote player {_networkPlayerId.Value} color updated {_color} -> {color}");
}
_color = color;
ApplyNow();
}
private void OnEnable()
{
ApplyNow();
}
private void LateUpdate()
{
_rescanTimer -= Time.unscaledDeltaTime;
if (_rescanTimer <= 0f)
{
_rescanTimer = 0.25f;
ApplyNow();
}
else
{
ApplyToCachedTargets();
}
}
private void ApplyNow()
{
_renderers.Clear();
_sprites.Clear();
_images.Clear();
((Component)this).GetComponentsInChildren<Renderer>(true, _renderers);
((Component)this).GetComponentsInChildren<tk2dSprite>(true, _sprites);
((Component)this).GetComponentsInChildren<Image>(true, _images);
DumpHierarchyOnce();
ApplyToCachedTargets();
}
private void ApplyToCachedTargets()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Expected O, but got Unknown
//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
//IL_0105: Unknown result type (might be due to invalid IL or missing references)
//IL_010b: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: Unknown result type (might be due to invalid IL or missing references)
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
//IL_0165: Unknown result type (might be due to invalid IL or missing references)
//IL_016a: Unknown result type (might be due to invalid IL or missing references)
//IL_016e: Unknown result type (might be due to invalid IL or missing references)
//IL_0174: Unknown result type (might be due to invalid IL or missing references)
//IL_017a: Unknown result type (might be due to invalid IL or missing references)
//IL_0180: Unknown result type (might be due to invalid IL or missing references)
//IL_0187: Unknown result type (might be due to invalid IL or missing references)
Color val = _color.ToUnityColor();
if (_renderers.Count > 0)
{
if (_block == null)
{
_block = new MaterialPropertyBlock();
}
foreach (Renderer renderer in _renderers)
{
if (!((Object)(object)renderer == (Object)null))
{
renderer.GetPropertyBlock(_block);
_block.SetColor(ColorPropertyId, new Color(val.r, val.g, val.b, 1f));
renderer.SetPropertyBlock(_block);
}
}
}
foreach (tk2dSprite sprite in _sprites)
{
if (!((Object)(object)sprite == (Object)null))
{
Color color = ((tk2dBaseSprite)sprite).color;
if (color.r != val.r || color.g != val.g || color.b != val.b)
{
((tk2dBaseSprite)sprite).color = new Color(val.r, val.g, val.b, color.a);
}
}
}
foreach (Image image in _images)
{
if (!((Object)(object)image == (Object)null))
{
Color color2 = ((Graphic)image).color;
((Graphic)image).color = new Color(val.r, val.g, val.b, color2.a);
}
}
}
private void DumpHierarchyOnce()
{
if (_diagDumped || !CloakPaletteConfig.DebugLogging)
{
return;
}
_diagDumped = true;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("[HornetCloakColor] MapMaskTint attached to '").Append(((Object)this).name).Append("' — ");
stringBuilder.Append("renderers=").Append(_renderers.Count).Append(", sprites=")
.Append(_sprites.Count)
.Append(", images=")
.Append(_images.Count);
stringBuilder.AppendLine();
for (int i = 0; i < _renderers.Count; i++)
{
Renderer val = _renderers[i];
if (!((Object)(object)val == (Object)null))
{
string value = (((Object)(object)val.sharedMaterial != (Object)null && (Object)(object)val.sharedMaterial.shader != (Object)null) ? ((Object)val.sharedMaterial.shader).name : "<null>");
stringBuilder.Append(" Renderer[").Append(i).Append("] type=")
.Append(((object)val).GetType().Name)
.Append(" path=")
.Append(GetPath(((Component)val).transform))
.Append(" shader=")
.Append(value)
.AppendLine();
}
}
for (int j = 0; j < _sprites.Count; j++)
{
tk2dSprite val2 = _sprites[j];
if (!((Object)(object)val2 == (Object)null))
{
stringBuilder.Append(" Sprite[").Append(j).Append("] path=")
.Append(GetPath(((Component)val2).transform))
.AppendLine();
}
}
for (int k = 0; k < _images.Count; k++)
{
Image val3 = _images[k];
if (!((Object)(object)val3 == (Object)null))
{
stringBuilder.Append(" Image[").Append(k).Append("] path=")
.Append(GetPath(((Component)val3).transform))
.AppendLine();
}
}
ManualLogSource? logSource = HornetCloakColorPlugin.LogSource;
if (logSource != null)
{
logSource.LogInfo((object)stringBuilder.ToString());
}
}
private static string GetPath(Transform t)
{
StringBuilder stringBuilder = new StringBuilder(((Object)t).name);
Transform parent = t.parent;
while ((Object)(object)parent != (Object)null)
{
stringBuilder.Insert(0, ((Object)parent).name + "/");
parent = parent.parent;
}
return stringBuilder.ToString();
}
private void OnDisable()
{
ClearTargets();
}
private void OnDestroy()
{
if (_networkPlayerId.HasValue)
{
PlayerMapMaskTintRegistry.Unregister(_networkPlayerId.Value);
}
LocalInstances.Remove(this);
ClearTargets();
}
private void ClearTargets()
{
foreach (Renderer renderer in _renderers)
{
if ((Object)(object)renderer != (Object)null)
{
renderer.SetPropertyBlock((MaterialPropertyBlock)null);
}
}
}
}
internal static class PlayerMapMaskTintRegistry
{
private static readonly Dictionary<ushort, MapMaskTint> ByPlayer = new Dictionary<ushort, MapMaskTint>();
internal static void Register(ushort id, MapMaskTint tint)
{
ByPlayer[id] = tint;
}
internal static void Unregister(ushort id)
{
ByPlayer.Remove(id);
}
internal static void SetColor(ushort playerId, CloakColor color)
{
if (ByPlayer.TryGetValue(playerId, out MapMaskTint value) && (Object)(object)value != (Object)null)
{
value.SetColor(color);
}
}
}
internal static class LocalMapMaskTint
{
internal static void Refresh(GameMap? gameMap, CloakColor color)
{
if (!((Object)(object)gameMap == (Object)null) && !((Object)(object)gameMap.compassIcon == (Object)null))
{
RefreshObject(gameMap.compassIcon, color);
}
}
internal static void RefreshObject(GameObject? icon, CloakColor color)
{
if (!((Object)(object)icon == (Object)null))
{
(icon.GetComponent<MapMaskTint>() ?? icon.AddComponent<MapMaskTint>()).InitLocal(color);
}
}
}
internal static class MapRemoteIconDeferredCreate
{
private static bool _applied;
private static float _nextLogPendingNoGameMap;
internal static void TryApply(Harmony harmony)
{
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_0082: Expected O, but got Unknown
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Expected O, but got Unknown
if (_applied || !SSMPBridge.IsAvailable)
{
return;
}
Type type = AccessTools.TypeByName("SSMP.Game.Client.MapManager");
if (type == null)
{
return;
}
MethodInfo methodInfo = AccessTools.Method(type, "UpdatePlayerHasIcon", new Type[2]
{
typeof(ushort),
typeof(bool)
}, (Type[])null);
if (methodInfo == null)
{
return;
}
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapRemoteIconDeferredCreate), "UpdatePlayerHasIcon_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Type type2 = AccessTools.TypeByName("SSMP.Game.Client.ClientManager");
Type type3 = AccessTools.TypeByName("SSMP.Networking.Packet.Data.ClientPlayerAlreadyInScene");
if (type2 != null && type3 != null)
{
MethodInfo methodInfo2 = AccessTools.Method(type2, "OnPlayerAlreadyInScene", new Type[1] { type3 }, (Type[])null);
if (methodInfo2 != null)
{
harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapRemoteIconDeferredCreate), "ClientManager_OnPlayerAlreadyInScene_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Log.Info("Hooked SSMP ClientManager.OnPlayerAlreadyInScene — retry remote map icons after late-join roster");
}
}
_applied = true;
Log.Info("Hooked SSMP MapManager.UpdatePlayerHasIcon — deferred remote map icon materialize");
}
private static void ClientManager_OnPlayerAlreadyInScene_Postfix(object __instance)
{
if (__instance == null)
{
return;
}
try
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Info("[MapIcon] OnPlayerAlreadyInScene finished — running deferred remote icon materialize pass.");
}
TryMaterializePendingIcons(__instance.GetType().GetField("_mapManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(__instance));
}
catch (Exception ex)
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Warn("[MapIcon] OnPlayerAlreadyInScene materialize hook: " + ex.Message);
}
}
}
private static void UpdatePlayerHasIcon_Postfix(object __instance, ushort id, bool hasMapIcon)
{
if (__instance != null)
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Info($"[MapIcon] UpdatePlayerHasIcon(player {id}, hasIcon={hasMapIcon}) — deferred materialize pass.");
}
TryMaterializePendingIcons(__instance);
}
}
internal static void TryMaterializePendingIcons(object? mapManager)
{
if (mapManager == null)
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Warn("[MapIcon] TryMaterializePendingIcons: MapManager instance null.");
}
return;
}
try
{
Type type = mapManager.GetType();
object obj = type.GetMethod("GetGameMap", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(mapManager, null);
MethodInfo method = type.GetMethod("CreatePlayerIcon", BindingFlags.Instance | BindingFlags.NonPublic);
if (method == null)
{
Log.Warn("[MapIcon] TryMaterializePendingIcons: CreatePlayerIcon not found on MapManager.");
return;
}
if (!(type.GetField("_mapEntries", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(mapManager) is IEnumerable enumerable))
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Warn("[MapIcon] TryMaterializePendingIcons: _mapEntries missing or not enumerable.");
}
return;
}
List<(ushort, object)> list = new List<(ushort, object)>();
foreach (object item2 in enumerable)
{
if (item2 == null)
{
continue;
}
Type type2 = item2.GetType();
PropertyInfo property = type2.GetProperty("Key");
PropertyInfo property2 = type2.GetProperty("Value");
if (property == null || property2 == null)
{
continue;
}
object value = property.GetValue(item2);
object value2 = property2.GetValue(item2);
if (!(value is ushort item) || value2 == null)
{
continue;
}
Type type3 = value2.GetType();
bool flag = (bool)type3.GetProperty("HasMapIcon").GetValue(value2);
object value3 = type3.GetProperty("GameObject").GetValue(value2);
if (flag && value3 == null)
{
object value4 = type3.GetProperty("Position").GetValue(value2);
if (value4 != null)
{
list.Add((item, value4));
}
}
}
if (list.Count == 0)
{
return;
}
if (obj == null)
{
if (CloakPaletteConfig.LogMapIconDiagnostics && Time.realtimeSinceStartup >= _nextLogPendingNoGameMap)
{
_nextLogPendingNoGameMap = Time.realtimeSinceStartup + 2f;
string arg = string.Join(",", list.ConvertAll(((ushort playerId, object pos) x) => x.playerId.ToString()));
Log.Warn($"[MapIcon] {list.Count} remote map entr(y/ies) need GameObjects (players {arg}) " + "but GetGameMap() is null — will retry when GameMap exists.");
}
return;
}
int num = 0;
foreach (var (num2, obj2) in list)
{
method.Invoke(mapManager, new object[2] { num2, obj2 });
num++;
}
if (num > 0 && CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Info($"[MapIcon] Deferred CreatePlayerIcon succeeded for {num} remote player(s) (GameMap was ready).");
}
}
catch (Exception ex)
{
Log.Warn("[MapIcon] TryMaterializePendingIcons: " + ex.Message);
}
}
}
internal static class PerfDiagnostics
{
private const float FlushIntervalSec = 2f;
private static float _windowStart = -1f;
private static int _recolorLateUpdates;
private static double _recolorMsTotal;
private static double _recolorMsMax;
private static string _recolorMaxOwner = "";
private static int _recolorMeshMax;
private static long _recolorMeshSum;
private static int _scannerRuns;
private static long _scannerSpritesTotal;
private static int _scannerSpritesMax;
private static long _scannerAppliedTotal;
private static double _scannerFindMsTotal;
private static double _scannerLoopMsTotal;
private static int _mapSyncCalls;
private static double _mapSyncMsTotal;
private static double _mapSyncMsMax;
public static bool Enabled => CloakPaletteConfig.PerfDiagnostics;
public static void RecordRecolorLateUpdate(string ownerName, int meshRendererCount, double elapsedMs)
{
if (Enabled)
{
_recolorLateUpdates++;
_recolorMsTotal += elapsedMs;
if (elapsedMs > _recolorMsMax)
{
_recolorMsMax = elapsedMs;
_recolorMaxOwner = ownerName;
}
_recolorMeshSum += meshRendererCount;
if (meshRendererCount > _recolorMeshMax)
{
_recolorMeshMax = meshRendererCount;
}
MaybeFlushWindow();
}
}
public static void RecordSceneScan(int tk2dSpriteCount, int appliedCount, double findObjectsMs, double loopMs)
{
if (Enabled)
{
_scannerRuns++;
_scannerSpritesTotal += tk2dSpriteCount;
if (tk2dSpriteCount > _scannerSpritesMax)
{
_scannerSpritesMax = tk2dSpriteCount;
}
_scannerAppliedTotal += appliedCount;
_scannerFindMsTotal += findObjectsMs;
_scannerLoopMsTotal += loopMs;
MaybeFlushWindow();
}
}
public static void RecordMapSyncVisible(double elapsedMs)
{
if (Enabled)
{
_mapSyncCalls++;
_mapSyncMsTotal += elapsedMs;
if (elapsedMs > _mapSyncMsMax)
{
_mapSyncMsMax = elapsedMs;
}
MaybeFlushWindow();
}
}
private static void MaybeFlushWindow()
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (_windowStart < 0f)
{
_windowStart = realtimeSinceStartup;
}
if (!(realtimeSinceStartup - _windowStart < 2f))
{
Emit();
_recolorLateUpdates = 0;
_recolorMsTotal = 0.0;
_recolorMsMax = 0.0;
_recolorMaxOwner = "";
_recolorMeshMax = 0;
_recolorMeshSum = 0L;
_scannerRuns = 0;
_scannerSpritesTotal = 0L;
_scannerSpritesMax = 0;
_scannerAppliedTotal = 0L;
_scannerFindMsTotal = 0.0;
_scannerLoopMsTotal = 0.0;
_mapSyncCalls = 0;
_mapSyncMsTotal = 0.0;
_mapSyncMsMax = 0.0;
_windowStart = realtimeSinceStartup;
}
}
private static void Emit()
{
if (_recolorLateUpdates > 0)
{
double num = _recolorMsTotal / (double)_recolorLateUpdates;
double num2 = (double)_recolorMeshSum / (double)_recolorLateUpdates;
Log.Info($"[HCC/Perf] CloakRecolor: {_recolorLateUpdates} LateUpdate(s) in ~{2f:F0}s " + $"(≈{(float)_recolorLateUpdates / 2f:F0}/s) — total {_recolorMsTotal:F1}ms, " + $"avg {num:F3}ms/update, max {_recolorMsMax:F3}ms on '{_recolorMaxOwner}', " + $"MeshRenderer avg {num2:F1}, max {_recolorMeshMax} " + "(multiple players ⇒ multiple components ⇒ cost scales up).");
}
if (_scannerRuns > 0)
{
double num3 = (double)_scannerSpritesTotal / (double)_scannerRuns;
double num4 = (double)_scannerAppliedTotal / (double)_scannerRuns;
double num5 = _scannerFindMsTotal / (double)_scannerRuns;
double num6 = _scannerLoopMsTotal / (double)_scannerRuns;
Log.Info($"[HCC/Perf] CloakSceneScanner: {_scannerRuns} scan(s) — tk2dSprite count avg {num3:F0}, max in one pass {_scannerSpritesMax}, " + $"Apply() calls avg {num4:F1}, FindObjects {num5:F2}ms/scan, loop+Apply {num6:F2}ms/scan " + "(MP adds sprites ⇒ larger FindObjects + longer loop).");
}
if (_mapSyncCalls > 0)
{
double num7 = _mapSyncMsTotal / (double)_mapSyncCalls;
Log.Info($"[HCC/Perf] SyncRemoteMapIconsVisible: {_mapSyncCalls} call(s) in ~{2f:F0}s " + $"(≈{(float)_mapSyncCalls / 2f:F0}/s) — total {_mapSyncMsTotal:F2}ms, avg {num7:F3}ms, max {_mapSyncMsMax:F3}ms " + "(reflection into SSMP MapManager; spikes if GameMap updates compass often).");
}
}
}
internal static class RemoteMapIconVisibility
{
private static object? _clientManager;
private static float _nextSyncLogTime;
internal static void RegisterClientManager(object clientManager)
{
_clientManager = clientManager;
}
internal static void TryApplyClientManagerHook(Harmony harmony)
{
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Expected O, but got Unknown
Type type = AccessTools.TypeByName("SSMP.Game.Client.ClientManager");
Type type2 = AccessTools.TypeByName("SSMP.Game.Server.ServerManager");
if (!(type == null) && !(type2 == null))
{
MethodInfo methodInfo = AccessTools.Method(type, "Initialize", new Type[1] { type2 }, (Type[])null);
if (!(methodInfo == null))
{
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(RemoteMapIconVisibility), "ClientManager_Initialize_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
Log.Info("Hooked SSMP ClientManager.Initialize (inventory map remote icon visibility)");
}
}
}
private static void ClientManager_Initialize_Postfix(object __instance)
{
if (__instance != null)
{
RegisterClientManager(__instance);
}
}
internal static void SyncRemoteMapIconsVisible()
{
if (_clientManager == null)
{
return;
}
Stopwatch stopwatch = (PerfDiagnostics.Enabled ? Stopwatch.StartNew() : null);
try
{
object obj = _clientManager.GetType().GetField("_mapManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(_clientManager);
if (obj == null)
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Warn("[MapIcon] SyncRemoteMapIconsVisible: ClientManager has no _mapManager.");
}
return;
}
SetDisplayingIconsAndRefresh(obj, showing: true);
MapRemoteIconDeferredCreate.TryMaterializePendingIcons(obj);
if (CloakPaletteConfig.LogMapIconDiagnostics && Time.realtimeSinceStartup >= _nextSyncLogTime)
{
_nextSyncLogTime = Time.realtimeSinceStartup + 1.25f;
Log.Info("[MapIcon] SyncRemoteMapIconsVisible: set _displayingIcons=true, UpdateMapIconsActive, materialize pass.");
}
}
catch (Exception ex)
{
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Warn("[MapIcon] SyncRemoteMapIconsVisible: " + ex.Message);
}
}
finally
{
if (stopwatch != null)
{
stopwatch.Stop();
PerfDiagnostics.RecordMapSyncVisible(stopwatch.Elapsed.TotalMilliseconds);
}
}
}
private static void SetDisplayingIconsAndRefresh(object mapManager, bool showing)
{
try
{
Type type = mapManager.GetType();
type.GetField("_displayingIcons", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(mapManager, showing);
type.GetMethod("UpdateMapIconsActive", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(mapManager, null);
}
catch
{
}
}
}
internal static class ServerMapStateSyncPatcher
{
private static bool _applied;
private static bool _warnedMissingUpdatePosMethod;
internal static void TryApply(Harmony harmony)
{
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Expected O, but got Unknown
if (_applied || !SSMPBridge.IsAvailable)
{
return;
}
Type type = AccessTools.TypeByName("SSMP.Game.Server.ServerManager");
Type type2 = AccessTools.TypeByName("SSMP.Game.Server.ServerPlayerData");
if (type == null || type2 == null)
{
Log.Warn("SSMP server types not found — late-join map icon sync patch skipped");
return;
}
MethodInfo methodInfo = AccessTools.Method(type, "OnClientEnterScene", new Type[1] { type2 }, (Type[])null);
if (methodInfo == null)
{
Log.Warn("SSMP ServerManager.OnClientEnterScene(ServerPlayerData) not found — map sync skipped");
return;
}
harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(ServerMapStateSyncPatcher), "OnClientEnterScene_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_applied = true;
Log.Info("Hooked SSMP ServerManager.OnClientEnterScene — co-scene map icon replay + peer push");
}
private static object? GetInstanceFieldFromHierarchy(object target, string fieldName)
{
Type type = target.GetType();
while (type != null)
{
FieldInfo field = type.GetField(fieldName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic);
if (field != null)
{
return field.GetValue(target);
}
type = type.BaseType;
}
return null;
}
private static void OnClientEnterScene_Postfix(object __instance, object playerData)
{
if (__instance == null || playerData == null)
{
return;
}
try
{
object instanceFieldFromHierarchy = GetInstanceFieldFromHierarchy(__instance, "_netServer");
object instanceFieldFromHierarchy2 = GetInstanceFieldFromHierarchy(__instance, "_playerData");
if (instanceFieldFromHierarchy == null || instanceFieldFromHierarchy2 == null)
{
Log.Warn("[MapIcon] ServerMapStateSync: could not read _netServer or _playerData via reflection " + $"(netServer={instanceFieldFromHierarchy != null}, playerDict={instanceFieldFromHierarchy2 != null}) — late-join replay skipped.");
return;
}
Type type = playerData.GetType();
ushort num = (ushort)type.GetProperty("Id").GetValue(playerData);
string text = (string)type.GetProperty("CurrentScene").GetValue(playerData);
MethodInfo method = instanceFieldFromHierarchy.GetType().GetMethod("GetUpdateManagerForClient", new Type[1] { typeof(ushort) });
if (method == null)
{
Log.Warn("[MapIcon] ServerMapStateSync: GetUpdateManagerForClient not found on NetServer.");
return;
}
object obj = method.Invoke(instanceFieldFromHierarchy, new object[1] { num });
if (obj == null)
{
Log.Warn($"[MapIcon] ServerMapStateSync: no ServerUpdateManager for entering client {num} " + "(scene " + text + ") — late-join map icon replay skipped.");
return;
}
Type type2 = obj.GetType();
MethodInfo methodInfo = AccessTools.Method(type2, "UpdatePlayerMapIcon", new Type[2]
{
typeof(ushort),
typeof(bool)
}, (Type[])null);
if (methodInfo == null)
{
Log.Warn("ServerMapStateSync: ServerUpdateManager.UpdatePlayerMapIcon(ushort,bool) not found — late-join map icon sync disabled");
return;
}
Type type3 = AccessTools.TypeByName("SSMP.Math.Vector2");
MethodInfo methodInfo2 = ((type3 != null) ? AccessTools.Method(type2, "UpdatePlayerMapPosition", new Type[2]
{
typeof(ushort),
type3
}, (Type[])null) : null);
if (methodInfo2 == null && !_warnedMissingUpdatePosMethod)
{
_warnedMissingUpdatePosMethod = true;
Log.Warn("[MapIcon] ServerMapStateSync: UpdatePlayerMapPosition not resolved — replaying HasIcon flags only (no stored positions).");
}
if (!(instanceFieldFromHierarchy2.GetType().GetProperty("Values")?.GetValue(instanceFieldFromHierarchy2) is IEnumerable enumerable))
{
Log.Warn("[MapIcon] ServerMapStateSync: could not enumerate _playerData.Values.");
return;
}
bool flag = (bool)type.GetProperty("HasMapIcon").GetValue(playerData);
object obj2 = type.GetProperty("MapPosition")?.GetValue(playerData);
List<string> list = new List<string>();
List<string> list2 = new List<string>();
int num2 = 0;
foreach (object item in enumerable)
{
if (item == null)
{
continue;
}
Type type4 = item.GetType();
ushort num3 = (ushort)type4.GetProperty("Id").GetValue(item);
if (num3 == num || !string.Equals((string)type4.GetProperty("CurrentScene").GetValue(item), text, StringComparison.Ordinal))
{
continue;
}
bool flag2 = (bool)type4.GetProperty("HasMapIcon").GetValue(item);
methodInfo.Invoke(obj, new object[2] { num3, flag2 });
bool flag3 = false;
if (flag2 && methodInfo2 != null)
{
object obj3 = type4.GetProperty("MapPosition")?.GetValue(item);
if (obj3 != null)
{
methodInfo2.Invoke(obj, new object[2] { num3, obj3 });
flag3 = true;
}
}
object obj4 = method.Invoke(instanceFieldFromHierarchy, new object[1] { num3 });
if (obj4 != null)
{
methodInfo.Invoke(obj4, new object[2] { num, flag });
bool flag4 = false;
if (flag && methodInfo2 != null && obj2 != null)
{
methodInfo2.Invoke(obj4, new object[2] { num, obj2 });
flag4 = true;
}
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
list2.Add(string.Format("peer {0} ← entering {1}: HasIcon={2}, pos={3}", num3, num, flag, flag4 ? "sent" : "none"));
}
}
else if (CloakPaletteConfig.LogMapIconDiagnostics)
{
list2.Add($"peer {num3}: no ServerUpdateManager (skipped push)");
}
num2++;
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
list.Add(string.Format("{0}:HasIcon={1},pos={2}", num3, flag2, flag3 ? "sent" : "none"));
}
}
if (CloakPaletteConfig.LogMapIconDiagnostics && num2 > 0)
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append($"[MapIcon] Server late-join map replay → client {num} scene={text}: ");
stringBuilder.Append($"{num2} co-scene peer(s) [");
stringBuilder.Append(string.Join("; ", list));
stringBuilder.Append("]; push entering state to peers [");
stringBuilder.Append(string.Join("; ", list2));
stringBuilder.Append("].");
Log.Info(stringBuilder.ToString());
}
else if (CloakPaletteConfig.LogMapIconDiagnostics && num2 == 0)
{
Log.Info($"[MapIcon] Server late-join map replay → client {num} scene={text}: " + "no other players in this scene on server (nothing to replay).");
}
}
catch (Exception ex)
{
Log.Warn("[MapIcon] ServerMapStateSync exception: " + ex.Message);
}
}
}
internal static class SsmMapCompassBroadcastFixPatcher
{
private static class MapBroadcastReflect
{
internal static FieldInfo? NetClient;
internal static PropertyInfo? NetIsConnected;
internal static FieldInfo? ServerSettings;
internal static PropertyInfo? AlwaysShowMapIcons;
internal static PropertyInfo? OnlyBroadcastMapIconWithCompass;
internal static FieldInfo? LastSentMapIcon;
internal static FieldInfo? LastPosition;
internal static MethodInfo? TryGetMapPosition;
internal static PropertyInfo? NetUpdateManager;
internal static MethodInfo? UpdatePlayerMapIconBool;
internal static MethodInfo? UpdatePlayerMapPosition;
internal static ConstructorInfo? SsmpVecCtorFf;
internal static PropertyInfo? GameplayCompassTool;
internal static PropertyInfo? CompassIsEquipped;
internal static object[]? TryGetMapPosArgs;
internal static object[]? InvokeOneArg;
internal static bool Ready { get; private set; }
internal static bool TryBuild(Type mapManagerType)
{
if (Ready)
{
return true;
}
try
{
NetClient = mapManagerType.GetField("_netClient", BindingFlags.Instance | BindingFlags.NonPublic);
if (NetClient == null)
{
return false;
}
Type fieldType = NetClient.FieldType;
NetIsConnected = fieldType.GetProperty("IsConnected", BindingFlags.Instance | BindingFlags.Public);
if (NetIsConnected == null)
{
return false;
}
NetUpdateManager = fieldType.GetProperty("UpdateManager", BindingFlags.Instance | BindingFlags.Public);
if (NetUpdateManager == null)
{
return false;
}
Type propertyType = NetUpdateManager.PropertyType;
UpdatePlayerMapIconBool = propertyType.GetMethod("UpdatePlayerMapIcon", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(bool) }, null);
if (UpdatePlayerMapIconBool == null)
{
return false;
}
ServerSettings = mapManagerType.GetField("_serverSettings", BindingFlags.Instance | BindingFlags.NonPublic);
if (ServerSettings == null)
{
return false;
}
Type fieldType2 = ServerSettings.FieldType;
AlwaysShowMapIcons = fieldType2.GetProperty("AlwaysShowMapIcons", BindingFlags.Instance | BindingFlags.Public);
OnlyBroadcastMapIconWithCompass = fieldType2.GetProperty("OnlyBroadcastMapIconWithCompass", BindingFlags.Instance | BindingFlags.Public);
if (AlwaysShowMapIcons == null || OnlyBroadcastMapIconWithCompass == null)
{
return false;
}
LastSentMapIcon = mapManagerType.GetField("_lastSentMapIcon", BindingFlags.Instance | BindingFlags.NonPublic);
LastPosition = mapManagerType.GetField("_lastPosition", BindingFlags.Instance | BindingFlags.NonPublic);
if (LastSentMapIcon == null || LastPosition == null)
{
return false;
}
TryGetMapPosition = mapManagerType.GetMethod("TryGetMapPosition", BindingFlags.Instance | BindingFlags.NonPublic);
if (TryGetMapPosition == null)
{
return false;
}
Type type = AccessTools.TypeByName("SSMP.Math.Vector2");
if (type == null)
{
return false;
}
SsmpVecCtorFf = type.GetConstructor(new Type[2]
{
typeof(float),
typeof(float)
});
if (SsmpVecCtorFf == null)
{
return false;
}
UpdatePlayerMapPosition = propertyType.GetMethod("UpdatePlayerMapPosition", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { type }, null);
if (UpdatePlayerMapPosition == null)
{
return false;
}
Type type2 = AccessTools.TypeByName("Gameplay");
if (type2 == null)
{
return false;
}
GameplayCompassTool = type2.GetProperty("CompassTool", BindingFlags.Static | BindingFlags.Public);
if (GameplayCompassTool == null)
{
return false;
}
CompassIsEquipped = GameplayCompassTool.PropertyType.GetProperty("IsEquipped", BindingFlags.Instance | BindingFlags.Public);
if (CompassIsEquipped == null)
{
return false;
}
TryGetMapPosArgs = new object[1];
InvokeOneArg = new object[1];
Ready = true;
return true;
}
catch
{
return false;
}
}
}
private static bool _applied;
private static float _nextLogDeferMapPos;
private static float _nextLogMapPosSent;
internal static void TryApply(Harmony harmony)
{
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Expected O, but got Unknown
if (_applied || !SSMPBridge.IsAvailable)
{
return;
}
Type type = AccessTools.TypeByName("SSMP.Game.Client.MapManager");
if (type == null)
{
return;
}
if (!MapBroadcastReflect.TryBuild(type))
{
Log.Warn("SsmMapCompassBroadcastFix: could not cache MapManager reflection — compass broadcast fix skipped.");
return;
}
MethodInfo methodInfo = AccessTools.Method(type, "HeroControllerOnUpdate", new Type[1] { typeof(HeroController) }, (Type[])null);
if (methodInfo == null)
{
Log.Warn("SSMP MapManager.HeroControllerOnUpdate not found — compass broadcast fix skipped");
return;
}
harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(SsmMapCompassBroadcastFixPatcher), "HeroControllerOnUpdate_Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
_applied = true;
Log.Info("Hooked SSMP MapManager.HeroControllerOnUpdate — corrected compass map-icon broadcast logic");
}
private static bool HeroControllerOnUpdate_Prefix(object __instance, HeroController heroController)
{
//IL_016b: Unknown result type (might be due to invalid IL or missing references)
//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
//IL_020a: Unknown result type (might be due to invalid IL or missing references)
//IL_0219: Unknown result type (might be due to invalid IL or missing references)
//IL_0271: Unknown result type (might be due to invalid IL or missing references)
//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
if (__instance == null || !MapBroadcastReflect.Ready)
{
return true;
}
try
{
object value = MapBroadcastReflect.NetClient.GetValue(__instance);
if (value == null)
{
return true;
}
if (!(bool)MapBroadcastReflect.NetIsConnected.GetValue(value))
{
return true;
}
object value2 = MapBroadcastReflect.ServerSettings.GetValue(__instance);
if (value2 == null)
{
return true;
}
bool flag = (bool)MapBroadcastReflect.AlwaysShowMapIcons.GetValue(value2);
bool flag2 = (bool)MapBroadcastReflect.OnlyBroadcastMapIconWithCompass.GetValue(value2);
Vector2 mapPosition;
bool flag3 = TryInvokeTryGetMapPosition(__instance, out mapPosition);
bool flag4 = true;
if (!flag)
{
if (!flag2)
{
flag4 = false;
}
else if (!IsCompassEquipped())
{
flag4 = false;
}
}
bool flag5 = (bool)MapBroadcastReflect.LastSentMapIcon.GetValue(__instance);
if (flag4 != flag5)
{
MapBroadcastReflect.LastSentMapIcon.SetValue(__instance, flag4);
object value3 = MapBroadcastReflect.NetUpdateManager.GetValue(value);
if (value3 == null)
{
Log.Warn("[MapIcon] MapManager._netClient.UpdateManager is null — compass broadcast fix skipped for this frame.");
return true;
}
MapBroadcastReflect.UpdatePlayerMapIconBool.Invoke(value3, new object[1] { flag4 });
if (CloakPaletteConfig.LogMapIconDiagnostics)
{
Log.Info($"[MapIcon] Queued PlayerMapUpdate hasIcon={flag4} " + $"(TryGetMapPosition={flag3}, alwaysShow={flag}, onlyCompass={flag2}, compassEquipped={IsCompassEquipped()}).");
}
if (!flag4)
{
MapBroadcastReflect.LastPosition.SetValue(__instance, Vector2.zero);
}
}
if (!flag4 || (Object)(object)GameManager.instance == (Object)null || GameManager.instance.IsInSceneTransition)
{
return false;
}
if (!flag3)
{
if (CloakPaletteConfig.LogMapIconDiagnostics && Time.realtimeSinceStartup >= _nextLogDeferMapPos)
{
_nextLogDeferMapPos = Time.realtimeSinceStartup + 2f;
Log.Info("[MapIcon] Network HasIcon is true but TryGetMapPosition=false (GameMap often null until the map opens); position packets deferred.");
}
return false;
}
Vector2 val = (Vector2)MapBroadcastReflect.LastPosition.GetValue(__instance);
if (mapPosition == val)
{
return false;
}
object obj = MapBroadcastReflect.SsmpVecCtorFf.Invoke(new object[2] { mapPosition.x, mapPosition.y });
object value4 = MapBroadcastReflect.NetUpdateManager.GetValue(value);
if (value4 == null)
{
Log.Warn("[MapIcon] UpdateManager null while sending map position.");
return false;
}
MapBroadcastReflect.InvokeOneArg[0] = obj;
MapBroadcastReflect.UpdatePlayerMapPosition.Invoke(value4, MapBroadcastReflect.InvokeOneArg);
MapBroadcastReflect.LastPosition.SetValue(__instance, mapPosition);
if (CloakPaletteConfig.LogMapIconDiagnostics && Time.realtimeSinceStartup >= _nextLogMapPosSent)
{
_nextLogMapPosSent = Time.realtimeSinceStartup + 0.75f;
Log.Info($"[MapIcon] Sent map position update ({mapPosition.x:F1}, {mapPosition.y:F1}).");
}
return false;
}
catch (Exception ex)
{
Log.Warn("SsmMapCompassBroadcastFix: falling back to vanilla MapManager update (" + ex.Message + ")");
return true;
}
}
private static bool TryInvokeTryGetMapPosition(object mapManager, out Vector2 mapPosition)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
mapPosition = Vector2.zero;
MapBroadcastReflect.TryGetMapPosArgs[0] = Vector2.zero;
bool result = (bool)MapBroadcastReflect.TryGetMapPosition.Invoke(mapManager, MapBroadcastReflect.TryGetMapPosArgs);
mapPosition = (Vector2)MapBroadcastReflect.TryGetMapPosArgs[0];
return result;
}
private static bool IsCompassEquipped()
{
object value = MapBroadcastReflect.GameplayCompassTool.GetValue(null);
if (value == null)
{
return false;
}
return (bool)MapBroadcastReflect.CompassIsEquipped.GetValue(value);
}
}
internal static class TextureDumper
{
private static readonly HashSet<int> _dumped = new HashSet<int>();
private static string? _dumpDir;
private static bool _warnedDir;
public static void TryDump(Texture? tex, string source)
{
if (!CloakPaletteConfig.DumpDiscoveredTextures || (Object)(object)tex == (Object)null)
{
return;
}
int instanceID = ((Object)tex).GetInstanceID();
if (!_dumped.Add(instanceID))
{
return;
}
string text = EnsureDumpDir();
if (text == null)
{
return;
}
Texture2D val = null;
try
{
val = MakeReadable(tex);
if ((Object)(object)val == (Object)null)
{
Log.Warn($"[Dumper] Could not blit '{((Object)tex).name}' (id={instanceID}, {tex.width}x{tex.height}) to a readable texture.");
return;
}
byte[] array = ImageConversion.EncodeToPNG(val);
if (array == null || array.Length == 0)
{
Log.Warn($"[Dumper] EncodeToPNG returned no bytes for '{((Object)tex).name}' (id={instanceID}).");
return;
}
string path = $"{Sanitize(((Object)tex).name)}_id{instanceID}_{tex.width}x{tex.height}_{Sanitize(source)}.png";
string text2 = Path.Combine(text, path);
File.WriteAllBytes(text2, array);
Log.Info($"[Dumper] Wrote {text2} ({array.Length:N0} bytes).");
}
catch (Exception ex)
{
Log.Warn($"[Dumper] Failed to dump '{((Object)tex).name}' (id={instanceID}): {ex.Message}");
}
finally
{
if ((Object)(object)val != (Object)null)
{
Object.Destroy((Object)(object)val);
}
}
}
private static Texture2D? MakeReadable(Texture src)
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Expected O, but got Unknown
int width = src.width;
int height = src.height;
if (width <= 0 || height <= 0)
{
return null;
}
RenderTexture temporary = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1);
RenderTexture active = RenderTexture.active;
try
{
Graphics.Blit(src, temporary);
RenderTexture.active = temporary;
Texture2D val = new Texture2D(width, height, (TextureFormat)4, false);
val.ReadPixels(new Rect(0f, 0f, (float)width, (float)height), 0, 0);
val.Apply(false, false);
return val;
}
finally
{
RenderTexture.active = active;
RenderTexture.ReleaseTemporary(temporary);
}
}
private static string? EnsureDumpDir()
{
if (_dumpDir != null)
{
return _dumpDir;
}
try
{
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (string.IsNullOrEmpty(directoryName))
{
if (!_warnedDir)
{
Log.Warn("[Dumper] Could not resolve assembly directory.");
_warnedDir = true;
}
return null;
}
_dumpDir = Path.Combine(directoryName, "TextureDumps");
Directory.CreateDirectory(_dumpDir);
return _dumpDir;
}
catch (Exception ex)
{
if (!_warnedDir)
{
Log.Warn("[Dumper] Could not create dump folder: " + ex.Message);
_warnedDir = true;
}
return null;
}
}
private static string Sanitize(string? name)
{
if (string.IsNullOrEmpty(name))
{
return "tex";
}
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
return string.Join("_", name.Split(invalidFileNameChars));
}
}
}