using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; 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.Interop; using HornetCloakColor.Shared; using Microsoft.CodeAnalysis; using Silksong.ModMenu.Elements; using Silksong.ModMenu.Models; using Silksong.ModMenu.Plugin; using UnityEngine; using UnityEngine.Rendering; 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("HornetCloakColor")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.15.0.0")] [assembly: AssemblyInformationalVersion("1.15.0+0eba9b2e5df530332fab65160f0c51fed55f93df")] [assembly: AssemblyProduct("HornetCloakColor")] [assembly: AssemblyTitle("HornetCloakColor")] [assembly: InternalsVisibleTo("HornetCloakColor.SSMP")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.15.0.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.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("hornet.cloak.color", "HornetCloakColor", "1.15.0")] public class HornetCloakColorPlugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__18 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float[] delaysSec; private float[] <>7__wrap1; private int <>7__wrap2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__18(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_005e; } <>1__state = -1; <>7__wrap1 = delaysSec; <>7__wrap2 = 0; goto IL_0071; IL_005e: CloakMaskManager.SweepAllowlistedLoadedTexturesForMissingMaskDumps(); <>7__wrap2++; goto IL_0071; IL_0071: if (<>7__wrap2 < <>7__wrap1.Length) { float num2 = <>7__wrap1[<>7__wrap2]; if (num2 > 0f) { <>2__current = (object)new WaitForSecondsRealtime(num2); <>1__state = 1; return true; } goto IL_005e; } <>7__wrap1 = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__22 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__22(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = null; <>1__state = 2; return true; case 2: <>1__state = -1; SSMPBridge.ResendStoredLocalColorsToServer(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__19 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public HornetCloakColorPlugin <>4__this; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__19(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; HornetCloakColorPlugin hornetCloakColorPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; 5__2++; break; } if (5__2 < 180) { if (SSMPBridge.IsAvailable) { UsernameColorHarmonyPatcher.Apply(); } if (SSMPBridge.IsAvailable && !SSMPBridge.IsRegistered) { hornetCloakColorPlugin.TryRegisterSsmpSatelliteAndLog(); } hornetCloakColorPlugin.RefreshLocalUsernameVisual(); if (SSMPBridge.IsRegistered && UsernameNetworkDelegates.TryResolveUsernameTransform != null) { hornetCloakColorPlugin.PushLocalUsernameToNetwork(); hornetCloakColorPlugin.PushLocalCloakColorToNetwork(); return false; } <>2__current = null; <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string ModVersion = "1.15.0"; 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; } internal UsernameColorConfig UsernameColorConfig { get; private set; } public static string Name => "HornetCloakColor"; public static string Version => "1.15.0"; private void Awake() { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown Instance = this; LogSource = ((BaseUnityPlugin)this).Logger; CloakPaletteConfig.Load(); float[] missingMaskDumpAllowlistSweepDelaysSec = CloakPaletteConfig.MissingMaskDumpAllowlistSweepDelaysSec; if (missingMaskDumpAllowlistSweepDelaysSec != null && missingMaskDumpAllowlistSweepDelaysSec.Length != 0) { ((MonoBehaviour)this).StartCoroutine(AllowlistMissingMaskSweepRoutine(missingMaskDumpAllowlistSweepDelaysSec)); } CloakSceneScanner.EnsureCreated(); ColorConfig = new CloakColorConfig(((BaseUnityPlugin)this).Config); UsernameColorConfig = new UsernameColorConfig(((BaseUnityPlugin)this).Config); CloakColorApplier.SetLocalSceneColor(ColorConfig.EffectiveColor); MapMaskHarmonyPatcher.Apply(); CloakSpawnHookHarmonyPatcher.Apply(); CloakTk2dHarmonyPatcher.Apply(); CloakMeshRendererEnableHarmonyPatcher.Apply(); CloakHeroDamageHarmonyPatcher.Apply(); UsernameColorHarmonyPatcher.Apply(); TryRegisterSsmpSatelliteAndLog(); ((MonoBehaviour)this).StartCoroutine(SsmpIntegrationRetryRoutine()); ColorConfig.ColorChanged += OnConfigColorChanged; UsernameColorConfig.UsernameTintChanged += OnUsernameTintConfigChanged; HeroController.OnHeroInstanceSet += new HeroSetDelegate(OnHeroInstanceSet); ((BaseUnityPlugin)this).Logger.LogInfo((object)(Name + " v1.15.0 loaded.")); } [IteratorStateMachine(typeof(d__18))] private IEnumerator AllowlistMissingMaskSweepRoutine(float[] delaysSec) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__18(0) { delaysSec = delaysSec }; } [IteratorStateMachine(typeof(d__19))] private IEnumerator SsmpIntegrationRetryRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__19(0) { <>4__this = this }; } private void TryRegisterSsmpSatelliteAndLog() { if (SSMPBridge.IsAvailable && SSMPBridge.TryRegister()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"SSMP detected — multiplayer cloak + username sync enabled."); PushLocalCloakColorToNetwork(); PushLocalUsernameToNetwork(); } } internal static void SchedulePostConnectColorResend() { if (!((Object)(object)Instance == (Object)null)) { ((MonoBehaviour)Instance).StartCoroutine(Instance.PostConnectSsmpColorResendRoutine()); } } [IteratorStateMachine(typeof(d__22))] private IEnumerator PostConnectSsmpColorResendRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__22(0); } private void PushLocalCloakColorToNetwork() { if (SSMPBridge.IsRegistered) { SSMPBridge.NotifyLocalCloakAppearanceChanged(BuildLocalCloakNetAppearance()); } } private CloakNetAppearance BuildLocalCloakNetAppearance() { return new CloakNetAppearance(ColorConfig.EffectiveColor, CloakNetAppearance.CentiFromMultiplier(ColorConfig.TextureSaturationBoost.Value)); } private void OnHeroInstanceSet(HeroController hero) { CloakColor effectiveColor = ColorConfig.EffectiveColor; CloakColorApplier.Apply(((Component)hero).gameObject, effectiveColor); CloakColorApplier.SetLocalSceneColor(effectiveColor); PushLocalCloakColorToNetwork(); PushLocalUsernameToNetworkAndVisual(); } 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); PushLocalCloakColorToNetwork(); LocalMapMaskTint.Refresh(GameManager.instance?.gameMap, color); MapMaskTint.BroadcastLocalColor(color); PushLocalUsernameToNetworkAndVisual(); } private void OnUsernameTintConfigChanged() { PushLocalUsernameToNetworkAndVisual(); } private void PushLocalUsernameToNetwork() { if (SSMPBridge.IsRegistered) { if (UsernameColorConfig.IsDisabled) { SSMPBridge.ClearLocalUsernameColorOnNetwork(); } else { SSMPBridge.NotifyLocalUsernameColorChanged(UsernameColorConfig.ResolveRgb(ColorConfig)); } } } private void RefreshLocalUsernameVisual() { UsernameTintCoordinator.ForceRefreshLocalHeroUsername(); } private void PushLocalUsernameToNetworkAndVisual() { PushLocalUsernameToNetwork(); RefreshLocalUsernameVisual(); } } internal static class SSMPBridge { public const string SSMPGuid = "ssmp"; private static bool _registered; private static Action? _notifyCloakAppearanceChanged; private static Action? _notifyUsernameColorChanged; private static Action? _clearLocalUsernameColorOnNetwork; private static Action? _resendStoredLocalColorsToServer; private static Func? _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 NotifyLocalCloakAppearanceChanged(CloakNetAppearance appearance) { if (_registered) { _notifyCloakAppearanceChanged?.Invoke(appearance); } } public static void NotifyLocalUsernameColorChanged(CloakColor color) { if (_registered) { _notifyUsernameColorChanged?.Invoke(color); } } public static void ClearLocalUsernameColorOnNetwork() { if (_registered) { _clearLocalUsernameColorOnNetwork?.Invoke(); } } public static CloakColor GetRemoteMapColorOrDefault(ushort playerId) { return _getRemoteMapColor?.Invoke(playerId) ?? CloakColor.Default; } public static void ResendStoredLocalColorsToServer() { if (_registered) { _resendStoredLocalColorsToServer?.Invoke(); } } internal static void SchedulePostConnectColorResend() { if (_registered) { HornetCloakColorPlugin.SchedulePostConnectColorResend(); } } 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("NotifyLocalCloakAppearanceChanged", BindingFlags.Static | BindingFlags.Public); if (method != null) { _notifyCloakAppearanceChanged = (Action)Delegate.CreateDelegate(typeof(Action), method); } MethodInfo method2 = type.GetMethod("GetRemoteMapColorOrDefault", BindingFlags.Static | BindingFlags.Public); if (method2 != null) { _getRemoteMapColor = (Func)Delegate.CreateDelegate(typeof(Func), method2); } MethodInfo method3 = type.GetMethod("NotifyLocalUsernameColorChanged", BindingFlags.Static | BindingFlags.Public); if (method3 != null) { _notifyUsernameColorChanged = (Action)Delegate.CreateDelegate(typeof(Action), method3); } MethodInfo method4 = type.GetMethod("ClearLocalUsernameColorOnNetwork", BindingFlags.Static | BindingFlags.Public); if (method4 != null) { _clearLocalUsernameColorOnNetwork = (Action)Delegate.CreateDelegate(typeof(Action), method4); } MethodInfo method5 = type.GetMethod("ResendStoredLocalColorsToServer", BindingFlags.Static | BindingFlags.Public); if (method5 != null) { _resendStoredLocalColorsToServer = (Action)Delegate.CreateDelegate(typeof(Action), method5); } return true; } } internal readonly struct UsernameResolveResult { internal bool Found { get; } internal bool IsLocal { get; } internal ushort PlayerId { get; } internal UsernameResolveResult(bool found, bool isLocal, ushort playerId) { Found = found; IsLocal = isLocal; PlayerId = playerId; } } internal static class UsernameNetworkDelegates { internal static Func? TryResolveUsernameTransform; internal static Func? GetRemoteUsernameTintOrNull; internal static Func? GetTeamsEnabled; internal static Func? GetServerCustomUsernameOverridesTeam; internal static Func? GetLocalPlayerTeamOrdinal; internal static void Clear() { TryResolveUsernameTransform = null; GetRemoteUsernameTintOrNull = null; GetTeamsEnabled = null; GetServerCustomUsernameOverridesTeam = null; GetLocalPlayerTeamOrdinal = null; } } public static class PluginInfo { public const string PLUGIN_GUID = "hornet.cloak.color"; public const string PLUGIN_NAME = "HornetCloakColor"; public const string PLUGIN_VERSION = "1.15.0"; } } namespace HornetCloakColor.Shared { public readonly struct CloakColor : IEquatable { 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); } } public readonly struct CloakNetAppearance : IEquatable { public CloakColor Color { get; } public byte TextureSaturationCenti { get; } public static CloakNetAppearance Default => new CloakNetAppearance(CloakColor.Default, 100); public float TextureSaturationMultiplier => (float)(int)TextureSaturationCenti / 100f; public CloakNetAppearance(CloakColor color, byte textureSaturationCenti) { Color = color; TextureSaturationCenti = textureSaturationCenti; } public static byte CentiFromMultiplier(float multiplier) { return (byte)Mathf.Clamp(Mathf.RoundToInt(multiplier * 100f), 0, 200); } public bool Equals(CloakNetAppearance other) { if (Color.Equals(other.Color)) { return TextureSaturationCenti == other.TextureSaturationCenti; } return false; } public override bool Equals(object? obj) { if (obj is CloakNetAppearance other) { return Equals(other); } return false; } public override int GetHashCode() { return (Color.GetHashCode() * 397) ^ TextureSaturationCenti; } public static bool operator ==(CloakNetAppearance a, CloakNetAppearance b) { return a.Equals(b); } public static bool operator !=(CloakNetAppearance a, CloakNetAppearance 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.Interop { public static class HornetCloakColorInterop { public static event Func? ModifyLocalCloakColorBeforeApply; public static event Func? ResolveLocalUsernameTint; public static event Func? ResolveRemoteUsernameTint; public static CloakColor ApplyCloakColorModifiers(CloakColor color) { if (HornetCloakColorInterop.ModifyLocalCloakColorBeforeApply == null) { return color; } CloakColor cloakColor = color; Delegate[] invocationList = HornetCloakColorInterop.ModifyLocalCloakColorBeforeApply.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { cloakColor = ((Func)invocationList[i])(cloakColor); } return cloakColor; } internal static Color? InvokeLocalUsernameInterop(in UsernameTintContext ctx) { return InvokeUsernameChain(HornetCloakColorInterop.ResolveLocalUsernameTint, in ctx); } internal static Color? InvokeRemoteUsernameInterop(in UsernameTintContext ctx) { return InvokeUsernameChain(HornetCloakColorInterop.ResolveRemoteUsernameTint, in ctx); } private static Color? InvokeUsernameChain(Func? chain, in UsernameTintContext ctx) { if (chain == null) { return null; } Delegate[] invocationList = chain.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Color? result = ((Func)invocationList[i])(ctx); if (result.HasValue) { return result; } } return null; } public static CloakColor GetLocalCloakColorAfterModifiers() { return HornetCloakColorPlugin.Instance?.ColorConfig.EffectiveColor ?? CloakColor.Default; } public static CloakColor GetSyncedRemoteUsernameRgbOrDefault(ushort networkPlayerId) { return UsernameNetworkDelegates.GetRemoteUsernameTintOrNull?.Invoke(networkPlayerId) ?? CloakColor.Default; } } public readonly struct UsernameTintContext { public bool IsLocalPlayer { get; } public ushort NetworkPlayerId { get; } public int TeamOrdinal { get; } public bool TeamsEnabledOnServer { get; } public bool ServerAllowsCustomUsernameOverTeam { get; } public Color ProposedColor { get; } public UsernameTintContext(bool isLocalPlayer, ushort networkPlayerId, int teamOrdinal, bool teamsEnabledOnServer, bool serverAllowsCustomUsernameOverTeam, Color proposedColor) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) IsLocalPlayer = isLocalPlayer; NetworkPlayerId = networkPlayerId; TeamOrdinal = teamOrdinal; TeamsEnabledOnServer = teamsEnabledOnServer; ServerAllowsCustomUsernameOverTeam = serverAllowsCustomUsernameOverTeam; ProposedColor = proposedColor; } } } namespace HornetCloakColor.Client { internal static class CloakColorApplier { public static void Apply(GameObject? playerObject, CloakColor color, float? textureSaturationBoostOverride = null) { if (!((Object)(object)playerObject == (Object)null)) { bool useCloakShader = (Object)(object)CloakShaderManager.Shader != (Object)null; CloakRecolor.AttachOrUpdate(playerObject, color, useCloakShader, textureSaturationBoostOverride); } } public static void SetLocalSceneColor(CloakColor color) { CloakSceneScanner.SetColor(color); CloakSpriteRendererTint.SetColor(color); } } internal class CloakColorConfig { public enum Preset { Custom, Default, Crimson, Scarlet, Amber, Gold, Emerald, Teal, Azure, Royal, Violet, Magenta, Obsidian, Ivory } [CompilerGenerated] private static class <>O { public static MenuElementGenerator <0>__TryCreateElement; } private static readonly CloakColor DefaultCustom = new CloakColor(200, 60, 60); public ConfigEntry PresetChoice { get; } public ConfigEntry CustomHex { get; } public ConfigEntry TextureSaturationBoost { get; } public CloakColor CurrentColor => Resolve(PresetChoice.Value, CustomHex.Value); public CloakColor EffectiveColor => HornetCloakColorInterop.ApplyCloakColorModifiers(CurrentColor); public event Action? ColorChanged; public CloakColorConfig(ConfigFile config) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown PresetChoice = config.Bind("Appearance", "Cloak Color Preset", Preset.Default, "Choose 'Custom' for your own hex."); CustomHex = config.Bind("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)."); AcceptableValueRange obj = new AcceptableValueRange(0f, 2f); object[] array = new object[1]; object obj2 = <>O.<0>__TryCreateElement; if (obj2 == null) { MenuElementGenerator val = CloakTextureSaturationModMenuElement.TryCreateElement; <>O.<0>__TryCreateElement = val; obj2 = (object)val; } array[0] = obj2; TextureSaturationBoost = config.Bind("Appearance", "Cloak Texture Saturation Boost", 1f, new ConfigDescription("Multiplies each masked pixel's saturation after the hue shift (1 = unchanged). Values above 1 punch up washed-out cloak art. This is not the same as your hex's HSV saturation (e.g. #00FF00 is already fully saturated as a color pick).", (AcceptableValueBase)(object)obj, array)); PresetChoice.SettingChanged += delegate { this.ColorChanged?.Invoke(EffectiveColor); }; CustomHex.SettingChanged += delegate { this.ColorChanged?.Invoke(EffectiveColor); }; TextureSaturationBoost.SettingChanged += delegate { this.ColorChanged?.Invoke(EffectiveColor); }; } internal static CloakColor ResolvePresetColor(Preset preset, string customHex) { return Resolve(preset, customHex); } 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 CloakDiskNames { public const string NoCollectionFolder = "_NoCollection"; public static string CollectionFolder(string? tk2dCollectionName) { if (!string.IsNullOrWhiteSpace(tk2dCollectionName)) { return SanitizeFileStem(tk2dCollectionName); } return "_NoCollection"; } public static string? MaskCollectionNameForLookup(string? tk2dCollectionName) { if (string.Equals(tk2dCollectionName, "Player Prefab", StringComparison.OrdinalIgnoreCase)) { return "Knight"; } return tk2dCollectionName; } public static string SanitizeFileStem(string? name) { if (string.IsNullOrEmpty(name)) { return "tex"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); StringBuilder stringBuilder = new StringBuilder(Math.Max(name.Length, 4)); foreach (char c in name) { if (Array.IndexOf(invalidFileNameChars, c) >= 0 || c < ' ') { stringBuilder.Append('_'); } else { stringBuilder.Append(c); } } string text = stringBuilder.ToString(); if (text.Length == 0 || text == "." || text == "..") { return "tex"; } return text; } } internal static class CloakHeroDamageHarmonyPatcher { private const string HarmonyId = "hornet.cloak.color.hero-damage"; private static bool _applied; internal static void Apply() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (_applied) { return; } Harmony val = new Harmony("hornet.cloak.color.hero-damage"); bool flag = false; MethodInfo[] methods = typeof(HealthManager).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "TakeDamage") && !methodInfo.IsAbstract) { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloakHeroDamageHarmonyPatcher), "AfterHeroHealthEvent", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("HornetCloakColor: patched HealthManager." + methodInfo.Name + " for post-damage cloak refresh."); flag = true; break; } } MethodInfo methodInfo2 = AccessTools.Method(typeof(HeroController), "DoSpecialDamage", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo2 != null) { val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloakHeroDamageHarmonyPatcher), "AfterHeroHealthEvent", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("HornetCloakColor: patched HeroController.DoSpecialDamage for post-damage cloak refresh."); flag = true; } if (!flag) { Log.Warn("HornetCloakColor: no damage hooks patched (API mismatch). Cloak may briefly lose tint after hazards."); } _applied = true; } private static void AfterHeroHealthEvent() { try { HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null)) { CloakRecolor.NotifyHeroPossibleSpriteRebuild(instance); } } catch (Exception ex) { Log.Warn("CloakHeroDamageHarmonyPatcher: refresh threw: " + ex.Message); } } } internal static class CloakMaskManager { [CompilerGenerated] private sealed class d__19 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private string primaryPath; public string <>3__primaryPath; private string aliasPath; public string <>3__aliasPath; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__19(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = primaryPath; <>1__state = 1; return true; case 1: <>1__state = -1; if (!string.Equals(primaryPath, aliasPath, StringComparison.OrdinalIgnoreCase)) { <>2__current = aliasPath; <>1__state = 2; return true; } break; case 2: <>1__state = -1; break; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__19 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__19(0); } d__.primaryPath = <>3__primaryPath; d__.aliasPath = <>3__aliasPath; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__20 : IEnumerable<(string path, string branch)>, IEnumerable, IEnumerator<(string path, string branch)>, IEnumerator, IDisposable { private int <>1__state; private (string path, string branch) <>2__current; private int <>l__initialThreadId; private string primaryPath; public string <>3__primaryPath; private string aliasPath; public string <>3__aliasPath; (string, string) IEnumerator<(string, string)>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__20(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (primaryPath, "primary(raw collection folder)"); <>1__state = 1; return true; case 1: <>1__state = -1; if (!string.Equals(primaryPath, aliasPath, StringComparison.OrdinalIgnoreCase)) { <>2__current = (aliasPath, "aliasCompat(e.g. Knight for Player Prefab)"); <>1__state = 2; return true; } break; case 2: <>1__state = -1; break; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<(string path, string branch)> IEnumerable<(string, string)>.GetEnumerator() { d__20 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__20(0); } d__.primaryPath = <>3__primaryPath; d__.aliasPath = <>3__aliasPath; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<(string, string)>)this).GetEnumerator(); } } private static readonly Dictionary ByMaskFilePath = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ByTextureCollectionKey = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary ByTexture2DMaskName = new Dictionary(StringComparer.OrdinalIgnoreCase); private static string? _pluginDir; private static Texture2D? _blackWeight1x1; private static readonly HashSet DumpedOriginalPaths = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet DumpedTemplatePaths = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet MaskDiagLoggedCompositeKeys = new HashSet(StringComparer.Ordinal); public static Texture2D BlackWeightMask { get { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_0044: Expected O, but got Unknown if ((Object)(object)_blackWeight1x1 != (Object)null) { return _blackWeight1x1; } Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false, true); val.SetPixel(0, 0, Color.clear); val.Apply(false, true); ((Object)val).name = "CloakMask:BlackFallback"; _blackWeight1x1 = val; return val; } } private static string PluginDir { get { if (_pluginDir != null) { return _pluginDir; } string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); _pluginDir = (string.IsNullOrEmpty(directoryName) ? string.Empty : directoryName); return _pluginDir; } } private static string TextureCollectionCacheKey(int textureInstanceId, string collectionStem) { return textureInstanceId + "\u001f" + collectionStem; } internal static bool IsMaskConfirmedAbsentForBinding(Texture mainTex, string? tk2dCollectionName) { if ((Object)(object)mainTex == (Object)null || mainTex.width <= 0) { return false; } string collectionStem = CloakDiskNames.CollectionFolder(tk2dCollectionName); string key = TextureCollectionCacheKey(((Object)mainTex).GetInstanceID(), collectionStem); if (ByTextureCollectionKey.TryGetValue(key, out Texture2D value)) { return (Object)(object)value == (Object)null; } return false; } public static void OnPaletteReloaded() { foreach (Texture2D value in ByMaskFilePath.Values) { if ((Object)(object)value != (Object)null && (Object)(object)value != (Object)(object)_blackWeight1x1) { Object.Destroy((Object)(object)value); } } ByMaskFilePath.Clear(); ByTextureCollectionKey.Clear(); ByTexture2DMaskName.Clear(); DumpedOriginalPaths.Clear(); DumpedTemplatePaths.Clear(); MaskDiagLoggedCompositeKeys.Clear(); CloakMaterialApplier.InvalidateAll(); } public static bool TryGetTexture2DMask(Texture? texture, string? spriteName, out Texture2D mask) { mask = BlackWeightMask; if (string.IsNullOrEmpty(PluginDir)) { return false; } string text = (((Object)(object)texture != (Object)null) ? ((Object)texture).name : null); if (TryGetTexture2DMaskByName(text, texture, out mask)) { return true; } if (!string.Equals(spriteName, text, StringComparison.Ordinal) && TryGetTexture2DMaskByName(spriteName, texture, out mask)) { return true; } return false; } private static bool TryGetTexture2DMaskByName(string? name, Texture? referenceTexture, out Texture2D mask) { mask = BlackWeightMask; if (string.IsNullOrWhiteSpace(name)) { return false; } string text = CloakDiskNames.SanitizeFileStem(name); string text2 = Path.Combine(PluginDir, "CloakMasks", "Texture2D", text + ".png"); if (ByTexture2DMaskName.TryGetValue(text2, out Texture2D value)) { if ((Object)(object)value == (Object)null) { return false; } mask = value; return true; } if (!File.Exists(text2)) { if ((Object)(object)referenceTexture != (Object)null && referenceTexture.width > 0 && referenceTexture.height > 0 && CloakPaletteConfig.ShouldAutoDumpMissingMask(name)) { MaybeDumpDiscoveredTextureFiles(referenceTexture, text2, createEmptyMask: true, missingMaskAutoDump: true); } ByTexture2DMaskName[text2] = null; return false; } Texture2D val = LoadMaskFromDisk(text2); if ((Object)(object)val == (Object)null) { ByTexture2DMaskName[text2] = null; return false; } if ((Object)(object)referenceTexture != (Object)null && (((Texture)val).width != referenceTexture.width || ((Texture)val).height != referenceTexture.height)) { Log.Warn($"[CloakMasks] '{text2}' size {((Texture)val).width}x{((Texture)val).height} does not match texture " + $"'{((Object)referenceTexture).name}' ({referenceTexture.width}x{referenceTexture.height}). Ignoring this SpriteRenderer mask."); Object.Destroy((Object)(object)val); ByTexture2DMaskName[text2] = null; return false; } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; ((Object)val).name = "CloakMask:Texture2D/" + text; ByTexture2DMaskName[text2] = val; if ((Object)(object)referenceTexture != (Object)null && CloakPaletteConfig.ShouldAutoDumpMissingMask(name)) { MaybeDumpDiscoveredTextureFiles(referenceTexture, text2, createEmptyMask: false); } mask = val; return true; } public static void SweepAllowlistedLoadedTexturesForMissingMaskDumps() { HashSet missingMaskDumpAllowlist = CloakPaletteConfig.MissingMaskDumpAllowlist; if (missingMaskDumpAllowlist == null || missingMaskDumpAllowlist.Count == 0 || string.IsNullOrEmpty(PluginDir) || !CloakPaletteConfig.DumpDiscoveredTextures) { return; } Texture2D[] array = Resources.FindObjectsOfTypeAll(); int num = 0; int num2 = 0; int num3 = 0; Texture2D[] array2 = array; foreach (Texture2D val in array2) { if ((Object)(object)val == (Object)null || ((Texture)val).width <= 0 || ((Texture)val).height <= 0) { continue; } string text = CloakDiskNames.SanitizeFileStem(((Object)val).name); if (text.Length != 0 && missingMaskDumpAllowlist.Contains(text)) { num2++; string text2 = Path.Combine(PluginDir, "CloakMasks", "Texture2D", text + ".png"); if (File.Exists(text2)) { num3++; } else if (CloakPaletteConfig.ShouldAutoDumpMissingMask(((Object)val).name)) { MaybeDumpDiscoveredTextureFiles((Texture)(object)val, text2, createEmptyMask: true, missingMaskAutoDump: true); num++; } } } string text3 = $"[CloakMasks] allowlistSweep: scanned {array.Length} Texture2D asset(s); " + $"allowlisted stem(s) seen in memory: {num2}; " + $"skipped {num3} (CloakMasks/Texture2D/*.png already exists); " + $"wrote {num} missing-mask dump(s)."; if (num2 == 0) { text3 += " No loaded Texture2D.name matched your allowlist — runtime names often differ from dumped PNG filenames, or those atlases only appear under tk2d (masks live in CloakMasks//.png, not Texture2D/). Trigger the effect in-game and watch [CloakMasks] missingMaskDump lines, or grep logs for the real tex name."; } Log.Info(text3); } public static bool TryGetMaskForMainTexture(Texture? mainTex, string? tk2dCollectionName, out Texture2D mask) { mask = BlackWeightMask; if ((Object)(object)mainTex == (Object)null || string.IsNullOrEmpty(PluginDir)) { return false; } if (mainTex.width <= 0 || mainTex.height <= 0) { return false; } string text = CloakDiskNames.CollectionFolder(tk2dCollectionName); string text2 = CloakDiskNames.CollectionFolder(CloakDiskNames.MaskCollectionNameForLookup(tk2dCollectionName)); int instanceID = ((Object)mainTex).GetInstanceID(); string compositeKey = TextureCollectionCacheKey(instanceID, text); bool logDetail = CloakPaletteConfig.LogMaskResolutionDiagnostics && !MaskDiagLoggedCompositeKeys.Contains(compositeKey); if (ByTextureCollectionKey.TryGetValue(compositeKey, out Texture2D value)) { if ((Object)(object)value == (Object)null) { if (logDetail) { Log.Info($"[CloakMasksDiag] cache hit — no mask for compositeKey={compositeKey} tex='{((Object)mainTex).name}' id={instanceID}"); FinishMaskDiag(); } return false; } mask = value; return true; } string text3 = CloakDiskNames.SanitizeFileStem(string.IsNullOrEmpty(((Object)mainTex).name) ? $"tex_{((Object)mainTex).GetInstanceID()}" : ((Object)mainTex).name); string path = Path.Combine(PluginDir, "CloakMasks"); string text4 = Path.Combine(path, text, text3 + ".png"); string text5 = Path.Combine(path, text2, text3 + ".png"); foreach (string item in EnumerateCandidateMaskPaths(text4, text5)) { if (ByMaskFilePath.TryGetValue(item, out Texture2D value2) && (Object)(object)value2 != (Object)null) { ByTextureCollectionKey[compositeKey] = value2; mask = value2; return true; } } if (logDetail) { string text6 = tk2dCollectionName ?? "(null)"; string text7 = (string.Equals(text, text2, StringComparison.Ordinal) ? "" : (" stemAlias='" + text2 + "'")); Log.Info($"[CloakMasksDiag] disk resolve start compositeKey={compositeKey} tex='{((Object)mainTex).name}' id={instanceID} size={mainTex.width}x{mainTex.height} collectionRaw='{text6}' stemPrimary='{text}'{text7} texStem='{text3}'"); Log.Info($"[CloakMasksDiag] primary exists={File.Exists(text4)} → {text4}"); if (!string.Equals(text4, text5, StringComparison.OrdinalIgnoreCase)) { Log.Info($"[CloakMasksDiag] aliasCompat exists={File.Exists(text5)} → {text5}"); } } Texture2D val = null; string text8 = null; string text9 = null; foreach (var (text10, text11) in EnumerateCandidateMaskPathsWithBranch(text4, text5)) { if (File.Exists(text10)) { text8 = text10; text9 = text11; break; } } if (logDetail) { Log.Info("[CloakMasksDiag] branch=" + (text9 ?? "(none)") + " resolvedPath=" + (text8 ?? "(null)")); } if (text8 != null) { val = LoadMaskFromDisk(text8); if ((Object)(object)val != (Object)null && (((Texture)val).width != mainTex.width || ((Texture)val).height != mainTex.height)) { Log.Warn($"[CloakMasks] '{text8}' size {((Texture)val).width}x{((Texture)val).height} does not match atlas " + $"'{((Object)mainTex).name}' ({mainTex.width}x{mainTex.height}). Using zero mask (no recolor) for this atlas."); if (logDetail) { Log.Info("[CloakMasksDiag] RESULT: rejected loaded mask — size mismatch after branch=" + text9); } Object.Destroy((Object)(object)val); val = null; } } if ((Object)(object)val == (Object)null) { MaybeDumpDiscoveredTextureFiles(mainTex, text4, createEmptyMask: true, CloakPaletteConfig.ShouldAutoDumpMissingMask(((Object)mainTex).name)); ByTextureCollectionKey[compositeKey] = null; if (logDetail) { Log.Info("[CloakMasksDiag] RESULT: no mask (texStem=" + text3 + ")"); FinishMaskDiag(); } return false; } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; string text12 = Path.GetFileName(Path.GetDirectoryName(text8)) ?? text; ((Object)val).name = "CloakMask:" + text12 + "/" + text3; ByMaskFilePath[text4] = val; if (!string.Equals(text4, text8, StringComparison.OrdinalIgnoreCase)) { ByMaskFilePath[text8] = val; } ByTextureCollectionKey[compositeKey] = val; MaybeDumpDiscoveredTextureFiles(mainTex, text4, createEmptyMask: false); mask = val; if (logDetail) { Log.Info("[CloakMasksDiag] RESULT: OK mask loaded bytes→texture name=" + ((Object)val).name); FinishMaskDiag(); } return true; void FinishMaskDiag() { if (logDetail) { MaskDiagLoggedCompositeKeys.Add(compositeKey); } } } [IteratorStateMachine(typeof(d__19))] private static IEnumerable EnumerateCandidateMaskPaths(string primaryPath, string aliasPath) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__19(-2) { <>3__primaryPath = primaryPath, <>3__aliasPath = aliasPath }; } [IteratorStateMachine(typeof(d__20))] private static IEnumerable<(string path, string branch)> EnumerateCandidateMaskPathsWithBranch(string primaryPath, string aliasPath) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__20(-2) { <>3__primaryPath = primaryPath, <>3__aliasPath = aliasPath }; } private static void MaybeDumpDiscoveredTextureFiles(Texture mainTex, string maskPath, bool createEmptyMask, bool missingMaskAutoDump = false) { if (!CloakPaletteConfig.DumpDiscoveredTextures) { return; } string logTag = (missingMaskAutoDump ? "missingMaskDump" : "dumpDiscoveredTextures"); string directoryName = Path.GetDirectoryName(maskPath); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(maskPath); if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(fileNameWithoutExtension)) { return; } int width = mainTex.width; int height = mainTex.height; if (width > 0 && height > 0) { Directory.CreateDirectory(directoryName); MaybeWriteOriginalTexture(mainTex, Path.Combine(directoryName, fileNameWithoutExtension + "-original.png"), width, height, logTag); if (createEmptyMask) { MaybeWriteEmptyMask(maskPath, width, height, logTag); } } } private static void MaybeWriteOriginalTexture(Texture mainTex, string outPath, int w, int h, string logTag) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (DumpedOriginalPaths.Contains(outPath) || File.Exists(outPath)) { return; } RenderTexture temporary = RenderTexture.GetTemporary(w, h, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); RenderTexture active = RenderTexture.active; try { Graphics.Blit(mainTex, temporary); RenderTexture.active = temporary; Texture2D val = new Texture2D(w, h, (TextureFormat)4, false, true); val.ReadPixels(new Rect(0f, 0f, (float)w, (float)h), 0, 0); val.Apply(false, false); File.WriteAllBytes(outPath, ImageConversion.EncodeToPNG(val)); DumpedOriginalPaths.Add(outPath); Log.Info("[CloakMasks] " + logTag + ": wrote '" + outPath + "'."); Object.Destroy((Object)(object)val); } catch (Exception ex) { Log.Warn("[CloakMasks] " + logTag + ": could not write '" + outPath + "': " + ex.Message); } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static void MaybeWriteEmptyMask(string maskPath, int w, int h, string logTag) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if (DumpedTemplatePaths.Contains(maskPath) || File.Exists(maskPath)) { return; } try { Texture2D val = new Texture2D(w, h, (TextureFormat)4, false, true); Color32[] pixels = (Color32[])(object)new Color32[w * h]; val.SetPixels32(pixels); val.Apply(false, false); File.WriteAllBytes(maskPath, ImageConversion.EncodeToPNG(val)); DumpedTemplatePaths.Add(maskPath); Log.Info("[CloakMasks] " + logTag + ": wrote empty mask template '" + maskPath + "'."); Object.Destroy((Object)(object)val); } catch (Exception ex) { Log.Warn("[CloakMasks] " + logTag + ": could not write empty mask '" + maskPath + "': " + ex.Message); } } private static Texture2D? LoadMaskFromDisk(string path) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown try { byte[] array = File.ReadAllBytes(path); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false, true); if (!ImageConversion.LoadImage(val, array)) { Object.Destroy((Object)(object)val); return null; } return val; } catch (Exception ex) { Log.Warn("[CloakMasks] Failed to read '" + path + "': " + ex.Message); return null; } } } internal static class CloakMaterialApplier { private enum AppliedMode : byte { None, CloakApplied, NoMaskRestored, VertexTinted } private readonly struct AppliedState { public readonly int SharedMatInstanceId; public readonly CloakColor Color; public readonly AppliedMode Mode; public readonly float TextureSaturationBoost; public AppliedState(int sharedMatInstanceId, CloakColor color, AppliedMode mode, float textureSaturationBoost = 0f) { SharedMatInstanceId = sharedMatInstanceId; Color = color; Mode = mode; TextureSaturationBoost = textureSaturationBoost; } } private static readonly Dictionary AppliedByRenderer = new Dictionary(); public static void InvalidateAll() { AppliedByRenderer.Clear(); } public static void InvalidateRenderer(MeshRenderer? renderer) { if (!((Object)(object)renderer == (Object)null)) { AppliedByRenderer.Remove(((Object)renderer).GetInstanceID()); } } public static void InvalidateSubtree(Transform? root) { if ((Object)(object)root == (Object)null) { return; } MeshRenderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (MeshRenderer val in componentsInChildren) { if ((Object)(object)val != (Object)null) { InvalidateRenderer(val); } } } internal static tk2dBaseSprite? ResolveTk2dSprite(MeshRenderer renderer) { if ((Object)(object)renderer == (Object)null) { return null; } Transform transform = ((Component)renderer).transform; tk2dBaseSprite component = ((Component)transform).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } if ((Object)(object)transform.parent != (Object)null) { tk2dBaseSprite component2 = ((Component)transform.parent).GetComponent(); if ((Object)(object)component2 != (Object)null) { return component2; } for (int i = 0; i < transform.parent.childCount; i++) { Transform child = transform.parent.GetChild(i); if (!((Object)(object)child == (Object)(object)transform)) { component = ((Component)child).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } } } } return ((Component)transform).GetComponentInParent(); } public static void Apply(MeshRenderer renderer, tk2dBaseSprite? sprite, CloakColor color, bool useCloakShader, Dictionary originalShaderByRenderer, float? textureSaturationBoostOverride = null) { if ((Object)(object)renderer == (Object)null) { return; } Material sharedMaterial = ((Renderer)renderer).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || !sharedMaterial.HasProperty(CloakShaderManager.MainTexId)) { return; } int instanceID = ((Object)renderer).GetInstanceID(); int instanceID2 = ((Object)sharedMaterial).GetInstanceID(); bool flag = useCloakShader && (Object)(object)CloakShaderManager.Shader != (Object)null; float num = textureSaturationBoostOverride ?? GetTextureSaturationBoost(); if (AppliedByRenderer.TryGetValue(instanceID, out var value) && value.SharedMatInstanceId == instanceID2) { if (flag) { if (value.Mode == AppliedMode.CloakApplied && value.Color.Equals(color) && Mathf.Approximately(value.TextureSaturationBoost, num)) { return; } if (value.Mode == AppliedMode.NoMaskRestored) { Texture mainTexture = sharedMaterial.mainTexture; tk2dBaseSprite val = sprite ?? ResolveTk2dSprite(renderer); string tk2dCollectionName = (((Object)(object)((val != null) ? val.Collection : null) != (Object)null) ? ((Object)val.Collection).name : null); if ((Object)(object)mainTexture != (Object)null && CloakMaskManager.IsMaskConfirmedAbsentForBinding(mainTexture, tk2dCollectionName)) { return; } } } else if (value.Mode == AppliedMode.VertexTinted && value.Color.Equals(color)) { return; } } if (sprite == null) { sprite = ResolveTk2dSprite(renderer); } Material material = ((Renderer)renderer).material; if ((Object)(object)material == (Object)null) { return; } if (flag && (Object)(object)material.mainTexture == (Object)null) { AppliedByRenderer.Remove(instanceID); return; } CloakColor color2 = color; AppliedMode appliedMode; if (flag) { string tk2dCollectionName2 = (((Object)(object)((sprite != null) ? sprite.Collection : null) != (Object)null) ? ((Object)sprite.Collection).name : null); if (!CloakMaskManager.TryGetMaskForMainTexture(material.mainTexture, tk2dCollectionName2, out Texture2D mask)) { RestoreOriginalShader(renderer, material, originalShaderByRenderer); appliedMode = AppliedMode.NoMaskRestored; color2 = default(CloakColor); } else { EnsureCloakShader(renderer, material, originalShaderByRenderer); ApplyShaderProperties(material, sprite, color, mask, num); appliedMode = AppliedMode.CloakApplied; } } else { RestoreOriginalShader(renderer, material, originalShaderByRenderer); ApplyVertexTint(material, sprite, color); appliedMode = AppliedMode.VertexTinted; } Material sharedMaterial2 = ((Renderer)renderer).sharedMaterial; if ((Object)(object)sharedMaterial2 != (Object)null) { float textureSaturationBoost = ((appliedMode == AppliedMode.CloakApplied) ? num : 0f); AppliedByRenderer[instanceID] = new AppliedState(((Object)sharedMaterial2).GetInstanceID(), color2, appliedMode, textureSaturationBoost); } } public static void Restore(MeshRenderer renderer, Dictionary originalShaderByRenderer) { if ((Object)(object)renderer == (Object)null) { return; } Material sharedMaterial = ((Renderer)renderer).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || !sharedMaterial.HasProperty(CloakShaderManager.MainTexId)) { return; } int instanceID = ((Object)renderer).GetInstanceID(); int instanceID2 = ((Object)sharedMaterial).GetInstanceID(); if (AppliedByRenderer.TryGetValue(instanceID, out var value) && value.SharedMatInstanceId == instanceID2 && value.Mode == AppliedMode.NoMaskRestored) { return; } Material material = ((Renderer)renderer).material; if (!((Object)(object)material == (Object)null)) { RestoreOriginalShader(renderer, material, originalShaderByRenderer); Material sharedMaterial2 = ((Renderer)renderer).sharedMaterial; if ((Object)(object)sharedMaterial2 != (Object)null) { AppliedByRenderer[instanceID] = new AppliedState(((Object)sharedMaterial2).GetInstanceID(), default(CloakColor), AppliedMode.NoMaskRestored); } } } private static void EnsureCloakShader(MeshRenderer renderer, Material mat, Dictionary 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 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, tk2dBaseSprite? sprite, CloakColor color, Texture2D mask, float satBoost) { if (color.Equals(CloakColor.Default)) { mat.SetFloat(CloakShaderManager.StrengthId, 0f); mat.SetTexture(CloakShaderManager.CloakMaskTexId, (Texture)(object)mask); 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 : satBoost); mat.SetFloat(CloakShaderManager.TargetValId, Mathf.Lerp(0.6f, 1.4f, v)); mat.SetFloat(CloakShaderManager.StrengthId, 1f); mat.SetTexture(CloakShaderManager.CloakMaskTexId, (Texture)(object)mask); } internal static float GetTextureSaturationBoost() { HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance; if (instance?.ColorConfig == null) { return 1f; } return Mathf.Clamp(instance.ColorConfig.TextureSaturationBoost.Value, 0f, 2f); } internal static bool Tk2dSpriteVertexColorsSafeToMutate(tk2dBaseSprite sprite) { if ((Object)(object)sprite == (Object)null) { return false; } MeshFilter component = ((Component)sprite).GetComponent(); Mesh val = (((Object)(object)component != (Object)null) ? component.sharedMesh : null); if ((Object)(object)val == (Object)null) { return false; } int vertexCount = val.vertexCount; if (vertexCount <= 0) { return false; } Color[] colors = val.colors; if (colors != null && colors.Length != 0 && colors.Length != vertexCount) { return false; } return true; } private static void ApplyVertexTint(Material mat, tk2dBaseSprite? 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_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) Color color2 = (mat.color = color.ToUnityColor()); if ((Object)(object)sprite != (Object)null && Tk2dSpriteVertexColorsSafeToMutate(sprite)) { sprite.color = color2; } } public static void PruneDestroyed(Dictionary map) { if (map.Count == 0) { return; } List list = null; foreach (MeshRenderer key in map.Keys) { if (!Object.op_Implicit((Object)(object)key)) { (list ?? (list = new List())).Add(key); } } if (list == null) { return; } foreach (MeshRenderer item in list) { map.Remove(item); } } } internal static class CloakMeshRendererEnableHarmonyPatcher { private const string HarmonyId = "hornet.cloak.color.renderer-visibility"; private static bool _applied; private static int _visibilityHookDepth; private static readonly List SetActiveMeshScratch = new List(); 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_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown if (_applied) { return; } _applied = true; Harmony val = new Harmony("hornet.cloak.color.renderer-visibility"); bool flag = false; MethodInfo methodInfo = typeof(Renderer).GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public)?.GetSetMethod(nonPublic: true); if (methodInfo != null) { try { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(CloakMeshRendererEnableHarmonyPatcher), "Renderer_Enabled_Setter_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("HornetCloakColor: hooked Renderer.enabled setter for same-frame hero cloak tint."); flag = true; } catch (Exception ex) { Log.Warn("CloakMeshRendererEnableHarmonyPatcher: Renderer.enabled setter patch failed (" + ex.GetType().Name + ": " + ex.Message + ")"); } } else { Log.Warn("CloakMeshRendererEnableHarmonyPatcher: Renderer.enabled setter not found."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(GameObject), "SetActive", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo2 != null) { try { val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(CloakMeshRendererEnableHarmonyPatcher), "GameObject_SetActive_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("HornetCloakColor: hooked GameObject.SetActive for hero cloak tint when subtrees activate."); flag = true; } catch (Exception ex2) { Log.Warn("CloakMeshRendererEnableHarmonyPatcher: GameObject.SetActive patch failed (" + ex2.GetType().Name + ": " + ex2.Message + ")"); } } if (!flag) { Log.Warn("CloakMeshRendererEnableHarmonyPatcher: no visibility hooks applied — Witch/dash cloak tint may lag until LateUpdate/pre-render."); } } private static void Renderer_Enabled_Setter_Postfix(Renderer __instance, bool value) { if (!value) { return; } MeshRenderer val = (MeshRenderer)(object)((__instance is MeshRenderer) ? __instance : null); if (val == null || _visibilityHookDepth > 0) { return; } _visibilityHookDepth++; try { TryRefreshUnderRecolor(val); } finally { _visibilityHookDepth--; } } private static void GameObject_SetActive_Postfix(GameObject __instance, bool value) { if (!value || (Object)(object)__instance == (Object)null || _visibilityHookDepth > 0) { return; } _visibilityHookDepth++; try { CloakRecolor componentInParent = __instance.GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return; } SetActiveMeshScratch.Clear(); __instance.GetComponentsInChildren(true, SetActiveMeshScratch); foreach (MeshRenderer item in SetActiveMeshScratch) { if ((Object)(object)item != (Object)null) { componentInParent.RefreshMeshRendererNow(item); } } } catch (Exception ex) { Log.Warn("CloakMeshRendererEnableHarmonyPatcher: SetActive postfix threw on '" + ((Object)__instance).name + "': " + ex.Message); } finally { _visibilityHookDepth--; } } private static void TryRefreshUnderRecolor(MeshRenderer meshRenderer) { try { CloakRecolor componentInParent = ((Component)meshRenderer).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null)) { componentInParent.RefreshMeshRendererNow(meshRenderer); } } catch (Exception ex) { Log.Warn("CloakMeshRendererEnableHarmonyPatcher: enabled postfix threw on '" + ((Object)meshRenderer).name + "': " + ex.Message); } } } internal static class CloakPaletteConfig { public static bool DebugLogging { get; private set; } public static bool MaskResolutionDebugLogging { get; private set; } internal static bool LogMaskResolutionDiagnostics { get { if (!DebugLogging) { return MaskResolutionDebugLogging; } return true; } } public static bool MapIconDebugLogging { get; private set; } public static bool LogMapIconDiagnostics { get { if (!DebugLogging) { return MapIconDebugLogging; } return true; } } public static bool DumpDiscoveredTextures { get; private set; } public static HashSet? MissingMaskDumpAllowlist { get; private set; } public static float[]? MissingMaskDumpAllowlistSweepDelaysSec { get; private set; } public static int HeroMeshRescanIntervalFrames { get; private set; } public static void Load() { ApplyDefaults(); string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (!string.IsNullOrEmpty(directoryName)) { string text = Path.Combine(directoryName, "cloak_palette.json"); if (File.Exists(text)) { try { if (TryApplyPaletteJson(File.ReadAllText(text))) { Log.Info("Loaded cloak runtime config from " + text + "."); if (MapIconDebugLogging) { Log.Info("[MapIcon] mapIconDebugLogging is true — tracing map/compass sync; grep log for \"[MapIcon]\"."); } if (MaskResolutionDebugLogging) { Log.Info("[CloakMasksDiag] maskResolutionDebugLogging is true — tracing mask path resolution; grep \"[CloakMasksDiag]\"."); } } 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."); } } } CloakMaskManager.OnPaletteReloaded(); LogMissingMaskDumpConfigSummary(); } private static void ApplyDefaults() { DebugLogging = false; MaskResolutionDebugLogging = false; MapIconDebugLogging = false; HeroMeshRescanIntervalFrames = 4; DumpDiscoveredTextures = false; MissingMaskDumpAllowlist = null; MissingMaskDumpAllowlistSweepDelaysSec = null; } private static void LogMissingMaskDumpConfigSummary() { int num = MissingMaskDumpAllowlist?.Count ?? 0; float[] missingMaskDumpAllowlistSweepDelaysSec = MissingMaskDumpAllowlistSweepDelaysSec; string arg = ((missingMaskDumpAllowlistSweepDelaysSec == null || missingMaskDumpAllowlistSweepDelaysSec.Length == 0) ? "sweep=off" : ("sweep=" + string.Join(",", missingMaskDumpAllowlistSweepDelaysSec) + "s")); Log.Info($"[CloakMasks] texture dump policy: dumpDiscoveredTextures={DumpDiscoveredTextures} " + $"(off = no PNG dumps); allowlist={num} stems, {arg} " + "(when dumps on: allowlist + stem match → missing-mask dump; else no missing-mask dump)."); } internal static bool ShouldAutoDumpMissingMask(string? textureName) { string text = NormalizeMaskDumpStem(textureName); if (text.Length == 0) { return false; } if (MissingMaskDumpAllowlist != null && MissingMaskDumpAllowlist.Count > 0) { return MissingMaskDumpAllowlist.Contains(text); } return false; } internal static string NormalizeMaskDumpStem(string? textureName) { if (string.IsNullOrWhiteSpace(textureName)) { return string.Empty; } string text = textureName.Trim(); if (text.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { string text2 = text; text = text2.Substring(0, text2.Length - 4); } return CloakDiskNames.SanitizeFileStem(text); } private static bool TryApplyPaletteJson(string json) { if (string.IsNullOrWhiteSpace(json)) { return false; } string text = json.TrimStart(); if (!text.StartsWith("{", StringComparison.Ordinal)) { return false; } if (TryExtractBool(text, "debugLogging", out var value)) { DebugLogging = value; } if (TryExtractBool(text, "maskResolutionDebugLogging", out var value2)) { MaskResolutionDebugLogging = value2; } if (TryExtractBool(text, "mapIconDebugLogging", out var value3)) { MapIconDebugLogging = value3; } if (TryExtractInt(text, "heroMeshRescanIntervalFrames", out var value4) && value4 > 0 && value4 <= 600) { HeroMeshRescanIntervalFrames = value4; } if (TryExtractBool(text, "dumpDiscoveredTextures", out var value5)) { DumpDiscoveredTextures = value5; } if (TryExtractStringArray(text, "missingMaskDumpAllowlist", out string[] values)) { MissingMaskDumpAllowlist = new HashSet(StringComparer.OrdinalIgnoreCase); string[] array = values; for (int i = 0; i < array.Length; i++) { string text2 = NormalizeMaskDumpStem(array[i]); if (text2.Length > 0) { MissingMaskDumpAllowlist.Add(text2); } } } if (TryExtractFloatArray(text, "missingMaskDumpAllowlistSweepDelaysSec", out float[] values2)) { MissingMaskDumpAllowlistSweepDelaysSec = ((values2.Length != 0) ? values2 : null); } return true; } private static bool TryExtractInt(string json, string key, out int value) { value = 0; string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*(?-?[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 bool TryExtractBool(string json, string key, out bool value) { value = false; string pattern = "\"" + Regex.Escape(key) + "\"\\s*:\\s*(?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 TryExtractStringArray(string json, string key, out string[] values) { values = Array.Empty(); if (!TryExtractJsonArraySlice(json, key, out string inner)) { return false; } List list = new List(); foreach (Match item in Regex.Matches(inner, "\"((?:\\\\.|[^\"\\\\])*)\"", RegexOptions.CultureInvariant)) { list.Add(UnescapeJsonString(item.Groups[1].Value)); } values = list.ToArray(); return true; } private static bool TryExtractFloatArray(string json, string key, out float[] values) { values = Array.Empty(); if (!TryExtractJsonArraySlice(json, key, out string inner)) { return false; } string[] array = inner.Split(','); List list = new List(array.Length); string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text = array2[i].Trim(); if (text.Length != 0) { if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return false; } list.Add(result); } } values = list.ToArray(); return true; } private static bool TryExtractJsonArraySlice(string json, string key, out string inner) { inner = string.Empty; string text = "\"" + key + "\""; int num = json.IndexOf(text, StringComparison.Ordinal); if (num < 0) { return false; } int num2 = json.IndexOf('[', num + text.Length); if (num2 < 0) { return false; } int num3 = 0; int num4 = -1; for (int i = num2; i < json.Length; i++) { switch (json[i]) { case '[': num3++; continue; case ']': num3--; if (num3 != 0) { continue; } break; default: continue; } num4 = i; break; } if (num4 < 0) { return false; } inner = json.Substring(num2 + 1, num4 - num2 - 1); return true; } private static string UnescapeJsonString(string s) { if (string.IsNullOrEmpty(s) || s.IndexOf('\\') < 0) { return s; } return s.Replace("\\\"", "\"", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); } } [DefaultExecutionOrder(32000)] [DisallowMultipleComponent] internal class CloakRecolor : MonoBehaviour { [CompilerGenerated] private static class <>O { public static CameraCallback <0>__OnBuiltInCameraPreRender; public static Action <1>__OnSrpBeginCameraRendering; } private static readonly List ActiveInstances = new List(); private static byte _preDrawHookRegistration; private static int _lastPreDrawTintFrame = -1; private float? _textureSaturationBoostOverride; private readonly Dictionary _originalShaderByRenderer = new Dictionary(); private readonly List _meshCache = new List(); private readonly HashSet _meshCacheSet = new HashSet(); private int _meshRescanCountdown; private bool _meshCacheInvalid = true; public CloakColor Color { get; private set; } = CloakColor.Default; public bool UseCloakShader { get; private set; } = true; internal void ApplyFromTk2dPipeline(tk2dBaseSprite sprite) { MeshRenderer component = ((Component)sprite).GetComponent(); if (!((Object)(object)component == (Object)null)) { EnsureMeshRendererTracked(component); CloakMaterialApplier.InvalidateRenderer(component); CloakMaterialApplier.Apply(component, sprite, Color, UseCloakShader, _originalShaderByRenderer, _textureSaturationBoostOverride); } } internal static void NotifyHeroPossibleSpriteRebuild(HeroController? hero) { if (!((Object)(object)hero == (Object)null)) { ((Component)hero).GetComponent()?.ForceHierarchyRefresh(); } } private void OnEnable() { _meshCacheInvalid = true; if (!ActiveInstances.Contains(this)) { ActiveInstances.Add(this); } EnsurePreDrawTintHooks(); } private void OnDisable() { ActiveInstances.Remove(this); TeardownPreDrawTintHooksIfIdle(); } private static void EnsurePreDrawTintHooks() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (_preDrawHookRegistration == 0) { CameraCallback onPreRender = Camera.onPreRender; object obj = <>O.<0>__OnBuiltInCameraPreRender; if (obj == null) { CameraCallback val = OnBuiltInCameraPreRender; <>O.<0>__OnBuiltInCameraPreRender = val; obj = (object)val; } Camera.onPreRender = (CameraCallback)Delegate.Combine((Delegate?)(object)onPreRender, (Delegate?)obj); _preDrawHookRegistration |= 1; if ((Object)(object)GraphicsSettings.defaultRenderPipeline != (Object)null) { RenderPipelineManager.beginCameraRendering += OnSrpBeginCameraRendering; _preDrawHookRegistration |= 2; } } } private static void TeardownPreDrawTintHooksIfIdle() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (ActiveInstances.Count > 0 || _preDrawHookRegistration == 0) { return; } if (((uint)_preDrawHookRegistration & (true ? 1u : 0u)) != 0) { CameraCallback onPreRender = Camera.onPreRender; object obj = <>O.<0>__OnBuiltInCameraPreRender; if (obj == null) { CameraCallback val = OnBuiltInCameraPreRender; <>O.<0>__OnBuiltInCameraPreRender = val; obj = (object)val; } Camera.onPreRender = (CameraCallback)Delegate.Remove((Delegate?)(object)onPreRender, (Delegate?)obj); } if ((_preDrawHookRegistration & 2u) != 0) { RenderPipelineManager.beginCameraRendering -= OnSrpBeginCameraRendering; } _preDrawHookRegistration = 0; } private static void OnBuiltInCameraPreRender(Camera cam) { if (IsGameplayCameraForPreDrawTint(cam)) { RunPreDrawTintPass(); } } private static void OnSrpBeginCameraRendering(ScriptableRenderContext _, Camera cam) { if (IsGameplayCameraForPreDrawTint(cam)) { RunPreDrawTintPass(); } } private static bool IsGameplayCameraForPreDrawTint(Camera? cam) { if ((Object)(object)cam == (Object)null || !((Behaviour)cam).enabled) { return false; } Camera main = Camera.main; if ((Object)(object)main != (Object)null) { return cam == main; } return true; } private static void RunPreDrawTintPass() { int frameCount = Time.frameCount; if (frameCount == _lastPreDrawTintFrame) { return; } _lastPreDrawTintFrame = frameCount; for (int num = ActiveInstances.Count - 1; num >= 0; num--) { CloakRecolor cloakRecolor = ActiveInstances[num]; if (!Object.op_Implicit((Object)(object)cloakRecolor)) { ActiveInstances.RemoveAt(num); } else { CloakMaterialApplier.PruneDestroyed(cloakRecolor._originalShaderByRenderer); cloakRecolor.ApplyToCachedMeshRenderersCore(); } } } internal void ForceHierarchyRefresh() { _meshCacheInvalid = true; RebuildMeshCache(); CloakMaterialApplier.InvalidateSubtree(((Component)this).transform); ApplyToCachedMeshRenderersCore(); } private void LateUpdate() { CloakMaterialApplier.PruneDestroyed(_originalShaderByRenderer); MaybeRefreshMeshCache(); ApplyToCachedMeshRenderersCore(); } public void Configure(CloakColor color, bool useCloakShader, float? textureSaturationBoostOverride = null) { Color = color; UseCloakShader = useCloakShader; _textureSaturationBoostOverride = textureSaturationBoostOverride; CloakSceneScanner.EnsureCreated(); CloakSceneScanner.ReleaseEligibleUnderTransform(((Component)this).transform); _meshCacheInvalid = true; RebuildMeshCache(); CloakMaterialApplier.InvalidateSubtree(((Component)this).transform); 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(); _meshCacheSet.Clear(); MeshRenderer[] componentsInChildren = ((Component)this).GetComponentsInChildren(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); _meshCacheSet.Add(val); } } } private void EnsureMeshRendererTracked(MeshRenderer meshRenderer) { if (!((Object)(object)meshRenderer == (Object)null) && !IsUnderSsmpUsernameObject(((Component)meshRenderer).transform) && _meshCacheSet.Add(meshRenderer)) { _meshCache.Add(meshRenderer); } } internal void RefreshMeshRendererNow(MeshRenderer mr) { if (!((Object)(object)mr == (Object)null) && !IsUnderSsmpUsernameObject(((Component)mr).transform)) { EnsureMeshRendererTracked(mr); CloakMaterialApplier.InvalidateRenderer(mr); CloakMaterialApplier.Apply(mr, CloakMaterialApplier.ResolveTk2dSprite(mr), Color, UseCloakShader, _originalShaderByRenderer, _textureSaturationBoostOverride); } } private void ApplyToCachedMeshRenderersCore() { foreach (MeshRenderer item in _meshCache) { if ((Object)(object)item == (Object)null) { _meshCacheInvalid = true; } else if (!IsUnderSsmpUsernameObject(((Component)item).transform)) { CloakMaterialApplier.Apply(item, null, Color, UseCloakShader, _originalShaderByRenderer, _textureSaturationBoostOverride); } } } 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, float? textureSaturationBoostOverride = null) { if ((Object)(object)playerObject == (Object)null) { return null; } CloakRecolor cloakRecolor = playerObject.GetComponent(); if ((Object)(object)cloakRecolor == (Object)null) { cloakRecolor = playerObject.AddComponent(); } cloakRecolor.Configure(color, useCloakShader, textureSaturationBoostOverride); return cloakRecolor; } } [DefaultExecutionOrder(10000)] [DisallowMultipleComponent] internal class CloakSceneScanner : MonoBehaviour { [CompilerGenerated] private sealed class d__29 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public CloakSceneScanner <>4__this; private Stopwatch 5__2; private SpriteRenderer[] <>7__wrap2; private int <>7__wrap3; private tk2dSprite[] <>7__wrap4; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__29(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>7__wrap2 = null; <>7__wrap4 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; CloakSceneScanner cloakSceneScanner = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; 5__2 = Stopwatch.StartNew(); SpriteRenderer[] array2 = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); if (array2 != null) { <>7__wrap2 = array2; <>7__wrap3 = 0; goto IL_00c5; } goto IL_00dc; } case 1: <>1__state = -1; 5__2.Restart(); goto IL_00b7; case 2: { <>1__state = -1; tk2dSprite[] array = Object.FindObjectsByType((FindObjectsSortMode)0); if (array == null) { break; } <>7__wrap4 = array; <>7__wrap3 = 0; goto IL_0185; } case 3: { <>1__state = -1; 5__2.Restart(); goto IL_0177; } IL_0185: if (<>7__wrap3 < <>7__wrap4.Length) { tk2dSprite val = <>7__wrap4[<>7__wrap3]; if ((Object)(object)val != (Object)null) { cloakSceneScanner.TryEnrollEligible((tk2dBaseSprite)(object)val, "Rescan"); } if (5__2.Elapsed.TotalMilliseconds > 1.5) { <>2__current = null; <>1__state = 3; return true; } goto IL_0177; } <>7__wrap4 = null; break; IL_00dc: <>2__current = null; <>1__state = 2; return true; IL_00b7: <>7__wrap3++; goto IL_00c5; IL_00c5: if (<>7__wrap3 < <>7__wrap2.Length) { SpriteRenderer val2 = <>7__wrap2[<>7__wrap3]; if ((Object)(object)val2 != (Object)null) { CloakSpriteRendererTint.Watch(val2); } if (5__2.Elapsed.TotalMilliseconds > 1.5) { <>2__current = null; <>1__state = 1; return true; } goto IL_00b7; } <>7__wrap2 = null; goto IL_00dc; IL_0177: <>7__wrap3++; goto IL_0185; } cloakSceneScanner._rebuildCo = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const float RescanIntervalSec = 2f; private const double RebuildBudgetMs = 1.5; private CloakColor _color = CloakColor.Default; private readonly Dictionary _originalShaderByRenderer = new Dictionary(); private readonly HashSet _loggedTextureIds = new HashSet(); private readonly HashSet _loggedRendererIds = new HashSet(); private readonly List _eligibleCache = new List(); private readonly Dictionary _eligibleSprite = new Dictionary(); private readonly HashSet _eligibleSet = new HashSet(); private bool _eligibleCacheDirty = true; private float _nextRescanTime; private Coroutine? _rebuildCo; 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(); } } internal static void ReleaseEligibleUnderTransform(Transform? heroRoot) { if (!((Object)(object)Instance == (Object)null) && !((Object)(object)heroRoot == (Object)null)) { Instance.RemoveEligibleUnderRoot(heroRoot); } } public static void SetColor(CloakColor color) { EnsureCreated(); Instance._color = color; CloakMaterialApplier.InvalidateAll(); } private void OnEnable() { SceneManager.activeSceneChanged += OnActiveSceneChanged; } private void OnDisable() { SceneManager.activeSceneChanged -= OnActiveSceneChanged; } private void OnActiveSceneChanged(Scene from, Scene to) { _eligibleCacheDirty = true; CloakMaterialApplier.InvalidateAll(); if (_rebuildCo != null) { ((MonoBehaviour)this).StopCoroutine(_rebuildCo); _rebuildCo = null; } _eligibleCache.Clear(); _eligibleSet.Clear(); _eligibleSprite.Clear(); } private void LateUpdate() { CloakMaterialApplier.PruneDestroyed(_originalShaderByRenderer); float realtimeSinceStartup = Time.realtimeSinceStartup; if (_rebuildCo == null && (_eligibleCacheDirty || realtimeSinceStartup >= _nextRescanTime)) { _eligibleCacheDirty = false; _nextRescanTime = realtimeSinceStartup + 2f; _rebuildCo = ((MonoBehaviour)this).StartCoroutine(RebuildEligibleCacheCo()); } ApplyEligibleCache(); } private void RemoveEligibleUnderRoot(Transform root) { for (int num = _eligibleCache.Count - 1; num >= 0; num--) { MeshRenderer val = _eligibleCache[num]; if ((Object)(object)val == (Object)null) { _eligibleCache.RemoveAt(num); } else if (IsTransformUnderAncestor(((Component)val).transform, root)) { _eligibleCache.RemoveAt(num); _eligibleSet.Remove(val); _eligibleSprite.Remove(val); CloakMaterialApplier.InvalidateRenderer(val); } } } internal static void OnSpriteSpawned(tk2dSprite sprite) { if (!((Object)(object)Instance == (Object)null) && !((Object)(object)sprite == (Object)null)) { Instance.TryEnrollEligible((tk2dBaseSprite)(object)sprite, "Spawn"); } } internal static void OnTk2dPipelineComplete(tk2dBaseSprite sprite) { if ((Object)(object)Instance == (Object)null || (Object)(object)sprite == (Object)null || (Object)(object)((Component)sprite).GetComponentInParent() != (Object)null || IsUnderSsmpPlayerContainer(((Component)sprite).transform)) { return; } MeshRenderer component = ((Component)sprite).GetComponent(); if (!((Object)(object)component == (Object)null)) { CloakMaterialApplier.InvalidateRenderer(component); if (Instance._eligibleSet.Contains(component)) { Instance._eligibleSprite[component] = sprite; CloakMaterialApplier.Apply(component, sprite, Instance._color, useCloakShader: true, Instance._originalShaderByRenderer); } else { Instance.TryEnrollEligible(sprite, "Tk2dPipeline"); } } } private static bool TryEvaluateScannerEligibility(MeshRenderer renderer, tk2dBaseSprite sprite, out bool wouldLogMissingMask) { wouldLogMissingMask = false; if ((Object)(object)renderer == (Object)null || (Object)(object)sprite == (Object)null) { return false; } Material sharedMaterial = ((Renderer)renderer).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null) { return false; } if (!sharedMaterial.HasProperty(CloakShaderManager.MainTexId)) { return false; } Texture mainTexture = sharedMaterial.mainTexture; if ((Object)(object)mainTexture == (Object)null) { return false; } if (IsUnderSsmpPlayerContainer(((Component)renderer).transform)) { return false; } if (IsUnderLocalHero(((Component)renderer).transform)) { return false; } if ((Object)(object)((Component)renderer).GetComponentInParent() != (Object)null) { return false; } if (IsCompassIcon(((Component)renderer).transform)) { return false; } string tk2dCollectionName = (((Object)(object)sprite.Collection != (Object)null) ? (((Object)sprite.Collection).name ?? string.Empty) : string.Empty); if (CloakMaskManager.TryGetMaskForMainTexture(mainTexture, tk2dCollectionName, out Texture2D _)) { return true; } wouldLogMissingMask = true; return false; } private void TryEnrollEligible(tk2dBaseSprite sprite, string source) { MeshRenderer component = ((Component)sprite).GetComponent(); if ((Object)(object)component == (Object)null || _eligibleSet.Contains(component)) { return; } if (!TryEvaluateScannerEligibility(component, sprite, out var wouldLogMissingMask)) { if (wouldLogMissingMask) { MaybeLogIgnoredTexture(component, sprite); } return; } _eligibleSet.Add(component); _eligibleCache.Add(component); _eligibleSprite[component] = sprite; if (CloakPaletteConfig.DebugLogging && _loggedRendererIds.Add(((Object)component).GetInstanceID())) { Texture val = (((Object)(object)((Renderer)component).sharedMaterial != (Object)null) ? ((Renderer)component).sharedMaterial.mainTexture : null); string arg = (((Object)(object)val != (Object)null) ? ((Object)val).name : "(null)"); int num = (((Object)(object)val != (Object)null) ? ((Object)val).GetInstanceID() : 0); string arg2 = (((Object)(object)sprite.Collection != (Object)null) ? (((Object)sprite.Collection).name ?? string.Empty) : string.Empty); Log.Info("[Scanner/" + source + "] Tinting masked orphan renderer '" + FormatTransformPath(((Component)component).transform) + "' " + $"(tex={arg}, collection='{arg2}', id={num})"); } CloakMaterialApplier.Apply(component, sprite, _color, useCloakShader: true, _originalShaderByRenderer); } private void MaybeLogIgnoredTexture(MeshRenderer renderer, tk2dBaseSprite sprite) { if (!CloakPaletteConfig.DebugLogging) { return; } Material sharedMaterial = ((Renderer)renderer).sharedMaterial; if (!((Object)(object)sharedMaterial == (Object)null) && sharedMaterial.HasProperty(CloakShaderManager.MainTexId)) { Texture mainTexture = sharedMaterial.mainTexture; if (!((Object)(object)mainTexture == (Object)null) && _loggedTextureIds.Add(((Object)mainTexture).GetInstanceID())) { string text = (((Object)(object)sprite.Collection != (Object)null) ? (((Object)sprite.Collection).name ?? string.Empty) : string.Empty); Log.Info("[Scanner] Ignored texture (no CloakMasks PNG): " + ((Object)mainTexture).name + " " + $"(id={((Object)mainTexture).GetInstanceID()}) on '{FormatTransformPath(((Component)renderer).transform)}' " + "(collection='" + text + "')"); } } } [IteratorStateMachine(typeof(d__29))] private IEnumerator RebuildEligibleCacheCo() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__29(0) { <>4__this = this }; } private void ApplyEligibleCache() { for (int num = _eligibleCache.Count - 1; num >= 0; num--) { MeshRenderer val = _eligibleCache[num]; if ((Object)(object)val == (Object)null) { _eligibleCache.RemoveAt(num); _eligibleSet.Remove(val); _eligibleSprite.Remove(val); } else { _eligibleSprite.TryGetValue(val, out tk2dBaseSprite value); CloakMaterialApplier.Apply(val, value, _color, useCloakShader: true, _originalShaderByRenderer); } } } 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 IsTransformUnderAncestor(Transform t, Transform ancestor) { Transform val = t; while ((Object)(object)val != (Object)null) { if ((Object)(object)val == (Object)(object)ancestor) { return true; } val = val.parent; } return false; } private static bool IsUnderLocalHero(Transform t) { HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { return false; } return IsTransformUnderAncestor(t, instance.transform); } private static bool IsUnderSsmpPlayerContainer(Transform t) { Transform val = t; while ((Object)(object)val != (Object)null) { if (((Object)val).name.StartsWith("Player Container ", StringComparison.Ordinal)) { return true; } val = val.parent; } return false; } internal static string FormatTransformPath(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 static AssetBundle? _bundle; private static Shader? _shader; 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 StrengthId = Shader.PropertyToID("_Strength"); public static readonly int CloakMaskTexId = Shader.PropertyToID("_CloakMaskTex"); public static readonly int MainTexId = Shader.PropertyToID("_MainTex"); private static bool _bundleInitialized; public static Shader? Shader { get { EnsureInitialized(); return _shader; } } public static bool BundleMissing { get { if (_bundleInitialized) { return (Object)(object)_shader == (Object)null; } return false; } } private static string? ShaderBundleResourceNameForCurrentOs() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return "windows/cloakshader.bundle"; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return "linux/cloakshader.bundle"; } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return "mac/cloakshader.bundle"; } return null; } private static void EnsureInitialized() { if (_bundleInitialized) { return; } _bundleInitialized = true; Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = ShaderBundleResourceNameForCurrentOs(); if (text == null) { Log.Warn("Cloak shader bundle not loaded (unsupported OS — expected Windows, Linux, or macOS). Cloak-only recolor disabled; falling back to whole-character tint."); return; } using Stream stream = executingAssembly.GetManifestResourceStream(text); if (stream == null) { Log.Warn("Cloak shader bundle not embedded (" + text + "). Cloak-only recolor disabled; falling back to whole-character tint."); return; } byte[] array; using (MemoryStream memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } try { _bundle = AssetBundle.LoadFromMemory(array); } catch (Exception arg) { Log.Error($"Failed to load cloak shader bundle: {arg}"); return; } if ((Object)(object)_bundle == (Object)null) { Log.Warn("AssetBundle.LoadFromMemory returned null for the cloak shader bundle."); return; } _shader = TryLoadCloakHueShift(_bundle); if ((Object)(object)_shader != (Object)null) { Log.Info("Loaded cloak shader '" + ((Object)_shader).name + "' from embedded bundle."); } else { Log.Warn("Cloak shader not found in embedded bundle (expected 'CloakHueShift' or 'HornetCloakColor/CloakHueShift'). Rebuild the bundle from Shaders/."); } } private static Shader? TryLoadCloakHueShift(AssetBundle bundle) { Shader val = bundle.LoadAsset("CloakHueShift"); if ((Object)(object)val != (Object)null) { return val; } val = bundle.LoadAsset("HornetCloakColor/CloakHueShift"); if ((Object)(object)val != (Object)null) { return val; } Shader[] array = bundle.LoadAllAssets(); 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 CloakSpawnHookHarmonyPatcher { private const string HarmonyId = "hornet.cloak.color.spawn-hook"; 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown if (!_applied) { _applied = true; Harmony val = new Harmony("hornet.cloak.color.spawn-hook"); Type typeFromHandle = typeof(tk2dSprite); MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "Awake", (Type[])null, (Type[])null); if (methodInfo == null) { Log.Warn("CloakSpawnHookHarmonyPatcher: tk2dSprite.Awake not found; spawn-hook disabled. The 2-second backstop scan still picks up new sprites, just with up to ~2s latency."); return; } val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloakSpawnHookHarmonyPatcher), "Tk2dSprite_Awake_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("Hooked " + typeFromHandle.Name + ".Awake (declared on " + (methodInfo.DeclaringType?.Name ?? "?") + ") for spawn-time cloak tint."); CloakSpriteRendererTintPatcher.Apply(val); } } private static void Tk2dSprite_Awake_Postfix(tk2dSprite __instance) { try { CloakRecolor componentInParent = ((Component)__instance).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { MeshRenderer component = ((Component)__instance).GetComponent(); if ((Object)(object)component != (Object)null) { componentInParent.RefreshMeshRendererNow(component); } } CloakSceneScanner.OnSpriteSpawned(__instance); } catch (Exception ex) { Log.Warn("CloakSpawnHookHarmonyPatcher: postfix threw on '" + (((__instance != null) ? ((Object)__instance).name : null) ?? "(null)") + "': " + ex.Message); } } } [DefaultExecutionOrder(10000)] [DisallowMultipleComponent] internal sealed class CloakSpriteRendererTint : MonoBehaviour { private static readonly HashSet Instances = new HashSet(); private static CloakColor _color = CloakColor.Default; private SpriteRenderer? _renderer; private Shader? _originalShader; private int _lastSpriteId; private int _lastMaterialId; private CloakColor _lastColor; private float _lastTextureSatBoost = float.NaN; private bool _applied; internal static void SetColor(CloakColor color) { _color = color; foreach (CloakSpriteRendererTint instance in Instances) { if ((Object)(object)instance != (Object)null) { instance.ForceApply(); } } } internal static void TryAttach(SpriteRenderer renderer) { if (!((Object)(object)renderer == (Object)null) && HasMask(renderer)) { Watch(renderer); } } internal static void Watch(SpriteRenderer renderer) { if (!((Object)(object)renderer == (Object)null) && IsLikelyMaskCandidate(renderer)) { CloakSpriteRendererTint cloakSpriteRendererTint = ((Component)renderer).GetComponent(); if ((Object)(object)cloakSpriteRendererTint == (Object)null) { cloakSpriteRendererTint = ((Component)renderer).gameObject.AddComponent(); } cloakSpriteRendererTint.ForceApply(); } } private static bool IsLikelyMaskCandidate(SpriteRenderer renderer) { Sprite sprite = renderer.sprite; if ((Object)(object)sprite == (Object)null) { return false; } if (CloakMaskManager.TryGetTexture2DMask((Texture?)(object)sprite.texture, ((Object)sprite).name, out Texture2D _)) { return true; } if (!ContainsKnownTexture2DMaskStem(((Object)renderer).name) && !ContainsKnownTexture2DMaskStem(((Object)((Component)renderer).gameObject).name) && !ContainsKnownTexture2DMaskStem(((Object)sprite).name)) { if ((Object)(object)sprite.texture != (Object)null) { return ContainsKnownTexture2DMaskStem(((Object)sprite.texture).name); } return false; } return true; } private static bool ContainsKnownTexture2DMaskStem(string? value) { if (string.IsNullOrEmpty(value)) { return false; } return value.IndexOf("diving_bell_bench_grab", StringComparison.OrdinalIgnoreCase) >= 0; } private void Awake() { _renderer = ((Component)this).GetComponent(); Instances.Add(this); } private void OnEnable() { Instances.Add(this); ForceApply(); } private void OnDisable() { Instances.Remove(this); Restore(); } private void OnDestroy() { Instances.Remove(this); } private void LateUpdate() { ApplyIfNeeded(force: false); } private void ForceApply() { _lastSpriteId = 0; _lastMaterialId = 0; ApplyIfNeeded(force: true); } private static bool HasMask(SpriteRenderer renderer) { Sprite sprite = renderer.sprite; if ((Object)(object)sprite == (Object)null) { return false; } Texture2D mask; return CloakMaskManager.TryGetTexture2DMask((Texture?)(object)sprite.texture, ((Object)sprite).name, out mask); } private void ApplyIfNeeded(bool force) { if (_renderer == null) { _renderer = ((Component)this).GetComponent(); } SpriteRenderer renderer = _renderer; if ((Object)(object)renderer == (Object)null || (Object)(object)renderer.sprite == (Object)null) { return; } Sprite sprite = renderer.sprite; Material sharedMaterial = ((Renderer)renderer).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null || !sharedMaterial.HasProperty(CloakShaderManager.MainTexId)) { return; } int instanceID = ((Object)sprite).GetInstanceID(); int instanceID2 = ((Object)sharedMaterial).GetInstanceID(); float textureSaturationBoost = CloakMaterialApplier.GetTextureSaturationBoost(); if (!force && _lastSpriteId == instanceID && _lastMaterialId == instanceID2 && _lastColor.Equals(_color) && Mathf.Approximately(_lastTextureSatBoost, textureSaturationBoost)) { return; } if (!CloakMaskManager.TryGetTexture2DMask((Texture?)(object)sprite.texture, ((Object)sprite).name, out Texture2D mask)) { Restore(); _lastSpriteId = instanceID; _lastMaterialId = instanceID2; _lastColor = _color; return; } Shader shader = CloakShaderManager.Shader; if ((Object)(object)shader == (Object)null) { return; } Material material = ((Renderer)renderer).material; if (!((Object)(object)material == (Object)null)) { if ((Object)(object)_originalShader == (Object)null && (Object)(object)material.shader != (Object)(object)shader) { _originalShader = material.shader; } Texture2D texture = sprite.texture; material.shader = shader; if ((Object)(object)texture != (Object)null) { material.mainTexture = (Texture)(object)texture; } ApplyShaderProperties(material, _color, mask); Material sharedMaterial2 = ((Renderer)renderer).sharedMaterial; _lastSpriteId = instanceID; _lastMaterialId = (((Object)(object)sharedMaterial2 != (Object)null) ? ((Object)sharedMaterial2).GetInstanceID() : instanceID2); _lastColor = _color; _lastTextureSatBoost = textureSaturationBoost; _applied = true; } } private void Restore() { if (!_applied || (Object)(object)_renderer == (Object)null || (Object)(object)_originalShader == (Object)null) { return; } Material material = ((Renderer)_renderer).material; if (!((Object)(object)material == (Object)null)) { Texture val = (Texture)(((Object)(object)_renderer.sprite != (Object)null) ? ((object)_renderer.sprite.texture) : ((object)material.mainTexture)); material.shader = _originalShader; if ((Object)(object)val != (Object)null) { material.mainTexture = val; } _applied = false; } } private static void ApplyShaderProperties(Material mat, CloakColor color, Texture2D mask) { if (color.Equals(CloakColor.Default)) { mat.SetFloat(CloakShaderManager.StrengthId, 0f); mat.SetTexture(CloakShaderManager.CloakMaskTexId, (Texture)(object)mask); 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 : CloakMaterialApplier.GetTextureSaturationBoost()); mat.SetFloat(CloakShaderManager.TargetValId, Mathf.Lerp(0.6f, 1.4f, v)); mat.SetFloat(CloakShaderManager.StrengthId, 1f); mat.SetTexture(CloakShaderManager.CloakMaskTexId, (Texture)(object)mask); } } internal static class CloakSpriteRendererTintPatcher { private static bool _applied; internal static void Apply(Harmony harmony) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (!_applied) { _applied = true; MethodInfo methodInfo = AccessTools.PropertySetter(typeof(SpriteRenderer), "sprite"); if (methodInfo == null) { Log.Warn("CloakSpriteRendererTintPatcher: SpriteRenderer.sprite setter not found; Texture2D SpriteRenderer tint disabled."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloakSpriteRendererTintPatcher), "SpriteRenderer_SetSprite_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("Hooked SpriteRenderer.sprite setter for Texture2D mask tint."); TryPatchOptionalSpriteRendererMethod(harmony, "OnEnable", "SpriteRenderer_OnEnable_Postfix"); TryPatchOptionalSpriteRendererMethod(harmony, "OnBecameVisible", "SpriteRenderer_OnBecameVisible_Postfix"); } } private static void SpriteRenderer_SetSprite_Postfix(SpriteRenderer __instance) { try { CloakSpriteRendererTint.Watch(__instance); } catch (Exception ex) { Log.Warn("CloakSpriteRendererTintPatcher: postfix threw on '" + (((__instance != null) ? ((Object)__instance).name : null) ?? "(null)") + "': " + ex.Message); } } private static void SpriteRenderer_OnEnable_Postfix(SpriteRenderer __instance) { SpriteRenderer_SetSprite_Postfix(__instance); } private static void SpriteRenderer_OnBecameVisible_Postfix(SpriteRenderer __instance) { SpriteRenderer_SetSprite_Postfix(__instance); } private static void TryPatchOptionalSpriteRendererMethod(Harmony harmony, string methodName, string postfixName) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(SpriteRenderer), methodName, (Type[])null, (Type[])null); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(CloakSpriteRendererTintPatcher), postfixName, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("Hooked SpriteRenderer." + methodName + " for Texture2D mask tint."); } } } internal static class CloakTextureSaturationModMenuElement { internal const string ConfigKey = "Cloak Texture Saturation Boost"; internal const string ConfigSection = "Appearance"; private const int SliderTicks = 41; private const string SliderMenuLabel = "Cloak saturation"; internal static bool TryCreateElement(ConfigEntryBase entry, out MenuElement menuElement) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown menuElement = null; ConfigEntry floatEntry = entry as ConfigEntry; if (floatEntry == null) { return false; } if (entry.Definition.Section != "Appearance" || entry.Definition.Key != "Cloak Texture Saturation Boost") { return false; } LinearFloatSliderModel model = new LinearFloatSliderModel(0f, 2f, 41); if (!((AbstractValueModel)(object)model).SetValue(Mathf.Clamp(floatEntry.Value, 0f, 2f))) { ((AbstractValueModel)(object)model).SetValue(1f); } SliderElement slider = new SliderElement(LocalizedText.op_Implicit("Cloak saturation"), (SliderModel)(object)model); ((SelectableValueElement)(object)slider).OnValueChanged += PushModelToConfig; ((ConfigEntryBase)floatEntry).ConfigFile.SettingChanged += OnEntryChanged; ((MenuDisposable)slider).OnDispose += delegate { ((SelectableValueElement)(object)slider).OnValueChanged -= PushModelToConfig; ((ConfigEntryBase)floatEntry).ConfigFile.SettingChanged -= OnEntryChanged; }; menuElement = (MenuElement)(object)slider; return true; void OnEntryChanged(object? _, SettingChangedEventArgs e) { if (e.ChangedSetting == floatEntry) { float num = Mathf.Clamp(floatEntry.Value, 0f, 2f); if (!Mathf.Approximately(((SelectableValueElement)(object)slider).Value, num)) { ((AbstractValueModel)(object)model).SetValue(num); } } } void PushModelToConfig(float _) { float num2 = Mathf.Clamp(((SelectableValueElement)(object)slider).Value, 0f, 2f); if (!Mathf.Approximately(floatEntry.Value, num2)) { floatEntry.Value = num2; } } } } internal static class CloakTk2dHarmonyPatcher { [CompilerGenerated] private sealed class d__7 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private MethodInfo <>2__current; private int <>l__initialThreadId; private MethodInfo[] <>7__wrap1; private int <>7__wrap2; private MethodInfo 5__4; private string 5__5; MethodInfo IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__7(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; 5__4 = null; 5__5 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; Type typeFromHandle = typeof(tk2dAnimatedSprite); <>7__wrap1 = typeFromHandle.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); <>7__wrap2 = 0; goto IL_0108; } case 1: <>1__state = -1; goto IL_00aa; case 2: { <>1__state = -1; goto IL_00ec; } IL_0108: if (<>7__wrap2 < <>7__wrap1.Length) { 5__4 = <>7__wrap1[<>7__wrap2]; if (!5__4.IsSpecialName) { 5__5 = 5__4.Name; if (5__5.Contains("Mesh", StringComparison.Ordinal)) { <>2__current = 5__4; <>1__state = 1; return true; } goto IL_00aa; } goto IL_00fa; } <>7__wrap1 = null; return false; IL_00aa: if (5__5.Contains("Build", StringComparison.Ordinal) && !5__5.Contains("Rebuild", StringComparison.Ordinal)) { <>2__current = 5__4; <>1__state = 2; return true; } goto IL_00ec; IL_00fa: <>7__wrap2++; goto IL_0108; IL_00ec: 5__5 = null; 5__4 = null; goto IL_00fa; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__7(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__5 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private MethodInfo <>2__current; private int <>l__initialThreadId; private Type type; public Type <>3__type; private string name; public string <>3__name; private MethodInfo[] <>7__wrap1; private int <>7__wrap2; MethodInfo IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__5(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_006b; } <>1__state = -1; <>7__wrap1 = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); <>7__wrap2 = 0; goto IL_0079; IL_006b: <>7__wrap2++; goto IL_0079; IL_0079: if (<>7__wrap2 < <>7__wrap1.Length) { MethodInfo methodInfo = <>7__wrap1[<>7__wrap2]; if (methodInfo.Name == name) { <>2__current = methodInfo; <>1__state = 1; return true; } goto IL_006b; } <>7__wrap1 = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__5 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__5(0); } d__.type = <>3__type; d__.name = <>3__name; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__6 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private MethodInfo <>2__current; private int <>l__initialThreadId; private Type leaf; public Type <>3__leaf; private string name; public string <>3__name; private Type 5__2; private Type 5__3; private IEnumerator <>7__wrap3; MethodInfo IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__6(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } 5__2 = null; 5__3 = null; <>7__wrap3 = null; <>1__state = -2; } private bool MoveNext() { try { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -3; goto IL_0086; } <>1__state = -1; 5__2 = typeof(tk2dBaseSprite); 5__3 = leaf; goto IL_00b1; IL_0086: if (<>7__wrap3.MoveNext()) { MethodInfo current = <>7__wrap3.Current; <>2__current = current; <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap3 = null; 5__3 = 5__3.BaseType; goto IL_00b1; IL_00b1: if (5__3 != null && 5__2.IsAssignableFrom(5__3)) { <>7__wrap3 = EnumerateDeclaredPipelineMethods(5__3, name).GetEnumerator(); <>1__state = -3; goto IL_0086; } 5__3 = null; return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap3 != null) { <>7__wrap3.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { d__6 d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; d__ = this; } else { d__ = new d__6(0); } d__.leaf = <>3__leaf; d__.name = <>3__name; return d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } [CompilerGenerated] private sealed class d__4 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private Type <>2__current; private int <>l__initialThreadId; private Type 5__2; private Type[] <>7__wrap2; private int <>7__wrap3; Type IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__4(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>7__wrap2 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = typeof(tk2dBaseSprite); <>2__current = 5__2; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = typeof(tk2dSprite); <>1__state = 2; return true; case 2: { <>1__state = -1; Assembly assembly; try { assembly = 5__2.Assembly; } catch { return false; } Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).Cast().ToArray(); } <>7__wrap2 = array; <>7__wrap3 = 0; goto IL_0167; } case 3: { <>1__state = -1; goto IL_0159; } IL_0167: if (<>7__wrap3 < <>7__wrap2.Length) { Type type = <>7__wrap2[<>7__wrap3]; if (!type.IsAbstract && type.IsClass && 5__2.IsAssignableFrom(type) && type.Name.StartsWith("tk2d", StringComparison.Ordinal) && !(type == 5__2) && !(type == typeof(tk2dSprite))) { <>2__current = type; <>1__state = 3; return true; } goto IL_0159; } <>7__wrap2 = null; return false; IL_0159: <>7__wrap3++; goto IL_0167; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__4(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private const string HarmonyId = "hornet.cloak.color.tk2d-post"; private static bool _applied; private static readonly string[] Tk2dDeclaredPipelineNames = new string[9] { "LateUpdate", "FixedUpdate", "Update", "OnEnable", "Start", "BuildMesh", "UpdateMesh", "UpdateColors", "SetColors" }; internal static void Apply() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown if (_applied) { return; } Harmony harmony = new Harmony("hornet.cloak.color.tk2d-post"); HarmonyMethod postfix = new HarmonyMethod(AccessTools.Method(typeof(CloakTk2dHarmonyPatcher), "Tk2dSprite_Postfix", (Type[])null, (Type[])null)); HashSet seen = new HashSet(StringComparer.Ordinal); int patched = 0; string[] tk2dDeclaredPipelineNames; foreach (Type item in EnumerateTk2dSpritePipelineTypes()) { tk2dDeclaredPipelineNames = Tk2dDeclaredPipelineNames; foreach (string name in tk2dDeclaredPipelineNames) { foreach (MethodInfo item2 in EnumerateDeclaredPipelineMethods(item, name)) { TryPatchPipelineMethod(harmony, postfix, item2, seen, ref patched); } } } tk2dDeclaredPipelineNames = new string[4] { "BuildMesh", "UpdateMesh", "UpdateVertices", "SwitchClip" }; foreach (string name2 in tk2dDeclaredPipelineNames) { foreach (MethodInfo item3 in EnumerateInheritedChainMethods(typeof(tk2dAnimatedSprite), name2)) { TryPatchPipelineMethod(harmony, postfix, item3, seen, ref patched); } } foreach (MethodInfo item4 in EnumerateAnimatedSpriteDeclaredMeshCandidates()) { TryPatchPipelineMethod(harmony, postfix, item4, seen, ref patched); } _applied = true; if (patched == 0) { Log.Warn("HornetCloakColor: no tk2d pipeline methods were patched — cloak tint may lag until CloakRecolor mesh rescans or hero damage refresh. Report Silksong tk2d API changes to the mod author."); } } [IteratorStateMachine(typeof(d__4))] private static IEnumerable EnumerateTk2dSpritePipelineTypes() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__4(-2); } [IteratorStateMachine(typeof(d__5))] private static IEnumerable EnumerateDeclaredPipelineMethods(Type type, string name) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(-2) { <>3__type = type, <>3__name = name }; } [IteratorStateMachine(typeof(d__6))] private static IEnumerable EnumerateInheritedChainMethods(Type leaf, string name) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__6(-2) { <>3__leaf = leaf, <>3__name = name }; } [IteratorStateMachine(typeof(d__7))] private static IEnumerable EnumerateAnimatedSpriteDeclaredMeshCandidates() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__7(-2); } private static void TryPatchPipelineMethod(Harmony harmony, HarmonyMethod postfix, MethodInfo method, HashSet seen, ref int patched) { if (!CanHarmonyDetour(method)) { return; } string item = $"{method.MetadataToken:X8}:{method.DeclaringType.AssemblyQualifiedName}"; if (!seen.Add(item)) { return; } string text = method.DeclaringType.Name + "." + FormatMethodSignature(method); try { harmony.Patch((MethodBase)method, (HarmonyMethod)null, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); patched++; Log.Info("HornetCloakColor: hooked " + text + " for post-tk2d cloak apply."); } catch (Exception ex) { Log.Warn("HornetCloakColor: skipped " + text + " — Harmony cannot detour it (" + ex.GetType().Name + ": " + ex.Message + ")."); } } private static string FormatMethodSignature(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); if (parameters.Length == 0) { return m.Name; } return m.Name + "(" + string.Join(",", parameters.Select((ParameterInfo p) => p.ParameterType.Name)) + ")"; } private static bool CanHarmonyDetour(MethodBase method) { if (method.IsAbstract) { return false; } if ((method.Attributes & MethodAttributes.PinvokeImpl) != 0) { return false; } if (!(method is MethodInfo methodInfo)) { return false; } try { return methodInfo.GetMethodBody() != null; } catch { return false; } } private static void Tk2dSprite_Postfix(MonoBehaviour __instance, MethodBase __originalMethod) { tk2dBaseSprite val = (tk2dBaseSprite)(object)((__instance is tk2dBaseSprite) ? __instance : null); if (val == null) { return; } try { PostTk2dBaseSprite(val, __originalMethod?.Name); } catch (Exception ex) { Log.Warn("CloakTk2dHarmonyPatcher: postfix threw on '" + (((val != null) ? ((Object)val).name : null) ?? "(null)") + "': " + ex.Message); } } private static void PostTk2dBaseSprite(tk2dBaseSprite sprite, string? patchedMethodName) { CloakRecolor componentInParent = ((Component)sprite).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { componentInParent.ApplyFromTk2dPipeline(sprite); } else if (!IsHighFrequencyUnityLifecycle(patchedMethodName)) { CloakSceneScanner.OnTk2dPipelineComplete(sprite); } } private static bool IsHighFrequencyUnityLifecycle(string? patchedMethodName) { if (!string.Equals(patchedMethodName, "LateUpdate", StringComparison.Ordinal) && !string.Equals(patchedMethodName, "Update", StringComparison.Ordinal)) { return string.Equals(patchedMethodName, "FixedUpdate", StringComparison.Ordinal); } return true; } } 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 if (!_applied) { _applied = true; Harmony harmony = new Harmony("hornet.cloak.color.mapmask"); ApplyVanillaMapTintPatches(harmony); if (SSMPBridge.IsAvailable) { ApplySsmpCompanionPatches(harmony); } } } private static void ApplyVanillaMapTintPatches(Harmony harmony) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(GameMap), "PositionCompassAndCorpse", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.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) { harmony.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) { harmony.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"); } } private static void ApplySsmpCompanionPatches(Harmony harmony) { TryPatchSsmpCreatePlayerIcon(harmony); ServerMapStateSyncPatcher.TryApply(harmony); RemoteMapIconVisibility.TryApplyClientManagerHook(harmony); SsmMapCompassBroadcastFixPatcher.TryApply(harmony); MapRemoteIconDeferredCreate.TryApply(harmony); } private static void TryPatchSsmpCreatePlayerIcon(Harmony harmony) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown Type mapManager = SsmpReflect.MapManager; Type mathVector = SsmpReflect.MathVector2; if (mapManager != null && mathVector != null) { MethodInfo methodInfo = AccessTools.Method(mapManager, "CreatePlayerIcon", new Type[2] { typeof(ushort), mathVector }, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (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."); } } private static void GameMap_PositionCompassAndCorpse_Postfix(GameMap __instance) { HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance; if (!((Object)(object)instance == (Object)null)) { LocalMapMaskTint.Refresh(__instance, instance.ColorConfig.EffectiveColor); if (SSMPBridge.IsAvailable) { RemoteMapIconVisibility.RefreshZoomedGameMapRemoteIcons(); } } } 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.EffectiveColor); } 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.EffectiveColor); } } 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(); if ((Object)(object)mapMaskTint == (Object)null) { mapMaskTint = val.AddComponent(); } 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 LocalInstances = new HashSet(); private MaterialPropertyBlock? _block; private readonly List _renderers = new List(); private readonly List _sprites = new List(); private readonly List _images = new List(); 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(true, _renderers); ((Component)this).GetComponentsInChildren(true, _sprites); ((Component)this).GetComponentsInChildren(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_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_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_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) 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) && CloakMaterialApplier.Tk2dSpriteVertexColorsSafeToMutate((tk2dBaseSprite)(object)sprite)) { 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 : ""); 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 ByPlayer = new Dictionary(); 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() ?? icon.AddComponent()).InitLocal(color); } } } internal static class MapRemoteIconDeferredCreate { private static bool _applied; private static float _nextLogPendingNoGameMap; internal static void TryApply(Harmony harmony) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown if (_applied || !SSMPBridge.IsAvailable) { return; } Type mapManager = SsmpReflect.MapManager; if (mapManager == null) { return; } MethodInfo methodInfo = AccessTools.Method(mapManager, "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 clientManager = SsmpReflect.ClientManager; Type clientPlayerAlreadyInScene = SsmpReflect.ClientPlayerAlreadyInScene; if (clientManager != null && clientPlayerAlreadyInScene != null) { MethodInfo methodInfo2 = AccessTools.Method(clientManager, "OnPlayerAlreadyInScene", new Type[1] { clientPlayerAlreadyInScene }, (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 MpRemoteCloakReapply { [CompilerGenerated] private sealed class d__1 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public GameObject playerObject; public CloakNetAppearance appearance; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; 5__2 = 0; break; case 2: <>1__state = -1; 5__2++; break; } if (5__2 < 5) { if (!Object.op_Implicit((Object)(object)playerObject)) { return false; } CloakColorApplier.Apply(playerObject, appearance.Color, appearance.TextureSaturationMultiplier); playerObject.GetComponent()?.ForceHierarchyRefresh(); <>2__current = null; <>1__state = 2; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static void Schedule(GameObject? playerObject, CloakNetAppearance appearance) { if (!((Object)(object)playerObject == (Object)null)) { HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance; if (!((Object)(object)instance == (Object)null)) { ((MonoBehaviour)instance).StartCoroutine(ReapplyRoutine(playerObject, appearance)); } } } [IteratorStateMachine(typeof(d__1))] private static IEnumerator ReapplyRoutine(GameObject playerObject, CloakNetAppearance appearance) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(0) { playerObject = playerObject, appearance = appearance }; } } internal static class RemoteMapIconVisibility { private static object? _clientManager; private static float _nextSyncLogTime; internal static void TryApplyClientManagerHook(Harmony harmony) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (!SSMPBridge.IsAvailable) { return; } Type clientManager = SsmpReflect.ClientManager; Type serverManager = SsmpReflect.ServerManager; if (!(clientManager == null) && !(serverManager == null)) { MethodInfo methodInfo = AccessTools.Method(clientManager, "Initialize", new Type[1] { serverManager }, (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 (zoomed GameMap remote icon sync)"); } } } private static void ClientManager_Initialize_Postfix(object __instance) { if (__instance != null) { _clientManager = __instance; } } internal static void RefreshZoomedGameMapRemoteIcons() { if (_clientManager == null) { return; } try { object obj = _clientManager.GetType().GetField("_mapManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(_clientManager); if (obj == null) { if (CloakPaletteConfig.LogMapIconDiagnostics) { Log.Warn("[MapIcon] RefreshZoomedGameMapRemoteIcons: 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] RefreshZoomedGameMapRemoteIcons: _displayingIcons=true, UpdateMapIconsActive, materialize pass."); } } catch (Exception ex) { if (CloakPaletteConfig.LogMapIconDiagnostics) { Log.Warn("[MapIcon] RefreshZoomedGameMapRemoteIcons: " + ex.Message); } } } 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown if (_applied || !SSMPBridge.IsAvailable) { return; } Type serverManager = SsmpReflect.ServerManager; Type serverPlayerData = SsmpReflect.ServerPlayerData; if (serverManager == null || serverPlayerData == null) { Log.Warn("SSMP server types not found — late-join map icon sync patch skipped"); return; } MethodInfo methodInfo = AccessTools.Method(serverManager, "OnClientEnterScene", new Type[1] { serverPlayerData }, (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 mathVector = SsmpReflect.MathVector2; MethodInfo methodInfo2 = ((mathVector != null) ? AccessTools.Method(type2, "UpdatePlayerMapPosition", new Type[2] { typeof(ushort), mathVector }, (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 list = new List(); List list2 = new List(); int num2 = 0; foreach (object item in enumerable) { if (item == null) { continue; } Type type3 = item.GetType(); ushort num3 = (ushort)type3.GetProperty("Id").GetValue(item); if (num3 == num || !string.Equals((string)type3.GetProperty("CurrentScene").GetValue(item), text, StringComparison.Ordinal)) { continue; } bool flag2 = (bool)type3.GetProperty("HasMapIcon").GetValue(item); methodInfo.Invoke(obj, new object[2] { num3, flag2 }); bool flag3 = false; if (flag2 && methodInfo2 != null) { object obj3 = type3.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 mathVector = SsmpReflect.MathVector2; if (mathVector == null) { return false; } SsmpVecCtorFf = mathVector.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] { mathVector }, null); if (UpdatePlayerMapPosition == null) { return false; } Type type = AccessTools.TypeByName("Gameplay"); if (type == null) { return false; } GameplayCompassTool = type.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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown if (_applied || !SSMPBridge.IsAvailable) { return; } Type mapManager = SsmpReflect.MapManager; if (mapManager == null) { return; } if (!MapBroadcastReflect.TryBuild(mapManager)) { Log.Warn("SsmMapCompassBroadcastFix: could not cache MapManager reflection — compass broadcast fix skipped."); return; } MethodInfo methodInfo = AccessTools.Method(mapManager, "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 SsmpReflect { private static Type? _mapManager; private static Type? _mathVector2; private static Type? _clientManager; private static Type? _serverManager; private static Type? _serverPlayerData; private static Type? _clientPlayerAlreadyInScene; internal static Type? MapManager => _mapManager ?? (_mapManager = AccessTools.TypeByName("SSMP.Game.Client.MapManager")); internal static Type? MathVector2 => _mathVector2 ?? (_mathVector2 = AccessTools.TypeByName("SSMP.Math.Vector2")); internal static Type? ClientManager => _clientManager ?? (_clientManager = AccessTools.TypeByName("SSMP.Game.Client.ClientManager")); internal static Type? ServerManager => _serverManager ?? (_serverManager = AccessTools.TypeByName("SSMP.Game.Server.ServerManager")); internal static Type? ServerPlayerData => _serverPlayerData ?? (_serverPlayerData = AccessTools.TypeByName("SSMP.Game.Server.ServerPlayerData")); internal static Type? ClientPlayerAlreadyInScene => _clientPlayerAlreadyInScene ?? (_clientPlayerAlreadyInScene = AccessTools.TypeByName("SSMP.Networking.Packet.Data.ClientPlayerAlreadyInScene")); } internal enum UsernameColorPreset { Disabled, MatchCloak, Custom, Default, Crimson, Scarlet, Amber, Gold, Emerald, Teal, Azure, Royal, Violet, Magenta, Obsidian, Ivory } internal class UsernameColorConfig { private static readonly CloakColor DefaultCustom = new CloakColor(200, 60, 60); public ConfigEntry PresetChoice { get; } public ConfigEntry CustomHex { get; } internal bool IsDisabled => PresetChoice.Value == UsernameColorPreset.Disabled; public event Action? UsernameTintChanged; public UsernameColorConfig(ConfigFile config) { PresetChoice = config.Bind("Multiplayer Username", "Username Color Preset", UsernameColorPreset.Disabled, "Off / match cloak / same presets as cloak / custom hex."); CustomHex = config.Bind("Multiplayer Username", "Custom Username Color", DefaultCustom.ToString(), "Only used when preset is Custom. #RRGGBB, RRGGBB, or r,g,b."); PresetChoice.SettingChanged += delegate { this.UsernameTintChanged?.Invoke(); }; CustomHex.SettingChanged += delegate { this.UsernameTintChanged?.Invoke(); }; } internal CloakColor ResolveRgb(CloakColorConfig cloakConfig) { CloakColor color; return PresetChoice.Value switch { UsernameColorPreset.Disabled => CloakColor.Default, UsernameColorPreset.MatchCloak => cloakConfig.EffectiveColor, UsernameColorPreset.Custom => CloakColor.TryParse(CustomHex.Value, out color) ? color : CloakColor.Default, UsernameColorPreset.Default => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Default, CustomHex.Value), UsernameColorPreset.Crimson => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Crimson, CustomHex.Value), UsernameColorPreset.Scarlet => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Scarlet, CustomHex.Value), UsernameColorPreset.Amber => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Amber, CustomHex.Value), UsernameColorPreset.Gold => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Gold, CustomHex.Value), UsernameColorPreset.Emerald => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Emerald, CustomHex.Value), UsernameColorPreset.Teal => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Teal, CustomHex.Value), UsernameColorPreset.Azure => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Azure, CustomHex.Value), UsernameColorPreset.Royal => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Royal, CustomHex.Value), UsernameColorPreset.Violet => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Violet, CustomHex.Value), UsernameColorPreset.Magenta => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Magenta, CustomHex.Value), UsernameColorPreset.Obsidian => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Obsidian, CustomHex.Value), UsernameColorPreset.Ivory => CloakColorConfig.ResolvePresetColor(CloakColorConfig.Preset.Ivory, CustomHex.Value), _ => CloakColor.Default, }; } } internal static class UsernameColorHarmonyPatcher { private const string HarmonyId = "hornet.cloak.color.usernametint"; private static bool _applied; private static bool _aborted; internal static bool IsActive => _applied; internal static void Apply() { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown if (_applied || _aborted || !SSMPBridge.IsAvailable) { return; } Type type = AccessTools.TypeByName("SSMP.Game.Client.PlayerManager"); if (type == null) { return; } MethodInfo methodInfo = null; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (!(declaredMethod.Name != "ChangeNameColor") && declaredMethod.GetParameters().Length == 2) { methodInfo = declaredMethod; break; } } if (methodInfo == null) { _aborted = true; Log.Warn("UsernameColorHarmonyPatcher: PlayerManager.ChangeNameColor not found (SSMP API may have changed). Username tint disabled."); return; } try { new Harmony("hornet.cloak.color.usernametint").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(UsernameColorHarmonyPatcher), "ChangeNameColor_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _applied = true; } catch (Exception ex) { _aborted = true; Log.Warn("UsernameColorHarmonyPatcher: failed to patch ChangeNameColor: " + ex.Message); } } private static void ChangeNameColor_Postfix(object textMeshObject, object? team) { try { if (team != null) { Component val = (Component)((textMeshObject is Component) ? textMeshObject : null); if (val != null) { UsernameTintCoordinator.OnAfterSsmpChangeNameColor(val, team); } } } catch (Exception ex) { Log.Warn("UsernameColorHarmonyPatcher postfix: " + ex.Message); } } } internal static class UsernameTintCoordinator { internal static void OnAfterSsmpChangeNameColor(Component textMesh, object teamEnumBoxed) { if ((Object)(object)textMesh == (Object)null || !UsernameColorHarmonyPatcher.IsActive) { return; } int teamOrdinal = Convert.ToInt32(teamEnumBoxed); Transform transform = textMesh.transform; bool teamsEnabled = UsernameNetworkDelegates.GetTeamsEnabled?.Invoke() ?? false; bool serverAllowsCustomOverTeam = UsernameNetworkDelegates.GetServerCustomUsernameOverridesTeam?.Invoke() ?? false; if ((Object)(object)HeroController.instance != (Object)null && transform.IsChildOf(HeroController.instance.transform)) { ApplyLocal(textMesh, teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam); } else if (UsernameNetworkDelegates.TryResolveUsernameTransform != null) { UsernameResolveResult usernameResolveResult = UsernameNetworkDelegates.TryResolveUsernameTransform(transform); if (usernameResolveResult.Found && !usernameResolveResult.IsLocal) { ApplyRemote(textMesh, usernameResolveResult.PlayerId, teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam); } } } internal static void ApplyRemoteUsernameAfterSync(Component textMesh, ushort playerId, int teamOrdinal) { if (!((Object)(object)textMesh == (Object)null) && UsernameColorHarmonyPatcher.IsActive) { bool teamsEnabled = UsernameNetworkDelegates.GetTeamsEnabled?.Invoke() ?? false; bool serverAllowsCustomOverTeam = UsernameNetworkDelegates.GetServerCustomUsernameOverridesTeam?.Invoke() ?? false; ApplyRemote(textMesh, playerId, teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam); } } internal static void ForceRefreshLocalHeroUsername() { if (!UsernameColorHarmonyPatcher.IsActive) { return; } HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null)) { Component val = UsernameTmpCompat.FindUnderHero(((Component)instance).gameObject); if (!((Object)(object)val == (Object)null)) { int num = UsernameNetworkDelegates.GetLocalPlayerTeamOrdinal?.Invoke() ?? 0; UsernameVanillaTintTable.Apply(val, num); OnAfterSsmpChangeNameColor(val, num); } } } private static void ApplyLocal(Component textMesh, int teamOrdinal, bool teamsEnabled, bool serverAllowsCustomOverTeam) { //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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) HornetCloakColorPlugin instance = HornetCloakColorPlugin.Instance; UsernameColorConfig usernameColorConfig = instance?.UsernameColorConfig; if (usernameColorConfig != null && !usernameColorConfig.IsDisabled && ShouldUseCustomUsernameTint(teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam)) { Color val = usernameColorConfig.ResolveRgb(instance.ColorConfig).ToUnityColor(); UsernameTintContext ctx = new UsernameTintContext(isLocalPlayer: true, 0, teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam, val); UsernameTmpCompat.SetColor(textMesh, HornetCloakColorInterop.InvokeLocalUsernameInterop(in ctx).GetValueOrDefault(val)); } } private static void ApplyRemote(Component textMesh, ushort playerId, int teamOrdinal, bool teamsEnabled, bool serverAllowsCustomOverTeam) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) CloakColor? cloakColor = UsernameNetworkDelegates.GetRemoteUsernameTintOrNull?.Invoke(playerId); if (cloakColor.HasValue) { CloakColor value = cloakColor.Value; if (ShouldUseCustomUsernameTint(teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam)) { Color val = value.ToUnityColor(); UsernameTintContext ctx = new UsernameTintContext(isLocalPlayer: false, playerId, teamOrdinal, teamsEnabled, serverAllowsCustomOverTeam, val); UsernameTmpCompat.SetColor(textMesh, HornetCloakColorInterop.InvokeRemoteUsernameInterop(in ctx).GetValueOrDefault(val)); } } } private static bool ShouldUseCustomUsernameTint(int teamOrdinal, bool teamsEnabled, bool serverAllowsCustomOverTeam) { if (!teamsEnabled || teamOrdinal == 0) { return true; } return serverAllowsCustomOverTeam; } } internal static class UsernameTmpCompat { private static readonly BindingFlags PropFlags = BindingFlags.Instance | BindingFlags.Public; internal static Component? FindUnderHero(GameObject root) { Transform[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { if (!(((Object)val).name != "Username")) { return FindOnGameObject(((Component)val).gameObject); } } return null; } internal static Component? FindOnGameObject(GameObject usernameObject) { Component[] components = usernameObject.GetComponents(); foreach (Component val in components) { if ((Object)(object)val != (Object)null && ((object)val).GetType().Name == "TextMeshPro") { return val; } } return null; } internal static void SetColor(Component? tmp, Color color) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)tmp == (Object)null)) { ((object)tmp).GetType().GetProperty("color", PropFlags, null, typeof(Color), Type.EmptyTypes, null)?.SetValue(tmp, color, null); } } } internal static class UsernameVanillaTintTable { internal static void Apply(Component? tmp, int teamOrdinal) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Color white = default(Color); switch (teamOrdinal) { case 1: ((Color)(ref white))..ctor(0f, 0.5882353f, 0f); break; case 2: ((Color)(ref white))..ctor(40f / 51f, 0.5882353f, 0f); break; case 3: ((Color)(ref white))..ctor(50f / 51f, 10f / 51f, 10f / 51f); break; case 4: ((Color)(ref white))..ctor(10f / 51f, 0.5882353f, 40f / 51f); break; default: white = Color.white; break; } UsernameTmpCompat.SetColor(tmp, white); } } }