using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using HaulOfFame.Config; using HaulOfFame.Core; using HaulOfFame.Network; using HaulOfFame.UI; using HaulOfFame.Utils; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; 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("HaulOfFame")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2")] [assembly: AssemblyProduct("HaulOfFame")] [assembly: AssemblyTitle("HaulOfFame")] [assembly: AssemblyVersion("1.0.2.0")] [module: RefSafetyRules(11)] 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; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace HaulOfFame { [BepInPlugin("com.juanma.hauloffame", "HaulOfFame", "1.0.2")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.juanma.hauloffame"; public const string PluginName = "HaulOfFame"; public const string PluginVersion = "1.0.2"; private static Plugin? _activeInstance; private ModConfig? _modConfig; private GameBindings? _bindings; private HaulController? _controller; private Harmony? _harmony; private void Awake() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown Scene activeScene = SceneManager.GetActiveScene(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HaulOfFame] Loaded v1.0.2"); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Lifecycle] Awake: unity={1}, gameVersion={2}, scene={3}[{4}], config={5}", "HaulOfFame", Application.unityVersion, Application.version, ((Scene)(ref activeScene)).name, ((Scene)(ref activeScene)).buildIndex, ((BaseUnityPlugin)this).Config.ConfigFilePath)); _modConfig = new ModConfig(((BaseUnityPlugin)this).Config); _bindings = new GameBindings(((BaseUnityPlugin)this).Logger); if (_bindings.Resolve()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HaulOfFame/Bindings] Required game bindings resolved."); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[HaulOfFame/Bindings] One or more inspected fields are unavailable; affected captures will be skipped safely."); } try { _controller = new HaulController(_modConfig, _bindings, ((BaseUnityPlugin)this).Logger); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[HaulOfFame/Lifecycle] Initialization failed safely: " + ex.GetType().Name + ": " + ex.Message)); return; } _activeInstance = this; _harmony = new Harmony("com.juanma.hauloffame"); int num = 0; num += (InstallPatch(typeof(ExtractionPoint), "SpawnExtractingVisuals", "ValuableExtractingPrefix", prefix: true) ? 1 : 0); num += (InstallPatch(typeof(RoundDirector), "ExtractionCompleted", "ExtractionCompletedPostfix", prefix: false) ? 1 : 0); num += (InstallPatch(typeof(ItemValuableBox), "StartAbsorbLocal", "ValuableStoredPrefix", prefix: true) ? 1 : 0); num += (InstallPatch(typeof(ItemValuableBox), "OnExtracted", "ValuableBoxExtractedPrefix", prefix: true) ? 1 : 0); MethodInfo methodInfo = AccessTools.Method(typeof(Plugin), "GameUpdatePostfix", (Type[])null, (Type[])null); if (methodInfo != null) { num += (InstallUpdateHook(typeof(GameDirector), methodInfo) ? 1 : 0); num += (InstallUpdateHook(typeof(RunManager), methodInfo) ? 1 : 0); } SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Lifecycle] Installed {1}/6 hooks. Ready to record extracted valuables.", "HaulOfFame", num)); } private void Update() { _controller?.Tick(Time.unscaledDeltaTime); } private static void GameUpdatePostfix() { _activeInstance?._controller?.Tick(Time.unscaledDeltaTime); } private static void ValuableExtractingPrefix(ExtractionPoint __instance, PhysGrabObject __0) { _activeInstance?._controller?.OnValuableExtracting(__instance, __0); } private static void ExtractionCompletedPostfix(RoundDirector __instance) { _activeInstance?._controller?.OnExtractionCompleted(__instance); } private static void ValuableStoredPrefix(ItemValuableBox __instance, PhysGrabObject __0) { _activeInstance?._controller?.OnValuableStored(__instance, __0); } private static void ValuableBoxExtractedPrefix(ItemValuableBox __instance) { _activeInstance?._controller?.OnValuableBoxExtracted(__instance); } private bool InstallPatch(Type gameType, string methodName, string patchName, bool prefix) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(gameType, methodName, (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Plugin), patchName, (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null || _harmony == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[HaulOfFame/Hooks] Could not patch " + gameType.Name + "." + methodName + ".")); return false; } if (prefix) { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[HaulOfFame/Hooks] Patched " + gameType.Name + "." + methodName + " (" + (prefix ? "prefix" : "postfix") + ").")); return true; } private bool InstallUpdateHook(Type gameType, MethodInfo postfix) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(gameType, "Update", (Type[])null, (Type[])null); if (methodInfo == null || _harmony == null) { return false; } _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0034: 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) ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[{0}/Scene] Loaded: {1}[{2}], mode={3}, frame={4}.", "HaulOfFame", ((Scene)(ref scene)).name, ((Scene)(ref scene)).buildIndex, mode, Time.frameCount)); _controller?.HandleSceneChanged(scene); } private void OnActiveSceneChanged(Scene previous, Scene current) { ModConfig? modConfig = _modConfig; if (modConfig != null && modConfig.DebugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[HaulOfFame/Scene] Active scene: " + ((Scene)(ref previous)).name + " -> " + ((Scene)(ref current)).name + ".")); } } private void OnDestroy() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HaulOfFame/Lifecycle] Shutdown started."); SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.activeSceneChanged -= OnActiveSceneChanged; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)_activeInstance == (Object)(object)this) { _activeInstance = null; } _controller?.Dispose(); _modConfig?.Dispose(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[HaulOfFame/Lifecycle] Shutdown complete."); } } } namespace HaulOfFame.Utils { internal static class TextureUtils { public static Sprite CreateRoundedRectSprite(string name, int width, int height, int radius, int borderThickness = 0) { //IL_0004: 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_001a: 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) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(width, height, (TextureFormat)4, false) { name = name + "_Texture", filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1, hideFlags = (HideFlags)61 }; Color32[] array = (Color32[])(object)new Color32[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { bool flag = InsideRoundedRect((float)j + 0.5f, (float)i + 0.5f, width, height, radius, 0); bool flag2 = borderThickness > 0 && InsideRoundedRect((float)j + 0.5f, (float)i + 0.5f, width, height, Mathf.Max(0, radius - borderThickness), borderThickness); array[i * width + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)((flag && !flag2) ? byte.MaxValue : 0)); } } val.SetPixels32(array); val.Apply(false, true); Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)width, (float)height), new Vector2(0.5f, 0.5f), 100f); ((Object)obj).name = name; ((Object)obj).hideFlags = (HideFlags)61; return obj; } private static bool InsideRoundedRect(float x, float y, int width, int height, int radius, int inset) { int num = width - inset; int num2 = height - inset; if (x < (float)inset || x > (float)num || y < (float)inset || y > (float)num2) { return false; } if (radius <= 0) { return true; } float num3 = Mathf.Clamp(x, (float)(inset + radius), (float)(num - radius)); float num4 = Mathf.Clamp(y, (float)(inset + radius), (float)(num2 - radius)); float num5 = x - num3; float num6 = y - num4; return num5 * num5 + num6 * num6 <= (float)(radius * radius); } } } namespace HaulOfFame.UI { public sealed class AnnouncementHud : IDisposable { private readonly ModConfig _config; private readonly GameObject _root; private readonly RectTransform _panel; private readonly CanvasGroup _canvasGroup; private readonly Image _panelImage; private readonly Image _accentImage; private readonly Text _title; private readonly Text _name; private readonly Text _details; private readonly Sprite _panelSprite; private readonly Font _font; private readonly bool _ownsFont; private readonly AudioClip _sound; private readonly AudioSource _audioSource; private float _elapsed; private float _duration; private bool _visible; public AnnouncementHud(ModConfig config) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_036a: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) _config = config; _font = Font.CreateDynamicFontFromOSFont(new string[4] { "Bahnschrift SemiCondensed", "Arial Narrow", "Segoe UI Semibold", "Arial" }, 20); if ((Object)(object)_font != (Object)null) { _ownsFont = true; } else { _font = Resources.GetBuiltinResource("Arial.ttf"); } _panelSprite = TextureUtils.CreateRoundedRectSprite("HaulOfFame_BannerPanel", 300, 64, 6); _sound = CreateSound(); _root = new GameObject("HaulOfFame_Announcement") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)_root); _audioSource = _root.AddComponent(); _audioSource.playOnAwake = false; _audioSource.loop = false; _audioSource.spatialBlend = 0f; _audioSource.ignoreListenerPause = true; Canvas obj = _root.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 25000; CanvasScaler obj2 = _root.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.screenMatchMode = (ScreenMatchMode)0; obj2.matchWidthOrHeight = 0.5f; GameObject val = new GameObject("Panel", new Type[4] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup) }); val.transform.SetParent(_root.transform, false); _panel = val.GetComponent(); _panel.anchorMin = new Vector2(0.5f, 1f); _panel.anchorMax = new Vector2(0.5f, 1f); _panel.pivot = new Vector2(0.5f, 1f); _panel.anchoredPosition = new Vector2(0f, -62f); _panel.sizeDelta = new Vector2(650f, 126f); _panelImage = val.GetComponent(); _panelImage.sprite = _panelSprite; _panelImage.type = (Type)1; ((Graphic)_panelImage).raycastTarget = false; _canvasGroup = val.GetComponent(); _canvasGroup.blocksRaycasts = false; _canvasGroup.interactable = false; _accentImage = CreateImage("Accent", (Transform)(object)_panel, new Vector2(0f, 0f), new Vector2(7f, 110f)); RectTransform rectTransform = ((Graphic)_accentImage).rectTransform; rectTransform.anchorMin = new Vector2(0f, 0.5f); rectTransform.anchorMax = new Vector2(0f, 0.5f); rectTransform.pivot = new Vector2(0f, 0.5f); rectTransform.anchoredPosition = new Vector2(8f, 0f); _title = CreateText("Title", (Transform)(object)_panel, 18, (FontStyle)1, (TextAnchor)3); SetRect(((Graphic)_title).rectTransform, new Vector2(26f, -12f), new Vector2(598f, 24f), new Vector2(0f, 1f)); _name = CreateText("Name", (Transform)(object)_panel, 29, (FontStyle)1, (TextAnchor)3); SetRect(((Graphic)_name).rectTransform, new Vector2(26f, -39f), new Vector2(598f, 38f), new Vector2(0f, 1f)); _details = CreateText("Details", (Transform)(object)_panel, 17, (FontStyle)0, (TextAnchor)3); SetRect(((Graphic)_details).rectTransform, new Vector2(26f, -82f), new Vector2(598f, 28f), new Vector2(0f, 1f)); ApplyConfig(); _root.SetActive(false); } public void Show(string title, string name, string details) { if (_config.Enabled.Value && _config.ShowAnnouncement.Value) { _title.text = title; _name.text = name; _details.text = details; _elapsed = 0f; _duration = Mathf.Clamp(_config.AnnouncementDuration.Value, 1f, 20f); _visible = true; _root.SetActive(true); if (_config.SoundEnabled.Value) { _audioSource.PlayOneShot(_sound, Mathf.Clamp01(_config.SoundVolume.Value)); } } } public void Tick(float unscaledDeltaTime) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) if (_visible) { _elapsed += Mathf.Max(0f, unscaledDeltaTime); float num = Mathf.Clamp01(_elapsed / 0.22f); float num2 = Mathf.Clamp01((_duration - _elapsed) / 0.55f); _canvasGroup.alpha = Mathf.Min(num, num2); float num3 = 1f - EaseOutBack(Mathf.Clamp01(_elapsed / 0.36f)); _panel.anchoredPosition = new Vector2(0f, -62f - num3 * 55f); if (_elapsed >= _duration) { Hide(); } } } public void Hide() { _visible = false; if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } } public void ApplyConfig() { //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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) Color accentColor = _config.GetAccentColor(); ((Graphic)_panelImage).color = new Color(0.006f, 0.012f, 0.016f, 0.94f); ((Graphic)_accentImage).color = accentColor; ((Graphic)_title).color = new Color(accentColor.r, accentColor.g, accentColor.b, 0.88f); ((Graphic)_name).color = new Color(0.96f, 1f, 0.97f, 1f); ((Graphic)_details).color = new Color(0.72f, 0.82f, 0.76f, 1f); if (!_config.Enabled.Value || !_config.ShowAnnouncement.Value) { Hide(); } } private Text CreateText(string name, Transform parent, int fontSize, FontStyle style, TextAnchor alignment) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0061: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Text) }); val.transform.SetParent(parent, false); Text component = val.GetComponent(); component.font = _font; component.fontSize = fontSize; component.fontStyle = style; component.alignment = alignment; component.horizontalOverflow = (HorizontalWrapMode)1; component.verticalOverflow = (VerticalWrapMode)0; component.supportRichText = true; ((Graphic)component).raycastTarget = false; return component; } private static Image CreateImage(string name, Transform parent, Vector2 position, Vector2 size) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(parent, false); Image component = val.GetComponent(); ((Graphic)component).raycastTarget = false; ((Graphic)component).rectTransform.anchoredPosition = position; ((Graphic)component).rectTransform.sizeDelta = size; return component; } private static void SetRect(RectTransform rect, Vector2 position, Vector2 size, Vector2 pivot) { //IL_000b: 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_002b: 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_0039: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0f, 1f); rect.anchorMax = new Vector2(0f, 1f); rect.pivot = pivot; rect.anchoredPosition = position; rect.sizeDelta = size; } private static float EaseOutBack(float value) { float num = value - 1f; return 1f + 2.70158f * num * num * num + 1.70158f * num * num; } private static AudioClip CreateSound() { int num = Mathf.CeilToInt(19404f); float[] array = new float[num]; for (int i = 0; i < num; i++) { float num2 = (float)i / 44100f; float num3 = ((num2 < 0.14f) ? 392f : ((num2 < 0.28f) ? 523.25f : 783.99f)); float num4 = Mathf.Clamp01(num2 * 35f) * Mathf.Exp(-4.8f * num2); array[i] = (Mathf.Sin(MathF.PI * 2f * num3 * num2) + 0.32f * Mathf.Sin(MathF.PI * 2f * num3 * 2f * num2)) * num4 * 0.16f; } AudioClip obj = AudioClip.Create("HaulOfFame_Result", num, 1, 44100, false); ((Object)obj).hideFlags = (HideFlags)61; obj.SetData(array, 0); return obj; } public void Dispose() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } if ((Object)(object)_panelSprite != (Object)null) { Texture2D texture = _panelSprite.texture; Object.Destroy((Object)(object)_panelSprite); if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } } if ((Object)(object)_sound != (Object)null) { Object.Destroy((Object)(object)_sound); } if (_ownsFont && (Object)(object)_font != (Object)null) { Object.Destroy((Object)(object)_font); } } } public sealed class TrophyController : IDisposable { private readonly ModConfig _config; private readonly GameBindings _bindings; private readonly ManualLogSource _logger; private readonly GameObject _root; private readonly Transform _modelMount; private readonly GameObject _fallbackModel; private readonly RectTransform _plaqueCanvas; private readonly Image _plaquePanel; private readonly Image _plaqueAccent; private readonly Text _plaqueText; private readonly Light _light; private readonly Material _darkMaterial; private readonly Material _accentMaterial; private readonly Material _fallbackMaterial; private readonly Font _font; private readonly bool _ownsFont; private readonly List _trimObjects = new List(); private Transform? _anchor; private TrophyModelSnapshot? _attachedSnapshot; private ChampionData? _champion; private float _anchorSearchTimer; private float _animationTime; private int _lastAnchorSceneHandle = int.MinValue; public TrophyController(ModConfig config, GameBindings bindings, ManualLogSource logger) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Expected O, but got Unknown //IL_0352: Unknown result type (might be due to invalid IL or missing references) _config = config; _bindings = bindings; _logger = logger; _font = Font.CreateDynamicFontFromOSFont(new string[4] { "Bahnschrift SemiCondensed", "Arial Narrow", "Segoe UI Semibold", "Arial" }, 18); if ((Object)(object)_font != (Object)null) { _ownsFont = true; } else { _font = Resources.GetBuiltinResource("Arial.ttf"); } _root = new GameObject("HaulOfFame_WorldTrophy") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)_root); _darkMaterial = CreateMaterial("HaulOfFame_Dark", new Color(0.018f, 0.024f, 0.028f, 1f), Color.black); _accentMaterial = CreateMaterial("HaulOfFame_Accent", new Color(0.1f, 0.34f, 0.15f, 1f), new Color(0.2f, 2f, 0.36f, 1f)); _fallbackMaterial = CreateMaterial("HaulOfFame_Fallback", new Color(0.12f, 0.85f, 0.28f, 0.82f), new Color(0.25f, 2.6f, 0.5f, 1f)); CreatePrimitive("PedestalBase", (PrimitiveType)2, _root.transform, new Vector3(0f, 0.08f, 0f), new Vector3(0.72f, 0.08f, 0.72f), _darkMaterial); CreatePrimitive("PedestalTop", (PrimitiveType)2, _root.transform, new Vector3(0f, 0.19f, 0f), new Vector3(0.55f, 0.045f, 0.55f), _darkMaterial); _trimObjects.Add(CreatePrimitive("GlowRingLow", (PrimitiveType)2, _root.transform, new Vector3(0f, 0.145f, 0f), new Vector3(0.64f, 0.012f, 0.64f), _accentMaterial)); _trimObjects.Add(CreatePrimitive("GlowRingTop", (PrimitiveType)2, _root.transform, new Vector3(0f, 0.245f, 0f), new Vector3(0.48f, 0.012f, 0.48f), _accentMaterial)); GameObject val = new GameObject("ModelMount") { hideFlags = (HideFlags)61 }; val.transform.SetParent(_root.transform, false); val.transform.localPosition = new Vector3(0f, 0.72f, 0f); _modelMount = val.transform; _fallbackModel = CreatePrimitive("FallbackHologram", (PrimitiveType)0, _modelMount, Vector3.zero, new Vector3(0.52f, 0.72f, 0.52f), _fallbackMaterial); GameObject val2 = new GameObject("HologramLight") { hideFlags = (HideFlags)61 }; val2.transform.SetParent(_root.transform, false); val2.transform.localPosition = new Vector3(0f, 0.7f, 0.1f); _light = val2.AddComponent(); _light.type = (LightType)2; _light.range = 3.2f; _light.intensity = 1.35f; (_plaqueCanvas, _plaquePanel, _plaqueAccent, _plaqueText) = CreatePlaque(_root.transform); ApplyConfig(); SetChampionVisualsActive(active: false); _root.SetActive(false); } public void ShowChampion(ChampionData champion, TrophyModelSnapshot? snapshot, string plaqueText) { DetachSnapshot(); _champion = champion; _attachedSnapshot = ((snapshot != null && snapshot.Valuable.ViewId == champion.Valuable.ViewId) ? snapshot : null); if (_attachedSnapshot != null) { _attachedSnapshot.AttachTo(_modelMount); } _fallbackModel.SetActive(_attachedSnapshot == null); _plaqueText.text = plaqueText; SetChampionVisualsActive(active: true); _anchorSearchTimer = 0f; _animationTime = 0f; if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Visual] Trophy prepared: view={champion.Valuable.ViewId}, name='{champion.Valuable.Name}', modelCopy={_attachedSnapshot != null}."); } } public void Tick(float unscaledDeltaTime) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_046f: Unknown result type (might be due to invalid IL or missing references) //IL_048e: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) //IL_04a3: Unknown result type (might be due to invalid IL or missing references) //IL_04bb: Unknown result type (might be due to invalid IL or missing references) //IL_04c0: Unknown result type (might be due to invalid IL or missing references) //IL_04c5: Unknown result type (might be due to invalid IL or missing references) bool flag = _champion != null; if (!_config.Enabled.Value || !_config.ShowWorldTrophy.Value || (!flag && !_config.ShowEmptyBase.Value)) { _root.SetActive(false); return; } GamePhase phase = _bindings.GetPhase(); if ((uint)phase <= 1u) { _root.SetActive(false); return; } if ((Object)(object)_anchor == (Object)null) { _anchorSearchTimer -= Mathf.Max(0f, unscaledDeltaTime); if (_anchorSearchTimer <= 0f) { _anchorSearchTimer = 0.75f; _anchor = _bindings.FindTruckAnchor(); if ((Object)(object)_anchor != (Object)null) { Scene scene = ((Component)_anchor).gameObject.scene; if (((Scene)(ref scene)).handle != _lastAnchorSceneHandle) { scene = ((Component)_anchor).gameObject.scene; _lastAnchorSceneHandle = ((Scene)(ref scene)).handle; ManualLogSource logger = _logger; string[] obj = new string[5] { "[HaulOfFame/Visual] Truck display anchor found: ", ((Object)_anchor).name, " in scene ", null, null }; scene = ((Component)_anchor).gameObject.scene; obj[3] = ((Scene)(ref scene)).name; obj[4] = "."; logger.LogInfo((object)string.Concat(obj)); } } } } if ((Object)(object)_anchor == (Object)null) { _root.SetActive(false); return; } try { Vector3 val = default(Vector3); Quaternion val2; if (_config.PositionMode.Value == TrophyPositionMode.FixedWorld) { ((Vector3)(ref val))..ctor(Mathf.Clamp(_config.WorldX.Value, -1000f, 1000f), Mathf.Clamp(_config.WorldY.Value, -1000f, 1000f), Mathf.Clamp(_config.WorldZ.Value, -1000f, 1000f)); val2 = Quaternion.Euler(0f, Mathf.Clamp(_config.RotationY.Value, -360f, 360f), 0f); } else { val = _anchor.position + _anchor.right * Mathf.Clamp(_config.OffsetX.Value, -5f, 5f) + _anchor.up * Mathf.Clamp(_config.OffsetY.Value, -5f, 5f) + _anchor.forward * Mathf.Clamp(_config.OffsetZ.Value, -5f, 5f); val2 = _anchor.rotation * Quaternion.Euler(0f, Mathf.Clamp(_config.RotationY.Value, -360f, 360f), 0f); } _root.transform.SetPositionAndRotation(val, val2); _root.transform.localScale = Vector3.one * Mathf.Clamp(_config.TrophyScale.Value, 0.25f, 3f); if (!_root.activeSelf) { _root.SetActive(true); } SetChampionVisualsActive(flag); } catch (MissingReferenceException) { _anchor = null; _root.SetActive(false); return; } if (!flag) { return; } _animationTime += Mathf.Max(0f, unscaledDeltaTime); _modelMount.Rotate(Vector3.up, Mathf.Clamp(_config.SpinSpeed.Value, -180f, 180f) * unscaledDeltaTime, (Space)1); float num = Mathf.Sin(_animationTime * Mathf.Clamp(_config.BobSpeed.Value, 0f, 10f) * MathF.PI * 2f) * Mathf.Clamp(_config.BobHeight.Value, 0f, 0.3f); _modelMount.localPosition = new Vector3(0f, 0.72f + num, 0f); Camera main = Camera.main; if ((Object)(object)main != (Object)null) { Vector3 val3 = ((Transform)_plaqueCanvas).position - ((Component)main).transform.position; if (((Vector3)(ref val3)).sqrMagnitude > 0.001f) { ((Transform)_plaqueCanvas).rotation = Quaternion.LookRotation(((Vector3)(ref val3)).normalized, Vector3.up); } } } public void HandleSceneChanged() { _anchor = null; _anchorSearchTimer = 0.25f; _root.SetActive(false); } public void ApplyConfig() { //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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) Color accentColor = _config.GetAccentColor(); ApplyMaterialColor(_accentMaterial, new Color(accentColor.r * 0.32f, accentColor.g * 0.32f, accentColor.b * 0.32f, 1f), accentColor * 2f); ApplyMaterialColor(_fallbackMaterial, new Color(accentColor.r, accentColor.g, accentColor.b, 0.86f), accentColor * 2.7f); _light.color = accentColor; ((Graphic)_plaqueAccent).color = accentColor; ((Graphic)_plaquePanel).color = new Color(0.006f, 0.012f, 0.016f, 0.93f); if (!_config.Enabled.Value || !_config.ShowWorldTrophy.Value) { _root.SetActive(false); } } public void Clear() { DetachSnapshot(); _champion = null; _plaqueText.text = string.Empty; SetChampionVisualsActive(active: false); _anchor = null; _root.SetActive(false); } private void SetChampionVisualsActive(bool active) { if (((Component)_modelMount).gameObject.activeSelf != active) { ((Component)_modelMount).gameObject.SetActive(active); } if (((Component)_plaqueCanvas).gameObject.activeSelf != active) { ((Component)_plaqueCanvas).gameObject.SetActive(active); } if (((Component)_light).gameObject.activeSelf != active) { ((Component)_light).gameObject.SetActive(active); } } private void DetachSnapshot() { if (_attachedSnapshot == null || (Object)(object)_attachedSnapshot.Root == (Object)null) { _attachedSnapshot = null; return; } _attachedSnapshot.Root.transform.SetParent((Transform)null, false); _attachedSnapshot.Hide(); _attachedSnapshot = null; } private (RectTransform canvas, Image panel, Image accent, Text text) CreatePlaque(Transform parent) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("PlaqueCanvas", new Type[2] { typeof(RectTransform), typeof(Canvas) }); ((Object)val).hideFlags = (HideFlags)61; val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); ((Transform)component).localPosition = new Vector3(0f, 1.58f, 0f); ((Transform)component).localRotation = Quaternion.identity; ((Transform)component).localScale = Vector3.one * 0.00145f; component.sizeDelta = new Vector2(620f, 180f); Canvas component2 = val.GetComponent(); component2.renderMode = (RenderMode)2; component2.sortingOrder = 100; GameObject val2 = new GameObject("Panel", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val2.transform.SetParent(val.transform, false); RectTransform component3 = val2.GetComponent(); component3.anchorMin = Vector2.zero; component3.anchorMax = Vector2.one; component3.offsetMin = Vector2.zero; component3.offsetMax = Vector2.zero; Image component4 = val2.GetComponent(); ((Graphic)component4).raycastTarget = false; GameObject val3 = new GameObject("Accent", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val3.transform.SetParent(val2.transform, false); RectTransform component5 = val3.GetComponent(); component5.anchorMin = new Vector2(0f, 0f); component5.anchorMax = new Vector2(0f, 1f); component5.pivot = new Vector2(0f, 0.5f); component5.anchoredPosition = new Vector2(8f, 0f); component5.sizeDelta = new Vector2(7f, -18f); Image component6 = val3.GetComponent(); ((Graphic)component6).raycastTarget = false; GameObject val4 = new GameObject("Text", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Text) }); val4.transform.SetParent(val2.transform, false); RectTransform component7 = val4.GetComponent(); component7.anchorMin = Vector2.zero; component7.anchorMax = Vector2.one; component7.offsetMin = new Vector2(30f, 14f); component7.offsetMax = new Vector2(-18f, -12f); Text component8 = val4.GetComponent(); component8.font = _font; component8.fontSize = 24; component8.fontStyle = (FontStyle)1; component8.alignment = (TextAnchor)3; ((Graphic)component8).color = new Color(0.94f, 1f, 0.96f, 1f); component8.supportRichText = true; component8.horizontalOverflow = (HorizontalWrapMode)0; component8.verticalOverflow = (VerticalWrapMode)0; ((Graphic)component8).raycastTarget = false; return (canvas: component, panel: component4, accent: component6, text: component8); } private static GameObject CreatePrimitive(string name, PrimitiveType primitive, Transform parent, Vector3 localPosition, Vector3 localScale, Material material) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) GameObject obj = GameObject.CreatePrimitive(primitive); ((Object)obj).name = name; ((Object)obj).hideFlags = (HideFlags)61; obj.transform.SetParent(parent, false); obj.transform.localPosition = localPosition; obj.transform.localRotation = Quaternion.identity; obj.transform.localScale = localScale; Collider component = obj.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } Renderer component2 = obj.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.sharedMaterial = material; } return obj; } private static Material CreateMaterial(string name, Color color, Color emission) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_007a: Expected O, but got Unknown Shader obj = Shader.Find("Standard") ?? Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Diffuse") ?? Shader.Find("Sprites/Default") ?? Shader.Find("UI/Default"); if ((Object)(object)obj == (Object)null) { throw new InvalidOperationException("No compatible built-in shader was found for the trophy display."); } Material val = new Material(obj) { name = name, color = color, hideFlags = (HideFlags)61 }; ApplyMaterialColor(val, color, emission); return val; } private static void ApplyMaterialColor(Material material, Color color, Color emission) { //IL_0001: 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) material.color = color; if (material.HasProperty("_EmissionColor")) { material.EnableKeyword("_EMISSION"); material.SetColor("_EmissionColor", emission); } } public void Dispose() { DetachSnapshot(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } Object.Destroy((Object)(object)_darkMaterial); Object.Destroy((Object)(object)_accentMaterial); Object.Destroy((Object)(object)_fallbackMaterial); if (_ownsFont && (Object)(object)_font != (Object)null) { Object.Destroy((Object)(object)_font); } _trimObjects.Clear(); } } public sealed class TrophyModelSnapshot : IDisposable { private readonly List _ownedMeshes = new List(); private bool _disposed; public CapturedValuable Valuable { get; } public GameObject Root { get; } private TrophyModelSnapshot(CapturedValuable valuable, GameObject root, List ownedMeshes) { Valuable = valuable; Root = root; _ownedMeshes.AddRange(ownedMeshes); } public static bool TryCreate(CapturedValuable valuable, PhysGrabObject source, ManualLogSource logger, out TrophyModelSnapshot snapshot) { //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Expected O, but got Unknown //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) snapshot = null; if ((Object)(object)source == (Object)null) { return false; } List list = new List(); List list2 = new List(); List list3 = new List(); try { MeshFilter[] componentsInChildren = ((Component)source).GetComponentsInChildren(true); foreach (MeshFilter val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.sharedMesh == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { MeshRenderer component = ((Component)val).GetComponent(); if (!((Object)(object)component == (Object)null) && ((Renderer)component).enabled) { GameObject val2 = CreateMeshObject(((Component)val).transform, val.sharedMesh, ((Renderer)component).sharedMaterials); list.Add(val2); list2.Add((Renderer)(object)val2.GetComponent()); } } } SkinnedMeshRenderer[] componentsInChildren2 = ((Component)source).GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val3 in componentsInChildren2) { if (!((Object)(object)val3 == (Object)null) && ((Renderer)val3).enabled && ((Component)val3).gameObject.activeInHierarchy) { Mesh val4 = new Mesh { name = "HaulOfFame_BakedMesh", hideFlags = (HideFlags)61 }; val3.BakeMesh(val4); if (val4.vertexCount == 0) { Object.Destroy((Object)(object)val4); continue; } list3.Add(val4); GameObject val5 = CreateMeshObject(((Component)val3).transform, val4, ((Renderer)val3).sharedMaterials); list.Add(val5); list2.Add((Renderer)(object)val5.GetComponent()); } } if (list2.Count == 0) { foreach (GameObject item in list) { Object.Destroy((Object)(object)item); } foreach (Mesh item2 in list3) { Object.Destroy((Object)(object)item2); } return false; } Bounds bounds = list2[0].bounds; for (int j = 1; j < list2.Count; j++) { ((Bounds)(ref bounds)).Encapsulate(list2[j].bounds); } GameObject val6 = new GameObject("HaulOfFame_Model_" + valuable.ViewId) { hideFlags = (HideFlags)61 }; val6.transform.position = ((Bounds)(ref bounds)).center; foreach (GameObject item3 in list) { item3.transform.SetParent(val6.transform, true); } val6.transform.position = Vector3.zero; val6.transform.rotation = Quaternion.identity; float num = Mathf.Max(((Bounds)(ref bounds)).size.x, Mathf.Max(((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds)).size.z)); val6.transform.localScale = Vector3.one * ((num > 0.001f) ? (1f / num) : 1f); Object.DontDestroyOnLoad((Object)(object)val6); val6.SetActive(false); snapshot = new TrophyModelSnapshot(valuable, val6, list3); return true; } catch (Exception ex) { foreach (GameObject item4 in list) { if ((Object)(object)item4 != (Object)null) { Object.Destroy((Object)(object)item4); } } foreach (Mesh item5 in list3) { if ((Object)(object)item5 != (Object)null) { Object.Destroy((Object)(object)item5); } } logger.LogWarning((object)("[HaulOfFame/Visual] Model snapshot failed for '" + valuable.Name + "': " + ex.GetType().Name + ": " + ex.Message)); return false; } } public void AttachTo(Transform parent) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !((Object)(object)Root == (Object)null)) { Root.transform.SetParent(parent, false); Root.transform.localPosition = Vector3.zero; Root.transform.localRotation = Quaternion.identity; Root.SetActive(true); } } public void Hide() { if (!_disposed && (Object)(object)Root != (Object)null) { Root.SetActive(false); } } private static GameObject CreateMeshObject(Transform sourceTransform, Mesh mesh, Material[] materials) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown GameObject val = new GameObject("HaulOfFame_RenderPart") { hideFlags = (HideFlags)61 }; val.transform.position = sourceTransform.position; val.transform.rotation = sourceTransform.rotation; val.transform.localScale = sourceTransform.lossyScale; val.AddComponent().sharedMesh = mesh; MeshRenderer obj = val.AddComponent(); ((Renderer)obj).sharedMaterials = materials; ((Renderer)obj).shadowCastingMode = (ShadowCastingMode)1; ((Renderer)obj).receiveShadows = true; ((Renderer)obj).lightProbeUsage = (LightProbeUsage)1; return val; } public void Dispose() { if (_disposed) { return; } _disposed = true; if ((Object)(object)Root != (Object)null) { Object.Destroy((Object)(object)Root); } foreach (Mesh ownedMesh in _ownedMeshes) { if ((Object)(object)ownedMesh != (Object)null) { Object.Destroy((Object)(object)ownedMesh); } } _ownedMeshes.Clear(); } } } namespace HaulOfFame.Network { public sealed class HaulNetwork : IDisposable { private enum MessageType : byte { Hello, PrepareValuable, Contribution, Champion } private const byte EventCode = 182; private const string Signature = "com.juanma.hauloffame"; private const byte ProtocolVersion = 1; private const float MinimumContributionInterval = 0.02f; private readonly ModConfig _config; private readonly ManualLogSource _logger; private readonly HashSet _moddedActors = new HashSet(); private readonly Dictionary _lastContributionTime = new Dictionary(); private LoadBalancingClient? _subscribedClient; private Room? _trackedRoom; public event Action? PrepareReceived; public event Action? ContributionReceived; public event Action? ChampionReceived; public HaulNetwork(ModConfig config, ManualLogSource logger) { _config = config; _logger = logger; RefreshSubscription(); } public void Tick() { RefreshSubscription(); Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == _trackedRoom) { return; } _trackedRoom = currentRoom; _moddedActors.Clear(); _lastContributionTime.Clear(); if (currentRoom != null && PhotonNetwork.LocalPlayer != null) { _moddedActors.Add(PhotonNetwork.LocalPlayer.ActorNumber); SendHello(); if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Network] Entered room '{currentRoom.Name}' as actor {PhotonNetwork.LocalPlayer.ActorNumber}; presence announced."); } } } public void BroadcastPrepare(int viewId, int extractionPointViewId) { if (CanSend() && viewId > 0) { object[] payload = new object[6] { "com.juanma.hauloffame", (byte)1, (byte)1, viewId, Mathf.Max(0, extractionPointViewId), PhotonNetwork.ServerTimestamp }; Raise(payload, (ReceiverGroup)0); } } public void SendContribution(int viewId, float score) { if (CanSend() && viewId > 0 && IsFinite(score)) { object[] payload = new object[6] { "com.juanma.hauloffame", (byte)1, (byte)2, viewId, Mathf.Clamp(score, 0f, 1000f), PhotonNetwork.ServerTimestamp }; Raise(payload, (ReceiverGroup)2); } } public void BroadcastChampion(ChampionData champion) { if (CanSend()) { object[] payload = new object[11] { "com.juanma.hauloffame", (byte)1, (byte)3, champion.Valuable.ViewId, CapturedValuable.SanitizeName(champion.Valuable.Name), champion.Valuable.CurrentValue, champion.Valuable.OriginalValue, champion.ContributorActors, champion.ContributorScores, champion.AttributionComplete, PhotonNetwork.ServerTimestamp }; Raise(payload, (ReceiverGroup)0); } } public bool AllPlayersHaveMod() { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { return true; } foreach (int key in currentRoom.Players.Keys) { if (!_moddedActors.Contains(key)) { return false; } } return true; } private void SendHello() { if (CanSend()) { object[] payload = new object[4] { "com.juanma.hauloffame", (byte)1, (byte)0, PhotonNetwork.ServerTimestamp }; Raise(payload, (ReceiverGroup)0); } } private bool Raise(object[] payload, ReceiverGroup receivers) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) RaiseEventOptions val = new RaiseEventOptions { Receivers = receivers, CachingOption = (EventCaching)0 }; bool flag = PhotonNetwork.RaiseEvent((byte)182, (object)payload, val, SendOptions.SendReliable); if (_config.DebugLogging.Value && !flag) { _logger.LogWarning((object)$"[HaulOfFame/Network] Photon rejected outgoing message type {payload[2]}."); } return flag; } private void RefreshSubscription() { LoadBalancingClient networkingClient = PhotonNetwork.NetworkingClient; if (networkingClient != _subscribedClient) { if (_subscribedClient != null) { _subscribedClient.EventReceived -= OnEventReceived; } _subscribedClient = networkingClient; if (_subscribedClient != null) { _subscribedClient.EventReceived += OnEventReceived; _logger.LogInfo((object)"[HaulOfFame/Network] Photon custom-event listener attached."); } } } private void OnEventReceived(EventData eventData) { if (eventData.Code != 182 || !(eventData.CustomData is object[] array) || array.Length < 4 || !(array[0] is string text) || text != "com.juanma.hauloffame" || !(array[1] is byte b) || b != 1 || !(array[2] is byte b2) || !Enum.IsDefined(typeof(MessageType), b2)) { return; } int sender = eventData.Sender; if (sender <= 0) { return; } switch ((MessageType)b2) { case MessageType.Hello: _moddedActors.Add(sender); if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Network] Actor {sender} reported compatible protocol {b}."); } break; case MessageType.PrepareValuable: ReceivePrepare(sender, array); break; case MessageType.Contribution: ReceiveContribution(sender, array); break; case MessageType.Champion: ReceiveChampion(sender, array); break; } } private void ReceivePrepare(int sender, object[] payload) { if (payload.Length == 6 && payload[3] is int num && payload[4] is int num2 && payload[5] is int timestamp && num > 0 && IsMasterActor(sender) && Fresh(timestamp, 15000)) { if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Network] Host prepared extracted valuable view={num}."); } this.PrepareReceived?.Invoke(num, Mathf.Max(0, num2)); } } private void ReceiveContribution(int sender, object[] payload) { if (payload.Length == 6 && payload[3] is int num && payload[4] is float num2 && payload[5] is int timestamp && PhotonNetwork.IsMasterClient && num > 0 && IsFinite(num2) && !(num2 < 0f) && !(num2 > 1000f) && Fresh(timestamp, 20000)) { float realtimeSinceStartup = Time.realtimeSinceStartup; long key = ((long)sender << 32) | (uint)num; if (!_lastContributionTime.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < 0.02f)) { _lastContributionTime[key] = realtimeSinceStartup; this.ContributionReceived?.Invoke(sender, num, num2); } } } private void ReceiveChampion(int sender, object[] payload) { if (payload.Length != 11 || !IsMasterActor(sender) || !(payload[3] is int num) || !(payload[4] is string name) || !(payload[5] is int num2) || !(payload[6] is int num3) || !(payload[7] is int[] array) || !(payload[8] is float[] array2) || !(payload[9] is bool attributionComplete) || !(payload[10] is int timestamp) || num <= 0 || num2 < 0 || num3 < num2 || array.Length != array2.Length || array.Length > 32 || !Fresh(timestamp, 30000)) { return; } float[] array3 = array2; foreach (float num4 in array3) { if (!IsFinite(num4) || num4 < 0f || num4 > 1000f) { return; } } ChampionData obj = new ChampionData(new CapturedValuable(num, name, num2, num3), array, array2, attributionComplete); this.ChampionReceived?.Invoke(obj); } private static bool CanSend() { if (PhotonNetwork.InRoom) { return PhotonNetwork.IsConnectedAndReady; } return false; } private static bool IsMasterActor(int actorNumber) { Player masterClient = PhotonNetwork.MasterClient; if (masterClient != null) { return masterClient.ActorNumber == actorNumber; } return false; } private static bool Fresh(int timestamp, int maximumAgeMilliseconds) { int num = PhotonNetwork.ServerTimestamp - timestamp; if (num >= -2000) { return num <= maximumAgeMilliseconds; } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } public void Dispose() { if (_subscribedClient != null) { _subscribedClient.EventReceived -= OnEventReceived; } _subscribedClient = null; _trackedRoom = null; _moddedActors.Clear(); _lastContributionTime.Clear(); this.PrepareReceived = null; this.ContributionReceived = null; this.ChampionReceived = null; } } } namespace HaulOfFame.Core { public sealed class CapturedValuable { public int ViewId { get; } public string Name { get; } public int CurrentValue { get; } public int OriginalValue { get; } public float RetainedPercent { get { if (OriginalValue > 0) { return Mathf.Clamp((float)CurrentValue * 100f / (float)OriginalValue, 0f, 999f); } return 100f; } } public CapturedValuable(int viewId, string name, int currentValue, int originalValue) { ViewId = viewId; Name = SanitizeName(name); CurrentValue = Mathf.Max(0, currentValue); OriginalValue = Mathf.Max(CurrentValue, originalValue); } public bool Beats(CapturedValuable? other) { if (other == null) { return true; } if (CurrentValue != other.CurrentValue) { return CurrentValue > other.CurrentValue; } int num = RetainedPercent.CompareTo(other.RetainedPercent); if (num != 0) { return num > 0; } return ViewId < other.ViewId; } public static string SanitizeName(string? value) { if (string.IsNullOrWhiteSpace(value)) { return "VALUABLE"; } List list = new List(Mathf.Min(value.Length, 64)); string text = value.Trim(); foreach (char c in text) { if (list.Count >= 64) { break; } if (!char.IsControl(c) && c != '<' && c != '>') { list.Add(c); } } if (list.Count != 0) { return new string(list.ToArray()); } return "VALUABLE"; } } public sealed class ChampionData { public CapturedValuable Valuable { get; } public int[] ContributorActors { get; } public float[] ContributorScores { get; } public bool AttributionComplete { get; } public ChampionData(CapturedValuable valuable, int[]? contributorActors, float[]? contributorScores, bool attributionComplete) { Valuable = valuable; ContributorActors = contributorActors ?? Array.Empty(); ContributorScores = contributorScores ?? Array.Empty(); AttributionComplete = attributionComplete; } } public enum GamePhase { Unavailable, MainMenu, Mission, Shop, Lobby, Other } public sealed class GameBindings { private readonly ManualLogSource _logger; private FieldInfo? _valuableCurrentField; private FieldInfo? _valuableOriginalField; private FieldInfo? _valuableValueSetField; private FieldInfo? _runStartedField; private FieldInfo? _extractionPointsField; private FieldInfo? _extractionPointsCompletedField; private FieldInfo? _extractionPointCurrentField; private bool _readErrorLogged; public GameBindings(ManualLogSource logger) { _logger = logger; } public bool Resolve() { _valuableCurrentField = ResolveField(typeof(ValuableObject), "dollarValueCurrent", typeof(float)); _valuableOriginalField = ResolveField(typeof(ValuableObject), "dollarValueOriginal", typeof(float)); _valuableValueSetField = ResolveField(typeof(ValuableObject), "dollarValueSet", typeof(bool)); _runStartedField = ResolveField(typeof(RunManager), "runStarted", typeof(bool)); _extractionPointsField = ResolveField(typeof(RoundDirector), "extractionPoints", typeof(int)); _extractionPointsCompletedField = ResolveField(typeof(RoundDirector), "extractionPointsCompleted", typeof(int)); _extractionPointCurrentField = ResolveField(typeof(RoundDirector), "extractionPointCurrent", typeof(ExtractionPoint)); if (_valuableCurrentField != null && _valuableOriginalField != null && _runStartedField != null && _extractionPointsField != null) { return _extractionPointsCompletedField != null; } return false; } public bool TryCapture(ExtractionPoint extractionPoint, PhysGrabObject physObject, out CapturedValuable captured, out float localHaulScore, out string failure) { localHaulScore = 0f; if ((Object)(object)extractionPoint == (Object)null || (Object)(object)physObject == (Object)null) { captured = null; failure = "missing extraction point or physics object"; return false; } if (!TryCaptureMetadata(physObject, out captured, out ValuableObject valuable, out failure)) { return false; } try { localHaulScore = valuable.GetLocalPlayerHaulScore(((Component)extractionPoint).gameObject); if (!IsFinite(localHaulScore) || localHaulScore < 0f) { localHaulScore = 0f; } } catch (Exception ex) { localHaulScore = 0f; if (!_readErrorLogged) { _readErrorLogged = true; _logger.LogWarning((object)("[HaulOfFame/Bindings] Could not read vanilla haul score: " + ex.GetType().Name + ": " + ex.Message)); } } return true; } public bool TryCaptureStored(PhysGrabObject physObject, out CapturedValuable captured, out float localHaulScore, out string failure) { localHaulScore = 0f; if ((Object)(object)physObject == (Object)null) { captured = null; failure = "missing stored physics object"; return false; } if (!TryCaptureMetadata(physObject, out captured, out ValuableObject valuable, out failure)) { return false; } try { ExtractionPoint[] array = Object.FindObjectsOfType(); foreach (ExtractionPoint val in array) { if (!((Object)(object)val == (Object)null)) { float localPlayerHaulScore = valuable.GetLocalPlayerHaulScore(((Component)val).gameObject); if (IsFinite(localPlayerHaulScore) && localPlayerHaulScore > localHaulScore) { localHaulScore = localPlayerHaulScore; } } } } catch (Exception ex) { localHaulScore = 0f; if (!_readErrorLogged) { _readErrorLogged = true; _logger.LogWarning((object)("[HaulOfFame/Bindings] Could not read stored valuable haul score: " + ex.GetType().Name + ": " + ex.Message)); } } return true; } private bool TryCaptureMetadata(PhysGrabObject physObject, out CapturedValuable captured, out ValuableObject valuable, out string failure) { captured = null; valuable = null; failure = string.Empty; valuable = ((Component)physObject).GetComponent() ?? ((Component)physObject).GetComponentInChildren(); if ((Object)(object)valuable == (Object)null) { failure = "object is not a ValuableObject"; return false; } if (_valuableValueSetField != null && TryRead(_valuableValueSetField, valuable, out var value) && !value) { failure = "valuable value has not been initialized"; return false; } if (!TryRead(_valuableCurrentField, valuable, out var value2) || !IsFinite(value2) || value2 < 0f) { failure = "invalid current value"; return false; } float num = value2; if (TryRead(_valuableOriginalField, valuable, out var value3) && IsFinite(value3) && value3 > 0f) { num = Mathf.Max(value2, value3); } PhotonView val = ((Component)physObject).GetComponent() ?? ((Component)physObject).GetComponentInParent() ?? ((Component)valuable).GetComponent(); int viewId = (((Object)(object)val != (Object)null && val.ViewID > 0) ? val.ViewID : Mathf.Abs(((Object)physObject).GetInstanceID())); captured = new CapturedValuable(viewId, CleanObjectName(((Object)((Component)valuable).gameObject).name, "VALUABLE"), Mathf.RoundToInt(value2), Mathf.RoundToInt(num)); return true; } public bool IsLastExtractionCompleted(RoundDirector? director, out int completed, out int total) { completed = 0; total = 0; if ((Object)(object)director == (Object)null || !TryRead(_extractionPointsCompletedField, director, out completed) || !TryRead(_extractionPointsField, director, out total)) { return false; } if (total > 0) { return completed >= total; } return false; } public GamePhase GetPhase() { try { if (SemiFunc.IsMainMenu()) { return GamePhase.MainMenu; } RunManager instance = RunManager.instance; if ((Object)(object)instance == (Object)null || !TryRead(_runStartedField, instance, out var value) || !value) { return GamePhase.Unavailable; } if (SemiFunc.RunIsLevel()) { return GamePhase.Mission; } if (SemiFunc.RunIsShop()) { return GamePhase.Shop; } if (SemiFunc.RunIsLobby()) { return GamePhase.Lobby; } return GamePhase.Other; } catch { return GamePhase.Unavailable; } } public Transform? FindTruckAnchor() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { TruckScreenText val = Object.FindObjectOfType(); Scene scene; if ((Object)(object)val != (Object)null) { scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return ((Component)val).transform; } } TruckScreenOpen val2 = Object.FindObjectOfType(); if ((Object)(object)val2 != (Object)null) { scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return ((Component)val2).transform; } } } catch { } return null; } public ExtractionPoint? FindActiveExtractionPoint(int preferredViewId = 0) { if (preferredViewId > 0) { try { PhotonView val = PhotonView.Find(preferredViewId); ExtractionPoint val2 = (((Object)(object)val != (Object)null) ? (((Component)val).GetComponent() ?? ((Component)val).GetComponentInParent()) : null); if ((Object)(object)val2 != (Object)null) { return val2; } } catch { } } try { RoundDirector instance = RoundDirector.instance; if ((Object)(object)instance != (Object)null && TryRead(_extractionPointCurrentField, instance, out var value) && (Object)(object)value != (Object)null) { return value; } } catch { } try { ExtractionPoint[] array = Object.FindObjectsOfType(); foreach (ExtractionPoint val3 in array) { if ((Object)(object)val3 != (Object)null && !val3.isLocked) { return val3; } } } catch { } return null; } public string GetActorName(int actorNumber) { if (actorNumber > 0) { try { Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null && currentRoom.Players.TryGetValue(actorNumber, out var value) && !string.IsNullOrWhiteSpace(value.NickName)) { return CapturedValuable.SanitizeName(value.NickName); } } catch { } } try { PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); string value2 = (((Object)(object)val != (Object)null) ? SemiFunc.PlayerGetName(val) : null); if (!string.IsNullOrWhiteSpace(value2)) { return CapturedValuable.SanitizeName(value2); } } catch { } return "PLAYER"; } public static string CleanObjectName(string? rawName, string fallback) { if (string.IsNullOrWhiteSpace(rawName)) { return fallback; } string text = rawName.Replace("(Clone)", string.Empty).Trim(); string[] array = new string[5] { "Valuable ", "Valuable_", "Valuable-", "Prefab ", "Object " }; foreach (string text2 in array) { if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { text = text.Substring(text2.Length); break; } } text = text.Replace('_', ' ').Replace('-', ' '); StringBuilder stringBuilder = new StringBuilder(text.Length + 8); for (int j = 0; j < text.Length; j++) { char c = text[j]; if (j > 0 && char.IsUpper(c) && char.IsLower(text[j - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } string value = stringBuilder.ToString().Trim(); if (!string.IsNullOrWhiteSpace(value)) { return CapturedValuable.SanitizeName(value); } return fallback; } private FieldInfo? ResolveField(Type type, string name, Type expectedType) { FieldInfo fieldInfo = AccessTools.Field(type, name); if (fieldInfo == null || fieldInfo.FieldType != expectedType) { _logger.LogWarning((object)("[HaulOfFame/Bindings] Missing field " + type.Name + "." + name + " (" + expectedType.Name + ").")); return null; } return fieldInfo; } private bool TryRead(FieldInfo? field, object target, out T value) { value = default(T); if (field == null || target == null) { return false; } try { if (field.GetValue(target) is T val) { value = val; return true; } } catch (Exception ex) { if (!_readErrorLogged) { _readErrorLogged = true; _logger.LogWarning((object)("[HaulOfFame/Bindings] Reflection read failed: " + ex.GetType().Name + ": " + ex.Message)); } } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public sealed class HaulController : IDisposable { private const float FinalizationDelay = 1.35f; private readonly ModConfig _config; private readonly GameBindings _bindings; private readonly ManualLogSource _logger; private readonly HaulNetwork _network; private readonly AnnouncementHud _announcement; private readonly TrophyController _trophy; private readonly Dictionary _candidates = new Dictionary(); private readonly Dictionary> _contributions = new Dictionary>(); private readonly Dictionary> _storedCandidatesByBox = new Dictionary>(); private readonly Dictionary _storedBestSnapshotsByBox = new Dictionary(); private readonly HashSet _capturedViewIds = new HashSet(); private readonly HashSet _storedViewIds = new HashSet(); private TrophyModelSnapshot? _bestSnapshot; private ChampionData? _champion; private GamePhase _phase; private float _finalizationTime = -1f; private int _missionSceneHandle = int.MinValue; private int _lastTickFrame = -1; private bool _disposed; public HaulController(ModConfig config, GameBindings bindings, ManualLogSource logger) { _config = config; _bindings = bindings; _logger = logger; _network = new HaulNetwork(config, logger); _announcement = new AnnouncementHud(config); _trophy = new TrophyController(config, bindings, logger); _network.PrepareReceived += OnPrepareReceived; _network.ContributionReceived += OnContributionReceived; _network.ChampionReceived += OnChampionReceived; _config.Changed += OnConfigChanged; } public void Tick(float unscaledDeltaTime) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) if (_disposed || _lastTickFrame == Time.frameCount) { return; } _lastTickFrame = Time.frameCount; _config.PollForExternalChanges(unscaledDeltaTime); _network.Tick(); _announcement.Tick(unscaledDeltaTime); _trophy.Tick(unscaledDeltaTime); GamePhase phase = _bindings.GetPhase(); Scene activeScene = SceneManager.GetActiveScene(); if (phase != _phase) { if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Lifecycle] Phase changed: {_phase} -> {phase}, scene={((Scene)(ref activeScene)).name}[{((Scene)(ref activeScene)).buildIndex}]."); } _phase = phase; } switch (phase) { case GamePhase.MainMenu: if (_missionSceneHandle != int.MinValue || _champion != null || _candidates.Count > 0) { ResetAll("returned to main menu"); } return; case GamePhase.Mission: if (((Scene)(ref activeScene)).handle != _missionSceneHandle) { BeginMission(activeScene); } break; } if (_finalizationTime > 0f && Time.realtimeSinceStartup >= _finalizationTime && IsAuthority()) { _finalizationTime = -1f; FinalizeChampion(); } } public void OnValuableExtracting(ExtractionPoint extractionPoint, PhysGrabObject physObject) { if (!_disposed && _config.Enabled.Value && !((Object)(object)extractionPoint == (Object)null) && !((Object)(object)physObject == (Object)null) && _bindings.GetPhase() == GamePhase.Mission && IsAuthority()) { PhotonView val = ((Component)physObject).GetComponent() ?? ((Component)physObject).GetComponentInParent(); int num = (((Object)(object)val != (Object)null) ? val.ViewID : 0); PhotonView val2 = ((Component)extractionPoint).GetComponent() ?? ((Component)extractionPoint).GetComponentInParent(); int extractionPointViewId = (((Object)(object)val2 != (Object)null) ? val2.ViewID : 0); CaptureLocal(extractionPoint, physObject, "host extraction hook"); if (PhotonNetwork.InRoom && num > 0) { _network.BroadcastPrepare(num, extractionPointViewId); } } } public void OnExtractionCompleted(RoundDirector director) { if (_disposed || !_config.Enabled.Value || !IsAuthority()) { return; } int completed; int total; bool flag = _bindings.IsLastExtractionCompleted(director, out completed, out total); _logger.LogInfo((object)$"[HaulOfFame/Extraction] Extraction completed: {completed}/{total}, captured={_candidates.Count}, final={flag}."); if (flag) { _finalizationTime = Time.realtimeSinceStartup + 1.35f; if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Extraction] Final result scheduled in {1.35f:0.00}s to receive remaining contribution reports."); } } } public void OnValuableStored(ItemValuableBox box, PhysGrabObject physObject) { if (_disposed || !_config.Enabled.Value || (Object)(object)box == (Object)null || (Object)(object)physObject == (Object)null || _bindings.GetPhase() != GamePhase.Mission) { return; } if (!_bindings.TryCaptureStored(physObject, out CapturedValuable captured, out float localHaulScore, out string failure)) { if (_config.DebugLogging.Value) { _logger.LogWarning((object)("[HaulOfFame/Hauler] Ignored absorbed target: " + failure + ".")); } } else if (_storedViewIds.Add(captured.ViewId)) { PhotonView val = ((Component)box).GetComponent() ?? ((Component)box).GetComponentInParent(); int num = (((Object)(object)val != (Object)null && val.ViewID > 0) ? val.ViewID : Mathf.Abs(((Object)box).GetInstanceID())); if (!_storedCandidatesByBox.TryGetValue(num, out Dictionary value)) { value = new Dictionary(); _storedCandidatesByBox[num] = value; } value[captured.ViewId] = captured; if ((!_storedBestSnapshotsByBox.TryGetValue(num, out TrophyModelSnapshot value2) || captured.Beats(value2.Valuable)) && TrophyModelSnapshot.TryCreate(captured, physObject, _logger, out TrophyModelSnapshot snapshot)) { value2?.Dispose(); _storedBestSnapshotsByBox[num] = snapshot; } int actorNumber = ((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0); RecordContribution(actorNumber, captured.ViewId, localHaulScore); if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { _network.SendContribution(captured.ViewId, localHaulScore); } if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Hauler] Stored '{captured.Name}', view={captured.ViewId}, box={num}, value=${captured.CurrentValue:N0}, localScore={localHaulScore:0.###}."); } } } public void OnValuableBoxExtracted(ItemValuableBox box) { if (_disposed || !_config.Enabled.Value || (Object)(object)box == (Object)null || !IsAuthority()) { return; } PhotonView val = ((Component)box).GetComponent() ?? ((Component)box).GetComponentInParent(); int num = (((Object)(object)val != (Object)null && val.ViewID > 0) ? val.ViewID : Mathf.Abs(((Object)box).GetInstanceID())); if (!_storedCandidatesByBox.TryGetValue(num, out Dictionary value) || value.Count == 0) { if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Hauler] Extracted box={num} had no individually tracked valuables."); } return; } foreach (KeyValuePair item in value) { _candidates[item.Key] = item.Value; _capturedViewIds.Add(item.Key); } _logger.LogInfo((object)$"[HaulOfFame/Hauler] Extracted box={num}; promoted {value.Count} stored valuables into the final selection."); } public void HandleSceneChanged(Scene scene) { _announcement.Hide(); _trophy.HandleSceneChanged(); if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Scene] Loaded {((Scene)(ref scene)).name}[{((Scene)(ref scene)).buildIndex}]; championPreserved={_champion != null}."); } } private void OnPrepareReceived(int viewId, int extractionPointViewId) { if (!_config.Enabled.Value || _capturedViewIds.Contains(viewId)) { return; } try { PhotonView val = PhotonView.Find(viewId); PhysGrabObject val2 = (((Object)(object)val != (Object)null) ? (((Component)val).GetComponent() ?? ((Component)val).GetComponentInParent() ?? ((Component)val).GetComponentInChildren()) : null); ExtractionPoint val3 = _bindings.FindActiveExtractionPoint(extractionPointViewId); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { _logger.LogWarning((object)$"[HaulOfFame/Capture] Could not resolve prepared valuable view={viewId}, extractionView={extractionPointViewId}."); } else { CaptureLocal(val3, val2, "host prepare event"); } } catch (Exception ex) { _logger.LogWarning((object)$"[HaulOfFame/Capture] Prepared valuable view={viewId} failed: {ex.GetType().Name}: {ex.Message}"); } } private void CaptureLocal(ExtractionPoint extractionPoint, PhysGrabObject physObject, string source) { if (!_bindings.TryCapture(extractionPoint, physObject, out CapturedValuable captured, out float localHaulScore, out string failure)) { if (_config.DebugLogging.Value) { _logger.LogWarning((object)("[HaulOfFame/Capture] Ignored extraction target from " + source + ": " + failure + ".")); } } else if (_capturedViewIds.Add(captured.ViewId)) { _candidates[captured.ViewId] = captured; if (captured.Beats(_bestSnapshot?.Valuable) && TrophyModelSnapshot.TryCreate(captured, physObject, _logger, out TrophyModelSnapshot snapshot)) { _bestSnapshot?.Dispose(); _bestSnapshot = snapshot; } int actorNumber = ((PhotonNetwork.InRoom && PhotonNetwork.LocalPlayer != null) ? PhotonNetwork.LocalPlayer.ActorNumber : 0); RecordContribution(actorNumber, captured.ViewId, localHaulScore); if (PhotonNetwork.InRoom && !PhotonNetwork.IsMasterClient) { _network.SendContribution(captured.ViewId, localHaulScore); } _logger.LogInfo((object)$"[HaulOfFame/Capture] Extracted '{captured.Name}', view={captured.ViewId}, value=${captured.CurrentValue:N0}/${captured.OriginalValue:N0}, localScore={localHaulScore:0.###}, source={source}, modelCopy={_bestSnapshot?.Valuable.ViewId == captured.ViewId}."); } } private void OnContributionReceived(int actorNumber, int viewId, float score) { if (!_candidates.ContainsKey(viewId) && !IsStoredCandidate(viewId)) { if (_config.DebugLogging.Value) { _logger.LogWarning((object)$"[HaulOfFame/Network] Contribution arrived for unknown view={viewId} from actor={actorNumber}."); } return; } RecordContribution(actorNumber, viewId, score); if (_config.DebugLogging.Value) { _logger.LogInfo((object)$"[HaulOfFame/Network] Contribution accepted: actor={actorNumber}, view={viewId}, score={score:0.###}."); } } private void RecordContribution(int actorNumber, int viewId, float score) { score = Mathf.Clamp(score, 0f, 1000f); if (!_contributions.TryGetValue(viewId, out Dictionary value)) { value = new Dictionary(); _contributions[viewId] = value; } if (!value.TryGetValue(actorNumber, out var value2) || score > value2) { value[actorNumber] = score; } } private void FinalizeChampion() { CapturedValuable capturedValuable = null; foreach (CapturedValuable value2 in _candidates.Values) { if (value2.Beats(capturedValuable)) { capturedValuable = value2; } } if (capturedValuable == null) { _logger.LogWarning((object)"[HaulOfFame/Result] Final extraction completed, but no valid valuable was captured."); return; } int[] array = Array.Empty(); float[] array2 = Array.Empty(); if (_contributions.TryGetValue(capturedValuable.ViewId, out Dictionary value)) { array = new int[value.Count]; array2 = new float[value.Count]; int num = 0; foreach (KeyValuePair item in value) { array[num] = item.Key; array2[num] = item.Value; num++; } } bool flag = !PhotonNetwork.InRoom || _network.AllPlayersHaveMod(); ChampionData champion = new ChampionData(capturedValuable, array, array2, flag); HandleChampion(champion); if (PhotonNetwork.InRoom) { _network.BroadcastChampion(champion); } _logger.LogInfo((object)$"[HaulOfFame/Result] Champion selected: '{capturedValuable.Name}', view={capturedValuable.ViewId}, value=${capturedValuable.CurrentValue:N0}, retained={capturedValuable.RetainedPercent:0.#}%, contributors={array.Length}, completeAttribution={flag}."); } private void OnChampionReceived(ChampionData champion) { if (_config.Enabled.Value) { HandleChampion(champion); _logger.LogInfo((object)$"[HaulOfFame/Network] Host champion received: '{champion.Valuable.Name}', view={champion.Valuable.ViewId}, value=${champion.Valuable.CurrentValue:N0}."); } } private void HandleChampion(ChampionData champion) { _champion = champion; (string, string, string, string) tuple = FormatChampion(champion); _announcement.Show(tuple.Item1, tuple.Item2, tuple.Item3); TrophyModelSnapshot snapshot = FindSnapshot(champion.Valuable.ViewId); _trophy.ShowChampion(champion, snapshot, tuple.Item4); } private (string Title, string Name, string Details, string Plaque) FormatChampion(ChampionData champion) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) bool isSpanish = _config.IsSpanish; string text = ColorUtility.ToHtmlStringRGB(_config.GetAccentColor()); string item = (isSpanish ? "HAUL OF FAME — CAMPEÓN DE CARGA" : "HAUL OF FAME — HAUL CHAMPION"); string text2 = "$" + champion.Valuable.CurrentValue.ToString("N0", CultureInfo.InvariantCulture); string text3 = champion.Valuable.RetainedPercent.ToString("0.#", CultureInfo.InvariantCulture) + "%"; string item2 = (isSpanish ? (text2 + " • " + text3 + " CONSERVADO") : (text2 + " • " + text3 + " PRESERVED")); string text4 = FormatContributors(champion, isSpanish); string item3 = "HAUL OF FAME\n" + champion.Valuable.Name.ToUpperInvariant() + "\n" + text2 + " • " + text3 + " " + (isSpanish ? "CONSERVADO" : "PRESERVED") + "\n" + text4; return (Title: item, Name: champion.Valuable.Name.ToUpperInvariant(), Details: item2, Plaque: item3); } private string FormatContributors(ChampionData champion, bool spanish) { List<(int, float)> list = new List<(int, float)>(); float num = 0f; for (int i = 0; i < champion.ContributorActors.Length && i < champion.ContributorScores.Length; i++) { float num2 = Mathf.Max(0f, champion.ContributorScores[i]); if (!(num2 <= 0f)) { list.Add((champion.ContributorActors[i], num2)); num += num2; } } if (num <= 0f || list.Count == 0) { if (!spanish) { return "TRANSPORTED BY THE CREW"; } return "TRANSPORTADO POR LA TRIPULACIÓN"; } list.Sort(((int Actor, float Score) left, (int Actor, float Score) right) => right.Score.CompareTo(left.Score)); int num3 = Mathf.Clamp(_config.MaxContributors.Value, 1, 8); float num4 = Mathf.Clamp(_config.MinimumContributionPercent.Value, 0f, 100f); List list2 = new List(); foreach (var item in list) { if (list2.Count >= num3) { break; } float num5 = item.Item2 * 100f / num; if (!(num5 < num4)) { list2.Add($"{_bindings.GetActorName(item.Item1).ToUpperInvariant()} {num5:0}%"); } } string text = (spanish ? "MVP: " : "MVP: ") + ((list2.Count > 0) ? string.Join(" • ", list2) : (spanish ? "TRIPULACIÓN" : "CREW")); if (!champion.AttributionComplete) { text += (spanish ? " • DATOS PARCIALES" : " • PARTIAL DATA"); } return text; } private void BeginMission(Scene scene) { ResetAll("new mission scene"); _missionSceneHandle = ((Scene)(ref scene)).handle; _logger.LogInfo((object)$"[HaulOfFame/Lifecycle] Mission tracking started in {((Scene)(ref scene)).name}[{((Scene)(ref scene)).buildIndex}]."); } private void ResetAll(string reason) { _trophy.Clear(); _announcement.Hide(); _bestSnapshot?.Dispose(); _bestSnapshot = null; foreach (TrophyModelSnapshot value in _storedBestSnapshotsByBox.Values) { value.Dispose(); } _storedBestSnapshotsByBox.Clear(); _champion = null; _candidates.Clear(); _contributions.Clear(); _storedCandidatesByBox.Clear(); _capturedViewIds.Clear(); _storedViewIds.Clear(); _finalizationTime = -1f; _missionSceneHandle = int.MinValue; if (_config.DebugLogging.Value) { _logger.LogInfo((object)("[HaulOfFame/Lifecycle] Runtime state cleared: " + reason + ".")); } } private void OnConfigChanged() { _announcement.ApplyConfig(); _trophy.ApplyConfig(); if (_champion != null) { (string, string, string, string) tuple = FormatChampion(_champion); TrophyModelSnapshot snapshot = FindSnapshot(_champion.Valuable.ViewId); _trophy.ShowChampion(_champion, snapshot, tuple.Item4); } _logger.LogInfo((object)"[HaulOfFame/Config] Configuration applied without restarting the game."); } private static bool IsAuthority() { if (PhotonNetwork.InRoom) { return PhotonNetwork.IsMasterClient; } return true; } private bool IsStoredCandidate(int viewId) { foreach (Dictionary value in _storedCandidatesByBox.Values) { if (value.ContainsKey(viewId)) { return true; } } return false; } private TrophyModelSnapshot? FindSnapshot(int viewId) { if (_bestSnapshot != null && _bestSnapshot.Valuable.ViewId == viewId) { return _bestSnapshot; } foreach (TrophyModelSnapshot value in _storedBestSnapshotsByBox.Values) { if (value.Valuable.ViewId == viewId) { return value; } } return null; } public void Dispose() { if (_disposed) { return; } _disposed = true; _config.Changed -= OnConfigChanged; _network.PrepareReceived -= OnPrepareReceived; _network.ContributionReceived -= OnContributionReceived; _network.ChampionReceived -= OnChampionReceived; _trophy.Clear(); _bestSnapshot?.Dispose(); _bestSnapshot = null; foreach (TrophyModelSnapshot value in _storedBestSnapshotsByBox.Values) { value.Dispose(); } _storedBestSnapshotsByBox.Clear(); _network.Dispose(); _announcement.Dispose(); _trophy.Dispose(); } } } namespace HaulOfFame.Config { public enum DisplayLanguage { Auto, English, Spanish } public enum TrophyPositionMode { FixedWorld, ScreenRelative } public sealed class ModConfig : IDisposable { private const float ExternalReloadInterval = 0.5f; private readonly ConfigFile _configFile; private readonly List _unsubscribeActions = new List(); private DateTime _lastWriteTimeUtc; private long _lastFileLength; private float _reloadTimer; private bool _reloading; public ConfigEntry Enabled { get; } public ConfigEntry ShowAnnouncement { get; } public ConfigEntry ShowWorldTrophy { get; } public ConfigEntry ShowEmptyBase { get; } public ConfigEntry AnnouncementDuration { get; } public ConfigEntry MaxContributors { get; } public ConfigEntry MinimumContributionPercent { get; } public ConfigEntry Language { get; } public ConfigEntry TrophyScale { get; } public ConfigEntry SpinSpeed { get; } public ConfigEntry BobHeight { get; } public ConfigEntry BobSpeed { get; } public ConfigEntry PositionMode { get; } public ConfigEntry WorldX { get; } public ConfigEntry WorldY { get; } public ConfigEntry WorldZ { get; } public ConfigEntry OffsetX { get; } public ConfigEntry OffsetY { get; } public ConfigEntry OffsetZ { get; } public ConfigEntry RotationY { get; } public ConfigEntry AccentColor { get; } public ConfigEntry SoundEnabled { get; } public ConfigEntry SoundVolume { get; } public ConfigEntry DebugLogging { get; } public bool IsSpanish { get { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if (Language.Value != DisplayLanguage.Spanish) { if (Language.Value == DisplayLanguage.Auto) { return (int)Application.systemLanguage == 34; } return false; } return true; } } public event Action? Changed; public ModConfig(ConfigFile config) { _configFile = config; Enabled = Bind(config, "General", "Enabled", defaultValue: true, "Enable Haul of Fame."); ShowAnnouncement = Bind(config, "General", "ShowAnnouncement", defaultValue: true, "Show the post-extraction champion banner."); ShowWorldTrophy = Bind(config, "General", "ShowWorldTrophy", defaultValue: true, "Display the winning valuable as a harmless trophy near the truck screen."); ShowEmptyBase = Bind(config, "General", "ShowEmptyBase", defaultValue: true, "Keep the empty trophy pedestal visible in the truck before a champion is selected."); AnnouncementDuration = Bind(config, "General", "AnnouncementDuration", 5f, "Seconds the champion banner remains visible."); MaxContributors = Bind(config, "General", "MaxContributors", 4, "Maximum contributor names displayed on the trophy plaque."); MinimumContributionPercent = Bind(config, "General", "MinimumContributionPercent", 2f, "Hide contributors below this percentage."); Language = Bind(config, "General", "Language", DisplayLanguage.Auto, "Auto, English, or Spanish."); TrophyScale = Bind(config, "Appearance", "TrophyScale", 1f, "Overall world trophy scale."); SpinSpeed = Bind(config, "Appearance", "SpinSpeed", 18f, "Model rotation speed in degrees per second."); BobHeight = Bind(config, "Appearance", "BobHeight", 0.06f, "Vertical hologram movement in metres."); BobSpeed = Bind(config, "Appearance", "BobSpeed", 1.4f, "Hologram bobbing speed."); PositionMode = Bind(config, "Position", "PositionMode", TrophyPositionMode.FixedWorld, "FixedWorld uses the calibrated truck-floor position. ScreenRelative uses offsets from the truck screen."); WorldX = Bind(config, "Position", "WorldX", 1.62f, "Calibrated world X position of the trophy base."); WorldY = Bind(config, "Position", "WorldY", 0.701f, "Calibrated world Y position of the trophy base."); WorldZ = Bind(config, "Position", "WorldZ", -0.809f, "Calibrated world Z position of the trophy base."); OffsetX = Bind(config, "Appearance", "OffsetX", 1.25f, "Position to the right of the truck screen."); OffsetY = Bind(config, "Appearance", "OffsetY", -0.35f, "Vertical position relative to the truck screen."); OffsetZ = Bind(config, "Appearance", "OffsetZ", 0.55f, "Forward position relative to the truck screen."); RotationY = Bind(config, "Appearance", "RotationY", 0f, "Additional trophy rotation around the vertical axis."); AccentColor = Bind(config, "Appearance", "AccentColor", "#55FF78", "Hologram accent color in HTML hex format."); SoundEnabled = Bind(config, "Appearance", "SoundEnabled", defaultValue: true, "Play a short result sound."); SoundVolume = Bind(config, "Appearance", "SoundVolume", 0.65f, "Result sound volume from 0 to 1."); DebugLogging = Bind(config, "Debug", "DebugLogging", defaultValue: false, "Write detailed capture, network, and display diagnostics without per-frame spam."); CaptureFileState(); } public Color GetAccentColor() { //IL_002e: 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) Color result = default(Color); if (!ColorUtility.TryParseHtmlString(AccentColor.Value, ref result)) { return new Color(0.33f, 1f, 0.47f, 1f); } return result; } public bool PollForExternalChanges(float unscaledDeltaTime) { _reloadTimer -= Mathf.Max(0f, unscaledDeltaTime); if (_reloadTimer > 0f) { return false; } _reloadTimer = 0.5f; try { string configFilePath = _configFile.ConfigFilePath; if (!File.Exists(configFilePath)) { return false; } FileInfo fileInfo = new FileInfo(configFilePath); if (fileInfo.LastWriteTimeUtc == _lastWriteTimeUtc && fileInfo.Length == _lastFileLength) { return false; } bool saveOnConfigSet = _configFile.SaveOnConfigSet; try { _reloading = true; _configFile.SaveOnConfigSet = false; _configFile.Reload(); } finally { _reloading = false; _configFile.SaveOnConfigSet = saveOnConfigSet; } CaptureFileState(); this.Changed?.Invoke(); return true; } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } } private ConfigEntry Bind(ConfigFile config, string section, string key, T defaultValue, string description) { ConfigEntry entry = config.Bind(section, key, defaultValue, description); entry.SettingChanged += OnSettingChanged; _unsubscribeActions.Add(delegate { entry.SettingChanged -= OnSettingChanged; }); return entry; } private void OnSettingChanged(object sender, EventArgs args) { if (!_reloading) { this.Changed?.Invoke(); } } private void CaptureFileState() { string configFilePath = _configFile.ConfigFilePath; if (File.Exists(configFilePath)) { FileInfo fileInfo = new FileInfo(configFilePath); _lastWriteTimeUtc = fileInfo.LastWriteTimeUtc; _lastFileLength = fileInfo.Length; } } public void Dispose() { foreach (Action unsubscribeAction in _unsubscribeActions) { unsubscribeAction(); } _unsubscribeActions.Clear(); this.Changed = null; } } }