using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using BoneLib; using FuzzySharp; using FuzzySharp.Edits; using FuzzySharp.Extensions; using FuzzySharp.Extractor; using FuzzySharp.PreProcess; using FuzzySharp.SimilarityRatio; using FuzzySharp.SimilarityRatio.Scorer; using FuzzySharp.SimilarityRatio.Scorer.Composite; using FuzzySharp.SimilarityRatio.Scorer.Generic; using FuzzySharp.SimilarityRatio.Scorer.StrategySensitive; using FuzzySharp.SimilarityRatio.Scorer.StrategySensitive.Generic; using FuzzySharp.SimilarityRatio.Strategy; using FuzzySharp.SimilarityRatio.Strategy.Generic; using FuzzySharp.Utils; using HarmonyLib; using Il2CppCysharp.Threading.Tasks; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Bonelab; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.SceneStreaming; using Il2CppSLZ.Marrow.Warehouse; using Il2CppSLZ.UI; using Il2CppTMPro; using LabFusion.Data; using LabFusion.Marrow.Serialization; using LabFusion.Network; using LabFusion.Player; using LabFusion.Scene; using LabFusion.Utilities; using MelonLoader; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using SearchThing; using SearchThing.Extensions; using SearchThing.Extensions.Components; using SearchThing.Extensions.Pages; using SearchThing.Extensions.Panel; using SearchThing.Extensions.Panel.Abstract; using SearchThing.Extensions.Panel.Data; using SearchThing.Extensions.Panel.Filter; using SearchThing.Extensions.Panel.History; using SearchThing.Extensions.Sort; using SearchThing.Fusion; using SearchThing.History; using SearchThing.Keyboard; using SearchThing.Patches; using SearchThing.Patches.Compatibility; using SearchThing.Presets; using SearchThing.Presets.Data; using SearchThing.Search; using SearchThing.Util; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Mod), "SearchThing", "0.5.0", "Mash", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("SearchThing")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SearchThing")] [assembly: AssemblyTitle("SearchThing")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace SearchThing { public class Mod : MelonMod { public static bool IsFusionLoaded; public override void OnInitializeMelon() { IsFusionLoaded = MelonBase.FindMelon("LabFusion", "Lakatrazz") != null; Hooking.OnWarehouseReady += OnWarehouseReady; } private static void HookFusion() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown FusionNetworkSpawnPatch.TryInitialize(); MultiplayerHooking.OnTargetLevelLoaded += new UpdateEvent(SpawnGunPatches.ClearSelectedCrate); } private static void OnWarehouseReady() { SearchManager.InitializeSearchThread(); PresetManager.LoadPresets(); if (IsFusionLoaded) { HookFusion(); } } public override void OnDeinitializeMelon() { SearchManager.ShutdownSearchThread(); } } public static class SpawnablesPanelManager { private static SpawnablePanelExtension? _extension; public static void Load(SpawnablesPanelView panelView) { if (_extension != null && _extension.Is(panelView)) { if (_extension.IsSearchActive()) { _extension.RequestRefresh(); } } else { _extension = new SpawnablePanelExtension(panelView); } } public static void OnTabSelected(SpawnablesPanelView panelView, int index) { if (_extension != null && _extension.Is(panelView)) { if (index == 5) { _extension.Show(); } else { _extension.Hide(); } } } public static SpawnablePanelExtension? Get() { return _extension; } public static bool TryGet(SpawnablesPanelView instance, [MaybeNullWhen(false)] out SpawnablePanelExtension extension) { if (_extension != null && _extension.Is(instance)) { extension = _extension; return true; } extension = null; return false; } } } namespace SearchThing.Util { public class BodylogAccessor { private static PullCordDevice? _cordDevice; private static RigManager GetFusionRig() { return RigData.Refs.RigManager; } private static RigManager GetRig() { if (Mod.IsFusionLoaded) { return GetFusionRig(); } return Player.RigManager; } public static PullCordDevice? GetCordDevice() { if ((Object)(object)_cordDevice != (Object)null) { return _cordDevice; } RigManager rig = GetRig(); if ((Object)(object)rig == (Object)null) { return null; } _cordDevice = (from item in (IEnumerable)rig.inventory.specialItems select ((Component)item).transform into transform where transform.childCount > 0 select ((Component)transform.GetChild(0)).GetComponent()).FirstOrDefault((Func)((PullCordDevice item) => (Object)(object)item != (Object)null)); return _cordDevice; } } public static class CrateIconProvider { private static readonly Sprite AvatarIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.AvatarIcon.png"); private static readonly Sprite CrateIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.CrateIcon.png"); private static readonly Sprite LevelIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.LevelIcon.png"); private static readonly Sprite GunIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.GunIcon.png"); private static readonly Sprite MeleeIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.MeleeIcon.png"); private static readonly Sprite ThrowableIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.ThrowableIcon.png"); private static readonly Sprite VehicleIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.VehicleIcon.png"); private static Sprite GetAvatarIcon() { return AvatarIcon; } private static Sprite GetPropIcon(ISearchableCrate searchableCrate) { return (Sprite)(searchableCrate.CrateSubType switch { CrateSubType.Gun => GunIcon, CrateSubType.Melee => MeleeIcon, CrateSubType.Throwable => ThrowableIcon, CrateSubType.Vehicle => VehicleIcon, _ => CrateIcon, }); } private static Sprite GetLevelIcon() { return LevelIcon; } public static Sprite GetIcon(ISearchableCrate searchableCrate) { return (Sprite)(searchableCrate.CrateType switch { CrateType.Avatar => GetAvatarIcon(), CrateType.Prop => GetPropIcon(searchableCrate), CrateType.Level => GetLevelIcon(), _ => CrateIcon, }); } } public static class ImageHelper { public static Texture2D LoadEmbeddedImage(string resourceName) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_0067: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown using Stream stream = typeof(ImageHelper).Assembly.GetManifestResourceStream(resourceName); if (stream == null) { throw new Exception("Resource not found: " + resourceName); } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = resourceName, hideFlags = (HideFlags)32 }; if (!ImageConversion.LoadImage(val, Il2CppStructArray.op_Implicit(array))) { throw new Exception("Failed to load image from resource: " + resourceName); } val.Apply(); return val; } public static Sprite LoadEmbeddedSprite(string resourceName) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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) Texture2D val = LoadEmbeddedImage(resourceName); Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), Vector2.one * 0.5f); ((Object)obj).hideFlags = (HideFlags)32; return obj; } } public static class SearchHelper { public static int GetSalt(this string str) { return str.Aggregate(23, (int current, char c) => current * 31 + c); } public static bool IsCrate(this Crate crate) where T : Crate { return (Object)(object)((Il2CppObjectBase)crate).TryCast() != (Object)null; } public static CrateType GetCrateType(this Crate crate) { if (crate.IsCrate()) { return CrateType.Avatar; } if (crate.IsCrate()) { return CrateType.Level; } return CrateType.Prop; } private static List GetTags(this Crate crate) { return (from tag in (IEnumerable)crate._tags._items where !string.IsNullOrEmpty(tag) select tag.ToLower()).ToList(); } private static List GetMetaDataList(this Crate crate) { List tags = crate.GetTags(); if (!string.IsNullOrEmpty(((Scannable)crate).Title)) { tags.AddRange(from p in ((Scannable)crate).Title.ToLower().Split(" ") select p.Trim()); } return tags; } private static CrateSubType GetPropSubType(this Crate crate) { List metaDataList = crate.GetMetaDataList(); if (metaDataList.Contains("gun")) { return CrateSubType.Gun; } if (metaDataList.Contains("grenade")) { return CrateSubType.Throwable; } if (metaDataList.Contains("melee")) { return CrateSubType.Melee; } if (metaDataList.Contains("vehicle")) { return CrateSubType.Vehicle; } return CrateSubType.None; } public static CrateSubType GetCrateSubType(this Crate crate, CrateType crateType) { if (crateType == CrateType.Prop) { return crate.GetPropSubType(); } return CrateSubType.None; } public static bool TryGetCrate(this ISearchableCrate crate, [MaybeNullWhen(false)] out Crate outCrate) { return AssetWarehouse.Instance.TryGetCrate(crate.Barcode, ref outCrate); } } public struct SearchTag { public string Original { get; } public string Preprocessed { get; } public SearchTag(string original) { Original = original; Preprocessed = Preprocess(original); } public int PartialRatio(string other) { return Fuzz.PartialRatio(Preprocessed, other, PreprocessMode.None); } public bool Contains(string preprocessedQuery) { return Preprocessed.Contains(preprocessedQuery); } public static string Preprocess(string value) { return StringHelper.RemoveUnityRichText(StringHelper.RemoveNonAlphanumeric(value.ToLowerInvariant())).Replace(" ", "").Trim(); } } public static class StringHelper { private static readonly Regex UnityRichTextRegex = new Regex("]*>|", RegexOptions.Compiled); private static readonly Regex NonAlphanumericRegex = new Regex("[^ a-zA-Z0-9]", RegexOptions.Compiled); public static string RemoveUnityRichText(string input) { return UnityRichTextRegex.Replace(input, string.Empty); } public static string RemoveNonAlphanumeric(string input) { return NonAlphanumericRegex.Replace(input, string.Empty); } } public static class ThreadUtils { public static void RunOnMainThread(this Action action) { MelonCoroutines.Start(InvokeOnMainThread(action)); } public static void RunOnMainThread(this Action action, T arg) { Action action2 = action; T arg2 = arg; MelonCoroutines.Start(InvokeOnMainThread(delegate { action2(arg2); })); } private static IEnumerator InvokeOnMainThread(Action action) { yield return null; action(); } } } namespace SearchThing.Search { public enum CrateType { Prop, Avatar, Level, Invalid } public enum CrateSubType { None, Gun, Melee, Throwable, Vehicle, Large, Medium, Small } public static class GlobalCrateManager { private static readonly SearchableCrateLookup SearchableCrateCrates = new SearchableCrateLookup(); public static ISearchableCrateList GetCrates() { return SearchableCrateCrates; } public static void AddPallet(Pallet pallet) { SearchableCrateCrates.AddCrates(from c in (IEnumerable)pallet._crates._items where (Object)(object)c != (Object)null select c into crate select new SearchableCrate(crate)); } public static ISearchableCrate? GetCrate(Barcode barcode) { return SearchableCrateCrates.GetCrateByBarcode(barcode._id); } } public interface ISearchableCrate { SearchTag Name { get; } SearchTag PalletName { get; } SearchTag Author { get; } SearchTag[] Tags { get; } string Description { get; } bool Redacted { get; } CrateType CrateType { get; } CrateSubType CrateSubType { get; } int Salt { get; } int Score { get; } DateTime DateAdded { get; } Barcode Barcode { get; } } public interface ISearchOrder { int Order(ISearchableCrate searchableCrate); } public class SearchableCrate : ISearchableCrate, IEquatable { public SearchTag Name { get; } public SearchTag PalletName { get; } public SearchTag Author { get; } public SearchTag[] Tags { get; } public string Description { get; } public bool Redacted { get; } public int Salt { get; } public CrateType CrateType { get; } public CrateSubType CrateSubType { get; } public virtual int Score => 0; public DateTime DateAdded { get; } public Barcode Barcode { get; } public SearchableCrate(Crate spawnableCrate) { if ((Object)(object)spawnableCrate == (Object)null) { throw new ArgumentNullException("spawnableCrate"); } Name = new SearchTag(((Object)spawnableCrate).name); Description = ((Scannable)spawnableCrate)._description; PalletName = new SearchTag(((Object)spawnableCrate._pallet).name); Author = new SearchTag(spawnableCrate._pallet._author); Tags = ((IEnumerable)spawnableCrate._tags.ToArray()).Select((string t) => new SearchTag(t)).ToArray(); Redacted = ((Scannable)spawnableCrate)._redacted; Salt = ((Object)spawnableCrate).name.GetSalt(); PalletManifest val = default(PalletManifest); if (!spawnableCrate._pallet.IsInMarrowGame() && AssetWarehouse.Instance.TryGetPalletManifest(((Scannable)spawnableCrate._pallet)._barcode, ref val)) { DateAdded = (long.TryParse(val.UpdatedDate, out var result) ? DateTime.UnixEpoch.AddMilliseconds(result) : DateTime.MinValue); } else { DateAdded = DateTime.MinValue; } CrateType = spawnableCrate.GetCrateType(); CrateSubType = spawnableCrate.GetCrateSubType(CrateType); Barcode = ((Scannable)spawnableCrate).Barcode; } public SearchableCrate(Barcode barcode) { Crate spawnableCrate = default(Crate); if (!AssetWarehouse.Instance.TryGetCrate(barcode, ref spawnableCrate)) { throw new ArgumentException($"Crate with barcode {barcode} not found in warehouse", "barcode"); } this..ctor(spawnableCrate); } public bool Equals(SearchableCrate? other) { if (other == null) { return false; } if (this == other) { return true; } return Barcode._id.Equals(other.Barcode._id); } public override bool Equals(object? obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((SearchableCrate)obj); } public override int GetHashCode() { return Barcode._id.GetHashCode(); } } public interface ISearchableCrateList where TCrate : class, ISearchableCrate { IEnumerable GetCrates(); } public class SearchableCrateList : ISearchableCrateList where TCrate : class, ISearchableCrate { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly List _crates = new List(); public void AddCrate(TCrate crate) { _lock.EnterWriteLock(); try { _crates.Add(crate); } finally { _lock.ExitWriteLock(); } } public void AddCrates(IEnumerable crates) { _lock.EnterWriteLock(); try { _crates.AddRange(crates); } finally { _lock.ExitWriteLock(); } } public IEnumerable GetCrates() { _lock.EnterReadLock(); try { return _crates.ToArray(); } finally { _lock.ExitReadLock(); } } } public record SearchableCrateListWrapper(IEnumerable Crates) : ISearchableCrateList where TCrate : class, ISearchableCrate { public IEnumerable GetCrates() { return Crates; } } public static class SearchableCrateListWrapperHelper { public static ISearchableCrateList ToSearchable(this IEnumerable crates) where TCrate : class, ISearchableCrate { return new SearchableCrateListWrapper(crates); } } public class SearchableCrateLookup : ISearchableCrateList { private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); private readonly Dictionary _lookup = new Dictionary(); private readonly List _crates = new List(); public void AddCrate(ISearchableCrate crate) { _lock.EnterWriteLock(); try { _lookup[crate.Barcode._id] = crate; _crates.Add(crate); } finally { _lock.ExitWriteLock(); } } public void AddCrates(IEnumerable crates) { _lock.EnterWriteLock(); try { foreach (ISearchableCrate crate in crates) { _lookup[crate.Barcode._id] = crate; _crates.Add(crate); } } finally { _lock.ExitWriteLock(); } } public ISearchableCrate? GetCrateByBarcode(string barcode) { _lock.EnterReadLock(); try { return _lookup.GetValueOrDefault(barcode); } finally { _lock.ExitReadLock(); } } public IEnumerable GetCrates() { _lock.EnterReadLock(); try { return _crates.ToArray(); } finally { _lock.ExitReadLock(); } } } public record ScoredCrate(ISearchableCrate Crate, int Score) : ISearchableCrate { public SearchTag Name => Crate.Name; public SearchTag PalletName => Crate.PalletName; public SearchTag Author => Crate.Author; public SearchTag[] Tags => Crate.Tags; public string Description => Crate.Description; public bool Redacted => Crate.Redacted; public CrateType CrateType => Crate.CrateType; public CrateSubType CrateSubType => Crate.CrateSubType; public int Salt => Crate.Salt; public Barcode Barcode => Crate.Barcode; public DateTime DateAdded => Crate.DateAdded; public static ScoredCrate ScoreCrate(ISearchableCrate crate, string preprocessedQuery) { return new ScoredCrate(crate, SearchManager.ScoreCrate(preprocessedQuery, crate)); } } internal interface ISearchTask { string Query { get; } ISearchableCrateList PureSourceList { get; } Func Filter { get; } ISearchOrder SearchOrder { get; } CancellationToken CancellationToken { get; } void RunAndStore(IEnumerable results); } public record SearchTask(string Query, ISearchableCrateList SourceList, Func Filter, ISearchOrder SearchOrder, Action> OnComplete, CancellationToken CancellationToken) : ISearchTask where TCrate : class, ISearchableCrate { public ISearchableCrateList PureSourceList => SourceList; public void RunAndStore(IEnumerable results) { List entries = results.OfType().ToList(); SearchResults searchResults = new SearchResults(entries); ThreadUtils.RunOnMainThread(delegate { OnComplete(searchResults); }); } } public static class SearchManager { private const int RequiredMatchRate = 75; private static readonly object SearchLock = new object(); private static CancellationTokenSource? _lastSearchCts = new CancellationTokenSource(); private static Thread? _searchThread; private static readonly BlockingCollection SearchQueue = new BlockingCollection(); private static readonly CancellationTokenSource SearchThreadCts = new CancellationTokenSource(); public static void InitializeSearchThread() { _searchThread = new Thread(SearchThreadWorker) { IsBackground = true, Name = "SearchWorker" }; _searchThread.Start(); } public static void ShutdownSearchThread() { SearchThreadCts.Cancel(); SearchQueue.CompleteAdding(); _searchThread?.Join(TimeSpan.FromMilliseconds(500.0)); } private static void SearchThreadWorker() { try { foreach (ISearchTask item in SearchQueue.GetConsumingEnumerable(SearchThreadCts.Token)) { ExecuteSearch(item); } } catch (OperationCanceledException) { } } private static void ExecuteSearch(ISearchTask task) { IEnumerable crates = task.PureSourceList.GetCrates(); try { if (string.IsNullOrWhiteSpace(task.Query) || !AssetWarehouse.ready) { OrderedParallelQuery results = crates.AsParallel().WithDegreeOfParallelism(Math.Max(1, Environment.ProcessorCount / 2)).WithCancellation(task.CancellationToken) .Where(task.Filter) .OrderByDescending(task.SearchOrder.Order) .ThenByDescending((ISearchableCrate c) => c.Salt); task.RunAndStore(results); return; } string preprocessedQuery = SearchTag.Preprocess(task.Query); ParallelQuery results2 = from c in (from crate in crates.AsParallel().WithDegreeOfParallelism(Math.Max(1, Environment.ProcessorCount / 2)).WithCancellation(task.CancellationToken) .Where(task.Filter) select new ScoredCrate(crate, ScoreCrate(preprocessedQuery, crate)) into c where c.Score >= 75 select c).OrderByDescending(task.SearchOrder.Order).ThenByDescending((ScoredCrate c) => c.Crate.Salt) select c.Crate; task.RunAndStore(results2); } catch (OperationCanceledException) { } catch (Exception ex2) { MelonLogger.Error("Search failed: {0}", ex2); } } public static void SearchAsync(string query, ISearchableCrateList crateList, Func filter, ISearchOrder searchOrder, Action> onComplete) where TCrate : class, ISearchableCrate { lock (SearchLock) { _lastSearchCts?.Cancel(); _lastSearchCts?.Dispose(); _lastSearchCts = new CancellationTokenSource(); } SearchTask item = new SearchTask(query, crateList, filter, searchOrder, onComplete, _lastSearchCts.Token); SearchQueue.Add(item); } public static int ScoreCrate(string preprocessedQuery, ISearchableCrate crate) { string preprocessedQuery2 = preprocessedQuery; int num = crate.Name.PartialRatio(preprocessedQuery2); int num2 = crate.PalletName.PartialRatio(preprocessedQuery2); int num3 = crate.Author.PartialRatio(preprocessedQuery2); int num4 = (crate.Tags.Any((SearchTag t) => t.PartialRatio(preprocessedQuery2) > 80) ? 90 : 0); return new int[4] { num, num2, num3, num4 }.Max(); } } public class SearchResults where TCrate : class, ISearchableCrate { public static SearchResults Empty { get; } = new SearchResults(new List()); private IReadOnlyList Entries { get; } public SearchResults(List entries) { Entries = entries; } private IEnumerable GetPageIterator(int start, int end) { for (int i = start; i < end; i++) { yield return Entries[i]; } } public IEnumerable GetPage(int page, int pageSize) { int num = page * pageSize; int end = Math.Min(num + pageSize, Entries.Count); if (num >= Entries.Count) { return Array.Empty(); } return GetPageIterator(num, end); } public TCrate? GetEntryAt(int index) { if (index < 0 || index >= Entries.Count) { return null; } return Entries[index]; } public TCrate? GetEntryAt(int page, int pageSize, int index) { int index2 = page * pageSize + index; return GetEntryAt(index2); } public int GetPageCount(int pageSize) { return (Entries.Count + pageSize - 1) / pageSize; } } } namespace SearchThing.Presets { public class Preset : BasicSearchPanel { private const string DefaultTag = "EMPTY"; private static readonly Sprite PresetRemoveIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.RemoveIcon.png"); private static readonly Sprite EditIcon = ImageHelper.LoadEmbeddedSprite("SearchThing.resources.EditIcon.png"); private string _tag = "EMPTY"; public override string Tag { get { if (!IsInitialized) { if (!PresetManager.IsAssignmentMode) { return "Empty Preset"; } return "Add Presset"; } return _tag; } } public override bool TagEditable => true; public bool IsInitialized { get; private set; } public override bool ResearchOnPageChange => true; public HashSet AssignedCrates { get; } = new HashSet(); public override bool HasPanelFunction => true; public override Sprite PanelFunctionIcon => EditIcon; public override bool HasItemFunction => true; public override Sprite ItemFunctionIcon => PresetRemoveIcon; public Preset() { IsInitialized = false; } public Preset(string tag) { _tag = tag; IsInitialized = true; } public override Color? GetItemFunctionHighlight(SpawnablePanelExtension extension, ISearchableCrate? crate) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return Color.red; } public override void OnItemFunction(SpawnablePanelExtension extension, ISearchableCrate crate) { PresetManager.IsAssignmentMode = false; RemoveCrate(crate); extension.RequestRefresh(); } public override Color? GetPanelFunctionHighlight(SpawnablePanelExtension extension) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return extension.IsEditing ? Color.green : Color.white; } public override void OnPanelFunction(SpawnablePanelExtension extension) { extension.SetIsEditing(!extension.IsEditing); extension.RenderFavoriteButton(); } public override Color? IsForceHighlighted(SpawnablePanelExtension extension, ISearchableCrate? selectedCrate) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (extension.IsPanelSelected(this)) { return Color.white; } if (!IsInitialized || selectedCrate == null || !AssignedCrates.Contains(selectedCrate)) { return null; } return Color.yellow; } private bool IsTagValid(string tag) { return !string.IsNullOrEmpty(tag); } public override void OnTagEdited(SpawnablePanelExtension extension, string newTag) { if (IsTagValid(newTag)) { _tag = newTag; IsInitialized = true; } } public override bool OnSelected(SpawnablePanelExtension extension) { if (!PresetManager.IsAssignmentMode) { return true; } bool flag = extension.IsPanelSelected(this); if (!IsInitialized) { _tag = $"Preset {Random.Shared.Next(1000, 9999)}"; IsInitialized = true; } ISearchableCrate selectedSpawnable = extension.GetSelectedSpawnable(); if (selectedSpawnable == null) { return false; } if (!AssignedCrates.Add(selectedSpawnable)) { AssignedCrates.Remove(selectedSpawnable); } PresetManager.IsAssignmentMode = false; if (flag) { extension.RequestRefresh(); } else { extension.RenderTags(); extension.RenderFavoriteButton(); } return false; } public void RemoveCrate(ISearchableCrate crate) { AssignedCrates.Remove(crate); } protected override void Search(string query, ISearchOrder order, Action> callback) { SearchManager.SearchAsync(query, AssignedCrates.ToSearchable(), (ISearchableCrate _) => true, order, callback); } public PresetData ToData() { return new PresetData { Name = _tag, Barcodes = AssignedCrates.Select((ISearchableCrate b) => b.Barcode._id).ToList() }; } public void FromData(PresetData data) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown _tag = data.Name; IsInitialized = _tag != "EMPTY"; if (!IsInitialized) { return; } AssignedCrates.Clear(); foreach (string barcode in data.Barcodes) { AssignedCrates.Add(new SearchableCrate(new Barcode(barcode))); } } } public static class PresetManager { private static readonly string FilePath; private const int MaxPresets = 6; private static bool _assignmentMode; private static readonly List PresetPages; public static bool IsAssignmentMode { get { return _assignmentMode; } set { if (!value) { SavePresets(); } _assignmentMode = value; } } static PresetManager() { FilePath = MelonEnvironment.UserDataDirectory + "/SearchThingPresets.json"; PresetPages = new List(); for (int i = 0; i < 6; i++) { PresetPages.Add(new PresetPage()); } } public static IReadOnlyList GetPages() { return PresetPages; } public static void LoadPresets() { if (!File.Exists(FilePath)) { return; } try { List list = JsonSerializer.Deserialize>(File.ReadAllText(FilePath)); if (list == null) { return; } for (int i = 0; i < list.Count; i++) { PresetPageData data = list[i]; if (i < PresetPages.Count) { PresetPages[i].FromData(data); continue; } break; } } catch (Exception ex) { MelonLogger.Error("Failed to load presets from file!", ex); } } public static void SavePresets() { try { string contents = JsonSerializer.Serialize(PresetPages.Select((PresetPage p) => p.ToData()).ToList(), new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(FilePath, contents); } catch (Exception ex) { MelonLogger.Error("Failed to save presets to file!", ex); } } } public class PresetPage : ISearchPage { public const int MaxPresets = 6; private readonly List _presets = new List(); public bool IsVisible => true; public IReadOnlyList Panels => _presets; public PresetPage() { for (int i = 0; i < 6; i++) { _presets.Add(new Preset()); } } public PresetPageData ToData() { return new PresetPageData { Presets = _presets.Select((Preset p) => p.ToData()).ToList() }; } public void FromData(PresetPageData data) { for (int i = 0; i < 6; i++) { _presets[i].FromData(data.Presets[i]); } } } } namespace SearchThing.Presets.Data { public class PresetData { public string Name { get; set; } = "New Preset"; public List Barcodes { get; set; } = new List(); } public class PresetPageData { public List Presets { get; set; } = new List(); } } namespace SearchThing.Patches { [HarmonyPatch(typeof(AssetWarehouse))] public static class AssetWarehousePatches { [HarmonyPatch("AddPallet")] [HarmonyPostfix] public static void AddPallet_Postfix(Pallet pallet) { GlobalCrateManager.AddPallet(pallet); } } [HarmonyPatch(typeof(PopUpMenuView))] public static class PopupMenuViewPatches { [HarmonyPatch("Activate")] [HarmonyPrefix] public static void Activate_Prefix(PopUpMenuView __instance) { if (!((Object)(object)__instance == (Object)null)) { __instance.AddSpawnMenu(); } } [HarmonyPatch("RemoveSpawnMenu")] [HarmonyPrefix] public static bool RemoveSpawnMenu_Prefix(PopUpMenuView __instance) { return false; } } [HarmonyPatch(typeof(RigManager))] public static class RigManagerPatches { private static bool CheckIfLocalRig(RigManager rigManager) { return FusionPlayer.IsLocalPlayer(rigManager); } [HarmonyPatch("SwapAvatarCrate")] [HarmonyPostfix] public static void SwapAvatarCrate_Prefix(RigManager __instance, Barcode barcode) { Crate crate = default(Crate); if (!((Object)(object)__instance == (Object)null) && (!Mod.IsFusionLoaded || CheckIfLocalRig(__instance)) && AssetWarehouse.ready && AssetWarehouse.Instance.TryGetCrate(barcode, ref crate)) { HistoryManager.AddEntry(crate); } } } [HarmonyPatch(typeof(SpawnGun))] public class SpawnGunPatches { private static SpawnableCrate? _selectedCrate; private static SpawnGun? HeldSpawnGun { get; set; } public static void SelectCrate(SpawnableCrate crate) { _selectedCrate = crate; if ((Object)(object)HeldSpawnGun != (Object)null) { HeldSpawnGun.OnSpawnableSelected(_selectedCrate); } } public static void ClearSelectedCrate() { _selectedCrate = null; } private static bool IsHeldByLocalPlayer(SpawnGun spawnGun) { if (!NetworkSceneManager.IsLevelNetworked) { return true; } InteractableHost host = ((Gun)spawnGun).host; if ((Object)(object)host == (Object)null) { return false; } Hand hand = host.GetHand(0); RigManager val = ((hand != null) ? hand.manager : null); if ((Object)(object)val != (Object)null) { return FusionPlayer.IsLocalPlayer(val); } return false; } [HarmonyPatch("OnTriggerGripAttached")] [HarmonyPostfix] public static void OnTriggerGripAttached_Prefix(SpawnGun __instance, Hand hand) { if (!((Object)(object)__instance == (Object)null) && (!Mod.IsFusionLoaded || IsHeldByLocalPlayer(__instance))) { HeldSpawnGun = __instance; if ((Object)(object)_selectedCrate != (Object)null) { __instance.OnSpawnableSelected(_selectedCrate); } } } [HarmonyPatch("OnTriggerGripDetached")] [HarmonyPostfix] public static void OnTriggerGripDetached_Prefix(SpawnGun __instance, Hand hand) { if (!((Object)(object)__instance == (Object)null) && (!Mod.IsFusionLoaded || IsHeldByLocalPlayer(__instance))) { HeldSpawnGun = null; } } [HarmonyPatch("OnFire")] [HarmonyPrefix] [HarmonyPriority(800)] public static void OnFire_Prefix(SpawnGun __instance) { if (!((Object)(object)__instance == (Object)null) && (!Mod.IsFusionLoaded || IsHeldByLocalPlayer(__instance))) { SpawnableCrate selectedCrate = __instance._selectedCrate; if (!((Object)(object)selectedCrate == (Object)null)) { HistoryManager.AddEntry((Crate)(object)selectedCrate); } } } [HarmonyPatch("OnSpawnableSelected")] [HarmonyPrefix] public static bool OnSpawnableSelected_Prefix(SpawnGun __instance, SpawnableCrate crate) { if ((Object)(object)__instance == (Object)null) { return true; } if (Mod.IsFusionLoaded && !IsHeldByLocalPlayer(__instance)) { return true; } _selectedCrate = crate; if ((Object)(object)((Il2CppObjectBase)crate).TryCast() == (Object)null) { return true; } return false; } } [HarmonyPatch(typeof(SpawnablesPanelView))] public static class ToolUiPatches { private static SpawnGun? _DummySpawnGun; private static SpawnGun GetDummySpawnGun() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_DummySpawnGun != (Object)null) { return _DummySpawnGun; } GameObject val = new GameObject("DummySpawnGun"); val.SetActive(false); _DummySpawnGun = val.AddComponent(); return _DummySpawnGun; } [HarmonyPatch("Activate")] [HarmonyPrefix] public static void SpawnablesPanelView_Activate_Prefix(SpawnablesPanelView __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.spawnGun != (Object)null)) { __instance.spawnGun = GetDummySpawnGun(); __instance._spawnGun_k__BackingField = GetDummySpawnGun(); } } [HarmonyPatch("Activate")] [HarmonyPostfix] public static void SpawnablesPanelView_Activate_Postfix(SpawnablesPanelView __instance) { if (!((Object)(object)__instance == (Object)null)) { SpawnablesPanelManager.Load(__instance); } } [HarmonyPatch("SelectTab")] [HarmonyPostfix] public static void SpawnablesPanelView_SelectTab_Postfix(SpawnablesPanelView __instance, int idx) { if (!((Object)(object)__instance == (Object)null)) { SpawnablesPanelManager.OnTabSelected(__instance, idx); } } [HarmonyPatch("NextPage")] [HarmonyPrefix] public static bool NextPage_Prefix(SpawnablesPanelView __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.ChangePanelPage(1); return false; } [HarmonyPatch("PrevPage")] [HarmonyPrefix] public static bool PrevPage_Prefix(SpawnablesPanelView __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.ChangePanelPage(-1); return false; } [HarmonyPatch("SelectItem")] [HarmonyPrefix] public static bool SelectItem_Prefix(SpawnablesPanelView __instance, int idx) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.OnSelectItem(idx); return false; } [HarmonyPatch("SelectCategory")] [HarmonyPrefix] public static bool SelectCategory_Prefix(SpawnablesPanelView __instance, int idx) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.SelectCategory(idx); return false; } [HarmonyPatch("NextTagPage")] [HarmonyPrefix] private static bool NextTagPage_Prefix(SpawnablesPanelView __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.ChangeTagPage(1); return false; } [HarmonyPatch("PrevTagPage")] [HarmonyPrefix] private static bool PrevTagPage_Prefix(SpawnablesPanelView __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.ChangeTagPage(-1); return false; } [HarmonyPatch("SwapSortButton")] [HarmonyPrefix] public static bool SwapSortButton_Prefix(SpawnablesPanelView __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.SwapSortButton(); return false; } [HarmonyPatch("FavoriteItem")] [HarmonyPrefix] public static bool FavoriteItem_Prefix(SpawnablesPanelView __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (!SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension)) { return true; } if (!extension.IsSearchActive()) { return true; } extension.OnFavoriteButton(); return false; } [HarmonyPatch("SelectItem")] [HarmonyPostfix] public static void SelectItem_Postfix(SpawnablesPanelView __instance, int idx) { if (!((Object)(object)__instance == (Object)null) && SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension) && extension.IsSearchActive()) { extension.RenderFavoriteButton(); } } [HarmonyPatch("SelectItem")] [HarmonyPrefix] public static void ToggleFavorite_Prefix(SpawnablesPanelView __instance, int idx) { if (!((Object)(object)__instance == (Object)null) && SpawnablesPanelManager.TryGet(__instance, out SpawnablePanelExtension extension) && extension.IsSearchActive()) { extension.RenderFavoriteButton(); } } } } namespace SearchThing.Patches.Compatibility { public static class FusionNetworkSpawnPatch { private static class FusionIntegrationPatches { [HarmonyPostfix] public static void RegisterSpawnHistory(ReceivedMessage received) { if (NetworkInfo.HasServer) { SerializedSpawnData val = ((ReceivedMessage)(ref received)).ReadData(); if (val != null) { FusionSpawnHistory.AddEntry(val.Barcode, ((ReceivedMessage)(ref received)).Sender); } } } } private static void TryPatch() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown try { Harmony val = new Harmony("com.mash.searchthing.fusionspawnhistoryintegration"); MethodInfo method = typeof(SpawnRequestMessage).GetMethod("OnHandleMessage", BindingFlags.Instance | BindingFlags.NonPublic); if (method == null) { MelonLogger.Warning("MIDT integration: Could not find OnSpawnDelegateFusion method"); return; } MethodInfo method2 = typeof(FusionIntegrationPatches).GetMethod("RegisterSpawnHistory"); if (val.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null) != null) { MelonLogger.Msg("Fusion integration: Successfully applied RegisterSpawnHistory patch"); } else { MelonLogger.Warning("Fusion integration: Failed to apply patch, harmony.Patch returned null"); } } catch (Exception value) { MelonLogger.Error($"Failed to patch MIDT integration: {value}"); } } public static void TryInitialize() { if (Mod.IsFusionLoaded) { TryPatch(); } } } } namespace SearchThing.Keyboard { public class Keyboard { private static readonly Vector2 KeySize = new Vector2(80f, 80f); private static readonly Vector2 KeySpacing = new Vector2(10f, 10f); private const int KeyboardLayer = 5; private static readonly string[] KeyRows = new string[4] { "1234567890", "QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM" }; private readonly GameObject _parent; private GameObject? _keyboardRoot; private string _text = ""; private readonly TMP_FontAsset _font; private readonly Sprite _bg; private readonly Sprite _outlineBg; public event Action? OnTextChanged; public Keyboard(GameObject parent, ButtonReferenceHolder resources) { _parent = parent; _font = ((TMP_Text)resources.tmp).font; _bg = resources.background.sprite; _outlineBg = resources.highlight.sprite; Create(); } public void SetText(string text, bool triggerEvent = true) { _text = text; if (triggerEvent) { OnInternalTextChanged(_text); } } private void Create() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: 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_010a: Unknown result type (might be due to invalid IL or missing references) _keyboardRoot = new GameObject("Keyboard"); _keyboardRoot.transform.SetParent(_parent.transform, false); _keyboardRoot.transform.localPosition = new Vector3(-85f, -450f, 0f); _keyboardRoot.transform.localRotation = Quaternion.Euler(0f, 0f, 0f); _keyboardRoot.layer = 5; _keyboardRoot.AddComponent(); ((Graphic)_keyboardRoot.AddComponent()).color = Color.clear; RectTransform component = _keyboardRoot.GetComponent(); component.anchorMin = new Vector2(0f, 0f); component.anchorMax = new Vector2(1f, 1f); component.pivot = new Vector2(0.5f, 0.5f); component.offsetMin = new Vector2(10f, 10f); component.offsetMax = new Vector2(-10f, -10f); CreateKeys(); } private void CreateKey(string text, Vector2 position, Vector2 size, Action action, Vector2? margin = null) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0054: 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_006b: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: 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_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: 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_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011f: 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_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024a: 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_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_keyboardRoot == (Object)null) { throw new InvalidOperationException("Keyboard root is not initialized."); } GameObject val = new GameObject("Key_" + text); val.transform.SetParent(_keyboardRoot.transform, false); RectTransform val2 = val.AddComponent(); val.AddComponent(); GameObject val3 = new GameObject("bg"); val3.transform.SetParent(val.transform, false); val3.AddComponent().sizeDelta = size; Image val4 = val3.AddComponent(); val4.sprite = _bg; val4.type = (Type)1; GameObject val5 = new GameObject("image_backline"); val5.transform.SetParent(val.transform, false); val5.AddComponent().sizeDelta = size; Image obj = val5.AddComponent(); obj.sprite = _outlineBg; obj.type = (Type)1; ((Graphic)obj).raycastTarget = false; obj.fillCenter = false; GameObject val6 = new GameObject("Collider"); val6.transform.SetParent(val.transform, false); BoxCollider obj2 = val6.AddComponent(); Vector2 val7 = size + margin.GetValueOrDefault(); obj2.size = new Vector3(val7.x, val7.y, 1f); ((Collider)obj2).isTrigger = true; Button obj3 = val.AddComponent