using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [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("LootPulse")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+775dd9c5dfdb8222af9eee00db9dd7384abd60f0")] [assembly: AssemblyProduct("LootPulse")] [assembly: AssemblyTitle("LootPulse")] [assembly: AssemblyVersion("1.0.0.0")] namespace LootPulse; [BepInPlugin("com.hiarlyscripter.repo.lootpulse", "LootPulse", "1.0.6")] public sealed class LootPulsePlugin : BaseUnityPlugin { internal static ConfigEntry ScanKey; internal static ConfigEntry ScanRange; internal static ConfigEntry CooldownSeconds; internal static ConfigEntry EnableVisualBrackets; internal static ConfigEntry EnableMapIcons; internal static ConfigEntry RequireLevelOrRun; internal static ConfigEntry VerboseLogging; private float _lastScanTime = -999f; private int _lastTickFrame = -1; private bool _harmonyLoopLogged; private bool _pluginLoopLogged; private static string _patchedMethodName; private static bool _discoverApiChecked; private static MethodInfo _discoverWithState; private static MethodInfo _discoverNoParam; private static object _discoverStateValue; internal static LootPulsePlugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_015e: 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_024c: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ScanKey = ((BaseUnityPlugin)this).Config.Bind("Scanner", "ScanKey", (KeyCode)102, "Key that triggers the valuable scanner."); ScanRange = ((BaseUnityPlugin)this).Config.Bind("Scanner", "ScanRange", 30f, new ConfigDescription("Detection radius in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 200f), Array.Empty())); CooldownSeconds = ((BaseUnityPlugin)this).Config.Bind("Scanner", "CooldownSeconds", 4f, new ConfigDescription("Minimum seconds between consecutive scans.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); EnableVisualBrackets = ((BaseUnityPlugin)this).Config.Bind("Scanner", "EnableVisualBrackets", true, "Display visual discovery brackets on valuables found."); EnableMapIcons = ((BaseUnityPlugin)this).Config.Bind("Scanner", "EnableMapIcons", true, "Add valuable icon to the map when scanning."); RequireLevelOrRun = ((BaseUnityPlugin)this).Config.Bind("Scanner", "RequireLevelOrRun", true, "Only scan during active runs (generated level). Disable only for testing outside a level."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("Scanner", "VerboseLogging", false, "Log details for each individually processed item."); Log.LogInfo((object)$"v1.0.6 loaded. Press {ScanKey.Value} to scan valuables."); Log.LogInfo((object)($"Config | key={ScanKey.Value} | range={ScanRange.Value} | cooldown={CooldownSeconds.Value} | " + $"visualBrackets={EnableVisualBrackets.Value} | mapIcons={EnableMapIcons.Value} | " + $"requireLevelOrRun={RequireLevelOrRun.Value} | verbose={VerboseLogging.Value}")); Assembly executingAssembly = Assembly.GetExecutingAssembly(); Log.LogInfo((object)"Build marker: v1.0.6-release"); Log.LogInfo((object)("Assembly location: " + executingAssembly.Location)); try { Log.LogInfo((object)$"Assembly file time UTC: {File.GetLastWriteTimeUtc(executingAssembly.Location):yyyy-MM-dd HH:mm:ss}"); } catch { } InstallHarmonyPatch(new Harmony("com.hiarlyscripter.repo.lootpulse")); } private void Update() { if (!_pluginLoopLogged) { _pluginLoopLogged = true; Log.LogInfo((object)"Update loop active. (plugin MonoBehaviour)"); } Tick("Plugin.Update"); } internal void TickFromGameLoop(string source) { if (!_harmonyLoopLogged) { _harmonyLoopLogged = true; Log.LogInfo((object)("Harmony game loop active: " + source)); } Tick(source); } private void Tick(string source) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0053: Unknown result type (might be due to invalid IL or missing references) int frameCount = Time.frameCount; if (frameCount == _lastTickFrame) { return; } _lastTickFrame = frameCount; bool keyDown = Input.GetKeyDown(ScanKey.Value); if (!keyDown && (int)ScanKey.Value != 102) { keyDown = Input.GetKeyDown((KeyCode)102); } if (!keyDown) { return; } Log.LogInfo((object)$"Scan key detected: {ScanKey.Value} | source={source}"); float time = Time.time; float num = time - _lastScanTime; if (num < CooldownSeconds.Value) { Log.LogInfo((object)$"Scan blocked: cooldown active ({CooldownSeconds.Value - num:F1}s remaining)"); return; } if (RequireLevelOrRun.Value) { bool flag = false; try { flag = SemiFunc.RunIsLevel(); } catch (Exception ex) { Log.LogWarning((object)("RunIsLevel check failed (" + ex.Message + "). Proceeding without validation.")); flag = true; } if (!flag) { Log.LogInfo((object)"Scan blocked: not in active level/run (RequireLevelOrRun=true)"); return; } } _lastScanTime = time; RunScan(); } private static void InstallHarmonyPatch(Harmony harmony) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown (string, string)[] obj = new(string, string)[3] { ("GameDirector", "Update"), ("PlayerAvatar", "Update"), ("PlayerController", "Update") }; HarmonyMethod val = new HarmonyMethod(typeof(LootPulsePlugin).GetMethod("HarmonyTickPostfix", BindingFlags.Static | BindingFlags.NonPublic)); (string, string)[] array = obj; for (int i = 0; i < array.Length; i++) { var (text, text2) = array[i]; try { Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int j = 0; j < assemblies.Length; j++) { type = assemblies[j].GetType(text); if (type != null) { break; } } if (type == null) { Log.LogWarning((object)("Harmony fallback: type not found — " + text)); continue; } MethodInfo method = type.GetMethod(text2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null) { Log.LogWarning((object)("Harmony fallback: method not found — " + text + "." + text2)); continue; } harmony.Patch((MethodBase)method, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patchedMethodName = text + "." + text2; Log.LogInfo((object)("Harmony tick patch installed: " + _patchedMethodName)); string watchedMethod = _patchedMethodName; ThreadPool.QueueUserWorkItem(delegate { Thread.Sleep(60000); if ((Object)(object)Instance != (Object)null && !Instance._harmonyLoopLogged) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("WARNING: Harmony patch installed but no game loop tick observed yet. Patched method: " + watchedMethod)); } } }); return; } catch (Exception ex) { Log.LogWarning((object)("Harmony patch failed for " + text + "." + text2 + ": " + ex.Message)); } } Log.LogError((object)"ERROR: no valid game loop method found. LootPulse cannot scan."); } private static void HarmonyTickPostfix() { Instance?.TickFromGameLoop(_patchedMethodName); } private void RunScan() { //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_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) Vector3 zero = Vector3.zero; try { PlayerAvatar val = SemiFunc.PlayerAvatarLocal(); if ((Object)(object)val == (Object)null) { Log.LogInfo((object)"Scan blocked: local player not found"); return; } if ((Object)(object)val.playerTransform == (Object)null) { Log.LogInfo((object)"Scan blocked: local player not found (transform null)"); return; } zero = val.playerTransform.position; } catch (Exception ex) { Log.LogWarning((object)("Scan blocked: local player not found (" + ex.Message + ")")); return; } List list = null; bool flag = false; try { list = SemiFunc.PhysGrabObjectAllValuablesWithinRange(ScanRange.Value, zero, false, default(LayerMask)); flag = true; } catch (Exception ex2) { Log.LogWarning((object)("PhysGrabObjectAllValuablesWithinRange failed (" + ex2.Message + "). Using fallback.")); try { list = FindValuablesFallback(zero, ScanRange.Value); } catch (Exception ex3) { Log.LogWarning((object)("Scan blocked: scan API unavailable (" + ex3.Message + ")")); return; } } if (list == null) { Log.LogWarning((object)"Scan blocked: scan API unavailable (null result)"); return; } int count = list.Count; int num = 0; int num2 = 0; int num3 = 0; Log.LogInfo((object)($"Scan executed | origin={zero:F1} | range={ScanRange.Value}m | " + string.Format("api={0} | found={1}", flag ? "SemiFunc" : "FindObjectsOfType", count))); foreach (PhysGrabObject item in list) { if ((Object)(object)item == (Object)null) { continue; } try { ValuableObject component = ((Component)item).GetComponent(); if ((Object)(object)component == (Object)null) { if (VerboseLogging.Value) { Log.LogDebug((object)("No ValuableObject on: " + ((Object)item).name)); } continue; } if (VerboseLogging.Value) { Log.LogDebug((object)("Processing: " + ((Object)item).name)); } if (EnableVisualBrackets.Value && TryApplyBrackets(component)) { num++; } if (EnableMapIcons.Value && TryAddToMap(component)) { num2++; } } catch (Exception ex4) { num3++; Log.LogWarning((object)("Error processing " + ((item != null) ? ((Object)item).name : null) + ": " + ex4.Message)); } } Log.LogInfo((object)$"Result | brackets={num}/{count} | map={num2}/{count} | errors={num3}"); } private static bool TryApplyBrackets(ValuableObject vo) { try { ResolveDiscoverApi(); if (_discoverWithState != null) { _discoverWithState.Invoke(vo, new object[1] { _discoverStateValue }); return true; } if (_discoverNoParam != null) { _discoverNoParam.Invoke(vo, null); return true; } return false; } catch (Exception ex) { if (VerboseLogging.Value) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)("Discover exception: " + ex.Message)); } } return false; } } private static void ResolveDiscoverApi() { if (_discoverApiChecked) { return; } _discoverApiChecked = true; Type typeFromHandle = typeof(ValuableObject); BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public; Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { type = assemblies[i].GetType("ValuableDiscoverGraphic"); if (type != null) { break; } } if (type != null) { Type nestedType = type.GetNestedType("State", BindingFlags.Public); if (nestedType != null && nestedType.IsEnum) { MethodInfo method = typeFromHandle.GetMethod("Discover", bindingAttr, null, new Type[1] { nestedType }, null); if (method != null) { _discoverWithState = method; _discoverStateValue = Enum.Parse(nestedType, "Discover"); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Discover API resolved: ValuableObject.Discover(ValuableDiscoverGraphic.State)"); } return; } } } MethodInfo method2 = typeFromHandle.GetMethod("Discover", bindingAttr, null, Type.EmptyTypes, null); if (method2 != null) { _discoverNoParam = method2; ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)"Discover API resolved: ValuableObject.Discover() [no-param fallback]"); } } else { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)"Discover API not found in any form. Visual brackets disabled."); } } } private static bool TryAddToMap(ValuableObject vo) { try { Map instance = Map.Instance; if ((Object)(object)instance == (Object)null) { if (VerboseLogging.Value) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)"Map.Instance is null."); } } return false; } instance.AddValuable(vo); return true; } catch (Exception ex) { if (VerboseLogging.Value) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogDebug((object)("Map.AddValuable failed: " + ex.Message)); } } return false; } } private static List FindValuablesFallback(Vector3 origin, float range) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) List list = new List(); float num = range * range; ValuableObject[] array = Object.FindObjectsOfType(); foreach (ValuableObject val in array) { if ((Object)(object)val == (Object)null) { continue; } Vector3 val2 = ((Component)val).transform.position - origin; if (!(((Vector3)(ref val2)).sqrMagnitude > num)) { PhysGrabObject component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { list.Add(component); } } } ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"Fallback: {list.Count} valuables found via FindObjectsOfType."); } return list; } }