Decompiled source of SpawnLab v1.0.1

Mods/SpawnLab.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.Json;
using BoneLib;
using BoneLib.BoneMenu;
using Il2CppSLZ.Marrow.Data;
using Il2CppSLZ.Marrow.Pool;
using LabFusion.Entities;
using LabFusion.Marrow.Pool;
using LabFusion.RPC;
using LabFusion.Scene;
using MelonLoader;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using SpawnLab;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(SpawnLabMod), "SpawnLab", "1.0.0", "ChappieStudios", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("SpawnLab")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SpawnLab")]
[assembly: AssemblyTitle("SpawnLab")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SpawnLab
{
	public sealed class SpawnLabMod : MelonMod
	{
		private readonly struct PalletFile
		{
			public string Path { get; }

			public bool IsDownloaded { get; }

			public PalletFile(string path, bool isDownloaded)
			{
				Path = path;
				IsDownloaded = isDownloaded;
			}
		}

		private readonly struct SpawnEntry
		{
			public string Title { get; }

			public string Barcode { get; }

			public string SourceName { get; }

			public bool IsDownloaded { get; }

			public SpawnCategory Category { get; }

			public SpawnEntry(string title, string barcode, string sourceName, bool isDownloaded, SpawnCategory category)
			{
				Title = title;
				Barcode = barcode;
				SourceName = sourceName;
				IsDownloaded = isDownloaded;
				Category = category;
			}
		}

		private enum SpawnCategory
		{
			Guns,
			Melee,
			NPCs,
			Vehicles,
			Gadgets,
			Money,
			Props,
			Misc
		}

		[CompilerGenerated]
		private sealed class <FindPalletFiles>d__12 : IEnumerable<PalletFile>, IEnumerable, IEnumerator<PalletFile>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private PalletFile <>2__current;

			private int <>l__initialThreadId;

			private IEnumerator<string> <>7__wrap1;

			PalletFile IEnumerator<PalletFile>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <FindPalletFiles>d__12(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				switch (<>1__state)
				{
				case -3:
				case 1:
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
					break;
				case -4:
				case 2:
					try
					{
					}
					finally
					{
						<>m__Finally2();
					}
					break;
				}
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					string path2;
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						string path = Path.Combine(Application.persistentDataPath, "Mods");
						if (Directory.Exists(path))
						{
							<>7__wrap1 = Directory.EnumerateFiles(path, "*.pallet.json", SearchOption.AllDirectories).GetEnumerator();
							<>1__state = -3;
							goto IL_0091;
						}
						goto IL_00ab;
					}
					case 1:
						<>1__state = -3;
						goto IL_0091;
					case 2:
						{
							<>1__state = -4;
							goto IL_011c;
						}
						IL_00ab:
						path2 = Path.Combine(MelonEnvironment.UnityGameDataDirectory, "StreamingAssets", "aa", "StandaloneWindows64");
						if (!Directory.Exists(path2))
						{
							break;
						}
						<>7__wrap1 = Directory.EnumerateFiles(path2, "*.pallet.json", SearchOption.TopDirectoryOnly).GetEnumerator();
						<>1__state = -4;
						goto IL_011c;
						IL_0091:
						if (<>7__wrap1.MoveNext())
						{
							string current = <>7__wrap1.Current;
							<>2__current = new PalletFile(current, isDownloaded: true);
							<>1__state = 1;
							return true;
						}
						<>m__Finally1();
						<>7__wrap1 = null;
						goto IL_00ab;
						IL_011c:
						if (<>7__wrap1.MoveNext())
						{
							string current2 = <>7__wrap1.Current;
							<>2__current = new PalletFile(current2, isDownloaded: false);
							<>1__state = 2;
							return true;
						}
						<>m__Finally2();
						<>7__wrap1 = null;
						break;
					}
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<>7__wrap1 != null)
				{
					<>7__wrap1.Dispose();
				}
			}

			private void <>m__Finally2()
			{
				<>1__state = -1;
				if (<>7__wrap1 != null)
				{
					<>7__wrap1.Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<PalletFile> IEnumerable<PalletFile>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <FindPalletFiles>d__12(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<PalletFile>)this).GetEnumerator();
			}
		}

		private const float SpawnDistance = 1.25f;

		private const int MaxMenuNameLength = 48;

		private static readonly SpawnCategory[] CategoryOrder = new SpawnCategory[8]
		{
			SpawnCategory.Guns,
			SpawnCategory.Melee,
			SpawnCategory.NPCs,
			SpawnCategory.Vehicles,
			SpawnCategory.Gadgets,
			SpawnCategory.Money,
			SpawnCategory.Props,
			SpawnCategory.Misc
		};

		private Page _rootPage;

		private List<SpawnEntry> _entries = new List<SpawnEntry>();

		public override void OnInitializeMelon()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			_rootPage = Page.Root.CreatePage("SpawnLab", Color.cyan, 0, true);
			RebuildMenu();
			((MelonBase)this).LoggerInstance.Msg("SpawnLab loaded.");
		}

		private void RebuildMenu()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			_entries = LoadSpawnEntries();
			_rootPage.RemoveAll();
			_rootPage.CreateFunction($"Refresh List ({_entries.Count})", Color.yellow, (Action)RebuildMenu);
			if (_entries.Count == 0)
			{
				_rootPage.CreateFunction("No Spawnables Found", Color.red, (Action)delegate
				{
				});
				return;
			}
			Page parentPage = _rootPage.CreatePage("All Categories", Color.cyan, 0, true);
			AddCategoryPages(parentPage, _entries);
			Page parentPage2 = _rootPage.CreatePage("Downloaded Mods", Color.green, 0, true);
			AddSourcePages(parentPage2, _entries.Where((SpawnEntry entry) => entry.IsDownloaded));
			Page parentPage3 = _rootPage.CreatePage("Base Game", Color.blue, 0, true);
			AddSourcePages(parentPage3, _entries.Where((SpawnEntry entry) => !entry.IsDownloaded));
		}

		private void AddSourcePages(Page parentPage, IEnumerable<SpawnEntry> entries)
		{
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			foreach (IGrouping<string, SpawnEntry> item in from entry in entries
				group entry by entry.SourceName into @group
				orderby @group.Key
				select @group)
			{
				List<SpawnEntry> list = item.OrderBy((SpawnEntry entry) => entry.Title).ToList();
				if (list.Count != 0)
				{
					Page parentPage2 = parentPage.CreatePage(TrimMenuName($"{item.Key} ({list.Count})"), Color.white, 0, true);
					AddCategoryPages(parentPage2, list);
				}
			}
		}

		private void AddCategoryPages(Page parentPage, IEnumerable<SpawnEntry> entries)
		{
			//IL_00e6: 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)
			List<SpawnEntry> source = entries.ToList();
			SpawnCategory[] categoryOrder = CategoryOrder;
			foreach (SpawnCategory category in categoryOrder)
			{
				List<SpawnEntry> list = (from entry in source
					where entry.Category == category
					orderby entry.Title, entry.SourceName
					select entry).ToList();
				if (list.Count == 0)
				{
					continue;
				}
				Page val = parentPage.CreatePage($"{GetCategoryName(category)} ({list.Count})", GetCategoryColor(category), 0, true);
				foreach (SpawnEntry item in list)
				{
					SpawnEntry capturedEntry = item;
					val.CreateFunction(TrimMenuName(capturedEntry.Title), Color.white, (Action)delegate
					{
						Spawn(capturedEntry);
					});
				}
			}
		}

		private void Spawn(SpawnEntry entry)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Spawnable val = LocalAssetSpawner.CreateSpawnable(entry.Barcode);
				GetSpawnTransform(out var position, out var rotation);
				if (NetworkSceneManager.IsLevelNetworked)
				{
					SpawnRequestInfo val2 = default(SpawnRequestInfo);
					val2.Spawnable = val;
					val2.Position = position;
					val2.Rotation = rotation;
					val2.SpawnEffect = true;
					val2.SpawnSource = (EntitySource)2;
					NetworkAssetSpawner.Spawn(val2);
				}
				else
				{
					LocalAssetSpawner.Register(val);
					LocalAssetSpawner.Spawn(val, position, rotation, (Action<Poolee>)null);
				}
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Error("Could not spawn " + entry.Title + ": " + ex.Message);
			}
		}

		private static void GetSpawnTransform(out Vector3 position, out Quaternion rotation)
		{
			//IL_0057: 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_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_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//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_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			Transform head = Player.Head;
			Transform val = (((Object)(object)Player.RigManager != (Object)null) ? ((Component)Player.RigManager).transform : null);
			Vector3 val2 = (((Object)(object)head != (Object)null) ? head.position : (((Object)(object)val != (Object)null) ? (val.position + Vector3.up * 1.5f) : Vector3.zero));
			Vector3 val3 = (((Object)(object)head != (Object)null) ? head.forward : (((Object)(object)val != (Object)null) ? val.forward : Vector3.forward));
			val3.y = 0f;
			if (((Vector3)(ref val3)).sqrMagnitude < 0.001f)
			{
				val3 = Vector3.forward;
			}
			((Vector3)(ref val3)).Normalize();
			position = val2 + val3 * 1.25f + Vector3.down * 0.12f;
			rotation = Quaternion.LookRotation(val3, Vector3.up);
		}

		private List<SpawnEntry> LoadSpawnEntries()
		{
			Dictionary<string, SpawnEntry> dictionary = new Dictionary<string, SpawnEntry>(StringComparer.OrdinalIgnoreCase);
			foreach (PalletFile item in FindPalletFiles())
			{
				try
				{
					ReadPalletFile(item, dictionary);
				}
				catch (Exception ex)
				{
					((MelonBase)this).LoggerInstance.Warning("Could not read pallet " + item.Path + ": " + ex.Message);
				}
			}
			return (from entry in dictionary.Values
				orderby (!entry.IsDownloaded) ? 1 : 0, entry.SourceName, entry.Category, entry.Title
				select entry).ToList();
		}

		[IteratorStateMachine(typeof(<FindPalletFiles>d__12))]
		private IEnumerable<PalletFile> FindPalletFiles()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <FindPalletFiles>d__12(-2);
		}

		private static void ReadPalletFile(PalletFile palletFile, Dictionary<string, SpawnEntry> entries)
		{
			using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(palletFile.Path));
			JsonElement rootElement = jsonDocument.RootElement;
			if (!rootElement.TryGetProperty("objects", out var value) || value.ValueKind != JsonValueKind.Object)
			{
				return;
			}
			string palletTitle = GetPalletTitle(rootElement, value, palletFile.Path);
			foreach (JsonProperty item in value.EnumerateObject())
			{
				JsonElement value2 = item.Value;
				if (!IsSpawnableCrate(value2))
				{
					continue;
				}
				string @string = GetString(value2, "barcode");
				if (!string.IsNullOrWhiteSpace(@string) && !entries.ContainsKey(@string))
				{
					string text = GetString(value2, "title");
					if (string.IsNullOrWhiteSpace(text))
					{
						text = @string;
					}
					List<string> tags = GetTags(value2);
					SpawnCategory category = Categorize(text, @string, tags);
					entries.Add(@string, new SpawnEntry(text, @string, palletTitle, palletFile.IsDownloaded, category));
				}
			}
		}

		private static string GetPalletTitle(JsonElement root, JsonElement objects, string path)
		{
			string text = null;
			if (root.TryGetProperty("root", out var value) && value.TryGetProperty("ref", out var value2) && value2.ValueKind == JsonValueKind.String)
			{
				text = value2.GetString();
			}
			if (!string.IsNullOrWhiteSpace(text) && objects.TryGetProperty(text, out var value3) && value3.TryGetProperty("title", out var value4) && value4.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(value4.GetString()))
			{
				return value4.GetString();
			}
			return Path.GetFileNameWithoutExtension(path).Replace(".pallet", string.Empty);
		}

		private static bool IsSpawnableCrate(JsonElement crate)
		{
			if (!crate.TryGetProperty("isa", out var value) || !value.TryGetProperty("type", out var value2))
			{
				return false;
			}
			if (value2.ValueKind == JsonValueKind.String)
			{
				return value2.GetString() == "crate-spawnable#0";
			}
			return false;
		}

		private static string GetString(JsonElement element, string propertyName)
		{
			if (element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String)
			{
				return value.GetString();
			}
			return null;
		}

		private static List<string> GetTags(JsonElement crate)
		{
			List<string> list = new List<string>();
			if (!crate.TryGetProperty("tags", out var value) || value.ValueKind != JsonValueKind.Array)
			{
				return list;
			}
			foreach (JsonElement item in value.EnumerateArray())
			{
				if (item.ValueKind == JsonValueKind.String)
				{
					list.Add(item.GetString());
				}
			}
			return list;
		}

		private static SpawnCategory Categorize(string title, string barcode, List<string> tags)
		{
			string text = $"{title} {barcode} {string.Join(" ", tags)}".ToLowerInvariant();
			if (ContainsAny(text, "money", "dollar", "bill", "coin", "penny", "nickel", "dime", "quarter", "wallet", "credit card"))
			{
				return SpawnCategory.Money;
			}
			if (ContainsAny(text, "npc", "enemy", "ford", "nullbody", "crablet", "skeleton", "zombie", "omni", "security", "agent", "peasant", "cultist"))
			{
				return SpawnCategory.NPCs;
			}
			if (ContainsAny(text, "rifle", "pistol", "gun", "shotgun", "smg", "revolver", "launcher", "magazine", "ammo", "cartridge"))
			{
				return SpawnCategory.Guns;
			}
			if (ContainsAny(text, "sword", "knife", "blade", "axe", "bat", "crowbar", "dagger", "spear", "hammer", "wrench", "mace", "katana"))
			{
				return SpawnCategory.Melee;
			}
			if (ContainsAny(text, "vehicle", "car", "kart", "truck", "taxi", "hover", "board", "bike", "plane"))
			{
				return SpawnCategory.Vehicles;
			}
			if (ContainsAny(text, "tool", "gadget", "utility", "nimbus", "spawn gun", "constrainer", "dev", "capsule", "button"))
			{
				return SpawnCategory.Gadgets;
			}
			if (ContainsAny(text, "prop", "crate", "box", "barrel", "table", "chair", "door", "ball", "target"))
			{
				return SpawnCategory.Props;
			}
			return SpawnCategory.Misc;
		}

		private static bool ContainsAny(string text, params string[] values)
		{
			foreach (string value in values)
			{
				if (text.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		private static string GetCategoryName(SpawnCategory category)
		{
			return category switch
			{
				SpawnCategory.Guns => "Guns", 
				SpawnCategory.Melee => "Melee", 
				SpawnCategory.NPCs => "NPCs", 
				SpawnCategory.Vehicles => "Vehicles", 
				SpawnCategory.Gadgets => "Gadgets", 
				SpawnCategory.Money => "Money", 
				SpawnCategory.Props => "Props", 
				_ => "Misc", 
			};
		}

		private static Color GetCategoryColor(SpawnCategory category)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: 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_004b: 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_0057: 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)
			return (Color)(category switch
			{
				SpawnCategory.Guns => Color.red, 
				SpawnCategory.Melee => new Color(1f, 0.45f, 0f), 
				SpawnCategory.NPCs => Color.magenta, 
				SpawnCategory.Vehicles => Color.cyan, 
				SpawnCategory.Gadgets => Color.yellow, 
				SpawnCategory.Money => Color.green, 
				SpawnCategory.Props => Color.white, 
				_ => Color.gray, 
			});
		}

		private static string TrimMenuName(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "Unnamed";
			}
			value = value.Replace("\r", " ").Replace("\n", " ").Trim();
			if (value.Length > 48)
			{
				return value.Substring(0, 45) + "...";
			}
			return value;
		}
	}
}