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.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using CyberpunkScoutTendrils.Config;
using CyberpunkScoutTendrils.Runtime;
using HarmonyLib;
using Microsoft.CodeAnalysis;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CyberpunkScoutTendrils")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CyberpunkScoutTendrils")]
[assembly: AssemblyTitle("CyberpunkScoutTendrils")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[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]
[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;
}
}
}
namespace CyberpunkScoutTendrils
{
[BepInPlugin("HawChow.CyberpunkScoutTendrils", "CyberpunkScoutTendrils", "0.1.0")]
public sealed class Plugin : BasePlugin
{
public const string PluginGuid = "HawChow.CyberpunkScoutTendrils";
public const string PluginName = "CyberpunkScoutTendrils";
public const string PluginVersion = "0.1.0";
private static Plugin? _instance;
private readonly ScoutTendrilEffect _effect = new ScoutTendrilEffect();
private Harmony? _harmony;
private FileSystemWatcher? _configWatcher;
private ConfigEntry<bool>? _enabled;
private ConfigEntry<bool>? _gradientEnabled;
private ConfigEntry<bool>? _breathEnabled;
private ConfigEntry<bool>? _glowEnabled;
private ConfigEntry<string>? _colors;
private ConfigEntry<string>? _colorOrder;
private ConfigEntry<string>? _intervalMode;
private ConfigEntry<double>? _fixedInterval;
private ConfigEntry<double>? _randomMin;
private ConfigEntry<double>? _randomMax;
private ConfigEntry<double>? _gradientDuration;
private ConfigEntry<double>? _breathPeriod;
private ConfigEntry<double>? _breathMin;
private ConfigEntry<double>? _breathMax;
private ConfigEntry<double>? _glowIntensity;
private CyberpunkSettings _settings = CyberpunkSettings.Defaults;
private int _reloadRequested;
private long _lastReloadSignal;
private int _patchInstalled;
private int _patchWarningLogged;
private int _patchFailureLogged;
public override void Load()
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Expected O, but got Unknown
_instance = this;
_harmony = new Harmony("HawChow.CyberpunkScoutTendrils");
BindConfiguration();
_settings = ReadSettings();
StartConfigWatcher();
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
TryInstallScoutPatch();
((BasePlugin)this).Log.LogInfo((object)"CyberpunkScoutTendrils loaded; only Scout active tendrils are targeted.");
}
public override bool Unload()
{
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Expected O, but got Unknown
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
_configWatcher?.Dispose();
_configWatcher = null;
try
{
Harmony? harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
catch (Exception ex)
{
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(24, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Harmony cleanup failed: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
}
log.LogWarning(val);
}
_effect.RestoreAll();
_harmony = null;
Interlocked.Exchange(ref _patchInstalled, 0);
if (_instance == this)
{
_instance = null;
}
return true;
}
private void BindConfiguration()
{
_enabled = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch for Scout tendril colors and glow.");
_gradientEnabled = ((BasePlugin)this).Config.Bind<bool>("Animation", "GradientEnabled", true, "Blend smoothly between palette colors.");
_breathEnabled = ((BasePlugin)this).Config.Bind<bool>("Animation", "BreathEnabled", true, "Pulse color brightness over time.");
_glowEnabled = ((BasePlugin)this).Config.Bind<bool>("Glow", "Enabled", true, "Apply emissive color through renderer property blocks.");
_colors = ((BasePlugin)this).Config.Bind<string>("Colors", "Palette", "#00F5FF,#FF2BD6,#B8FF00,#8A5CFF", "Comma/newline-separated #RRGGBB or #RRGGBBAA colors; add, remove, or edit entries freely.");
_colorOrder = ((BasePlugin)this).Config.Bind<string>("Colors", "Order", "Fixed", "Palette order: Fixed or Random.");
_intervalMode = ((BasePlugin)this).Config.Bind<string>("Animation", "IntervalMode", "Fixed", "Color interval mode: Fixed or Random.");
_fixedInterval = ((BasePlugin)this).Config.Bind<double>("Animation", "FixedIntervalSeconds", 2.5, "Seconds between color changes when IntervalMode=Fixed (0.05–3600).");
_randomMin = ((BasePlugin)this).Config.Bind<double>("Animation", "RandomIntervalMinSeconds", 1.0, "Minimum random color interval in seconds (0.05–3600).");
_randomMax = ((BasePlugin)this).Config.Bind<double>("Animation", "RandomIntervalMaxSeconds", 4.0, "Maximum random color interval in seconds (0.05–3600).");
_gradientDuration = ((BasePlugin)this).Config.Bind<double>("Animation", "GradientDurationSeconds", 1.25, "Gradient duration in seconds (0–3600); capped by the selected color interval.");
_breathPeriod = ((BasePlugin)this).Config.Bind<double>("Animation", "BreathPeriodSeconds", 2.4, "Breathing cycle duration in seconds (0.1–3600).");
_breathMin = ((BasePlugin)this).Config.Bind<double>("Animation", "BreathMinIntensity", 0.45, "Brightness multiplier at the dimmest point (0–16).");
_breathMax = ((BasePlugin)this).Config.Bind<double>("Animation", "BreathMaxIntensity", 1.1, "Brightness multiplier at the brightest point (0–16).");
_glowIntensity = ((BasePlugin)this).Config.Bind<double>("Glow", "Intensity", 3.0, "Emissive RGB multiplier (0–32); requires a compatible tendril shader and game bloom for post-process halos.");
}
private CyberpunkSettings ReadSettings()
{
return CyberpunkSettings.Normalize(new SettingsInput
{
Enabled = (_enabled?.Value ?? true),
GradientEnabled = (_gradientEnabled?.Value ?? true),
BreathEnabled = (_breathEnabled?.Value ?? true),
GlowEnabled = (_glowEnabled?.Value ?? true),
ColorList = (_colors?.Value ?? "#00F5FF,#FF2BD6,#B8FF00,#8A5CFF"),
ColorOrder = (_colorOrder?.Value ?? "Fixed"),
IntervalMode = (_intervalMode?.Value ?? "Fixed"),
FixedIntervalSeconds = (_fixedInterval?.Value ?? 2.5),
RandomIntervalMinSeconds = (_randomMin?.Value ?? 1.0),
RandomIntervalMaxSeconds = (_randomMax?.Value ?? 4.0),
GradientDurationSeconds = (_gradientDuration?.Value ?? 1.25),
BreathPeriodSeconds = (_breathPeriod?.Value ?? 2.4),
BreathMinIntensity = (_breathMin?.Value ?? 0.45),
BreathMaxIntensity = (_breathMax?.Value ?? 1.1),
GlowIntensity = (_glowIntensity?.Value ?? 3.0)
}, delegate(string message)
{
((BasePlugin)this).Log.LogWarning((object)message);
});
}
private void StartConfigWatcher()
{
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Expected O, but got Unknown
try
{
string fullPath = Path.GetFullPath(((BasePlugin)this).Config.ConfigFilePath);
string directoryName = Path.GetDirectoryName(fullPath);
string fileName = Path.GetFileName(fullPath);
if (!string.IsNullOrEmpty(directoryName) && !string.IsNullOrEmpty(fileName))
{
_configWatcher = new FileSystemWatcher(directoryName, fileName)
{
NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite),
IncludeSubdirectories = false,
EnableRaisingEvents = false
};
_configWatcher.Changed += OnConfigFileChanged;
_configWatcher.Created += OnConfigFileChanged;
_configWatcher.Renamed += OnConfigFileRenamed;
_configWatcher.EnableRaisingEvents = true;
}
}
catch (Exception ex)
{
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(70, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Config file watcher unavailable; restart may be required after edits: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
}
log.LogWarning(val);
}
}
private void OnConfigFileChanged(object sender, FileSystemEventArgs args)
{
MarkConfigReload();
}
private void OnConfigFileRenamed(object sender, RenamedEventArgs args)
{
MarkConfigReload();
}
private void MarkConfigReload()
{
Interlocked.Exchange(ref _lastReloadSignal, Stopwatch.GetTimestamp());
Interlocked.Exchange(ref _reloadRequested, 1);
}
private void ProcessPendingConfigReload()
{
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Expected O, but got Unknown
if (Interlocked.Exchange(ref _reloadRequested, 0) == 0)
{
return;
}
long num = Interlocked.Read(in _lastReloadSignal);
if (Stopwatch.GetTimestamp() - num < Stopwatch.Frequency / 3)
{
Interlocked.Exchange(ref _reloadRequested, 1);
return;
}
try
{
((BasePlugin)this).Config.Reload();
_settings = ReadSettings();
((BasePlugin)this).Log.LogInfo((object)"CyberpunkScoutTendrils config reloaded.");
}
catch (Exception ex)
{
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(61, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Config reload failed; keeping the previous runtime settings: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
}
log.LogWarning(val);
}
}
private void OnAssemblyLoad(object? sender, AssemblyLoadEventArgs args)
{
TryInstallScoutPatch();
}
private void TryInstallScoutPatch()
{
//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
//IL_00f2: Expected O, but got Unknown
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Expected O, but got Unknown
if (_harmony == null || Interlocked.CompareExchange(ref _patchInstalled, 1, 0) != 0)
{
return;
}
try
{
Type type = ReflectionAccess.FindLoadedType("Enemies.ScoutAntennaDetection");
MethodInfo methodInfo = ((type == null) ? null : ReflectionAccess.FindInstanceMethod(type, "UpdateTendrilState"));
MethodInfo method = typeof(Plugin).GetMethod("ScoutUpdatePostfix", BindingFlags.Static | BindingFlags.NonPublic);
if (methodInfo == null || method == null)
{
Interlocked.Exchange(ref _patchInstalled, 0);
if (type != null && Interlocked.Exchange(ref _patchWarningLogged, 1) == 0)
{
((BasePlugin)this).Log.LogWarning((object)"ScoutAntennaDetection.UpdateTendrilState was not found; waiting for game assembly load.");
}
}
else
{
_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
((BasePlugin)this).Log.LogInfo((object)"Installed Scout-only UpdateTendrilState postfix.");
}
}
catch (Exception ex)
{
Interlocked.Exchange(ref _patchInstalled, 0);
if (Interlocked.Exchange(ref _patchFailureLogged, 1) == 0)
{
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(57, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Scout hook installation failed; plugin remains inactive: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
}
log.LogWarning(val);
}
}
}
private static void ScoutUpdatePostfix(object __instance)
{
_instance?.OnScoutUpdated(__instance);
}
private void OnScoutUpdated(object scout)
{
ProcessPendingConfigReload();
double now = (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
_effect.Apply(scout, _settings, now, delegate(string message)
{
((BasePlugin)this).Log.LogWarning((object)message);
});
}
}
}
namespace CyberpunkScoutTendrils.Runtime
{
public sealed class ColorCycleEngine
{
private readonly Random _random;
private CyberpunkSettings? _settings;
private bool _initialized;
private int _currentIndex;
private int _targetIndex;
private RgbaColor _from;
private RgbaColor _to;
private double _transitionStart;
private double _transitionDuration;
private double _nextSwitchAt;
private double _nextInterval;
public int CurrentIndex => _currentIndex;
public int TargetIndex => _targetIndex;
public double LastIntervalSeconds => _nextInterval;
public ColorCycleEngine(int? randomSeed = null)
{
_random = (randomSeed.HasValue ? new Random(randomSeed.Value) : new Random());
}
public void Configure(CyberpunkSettings settings, double now)
{
_settings = settings ?? throw new ArgumentNullException("settings");
_currentIndex = ((settings.ColorOrder == ColorOrderMode.Random && settings.Colors.Count > 1) ? _random.Next(settings.Colors.Count) : 0);
_targetIndex = _currentIndex;
_from = (_to = settings.Colors[_currentIndex]);
_transitionStart = now;
_transitionDuration = 0.0;
_nextInterval = ChooseInterval(settings);
_nextSwitchAt = now + _nextInterval;
_initialized = true;
}
public ColorSample Sample(double now)
{
if (_settings == null)
{
throw new InvalidOperationException("Configure must be called before Sample.");
}
CyberpunkSettings settings = _settings;
if (!_initialized)
{
Configure(settings, now);
}
int num = 0;
while (now >= _nextSwitchAt && num++ < 2048)
{
double nextSwitchAt = _nextSwitchAt;
_currentIndex = _targetIndex;
_from = _to;
_targetIndex = ChooseNextIndex(settings, _currentIndex);
_to = settings.Colors[_targetIndex];
_transitionStart = nextSwitchAt;
_nextInterval = ChooseInterval(settings);
_transitionDuration = (settings.GradientEnabled ? Math.Min(settings.GradientDurationSeconds, _nextInterval) : 0.0);
_nextSwitchAt = nextSwitchAt + _nextInterval;
}
if (num >= 2048 && now >= _nextSwitchAt)
{
_transitionStart = now;
_nextSwitchAt = now + ChooseInterval(settings);
}
float value = ((_transitionDuration <= 0.0) ? 1f : ((float)((now - _transitionStart) / _transitionDuration)));
value = RgbaColor.Clamp01(value);
ColorSample result = ColorSample.Lerp(ColorSample.From(_from), ColorSample.From(_to), value);
if (!settings.BreathEnabled)
{
return result;
}
double num2 = 0.5 - 0.5 * Math.Cos(Math.PI * 2.0 * now / settings.BreathPeriodSeconds);
float multiplier = (float)(settings.BreathMinIntensity + (settings.BreathMaxIntensity - settings.BreathMinIntensity) * num2);
return result.MultiplyRgb(multiplier);
}
private int ChooseNextIndex(CyberpunkSettings settings, int current)
{
if (settings.Colors.Count <= 1)
{
return 0;
}
if (settings.ColorOrder == ColorOrderMode.Fixed)
{
return (current + 1) % settings.Colors.Count;
}
int num = _random.Next(settings.Colors.Count - 1);
if (num < current)
{
return num;
}
return num + 1;
}
private double ChooseInterval(CyberpunkSettings settings)
{
if (settings.IntervalMode != ColorIntervalMode.Fixed)
{
return settings.RandomIntervalMinSeconds + _random.NextDouble() * (settings.RandomIntervalMaxSeconds - settings.RandomIntervalMinSeconds);
}
return settings.FixedIntervalSeconds;
}
}
internal static class ReflectionAccess
{
private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly string[] TraversalNames = new string[18]
{
"gameObject", "m_gameObject", "renderer", "m_renderer", "renderers", "m_renderers", "transform", "m_transform", "visual", "m_visual",
"model", "m_model", "meshRenderer", "m_meshRenderer", "antenna", "m_antenna", "tendril", "m_tendril"
};
public static Type? FindLoadedType(string fullName)
{
Type type = Type.GetType(fullName, throwOnError: false, ignoreCase: false);
if (type != null)
{
return type;
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
try
{
Type type2 = assembly.GetType(fullName, throwOnError: false, ignoreCase: false);
if (type2 != null)
{
return type2;
}
}
catch
{
}
}
return null;
}
public static MethodInfo? FindInstanceMethod(Type type, string name)
{
return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
}
public static MemberInfo? FindMember(Type? type, string name)
{
Type type2 = type;
while (type2 != null)
{
try
{
FieldInfo field = type2.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field;
}
PropertyInfo property = type2.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.GetIndexParameters().Length == 0)
{
return property;
}
}
catch
{
}
type2 = type2.BaseType;
}
return null;
}
public static object? GetValue(MemberInfo? member, object instance)
{
try
{
return (member is FieldInfo fieldInfo) ? fieldInfo.GetValue(instance) : ((!(member is PropertyInfo propertyInfo) || !(propertyInfo.GetMethod != null)) ? null : propertyInfo.GetValue(instance));
}
catch
{
return null;
}
}
public static Type? GetMemberType(MemberInfo? member)
{
if (!(member is FieldInfo fieldInfo))
{
if (member is PropertyInfo propertyInfo)
{
return propertyInfo.PropertyType;
}
return null;
}
return fieldInfo.FieldType;
}
public static bool TrySetValue(MemberInfo? member, object instance, object? value)
{
try
{
if (!(member is FieldInfo fieldInfo))
{
if (member is PropertyInfo propertyInfo && propertyInfo.SetMethod != null)
{
propertyInfo.SetValue(instance, value);
return true;
}
}
else if (!fieldInfo.IsInitOnly)
{
fieldInfo.SetValue(instance, value);
return true;
}
}
catch
{
}
return false;
}
public static IReadOnlyList<object> ReadActiveTendrils(object scout)
{
return ReadCollection(GetValue(FindMember(scout.GetType(), "m_activeTendrils"), scout), 2048);
}
public static IReadOnlyList<object> ReadCollection(object? collection, int maxItems = 256)
{
if (collection == null || maxItems <= 0)
{
return Array.Empty<object>();
}
List<object> list = new List<object>();
if (collection is IEnumerable enumerable)
{
try
{
foreach (object item in enumerable)
{
if (item != null)
{
list.Add(item);
}
if (list.Count >= maxItems)
{
break;
}
}
return list;
}
catch
{
list.Clear();
}
}
try
{
Type type = collection.GetType();
int val = Convert.ToInt32(GetValue(FindMember(type, "Count"), collection) ?? GetValue(FindMember(type, "Length"), collection) ?? ((object)0));
PropertyInfo property = type.GetProperty("Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property?.GetMethod == null)
{
return list;
}
for (int i = 0; i < Math.Min(val, maxItems); i++)
{
object value = property.GetValue(collection, new object[1] { i });
if (value != null)
{
list.Add(value);
}
}
}
catch
{
}
return list;
}
public static long Identity(object instance)
{
try
{
object value = GetValue(FindMember(instance.GetType(), "Pointer"), instance);
if (value is IntPtr intPtr && intPtr != IntPtr.Zero)
{
return intPtr.ToInt64();
}
if (value != null)
{
long num = Convert.ToInt64(value);
if (num != 0L)
{
return num;
}
}
}
catch
{
}
return RuntimeHelpers.GetHashCode(instance);
}
public static IReadOnlyList<object> FindRenderers(object tendril)
{
Type type = FindLoadedType("UnityEngine.Renderer");
if (type == null)
{
return Array.Empty<object>();
}
Dictionary<long, object> dictionary = new Dictionary<long, object>();
HashSet<int> hashSet = new HashSet<int>();
Stack<(object, int)> stack = new Stack<(object, int)>();
stack.Push((tendril, 0));
int num = 256;
while (stack.Count > 0 && num-- > 0)
{
(object, int) tuple = stack.Pop();
object item = tuple.Item1;
int item2 = tuple.Item2;
Type type2 = item.GetType();
if (type.IsInstanceOfType(item) || HasTypeName(type2, "UnityEngine.Renderer"))
{
dictionary[Identity(item)] = item;
}
else
{
if (item2 >= 5 || IsLeaf(type2) || !hashSet.Add(RuntimeHelpers.GetHashCode(item)))
{
continue;
}
string[] traversalNames = TraversalNames;
foreach (string text in traversalNames)
{
MemberInfo memberInfo = FindMember(type2, text);
if (memberInfo == null)
{
continue;
}
object value = GetValue(memberInfo, item);
if (value == null || value == item)
{
continue;
}
if (text.IndexOf("gameObject", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("transform", StringComparison.OrdinalIgnoreCase) >= 0)
{
foreach (object componentsInChild in GetComponentsInChildren(value, type))
{
dictionary[Identity(componentsInChild)] = componentsInChild;
}
}
PushChild(value, item2 + 1, stack, 64);
}
}
}
return dictionary.Values.ToArray();
}
private static IReadOnlyList<object> GetComponentsInChildren(object gameObject, Type rendererType)
{
MethodInfo[] methods;
try
{
methods = gameObject.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
catch
{
return Array.Empty<object>();
}
MethodInfo[] array = methods;
foreach (MethodInfo methodInfo in array)
{
if (methodInfo.Name != "GetComponentsInChildren")
{
continue;
}
ParameterInfo[] parameters = methodInfo.GetParameters();
object obj2 = null;
try
{
if (methodInfo.IsGenericMethodDefinition && parameters.Length == 1 && parameters[0].ParameterType == typeof(bool))
{
obj2 = methodInfo.MakeGenericMethod(rendererType).Invoke(gameObject, new object[1] { true });
}
else if (parameters.Length == 2 && parameters[1].ParameterType == typeof(bool))
{
object obj3 = ((parameters[0].ParameterType == typeof(Type)) ? rendererType : MakeIl2CppType(rendererType));
if (obj3 != null && parameters[0].ParameterType.IsInstanceOfType(obj3))
{
obj2 = methodInfo.Invoke(gameObject, new object[2] { obj3, true });
}
}
}
catch
{
continue;
}
if (obj2 != null)
{
IReadOnlyList<object> readOnlyList = ReadCollection(obj2, 128);
if (readOnlyList.Count > 0)
{
return readOnlyList;
}
}
}
return Array.Empty<object>();
}
private static object? MakeIl2CppType(Type type)
{
try
{
return (FindLoadedType("Il2CppInterop.Runtime.Il2CppType")?.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault((MethodInfo m) => m.Name == "Of" && m.IsGenericMethodDefinition && m.GetParameters().Length == 0))?.MakeGenericMethod(type).Invoke(null, null);
}
catch
{
return null;
}
}
private static void PushChild(object child, int depth, Stack<(object Value, int Depth)> pending, int maxItems)
{
if (child is string || IsLeaf(child.GetType()))
{
return;
}
if (child is IEnumerable enumerable)
{
int num = 0;
try
{
foreach (object item in enumerable)
{
if (item != null)
{
pending.Push((item, depth));
}
if (++num >= maxItems)
{
break;
}
}
return;
}
catch
{
}
}
pending.Push((child, depth));
}
private static bool IsLeaf(Type type)
{
if (!type.IsPrimitive && !type.IsEnum && !(type == typeof(string)) && !(type == typeof(IntPtr)) && !(type == typeof(UIntPtr)) && !(type == typeof(decimal)))
{
return type == typeof(DateTime);
}
return true;
}
private static bool HasTypeName(Type type, string fullName)
{
Type type2 = type;
while (type2 != null)
{
if (string.Equals(type2.FullName, fullName, StringComparison.Ordinal))
{
return true;
}
type2 = type2.BaseType;
}
return false;
}
}
internal sealed class ScoutTendrilEffect
{
private sealed class TendrilState
{
public object Tendril;
public MemberInfo ColorMember;
public object? OriginalColor;
}
private sealed class RendererState
{
public object Renderer;
public object OriginalBlock;
public object WorkingBlock;
public MethodInfo GetBlock;
public MethodInfo SetBlock;
public MethodInfo SetColor;
public string PropertyName = string.Empty;
}
private sealed class ScoutState
{
public readonly Dictionary<long, TendrilState> Tendrils = new Dictionary<long, TendrilState>();
public readonly Dictionary<long, RendererState> Renderers = new Dictionary<long, RendererState>();
}
private readonly Dictionary<long, ScoutState> _scouts = new Dictionary<long, ScoutState>();
private readonly HashSet<string> _warned = new HashSet<string>(StringComparer.Ordinal);
private readonly ColorCycleEngine _cycle = new ColorCycleEngine();
private CyberpunkSettings? _settings;
public void Apply(object scoutInstance, CyberpunkSettings settings, double now, Action<string> logWarning)
{
if (!settings.Enabled)
{
RestoreAll();
_settings = settings;
return;
}
if (_settings != settings)
{
_settings = settings;
_cycle.Configure(settings, now);
}
long key = ReflectionAccess.Identity(scoutInstance);
if (!_scouts.TryGetValue(key, out ScoutState value))
{
value = new ScoutState();
_scouts.Add(key, value);
}
HashSet<long> hashSet = new HashSet<long>();
HashSet<long> hashSet2 = new HashSet<long>();
ColorSample color = _cycle.Sample(now);
foreach (object item in ReflectionAccess.ReadActiveTendrils(scoutInstance))
{
MemberInfo memberInfo = ReflectionAccess.FindMember(item.GetType(), "m_colorDefault");
Type memberType = ReflectionAccess.GetMemberType(memberInfo);
if (memberInfo == null || memberType == null)
{
WarnOnce("color-member", "Could not find Scout tendril m_colorDefault; tint update skipped.", logWarning);
continue;
}
long num = ReflectionAccess.Identity(item);
hashSet.Add(num);
if (!value.Tendrils.TryGetValue(num, out TendrilState value2))
{
value2 = new TendrilState
{
Tendril = item,
ColorMember = memberInfo,
OriginalColor = ReflectionAccess.GetValue(memberInfo, item)
};
value.Tendrils[num] = value2;
}
object obj = CreateUnityColor(memberType, color);
if (obj != null && !ReflectionAccess.TrySetValue(memberInfo, item, obj))
{
WarnOnce("color-write", "Could not write Scout tendril m_colorDefault; renderer glow may still apply.", logWarning);
}
if (!settings.GlowEnabled || settings.GlowIntensity <= 0.0)
{
continue;
}
ColorSample color2 = color.MultiplyRgb((float)settings.GlowIntensity);
foreach (object item2 in ReflectionAccess.FindRenderers(item))
{
long num2 = ReflectionAccess.Identity(item2);
if (TryApplyEmission(item2, memberType, color2, num2, value, out string error))
{
hashSet2.Add(num2);
}
else if (!string.IsNullOrEmpty(error))
{
WarnOnce("glow-" + error, error, logWarning);
}
}
}
CleanupStaleTendrils(value, hashSet);
if (settings.GlowEnabled && settings.GlowIntensity > 0.0)
{
CleanupStaleRenderers(value, hashSet2);
}
else
{
RestoreRenderers(value);
}
if (value.Tendrils.Count == 0 && value.Renderers.Count == 0)
{
_scouts.Remove(key);
}
}
public void RestoreAll()
{
foreach (ScoutState value in _scouts.Values)
{
foreach (TendrilState value2 in value.Tendrils.Values)
{
if (value2.OriginalColor != null)
{
ReflectionAccess.TrySetValue(value2.ColorMember, value2.Tendril, value2.OriginalColor);
}
}
RestoreRenderers(value);
}
_scouts.Clear();
}
private bool TryApplyEmission(object renderer, Type colorType, ColorSample color, long key, ScoutState scoutState, out string error)
{
error = string.Empty;
try
{
object material = ReflectionAccess.GetValue(ReflectionAccess.FindMember(renderer.GetType(), "sharedMaterial"), renderer);
if (material == null)
{
return false;
}
MethodInfo hasProperty = material.GetType().GetMethod("HasProperty", new Type[1] { typeof(string) });
if (hasProperty == null)
{
return false;
}
string text = new string[2] { "_EmissiveColor", "_EmissionColor" }.FirstOrDefault((string name) => (bool)(hasProperty.Invoke(material, new object[1] { name }) ?? ((object)false)));
if (text == null)
{
return false;
}
if (!scoutState.Renderers.TryGetValue(key, out RendererState value))
{
Type type = ReflectionAccess.FindLoadedType("UnityEngine.MaterialPropertyBlock");
if (type == null)
{
error = "UnityEngine.MaterialPropertyBlock type is unavailable.";
return false;
}
MethodInfo methodInfo = FindPropertyBlockMethod(renderer.GetType(), "GetPropertyBlock", type);
MethodInfo methodInfo2 = FindPropertyBlockMethod(renderer.GetType(), "SetPropertyBlock", type);
MethodInfo method = type.GetMethod("SetColor", new Type[2]
{
typeof(string),
colorType
});
if (methodInfo == null || methodInfo2 == null || method == null)
{
error = "Renderer emissive property-block API is unavailable.";
return false;
}
object obj = Activator.CreateInstance(type);
object obj2 = Activator.CreateInstance(type);
if (obj == null || obj2 == null)
{
error = "MaterialPropertyBlock construction failed.";
return false;
}
methodInfo.Invoke(renderer, new object[1] { obj });
value = new RendererState
{
Renderer = renderer,
OriginalBlock = obj,
WorkingBlock = obj2,
GetBlock = methodInfo,
SetBlock = methodInfo2,
SetColor = method,
PropertyName = text
};
scoutState.Renderers[key] = value;
}
value.GetBlock.Invoke(renderer, new object[1] { value.WorkingBlock });
object obj3 = CreateUnityColor(colorType, color);
if (obj3 == null)
{
return false;
}
value.SetColor.Invoke(value.WorkingBlock, new object[2] { value.PropertyName, obj3 });
value.SetBlock.Invoke(renderer, new object[1] { value.WorkingBlock });
return true;
}
catch (TargetInvocationException ex)
{
error = "Emissive property-block call failed: " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name);
return false;
}
catch (Exception ex2)
{
error = "Emissive renderer update failed: " + ex2.GetType().Name;
return false;
}
}
private static MethodInfo? FindPropertyBlockMethod(Type rendererType, string methodName, Type blockType)
{
Type type = rendererType;
while (type != null)
{
MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo candidate) => candidate.Name == methodName && candidate.GetParameters().Length == 1 && candidate.GetParameters()[0].ParameterType.IsAssignableFrom(blockType));
if (methodInfo != null)
{
return methodInfo;
}
type = type.BaseType;
}
return null;
}
private static object? CreateUnityColor(Type colorType, ColorSample color)
{
try
{
return Activator.CreateInstance(colorType, color.R, color.G, color.B, color.A);
}
catch
{
try
{
object obj2 = Activator.CreateInstance(colorType);
if (obj2 == null)
{
return null;
}
SetFloatMember(obj2, "r", color.R);
SetFloatMember(obj2, "g", color.G);
SetFloatMember(obj2, "b", color.B);
SetFloatMember(obj2, "a", color.A);
return obj2;
}
catch
{
return null;
}
}
}
private static void SetFloatMember(object instance, string name, float value)
{
MemberInfo memberInfo = ReflectionAccess.FindMember(instance.GetType(), name);
if (memberInfo is FieldInfo fieldInfo)
{
fieldInfo.SetValue(instance, value);
}
else if (memberInfo is PropertyInfo propertyInfo && propertyInfo.SetMethod != null)
{
propertyInfo.SetValue(instance, value);
}
}
private static void CleanupStaleTendrils(ScoutState scout, HashSet<long> seen)
{
long[] array = scout.Tendrils.Keys.Where((long item) => !seen.Contains(item)).ToArray();
foreach (long key in array)
{
TendrilState tendrilState = scout.Tendrils[key];
if (tendrilState.OriginalColor != null)
{
ReflectionAccess.TrySetValue(tendrilState.ColorMember, tendrilState.Tendril, tendrilState.OriginalColor);
}
scout.Tendrils.Remove(key);
}
}
private static void CleanupStaleRenderers(ScoutState scout, HashSet<long> seen)
{
long[] array = scout.Renderers.Keys.Where((long item) => !seen.Contains(item)).ToArray();
foreach (long key in array)
{
RestoreRenderer(scout, key);
}
}
private static void RestoreRenderers(ScoutState scout)
{
long[] array = scout.Renderers.Keys.ToArray();
foreach (long key in array)
{
RestoreRenderer(scout, key);
}
}
private static void RestoreRenderer(ScoutState scout, long key)
{
if (scout.Renderers.TryGetValue(key, out RendererState value))
{
try
{
value.SetBlock.Invoke(value.Renderer, new object[1] { value.OriginalBlock });
}
catch
{
}
scout.Renderers.Remove(key);
}
}
private void WarnOnce(string key, string message, Action<string> logWarning)
{
if (_warned.Add(key))
{
logWarning(message);
}
}
}
}
namespace CyberpunkScoutTendrils.Config
{
public readonly struct RgbaColor : IEquatable<RgbaColor>
{
public readonly byte R;
public readonly byte G;
public readonly byte B;
public readonly byte A;
public float Red => (float)(int)R / 255f;
public float Green => (float)(int)G / 255f;
public float Blue => (float)(int)B / 255f;
public float Alpha => (float)(int)A / 255f;
public RgbaColor(byte r, byte g, byte b, byte a = byte.MaxValue)
{
R = r;
G = g;
B = b;
A = a;
}
public static bool TryParse(string? text, out RgbaColor color)
{
color = default(RgbaColor);
if (text == null)
{
return false;
}
string text2 = text.Trim();
if ((text2.Length != 7 && text2.Length != 9) || text2[0] != '#')
{
return false;
}
if (!byte.TryParse(text2.Substring(1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result) || !byte.TryParse(text2.Substring(3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result2) || !byte.TryParse(text2.Substring(5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result3))
{
return false;
}
byte result4 = byte.MaxValue;
if (text2.Length == 9 && !byte.TryParse(text2.Substring(7, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result4))
{
return false;
}
color = new RgbaColor(result, result2, result3, result4);
return true;
}
public string ToHex(bool includeAlpha = false)
{
if (!includeAlpha && A == byte.MaxValue)
{
return $"#{R:X2}{G:X2}{B:X2}";
}
return $"#{R:X2}{G:X2}{B:X2}{A:X2}";
}
public static RgbaColor Lerp(RgbaColor from, RgbaColor to, float t)
{
t = Clamp01(t);
return new RgbaColor(ToByte((float)(int)from.R + (float)(to.R - from.R) * t), ToByte((float)(int)from.G + (float)(to.G - from.G) * t), ToByte((float)(int)from.B + (float)(to.B - from.B) * t), ToByte((float)(int)from.A + (float)(to.A - from.A) * t));
}
private static byte ToByte(float value)
{
return (byte)Math.Min(255, Math.Max(0, (int)Math.Round(value, MidpointRounding.AwayFromZero)));
}
public static float Clamp01(float value)
{
if (!(value < 0f))
{
if (!(value > 1f))
{
return value;
}
return 1f;
}
return 0f;
}
public bool Equals(RgbaColor other)
{
if (R == other.R && G == other.G && B == other.B)
{
return A == other.A;
}
return false;
}
public override bool Equals(object? obj)
{
if (obj is RgbaColor other)
{
return Equals(other);
}
return false;
}
public override int GetHashCode()
{
return (((((R * 397) ^ G) * 397) ^ B) * 397) ^ A;
}
public override string ToString()
{
return ToHex(A != byte.MaxValue);
}
}
public readonly struct ColorSample
{
public readonly float R;
public readonly float G;
public readonly float B;
public readonly float A;
public ColorSample(float r, float g, float b, float a)
{
R = r;
G = g;
B = b;
A = a;
}
public static ColorSample From(RgbaColor color, float multiplier = 1f)
{
return new ColorSample(color.Red * multiplier, color.Green * multiplier, color.Blue * multiplier, color.Alpha);
}
public static ColorSample Lerp(ColorSample from, ColorSample to, float t)
{
t = RgbaColor.Clamp01(t);
return new ColorSample(from.R + (to.R - from.R) * t, from.G + (to.G - from.G) * t, from.B + (to.B - from.B) * t, from.A + (to.A - from.A) * t);
}
public ColorSample MultiplyRgb(float multiplier)
{
return new ColorSample(R * multiplier, G * multiplier, B * multiplier, A);
}
}
public enum ColorOrderMode
{
Fixed,
Random
}
public enum ColorIntervalMode
{
Fixed,
Random
}
public sealed class SettingsInput
{
public bool Enabled { get; set; } = true;
public bool GradientEnabled { get; set; } = true;
public bool BreathEnabled { get; set; } = true;
public bool GlowEnabled { get; set; } = true;
public string ColorList { get; set; } = "#00F5FF,#FF2BD6,#B8FF00,#8A5CFF";
public string ColorOrder { get; set; } = "Fixed";
public string IntervalMode { get; set; } = "Fixed";
public double FixedIntervalSeconds { get; set; } = 2.5;
public double RandomIntervalMinSeconds { get; set; } = 1.0;
public double RandomIntervalMaxSeconds { get; set; } = 4.0;
public double GradientDurationSeconds { get; set; } = 1.25;
public double BreathPeriodSeconds { get; set; } = 2.4;
public double BreathMinIntensity { get; set; } = 0.45;
public double BreathMaxIntensity { get; set; } = 1.1;
public double GlowIntensity { get; set; } = 3.0;
}
public sealed class CyberpunkSettings
{
private static readonly RgbaColor[] DefaultPalette = new RgbaColor[4]
{
new RgbaColor(0, 245, byte.MaxValue),
new RgbaColor(byte.MaxValue, 43, 214),
new RgbaColor(184, byte.MaxValue, 0),
new RgbaColor(138, 92, byte.MaxValue)
};
public bool Enabled { get; }
public bool GradientEnabled { get; }
public bool BreathEnabled { get; }
public bool GlowEnabled { get; }
public IReadOnlyList<RgbaColor> Colors { get; }
public ColorOrderMode ColorOrder { get; }
public ColorIntervalMode IntervalMode { get; }
public double FixedIntervalSeconds { get; }
public double RandomIntervalMinSeconds { get; }
public double RandomIntervalMaxSeconds { get; }
public double GradientDurationSeconds { get; }
public double BreathPeriodSeconds { get; }
public double BreathMinIntensity { get; }
public double BreathMaxIntensity { get; }
public double GlowIntensity { get; }
public static CyberpunkSettings Defaults => Normalize(new SettingsInput());
private CyberpunkSettings(SettingsInput input, List<RgbaColor> colors, ColorOrderMode order, ColorIntervalMode interval, double fixedInterval, double randomMin, double randomMax, double gradientDuration, double breathPeriod, double breathMin, double breathMax, double glowIntensity)
{
Enabled = input.Enabled;
GradientEnabled = input.GradientEnabled;
BreathEnabled = input.BreathEnabled;
GlowEnabled = input.GlowEnabled;
Colors = colors.AsReadOnly();
ColorOrder = order;
IntervalMode = interval;
FixedIntervalSeconds = fixedInterval;
RandomIntervalMinSeconds = randomMin;
RandomIntervalMaxSeconds = randomMax;
GradientDurationSeconds = gradientDuration;
BreathPeriodSeconds = breathPeriod;
BreathMinIntensity = breathMin;
BreathMaxIntensity = breathMax;
GlowIntensity = glowIntensity;
}
public static CyberpunkSettings Normalize(SettingsInput? input, Action<string>? warning = null)
{
if (input == null)
{
input = new SettingsInput();
}
List<RgbaColor> list = ParseColorList(input.ColorList, warning);
if (list.Count == 0)
{
warning?.Invoke("Colors had no valid Hex entries; built-in neon palette restored.");
list.AddRange(DefaultPalette);
}
ColorOrderMode order = ParseEnum(input.ColorOrder, ColorOrderMode.Fixed, "ColorOrder", warning);
ColorIntervalMode interval = ParseEnum(input.IntervalMode, ColorIntervalMode.Fixed, "IntervalMode", warning);
double fixedInterval = ClampFinite(input.FixedIntervalSeconds, 2.5, 0.05, 3600.0, "FixedIntervalSeconds", warning);
double num = ClampFinite(input.RandomIntervalMinSeconds, 1.0, 0.05, 3600.0, "RandomIntervalMinSeconds", warning);
double num2 = ClampFinite(input.RandomIntervalMaxSeconds, 4.0, 0.05, 3600.0, "RandomIntervalMaxSeconds", warning);
if (num > num2)
{
warning?.Invoke("Random interval minimum exceeded maximum; values swapped.");
double num3 = num2;
num2 = num;
num = num3;
}
double gradientDuration = ClampFinite(input.GradientDurationSeconds, 1.25, 0.0, 3600.0, "GradientDurationSeconds", warning);
double breathPeriod = ClampFinite(input.BreathPeriodSeconds, 2.4, 0.1, 3600.0, "BreathPeriodSeconds", warning);
double num4 = ClampFinite(input.BreathMinIntensity, 0.45, 0.0, 16.0, "BreathMinIntensity", warning);
double num5 = ClampFinite(input.BreathMaxIntensity, 1.1, 0.0, 16.0, "BreathMaxIntensity", warning);
if (num4 > num5)
{
warning?.Invoke("Breath minimum intensity exceeded maximum; values swapped.");
double num6 = num5;
num5 = num4;
num4 = num6;
}
double glowIntensity = ClampFinite(input.GlowIntensity, 3.0, 0.0, 32.0, "GlowIntensity", warning);
return new CyberpunkSettings(input, list, order, interval, fixedInterval, num, num2, gradientDuration, breathPeriod, num4, num5, glowIntensity);
}
public static List<RgbaColor> ParseColorList(string? text, Action<string>? warning = null)
{
List<RgbaColor> list = new List<RgbaColor>();
string[] array = (text ?? string.Empty).Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < array.Length; i++)
{
string text2 = array[i].Trim();
if (RgbaColor.TryParse(text2, out var color))
{
list.Add(color);
}
else
{
warning?.Invoke("Ignored invalid color '" + text2 + "'. Use #RRGGBB or #RRGGBBAA.");
}
}
return list;
}
private static T ParseEnum<T>(string? raw, T fallback, string key, Action<string>? warning) where T : struct
{
if (Enum.TryParse<T>(raw?.Trim(), ignoreCase: true, out var result) && Enum.IsDefined(typeof(T), result))
{
return result;
}
warning?.Invoke($"Invalid {key} '{raw}'; using {fallback}.");
return fallback;
}
private static double ClampFinite(double value, double fallback, double min, double max, string key, Action<string>? warning)
{
if (double.IsNaN(value) || double.IsInfinity(value))
{
warning?.Invoke($"Invalid {key}; using {fallback}.");
value = fallback;
}
double num = ((value < min) ? min : ((value > max) ? max : value));
if (Math.Abs(num - value) > 1E-09)
{
warning?.Invoke($"{key} clamped to [{min}, {max}].");
}
return num;
}
}
}